Move logic for starting language servers to the project

This commit is contained in:
Max Brunsfeld 2022-01-19 14:05:06 -08:00
parent 10c64f527c
commit f43dcd6763
9 changed files with 846 additions and 863 deletions

View File

@ -731,6 +731,8 @@ mod tests {
// Create some diagnostics
worktree.update(&mut cx, |worktree, cx| {
worktree
.as_local_mut()
.unwrap()
.update_diagnostic_entries(
Arc::from("/test/main.rs".as_ref()),
None,
@ -882,6 +884,8 @@ mod tests {
// Diagnostics are added for another earlier path.
worktree.update(&mut cx, |worktree, cx| {
worktree
.as_local_mut()
.unwrap()
.update_diagnostic_entries(
Arc::from("/test/consts.rs".as_ref()),
None,
@ -980,6 +984,8 @@ mod tests {
// Diagnostics are added to the first path
worktree.update(&mut cx, |worktree, cx| {
worktree
.as_local_mut()
.unwrap()
.update_diagnostic_entries(
Arc::from("/test/consts.rs".as_ref()),
None,

View File

@ -840,7 +840,7 @@ mod tests {
("mod.body".to_string(), Color::red().into()),
("fn.name".to_string(), Color::blue().into()),
]);
let lang = Arc::new(
let language = Arc::new(
Language::new(
LanguageConfig {
name: "Test".to_string(),
@ -857,10 +857,9 @@ mod tests {
)
.unwrap(),
);
lang.set_theme(&theme);
language.set_theme(&theme);
let buffer =
cx.add_model(|cx| Buffer::new(0, text, cx).with_language(Some(lang), None, cx));
let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
buffer.condition(&cx, |buf, _| !buf.is_parsing()).await;
let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
@ -928,7 +927,7 @@ mod tests {
("mod.body".to_string(), Color::red().into()),
("fn.name".to_string(), Color::blue().into()),
]);
let lang = Arc::new(
let language = Arc::new(
Language::new(
LanguageConfig {
name: "Test".to_string(),
@ -945,10 +944,9 @@ mod tests {
)
.unwrap(),
);
lang.set_theme(&theme);
language.set_theme(&theme);
let buffer =
cx.add_model(|cx| Buffer::new(0, text, cx).with_language(Some(lang), None, cx));
let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
buffer.condition(&cx, |buf, _| !buf.is_parsing()).await;
let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));

View File

@ -533,9 +533,8 @@ impl Editor {
_: &workspace::OpenNew,
cx: &mut ViewContext<Workspace>,
) {
let buffer = cx.add_model(|cx| {
Buffer::new(0, "", cx).with_language(Some(language::PLAIN_TEXT.clone()), None, cx)
});
let buffer = cx
.add_model(|cx| Buffer::new(0, "", cx).with_language(language::PLAIN_TEXT.clone(), cx));
workspace.open_item(BufferItemHandle(buffer), cx);
}
@ -5746,10 +5745,10 @@ mod tests {
#[gpui::test]
async fn test_select_larger_smaller_syntax_node(mut cx: gpui::TestAppContext) {
let settings = cx.read(EditorSettings::test);
let language = Some(Arc::new(Language::new(
let language = Arc::new(Language::new(
LanguageConfig::default(),
Some(tree_sitter_rust::language()),
)));
));
let text = r#"
use mod1::mod2::{mod3, mod4};
@ -5760,7 +5759,7 @@ mod tests {
"#
.unindent();
let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, None, cx));
let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
let (_, view) = cx.add_window(|cx| build_editor(buffer, settings, cx));
view.condition(&cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
@ -5887,7 +5886,7 @@ mod tests {
#[gpui::test]
async fn test_autoindent_selections(mut cx: gpui::TestAppContext) {
let settings = cx.read(EditorSettings::test);
let language = Some(Arc::new(
let language = Arc::new(
Language::new(
LanguageConfig {
brackets: vec![
@ -5915,11 +5914,11 @@ mod tests {
"#,
)
.unwrap(),
));
);
let text = "fn a() {}";
let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, None, cx));
let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
let (_, editor) = cx.add_window(|cx| build_editor(buffer, settings, cx));
editor
@ -5944,7 +5943,7 @@ mod tests {
#[gpui::test]
async fn test_autoclose_pairs(mut cx: gpui::TestAppContext) {
let settings = cx.read(EditorSettings::test);
let language = Some(Arc::new(Language::new(
let language = Arc::new(Language::new(
LanguageConfig {
brackets: vec![
BracketPair {
@ -5963,7 +5962,7 @@ mod tests {
..Default::default()
},
Some(tree_sitter_rust::language()),
)));
));
let text = r#"
a
@ -5973,7 +5972,7 @@ mod tests {
"#
.unindent();
let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, None, cx));
let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
let (_, view) = cx.add_window(|cx| build_editor(buffer, settings, cx));
view.condition(&cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))
@ -6055,13 +6054,13 @@ mod tests {
#[gpui::test]
async fn test_toggle_comment(mut cx: gpui::TestAppContext) {
let settings = cx.read(EditorSettings::test);
let language = Some(Arc::new(Language::new(
let language = Arc::new(Language::new(
LanguageConfig {
line_comment: Some("// ".to_string()),
..Default::default()
},
Some(tree_sitter_rust::language()),
)));
));
let text = "
fn a() {
@ -6072,7 +6071,7 @@ mod tests {
"
.unindent();
let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, None, cx));
let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
let (_, view) = cx.add_window(|cx| build_editor(buffer, settings, cx));
@ -6323,7 +6322,7 @@ mod tests {
#[gpui::test]
async fn test_extra_newline_insertion(mut cx: gpui::TestAppContext) {
let settings = cx.read(EditorSettings::test);
let language = Some(Arc::new(Language::new(
let language = Arc::new(Language::new(
LanguageConfig {
brackets: vec![
BracketPair {
@ -6342,7 +6341,7 @@ mod tests {
..Default::default()
},
Some(tree_sitter_rust::language()),
)));
));
let text = concat!(
"{ }\n", // Suppress rustfmt
@ -6352,7 +6351,7 @@ mod tests {
"{{} }\n", //
);
let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, None, cx));
let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
let (_, view) = cx.add_window(|cx| build_editor(buffer, settings, cx));
view.condition(&cx, |view, cx| !view.buffer.read(cx).is_parsing(cx))

View File

@ -34,7 +34,7 @@ impl PathOpener for BufferOpener {
) -> Option<Task<Result<Box<dyn ItemHandle>>>> {
let buffer = worktree.open_buffer(project_path.path, cx);
let task = cx.spawn(|_, _| async move {
let buffer = buffer.await?;
let buffer = buffer.await?.0;
Ok(Box::new(BufferItemHandle(buffer)) as Box<dyn ItemHandle>)
});
Some(task)

View File

@ -385,13 +385,17 @@ impl Buffer {
}
}
pub fn with_language(
pub fn with_language(mut self, language: Arc<Language>, cx: &mut ModelContext<Self>) -> Self {
self.set_language(Some(language), cx);
self
}
pub fn with_language_server(
mut self,
language: Option<Arc<Language>>,
language_server: Option<Arc<LanguageServer>>,
server: Arc<LanguageServer>,
cx: &mut ModelContext<Self>,
) -> Self {
self.set_language(language, language_server, cx);
self.set_language_server(Some(server), cx);
self
}
@ -523,13 +527,16 @@ impl Buffer {
}))
}
pub fn set_language(
pub fn set_language(&mut self, language: Option<Arc<Language>>, cx: &mut ModelContext<Self>) {
self.language = language;
self.reparse(cx);
}
pub fn set_language_server(
&mut self,
language: Option<Arc<Language>>,
language_server: Option<Arc<lsp::LanguageServer>>,
cx: &mut ModelContext<Self>,
) {
self.language = language;
self.language_server = if let Some(server) = language_server {
let (latest_snapshot_tx, mut latest_snapshot_rx) = watch::channel();
Some(LanguageServerState {
@ -611,7 +618,6 @@ impl Buffer {
None
};
self.reparse(cx);
self.update_language_server();
}

View File

@ -145,9 +145,8 @@ async fn test_apply_diff(mut cx: gpui::TestAppContext) {
#[gpui::test]
async fn test_reparse(mut cx: gpui::TestAppContext) {
let text = "fn a() {}";
let buffer = cx.add_model(|cx| {
Buffer::new(0, text, cx).with_language(Some(Arc::new(rust_lang())), None, cx)
});
let buffer =
cx.add_model(|cx| Buffer::new(0, text, cx).with_language(Arc::new(rust_lang()), cx));
// Wait for the initial text to parse
buffer
@ -280,7 +279,7 @@ async fn test_reparse(mut cx: gpui::TestAppContext) {
#[gpui::test]
async fn test_outline(mut cx: gpui::TestAppContext) {
let language = Some(Arc::new(
let language = Arc::new(
rust_lang()
.with_outline_query(
r#"
@ -308,7 +307,7 @@ async fn test_outline(mut cx: gpui::TestAppContext) {
"#,
)
.unwrap(),
));
);
let text = r#"
struct Person {
@ -337,7 +336,7 @@ async fn test_outline(mut cx: gpui::TestAppContext) {
"#
.unindent();
let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, None, cx));
let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
let outline = buffer
.read_with(&cx, |buffer, _| buffer.snapshot().outline(None))
.unwrap();
@ -422,7 +421,7 @@ fn test_enclosing_bracket_ranges(cx: &mut MutableAppContext) {
}
"
.unindent();
Buffer::new(0, text, cx).with_language(Some(Arc::new(rust_lang())), None, cx)
Buffer::new(0, text, cx).with_language(Arc::new(rust_lang()), cx)
});
let buffer = buffer.read(cx);
assert_eq!(
@ -452,8 +451,7 @@ fn test_enclosing_bracket_ranges(cx: &mut MutableAppContext) {
fn test_edit_with_autoindent(cx: &mut MutableAppContext) {
cx.add_model(|cx| {
let text = "fn a() {}";
let mut buffer =
Buffer::new(0, text, cx).with_language(Some(Arc::new(rust_lang())), None, cx);
let mut buffer = Buffer::new(0, text, cx).with_language(Arc::new(rust_lang()), cx);
buffer.edit_with_autoindent([8..8], "\n\n", cx);
assert_eq!(buffer.text(), "fn a() {\n \n}");
@ -479,8 +477,7 @@ fn test_autoindent_does_not_adjust_lines_with_unchanged_suggestion(cx: &mut Muta
"
.unindent();
let mut buffer =
Buffer::new(0, text, cx).with_language(Some(Arc::new(rust_lang())), None, cx);
let mut buffer = Buffer::new(0, text, cx).with_language(Arc::new(rust_lang()), cx);
// Lines 2 and 3 don't match the indentation suggestion. When editing these lines,
// their indentation is not adjusted.
@ -529,8 +526,7 @@ fn test_autoindent_adjusts_lines_when_only_text_changes(cx: &mut MutableAppConte
"
.unindent();
let mut buffer =
Buffer::new(0, text, cx).with_language(Some(Arc::new(rust_lang())), None, cx);
let mut buffer = Buffer::new(0, text, cx).with_language(Arc::new(rust_lang()), cx);
buffer.edit_with_autoindent([5..5], "\nb", cx);
assert_eq!(
@ -575,7 +571,9 @@ async fn test_diagnostics(mut cx: gpui::TestAppContext) {
.unindent();
let buffer = cx.add_model(|cx| {
Buffer::new(0, text, cx).with_language(Some(Arc::new(rust_lang)), Some(language_server), cx)
Buffer::new(0, text, cx)
.with_language(Arc::new(rust_lang), cx)
.with_language_server(language_server, cx)
});
let open_notification = fake
@ -849,7 +847,7 @@ async fn test_empty_diagnostic_ranges(mut cx: gpui::TestAppContext) {
);
let mut buffer = Buffer::new(0, text, cx);
buffer.set_language(Some(Arc::new(rust_lang())), None, cx);
buffer.set_language(Some(Arc::new(rust_lang())), cx);
buffer
.update_diagnostics(
None,

View File

@ -5,7 +5,7 @@ pub mod worktree;
use anyhow::{anyhow, Result};
use client::{proto, Client, PeerId, TypedEnvelope, User, UserStore};
use clock::ReplicaId;
use collections::HashMap;
use collections::{hash_map, HashMap, HashSet};
use futures::Future;
use fuzzy::{PathMatch, PathMatchCandidate, PathMatchCandidateSet};
use gpui::{
@ -27,7 +27,7 @@ pub struct Project {
worktrees: Vec<ModelHandle<Worktree>>,
active_entry: Option<ProjectEntry>,
languages: Arc<LanguageRegistry>,
language_servers: HashMap<(Arc<Path>, String), Arc<LanguageServer>>,
language_servers: HashMap<(WorktreeId, String), Arc<LanguageServer>>,
client: Arc<client::Client>,
user_store: ModelHandle<UserStore>,
fs: Arc<dyn Fs>,
@ -63,6 +63,7 @@ pub enum Event {
ActiveEntryChanged(Option<ProjectEntry>),
WorktreeRemoved(WorktreeId),
DiskBasedDiagnosticsStarted,
DiskBasedDiagnosticsUpdated { worktree_id: WorktreeId },
DiskBasedDiagnosticsFinished,
DiagnosticsUpdated(ProjectPath),
}
@ -223,7 +224,6 @@ impl Project {
worktree,
client.clone(),
user_store.clone(),
languages.clone(),
cx,
)
.await?,
@ -463,39 +463,21 @@ impl Project {
path: ProjectPath,
cx: &mut ModelContext<Self>,
) -> Task<Result<ModelHandle<Buffer>>> {
if let Some(worktree) = self.worktree_for_id(path.worktree_id, cx) {
let language_server = worktree
.read(cx)
.as_local()
.map(|w| w.abs_path().clone())
.and_then(|worktree_abs_path| {
let abs_path = worktree.abs_path().join(&path.path);
let language = self.languages.select_language(&abs_path).cloned();
if let Some(language) = language {
if let Some(language_server) =
self.register_language_server(worktree.abs_path(), &language, cx)
{
worktree.register_language(&language, &language_server);
}
}
});
worktree.update(cx, |worktree, cx| {
if let Some(worktree) = worktree.as_local_mut() {
let abs_path = worktree.abs_path().join(&path.path);
let language = self.languages.select_language(&abs_path).cloned();
if let Some(language) = language {
if let Some(language_server) =
self.register_language_server(worktree.abs_path(), &language, cx)
{
worktree.register_language(&language, &language_server);
}
}
}
worktree.open_buffer(path.path, cx)
})
let worktree = if let Some(worktree) = self.worktree_for_id(path.worktree_id, cx) {
worktree
} else {
cx.spawn(|_, _| async move { Err(anyhow!("no such worktree")) })
}
return cx.spawn(|_, _| async move { Err(anyhow!("no such worktree")) });
};
let buffer_task = worktree.update(cx, |worktree, cx| worktree.open_buffer(path.path, cx));
cx.spawn(|this, mut cx| async move {
let (buffer, buffer_is_new) = buffer_task.await?;
if buffer_is_new {
this.update(&mut cx, |this, cx| {
this.buffer_added(worktree, buffer.clone(), cx)
});
}
Ok(buffer)
})
}
pub fn save_buffer_as(
@ -504,189 +486,213 @@ impl Project {
abs_path: PathBuf,
cx: &mut ModelContext<Project>,
) -> Task<Result<()>> {
let result = self.worktree_for_abs_path(&abs_path, cx);
let languages = self.languages.clone();
let worktree_task = self.worktree_for_abs_path(&abs_path, cx);
cx.spawn(|this, mut cx| async move {
let (worktree, path) = result.await?;
let (worktree, path) = worktree_task.await?;
worktree
.update(&mut cx, |worktree, cx| {
let worktree = worktree.as_local_mut().unwrap();
let language = languages.select_language(&abs_path);
if let Some(language) = language {
if let Some(language_server) = this.update(cx, |this, cx| {
this.register_language_server(worktree.abs_path(), language, cx)
}) {
worktree.register_language(&language, &language_server);
}
}
worktree.save_buffer_as(buffer.clone(), path, cx)
worktree
.as_local_mut()
.unwrap()
.save_buffer_as(buffer.clone(), path, cx)
})
.await?;
this.update(&mut cx, |this, cx| this.buffer_added(worktree, buffer, cx));
Ok(())
})
}
fn register_language_server(
fn buffer_added(
&mut self,
worktree_abs_path: Arc<Path>,
language: &Arc<Language>,
worktree: ModelHandle<Worktree>,
buffer: ModelHandle<Buffer>,
cx: &mut ModelContext<Self>,
) -> Option<()> {
// Set the buffer's language
let full_path = buffer.read(cx).file()?.full_path();
let language = self.languages.select_language(&full_path)?.clone();
buffer.update(cx, |buffer, cx| {
buffer.set_language(Some(language.clone()), cx);
});
// For local worktrees, start a language server if needed.
let worktree = worktree.read(cx);
let worktree_id = worktree.id();
let worktree_abs_path = worktree.as_local()?.abs_path().clone();
let language_server = match self
.language_servers
.entry((worktree_id, language.name().to_string()))
{
hash_map::Entry::Occupied(e) => Some(e.get().clone()),
hash_map::Entry::Vacant(e) => Self::start_language_server(
self.client.clone(),
language,
worktree_id,
&worktree_abs_path,
cx,
)
.map(|server| e.insert(server).clone()),
};
buffer.update(cx, |buffer, cx| {
buffer.set_language_server(language_server, cx)
});
None
}
fn start_language_server(
rpc: Arc<Client>,
language: Arc<Language>,
worktree_id: WorktreeId,
worktree_path: &Path,
cx: &mut ModelContext<Self>,
) -> Option<Arc<LanguageServer>> {
if let Some(server) = self
.language_servers
.get(&(worktree_abs_path.clone(), language.name().to_string()))
{
return Some(server.clone());
}
if let Some(language_server) = language
.start_server(&worktree_abs_path, cx)
let language_server = language
.start_server(worktree_path, cx)
.log_err()
.flatten()
{
enum DiagnosticProgress {
Updating,
Updated,
}
.flatten()?;
let disk_based_sources = language
.disk_based_diagnostic_sources()
.cloned()
.unwrap_or_default();
let disk_based_diagnostics_progress_token =
language.disk_based_diagnostics_progress_token().cloned();
let (diagnostics_tx, diagnostics_rx) = smol::channel::unbounded();
let (disk_based_diagnostics_done_tx, disk_based_diagnostics_done_rx) =
smol::channel::unbounded();
language_server
.on_notification::<lsp::notification::PublishDiagnostics, _>(move |params| {
smol::block_on(diagnostics_tx.send(params)).ok();
})
.detach();
cx.spawn_weak(|this, mut cx| {
let has_disk_based_diagnostic_progress_token =
disk_based_diagnostics_progress_token.is_some();
let disk_based_diagnostics_done_tx = disk_based_diagnostics_done_tx.clone();
async move {
while let Ok(diagnostics) = diagnostics_rx.recv().await {
if let Some(handle) = cx.read(|cx| this.upgrade(cx)) {
handle.update(&mut cx, |this, cx| {
if !has_disk_based_diagnostic_progress_token {
smol::block_on(
disk_based_diagnostics_done_tx
.send(DiagnosticProgress::Updating),
)
.ok();
}
this.update_diagnostics(diagnostics, &disk_based_sources, cx)
.log_err();
if !has_disk_based_diagnostic_progress_token {
smol::block_on(
disk_based_diagnostics_done_tx
.send(DiagnosticProgress::Updated),
)
.ok();
}
})
} else {
break;
}
}
}
})
.detach();
let mut pending_disk_based_diagnostics: i32 = 0;
language_server
.on_notification::<lsp::notification::Progress, _>(move |params| {
let token = match params.token {
lsp::NumberOrString::Number(_) => None,
lsp::NumberOrString::String(token) => Some(token),
};
if token == disk_based_diagnostics_progress_token {
match params.value {
lsp::ProgressParamsValue::WorkDone(progress) => match progress {
lsp::WorkDoneProgress::Begin(_) => {
if pending_disk_based_diagnostics == 0 {
smol::block_on(
disk_based_diagnostics_done_tx
.send(DiagnosticProgress::Updating),
)
.ok();
}
pending_disk_based_diagnostics += 1;
}
lsp::WorkDoneProgress::End(_) => {
pending_disk_based_diagnostics -= 1;
if pending_disk_based_diagnostics == 0 {
smol::block_on(
disk_based_diagnostics_done_tx
.send(DiagnosticProgress::Updated),
)
.ok();
}
}
_ => {}
},
}
}
})
.detach();
let rpc = self.client.clone();
cx.spawn_weak(|this, mut cx| async move {
while let Ok(progress) = disk_based_diagnostics_done_rx.recv().await {
if let Some(handle) = cx.read(|cx| this.upgrade(cx)) {
match progress {
DiagnosticProgress::Updating => {
let message = handle.update(&mut cx, |this, cx| {
cx.emit(Event::DiskBasedDiagnosticsUpdating);
let this = this.as_local().unwrap();
this.share.as_ref().map(|share| {
proto::DiskBasedDiagnosticsUpdating {
project_id: share.project_id,
worktree_id: this.id().to_proto(),
}
})
});
if let Some(message) = message {
rpc.send(message).await.log_err();
}
}
DiagnosticProgress::Updated => {
let message = handle.update(&mut cx, |this, cx| {
cx.emit(Event::DiskBasedDiagnosticsUpdated);
let this = this.as_local().unwrap();
this.share.as_ref().map(|share| {
proto::DiskBasedDiagnosticsUpdated {
project_id: share.project_id,
worktree_id: this.id().to_proto(),
}
})
});
if let Some(message) = message {
rpc.send(message).await.log_err();
}
}
}
} else {
break;
}
}
})
.detach();
self.language_servers.insert(
(worktree_abs_path.clone(), language.name().to_string()),
language_server.clone(),
);
Some(language_server)
} else {
None
enum DiagnosticProgress {
Updating,
Publish(lsp::PublishDiagnosticsParams),
Updated,
}
let disk_based_sources = language
.disk_based_diagnostic_sources()
.cloned()
.unwrap_or_default();
let disk_based_diagnostics_progress_token =
language.disk_based_diagnostics_progress_token().cloned();
let has_disk_based_diagnostic_progress_token =
disk_based_diagnostics_progress_token.is_some();
let (diagnostics_tx, diagnostics_rx) = smol::channel::unbounded();
language_server
.on_notification::<lsp::notification::PublishDiagnostics, _>({
let diagnostics_tx = diagnostics_tx.clone();
move |params| {
if !has_disk_based_diagnostic_progress_token {
smol::block_on(diagnostics_tx.send(DiagnosticProgress::Updating)).ok();
}
smol::block_on(diagnostics_tx.send(DiagnosticProgress::Publish(params))).ok();
if !has_disk_based_diagnostic_progress_token {
smol::block_on(diagnostics_tx.send(DiagnosticProgress::Updated)).ok();
}
}
})
.detach();
let mut pending_disk_based_diagnostics: i32 = 0;
language_server
.on_notification::<lsp::notification::Progress, _>(move |params| {
let token = match params.token {
lsp::NumberOrString::Number(_) => None,
lsp::NumberOrString::String(token) => Some(token),
};
if token == disk_based_diagnostics_progress_token {
match params.value {
lsp::ProgressParamsValue::WorkDone(progress) => match progress {
lsp::WorkDoneProgress::Begin(_) => {
if pending_disk_based_diagnostics == 0 {
smol::block_on(
diagnostics_tx.send(DiagnosticProgress::Updating),
)
.ok();
}
pending_disk_based_diagnostics += 1;
}
lsp::WorkDoneProgress::End(_) => {
pending_disk_based_diagnostics -= 1;
if pending_disk_based_diagnostics == 0 {
smol::block_on(
diagnostics_tx.send(DiagnosticProgress::Updated),
)
.ok();
}
}
_ => {}
},
}
}
})
.detach();
cx.spawn_weak(|this, mut cx| async move {
while let Ok(message) = diagnostics_rx.recv().await {
let this = cx.read(|cx| this.upgrade(cx))?;
match message {
DiagnosticProgress::Updating => {
let project_id = this.update(&mut cx, |this, cx| {
cx.emit(Event::DiskBasedDiagnosticsStarted);
this.remote_id()
});
if let Some(project_id) = project_id {
rpc.send(proto::DiskBasedDiagnosticsUpdating {
project_id,
worktree_id: worktree_id.to_proto(),
})
.await
.log_err();
}
}
DiagnosticProgress::Publish(params) => {
this.update(&mut cx, |this, cx| {
this.update_diagnostics(params, &disk_based_sources, cx)
.log_err();
});
}
DiagnosticProgress::Updated => {
let project_id = this.update(&mut cx, |this, cx| {
cx.emit(Event::DiskBasedDiagnosticsFinished);
this.remote_id()
});
if let Some(project_id) = project_id {
rpc.send(proto::DiskBasedDiagnosticsUpdated {
project_id,
worktree_id: worktree_id.to_proto(),
})
.await
.log_err();
}
}
}
}
Some(())
})
.detach();
Some(language_server)
}
fn update_diagnostics(
&mut self,
diagnostics: lsp::PublishDiagnosticsParams,
disk_based_sources: &HashSet<String>,
cx: &mut ModelContext<Self>,
) -> Result<()> {
let path = diagnostics
.uri
.to_file_path()
.map_err(|_| anyhow!("URI is not a file"))?;
for tree in &self.worktrees {
let relative_path = tree.update(cx, |tree, _| {
path.strip_prefix(tree.as_local()?.abs_path()).ok()
});
if let Some(relative_path) = relative_path {
return tree.update(cx, |tree, cx| {
tree.as_local_mut().unwrap().update_diagnostics(
relative_path.into(),
diagnostics,
disk_based_sources,
cx,
)
});
}
}
todo!()
}
pub fn worktree_for_abs_path(
@ -726,12 +732,10 @@ impl Project {
let fs = self.fs.clone();
let client = self.client.clone();
let user_store = self.user_store.clone();
let languages = self.languages.clone();
let path = Arc::from(abs_path.as_ref());
cx.spawn(|project, mut cx| async move {
let worktree =
Worktree::open_local(client.clone(), user_store, path, fs, languages, &mut cx)
.await?;
Worktree::open_local(client.clone(), user_store, path, fs, &mut cx).await?;
let (remote_project_id, is_shared) = project.update(&mut cx, |project, cx| {
project.add_worktree(worktree.clone(), cx);
@ -936,13 +940,11 @@ impl Project {
.worktree
.ok_or_else(|| anyhow!("invalid worktree"))?;
let user_store = self.user_store.clone();
let languages = self.languages.clone();
cx.spawn(|this, mut cx| {
async move {
let worktree = Worktree::remote(
remote_id, replica_id, worktree, client, user_store, languages, &mut cx,
)
.await?;
let worktree =
Worktree::remote(remote_id, replica_id, worktree, client, user_store, &mut cx)
.await?;
this.update(&mut cx, |this, cx| this.add_worktree(worktree, cx));
Ok(())
}
@ -1248,6 +1250,25 @@ impl Entity for Project {
}
}
}
fn app_will_quit(
&mut self,
_: &mut MutableAppContext,
) -> Option<std::pin::Pin<Box<dyn 'static + Future<Output = ()>>>> {
use futures::FutureExt;
let shutdown_futures = self
.language_servers
.drain()
.filter_map(|(_, server)| server.shutdown())
.collect::<Vec<_>>();
Some(
async move {
futures::future::join_all(shutdown_futures).await;
}
.boxed(),
)
}
}
impl Collaborator {
@ -1275,8 +1296,12 @@ mod tests {
use super::*;
use client::test::FakeHttpClient;
use fs::RealFs;
use gpui::TestAppContext;
use language::LanguageRegistry;
use futures::StreamExt;
use gpui::{test::subscribe, TestAppContext};
use language::{
tree_sitter_rust, Diagnostic, LanguageConfig, LanguageRegistry, LanguageServerConfig, Point,
};
use lsp::Url;
use serde_json::json;
use std::{os::unix, path::PathBuf};
use util::test::temp_tree;
@ -1344,6 +1369,114 @@ mod tests {
);
}
#[gpui::test]
async fn test_language_server_diagnostics(mut cx: gpui::TestAppContext) {
let (language_server_config, mut fake_server) =
LanguageServerConfig::fake(cx.background()).await;
let progress_token = language_server_config
.disk_based_diagnostics_progress_token
.clone()
.unwrap();
let mut languages = LanguageRegistry::new();
languages.add(Arc::new(Language::new(
LanguageConfig {
name: "Rust".to_string(),
path_suffixes: vec!["rs".to_string()],
language_server: Some(language_server_config),
..Default::default()
},
Some(tree_sitter_rust::language()),
)));
let dir = temp_tree(json!({
"a.rs": "fn a() { A }",
"b.rs": "const y: i32 = 1",
}));
let http_client = FakeHttpClient::with_404_response();
let client = Client::new(http_client.clone());
let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx));
let tree = Worktree::open_local(
client,
user_store,
dir.path(),
Arc::new(RealFs),
&mut cx.to_async(),
)
.await
.unwrap();
cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
.await;
// Cause worktree to start the fake language server
let _buffer = tree
.update(&mut cx, |tree, cx| tree.open_buffer("b.rs", cx))
.await
.unwrap();
let mut events = subscribe(&tree, &mut cx);
fake_server.start_progress(&progress_token).await;
assert_eq!(
events.next().await.unwrap(),
Event::DiskBasedDiagnosticsUpdating
);
fake_server.start_progress(&progress_token).await;
fake_server.end_progress(&progress_token).await;
fake_server.start_progress(&progress_token).await;
fake_server
.notify::<lsp::notification::PublishDiagnostics>(lsp::PublishDiagnosticsParams {
uri: Url::from_file_path(dir.path().join("a.rs")).unwrap(),
version: None,
diagnostics: vec![lsp::Diagnostic {
range: lsp::Range::new(lsp::Position::new(0, 9), lsp::Position::new(0, 10)),
severity: Some(lsp::DiagnosticSeverity::ERROR),
message: "undefined variable 'A'".to_string(),
..Default::default()
}],
})
.await;
assert_eq!(
events.next().await.unwrap(),
Event::DiagnosticsUpdated(Arc::from(Path::new("a.rs")))
);
fake_server.end_progress(&progress_token).await;
fake_server.end_progress(&progress_token).await;
assert_eq!(
events.next().await.unwrap(),
Event::DiskBasedDiagnosticsUpdated
);
let (buffer, _) = tree
.update(&mut cx, |tree, cx| tree.open_buffer("a.rs", cx))
.await
.unwrap();
buffer.read_with(&cx, |buffer, _| {
let snapshot = buffer.snapshot();
let diagnostics = snapshot
.diagnostics_in_range::<_, Point>(0..buffer.len())
.collect::<Vec<_>>();
assert_eq!(
diagnostics,
&[DiagnosticEntry {
range: Point::new(0, 9)..Point::new(0, 10),
diagnostic: Diagnostic {
severity: lsp::DiagnosticSeverity::ERROR,
message: "undefined variable 'A'".to_string(),
group_id: 0,
is_primary: true,
..Default::default()
}
}]
)
});
}
#[gpui::test]
async fn test_search_worktree_without_files(mut cx: gpui::TestAppContext) {
let dir = temp_tree(json!({

File diff suppressed because it is too large Load Diff

View File

@ -1213,7 +1213,8 @@ mod tests {
let buffer_b = worktree_b
.update(&mut cx_b, |worktree, cx| worktree.open_buffer("b.txt", cx))
.await
.unwrap();
.unwrap()
.0;
let buffer_b = cx_b.add_model(|cx| MultiBuffer::singleton(buffer_b, cx));
buffer_b.read_with(&cx_b, |buf, cx| {
assert_eq!(buf.read(cx).text(), "b-contents")
@ -1222,7 +1223,8 @@ mod tests {
let buffer_a = worktree_a
.update(&mut cx_a, |tree, cx| tree.open_buffer("b.txt", cx))
.await
.unwrap();
.unwrap()
.0;
let editor_b = cx_b.add_view(window_b, |cx| {
Editor::for_buffer(buffer_b, Arc::new(|cx| EditorSettings::test(cx)), cx)
@ -1436,11 +1438,13 @@ mod tests {
let buffer_b = worktree_b
.update(&mut cx_b, |tree, cx| tree.open_buffer("file1", cx))
.await
.unwrap();
.unwrap()
.0;
let buffer_c = worktree_c
.update(&mut cx_c, |tree, cx| tree.open_buffer("file1", cx))
.await
.unwrap();
.unwrap()
.0;
buffer_b.update(&mut cx_b, |buf, cx| buf.edit([0..0], "i-am-b, ", cx));
buffer_c.update(&mut cx_c, |buf, cx| buf.edit([0..0], "i-am-c, ", cx));
@ -1448,7 +1452,8 @@ mod tests {
let buffer_a = worktree_a
.update(&mut cx_a, |tree, cx| tree.open_buffer("file1", cx))
.await
.unwrap();
.unwrap()
.0;
buffer_a
.condition(&mut cx_a, |buf, _| buf.text() == "i-am-c, i-am-b, ")
@ -1574,7 +1579,8 @@ mod tests {
let buffer_b = worktree_b
.update(&mut cx_b, |worktree, cx| worktree.open_buffer("a.txt", cx))
.await
.unwrap();
.unwrap()
.0;
let mtime = buffer_b.read_with(&cx_b, |buf, _| buf.file().unwrap().mtime());
buffer_b.update(&mut cx_b, |buf, cx| buf.edit([0..0], "world ", cx));
@ -1669,7 +1675,8 @@ mod tests {
let buffer_a = worktree_a
.update(&mut cx_a, |tree, cx| tree.open_buffer("a.txt", cx))
.await
.unwrap();
.unwrap()
.0;
// Start opening the same buffer as client B
let buffer_b = cx_b
@ -1681,7 +1688,7 @@ mod tests {
buffer_a.update(&mut cx_a, |buf, cx| buf.edit([0..0], "z", cx));
let text = buffer_a.read_with(&cx_a, |buf, _| buf.text());
let buffer_b = buffer_b.await.unwrap();
let buffer_b = buffer_b.await.unwrap().0;
buffer_b.condition(&cx_b, |buf, _| buf.text() == text).await;
}
@ -2016,7 +2023,8 @@ mod tests {
.background()
.spawn(worktree_b.update(&mut cx_b, |worktree, cx| worktree.open_buffer("a.rs", cx)))
.await
.unwrap();
.unwrap()
.0;
buffer_b.read_with(&cx_b, |buffer, _| {
assert_eq!(
@ -2128,7 +2136,8 @@ mod tests {
.background()
.spawn(worktree_b.update(&mut cx_b, |worktree, cx| worktree.open_buffer("a.rs", cx)))
.await
.unwrap();
.unwrap()
.0;
let format = buffer_b.update(&mut cx_b, |buffer, cx| buffer.format(cx));
let (request_id, _) = fake_language_server