From 09d3ff9dbe0d76549611db7c958c5ac737fe88f3 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Tue, 6 May 2025 20:16:41 +0200 Subject: [PATCH] debugger: Rework language association with the debuggers (#29945) - Languages now define their preferred debuggers in `config.toml`. - `LanguageRegistry` now exposes language config even for languages that are not yet loaded. This necessitated extension registry changes (we now deserialize config.toml of all language entries when loading new extension index), but it should be backwards compatible with the old format. /cc @maxdeviant Release Notes: - N/A --------- Co-authored-by: Anthony Eid Co-authored-by: Remco Smits Co-authored-by: Anthony --- assets/settings/default.json | 2 + crates/debugger_ui/src/debugger_panel.rs | 38 ++++-- crates/debugger_ui/src/debugger_ui.rs | 67 +++++++--- crates/debugger_ui/src/new_session_modal.rs | 82 +++++++++--- crates/editor/src/editor.rs | 22 ++- crates/extension/src/extension_host_proxy.rs | 14 +- crates/extension_host/src/extension_host.rs | 113 ++++++++-------- .../src/extension_store_test.rs | 125 +++++++++++++++--- crates/extension_host/src/headless_host.rs | 5 +- crates/language/src/language.rs | 28 ++-- crates/language/src/language_registry.rs | 54 +++----- crates/language/src/language_settings.rs | 6 + crates/language/src/task_context.rs | 3 - .../src/language_extension.rs | 10 +- crates/languages/src/c/config.toml | 1 + crates/languages/src/cpp/config.toml | 1 + crates/languages/src/go.rs | 4 - crates/languages/src/go/config.toml | 1 + crates/languages/src/javascript/config.toml | 1 + crates/languages/src/lib.rs | 5 +- crates/languages/src/python.rs | 4 - crates/languages/src/python/config.toml | 1 + crates/languages/src/rust.rs | 4 - crates/languages/src/rust/config.toml | 1 + crates/languages/src/tsx/config.toml | 1 + crates/languages/src/typescript/config.toml | 1 + crates/project/src/task_inventory.rs | 8 -- 27 files changed, 386 insertions(+), 216 deletions(-) diff --git a/assets/settings/default.json b/assets/settings/default.json index 47d0ac45da4df574184dc984e34008733663726f..2506150012eb0cac1978258a9d4dd1c445df7bf8 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -908,6 +908,8 @@ "hard_tabs": false, // How many columns a tab should occupy. "tab_size": 4, + // What debuggers are preferred by default for all languages. + "debuggers": [], // Control what info is collected by Zed. "telemetry": { // Send debug info like crash reports. diff --git a/crates/debugger_ui/src/debugger_panel.rs b/crates/debugger_ui/src/debugger_panel.rs index 4c6367bd259ff2c0b8f37fbc1ef9dd5685505bb1..50ac46d01ad9938d7100a756a377b921533a5ace 100644 --- a/crates/debugger_ui/src/debugger_panel.rs +++ b/crates/debugger_ui/src/debugger_panel.rs @@ -621,20 +621,32 @@ impl DebugPanel { move |_, window, cx| { let weak_panel = weak_panel.clone(); let past_debug_definition = past_debug_definition.clone(); + let workspace = workspace.clone(); + window + .spawn(cx, async move |cx| { + let task_contexts = workspace + .update_in(cx, |workspace, window, cx| { + tasks_ui::task_contexts(workspace, window, cx) + })? + .await; + + workspace.update_in(cx, |this, window, cx| { + this.toggle_modal(window, cx, |window, cx| { + NewSessionModal::new( + past_debug_definition, + weak_panel, + workspace.clone(), + None, + task_contexts, + window, + cx, + ) + }); + })?; - let _ = workspace.update(cx, |this, cx| { - let workspace = cx.weak_entity(); - this.toggle_modal(window, cx, |window, cx| { - NewSessionModal::new( - past_debug_definition, - weak_panel, - workspace, - None, - window, - cx, - ) - }); - }); + Result::<_, anyhow::Error>::Ok(()) + }) + .detach(); } }) .tooltip({ diff --git a/crates/debugger_ui/src/debugger_ui.rs b/crates/debugger_ui/src/debugger_ui.rs index 5fb1a2dca3ca40499d0013d8c08c679c9674835a..b8ef6e121701943d992284cf807b490511b17b9f 100644 --- a/crates/debugger_ui/src/debugger_ui.rs +++ b/crates/debugger_ui/src/debugger_ui.rs @@ -153,16 +153,29 @@ pub fn init(cx: &mut App) { let weak_panel = debug_panel.downgrade(); let weak_workspace = cx.weak_entity(); - workspace.toggle_modal(window, cx, |window, cx| { - NewSessionModal::new( - debug_panel.read(cx).past_debug_definition.clone(), - weak_panel, - weak_workspace, - None, - window, - cx, - ) - }); + cx.spawn_in(window, async move |this, cx| { + let task_contexts = this + .update_in(cx, |workspace, window, cx| { + tasks_ui::task_contexts(workspace, window, cx) + })? + .await; + this.update_in(cx, |workspace, window, cx| { + workspace.toggle_modal(window, cx, |window, cx| { + NewSessionModal::new( + debug_panel.read(cx).past_debug_definition.clone(), + weak_panel, + weak_workspace, + None, + task_contexts, + window, + cx, + ) + }); + })?; + + Result::<_, anyhow::Error>::Ok(()) + }) + .detach(); } }, ) @@ -172,16 +185,30 @@ pub fn init(cx: &mut App) { let weak_workspace = cx.weak_entity(); let task_store = workspace.project().read(cx).task_store().clone(); - workspace.toggle_modal(window, cx, |window, cx| { - NewSessionModal::new( - debug_panel.read(cx).past_debug_definition.clone(), - weak_panel, - weak_workspace, - Some(task_store), - window, - cx, - ) - }); + cx.spawn_in(window, async move |this, cx| { + let task_contexts = this + .update_in(cx, |workspace, window, cx| { + tasks_ui::task_contexts(workspace, window, cx) + })? + .await; + + this.update_in(cx, |workspace, window, cx| { + workspace.toggle_modal(window, cx, |window, cx| { + NewSessionModal::new( + debug_panel.read(cx).past_debug_definition.clone(), + weak_panel, + weak_workspace, + Some(task_store), + task_contexts, + window, + cx, + ) + }); + })?; + + anyhow::Ok(()) + }) + .detach() } }); }) diff --git a/crates/debugger_ui/src/new_session_modal.rs b/crates/debugger_ui/src/new_session_modal.rs index 73aa4b095e33ff76a25533dd30f3d1bb029570a7..d82982e45315fc31d228ec94e6421fd5a2402a28 100644 --- a/crates/debugger_ui/src/new_session_modal.rs +++ b/crates/debugger_ui/src/new_session_modal.rs @@ -1,9 +1,12 @@ use std::{ borrow::Cow, + cmp::Reverse, ops::Not, path::{Path, PathBuf}, + sync::Arc, }; +use collections::{HashMap, HashSet}; use dap::{ DapRegistry, DebugRequest, adapters::{DebugAdapterName, DebugTaskDefinition}, @@ -15,10 +18,9 @@ use gpui::{ Subscription, TextStyle, WeakEntity, }; use picker::{Picker, PickerDelegate, highlighted_match_with_paths::HighlightedMatch}; -use project::{TaskSourceKind, task_store::TaskStore}; +use project::{TaskContexts, TaskSourceKind, task_store::TaskStore}; use settings::Settings; use task::{DebugScenario, LaunchRequest}; -use tasks_ui::task_contexts; use theme::ThemeSettings; use ui::{ ActiveTheme, Button, ButtonCommon, ButtonSize, CheckboxWithLabel, Clickable, Color, Context, @@ -32,7 +34,6 @@ use workspace::{ModalView, Workspace}; use crate::{attach_modal::AttachModal, debugger_panel::DebugPanel}; -#[derive(Clone)] pub(super) struct NewSessionModal { workspace: WeakEntity, debug_panel: WeakEntity, @@ -41,6 +42,7 @@ pub(super) struct NewSessionModal { initialize_args: Option, debugger: Option, last_selected_profile_name: Option, + task_contexts: Arc, } fn suggested_label(request: &DebugRequest, debugger: &str) -> SharedString { @@ -67,6 +69,7 @@ impl NewSessionModal { debug_panel: WeakEntity, workspace: WeakEntity, task_store: Option>, + task_contexts: TaskContexts, window: &mut Window, cx: &mut Context, ) -> Self { @@ -105,6 +108,7 @@ impl NewSessionModal { .unwrap_or(ToggleState::Unselected), last_selected_profile_name: None, initialize_args: None, + task_contexts: Arc::new(task_contexts), } } @@ -145,22 +149,10 @@ impl NewSessionModal { }; let debug_panel = self.debug_panel.clone(); - let workspace = self.workspace.clone(); + let task_contexts = self.task_contexts.clone(); cx.spawn_in(window, async move |this, cx| { - let task_contexts = workspace - .update_in(cx, |this, window, cx| task_contexts(this, window, cx))? - .await; + let task_context = task_contexts.active_context().cloned().unwrap_or_default(); let worktree_id = task_contexts.worktree(); - let task_context = task_contexts - .active_item_context - .map(|(_, _, context)| context) - .or_else(|| { - task_contexts - .active_worktree_context - .map(|(_, context)| context) - }) - .unwrap_or_default(); - debug_panel.update_in(cx, |debug_panel, window, cx| { debug_panel.start_session(config, task_context, None, worktree_id, window, cx) })?; @@ -198,14 +190,27 @@ impl NewSessionModal { &self, window: &mut Window, cx: &mut Context, - ) -> ui::DropdownMenu { + ) -> Option { let workspace = self.workspace.clone(); + let language_registry = self + .workspace + .update(cx, |this, _| this.app_state().languages.clone()) + .ok()?; let weak = cx.weak_entity(); let label = self .debugger .as_ref() .map(|d| d.0.clone()) .unwrap_or_else(|| SELECT_DEBUGGER_LABEL.clone()); + let active_buffer_language_name = + self.task_contexts + .active_item_context + .as_ref() + .and_then(|item| { + item.1 + .as_ref() + .and_then(|location| location.buffer.read(cx).language()?.name().into()) + }); DropdownMenu::new( "dap-adapter-picker", label, @@ -224,17 +229,50 @@ impl NewSessionModal { } }; - let available_adapters = workspace + let available_languages = language_registry.language_names(); + let mut debugger_to_languages = HashMap::default(); + for language in available_languages { + let Some(language) = + language_registry.available_language_for_name(language.as_str()) + else { + continue; + }; + + language.config().debuggers.iter().for_each(|adapter| { + debugger_to_languages + .entry(adapter.clone()) + .or_insert_with(HashSet::default) + .insert(language.name()); + }); + } + let mut available_adapters = workspace .update(cx, |_, cx| DapRegistry::global(cx).enumerate_adapters()) .ok() .unwrap_or_default(); - for adapter in available_adapters { + available_adapters.sort_by_key(|name| { + let languages_for_debugger = debugger_to_languages.get(name.as_ref()); + let languages_count = + languages_for_debugger.map_or(0, |languages| languages.len()); + let contains_language_of_active_buffer = languages_for_debugger + .zip(active_buffer_language_name.as_ref()) + .map_or(false, |(languages, active_buffer_language)| { + languages.contains(active_buffer_language) + }); + + ( + Reverse(contains_language_of_active_buffer), + Reverse(languages_count), + ) + }); + + for adapter in available_adapters.into_iter() { menu = menu.entry(adapter.0.clone(), None, setter_for_name(adapter.clone())); } menu }), ) + .into() } fn debug_config_drop_down_menu( @@ -591,7 +629,9 @@ impl Render for NewSessionModal { ), ) .justify_between() - .child(self.adapter_drop_down_menu(window, cx)) + .when(!matches!(self.mode, NewSessionMode::Scenario(_)), |this| { + this.children(self.adapter_drop_down_menu(window, cx)) + }) .border_color(cx.theme().colors().border_variant) .border_b_1(), ) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 01567e0208f658542830e63a269fe7f3fb45646f..1c3318b08964c0364a948698703058c4243042f2 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -5206,12 +5206,22 @@ impl Editor { let dap_store = project.read(cx).dap_store(); let mut scenarios = vec![]; let resolved_tasks = resolved_tasks.as_ref()?; - let debug_adapter: SharedString = buffer - .read(cx) - .language()? - .context_provider()? - .debug_adapter()? - .into(); + let buffer = buffer.read(cx); + let language = buffer.language()?; + let file = buffer.file(); + let debug_adapter = + language_settings(language.name().into(), file, cx) + .debuggers + .first() + .map(SharedString::from) + .or_else(|| { + language + .config() + .debuggers + .first() + .map(SharedString::from) + })?; + dap_store.update(cx, |this, cx| { for (_, task) in &resolved_tasks.templates { if let Some(scenario) = this diff --git a/crates/extension/src/extension_host_proxy.rs b/crates/extension/src/extension_host_proxy.rs index 7858a1eddf321fefeef1cc506dc478b7609c4c5d..dd145154281b96b806d50c8b12d5e015b1fd3d92 100644 --- a/crates/extension/src/extension_host_proxy.rs +++ b/crates/extension/src/extension_host_proxy.rs @@ -4,7 +4,7 @@ use std::sync::Arc; use anyhow::Result; use fs::Fs; use gpui::{App, Global, ReadGlobal, SharedString, Task}; -use language::{BinaryStatus, LanguageMatcher, LanguageName, LoadedLanguage}; +use language::{BinaryStatus, LanguageConfig, LanguageName, LoadedLanguage}; use lsp::LanguageServerName; use parking_lot::RwLock; @@ -224,10 +224,7 @@ impl ExtensionGrammarProxy for ExtensionHostProxy { pub trait ExtensionLanguageProxy: Send + Sync + 'static { fn register_language( &self, - language: LanguageName, - grammar: Option>, - matcher: LanguageMatcher, - hidden: bool, + config: LanguageConfig, load: Arc Result + Send + Sync + 'static>, ); @@ -241,17 +238,14 @@ pub trait ExtensionLanguageProxy: Send + Sync + 'static { impl ExtensionLanguageProxy for ExtensionHostProxy { fn register_language( &self, - language: LanguageName, - grammar: Option>, - matcher: LanguageMatcher, - hidden: bool, + language: LanguageConfig, load: Arc Result + Send + Sync + 'static>, ) { let Some(proxy) = self.language_proxy.read().clone() else { return; }; - proxy.register_language(language, grammar, matcher, hidden, load) + proxy.register_language(language, load) } fn remove_languages( diff --git a/crates/extension_host/src/extension_host.rs b/crates/extension_host/src/extension_host.rs index 578b0526d77406b97ec03549d9c4a710476f3c81..88ce7aeeea900489c33fc0d03dd234739b909e62 100644 --- a/crates/extension_host/src/extension_host.rs +++ b/crates/extension_host/src/extension_host.rs @@ -34,8 +34,7 @@ use gpui::{ }; use http_client::{AsyncBody, HttpClient, HttpClientWithUrl}; use language::{ - LanguageConfig, LanguageMatcher, LanguageName, LanguageQueries, LoadedLanguage, - QUERY_FILENAME_PREFIXES, Rope, + LanguageConfig, LanguageName, LanguageQueries, LoadedLanguage, QUERY_FILENAME_PREFIXES, Rope, }; use node_runtime::NodeRuntime; use project::ContextProviderWithTasks; @@ -140,7 +139,7 @@ struct GlobalExtensionStore(Entity); impl Global for GlobalExtensionStore {} -#[derive(Debug, Deserialize, Serialize, Default, PartialEq, Eq)] +#[derive(Deserialize, Serialize, Default)] pub struct ExtensionIndex { pub extensions: BTreeMap, ExtensionIndexEntry>, pub themes: BTreeMap, ExtensionIndexThemeEntry>, @@ -167,13 +166,12 @@ pub struct ExtensionIndexIconThemeEntry { pub path: PathBuf, } -#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Deserialize, Serialize)] +#[derive(Clone, Debug, Deserialize, Serialize)] pub struct ExtensionIndexLanguageEntry { pub extension: Arc, pub path: PathBuf, - pub matcher: LanguageMatcher, - pub hidden: bool, - pub grammar: Option>, + #[serde(skip)] + pub config: LanguageConfig, } actions!(zed, [ReloadExtensions]); @@ -1015,7 +1013,7 @@ impl ExtensionStore { /// added to the manifest, or whose files have changed on disk. fn extensions_updated( &mut self, - new_index: ExtensionIndex, + mut new_index: ExtensionIndex, cx: &mut Context, ) -> Task<()> { let old_index = &self.extension_index; @@ -1143,11 +1141,6 @@ impl ExtensionStore { self.proxy .remove_languages(&languages_to_remove, &grammars_to_remove); - let languages_to_add = new_index - .languages - .iter() - .filter(|(_, entry)| extensions_to_load.contains(&entry.extension)) - .collect::>(); let mut grammars_to_add = Vec::new(); let mut themes_to_add = Vec::new(); let mut icon_themes_to_add = Vec::new(); @@ -1189,39 +1182,7 @@ impl ExtensionStore { self.proxy.register_grammars(grammars_to_add); - for (language_name, language) in languages_to_add { - let mut language_path = self.installed_dir.clone(); - language_path.extend([ - Path::new(language.extension.as_ref()), - language.path.as_path(), - ]); - self.proxy.register_language( - language_name.clone(), - language.grammar.clone(), - language.matcher.clone(), - language.hidden, - Arc::new(move || { - let config = std::fs::read_to_string(language_path.join("config.toml"))?; - let config: LanguageConfig = ::toml::from_str(&config)?; - let queries = load_plugin_queries(&language_path); - let context_provider = - std::fs::read_to_string(language_path.join("tasks.json")) - .ok() - .and_then(|contents| { - let definitions = - serde_json_lenient::from_str(&contents).log_err()?; - Some(Arc::new(ContextProviderWithTasks::new(definitions)) as Arc<_>) - }); - - Ok(LoadedLanguage { - config, - queries, - context_provider, - toolchain_provider: None, - }) - }), - ); - } + let installed_dir = self.installed_dir.clone(); let fs = self.fs.clone(); let wasm_host = self.wasm_host.clone(); @@ -1232,11 +1193,59 @@ impl ExtensionStore { .filter_map(|name| new_index.extensions.get(name).cloned()) .collect::>(); - self.extension_index = new_index; - cx.notify(); - cx.emit(Event::ExtensionsUpdated); - cx.spawn(async move |this, cx| { + let languages_to_add = new_index + .languages + .iter_mut() + .filter(|(_, entry)| extensions_to_load.contains(&entry.extension)) + .collect::>(); + for (_, language) in languages_to_add { + let mut language_path = installed_dir.clone(); + language_path.extend([ + Path::new(language.extension.as_ref()), + language.path.as_path(), + ]); + let Some(config) = fs.load(&language_path.join("config.toml")).await.ok() else { + log::error!("Could not load config.toml in {:?}", language_path); + continue; + }; + let Some(config) = ::toml::from_str::(&config).ok() else { + log::error!( + "Could not parse language config.toml in {:?}", + language_path + ); + continue; + }; + language.config = config.clone(); + proxy.register_language( + language.config.clone(), + Arc::new(move || { + let queries = load_plugin_queries(&language_path); + let context_provider = + std::fs::read_to_string(language_path.join("tasks.json")) + .ok() + .and_then(|contents| { + let definitions = + serde_json_lenient::from_str(&contents).log_err()?; + Some(Arc::new(ContextProviderWithTasks::new(definitions)) + as Arc<_>) + }); + + Ok(LoadedLanguage { + config: config.clone(), + queries, + context_provider, + toolchain_provider: None, + }) + }), + ); + } + this.update(cx, |this, cx| { + this.extension_index = new_index; + cx.notify(); + cx.emit(Event::ExtensionsUpdated); + }) + .ok(); cx.background_spawn({ let fs = fs.clone(); async move { @@ -1439,9 +1448,7 @@ impl ExtensionStore { ExtensionIndexLanguageEntry { extension: extension_id.clone(), path: relative_path, - matcher: config.matcher, - hidden: config.hidden, - grammar: config.grammar, + config, }, ); } diff --git a/crates/extension_host/src/extension_store_test.rs b/crates/extension_host/src/extension_store_test.rs index 2acc01cb03a27c8640fa32f0d82753ba44670603..797b9c45236fb6cd0f5a9c192eeebadaaf0f5e24 100644 --- a/crates/extension_host/src/extension_store_test.rs +++ b/crates/extension_host/src/extension_store_test.rs @@ -10,7 +10,7 @@ use fs::{FakeFs, Fs, RealFs}; use futures::{AsyncReadExt, StreamExt, io::BufReader}; use gpui::{AppContext as _, SemanticVersion, SharedString, TestAppContext}; use http_client::{FakeHttpClient, Response}; -use language::{BinaryStatus, LanguageMatcher, LanguageRegistry}; +use language::{BinaryStatus, LanguageConfig, LanguageMatcher, LanguageRegistry}; use lsp::LanguageServerName; use node_runtime::NodeRuntime; use parking_lot::Mutex; @@ -206,11 +206,14 @@ async fn test_extension_store(cx: &mut TestAppContext) { ExtensionIndexLanguageEntry { extension: "zed-ruby".into(), path: "languages/erb".into(), - grammar: Some("embedded_template".into()), - hidden: false, - matcher: LanguageMatcher { - path_suffixes: vec!["erb".into()], - first_line_pattern: None, + config: LanguageConfig { + grammar: Some("embedded_template".into()), + hidden: false, + matcher: LanguageMatcher { + path_suffixes: vec!["erb".into()], + first_line_pattern: None, + }, + ..Default::default() }, }, ), @@ -219,11 +222,14 @@ async fn test_extension_store(cx: &mut TestAppContext) { ExtensionIndexLanguageEntry { extension: "zed-ruby".into(), path: "languages/ruby".into(), - grammar: Some("ruby".into()), - hidden: false, - matcher: LanguageMatcher { - path_suffixes: vec!["rb".into()], - first_line_pattern: None, + config: LanguageConfig { + grammar: Some("ruby".into()), + hidden: false, + matcher: LanguageMatcher { + path_suffixes: vec!["rb".into()], + first_line_pattern: None, + }, + ..Default::default() }, }, ), @@ -290,7 +296,24 @@ async fn test_extension_store(cx: &mut TestAppContext) { store.read_with(cx, |store, _| { let index = &store.extension_index; assert_eq!(index.extensions, expected_index.extensions); - assert_eq!(index.languages, expected_index.languages); + + for ((actual_key, actual_language), (expected_key, expected_language)) in + index.languages.iter().zip(expected_index.languages.iter()) + { + assert_eq!(actual_key, expected_key); + assert_eq!( + actual_language.config.grammar, + expected_language.config.grammar + ); + assert_eq!( + actual_language.config.matcher, + expected_language.config.matcher + ); + assert_eq!( + actual_language.config.hidden, + expected_language.config.hidden + ); + } assert_eq!(index.themes, expected_index.themes); assert_eq!( @@ -377,8 +400,26 @@ async fn test_extension_store(cx: &mut TestAppContext) { cx.executor().advance_clock(RELOAD_DEBOUNCE_DURATION); store.read_with(cx, |store, _| { let index = &store.extension_index; + + for ((actual_key, actual_language), (expected_key, expected_language)) in + index.languages.iter().zip(expected_index.languages.iter()) + { + assert_eq!(actual_key, expected_key); + assert_eq!( + actual_language.config.grammar, + expected_language.config.grammar + ); + assert_eq!( + actual_language.config.matcher, + expected_language.config.matcher + ); + assert_eq!( + actual_language.config.hidden, + expected_language.config.hidden + ); + } + assert_eq!(index.extensions, expected_index.extensions); - assert_eq!(index.languages, expected_index.languages); assert_eq!(index.themes, expected_index.themes); assert_eq!( @@ -415,7 +456,34 @@ async fn test_extension_store(cx: &mut TestAppContext) { cx.executor().run_until_parked(); store.read_with(cx, |store, _| { - assert_eq!(store.extension_index, expected_index); + assert_eq!(store.extension_index.extensions, expected_index.extensions); + assert_eq!(store.extension_index.themes, expected_index.themes); + assert_eq!( + store.extension_index.icon_themes, + expected_index.icon_themes + ); + + for ((actual_key, actual_language), (expected_key, expected_language)) in store + .extension_index + .languages + .iter() + .zip(expected_index.languages.iter()) + { + assert_eq!(actual_key, expected_key); + assert_eq!( + actual_language.config.grammar, + expected_language.config.grammar + ); + assert_eq!( + actual_language.config.matcher, + expected_language.config.matcher + ); + assert_eq!( + actual_language.config.hidden, + expected_language.config.hidden + ); + } + assert_eq!( language_registry.language_names(), ["ERB", "Plain Text", "Ruby"] @@ -452,7 +520,34 @@ async fn test_extension_store(cx: &mut TestAppContext) { expected_index.languages.remove("ERB"); store.read_with(cx, |store, _| { - assert_eq!(store.extension_index, expected_index); + assert_eq!(store.extension_index.extensions, expected_index.extensions); + assert_eq!(store.extension_index.themes, expected_index.themes); + assert_eq!( + store.extension_index.icon_themes, + expected_index.icon_themes + ); + + for ((actual_key, actual_language), (expected_key, expected_language)) in store + .extension_index + .languages + .iter() + .zip(expected_index.languages.iter()) + { + assert_eq!(actual_key, expected_key); + assert_eq!( + actual_language.config.grammar, + expected_language.config.grammar + ); + assert_eq!( + actual_language.config.matcher, + expected_language.config.matcher + ); + assert_eq!( + actual_language.config.hidden, + expected_language.config.hidden + ); + } + assert_eq!(language_registry.language_names(), ["Plain Text"]); assert_eq!(language_registry.grammar_names(), []); }); diff --git a/crates/extension_host/src/headless_host.rs b/crates/extension_host/src/headless_host.rs index 076f03e204a0489e27c24c6b3d4d150fb3a2a9ca..dd04bf1c357a121d54189e32cdb6f7dc0c1a4b42 100644 --- a/crates/extension_host/src/headless_host.rs +++ b/crates/extension_host/src/headless_host.rs @@ -149,10 +149,7 @@ impl HeadlessExtensionStore { config.grammar = None; this.proxy.register_language( - config.name.clone(), - None, - config.matcher.clone(), - config.hidden, + config.clone(), Arc::new(move || { Ok(LoadedLanguage { config: config.clone(), diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index 7de1b9ba4fe41d4ec4afe9ec2837f5835c593814..b7252b13cf98c7bc2838abc61605ae98bac7e457 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -26,7 +26,7 @@ pub use crate::language_settings::EditPredictionsMode; use crate::language_settings::SoftWrap; use anyhow::{Context as _, Result, anyhow}; use async_trait::async_trait; -use collections::{HashMap, HashSet}; +use collections::{HashMap, HashSet, IndexSet}; use fs::Fs; use futures::Future; use gpui::{App, AsyncApp, Entity, SharedString, Task}; @@ -666,7 +666,7 @@ pub struct CodeLabel { pub filter_range: Range, } -#[derive(Clone, Deserialize, JsonSchema)] +#[derive(Clone, Deserialize, JsonSchema, Serialize, Debug)] pub struct LanguageConfig { /// Human-readable name of the language. pub name: LanguageName, @@ -690,12 +690,20 @@ pub struct LanguageConfig { pub auto_indent_on_paste: Option, /// A regex that is used to determine whether the indentation level should be /// increased in the following line. - #[serde(default, deserialize_with = "deserialize_regex")] + #[serde( + default, + deserialize_with = "deserialize_regex", + serialize_with = "serialize_regex" + )] #[schemars(schema_with = "regex_json_schema")] pub increase_indent_pattern: Option, /// A regex that is used to determine whether the indentation level should be /// decreased in the following line. - #[serde(default, deserialize_with = "deserialize_regex")] + #[serde( + default, + deserialize_with = "deserialize_regex", + serialize_with = "serialize_regex" + )] #[schemars(schema_with = "regex_json_schema")] pub decrease_indent_pattern: Option, /// A list of characters that trigger the automatic insertion of a closing @@ -748,6 +756,9 @@ pub struct LanguageConfig { /// A list of characters that Zed should treat as word characters for completion queries. #[serde(default)] pub completion_query_characters: HashSet, + /// A list of preferred debuggers for this language. + #[serde(default)] + pub debuggers: IndexSet, } #[derive(Clone, Debug, Serialize, Deserialize, Default, JsonSchema)] @@ -766,7 +777,7 @@ pub struct LanguageMatcher { } /// The configuration for JSX tag auto-closing. -#[derive(Clone, Deserialize, JsonSchema)] +#[derive(Clone, Deserialize, JsonSchema, Serialize, Debug)] pub struct JsxTagAutoCloseConfig { /// The name of the node for a opening tag pub open_tag_node_name: String, @@ -807,7 +818,7 @@ pub struct LanguageScope { override_id: Option, } -#[derive(Clone, Deserialize, Default, Debug, JsonSchema)] +#[derive(Clone, Deserialize, Default, Debug, JsonSchema, Serialize)] pub struct LanguageConfigOverride { #[serde(default)] pub line_comments: Override>>, @@ -872,6 +883,7 @@ impl Default for LanguageConfig { hidden: false, jsx_tag_auto_close: None, completion_query_characters: Default::default(), + debuggers: Default::default(), } } } @@ -932,7 +944,7 @@ pub struct FakeLspAdapter { /// /// This struct includes settings for defining which pairs of characters are considered brackets and /// also specifies any language-specific scopes where these pairs should be ignored for bracket matching purposes. -#[derive(Clone, Debug, Default, JsonSchema)] +#[derive(Clone, Debug, Default, JsonSchema, Serialize)] pub struct BracketPairConfig { /// A list of character pairs that should be treated as brackets in the context of a given language. pub pairs: Vec, @@ -982,7 +994,7 @@ impl<'de> Deserialize<'de> for BracketPairConfig { /// Describes a single bracket pair and how an editor should react to e.g. inserting /// an opening bracket or to a newline character insertion in between `start` and `end` characters. -#[derive(Clone, Debug, Default, Deserialize, PartialEq, JsonSchema)] +#[derive(Clone, Debug, Default, Deserialize, PartialEq, JsonSchema, Serialize)] pub struct BracketPair { /// Starting substring for a bracket. pub start: String, diff --git a/crates/language/src/language_registry.rs b/crates/language/src/language_registry.rs index 3213fd2514d489969795e163911818c0d819af99..682404828c64e4cc60f1d9cfea2add8ec2763aab 100644 --- a/crates/language/src/language_registry.rs +++ b/crates/language/src/language_registry.rs @@ -145,24 +145,24 @@ pub enum BinaryStatus { #[derive(Clone)] pub struct AvailableLanguage { id: LanguageId, - name: LanguageName, - grammar: Option>, - matcher: LanguageMatcher, - hidden: bool, + config: LanguageConfig, load: Arc Result + 'static + Send + Sync>, loaded: bool, } impl AvailableLanguage { pub fn name(&self) -> LanguageName { - self.name.clone() + self.config.name.clone() } pub fn matcher(&self) -> &LanguageMatcher { - &self.matcher + &self.config.matcher } pub fn hidden(&self) -> bool { - self.hidden + self.config.hidden + } + pub fn config(&self) -> &LanguageConfig { + &self.config } } @@ -326,10 +326,7 @@ impl LanguageRegistry { #[cfg(any(feature = "test-support", test))] pub fn register_test_language(&self, config: LanguageConfig) { self.register_language( - config.name.clone(), - config.grammar.clone(), - config.matcher.clone(), - config.hidden, + config.clone(), Arc::new(move || { Ok(LoadedLanguage { config: config.clone(), @@ -488,18 +485,14 @@ impl LanguageRegistry { /// Adds a language to the registry, which can be loaded if needed. pub fn register_language( &self, - name: LanguageName, - grammar_name: Option>, - matcher: LanguageMatcher, - hidden: bool, + config: LanguageConfig, load: Arc Result + 'static + Send + Sync>, ) { let state = &mut *self.state.write(); for existing_language in &mut state.available_languages { - if existing_language.name == name { - existing_language.grammar = grammar_name; - existing_language.matcher = matcher; + if existing_language.config.name == config.name { + existing_language.config = config; existing_language.load = load; return; } @@ -507,11 +500,8 @@ impl LanguageRegistry { state.available_languages.push(AvailableLanguage { id: LanguageId::new(), - name, - grammar: grammar_name, - matcher, + config, load, - hidden, loaded: false, }); state.version += 1; @@ -557,7 +547,7 @@ impl LanguageRegistry { let mut result = state .available_languages .iter() - .filter_map(|l| l.loaded.not().then_some(l.name.to_string())) + .filter_map(|l| l.loaded.not().then_some(l.config.name.to_string())) .chain(state.languages.iter().map(|l| l.config.name.to_string())) .collect::>(); result.sort_unstable_by_key(|language_name| language_name.to_lowercase()); @@ -576,10 +566,7 @@ impl LanguageRegistry { let mut state = self.state.write(); state.available_languages.push(AvailableLanguage { id: language.id, - name: language.name(), - grammar: language.config.grammar.clone(), - matcher: language.config.matcher.clone(), - hidden: language.config.hidden, + config: language.config.clone(), load: Arc::new(|| Err(anyhow!("already loaded"))), loaded: true, }); @@ -648,7 +635,7 @@ impl LanguageRegistry { state .available_languages .iter() - .find(|l| l.name.0.as_ref() == name) + .find(|l| l.config.name.0.as_ref() == name) .cloned() } @@ -765,8 +752,11 @@ impl LanguageRegistry { let current_match_type = best_language_match .as_ref() .map_or(LanguageMatchPrecedence::default(), |(_, score)| *score); - let language_score = - callback(&language.name, &language.matcher, current_match_type); + let language_score = callback( + &language.config.name, + &language.config.matcher, + current_match_type, + ); debug_assert!( language_score.is_none_or(|new_score| new_score > current_match_type), "Matching callback should only return a better match than the current one" @@ -814,7 +804,7 @@ impl LanguageRegistry { let this = self.clone(); let id = language.id; - let name = language.name.clone(); + let name = language.config.name.clone(); let language_load = language.load.clone(); self.executor @@ -1130,7 +1120,7 @@ impl LanguageRegistryState { self.languages .retain(|language| !languages_to_remove.contains(&language.name())); self.available_languages - .retain(|language| !languages_to_remove.contains(&language.name)); + .retain(|language| !languages_to_remove.contains(&language.config.name)); self.grammars .retain(|name, _| !grammars_to_remove.contains(name)); self.version += 1; diff --git a/crates/language/src/language_settings.rs b/crates/language/src/language_settings.rs index 8fc0042372095ee7ed84ea5eac37a92312aa9c16..b5e1fcb4d8c747e7c34035b68633db58dadb1836 100644 --- a/crates/language/src/language_settings.rs +++ b/crates/language/src/language_settings.rs @@ -153,6 +153,8 @@ pub struct LanguageSettings { pub show_completion_documentation: bool, /// Completion settings for this language. pub completions: CompletionSettings, + /// Preferred debuggers for this language. + pub debuggers: Vec, } impl LanguageSettings { @@ -551,6 +553,10 @@ pub struct LanguageSettingsContent { pub show_completion_documentation: Option, /// Controls how completions are processed for this language. pub completions: Option, + /// Preferred debuggers for this language. + /// + /// Default: [] + pub debuggers: Option>, } /// The behavior of `editor::Rewrap`. diff --git a/crates/language/src/task_context.rs b/crates/language/src/task_context.rs index c3faf10b817500822571cccfc636b42aca975f7d..37913884e6c2b15d0bba3ba01b679e03fca2d225 100644 --- a/crates/language/src/task_context.rs +++ b/crates/language/src/task_context.rs @@ -47,7 +47,4 @@ pub trait ContextProvider: Send + Sync { fn lsp_task_source(&self) -> Option { None } - - /// Default debug adapter for a given language. - fn debug_adapter(&self) -> Option; } diff --git a/crates/language_extension/src/language_extension.rs b/crates/language_extension/src/language_extension.rs index 59951c87e482936373d70236683c46cdcdd193ab..1e714171454c4f0a8e1bd987431e9437729e2230 100644 --- a/crates/language_extension/src/language_extension.rs +++ b/crates/language_extension/src/language_extension.rs @@ -5,7 +5,7 @@ use std::sync::Arc; use anyhow::Result; use extension::{ExtensionGrammarProxy, ExtensionHostProxy, ExtensionLanguageProxy}; -use language::{LanguageMatcher, LanguageName, LanguageRegistry, LoadedLanguage}; +use language::{LanguageConfig, LanguageName, LanguageRegistry, LoadedLanguage}; pub fn init( extension_host_proxy: Arc, @@ -31,14 +31,10 @@ impl ExtensionGrammarProxy for LanguageServerRegistryProxy { impl ExtensionLanguageProxy for LanguageServerRegistryProxy { fn register_language( &self, - language: LanguageName, - grammar: Option>, - matcher: LanguageMatcher, - hidden: bool, + language: LanguageConfig, load: Arc Result + Send + Sync + 'static>, ) { - self.language_registry - .register_language(language, grammar, matcher, hidden, load); + self.language_registry.register_language(language, load); } fn remove_languages( diff --git a/crates/languages/src/c/config.toml b/crates/languages/src/c/config.toml index b41f469bd5a2fee758ff1afebdd88743a8b432eb..8c9c5da9828940b303db685f665e1a397a3591e4 100644 --- a/crates/languages/src/c/config.toml +++ b/crates/languages/src/c/config.toml @@ -11,3 +11,4 @@ brackets = [ { start = "'", end = "'", close = true, newline = false, not_in = ["string", "comment"] }, { start = "/*", end = " */", close = true, newline = false, not_in = ["string", "comment"] }, ] +debuggers = ["CodeLLDB", "GDB"] diff --git a/crates/languages/src/cpp/config.toml b/crates/languages/src/cpp/config.toml index ebd78870a29c760d1a5a34e791d0e2413a0de140..62503fd5ba926a6ae03fec39c10aa0d3d8b2f204 100644 --- a/crates/languages/src/cpp/config.toml +++ b/crates/languages/src/cpp/config.toml @@ -11,3 +11,4 @@ brackets = [ { start = "'", end = "'", close = true, newline = false, not_in = ["string", "comment"] }, { start = "/*", end = " */", close = true, newline = false, not_in = ["string", "comment"] }, ] +debuggers = ["CodeLLDB", "GDB"] diff --git a/crates/languages/src/go.rs b/crates/languages/src/go.rs index 3156685b3335c35e75db3f1b8efd248bdf2e3740..72051feab2d81f39c48ade3c36e742290aba5b30 100644 --- a/crates/languages/src/go.rs +++ b/crates/languages/src/go.rs @@ -630,10 +630,6 @@ impl ContextProvider for GoContextProvider { }, ])) } - - fn debug_adapter(&self) -> Option { - Some("Delve".into()) - } } fn extract_subtest_name(input: &str) -> Option { diff --git a/crates/languages/src/go/config.toml b/crates/languages/src/go/config.toml index d1230c549a728c0366f10213ed3282070c35a691..15def17893b9625ed720e3a151b28b6cf9c76d5c 100644 --- a/crates/languages/src/go/config.toml +++ b/crates/languages/src/go/config.toml @@ -14,3 +14,4 @@ brackets = [ ] tab_size = 4 hard_tabs = true +debuggers = ["Delve"] diff --git a/crates/languages/src/javascript/config.toml b/crates/languages/src/javascript/config.toml index b5f8e1bc7aab20bee191f9d48caa0874e2802c98..112357f6c0968567745bbae7d0b074c639e3b6dc 100644 --- a/crates/languages/src/javascript/config.toml +++ b/crates/languages/src/javascript/config.toml @@ -19,6 +19,7 @@ word_characters = ["$", "#"] tab_size = 2 scope_opt_in_language_servers = ["tailwindcss-language-server", "emmet-language-server"] prettier_parser_name = "babel" +debuggers = ["JavaScript"] [jsx_tag_auto_close] open_tag_node_name = "jsx_opening_element" diff --git a/crates/languages/src/lib.rs b/crates/languages/src/lib.rs index f5fbe66e26e241b00c0c40ac588edaadc7771cd8..b31922907199cd88a5837422fa6fc5d2ffcfab75 100644 --- a/crates/languages/src/lib.rs +++ b/crates/languages/src/lib.rs @@ -325,10 +325,7 @@ fn register_language( languages.register_lsp_adapter(config.name.clone(), adapter); } languages.register_language( - config.name.clone(), - config.grammar.clone(), - config.matcher.clone(), - config.hidden, + config.clone(), Arc::new(move || { Ok(LoadedLanguage { config: config.clone(), diff --git a/crates/languages/src/python.rs b/crates/languages/src/python.rs index 6d515f2e442d375a93bf5bfaef351c092a842df4..2373d3a225f8e7d52db7c58ebcefd330c8b8d9a3 100644 --- a/crates/languages/src/python.rs +++ b/crates/languages/src/python.rs @@ -474,10 +474,6 @@ impl ContextProvider for PythonContextProvider { Some(TaskTemplates(tasks)) } - - fn debug_adapter(&self) -> Option { - Some("Debugpy".into()) - } } fn selected_test_runner(location: Option<&Arc>, cx: &App) -> TestRunner { diff --git a/crates/languages/src/python/config.toml b/crates/languages/src/python/config.toml index 6749f39060ba1289cfa8bed562e17ddb875bd81a..8181c0581ab7951d56ec0a479337762b59d875d4 100644 --- a/crates/languages/src/python/config.toml +++ b/crates/languages/src/python/config.toml @@ -29,3 +29,4 @@ brackets = [ auto_indent_using_last_non_empty_line = false increase_indent_pattern = "^[^#].*:\\s*$" decrease_indent_pattern = "^\\s*(else|elif|except|finally)\\b.*:" +debuggers = ["Debugpy"] diff --git a/crates/languages/src/rust.rs b/crates/languages/src/rust.rs index 726ce021f835db38c21f30dabe0cbf4ad949a631..5dd94f8b4d754e1d4fd10ebae7034da4b214676d 100644 --- a/crates/languages/src/rust.rs +++ b/crates/languages/src/rust.rs @@ -803,10 +803,6 @@ impl ContextProvider for RustContextProvider { fn lsp_task_source(&self) -> Option { Some(SERVER_NAME) } - - fn debug_adapter(&self) -> Option { - Some("CodeLLDB".to_owned()) - } } /// Part of the data structure of Cargo metadata diff --git a/crates/languages/src/rust/config.toml b/crates/languages/src/rust/config.toml index 96207904f5bc9552c59bb32f7471fbfac5b7bbb6..31fc9254191344b0fdb82d014bab21aa4840c873 100644 --- a/crates/languages/src/rust/config.toml +++ b/crates/languages/src/rust/config.toml @@ -15,3 +15,4 @@ brackets = [ { start = "/*", end = " */", close = true, newline = false, not_in = ["string", "comment"] }, ] collapsed_placeholder = " /* ... */ " +debuggers = ["CodeLLDB", "GDB"] diff --git a/crates/languages/src/tsx/config.toml b/crates/languages/src/tsx/config.toml index 6c0fdbbd5aa65c597b51bdf54f06c2bf4ea99992..8bdf0b5138e0a3e293f768576495cd91fa6eebaf 100644 --- a/crates/languages/src/tsx/config.toml +++ b/crates/languages/src/tsx/config.toml @@ -17,6 +17,7 @@ word_characters = ["#", "$"] scope_opt_in_language_servers = ["tailwindcss-language-server", "emmet-language-server"] prettier_parser_name = "typescript" tab_size = 2 +debuggers = ["JavaScript"] [jsx_tag_auto_close] open_tag_node_name = "jsx_opening_element" diff --git a/crates/languages/src/typescript/config.toml b/crates/languages/src/typescript/config.toml index 929deea31f9715bca7d72d8b2fafcce0cc150e48..29d71e94a156a618d19f19b205aecc9f4c7f1d98 100644 --- a/crates/languages/src/typescript/config.toml +++ b/crates/languages/src/typescript/config.toml @@ -17,6 +17,7 @@ brackets = [ word_characters = ["#", "$"] prettier_parser_name = "typescript" tab_size = 2 +debuggers = ["JavaScript"] [overrides.string] completion_query_characters = ["."] diff --git a/crates/project/src/task_inventory.rs b/crates/project/src/task_inventory.rs index 642ee2c0868c00d1f75a1dbaacbfec50fbde3cbc..88e1042be6e6dc1d5673ed93e14aaf2e423e77d1 100644 --- a/crates/project/src/task_inventory.rs +++ b/crates/project/src/task_inventory.rs @@ -808,10 +808,6 @@ impl ContextProvider for BasicContextProvider { Task::ready(Ok(task_variables)) } - - fn debug_adapter(&self) -> Option { - None - } } /// A ContextProvider that doesn't provide any task variables on it's own, though it has some associated tasks. @@ -835,10 +831,6 @@ impl ContextProvider for ContextProviderWithTasks { ) -> Option { Some(self.templates.clone()) } - - fn debug_adapter(&self) -> Option { - None - } } #[cfg(test)]