Merge pull request #2261 from zed-industries/async-language-loading
Allow waiting for language to be loaded in LanguageRegistry APIs
This commit is contained in:
commit
d39c761de5
14 changed files with 352 additions and 218 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -3157,6 +3157,7 @@ dependencies = [
|
||||||
name = "journal"
|
name = "journal"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
"chrono",
|
"chrono",
|
||||||
"dirs 4.0.0",
|
"dirs 4.0.0",
|
||||||
"editor",
|
"editor",
|
||||||
|
|
|
@ -224,7 +224,7 @@ impl Telemetry {
|
||||||
.header("Content-Type", "application/json")
|
.header("Content-Type", "application/json")
|
||||||
.body(json_bytes.into())?;
|
.body(json_bytes.into())?;
|
||||||
this.http_client.send(request).await?;
|
this.http_client.send(request).await?;
|
||||||
Ok(())
|
anyhow::Ok(())
|
||||||
}
|
}
|
||||||
.log_err(),
|
.log_err(),
|
||||||
)
|
)
|
||||||
|
@ -320,7 +320,7 @@ impl Telemetry {
|
||||||
.header("Content-Type", "application/json")
|
.header("Content-Type", "application/json")
|
||||||
.body(json_bytes.into())?;
|
.body(json_bytes.into())?;
|
||||||
this.http_client.send(request).await?;
|
this.http_client.send(request).await?;
|
||||||
Ok(())
|
anyhow::Ok(())
|
||||||
}
|
}
|
||||||
.log_err(),
|
.log_err(),
|
||||||
)
|
)
|
||||||
|
|
|
@ -68,7 +68,7 @@ impl PickerDelegate for ContactFinder {
|
||||||
this.potential_contacts = potential_contacts.into();
|
this.potential_contacts = potential_contacts.into();
|
||||||
cx.notify();
|
cx.notify();
|
||||||
});
|
});
|
||||||
Ok(())
|
anyhow::Ok(())
|
||||||
}
|
}
|
||||||
.log_err()
|
.log_err()
|
||||||
.await;
|
.await;
|
||||||
|
|
|
@ -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());
|
||||||
|
|
||||||
|
|
|
@ -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,23 +201,27 @@ 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>,
|
||||||
) {
|
) {
|
||||||
|
let markdown = app_state.languages.language_for_name("Markdown");
|
||||||
|
cx.spawn(|workspace, mut cx| async move {
|
||||||
|
let markdown = markdown.await.log_err();
|
||||||
workspace
|
workspace
|
||||||
.with_local_workspace(&app_state, cx, |workspace, cx| {
|
.update(&mut cx, |workspace, cx| {
|
||||||
|
workspace.with_local_workspace(&app_state, cx, |workspace, cx| {
|
||||||
let project = workspace.project().clone();
|
let project = workspace.project().clone();
|
||||||
let markdown_language = project.read(cx).languages().language_for_name("Markdown");
|
|
||||||
let buffer = project
|
let buffer = project
|
||||||
.update(cx, |project, cx| {
|
.update(cx, |project, cx| project.create_buffer("", markdown, cx))
|
||||||
project.create_buffer("", markdown_language, cx)
|
|
||||||
})
|
|
||||||
.expect("creating buffers on a local workspace always succeeds");
|
.expect("creating buffers on a local workspace always succeeds");
|
||||||
let feedback_editor =
|
let feedback_editor = cx
|
||||||
cx.add_view(|cx| FeedbackEditor::new(system_specs, project, buffer, cx));
|
.add_view(|cx| FeedbackEditor::new(system_specs, project, buffer, cx));
|
||||||
workspace.add_item(Box::new(feedback_editor), cx);
|
workspace.add_item(Box::new(feedback_editor), cx);
|
||||||
})
|
})
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
})
|
||||||
.detach();
|
.detach();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@ editor = { path = "../editor" }
|
||||||
gpui = { path = "../gpui" }
|
gpui = { path = "../gpui" }
|
||||||
util = { path = "../util" }
|
util = { path = "../util" }
|
||||||
workspace = { path = "../workspace" }
|
workspace = { path = "../workspace" }
|
||||||
|
anyhow = "1.0"
|
||||||
chrono = "0.4"
|
chrono = "0.4"
|
||||||
dirs = "4.0"
|
dirs = "4.0"
|
||||||
log = { version = "0.4.16", features = ["kv_unstable_serde"] }
|
log = { version = "0.4.16", features = ["kv_unstable_serde"] }
|
||||||
|
|
|
@ -73,7 +73,7 @@ pub fn new_journal_entry(app_state: Arc<AppState>, cx: &mut MutableAppContext) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
anyhow::Ok(())
|
||||||
}
|
}
|
||||||
.log_err()
|
.log_err()
|
||||||
})
|
})
|
||||||
|
|
|
@ -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
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>> {
|
||||||
|
|
|
@ -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,10 +383,10 @@ 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 = ®istry;
|
.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();
|
||||||
|
|
|
@ -160,15 +160,13 @@ impl LanguageServer {
|
||||||
server: Option<Child>,
|
server: Option<Child>,
|
||||||
root_path: &Path,
|
root_path: &Path,
|
||||||
cx: AsyncAppContext,
|
cx: AsyncAppContext,
|
||||||
mut on_unhandled_notification: F,
|
on_unhandled_notification: F,
|
||||||
) -> Self
|
) -> Self
|
||||||
where
|
where
|
||||||
Stdin: AsyncWrite + Unpin + Send + 'static,
|
Stdin: AsyncWrite + Unpin + Send + 'static,
|
||||||
Stdout: AsyncRead + Unpin + Send + 'static,
|
Stdout: AsyncRead + Unpin + Send + 'static,
|
||||||
F: FnMut(AnyNotification) + 'static + Send,
|
F: FnMut(AnyNotification) + 'static + Send,
|
||||||
{
|
{
|
||||||
let mut stdin = BufWriter::new(stdin);
|
|
||||||
let mut stdout = BufReader::new(stdout);
|
|
||||||
let (outbound_tx, outbound_rx) = channel::unbounded::<Vec<u8>>();
|
let (outbound_tx, outbound_rx) = channel::unbounded::<Vec<u8>>();
|
||||||
let notification_handlers =
|
let notification_handlers =
|
||||||
Arc::new(Mutex::new(HashMap::<_, NotificationHandler>::default()));
|
Arc::new(Mutex::new(HashMap::<_, NotificationHandler>::default()));
|
||||||
|
@ -177,7 +175,49 @@ impl LanguageServer {
|
||||||
let input_task = cx.spawn(|cx| {
|
let input_task = cx.spawn(|cx| {
|
||||||
let notification_handlers = notification_handlers.clone();
|
let notification_handlers = notification_handlers.clone();
|
||||||
let response_handlers = response_handlers.clone();
|
let response_handlers = response_handlers.clone();
|
||||||
async move {
|
Self::handle_input(
|
||||||
|
stdout,
|
||||||
|
on_unhandled_notification,
|
||||||
|
notification_handlers,
|
||||||
|
response_handlers,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
.log_err()
|
||||||
|
});
|
||||||
|
let (output_done_tx, output_done_rx) = barrier::channel();
|
||||||
|
let output_task = cx.background().spawn({
|
||||||
|
let response_handlers = response_handlers.clone();
|
||||||
|
Self::handle_output(stdin, outbound_rx, output_done_tx, response_handlers).log_err()
|
||||||
|
});
|
||||||
|
|
||||||
|
Self {
|
||||||
|
server_id,
|
||||||
|
notification_handlers,
|
||||||
|
response_handlers,
|
||||||
|
name: Default::default(),
|
||||||
|
capabilities: Default::default(),
|
||||||
|
next_id: Default::default(),
|
||||||
|
outbound_tx,
|
||||||
|
executor: cx.background(),
|
||||||
|
io_tasks: Mutex::new(Some((input_task, output_task))),
|
||||||
|
output_done_rx: Mutex::new(Some(output_done_rx)),
|
||||||
|
root_path: root_path.to_path_buf(),
|
||||||
|
_server: server,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle_input<Stdout, F>(
|
||||||
|
stdout: Stdout,
|
||||||
|
mut on_unhandled_notification: F,
|
||||||
|
notification_handlers: Arc<Mutex<HashMap<&'static str, NotificationHandler>>>,
|
||||||
|
response_handlers: Arc<Mutex<Option<HashMap<usize, ResponseHandler>>>>,
|
||||||
|
cx: AsyncAppContext,
|
||||||
|
) -> anyhow::Result<()>
|
||||||
|
where
|
||||||
|
Stdout: AsyncRead + Unpin + Send + 'static,
|
||||||
|
F: FnMut(AnyNotification) + 'static + Send,
|
||||||
|
{
|
||||||
|
let mut stdout = BufReader::new(stdout);
|
||||||
let _clear_response_handlers = util::defer({
|
let _clear_response_handlers = util::defer({
|
||||||
let response_handlers = response_handlers.clone();
|
let response_handlers = response_handlers.clone();
|
||||||
move || {
|
move || {
|
||||||
|
@ -233,12 +273,17 @@ impl LanguageServer {
|
||||||
smol::future::yield_now().await;
|
smol::future::yield_now().await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.log_err()
|
|
||||||
});
|
async fn handle_output<Stdin>(
|
||||||
let (output_done_tx, output_done_rx) = barrier::channel();
|
stdin: Stdin,
|
||||||
let output_task = cx.background().spawn({
|
outbound_rx: channel::Receiver<Vec<u8>>,
|
||||||
let response_handlers = response_handlers.clone();
|
output_done_tx: barrier::Sender,
|
||||||
async move {
|
response_handlers: Arc<Mutex<Option<HashMap<usize, ResponseHandler>>>>,
|
||||||
|
) -> anyhow::Result<()>
|
||||||
|
where
|
||||||
|
Stdin: AsyncWrite + Unpin + Send + 'static,
|
||||||
|
{
|
||||||
|
let mut stdin = BufWriter::new(stdin);
|
||||||
let _clear_response_handlers = util::defer({
|
let _clear_response_handlers = util::defer({
|
||||||
let response_handlers = response_handlers.clone();
|
let response_handlers = response_handlers.clone();
|
||||||
move || {
|
move || {
|
||||||
|
@ -259,24 +304,6 @@ impl LanguageServer {
|
||||||
drop(output_done_tx);
|
drop(output_done_tx);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
.log_err()
|
|
||||||
});
|
|
||||||
|
|
||||||
Self {
|
|
||||||
server_id,
|
|
||||||
notification_handlers,
|
|
||||||
response_handlers,
|
|
||||||
name: Default::default(),
|
|
||||||
capabilities: Default::default(),
|
|
||||||
next_id: Default::default(),
|
|
||||||
outbound_tx,
|
|
||||||
executor: cx.background(),
|
|
||||||
io_tasks: Mutex::new(Some((input_task, output_task))),
|
|
||||||
output_done_rx: Mutex::new(Some(output_done_rx)),
|
|
||||||
root_path: root_path.to_path_buf(),
|
|
||||||
_server: server,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Initializes a language server.
|
/// Initializes a language server.
|
||||||
/// Note that `options` is used directly to construct [`InitializeParams`],
|
/// Note that `options` is used directly to construct [`InitializeParams`],
|
||||||
|
@ -389,7 +416,7 @@ impl LanguageServer {
|
||||||
output_done.recv().await;
|
output_done.recv().await;
|
||||||
log::debug!("language server shutdown finished");
|
log::debug!("language server shutdown finished");
|
||||||
drop(tasks);
|
drop(tasks);
|
||||||
Ok(())
|
anyhow::Ok(())
|
||||||
}
|
}
|
||||||
.log_err(),
|
.log_err(),
|
||||||
)
|
)
|
||||||
|
|
|
@ -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,9 +2252,15 @@ 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
|
||||||
|
.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);
|
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;
|
||||||
|
@ -5831,7 +5843,7 @@ impl Project {
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
anyhow::Ok(())
|
||||||
}
|
}
|
||||||
.log_err(),
|
.log_err(),
|
||||||
)
|
)
|
||||||
|
@ -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(),
|
||||||
|
|
|
@ -124,11 +124,15 @@ pub trait TryFutureExt {
|
||||||
fn warn_on_err(self) -> LogErrorFuture<Self>
|
fn warn_on_err(self) -> LogErrorFuture<Self>
|
||||||
where
|
where
|
||||||
Self: Sized;
|
Self: Sized;
|
||||||
|
fn unwrap(self) -> UnwrapFuture<Self>
|
||||||
|
where
|
||||||
|
Self: Sized;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<F, T> TryFutureExt for F
|
impl<F, T, E> TryFutureExt for F
|
||||||
where
|
where
|
||||||
F: Future<Output = anyhow::Result<T>>,
|
F: Future<Output = Result<T, E>>,
|
||||||
|
E: std::fmt::Debug,
|
||||||
{
|
{
|
||||||
fn log_err(self) -> LogErrorFuture<Self>
|
fn log_err(self) -> LogErrorFuture<Self>
|
||||||
where
|
where
|
||||||
|
@ -143,17 +147,25 @@ where
|
||||||
{
|
{
|
||||||
LogErrorFuture(self, log::Level::Warn)
|
LogErrorFuture(self, log::Level::Warn)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn unwrap(self) -> UnwrapFuture<Self>
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
UnwrapFuture(self)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct LogErrorFuture<F>(F, log::Level);
|
pub struct LogErrorFuture<F>(F, log::Level);
|
||||||
|
|
||||||
impl<F, T> Future for LogErrorFuture<F>
|
impl<F, T, E> Future for LogErrorFuture<F>
|
||||||
where
|
where
|
||||||
F: Future<Output = anyhow::Result<T>>,
|
F: Future<Output = Result<T, E>>,
|
||||||
|
E: std::fmt::Debug,
|
||||||
{
|
{
|
||||||
type Output = Option<T>;
|
type Output = Option<T>;
|
||||||
|
|
||||||
fn poll(self: std::pin::Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
|
||||||
let level = self.1;
|
let level = self.1;
|
||||||
let inner = unsafe { Pin::new_unchecked(&mut self.get_unchecked_mut().0) };
|
let inner = unsafe { Pin::new_unchecked(&mut self.get_unchecked_mut().0) };
|
||||||
match inner.poll(cx) {
|
match inner.poll(cx) {
|
||||||
|
@ -169,6 +181,24 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct UnwrapFuture<F>(F);
|
||||||
|
|
||||||
|
impl<F, T, E> Future for UnwrapFuture<F>
|
||||||
|
where
|
||||||
|
F: Future<Output = Result<T, E>>,
|
||||||
|
E: std::fmt::Debug,
|
||||||
|
{
|
||||||
|
type Output = T;
|
||||||
|
|
||||||
|
fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
|
||||||
|
let inner = unsafe { Pin::new_unchecked(&mut self.get_unchecked_mut().0) };
|
||||||
|
match inner.poll(cx) {
|
||||||
|
Poll::Ready(result) => Poll::Ready(result.unwrap()),
|
||||||
|
Poll::Pending => Poll::Pending,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct Defer<F: FnOnce()>(Option<F>);
|
struct Defer<F: FnOnce()>(Option<F>);
|
||||||
|
|
||||||
impl<F: FnOnce()> Drop for Defer<F> {
|
impl<F: FnOnce()> Drop for Defer<F> {
|
||||||
|
|
|
@ -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();
|
||||||
|
cx.spawn(|workspace, mut cx| async move {
|
||||||
|
let markdown = markdown.await.log_err();
|
||||||
|
workspace
|
||||||
|
.update(&mut cx, |workspace, cx| {
|
||||||
|
workspace.with_local_workspace(&app_state, cx, move |workspace, cx| {
|
||||||
let project = workspace.project().clone();
|
let project = workspace.project().clone();
|
||||||
let json_language = project
|
|
||||||
.read(cx)
|
let buffer = project
|
||||||
.languages()
|
|
||||||
.language_for_name("JSON")
|
|
||||||
.unwrap();
|
|
||||||
if project.read(cx).is_remote() {
|
|
||||||
cx.propagate_action();
|
|
||||||
} else if let Some(buffer) = project
|
|
||||||
.update(cx, |project, cx| {
|
.update(cx, |project, cx| {
|
||||||
project.create_buffer(&content, Some(json_language), cx)
|
project.create_buffer(&content, markdown, cx)
|
||||||
})
|
})
|
||||||
.log_err()
|
.expect("creating buffers on a local workspace always succeeds");
|
||||||
{
|
let buffer = cx.add_model(|cx| {
|
||||||
|
MultiBuffer::singleton(buffer, cx)
|
||||||
|
.with_title("Debug Elements".into())
|
||||||
|
});
|
||||||
workspace.add_item(
|
workspace.add_item(
|
||||||
Box::new(
|
Box::new(cx.add_view(|cx| {
|
||||||
cx.add_view(|cx| Editor::for_buffer(buffer, Some(project.clone()), cx)),
|
Editor::for_multibuffer(buffer, Some(project.clone()), cx)
|
||||||
),
|
})),
|
||||||
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,34 +673,41 @@ 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>,
|
||||||
) {
|
) {
|
||||||
|
let language = app_state.languages.language_for_name(language);
|
||||||
|
cx.spawn(|workspace, mut cx| async move {
|
||||||
|
let language = language.await.log_err();
|
||||||
workspace
|
workspace
|
||||||
.with_local_workspace(&app_state, cx, |workspace, cx| {
|
.update(&mut cx, |workspace, cx| {
|
||||||
let project = workspace.project().clone();
|
workspace.with_local_workspace(&app_state, cx, |workspace, cx| {
|
||||||
|
let project = workspace.project();
|
||||||
let buffer = project.update(cx, |project, cx| {
|
let buffer = project.update(cx, |project, cx| {
|
||||||
let text = Assets::get(asset_path)
|
let text = Assets::get(asset_path)
|
||||||
.map(|f| f.data)
|
.map(|f| f.data)
|
||||||
.unwrap_or_else(|| Cow::Borrowed(b"File not found"));
|
.unwrap_or_else(|| Cow::Borrowed(b"File not found"));
|
||||||
let text = str::from_utf8(text.as_ref()).unwrap();
|
let text = str::from_utf8(text.as_ref()).unwrap();
|
||||||
project
|
project
|
||||||
.create_buffer(text, project.languages().language_for_name(language), cx)
|
.create_buffer(text, language, cx)
|
||||||
.expect("creating buffers on a local workspace always succeeds")
|
.expect("creating buffers on a local workspace always succeeds")
|
||||||
});
|
});
|
||||||
let buffer =
|
let buffer = cx.add_model(|cx| {
|
||||||
cx.add_model(|cx| MultiBuffer::singleton(buffer, cx).with_title(title.into()));
|
MultiBuffer::singleton(buffer, cx).with_title(title.into())
|
||||||
|
});
|
||||||
workspace.add_item(
|
workspace.add_item(
|
||||||
Box::new(
|
Box::new(cx.add_view(|cx| {
|
||||||
cx.add_view(|cx| Editor::for_multibuffer(buffer, Some(project.clone()), cx)),
|
Editor::for_multibuffer(buffer, Some(project.clone()), cx)
|
||||||
),
|
})),
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
})
|
||||||
.detach();
|
.detach();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue