summaryrefslogtreecommitdiff
path: root/helix-core
diff options
context:
space:
mode:
authorBlaž Hrastnik2023-05-19 00:39:35 +0000
committerGitHub2023-05-19 00:39:35 +0000
commit53f47bc47771c94dab51626ca025be28e62eba0c (patch)
treec8f5c59d40d1ecde227c209f898cc7afd6da5477 /helix-core
parent7f5940be80eaa3aec7903903072b7108f41dd97b (diff)
parent2a512f7c487f0a707a7eb158e24bd478433bcd91 (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.rs1
-rw-r--r--helix-core/src/syntax.rs178
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();