Skip to content

Commit

Permalink
project: Allow running multiple instances of a single language server…
Browse files Browse the repository at this point in the history
… within a single worktree (#23473)

This PR introduces a new entity called Project Tree which is responsible
for finding subprojects within a worktree;
a subproject is a language-specific subset of a worktree which should be
accurately tracked on the language server side. We'll have an ability to
set multiple disjoint workspaceFolders on language server side OR spawn
multiple instances of a single language server (which will be the case
with e.g. Python language servers, as they need to interact with
multiple disjoint virtual environments).
Project Tree assumes that projects of the same LspAdapter kind cannot
overlap. Additionally project nesting is not allowed within the scope of
a single LspAdapter.

Closes #5108
Re-lands #22182 which I had to revert due to merging it into todays
Preview.

Release Notes:

- Language servers now track their working directory more accurately.

---------

Co-authored-by: João <[email protected]>
  • Loading branch information
osiewicz and João authored Jan 22, 2025
1 parent 2c2a3ef commit 08b3c03
Show file tree
Hide file tree
Showing 29 changed files with 2,154 additions and 946 deletions.
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 4 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -372,7 +372,7 @@ async-tungstenite = "0.28"
async-watch = "0.3.1"
async_zip = { version = "0.0.17", features = ["deflate", "deflate64"] }
base64 = "0.22"
bitflags = "2.6.0"
bitflags = "2.8.0"
blade-graphics = { git = "https://github.com/kvark/blade", rev = "091a8401033847bb9b6ace3fcf70448d069621c5" }
blade-macros = { git = "https://github.com/kvark/blade", rev = "091a8401033847bb9b6ace3fcf70448d069621c5" }
blade-util = { git = "https://github.com/kvark/blade", rev = "091a8401033847bb9b6ace3fcf70448d069621c5" }
Expand Down Expand Up @@ -420,12 +420,13 @@ libc = "0.2"
libsqlite3-sys = { version = "0.30.1", features = ["bundled"] }
linkify = "0.10.0"
livekit = { git = "https://github.com/zed-industries/livekit-rust-sdks", rev="060964da10574cd9bf06463a53bf6e0769c5c45e", features = ["dispatcher", "services-dispatcher", "rustls-tls-native-roots"], default-features = false }
log = { version = "0.4.16", features = ["kv_unstable_serde", "serde"] }
log = { version = "0.4.25", features = ["kv_unstable_serde", "serde"] }
markup5ever_rcdom = "0.3.0"
nanoid = "0.4"
nbformat = { version = "0.10.0" }
nix = "0.29"
num-format = "0.4.4"
once_cell = "1.20"
ordered-float = "2.1.1"
palette = { version = "0.7.5", default-features = false, features = ["std"] }
parking_lot = "0.12.1"
Expand Down Expand Up @@ -508,7 +509,7 @@ tree-sitter = { version = "0.23", features = ["wasm"] }
tree-sitter-bash = "0.23"
tree-sitter-c = "0.23"
tree-sitter-cpp = "0.23"
tree-sitter-css = "0.23"
tree-sitter-css = "0.23.2"
tree-sitter-elixir = "0.3"
tree-sitter-embedded-template = "0.23.0"
tree-sitter-go = "0.23"
Expand Down
4 changes: 2 additions & 2 deletions crates/collab/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ reqwest_client.workspace = true
rpc.workspace = true
rustc-demangle.workspace = true
scrypt = "0.11"
sea-orm = { version = "1.1.0-rc.1", features = ["sqlx-postgres", "postgres-array", "runtime-tokio-rustls", "with-uuid"] }
sea-orm = { version = "1.1.4", features = ["sqlx-postgres", "postgres-array", "runtime-tokio-rustls", "with-uuid"] }
semantic_version.workspace = true
semver.workspace = true
serde.workspace = true
Expand Down Expand Up @@ -116,7 +116,7 @@ release_channel.workspace = true
remote = { workspace = true, features = ["test-support"] }
remote_server.workspace = true
rpc = { workspace = true, features = ["test-support"] }
sea-orm = { version = "1.1.0-rc.1", features = ["sqlx-sqlite"] }
sea-orm = { version = "1.1.4", features = ["sqlx-sqlite"] }
serde_json.workspace = true
session = { workspace = true, features = ["test-support"] }
settings = { workspace = true, features = ["test-support"] }
Expand Down
6 changes: 4 additions & 2 deletions crates/copilot/src/copilot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -461,12 +461,14 @@ impl Copilot {
.on_notification::<StatusNotification, _>(|_, _| { /* Silence the notification */ })
.detach();

let initialize_params = None;
let configuration = lsp::DidChangeConfigurationParams {
settings: Default::default(),
};
let server = cx
.update(|cx| server.initialize(initialize_params, configuration.into(), cx))?
.update(|cx| {
let params = server.default_initialize_params(cx);
server.initialize(params, configuration.into(), cx)
})?
.await?;

let status = server
Expand Down
80 changes: 45 additions & 35 deletions crates/editor/src/editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12476,28 +12476,27 @@ impl Editor {
cx.emit(SearchEvent::MatchesInvalidated);
if *singleton_buffer_edited {
if let Some(project) = &self.project {
let project = project.read(cx);
#[allow(clippy::mutable_key_type)]
let languages_affected = multibuffer
.read(cx)
.all_buffers()
.into_iter()
.filter_map(|buffer| {
let buffer = buffer.read(cx);
let language = buffer.language()?;
if project.is_local()
&& project
.language_servers_for_local_buffer(buffer, cx)
.count()
== 0
{
None
} else {
Some(language)
}
})
.cloned()
.collect::<HashSet<_>>();
let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
multibuffer
.all_buffers()
.into_iter()
.filter_map(|buffer| {
buffer.update(cx, |buffer, cx| {
let language = buffer.language()?;
let should_discard = project.update(cx, |project, cx| {
project.is_local()
&& project.for_language_servers_for_local_buffer(
buffer,
|it| it.count() == 0,
cx,
)
});
should_discard.not().then_some(language.clone())
})
})
.collect::<HashSet<_>>()
});
if !languages_affected.is_empty() {
self.refresh_inlay_hints(
InlayHintRefreshReason::BufferEdited(languages_affected),
Expand Down Expand Up @@ -13051,15 +13050,18 @@ impl Editor {
self.handle_input(text, cx);
}

pub fn supports_inlay_hints(&self, cx: &AppContext) -> bool {
pub fn supports_inlay_hints(&self, cx: &mut AppContext) -> bool {
let Some(provider) = self.semantics_provider.as_ref() else {
return false;
};

let mut supports = false;
self.buffer().read(cx).for_each_buffer(|buffer| {
supports |= provider.supports_inlay_hints(buffer, cx);
self.buffer().update(cx, |this, cx| {
this.for_each_buffer(|buffer| {
supports |= provider.supports_inlay_hints(buffer, cx);
})
});

supports
}

Expand Down Expand Up @@ -13671,7 +13673,7 @@ pub trait SemanticsProvider {
cx: &mut AppContext,
) -> Option<Task<anyhow::Result<InlayHint>>>;

fn supports_inlay_hints(&self, buffer: &Model<Buffer>, cx: &AppContext) -> bool;
fn supports_inlay_hints(&self, buffer: &Model<Buffer>, cx: &mut AppContext) -> bool;

fn document_highlights(
&self,
Expand Down Expand Up @@ -14056,17 +14058,25 @@ impl SemanticsProvider for Model<Project> {
}))
}

fn supports_inlay_hints(&self, buffer: &Model<Buffer>, cx: &AppContext) -> bool {
fn supports_inlay_hints(&self, buffer: &Model<Buffer>, cx: &mut AppContext) -> bool {
// TODO: make this work for remote projects
self.read(cx)
.language_servers_for_local_buffer(buffer.read(cx), cx)
.any(
|(_, server)| match server.capabilities().inlay_hint_provider {
Some(lsp::OneOf::Left(enabled)) => enabled,
Some(lsp::OneOf::Right(_)) => true,
None => false,
},
)
buffer.update(cx, |buffer, cx| {
self.update(cx, |this, cx| {
this.for_language_servers_for_local_buffer(
buffer,
|mut it| {
it.any(
|(_, server)| match server.capabilities().inlay_hint_provider {
Some(lsp::OneOf::Left(enabled)) => enabled,
Some(lsp::OneOf::Right(_)) => true,
None => false,
},
)
},
cx,
)
})
})
}

fn inlay_hints(
Expand Down
7 changes: 3 additions & 4 deletions crates/editor/src/editor_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6839,7 +6839,7 @@ async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) {
let fs = FakeFs::new(cx.executor());
fs.insert_file("/file.rs", Default::default()).await;

let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
let project = Project::test(fs, ["/".as_ref()], cx).await;

let language_registry = project.read_with(cx, |project, _| project.languages().clone());
language_registry.add(rust_lang());
Expand Down Expand Up @@ -7193,7 +7193,7 @@ async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) {
let fs = FakeFs::new(cx.executor());
fs.insert_file("/file.rs", Default::default()).await;

let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
let project = Project::test(fs, ["/".as_ref()], cx).await;

let language_registry = project.read_with(cx, |project, _| project.languages().clone());
language_registry.add(rust_lang());
Expand Down Expand Up @@ -7327,7 +7327,7 @@ async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) {
let fs = FakeFs::new(cx.executor());
fs.insert_file("/file.rs", Default::default()).await;

let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
let project = Project::test(fs, ["/".as_ref()], cx).await;

let language_registry = project.read_with(cx, |project, _| project.languages().clone());
language_registry.add(Arc::new(Language::new(
Expand Down Expand Up @@ -10742,7 +10742,6 @@ async fn test_language_server_restart_due_to_settings_change(cx: &mut gpui::Test
0,
"Should not restart LSP server on an unrelated LSP settings change"
);

update_test_project_settings(cx, |project_settings| {
project_settings.lsp.insert(
language_server_name.into(),
Expand Down
37 changes: 20 additions & 17 deletions crates/editor/src/lsp_ext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use multi_buffer::Anchor;

pub(crate) fn find_specific_language_server_in_selection<F>(
editor: &Editor,
cx: &WindowContext,
cx: &mut WindowContext,
filter_language: F,
language_server_name: &str,
) -> Option<(Anchor, Arc<Language>, LanguageServerId, Model<Buffer>)>
Expand All @@ -21,37 +21,40 @@ where
let Some(project) = &editor.project else {
return None;
};
let multibuffer = editor.buffer().read(cx);
let mut language_servers_for = HashMap::default();
editor
.selections
.disjoint_anchors()
.iter()
.filter(|selection| selection.start == selection.end)
.filter_map(|selection| Some((selection.start.buffer_id?, selection.start)))
.filter_map(|(buffer_id, trigger_anchor)| {
let buffer = multibuffer.buffer(buffer_id)?;
.find_map(|(buffer_id, trigger_anchor)| {
let buffer = editor.buffer().read(cx).buffer(buffer_id)?;
let server_id = *match language_servers_for.entry(buffer_id) {
Entry::Occupied(occupied_entry) => occupied_entry.into_mut(),
Entry::Vacant(vacant_entry) => {
let language_server_id = project
.read(cx)
.language_servers_for_local_buffer(buffer.read(cx), cx)
.find_map(|(adapter, server)| {
if adapter.name.0.as_ref() == language_server_name {
Some(server.server_id())
} else {
None
}
});
let language_server_id = buffer.update(cx, |buffer, cx| {
project.update(cx, |project, cx| {
project.for_language_servers_for_local_buffer(
buffer,
|mut it| {
it.find_map(|(adapter, server)| {
if adapter.name.0.as_ref() == language_server_name {
Some(server.server_id())
} else {
None
}
})
},
cx,
)
})
});
vacant_entry.insert(language_server_id)
}
}
.as_ref()?;

Some((buffer, trigger_anchor, server_id))
})
.find_map(|(buffer, trigger_anchor, server_id)| {
let language = buffer.read(cx).language_at(trigger_anchor.text_anchor)?;
if !filter_language(&language) {
return None;
Expand Down
2 changes: 1 addition & 1 deletion crates/editor/src/proposed_changes_editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -455,7 +455,7 @@ impl SemanticsProvider for BranchBufferSemanticsProvider {
self.0.resolve_inlay_hint(hint, buffer, server_id, cx)
}

fn supports_inlay_hints(&self, buffer: &Model<Buffer>, cx: &AppContext) -> bool {
fn supports_inlay_hints(&self, buffer: &Model<Buffer>, cx: &mut AppContext) -> bool {
if let Some(buffer) = self.to_base(&buffer, &[], cx) {
self.0.supports_inlay_hints(&buffer, cx)
} else {
Expand Down
2 changes: 1 addition & 1 deletion crates/gpui_macros/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,6 @@ proc-macro = true
doctest = false

[dependencies]
proc-macro2 = "1.0.66"
proc-macro2 = "1.0.93"
quote = "1.0.9"
syn = { version = "1.0.72", features = ["full", "extra-traits"] }
Loading

0 comments on commit 08b3c03

Please sign in to comment.