diff options
author | Blaž Hrastnik | 2023-05-19 00:39:35 +0000 |
---|---|---|
committer | GitHub | 2023-05-19 00:39:35 +0000 |
commit | 53f47bc47771c94dab51626ca025be28e62eba0c (patch) | |
tree | c8f5c59d40d1ecde227c209f898cc7afd6da5477 /helix-core | |
parent | 7f5940be80eaa3aec7903903072b7108f41dd97b (diff) | |
parent | 2a512f7c487f0a707a7eb158e24bd478433bcd91 (diff) |
Merge pull request #2507 from Philipp-M/multiple-language-servers
Add support for multiple language servers per language
Diffstat (limited to 'helix-core')
-rw-r--r-- | helix-core/src/diagnostic.rs | 1 | ||||
-rw-r--r-- | helix-core/src/syntax.rs | 178 |
2 files changed, 166 insertions, 13 deletions
diff --git a/helix-core/src/diagnostic.rs b/helix-core/src/diagnostic.rs index 58ddb038..0b75d2a5 100644 --- a/helix-core/src/diagnostic.rs +++ b/helix-core/src/diagnostic.rs @@ -43,6 +43,7 @@ pub struct Diagnostic { pub message: String, pub severity: Option<Severity>, pub code: Option<NumberOrString>, + pub language_server_id: usize, pub tags: Vec<DiagnosticTag>, pub source: Option<String>, pub data: Option<serde_json::Value>, diff --git a/helix-core/src/syntax.rs b/helix-core/src/syntax.rs index f36c985e..3fa7994d 100644 --- a/helix-core/src/syntax.rs +++ b/helix-core/src/syntax.rs @@ -16,8 +16,8 @@ use slotmap::{DefaultKey as LayerId, HopSlotMap}; use std::{ borrow::Cow, cell::RefCell, - collections::{HashMap, VecDeque}, - fmt, + collections::{HashMap, HashSet, VecDeque}, + fmt::{self, Display}, hash::{Hash, Hasher}, mem::{replace, transmute}, path::{Path, PathBuf}, @@ -26,7 +26,7 @@ use std::{ }; use once_cell::sync::{Lazy, OnceCell}; -use serde::{Deserialize, Serialize}; +use serde::{ser::SerializeSeq, Deserialize, Serialize}; use helix_loader::grammar::{get_language, load_runtime_file}; @@ -60,8 +60,11 @@ fn default_timeout() -> u64 { } #[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "kebab-case")] pub struct Configuration { pub language: Vec<LanguageConfiguration>, + #[serde(default)] + pub language_server: HashMap<String, LanguageServerConfiguration>, } impl Default for Configuration { @@ -75,7 +78,10 @@ impl Default for Configuration { #[serde(rename_all = "kebab-case", deny_unknown_fields)] pub struct LanguageConfiguration { #[serde(rename = "name")] - pub language_id: String, // c-sharp, rust + pub language_id: String, // c-sharp, rust, tsx + #[serde(rename = "language-id")] + // see the table under https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocumentItem + pub language_server_language_id: Option<String>, // csharp, rust, typescriptreact, for the language-server pub scope: String, // source.rust pub file_types: Vec<FileType>, // filename extension or ends_with? <Gemfile, rb, etc> #[serde(default)] @@ -85,9 +91,6 @@ pub struct LanguageConfiguration { pub text_width: Option<usize>, pub soft_wrap: Option<SoftWrap>, - #[serde(default, skip_serializing, deserialize_with = "deserialize_lsp_config")] - pub config: Option<serde_json::Value>, - #[serde(default)] pub auto_format: bool, @@ -107,8 +110,13 @@ pub struct LanguageConfiguration { #[serde(skip)] pub(crate) highlight_config: OnceCell<Option<Arc<HighlightConfiguration>>>, // tags_config OnceCell<> https://github.com/tree-sitter/tree-sitter/pull/583 - #[serde(skip_serializing_if = "Option::is_none")] - pub language_server: Option<LanguageServerConfiguration>, + #[serde( + default, + skip_serializing_if = "Vec::is_empty", + serialize_with = "serialize_lang_features", + deserialize_with = "deserialize_lang_features" + )] + pub language_servers: Vec<LanguageServerFeatures>, #[serde(skip_serializing_if = "Option::is_none")] pub indent: Option<IndentationConfiguration>, @@ -208,6 +216,133 @@ impl<'de> Deserialize<'de> for FileType { } } +#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)] +#[serde(rename_all = "kebab-case")] +pub enum LanguageServerFeature { + Format, + GotoDeclaration, + GotoDefinition, + GotoTypeDefinition, + GotoReference, + GotoImplementation, + // Goto, use bitflags, combining previous Goto members? + SignatureHelp, + Hover, + DocumentHighlight, + Completion, + CodeAction, + WorkspaceCommand, + DocumentSymbols, + WorkspaceSymbols, + // Symbols, use bitflags, see above? + Diagnostics, + RenameSymbol, + InlayHints, +} + +impl Display for LanguageServerFeature { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + use LanguageServerFeature::*; + let feature = match self { + Format => "format", + GotoDeclaration => "goto-declaration", + GotoDefinition => "goto-definition", + GotoTypeDefinition => "goto-type-definition", + GotoReference => "goto-type-definition", + GotoImplementation => "goto-implementation", + SignatureHelp => "signature-help", + Hover => "hover", + DocumentHighlight => "document-highlight", + Completion => "completion", + CodeAction => "code-action", + WorkspaceCommand => "workspace-command", + DocumentSymbols => "document-symbols", + WorkspaceSymbols => "workspace-symbols", + Diagnostics => "diagnostics", + RenameSymbol => "rename-symbol", + InlayHints => "inlay-hints", + }; + write!(f, "{feature}",) + } +} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(untagged, rename_all = "kebab-case", deny_unknown_fields)] +enum LanguageServerFeatureConfiguration { + #[serde(rename_all = "kebab-case")] + Features { + #[serde(default, skip_serializing_if = "HashSet::is_empty")] + only_features: HashSet<LanguageServerFeature>, + #[serde(default, skip_serializing_if = "HashSet::is_empty")] + except_features: HashSet<LanguageServerFeature>, + name: String, + }, + Simple(String), +} + +#[derive(Debug, Default)] +pub struct LanguageServerFeatures { + pub name: String, + pub only: HashSet<LanguageServerFeature>, + pub excluded: HashSet<LanguageServerFeature>, +} + +impl LanguageServerFeatures { + pub fn has_feature(&self, feature: LanguageServerFeature) -> bool { + (self.only.is_empty() || self.only.contains(&feature)) && !self.excluded.contains(&feature) + } +} + +fn deserialize_lang_features<'de, D>( + deserializer: D, +) -> Result<Vec<LanguageServerFeatures>, D::Error> +where + D: serde::Deserializer<'de>, +{ + let raw: Vec<LanguageServerFeatureConfiguration> = Deserialize::deserialize(deserializer)?; + let res = raw + .into_iter() + .map(|config| match config { + LanguageServerFeatureConfiguration::Simple(name) => LanguageServerFeatures { + name, + ..Default::default() + }, + LanguageServerFeatureConfiguration::Features { + only_features, + except_features, + name, + } => LanguageServerFeatures { + name, + only: only_features, + excluded: except_features, + }, + }) + .collect(); + Ok(res) +} +fn serialize_lang_features<S>( + map: &Vec<LanguageServerFeatures>, + serializer: S, +) -> Result<S::Ok, S::Error> +where + S: serde::Serializer, +{ + let mut serializer = serializer.serialize_seq(Some(map.len()))?; + for features in map { + let features = if features.only.is_empty() && features.excluded.is_empty() { + LanguageServerFeatureConfiguration::Simple(features.name.to_owned()) + } else { + LanguageServerFeatureConfiguration::Features { + only_features: features.only.clone(), + except_features: features.excluded.clone(), + name: features.name.to_owned(), + } + }; + serializer.serialize_element(&features)?; + } + serializer.end() +} + #[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "kebab-case")] pub struct LanguageServerConfiguration { @@ -217,9 +352,10 @@ pub struct LanguageServerConfiguration { pub args: Vec<String>, #[serde(default, skip_serializing_if = "HashMap::is_empty")] pub environment: HashMap<String, String>, + #[serde(default, skip_serializing, deserialize_with = "deserialize_lsp_config")] + pub config: Option<serde_json::Value>, #[serde(default = "default_timeout")] pub timeout: u64, - pub language_id: Option<String>, } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -594,6 +730,8 @@ pub struct Loader { language_config_ids_by_suffix: HashMap<String, usize>, language_config_ids_by_shebang: HashMap<String, usize>, + language_server_configs: HashMap<String, LanguageServerConfiguration>, + scopes: ArcSwap<Vec<String>>, } @@ -601,6 +739,7 @@ impl Loader { pub fn new(config: Configuration) -> Self { let mut loader = Self { language_configs: Vec::new(), + language_server_configs: config.language_server, language_config_ids_by_extension: HashMap::new(), language_config_ids_by_suffix: HashMap::new(), language_config_ids_by_shebang: HashMap::new(), @@ -725,6 +864,10 @@ impl Loader { self.language_configs.iter() } + pub fn language_server_configs(&self) -> &HashMap<String, LanguageServerConfiguration> { + &self.language_server_configs + } + pub fn set_scopes(&self, scopes: Vec<String>) { self.scopes.store(Arc::new(scopes)); @@ -2370,7 +2513,10 @@ mod test { "#, ); - let loader = Loader::new(Configuration { language: vec![] }); + let loader = Loader::new(Configuration { + language: vec![], + language_server: HashMap::new(), + }); let language = get_language("rust").unwrap(); let query = Query::new(language, query_str).unwrap(); @@ -2429,7 +2575,10 @@ mod test { .map(String::from) .collect(); - let loader = Loader::new(Configuration { language: vec![] }); + let loader = Loader::new(Configuration { + language: vec![], + language_server: HashMap::new(), + }); let language = get_language("rust").unwrap(); let config = HighlightConfiguration::new( @@ -2532,7 +2681,10 @@ mod test { ) { let source = Rope::from_str(source); - let loader = Loader::new(Configuration { language: vec![] }); + let loader = Loader::new(Configuration { + language: vec![], + language_server: HashMap::new(), + }); let language = get_language(language_name).unwrap(); let config = HighlightConfiguration::new(language, "", "", "").unwrap(); |