Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

project: Allow running multiple instances of a single language server within a single worktree #22182

Open
wants to merge 104 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
104 commits
Select commit Hold shift + click to select a range
db69230
Checkpoint
osiewicz Dec 13, 2024
5db0092
WIP, move ProjectTree into project
osiewicz Dec 14, 2024
bf8bbdc
Checkpoint
osiewicz Dec 16, 2024
c90753c
Checkpoint
osiewicz Dec 16, 2024
d55b992
Checkpoint
osiewicz Dec 17, 2024
95a9211
Checkpoint
osiewicz Dec 17, 2024
4e89e26
Start wiring LSPTree into LspStore
osiewicz Dec 17, 2024
d6776ba
:bug:
osiewicz Dec 17, 2024
d687d4b
Remove dbg!s
osiewicz Dec 18, 2024
cd15dac
Shredding for the new path trie
osiewicz Dec 19, 2024
78d908d
Merge branch 'main' into lsp-tree
osiewicz Dec 20, 2024
0c5f07c
Merge branch 'main' into lsp-tree
osiewicz Dec 20, 2024
909fb66
Renames
osiewicz Dec 23, 2024
cb24881
Add labels to pathtrie
osiewicz Dec 23, 2024
96cac12
Checkpoint, start wiring in pathtrie
osiewicz Dec 23, 2024
2a8bcc2
Checkpoint
osiewicz Dec 23, 2024
570839e
Document label presence
osiewicz Dec 23, 2024
b72fe37
Merge branch 'main' into lsp-tree
osiewicz Dec 25, 2024
2c77faf
Store known nodes in a tree
osiewicz Dec 26, 2024
89fa8df
Merge branch 'main' into lsp-tree
osiewicz Dec 27, 2024
bc79fea
Use lsp tree in language_servers_for_buffer
osiewicz Dec 27, 2024
e543059
Remove stray println
osiewicz Dec 27, 2024
b02fb3e
clippy
osiewicz Dec 27, 2024
3f6d00b
WIP
osiewicz Dec 28, 2024
2edaf52
Merge branch 'main' into lsp-tree
osiewicz Dec 30, 2024
64ef55d
Fix double invokation in ProjectTree::walk
osiewicz Dec 30, 2024
35338ce
Whitespace
osiewicz Dec 30, 2024
150cc03
Add partial support for changing workspace folders
osiewicz Dec 30, 2024
3524201
Split up server_id into two
osiewicz Dec 30, 2024
d3aad34
Use stored workspace folders in workspacefolder requests
osiewicz Dec 30, 2024
771da2f
Merge branch 'main' into lsp-tree
osiewicz Jan 2, 2025
39f5bfb
Add test for path trie
osiewicz Jan 2, 2025
b51c63c
Fix removing entries from path trie
osiewicz Jan 2, 2025
9cbd2c7
clippy
osiewicz Jan 2, 2025
e73c3ad
WIP
osiewicz Jan 2, 2025
4dc1565
Merge branch 'main' into lsp-tree
osiewicz Jan 4, 2025
79bec92
Checkpoint
osiewicz Jan 4, 2025
ac5dc48
Merge branch 'main' into lsp-tree
osiewicz Jan 7, 2025
37342d7
WIP, retract tracking root language servers on LspStore
osiewicz Jan 7, 2025
64553c7
Pass language server id in workspace symbols
osiewicz Jan 8, 2025
9a7099d
Compiling again with a bunch of TODOs sprinkled over
osiewicz Jan 8, 2025
ef29f3b
Get rid of path in RPC
osiewicz Jan 8, 2025
2818b3e
Send out workspace folder change notification only when the list of f…
osiewicz Jan 8, 2025
f9b4530
Merge branch 'main' into lsp-tree
osiewicz Jan 9, 2025
d12db9c
Compiling - again; (queries lsp adapters for paths to the root of a w…
osiewicz Jan 9, 2025
0d2ebfe
Remove a bunch of dbgs
osiewicz Jan 9, 2025
8497f5e
Merge branch 'main' into lsp-tree
osiewicz Jan 10, 2025
b5d0980
Query Rust adapter for worktree root paths
osiewicz Jan 11, 2025
5e4f9fb
Merge branch 'main' into lsp-tree
osiewicz Jan 13, 2025
d3750c5
Add rudimentary Rust project root detection, fix depth calculation fo…
osiewicz Jan 13, 2025
1cae50c
Remove unused import
osiewicz Jan 13, 2025
80a4ef0
Send didchangeworkspacefolders
osiewicz Jan 13, 2025
1fa944a
Allow one to save pending workspace folders even when server init is …
osiewicz Jan 13, 2025
5da7866
clippy
osiewicz Jan 13, 2025
b2242b2
Fix test build
osiewicz Jan 13, 2025
04c3858
fixup! Fix test build
osiewicz Jan 13, 2025
fceb0b5
clippy
osiewicz Jan 13, 2025
5d0b69b
clippy
osiewicz Jan 13, 2025
4aa02a4
WIP: settings refresh
osiewicz Jan 13, 2025
6fc1647
Disallow initializing the language server node if it is no longer a p…
osiewicz Jan 13, 2025
e2f89a4
Remove unused import
osiewicz Jan 13, 2025
fc0e189
Remove unused variable
osiewicz Jan 13, 2025
24cde09
fixup! Remove unused variable
osiewicz Jan 13, 2025
6d3e8aa
Add ProjectTreeEvent
osiewicz Jan 13, 2025
fb48b7b
:facepalm:
osiewicz Jan 13, 2025
0fa9656
Merge branch 'main' into lsp-tree
osiewicz Jan 13, 2025
b86add3
Merge branch 'main' into lsp-tree
osiewicz Jan 14, 2025
1acb514
Make server tree manage which adapters are queried
osiewicz Jan 14, 2025
6240663
Cleanups for clippy
osiewicz Jan 14, 2025
7cdebb6
Clippy and cleanups
osiewicz Jan 15, 2025
7b23497
fixup! Clippy and cleanups
osiewicz Jan 15, 2025
4420b53
clippy
osiewicz Jan 15, 2025
25edee6
clippy
osiewicz Jan 15, 2025
3a24cda
Come on clippy..
osiewicz Jan 15, 2025
42283ae
Make stop_local_language_server take just the server id as the argument
osiewicz Jan 15, 2025
019b285
Make Rust project finder consider outermost Cargo.toml, not the inner…
osiewicz Jan 15, 2025
85d4bb4
lsp_log: Print registered workspace folders
osiewicz Jan 15, 2025
ff35b69
WIP: settings handled proper
osiewicz Jan 15, 2025
fffd542
Merge branch 'main' into lsp-tree
osiewicz Jan 15, 2025
4b3888f
Compiling again
osiewicz Jan 16, 2025
c492519
Whoopsie daisy
osiewicz Jan 16, 2025
d6facd7
Get rid of language_registry changes
osiewicz Jan 16, 2025
9fc3f92
Track languages for which a given language server was enabled
osiewicz Jan 16, 2025
0874487
Store LSP settings on the server tree itself.
osiewicz Jan 16, 2025
22ccb16
Cleanup
osiewicz Jan 16, 2025
982d3b2
Merge branch 'main' into lsp-tree
osiewicz Jan 17, 2025
91ff26b
WIP
osiewicz Jan 17, 2025
bbb78e6
Merge branch 'main' into lsp-tree
osiewicz Jan 18, 2025
0f72856
Merge branch 'main' into lsp-tree
osiewicz Jan 19, 2025
8afbe7b
Restarting the language server
osiewicz Jan 20, 2025
64dc02c
Progress
osiewicz Jan 20, 2025
d551891
Do not double-spawn the language server
osiewicz Jan 20, 2025
49e2b60
Track registered buffers on the language server itself
osiewicz Jan 20, 2025
084ca2f
WIP
osiewicz Jan 20, 2025
ae1734d
Fix test_extension_store_with_test_extension
osiewicz Jan 21, 2025
ae0625f
Remove dbgs
osiewicz Jan 21, 2025
1946525
clippy
osiewicz Jan 21, 2025
b0ca53a
remove unused methods
osiewicz Jan 21, 2025
56a49a3
Merge branch 'main' into lsp-tree
osiewicz Jan 21, 2025
aa88541
Merge branch 'main' into lsp-tree
osiewicz Jan 21, 2025
02ca65b
Launch new servers when they're about to be registered with servers
osiewicz Jan 21, 2025
858df44
Remove unused variables
osiewicz Jan 21, 2025
cdea6f2
test_language_server_restart_due_to_settings_change
osiewicz Jan 21, 2025
86a05e1
Adjust test data for test_definition
osiewicz Jan 21, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -370,7 +370,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 @@ -418,12 +418,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 @@ -505,7 +506,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 @@ -115,7 +115,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 @@ -12420,28 +12420,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 @@ -12995,15 +12994,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 @@ -13615,7 +13617,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 @@ -14000,17 +14002,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
1 change: 0 additions & 1 deletion crates/editor/src/editor_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10500,7 +10500,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"] }
49 changes: 48 additions & 1 deletion crates/language/src/language.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
use serde_json::Value;
use settings::WorktreeId;
use smol::future::FutureExt as _;
use std::num::NonZeroU32;
use std::{
any::Any,
ffi::OsStr,
Expand All @@ -61,6 +60,7 @@ use std::{
Arc, LazyLock,
},
};
use std::{num::NonZeroU32, sync::OnceLock};
use syntax_map::{QueryCursorHandle, SyntaxSnapshot};
use task::RunnableTag;
pub use task_context::{ContextProvider, RunnableRange};
Expand Down Expand Up @@ -163,6 +163,7 @@ pub struct CachedLspAdapter {
pub adapter: Arc<dyn LspAdapter>,
pub reinstall_attempt_count: AtomicU64,
cached_binary: futures::lock::Mutex<Option<LanguageServerBinary>>,
attach_kind: OnceLock<Attach>,
}

impl Debug for CachedLspAdapter {
Expand Down Expand Up @@ -198,6 +199,7 @@ impl CachedLspAdapter {
adapter,
cached_binary: Default::default(),
reinstall_attempt_count: AtomicU64::new(0),
attach_kind: Default::default(),
})
}

