aboutsummaryrefslogtreecommitdiff
path: root/helix-core
diff options
context:
space:
mode:
authorPhilipp Mildenberger2022-05-23 16:10:48 +0000
committerPhilipp Mildenberger2023-05-18 19:48:30 +0000
commit71551d395b4e47804df2d8ecea99e34dbbf16157 (patch)
tree042b861690af3288ba81b9bad6fa5675255d6858 /helix-core
parent7f5940be80eaa3aec7903903072b7108f41dd97b (diff)
Adds support for multiple language servers per language.
Language Servers are now configured in a separate table in `languages.toml`: ```toml [langauge-server.mylang-lsp] command = "mylang-lsp" args = ["--stdio"] config = { provideFormatter = true } [language-server.efm-lsp-prettier] command = "efm-langserver" [language-server.efm-lsp-prettier.config] documentFormatting = true languages = { typescript = [ { formatCommand ="prettier --stdin-filepath ${INPUT}", formatStdin = true } ] } ``` The language server for a language is configured like this (`typescript-language-server` is configured by default): ```toml [[language]] name = "typescript" language-servers = [ { name = "efm-lsp-prettier", only-features = [ "format" ] }, "typescript-language-server" ] ``` or equivalent: ```toml [[language]] name = "typescript" language-servers = [ { name = "typescript-language-server", except-features = [ "format" ] }, "efm-lsp-prettier" ] ``` Each requested LSP feature is priorized in the order of the `language-servers` array. For example the first `goto-definition` supported language server (in this case `typescript-language-server`) will be taken for the relevant LSP request (command `goto_definition`). If no `except-features` or `only-features` is given all features for the language server are enabled, as long as the language server supports these. If it doesn't the next language server which supports the feature is tried. The list of supported features are: - `format` - `goto-definition` - `goto-declaration` - `goto-type-definition` - `goto-reference` - `goto-implementation` - `signature-help` - `hover` - `document-highlight` - `completion` - `code-action` - `workspace-command` - `document-symbols` - `workspace-symbols` - `diagnostics` - `rename-symbol` - `inlay-hints` Another side-effect/difference that comes with this PR, is that only one language server instance is started if different languages use the same language server.
Diffstat (limited to 'helix-core')
-rw-r--r--helix-core/src/diagnostic.rs1
-rw-r--r--helix-core/src/syntax.rs113
2 files changed, 103 insertions, 11 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..ff4bb6c2 100644
--- a/helix-core/src/syntax.rs
+++ b/helix-core/src/syntax.rs
@@ -17,7 +17,7 @@ use std::{
borrow::Cow,
cell::RefCell,
collections::{HashMap, VecDeque},
- fmt,
+ fmt::{self, Display},
hash::{Hash, Hasher},
mem::{replace, transmute},
path::{Path, PathBuf},
@@ -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,8 @@ 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")]
+ pub language_servers: Vec<LanguageServerFeatureConfiguration>,
#[serde(skip_serializing_if = "Option::is_none")]
pub indent: Option<IndentationConfiguration>,
@@ -208,6 +211,68 @@ impl<'de> Deserialize<'de> for FileType {
}
}
+#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)]
+#[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 {
+ match self {
+ LanguageServerFeature::Format => write!(f, "format"),
+ LanguageServerFeature::GotoDeclaration => write!(f, "goto-declaration"),
+ LanguageServerFeature::GotoDefinition => write!(f, "goto-definition"),
+ LanguageServerFeature::GotoTypeDefinition => write!(f, "goto-type-definition"),
+ LanguageServerFeature::GotoReference => write!(f, "goto-type-definition"),
+ LanguageServerFeature::GotoImplementation => write!(f, "goto-implementation"),
+ LanguageServerFeature::SignatureHelp => write!(f, "signature-help"),
+ LanguageServerFeature::Hover => write!(f, "hover"),
+ LanguageServerFeature::DocumentHighlight => write!(f, "document-highlight"),
+ LanguageServerFeature::Completion => write!(f, "completion"),
+ LanguageServerFeature::CodeAction => write!(f, "code-action"),
+ LanguageServerFeature::WorkspaceCommand => write!(f, "workspace-command"),
+ LanguageServerFeature::DocumentSymbols => write!(f, "document-symbols"),
+ LanguageServerFeature::WorkspaceSymbols => write!(f, "workspace-symbols"),
+ LanguageServerFeature::Diagnostics => write!(f, "diagnostics"),
+ LanguageServerFeature::RenameSymbol => write!(f, "rename-symbol"),
+ LanguageServerFeature::InlayHints => write!(f, "inlay-hints"),
+ }
+ }
+}
+
+#[derive(Debug, Serialize, Deserialize)]
+#[serde(untagged, rename_all = "kebab-case", deny_unknown_fields)]
+pub enum LanguageServerFeatureConfiguration {
+ #[serde(rename_all = "kebab-case")]
+ Features {
+ #[serde(default, skip_serializing_if = "Vec::is_empty")]
+ only_features: Vec<LanguageServerFeature>,
+ #[serde(default, skip_serializing_if = "Vec::is_empty")]
+ except_features: Vec<LanguageServerFeature>,
+ name: String,
+ },
+ Simple(String),
+}
+
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct LanguageServerConfiguration {
@@ -217,9 +282,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)]
@@ -584,6 +650,15 @@ pub struct SoftWrap {
pub wrap_at_text_width: Option<bool>,
}
+impl LanguageServerFeatureConfiguration {
+ pub fn name(&self) -> &String {
+ match self {
+ LanguageServerFeatureConfiguration::Simple(name) => name,
+ LanguageServerFeatureConfiguration::Features { name, .. } => name,
+ }
+ }
+}
+
// Expose loader as Lazy<> global since it's always static?
#[derive(Debug)]
@@ -594,6 +669,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 +678,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 +803,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 +2452,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 +2514,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 +2620,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();