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 <hello@anthonyeid.me>
Co-authored-by: Remco Smits <djsmits12@gmail.com>
Co-authored-by: Anthony <anthony@zed.dev>
This commit is contained in:
Piotr Osiewicz 2025-05-06 20:16:41 +02:00 committed by GitHub
parent 544e8fc46c
commit 09d3ff9dbe
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
27 changed files with 386 additions and 216 deletions

View file

@ -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<usize>,
}
#[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<bool>,
/// 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<Regex>,
/// 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<Regex>,
/// 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<char>,
/// A list of preferred debuggers for this language.
#[serde(default)]
pub debuggers: IndexSet<String>,
}
#[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<u32>,
}
#[derive(Clone, Deserialize, Default, Debug, JsonSchema)]
#[derive(Clone, Deserialize, Default, Debug, JsonSchema, Serialize)]
pub struct LanguageConfigOverride {
#[serde(default)]
pub line_comments: Override<Vec<Arc<str>>>,
@ -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<BracketPair>,
@ -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,

View file

@ -145,24 +145,24 @@ pub enum BinaryStatus {
#[derive(Clone)]
pub struct AvailableLanguage {
id: LanguageId,
name: LanguageName,
grammar: Option<Arc<str>>,
matcher: LanguageMatcher,
hidden: bool,
config: LanguageConfig,
load: Arc<dyn Fn() -> Result<LoadedLanguage> + '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<Arc<str>>,
matcher: LanguageMatcher,
hidden: bool,
config: LanguageConfig,
load: Arc<dyn Fn() -> Result<LoadedLanguage> + '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::<Vec<_>>();
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;

View file

@ -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<String>,
}
impl LanguageSettings {
@ -551,6 +553,10 @@ pub struct LanguageSettingsContent {
pub show_completion_documentation: Option<bool>,
/// Controls how completions are processed for this language.
pub completions: Option<CompletionSettings>,
/// Preferred debuggers for this language.
///
/// Default: []
pub debuggers: Option<Vec<String>>,
}
/// The behavior of `editor::Rewrap`.

View file

@ -47,7 +47,4 @@ pub trait ContextProvider: Send + Sync {
fn lsp_task_source(&self) -> Option<LanguageServerName> {
None
}
/// Default debug adapter for a given language.
fn debug_adapter(&self) -> Option<String>;
}