Expand Down Expand Up @@ -259,6 +261,38 @@ impl CachedLspAdapter {
.cloned()
.unwrap_or_else(|| language_name.lsp_id())
}
pub fn find_project_root(
&self,
path: &Path,
ancestor_depth: usize,
delegate: &Arc<dyn LspAdapterDelegate>,
) -> Option<Arc<Path>> {
self.adapter
.find_project_root(path, ancestor_depth, delegate)
}
pub fn attach_kind(&self) -> Attach {
*self.attach_kind.get_or_init(|| self.adapter.attach_kind())
}
}

#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Attach {
/// Create a single language server instance per subproject root.
InstancePerRoot,
/// Use one shared language server instance for all subprojects within a project.
Shared,
}

impl Attach {
pub fn root_path(
&self,
root_subproject_path: (WorktreeId, Arc<Path>),
) -> (WorktreeId, Arc<Path>) {
match self {
Attach::InstancePerRoot => root_subproject_path,
Attach::Shared => (root_subproject_path.0, Arc::from(Path::new(""))),
}
}
}

/// [`LspAdapterDelegate`] allows [`LspAdapter]` implementations to interface with the application
Expand Down Expand Up @@ -505,6 +539,19 @@ pub trait LspAdapter: 'static + Send + Sync {
fn prepare_initialize_params(&self, original: InitializeParams) -> Result<InitializeParams> {
Ok(original)
}
fn attach_kind(&self) -> Attach {
Attach::Shared
}
fn find_project_root(
&self,

_path: &Path,
_ancestor_depth: usize,
_: &Arc<dyn LspAdapterDelegate>,
) -> Option<Arc<Path>> {
// By default all language servers are rooted at the root of the worktree.
Some(Arc::from("".as_ref()))
}
}

async fn try_fetch_server_binary<L: LspAdapter + 'static + Send + Sync + ?Sized>(
Expand Down
Loading
Loading