diff --git a/assets/settings/default.json b/assets/settings/default.json index 47d0ac45da..2506150012 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 4c6367bd25..50ac46d01a 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; - 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, - ) - }); - }); + 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, + ) + }); + })?; + + 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 5fb1a2dca3..b8ef6e1217 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 73aa4b095e..d82982e453 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 01567e0208..1c3318b089 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 7858a1eddf..dd14515428 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 578b0526d7..88ce7aeeea 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 2acc01cb03..797b9c4523 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 076f03e204..dd04bf1c35 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 7de1b9ba4f..b7252b13cf 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 3213fd2514..682404828c 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 8fc0042372..b5e1fcb4d8 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 c3faf10b81..37913884e6 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 59951c87e4..1e71417145 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 b41f469bd5..8c9c5da982 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 ebd78870a2..62503fd5ba 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 3156685b33..72051feab2 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 d1230c549a..15def17893 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 b5f8e1bc7a..112357f6c0 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 f5fbe66e26..b319229071 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 6d515f2e44..2373d3a225 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 6749f39060..8181c0581a 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 726ce021f8..5dd94f8b4d 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 96207904f5..31fc925419 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 6c0fdbbd5a..8bdf0b5138 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 929deea31f..29d71e94a1 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 642ee2c086..88e1042be6 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)]