Allow waiting for language to be loaded in LanguageRegistry APIs

This commit is contained in:
Antonio Scandurra 2023-03-10 12:17:47 +01:00
parent 221bb54e48
commit 7a600e7a65
7 changed files with 201 additions and 126 deletions

View file

@ -1,3 +1,4 @@
use futures::FutureExt;
use gpui::{ use gpui::{
actions, actions,
elements::{Flex, MouseEventHandler, Padding, Text}, elements::{Flex, MouseEventHandler, Padding, Text},
@ -327,12 +328,10 @@ impl InfoPopover {
MouseEventHandler::<InfoPopover>::new(0, cx, |_, cx| { MouseEventHandler::<InfoPopover>::new(0, cx, |_, cx| {
let mut flex = Flex::new(Axis::Vertical).scrollable::<HoverBlock, _>(1, None, cx); let mut flex = Flex::new(Axis::Vertical).scrollable::<HoverBlock, _>(1, None, cx);
flex.extend(self.contents.iter().map(|content| { flex.extend(self.contents.iter().map(|content| {
let project = self.project.read(cx); let languages = self.project.read(cx).languages();
if let Some(language) = content if let Some(language) = content.language.clone().and_then(|language| {
.language languages.language_for_name(&language).now_or_never()?.ok()
.clone() }) {
.and_then(|language| project.languages().language_for_name(&language))
{
let runs = language let runs = language
.highlight_text(&content.text.as_str().into(), 0..content.text.len()); .highlight_text(&content.text.as_str().into(), 0..content.text.len());

View file

@ -20,6 +20,7 @@ use postage::prelude::Stream;
use project::Project; use project::Project;
use serde::Serialize; use serde::Serialize;
use util::ResultExt;
use workspace::{ use workspace::{
item::{Item, ItemHandle}, item::{Item, ItemHandle},
searchable::{SearchableItem, SearchableItemHandle}, searchable::{SearchableItem, SearchableItemHandle},
@ -200,24 +201,28 @@ impl FeedbackEditor {
impl FeedbackEditor { impl FeedbackEditor {
pub fn deploy( pub fn deploy(
system_specs: SystemSpecs, system_specs: SystemSpecs,
workspace: &mut Workspace, _: &mut Workspace,
app_state: Arc<AppState>, app_state: Arc<AppState>,
cx: &mut ViewContext<Workspace>, cx: &mut ViewContext<Workspace>,
) { ) {
workspace let markdown = app_state.languages.language_for_name("Markdown");
.with_local_workspace(&app_state, cx, |workspace, cx| { cx.spawn(|workspace, mut cx| async move {
let project = workspace.project().clone(); let markdown = markdown.await.log_err();
let markdown_language = project.read(cx).languages().language_for_name("Markdown"); workspace
let buffer = project .update(&mut cx, |workspace, cx| {
.update(cx, |project, cx| { workspace.with_local_workspace(&app_state, cx, |workspace, cx| {
project.create_buffer("", markdown_language, cx) let project = workspace.project().clone();
let buffer = project
.update(cx, |project, cx| project.create_buffer("", markdown, cx))
.expect("creating buffers on a local workspace always succeeds");
let feedback_editor = cx
.add_view(|cx| FeedbackEditor::new(system_specs, project, buffer, cx));
workspace.add_item(Box::new(feedback_editor), cx);
}) })
.expect("creating buffers on a local workspace always succeeds"); })
let feedback_editor = .await;
cx.add_view(|cx| FeedbackEditor::new(system_specs, project, buffer, cx)); })
workspace.add_item(Box::new(feedback_editor), cx); .detach();
})
.detach();
} }
} }

View file

@ -80,31 +80,49 @@ fn test_select_language() {
// matching file extension // matching file extension
assert_eq!( assert_eq!(
registry.language_for_path("zed/lib.rs").map(|l| l.name()), registry
.language_for_path("zed/lib.rs")
.now_or_never()
.and_then(|l| Some(l.ok()?.name())),
Some("Rust".into()) Some("Rust".into())
); );
assert_eq!( assert_eq!(
registry.language_for_path("zed/lib.mk").map(|l| l.name()), registry
.language_for_path("zed/lib.mk")
.now_or_never()
.and_then(|l| Some(l.ok()?.name())),
Some("Make".into()) Some("Make".into())
); );
// matching filename // matching filename
assert_eq!( assert_eq!(
registry.language_for_path("zed/Makefile").map(|l| l.name()), registry
.language_for_path("zed/Makefile")
.now_or_never()
.and_then(|l| Some(l.ok()?.name())),
Some("Make".into()) Some("Make".into())
); );
// matching suffix that is not the full file extension or filename // matching suffix that is not the full file extension or filename
assert_eq!( assert_eq!(
registry.language_for_path("zed/cars").map(|l| l.name()), registry
.language_for_path("zed/cars")
.now_or_never()
.and_then(|l| Some(l.ok()?.name())),
None None
); );
assert_eq!( assert_eq!(
registry.language_for_path("zed/a.cars").map(|l| l.name()), registry
.language_for_path("zed/a.cars")
.now_or_never()
.and_then(|l| Some(l.ok()?.name())),
None None
); );
assert_eq!( assert_eq!(
registry.language_for_path("zed/sumk").map(|l| l.name()), registry
.language_for_path("zed/sumk")
.now_or_never()
.and_then(|l| Some(l.ok()?.name())),
None None
); );
} }

View file

@ -13,8 +13,9 @@ use async_trait::async_trait;
use client::http::HttpClient; use client::http::HttpClient;
use collections::HashMap; use collections::HashMap;
use futures::{ use futures::{
channel::oneshot,
future::{BoxFuture, Shared}, future::{BoxFuture, Shared},
FutureExt, TryFutureExt, FutureExt, TryFutureExt as _,
}; };
use gpui::{executor::Background, MutableAppContext, Task}; use gpui::{executor::Background, MutableAppContext, Task};
use highlight_map::HighlightMap; use highlight_map::HighlightMap;
@ -43,7 +44,7 @@ use syntax_map::SyntaxSnapshot;
use theme::{SyntaxTheme, Theme}; use theme::{SyntaxTheme, Theme};
use tree_sitter::{self, Query}; use tree_sitter::{self, Query};
use unicase::UniCase; use unicase::UniCase;
use util::ResultExt; use util::{ResultExt, TryFutureExt as _, UnwrapFuture};
#[cfg(any(test, feature = "test-support"))] #[cfg(any(test, feature = "test-support"))]
use futures::channel::mpsc; use futures::channel::mpsc;
@ -484,7 +485,7 @@ impl LanguageRegistry {
let (lsp_binary_statuses_tx, lsp_binary_statuses_rx) = async_broadcast::broadcast(16); let (lsp_binary_statuses_tx, lsp_binary_statuses_rx) = async_broadcast::broadcast(16);
Self { Self {
language_server_download_dir: None, language_server_download_dir: None,
languages: Default::default(), languages: RwLock::new(vec![PLAIN_TEXT.clone()]),
available_languages: Default::default(), available_languages: Default::default(),
lsp_binary_statuses_tx, lsp_binary_statuses_tx,
lsp_binary_statuses_rx, lsp_binary_statuses_rx,
@ -568,12 +569,18 @@ impl LanguageRegistry {
self.language_server_download_dir = Some(path.into()); self.language_server_download_dir = Some(path.into());
} }
pub fn language_for_name(self: &Arc<Self>, name: &str) -> Option<Arc<Language>> { pub fn language_for_name(
self: &Arc<Self>,
name: &str,
) -> UnwrapFuture<oneshot::Receiver<Result<Arc<Language>>>> {
let name = UniCase::new(name); let name = UniCase::new(name);
self.get_or_load_language(|config| UniCase::new(config.name.as_ref()) == name) self.get_or_load_language(|config| UniCase::new(config.name.as_ref()) == name)
} }
pub fn language_for_name_or_extension(self: &Arc<Self>, string: &str) -> Option<Arc<Language>> { pub fn language_for_name_or_extension(
self: &Arc<Self>,
string: &str,
) -> UnwrapFuture<oneshot::Receiver<Result<Arc<Language>>>> {
let string = UniCase::new(string); let string = UniCase::new(string);
self.get_or_load_language(|config| { self.get_or_load_language(|config| {
UniCase::new(config.name.as_ref()) == string UniCase::new(config.name.as_ref()) == string
@ -584,7 +591,10 @@ impl LanguageRegistry {
}) })
} }
pub fn language_for_path(self: &Arc<Self>, path: impl AsRef<Path>) -> Option<Arc<Language>> { pub fn language_for_path(
self: &Arc<Self>,
path: impl AsRef<Path>,
) -> UnwrapFuture<oneshot::Receiver<Result<Arc<Language>>>> {
let path = path.as_ref(); let path = path.as_ref();
let filename = path.file_name().and_then(|name| name.to_str()); let filename = path.file_name().and_then(|name| name.to_str());
let extension = path.extension().and_then(|name| name.to_str()); let extension = path.extension().and_then(|name| name.to_str());
@ -600,17 +610,17 @@ impl LanguageRegistry {
fn get_or_load_language( fn get_or_load_language(
self: &Arc<Self>, self: &Arc<Self>,
callback: impl Fn(&LanguageConfig) -> bool, callback: impl Fn(&LanguageConfig) -> bool,
) -> Option<Arc<Language>> { ) -> UnwrapFuture<oneshot::Receiver<Result<Arc<Language>>>> {
let (tx, rx) = oneshot::channel();
if let Some(language) = self if let Some(language) = self
.languages .languages
.read() .read()
.iter() .iter()
.find(|language| callback(&language.config)) .find(|language| callback(&language.config))
{ {
return Some(language.clone()); let _ = tx.send(Ok(language.clone()));
} } else if let Some(executor) = self.executor.clone() {
if let Some(executor) = self.executor.clone() {
let mut available_languages = self.available_languages.write(); let mut available_languages = self.available_languages.write();
if let Some(ix) = available_languages.iter().position(|l| callback(&l.config)) { if let Some(ix) = available_languages.iter().position(|l| callback(&l.config)) {
@ -625,18 +635,29 @@ impl LanguageRegistry {
.with_lsp_adapter(language.lsp_adapter) .with_lsp_adapter(language.lsp_adapter)
.await; .await;
match language.with_queries(queries) { match language.with_queries(queries) {
Ok(language) => this.add(Arc::new(language)), Ok(language) => {
let language = Arc::new(language);
this.add(language.clone());
let _ = tx.send(Ok(language));
}
Err(err) => { Err(err) => {
log::error!("failed to load language {}: {}", name, err); let _ = tx.send(Err(anyhow!(
return; "failed to load language {}: {}",
name,
err
)));
} }
}; };
}) })
.detach(); .detach();
} else {
let _ = tx.send(Err(anyhow!("language not found")));
} }
} else {
let _ = tx.send(Err(anyhow!("executor does not exist")));
} }
None rx.unwrap()
} }
pub fn to_vec(&self) -> Vec<Arc<Language>> { pub fn to_vec(&self) -> Vec<Arc<Language>> {

View file

@ -1,5 +1,6 @@
use crate::{Grammar, InjectionConfig, Language, LanguageRegistry}; use crate::{Grammar, InjectionConfig, Language, LanguageRegistry};
use collections::HashMap; use collections::HashMap;
use futures::FutureExt;
use lazy_static::lazy_static; use lazy_static::lazy_static;
use parking_lot::Mutex; use parking_lot::Mutex;
use std::{ use std::{
@ -382,11 +383,11 @@ impl SyntaxSnapshot {
cursor.next(text); cursor.next(text);
while let Some(layer) = cursor.item() { while let Some(layer) = cursor.item() {
let SyntaxLayerContent::Pending { language_name } = &layer.content else { unreachable!() }; let SyntaxLayerContent::Pending { language_name } = &layer.content else { unreachable!() };
if { if registry
let language_registry = &registry; .language_for_name_or_extension(language_name)
language_registry.language_for_name_or_extension(language_name) .now_or_never()
} .and_then(|language| language.ok())
.is_some() .is_some()
{ {
resolved_injection_ranges.push(layer.range.to_offset(text)); resolved_injection_ranges.push(layer.range.to_offset(text));
} }
@ -1116,7 +1117,10 @@ fn get_injections(
combined_injection_ranges.clear(); combined_injection_ranges.clear();
for pattern in &config.patterns { for pattern in &config.patterns {
if let (Some(language_name), true) = (pattern.language.as_ref(), pattern.combined) { if let (Some(language_name), true) = (pattern.language.as_ref(), pattern.combined) {
if let Some(language) = language_registry.language_for_name_or_extension(language_name) if let Some(language) = language_registry
.language_for_name_or_extension(language_name)
.now_or_never()
.and_then(|language| language.ok())
{ {
combined_injection_ranges.insert(language, Vec::new()); combined_injection_ranges.insert(language, Vec::new());
} }
@ -1162,10 +1166,10 @@ fn get_injections(
}; };
if let Some(language_name) = language_name { if let Some(language_name) = language_name {
let language = { let language = language_registry
let language_name: &str = &language_name; .language_for_name_or_extension(&language_name)
language_registry.language_for_name_or_extension(language_name) .now_or_never()
}; .and_then(|language| language.ok());
let range = text.anchor_before(step_range.start)..text.anchor_after(step_range.end); let range = text.anchor_before(step_range.start)..text.anchor_after(step_range.end);
if let Some(language) = language { if let Some(language) = language {
if combined { if combined {
@ -2522,7 +2526,11 @@ mod tests {
registry.add(Arc::new(html_lang())); registry.add(Arc::new(html_lang()));
registry.add(Arc::new(erb_lang())); registry.add(Arc::new(erb_lang()));
registry.add(Arc::new(markdown_lang())); registry.add(Arc::new(markdown_lang()));
let language = registry.language_for_name(language_name).unwrap(); let language = registry
.language_for_name(language_name)
.now_or_never()
.unwrap()
.unwrap();
let mut buffer = Buffer::new(0, 0, Default::default()); let mut buffer = Buffer::new(0, 0, Default::default());
let mut mutated_syntax_map = SyntaxMap::new(); let mut mutated_syntax_map = SyntaxMap::new();

View file

@ -1838,7 +1838,11 @@ impl Project {
) -> Option<()> { ) -> Option<()> {
// If the buffer has a language, set it and start the language server if we haven't already. // If the buffer has a language, set it and start the language server if we haven't already.
let full_path = buffer.read(cx).file()?.full_path(cx); let full_path = buffer.read(cx).file()?.full_path(cx);
let new_language = self.languages.language_for_path(&full_path)?; let new_language = self
.languages
.language_for_path(&full_path)
.now_or_never()?
.ok()?;
buffer.update(cx, |buffer, cx| { buffer.update(cx, |buffer, cx| {
if buffer.language().map_or(true, |old_language| { if buffer.language().map_or(true, |old_language| {
!Arc::ptr_eq(old_language, &new_language) !Arc::ptr_eq(old_language, &new_language)
@ -2248,8 +2252,14 @@ impl Project {
}) })
.collect(); .collect();
for (worktree_id, worktree_abs_path, full_path) in language_server_lookup_info { for (worktree_id, worktree_abs_path, full_path) in language_server_lookup_info {
let language = self.languages.language_for_path(&full_path)?; if let Some(language) = self
self.restart_language_server(worktree_id, worktree_abs_path, language, cx); .languages
.language_for_path(&full_path)
.now_or_never()
.and_then(|language| language.ok())
{
self.restart_language_server(worktree_id, worktree_abs_path, language, cx);
}
} }
None None
@ -3278,12 +3288,14 @@ impl Project {
path: path.into(), path: path.into(),
}; };
let signature = this.symbol_signature(&project_path); let signature = this.symbol_signature(&project_path);
let adapter_language = adapter_language.clone();
let language = this let language = this
.languages .languages
.language_for_path(&project_path.path) .language_for_path(&project_path.path)
.unwrap_or(adapter_language.clone()); .unwrap_or_else(move |_| adapter_language);
let language_server_name = adapter.name.clone(); let language_server_name = adapter.name.clone();
Some(async move { Some(async move {
let language = language.await;
let label = language let label = language
.label_for_symbol(&lsp_symbol.name, lsp_symbol.kind) .label_for_symbol(&lsp_symbol.name, lsp_symbol.kind)
.await; .await;
@ -6060,7 +6072,7 @@ impl Project {
worktree_id, worktree_id,
path: PathBuf::from(serialized_symbol.path).into(), path: PathBuf::from(serialized_symbol.path).into(),
}; };
let language = languages.language_for_path(&path.path); let language = languages.language_for_path(&path.path).await.log_err();
Ok(Symbol { Ok(Symbol {
language_server_name: LanguageServerName( language_server_name: LanguageServerName(
serialized_symbol.language_server_name.into(), serialized_symbol.language_server_name.into(),

View file

@ -167,9 +167,8 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut gpui::MutableAppContext) {
}); });
cx.add_action({ cx.add_action({
let app_state = app_state.clone(); let app_state = app_state.clone();
move |workspace: &mut Workspace, _: &OpenLicenses, cx: &mut ViewContext<Workspace>| { move |_: &mut Workspace, _: &OpenLicenses, cx: &mut ViewContext<Workspace>| {
open_bundled_file( open_bundled_file(
workspace,
app_state.clone(), app_state.clone(),
"licenses.md", "licenses.md",
"Open Source License Attribution", "Open Source License Attribution",
@ -192,9 +191,8 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut gpui::MutableAppContext) {
}); });
cx.add_action({ cx.add_action({
let app_state = app_state.clone(); let app_state = app_state.clone();
move |workspace: &mut Workspace, _: &OpenDefaultKeymap, cx: &mut ViewContext<Workspace>| { move |_: &mut Workspace, _: &OpenDefaultKeymap, cx: &mut ViewContext<Workspace>| {
open_bundled_file( open_bundled_file(
workspace,
app_state.clone(), app_state.clone(),
"keymaps/default.json", "keymaps/default.json",
"Default Key Bindings", "Default Key Bindings",
@ -205,11 +203,8 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut gpui::MutableAppContext) {
}); });
cx.add_action({ cx.add_action({
let app_state = app_state.clone(); let app_state = app_state.clone();
move |workspace: &mut Workspace, move |_: &mut Workspace, _: &OpenDefaultSettings, cx: &mut ViewContext<Workspace>| {
_: &OpenDefaultSettings,
cx: &mut ViewContext<Workspace>| {
open_bundled_file( open_bundled_file(
workspace,
app_state.clone(), app_state.clone(),
"settings/default.json", "settings/default.json",
"Default Settings", "Default Settings",
@ -218,32 +213,41 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut gpui::MutableAppContext) {
); );
} }
}); });
cx.add_action( cx.add_action({
|workspace: &mut Workspace, _: &DebugElements, cx: &mut ViewContext<Workspace>| { let app_state = app_state.clone();
move |_: &mut Workspace, _: &DebugElements, cx: &mut ViewContext<Workspace>| {
let app_state = app_state.clone();
let markdown = app_state.languages.language_for_name("JSON");
let content = to_string_pretty(&cx.debug_elements()).unwrap(); let content = to_string_pretty(&cx.debug_elements()).unwrap();
let project = workspace.project().clone(); cx.spawn(|workspace, mut cx| async move {
let json_language = project let markdown = markdown.await.log_err();
.read(cx) workspace
.languages() .update(&mut cx, |workspace, cx| {
.language_for_name("JSON") workspace.with_local_workspace(&app_state, cx, move |workspace, cx| {
.unwrap(); let project = workspace.project().clone();
if project.read(cx).is_remote() {
cx.propagate_action(); let buffer = project
} else if let Some(buffer) = project .update(cx, |project, cx| {
.update(cx, |project, cx| { project.create_buffer(&content, markdown, cx)
project.create_buffer(&content, Some(json_language), cx) })
}) .expect("creating buffers on a local workspace always succeeds");
.log_err() let buffer = cx.add_model(|cx| {
{ MultiBuffer::singleton(buffer, cx)
workspace.add_item( .with_title("Debug Elements".into())
Box::new( });
cx.add_view(|cx| Editor::for_buffer(buffer, Some(project.clone()), cx)), workspace.add_item(
), Box::new(cx.add_view(|cx| {
cx, Editor::for_multibuffer(buffer, Some(project.clone()), cx)
); })),
} cx,
}, );
); })
})
.await;
})
.detach();
}
});
cx.add_action( cx.add_action(
|workspace: &mut Workspace, |workspace: &mut Workspace,
_: &project_panel::ToggleFocus, _: &project_panel::ToggleFocus,
@ -628,6 +632,7 @@ fn open_telemetry_log_file(
start_offset += newline_offset + 1; start_offset += newline_offset + 1;
} }
let log_suffix = &log[start_offset..]; let log_suffix = &log[start_offset..];
let json = app_state.languages.language_for_name("JSON").await.log_err();
workspace.update(&mut cx, |workspace, cx| { workspace.update(&mut cx, |workspace, cx| {
let project = workspace.project().clone(); let project = workspace.project().clone();
@ -635,7 +640,7 @@ fn open_telemetry_log_file(
.update(cx, |project, cx| project.create_buffer("", None, cx)) .update(cx, |project, cx| project.create_buffer("", None, cx))
.expect("creating buffers on a local workspace always succeeds"); .expect("creating buffers on a local workspace always succeeds");
buffer.update(cx, |buffer, cx| { buffer.update(cx, |buffer, cx| {
buffer.set_language(app_state.languages.language_for_name("JSON"), cx); buffer.set_language(json, cx);
buffer.edit( buffer.edit(
[( [(
0..0, 0..0,
@ -668,35 +673,42 @@ fn open_telemetry_log_file(
} }
fn open_bundled_file( fn open_bundled_file(
workspace: &mut Workspace,
app_state: Arc<AppState>, app_state: Arc<AppState>,
asset_path: &'static str, asset_path: &'static str,
title: &'static str, title: &'static str,
language: &'static str, language: &'static str,
cx: &mut ViewContext<Workspace>, cx: &mut ViewContext<Workspace>,
) { ) {
workspace let language = app_state.languages.language_for_name(language);
.with_local_workspace(&app_state, cx, |workspace, cx| { cx.spawn(|workspace, mut cx| async move {
let project = workspace.project().clone(); let language = language.await.log_err();
let buffer = project.update(cx, |project, cx| { workspace
let text = Assets::get(asset_path) .update(&mut cx, |workspace, cx| {
.map(|f| f.data) workspace.with_local_workspace(&app_state, cx, |workspace, cx| {
.unwrap_or_else(|| Cow::Borrowed(b"File not found")); let project = workspace.project();
let text = str::from_utf8(text.as_ref()).unwrap(); let buffer = project.update(cx, |project, cx| {
project let text = Assets::get(asset_path)
.create_buffer(text, project.languages().language_for_name(language), cx) .map(|f| f.data)
.expect("creating buffers on a local workspace always succeeds") .unwrap_or_else(|| Cow::Borrowed(b"File not found"));
}); let text = str::from_utf8(text.as_ref()).unwrap();
let buffer = project
cx.add_model(|cx| MultiBuffer::singleton(buffer, cx).with_title(title.into())); .create_buffer(text, language, cx)
workspace.add_item( .expect("creating buffers on a local workspace always succeeds")
Box::new( });
cx.add_view(|cx| Editor::for_multibuffer(buffer, Some(project.clone()), cx)), let buffer = cx.add_model(|cx| {
), MultiBuffer::singleton(buffer, cx).with_title(title.into())
cx, });
); workspace.add_item(
}) Box::new(cx.add_view(|cx| {
.detach(); Editor::for_multibuffer(buffer, Some(project.clone()), cx)
})),
cx,
);
})
})
.await;
})
.detach();
} }
fn schema_file_match(path: &Path) -> &Path { fn schema_file_match(path: &Path) -> &Path {