From 3d7e012e09f569e731e0a425dda5dc365e222b45 Mon Sep 17 00:00:00 2001 From: Bennet Bo Fenner Date: Fri, 4 Jul 2025 17:11:38 +0200 Subject: [PATCH 01/13] gemini: Fix issue with builtin tool schemas (#33917) Closes #33894 After #33635 Gemini Integration was broken because we now produce `const` fields for enums, which are not supported. Changing this to `openapi3` fixes the issue. Release Notes: - Fixed an issue where Gemini Models would not work because of incompatible tool schemas --- crates/assistant_tools/src/schema.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/crates/assistant_tools/src/schema.rs b/crates/assistant_tools/src/schema.rs index 888e11de4e83df853d5d1c252d30cecf84c701a2..10a8bf0acd99131d2c0a80411072f312c9a42f50 100644 --- a/crates/assistant_tools/src/schema.rs +++ b/crates/assistant_tools/src/schema.rs @@ -25,9 +25,7 @@ fn schema_to_json( fn root_schema_for(format: LanguageModelToolSchemaFormat) -> Schema { let mut generator = match format { LanguageModelToolSchemaFormat::JsonSchema => SchemaSettings::draft07().into_generator(), - // TODO: Gemini docs mention using a subset of OpenAPI 3, so this may benefit from using - // `SchemaSettings::openapi3()`. - LanguageModelToolSchemaFormat::JsonSchemaSubset => SchemaSettings::draft07() + LanguageModelToolSchemaFormat::JsonSchemaSubset => SchemaSettings::openapi3() .with(|settings| { settings.meta_schema = None; settings.inline_subschemas = true; From 59cdea00c5d6f9073a17a4fc4d38c6742b55b570 Mon Sep 17 00:00:00 2001 From: Bennet Bo Fenner Date: Fri, 4 Jul 2025 18:27:11 +0200 Subject: [PATCH 02/13] agent: Fix context server restart when settings unchanged (#33920) Closes #33891 Release Notes: - agent: Fix an issue where configuring an MCP server would not restart the underlying server correctly --- .../configure_context_server_modal.rs | 28 +++++++++++++++---- 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/crates/agent_ui/src/agent_configuration/configure_context_server_modal.rs b/crates/agent_ui/src/agent_configuration/configure_context_server_modal.rs index 299f3cee34b1c7635c3c0a8f46a52cc730993b01..ba0021c33ca32c50351387ab290bf33ce604b2e4 100644 --- a/crates/agent_ui/src/agent_configuration/configure_context_server_modal.rs +++ b/crates/agent_ui/src/agent_configuration/configure_context_server_modal.rs @@ -379,6 +379,14 @@ impl ConfigureContextServerModal { }; self.state = State::Waiting; + + let existing_server = self.context_server_store.read(cx).get_running_server(&id); + if existing_server.is_some() { + self.context_server_store.update(cx, |store, cx| { + store.stop_server(&id, cx).log_err(); + }); + } + let wait_for_context_server_task = wait_for_context_server(&self.context_server_store, id.clone(), cx); cx.spawn({ @@ -399,13 +407,21 @@ impl ConfigureContextServerModal { }) .detach(); - // When we write the settings to the file, the context server will be restarted. - workspace.update(cx, |workspace, cx| { - let fs = workspace.app_state().fs.clone(); - update_settings_file::(fs.clone(), cx, |project_settings, _| { - project_settings.context_servers.insert(id.0, settings); + let settings_changed = + ProjectSettings::get_global(cx).context_servers.get(&id.0) != Some(&settings); + + if settings_changed { + // When we write the settings to the file, the context server will be restarted. + workspace.update(cx, |workspace, cx| { + let fs = workspace.app_state().fs.clone(); + update_settings_file::(fs.clone(), cx, |project_settings, _| { + project_settings.context_servers.insert(id.0, settings); + }); }); - }); + } else if let Some(existing_server) = existing_server { + self.context_server_store + .update(cx, |store, cx| store.start_server(existing_server, cx)); + } } fn cancel(&mut self, _: &menu::Cancel, cx: &mut Context) { From 8fecacfbaa7d388cff16b723338d64cc98fdb93c Mon Sep 17 00:00:00 2001 From: Bennet Bo Fenner Date: Fri, 4 Jul 2025 18:30:21 +0200 Subject: [PATCH 03/13] settings: Remove version keys from default settings (#33921) Follow up to #33372 Release Notes: - N/A --- assets/settings/default.json | 4 ---- 1 file changed, 4 deletions(-) diff --git a/assets/settings/default.json b/assets/settings/default.json index 56fd9353ccf9fdbcd1b24871f40a7bc2d234b7a2..9d858b42a8867b9968f6e6d8113e5d0c7fe357ff 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -746,8 +746,6 @@ "default_width": 380 }, "agent": { - // Version of this setting. - "version": "2", // Whether the agent is enabled. "enabled": true, /// What completion mode to start new threads in, if available. Can be 'normal' or 'burn'. @@ -1658,7 +1656,6 @@ // Different settings for specific language models. "language_models": { "anthropic": { - "version": "1", "api_url": "https://api.anthropic.com" }, "google": { @@ -1668,7 +1665,6 @@ "api_url": "http://localhost:11434" }, "openai": { - "version": "1", "api_url": "https://api.openai.com/v1" }, "open_router": { From 543a7b123ae1084076a95fa0572b95b7fe6d0864 Mon Sep 17 00:00:00 2001 From: Cole Miller Date: Fri, 4 Jul 2025 13:33:10 -0400 Subject: [PATCH 04/13] debugger: Fix errors in JavaScript DAP schema (#33884) `program` isn't required, and in fact our built-in `JavaScript debug terminal` configuration doesn't have it. Also add `node-terminal` to the list of allowed types. Co-authored-by: Michael Release Notes: - N/A --- crates/dap_adapters/src/javascript.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/crates/dap_adapters/src/javascript.rs b/crates/dap_adapters/src/javascript.rs index 23a378cdf9a2cc4779e9aed44538f04483a3dc56..d261d3b8b6e88c3b8069935caa9e3fd2b9d2d836 100644 --- a/crates/dap_adapters/src/javascript.rs +++ b/crates/dap_adapters/src/javascript.rs @@ -245,7 +245,7 @@ impl DebugAdapter for JsDebugAdapter { "properties": { "type": { "type": "string", - "enum": ["pwa-node", "node", "chrome", "pwa-chrome", "msedge", "pwa-msedge"], + "enum": ["pwa-node", "node", "chrome", "pwa-chrome", "msedge", "pwa-msedge", "node-terminal"], "description": "The type of debug session", "default": "pwa-node" }, @@ -379,10 +379,6 @@ impl DebugAdapter for JsDebugAdapter { } } }, - "oneOf": [ - { "required": ["program"] }, - { "required": ["url"] } - ] } ] }, From 75928f4859eb89046f54d3621cbcebef0fe6cf05 Mon Sep 17 00:00:00 2001 From: Ryan Hawkins Date: Fri, 4 Jul 2025 15:26:09 -0600 Subject: [PATCH 05/13] Sync extension debuggers to remote host (#33876) Closes #33835 Release Notes: - Fixed debugger extensions not working in remote projects. --- crates/extension/src/extension_builder.rs | 11 +++---- crates/extension/src/extension_manifest.rs | 33 +++++++++++++++++++++ crates/extension_host/src/extension_host.rs | 17 +++++++++++ crates/extension_host/src/headless_host.rs | 27 ++++++++++++++--- 4 files changed, 77 insertions(+), 11 deletions(-) diff --git a/crates/extension/src/extension_builder.rs b/crates/extension/src/extension_builder.rs index 7a3897eea78fde5ad791d7766c3dca146dc3c760..621ba9250c12f8edd4ab49bbdef13bc976a239dd 100644 --- a/crates/extension/src/extension_builder.rs +++ b/crates/extension/src/extension_builder.rs @@ -1,5 +1,6 @@ use crate::{ - ExtensionLibraryKind, ExtensionManifest, GrammarManifestEntry, parse_wasm_extension_version, + ExtensionLibraryKind, ExtensionManifest, GrammarManifestEntry, build_debug_adapter_schema_path, + parse_wasm_extension_version, }; use anyhow::{Context as _, Result, bail}; use async_compression::futures::bufread::GzipDecoder; @@ -99,12 +100,8 @@ impl ExtensionBuilder { } for (debug_adapter_name, meta) in &mut extension_manifest.debug_adapters { - let debug_adapter_relative_schema_path = - meta.schema_path.clone().unwrap_or_else(|| { - Path::new("debug_adapter_schemas") - .join(Path::new(debug_adapter_name.as_ref()).with_extension("json")) - }); - let debug_adapter_schema_path = extension_dir.join(debug_adapter_relative_schema_path); + let debug_adapter_schema_path = + extension_dir.join(build_debug_adapter_schema_path(debug_adapter_name, meta)); let debug_adapter_schema = fs::read_to_string(&debug_adapter_schema_path) .with_context(|| { diff --git a/crates/extension/src/extension_manifest.rs b/crates/extension/src/extension_manifest.rs index 9439f0c290899d77b0989bf1d1fc21217af65c14..4e3f8a3dc214e7b6f8970c72562b85838a1660aa 100644 --- a/crates/extension/src/extension_manifest.rs +++ b/crates/extension/src/extension_manifest.rs @@ -132,6 +132,16 @@ impl ExtensionManifest { } } +pub fn build_debug_adapter_schema_path( + adapter_name: &Arc, + meta: &DebugAdapterManifestEntry, +) -> PathBuf { + meta.schema_path.clone().unwrap_or_else(|| { + Path::new("debug_adapter_schemas") + .join(Path::new(adapter_name.as_ref()).with_extension("json")) + }) +} + /// A capability for an extension. #[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] #[serde(tag = "kind")] @@ -320,6 +330,29 @@ mod tests { } } + #[test] + fn test_build_adapter_schema_path_with_schema_path() { + let adapter_name = Arc::from("my_adapter"); + let entry = DebugAdapterManifestEntry { + schema_path: Some(PathBuf::from("foo/bar")), + }; + + let path = build_debug_adapter_schema_path(&adapter_name, &entry); + assert_eq!(path, PathBuf::from("foo/bar")); + } + + #[test] + fn test_build_adapter_schema_path_without_schema_path() { + let adapter_name = Arc::from("my_adapter"); + let entry = DebugAdapterManifestEntry { schema_path: None }; + + let path = build_debug_adapter_schema_path(&adapter_name, &entry); + assert_eq!( + path, + PathBuf::from("debug_adapter_schemas").join("my_adapter.json") + ); + } + #[test] fn test_allow_exact_match() { let manifest = ExtensionManifest { diff --git a/crates/extension_host/src/extension_host.rs b/crates/extension_host/src/extension_host.rs index 8d3a218a03069cf79ec87799d566595e0b0dd3ce..eb6fb52eb82acec5b628aac05fd9131568a6c919 100644 --- a/crates/extension_host/src/extension_host.rs +++ b/crates/extension_host/src/extension_host.rs @@ -1639,6 +1639,23 @@ impl ExtensionStore { } } + for (adapter_name, meta) in loaded_extension.manifest.debug_adapters.iter() { + let schema_path = &extension::build_debug_adapter_schema_path(adapter_name, meta); + + if fs.is_file(&src_dir.join(schema_path)).await { + match schema_path.parent() { + Some(parent) => fs.create_dir(&tmp_dir.join(parent)).await?, + None => {} + } + fs.copy_file( + &src_dir.join(schema_path), + &tmp_dir.join(schema_path), + fs::CopyOptions::default(), + ) + .await? + } + } + Ok(()) }) } diff --git a/crates/extension_host/src/headless_host.rs b/crates/extension_host/src/headless_host.rs index 31626c50d8c6a82282b1855141986358dde2710a..ad3931ce838043c7644fd3e0c3d0eb249db1dd9b 100644 --- a/crates/extension_host/src/headless_host.rs +++ b/crates/extension_host/src/headless_host.rs @@ -4,8 +4,8 @@ use anyhow::{Context as _, Result}; use client::{TypedEnvelope, proto}; use collections::{HashMap, HashSet}; use extension::{ - Extension, ExtensionHostProxy, ExtensionLanguageProxy, ExtensionLanguageServerProxy, - ExtensionManifest, + Extension, ExtensionDebugAdapterProviderProxy, ExtensionHostProxy, ExtensionLanguageProxy, + ExtensionLanguageServerProxy, ExtensionManifest, }; use fs::{Fs, RemoveOptions, RenameOptions}; use gpui::{App, AppContext as _, AsyncApp, Context, Entity, Task, WeakEntity}; @@ -169,8 +169,9 @@ impl HeadlessExtensionStore { return Ok(()); } - let wasm_extension: Arc = - Arc::new(WasmExtension::load(extension_dir, &manifest, wasm_host.clone(), &cx).await?); + let wasm_extension: Arc = Arc::new( + WasmExtension::load(extension_dir.clone(), &manifest, wasm_host.clone(), &cx).await?, + ); for (language_server_id, language_server_config) in &manifest.language_servers { for language in language_server_config.languages() { @@ -186,6 +187,24 @@ impl HeadlessExtensionStore { ); })?; } + for (debug_adapter, meta) in &manifest.debug_adapters { + let schema_path = extension::build_debug_adapter_schema_path(debug_adapter, meta); + + this.update(cx, |this, _cx| { + this.proxy.register_debug_adapter( + wasm_extension.clone(), + debug_adapter.clone(), + &extension_dir.join(schema_path), + ); + })?; + } + + for debug_adapter in manifest.debug_locators.keys() { + this.update(cx, |this, _cx| { + this.proxy + .register_debug_locator(wasm_extension.clone(), debug_adapter.clone()); + })?; + } } Ok(()) From 31ec7ef2ec75b51cb72265ec701ab9b3f5dad8c6 Mon Sep 17 00:00:00 2001 From: xdBronch <51252236+xdBronch@users.noreply.github.com> Date: Fri, 4 Jul 2025 18:26:20 -0400 Subject: [PATCH 06/13] Debugger: check for `supports_single_thread_execution_requests` in continue (#33937) i found this to break my ability to continue with an lldb fork i use Release Notes: - N/A --- crates/project/src/debugger/session.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/project/src/debugger/session.rs b/crates/project/src/debugger/session.rs index f04aadf2df03837077226935d699204d2292935f..bd52c0f6fa6f7c77baaa7fa052cd7499b44f0858 100644 --- a/crates/project/src/debugger/session.rs +++ b/crates/project/src/debugger/session.rs @@ -1935,12 +1935,14 @@ impl Session { } pub fn continue_thread(&mut self, thread_id: ThreadId, cx: &mut Context) { + let supports_single_thread_execution_requests = + self.capabilities.supports_single_thread_execution_requests; self.thread_states.continue_thread(thread_id); self.request( ContinueCommand { args: ContinueArguments { thread_id: thread_id.0, - single_thread: Some(true), + single_thread: supports_single_thread_execution_requests, }, }, Self::on_step_response::(thread_id), From d3da0a809ec317981645603ea85d86ed3030b287 Mon Sep 17 00:00:00 2001 From: Jacob Duba Date: Fri, 4 Jul 2025 18:50:09 -0500 Subject: [PATCH 07/13] Update documentation for tailwindcss language server now that HTML/ERB is it's own file. (#33684) Hello, Recently my tailwind auto completion broke in ERB files. I noticed that HTML/ERB is it's own file type now. It used to be ERB. This broke the previous tailwindcss ERB configuration. I made the attached change to my configuration and it works now. --- docs/src/languages/ruby.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/languages/ruby.md b/docs/src/languages/ruby.md index 67904e35f18f704197ecf7d6d607649b7133d009..f8df187e8a2cb439078b110075da950551b93f2a 100644 --- a/docs/src/languages/ruby.md +++ b/docs/src/languages/ruby.md @@ -256,7 +256,7 @@ In order to do that, you need to configure the language server so that it knows "tailwindcss-language-server": { "settings": { "includeLanguages": { - "erb": "html", + "html/erb": "html", "ruby": "html" }, "experimental": { From 0555bbd0ec1d70fca33c8d0b78fdfb1406b1aa09 Mon Sep 17 00:00:00 2001 From: Vitaly Slobodin Date: Sat, 5 Jul 2025 01:50:51 +0200 Subject: [PATCH 08/13] ruby: Document how to use `erb-formatter` for ERB files (#33872) Hi! This is a small pull request that adds a new section about configuring the `erb-formatter` for formatting ERB files. Thanks. Release Notes: - N/A --- docs/src/languages/ruby.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/docs/src/languages/ruby.md b/docs/src/languages/ruby.md index f8df187e8a2cb439078b110075da950551b93f2a..b7856b2cd07ab15bb1b5fd402908c162a543f86d 100644 --- a/docs/src/languages/ruby.md +++ b/docs/src/languages/ruby.md @@ -379,3 +379,22 @@ The Ruby extension provides a debug adapter for debugging Ruby code. Zed's name } ] ``` + +## Formatters + +### `erb-formatter` + +To format ERB templates, you can use the `erb-formatter` formatter. This formatter uses the [`erb-formatter`](https://rubygems.org/gems/erb-formatter) gem to format ERB templates. + +```jsonc +{ + "HTML/ERB": { + "formatter": { + "external": { + "command": "erb-formatter", + "arguments": ["--stdin-filename", "{buffer_path}"], + }, + }, + }, +} +``` From 4ad47fc9cc43478d3ab02d1f27f3a1c30226d528 Mon Sep 17 00:00:00 2001 From: Vitaly Slobodin Date: Sat, 5 Jul 2025 01:51:25 +0200 Subject: [PATCH 09/13] emmet: Enable in `HTML/ERB` files (#33865) Closes [#133](https://github.com/zed-extensions/ruby/issues/133) This PR enables the Emmet LS in `HTML/ERB` files that we added in the https://github.com/zed-extensions/ruby/pull/113. Let me know if I picked the right approach here. Thanks! Release Notes: - N/A --- extensions/emmet/extension.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/extensions/emmet/extension.toml b/extensions/emmet/extension.toml index 02198a2c822fe41c46e981f2867e051787993169..99aa80a2d4330b7957e77f47f7c29b89decc1d32 100644 --- a/extensions/emmet/extension.toml +++ b/extensions/emmet/extension.toml @@ -9,12 +9,13 @@ repository = "https://github.com/zed-industries/zed" [language_servers.emmet-language-server] name = "Emmet Language Server" language = "HTML" -languages = ["HTML", "PHP", "ERB", "JavaScript", "TSX", "CSS", "HEEX", "Elixir"] +languages = ["HTML", "PHP", "ERB", "HTML/ERB", "JavaScript", "TSX", "CSS", "HEEX", "Elixir"] [language_servers.emmet-language-server.language_ids] "HTML" = "html" "PHP" = "php" "ERB" = "eruby" +"HTML/ERB" = "eruby" "JavaScript" = "javascriptreact" "TSX" = "typescriptreact" "CSS" = "css" From 69fd23e947edebfaa5704eee800d935645755759 Mon Sep 17 00:00:00 2001 From: abhimanyu maurya <47671638+Aerma7309@users.noreply.github.com> Date: Sat, 5 Jul 2025 05:24:43 +0530 Subject: [PATCH 10/13] Fix path parsing for goto syntax provided by Haskell language server (#33697) path parsing for multiline errors provided by haskell language server where not working correctly image while its being parsed correctly in vscode image Release Notes: - Fixed path parsing paths in the format of the Haskell language server --- crates/util/src/paths.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/crates/util/src/paths.rs b/crates/util/src/paths.rs index 47ea662d7de5b5d367dc854ee03c07faf02f5fca..2e02f051d15fc8a20d3181b3a37cbad0b9745651 100644 --- a/crates/util/src/paths.rs +++ b/crates/util/src/paths.rs @@ -170,6 +170,12 @@ impl> From for SanitizedPath { pub const FILE_ROW_COLUMN_DELIMITER: char = ':'; const ROW_COL_CAPTURE_REGEX: &str = r"(?xs) + ([^\(]+)\:(?: + \((\d+)[,:](\d+)\) # filename:(row,column), filename:(row:column) + | + \((\d+)\)() # filename:(row) + ) + | ([^\(]+)(?: \((\d+)[,:](\d+)\) # filename(row,column), filename(row:column) | @@ -674,6 +680,15 @@ mod tests { column: None } ); + + assert_eq!( + PathWithPosition::parse_str("Types.hs:(617,9)-(670,28):"), + PathWithPosition { + path: PathBuf::from("Types.hs"), + row: Some(617), + column: Some(9), + } + ); } #[test] From 44d1f512f87cd6cbb7f3bcd88cab92b8cbfbf4d2 Mon Sep 17 00:00:00 2001 From: Alvaro Parker <64918109+AlvaroParker@users.noreply.github.com> Date: Fri, 4 Jul 2025 20:27:21 -0400 Subject: [PATCH 11/13] Use /usr/bin/env to run bash restart script (#33936) Closes #33935 Release Notes: - Use `/usr/bin/env` to launch the bash restart script --- crates/gpui/src/platform/linux/platform.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/gpui/src/platform/linux/platform.rs b/crates/gpui/src/platform/linux/platform.rs index 01a587e72a65c4466719cffe762ea90a76f1449c..af53899b437c244fd06d43b7963920c9596b94a0 100644 --- a/crates/gpui/src/platform/linux/platform.rs +++ b/crates/gpui/src/platform/linux/platform.rs @@ -200,8 +200,8 @@ impl Platform for P { app_path = app_path.display() ); - // execute the script using /bin/bash - let restart_process = Command::new("/bin/bash") + let restart_process = Command::new("/usr/bin/env") + .arg("bash") .arg("-c") .arg(script) .process_group(0) From 76fe33245fcff14013760255223d4b1cb92573c1 Mon Sep 17 00:00:00 2001 From: Smit Barmase Date: Sat, 5 Jul 2025 05:57:37 +0530 Subject: [PATCH 12/13] project_panel: Fix indent guide collapse on secondary click for multiple worktrees (#33939) Release Notes: - Fixed issue where `cmd`/`ctrl` click on indent guide would not collapse directory in case of multiple projects. --- crates/project_panel/src/project_panel.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 614f8ccf81967941c099ada140fcd20bc6cc94c6..ded6e0e3f48f0650f97f265a1b5aed1b9b1b443a 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -3303,12 +3303,13 @@ impl ProjectPanel { fn entry_at_index(&self, index: usize) -> Option<(WorktreeId, GitEntryRef<'_>)> { let mut offset = 0; for (worktree_id, visible_worktree_entries, _) in &self.visible_entries { - if visible_worktree_entries.len() > offset + index { + let current_len = visible_worktree_entries.len(); + if index < offset + current_len { return visible_worktree_entries - .get(index) + .get(index - offset) .map(|entry| (*worktree_id, entry.to_ref())); } - offset += visible_worktree_entries.len(); + offset += current_len; } None } From 66e45818af4c93c64bf0707de1103cb23ecb34e6 Mon Sep 17 00:00:00 2001 From: Remco Smits Date: Sat, 5 Jul 2025 16:20:41 +0200 Subject: [PATCH 13/13] debugger: Improve debug console autocompletions (#33868) Partially fixes: https://github.com/zed-industries/zed/discussions/33777#discussioncomment-13646294 ### Improves debug console autocompletion behavior This PR fixes a regression in completion trigger support for the debug console, as we only looked if a completion trigger, was in the beginning of the search text, but we also had to check if the current text is a word so we also show completions for variables/input that doesn't start with any of the completion triggers. We now also leverage DAP provided information to sort completion items more effectively. This results in improved prioritization, showing variable completions above classes and global scope types. I also added for completion the documentation field, that directly comes from the DAP server. NOTE: I haven't found an adapter that returns this, but it needs to have. **Before** Screenshot 2025-07-03 at 21 00 19 **After** Screenshot 2025-07-03 at 20 59 38 Release Notes: - Debugger: Improve autocompletion sorting for debug console - Debugger: Fix autocompletion menu now shown when you type - Debugger: Fix completion item showing up twice for some adapters --- .../src/session/running/console.rs | 88 ++++++++++++++----- crates/editor/src/code_context_menus.rs | 9 +- crates/project/src/lsp_store.rs | 17 +++- crates/project/src/project.rs | 4 + crates/proto/proto/lsp.proto | 2 + 5 files changed, 93 insertions(+), 27 deletions(-) diff --git a/crates/debugger_ui/src/session/running/console.rs b/crates/debugger_ui/src/session/running/console.rs index b75586020b7c2b10d96f11ad3f97f2dd5b1f2d35..9375c8820b0eb335f1d36534f219f339ec587df1 100644 --- a/crates/debugger_ui/src/session/running/console.rs +++ b/crates/debugger_ui/src/session/running/console.rs @@ -5,7 +5,7 @@ use super::{ use alacritty_terminal::vte::ansi; use anyhow::Result; use collections::HashMap; -use dap::OutputEvent; +use dap::{CompletionItem, CompletionItemType, OutputEvent}; use editor::{Bias, CompletionProvider, Editor, EditorElement, EditorStyle, ExcerptId}; use fuzzy::StringMatchCandidate; use gpui::{ @@ -17,6 +17,7 @@ use menu::{Confirm, SelectNext, SelectPrevious}; use project::{ Completion, CompletionResponse, debugger::session::{CompletionsQuery, OutputToken, Session}, + lsp_store::CompletionDocumentation, search_history::{SearchHistory, SearchHistoryCursor}, }; use settings::Settings; @@ -555,15 +556,27 @@ impl CompletionProvider for ConsoleQueryBarCompletionProvider { buffer: &Entity, position: language::Anchor, text: &str, - _trigger_in_words: bool, + trigger_in_words: bool, menu_is_open: bool, cx: &mut Context, ) -> bool { + let mut chars = text.chars(); + let char = if let Some(char) = chars.next() { + char + } else { + return false; + }; + let snapshot = buffer.read(cx).snapshot(); if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input { return false; } + let classifier = snapshot.char_classifier_at(position).for_completion(true); + if trigger_in_words && classifier.is_word(char) { + return true; + } + self.0 .read_with(cx, |console, cx| { console @@ -596,21 +609,28 @@ impl ConsoleQueryBarCompletionProvider { variable_list.completion_variables(cx) }) { if let Some(evaluate_name) = &variable.evaluate_name { - variables.insert(evaluate_name.clone(), variable.value.clone()); + if variables + .insert(evaluate_name.clone(), variable.value.clone()) + .is_none() + { + string_matches.push(StringMatchCandidate { + id: 0, + string: evaluate_name.clone(), + char_bag: evaluate_name.chars().collect(), + }); + } + } + + if variables + .insert(variable.name.clone(), variable.value.clone()) + .is_none() + { string_matches.push(StringMatchCandidate { id: 0, - string: evaluate_name.clone(), - char_bag: evaluate_name.chars().collect(), + string: variable.name.clone(), + char_bag: variable.name.chars().collect(), }); } - - variables.insert(variable.name.clone(), variable.value.clone()); - - string_matches.push(StringMatchCandidate { - id: 0, - string: variable.name.clone(), - char_bag: variable.name.chars().collect(), - }); } (variables, string_matches) @@ -656,11 +676,13 @@ impl ConsoleQueryBarCompletionProvider { new_text: string_match.string.clone(), label: CodeLabel { filter_range: 0..string_match.string.len(), - text: format!("{} {}", string_match.string, variable_value), + text: string_match.string.clone(), runs: Vec::new(), }, icon_path: None, - documentation: None, + documentation: Some(CompletionDocumentation::MultiLineMarkdown( + variable_value.into(), + )), confirm: None, source: project::CompletionSource::Custom, insert_text_mode: None, @@ -675,6 +697,32 @@ impl ConsoleQueryBarCompletionProvider { }) } + const fn completion_type_score(completion_type: CompletionItemType) -> usize { + match completion_type { + CompletionItemType::Field | CompletionItemType::Property => 0, + CompletionItemType::Variable | CompletionItemType::Value => 1, + CompletionItemType::Method + | CompletionItemType::Function + | CompletionItemType::Constructor => 2, + CompletionItemType::Class + | CompletionItemType::Interface + | CompletionItemType::Module => 3, + _ => 4, + } + } + + fn completion_item_sort_text(completion_item: &CompletionItem) -> String { + completion_item.sort_text.clone().unwrap_or_else(|| { + format!( + "{:03}_{}", + Self::completion_type_score( + completion_item.type_.unwrap_or(CompletionItemType::Text) + ), + completion_item.label.to_ascii_lowercase() + ) + }) + } + fn client_completions( &self, console: &Entity, @@ -699,6 +747,7 @@ impl ConsoleQueryBarCompletionProvider { let completions = completions .into_iter() .map(|completion| { + let sort_text = Self::completion_item_sort_text(&completion); let new_text = completion .text .as_ref() @@ -731,12 +780,11 @@ impl ConsoleQueryBarCompletionProvider { runs: Vec::new(), }, icon_path: None, - documentation: None, + documentation: completion.detail.map(|detail| { + CompletionDocumentation::MultiLineMarkdown(detail.into()) + }), confirm: None, - source: project::CompletionSource::BufferWord { - word_range: buffer_position..language::Anchor::MAX, - resolved: false, - }, + source: project::CompletionSource::Dap { sort_text }, insert_text_mode: None, } }) diff --git a/crates/editor/src/code_context_menus.rs b/crates/editor/src/code_context_menus.rs index 291c03422def426054457c04ab8c9e4e710112a7..8fbae8d6052d89299b10f3cd0c971af79abd3c90 100644 --- a/crates/editor/src/code_context_menus.rs +++ b/crates/editor/src/code_context_menus.rs @@ -1083,11 +1083,10 @@ impl CompletionsMenu { if lsp_completion.kind == Some(CompletionItemKind::SNIPPET) ); - let sort_text = if let CompletionSource::Lsp { lsp_completion, .. } = &completion.source - { - lsp_completion.sort_text.as_deref() - } else { - None + let sort_text = match &completion.source { + CompletionSource::Lsp { lsp_completion, .. } => lsp_completion.sort_text.as_deref(), + CompletionSource::Dap { sort_text } => Some(sort_text.as_str()), + _ => None, }; let (sort_kind, sort_label) = completion.sort_key(); diff --git a/crates/project/src/lsp_store.rs b/crates/project/src/lsp_store.rs index f2b04b9b210712107a2836c8445160a5c31bb5b0..8a14e02e0b40946ed8e81b72e6cea5eb2a6c56ef 100644 --- a/crates/project/src/lsp_store.rs +++ b/crates/project/src/lsp_store.rs @@ -6043,7 +6043,9 @@ impl LspStore { ); server.request::(*lsp_completion.clone()) } - CompletionSource::BufferWord { .. } | CompletionSource::Custom => { + CompletionSource::BufferWord { .. } + | CompletionSource::Dap { .. } + | CompletionSource::Custom => { return Ok(()); } } @@ -6195,7 +6197,9 @@ impl LspStore { } serde_json::to_string(lsp_completion).unwrap().into_bytes() } - CompletionSource::Custom | CompletionSource::BufferWord { .. } => { + CompletionSource::Custom + | CompletionSource::Dap { .. } + | CompletionSource::BufferWord { .. } => { return Ok(()); } } @@ -11081,6 +11085,10 @@ impl LspStore { serialized_completion.source = proto::completion::Source::Custom as i32; serialized_completion.resolved = true; } + CompletionSource::Dap { sort_text } => { + serialized_completion.source = proto::completion::Source::Dap as i32; + serialized_completion.sort_text = Some(sort_text.clone()); + } } serialized_completion @@ -11135,6 +11143,11 @@ impl LspStore { resolved: completion.resolved, } } + Some(proto::completion::Source::Dap) => CompletionSource::Dap { + sort_text: completion + .sort_text + .context("expected sort text to exist")?, + }, _ => anyhow::bail!("Unexpected completion source {}", completion.source), }, }) diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 8a41a75d682e918a4ae698b6fe13ea8c2f9b9816..c7a1f057615c0e75414389935dbbabab9bc7155d 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -456,6 +456,10 @@ pub enum CompletionSource { /// Whether this completion has been resolved, to ensure it happens once per completion. resolved: bool, }, + Dap { + /// The sort text for this completion. + sort_text: String, + }, Custom, BufferWord { word_range: Range, diff --git a/crates/proto/proto/lsp.proto b/crates/proto/proto/lsp.proto index c0eadd5e699f4b89fff5c84169b928598bc706ea..e3c2f69c0b7587580a393b343eff1c4cd932fd72 100644 --- a/crates/proto/proto/lsp.proto +++ b/crates/proto/proto/lsp.proto @@ -222,11 +222,13 @@ message Completion { optional Anchor buffer_word_end = 10; Anchor old_insert_start = 11; Anchor old_insert_end = 12; + optional string sort_text = 13; enum Source { Lsp = 0; Custom = 1; BufferWord = 2; + Dap = 3; } }