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": { 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) { 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; 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/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(()) 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) 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), 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/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 } 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; } } 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] diff --git a/docs/src/languages/ruby.md b/docs/src/languages/ruby.md index 67904e35f18f704197ecf7d6d607649b7133d009..b7856b2cd07ab15bb1b5fd402908c162a543f86d 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": { @@ -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}"], + }, + }, + }, +} +``` 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"