mirror of https://github.com/helix-editor/helix
Refactored doc.language_servers and doc.language_servers_with_feature to return an iterator and refactor LanguageServerFeature handling to a HashMap (language server name maps to features)
Co-authored-by: Pascal Kuthe <pascal.kuthe@semimod.de>pull/2507/head
parent
0637691eb1
commit
76b5cab524
|
@ -16,7 +16,7 @@ use slotmap::{DefaultKey as LayerId, HopSlotMap};
|
||||||
use std::{
|
use std::{
|
||||||
borrow::Cow,
|
borrow::Cow,
|
||||||
cell::RefCell,
|
cell::RefCell,
|
||||||
collections::{HashMap, VecDeque},
|
collections::{HashMap, HashSet, VecDeque},
|
||||||
fmt::{self, Display},
|
fmt::{self, Display},
|
||||||
hash::{Hash, Hasher},
|
hash::{Hash, Hasher},
|
||||||
mem::{replace, transmute},
|
mem::{replace, transmute},
|
||||||
|
@ -26,7 +26,7 @@ use std::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use once_cell::sync::{Lazy, OnceCell};
|
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};
|
use helix_loader::grammar::{get_language, load_runtime_file};
|
||||||
|
|
||||||
|
@ -110,8 +110,13 @@ pub struct LanguageConfiguration {
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
pub(crate) highlight_config: OnceCell<Option<Arc<HighlightConfiguration>>>,
|
pub(crate) highlight_config: OnceCell<Option<Arc<HighlightConfiguration>>>,
|
||||||
// tags_config OnceCell<> https://github.com/tree-sitter/tree-sitter/pull/583
|
// tags_config OnceCell<> https://github.com/tree-sitter/tree-sitter/pull/583
|
||||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
#[serde(
|
||||||
pub language_servers: Vec<LanguageServerFeatureConfiguration>,
|
default,
|
||||||
|
skip_serializing_if = "HashMap::is_empty",
|
||||||
|
serialize_with = "serialize_lang_features",
|
||||||
|
deserialize_with = "deserialize_lang_features"
|
||||||
|
)]
|
||||||
|
pub language_servers: HashMap<String, LanguageServerFeatures>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub indent: Option<IndentationConfiguration>,
|
pub indent: Option<IndentationConfiguration>,
|
||||||
|
|
||||||
|
@ -211,7 +216,7 @@ impl<'de> Deserialize<'de> for FileType {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
|
||||||
#[serde(rename_all = "kebab-case")]
|
#[serde(rename_all = "kebab-case")]
|
||||||
pub enum LanguageServerFeature {
|
pub enum LanguageServerFeature {
|
||||||
Format,
|
Format,
|
||||||
|
@ -261,18 +266,81 @@ impl Display for LanguageServerFeature {
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
#[serde(untagged, rename_all = "kebab-case", deny_unknown_fields)]
|
#[serde(untagged, rename_all = "kebab-case", deny_unknown_fields)]
|
||||||
pub enum LanguageServerFeatureConfiguration {
|
enum LanguageServerFeatureConfiguration {
|
||||||
#[serde(rename_all = "kebab-case")]
|
#[serde(rename_all = "kebab-case")]
|
||||||
Features {
|
Features {
|
||||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
#[serde(default, skip_serializing_if = "HashSet::is_empty")]
|
||||||
only_features: Vec<LanguageServerFeature>,
|
only_features: HashSet<LanguageServerFeature>,
|
||||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
#[serde(default, skip_serializing_if = "HashSet::is_empty")]
|
||||||
except_features: Vec<LanguageServerFeature>,
|
except_features: HashSet<LanguageServerFeature>,
|
||||||
name: String,
|
name: String,
|
||||||
},
|
},
|
||||||
Simple(String),
|
Simple(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct LanguageServerFeatures {
|
||||||
|
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<HashMap<String, 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) => {
|
||||||
|
(name, LanguageServerFeatures::default())
|
||||||
|
}
|
||||||
|
LanguageServerFeatureConfiguration::Features {
|
||||||
|
only_features,
|
||||||
|
except_features,
|
||||||
|
name,
|
||||||
|
} => (
|
||||||
|
name,
|
||||||
|
LanguageServerFeatures {
|
||||||
|
only: only_features,
|
||||||
|
excluded: except_features,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
fn serialize_lang_features<S>(
|
||||||
|
map: &HashMap<String, LanguageServerFeatures>,
|
||||||
|
serializer: S,
|
||||||
|
) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: serde::Serializer,
|
||||||
|
{
|
||||||
|
let mut serializer = serializer.serialize_seq(Some(map.len()))?;
|
||||||
|
for (name, features) in map {
|
||||||
|
let features = if features.only.is_empty() && features.excluded.is_empty() {
|
||||||
|
LanguageServerFeatureConfiguration::Simple(name.to_owned())
|
||||||
|
} else {
|
||||||
|
LanguageServerFeatureConfiguration::Features {
|
||||||
|
only_features: features.only.clone(),
|
||||||
|
except_features: features.excluded.clone(),
|
||||||
|
name: name.to_owned(),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
serializer.serialize_element(&features)?;
|
||||||
|
}
|
||||||
|
serializer.end()
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "kebab-case")]
|
#[serde(rename_all = "kebab-case")]
|
||||||
pub struct LanguageServerConfiguration {
|
pub struct LanguageServerConfiguration {
|
||||||
|
@ -650,15 +718,6 @@ pub struct SoftWrap {
|
||||||
pub wrap_at_text_width: Option<bool>,
|
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?
|
// Expose loader as Lazy<> global since it's always static?
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
|
|
@ -689,12 +689,10 @@ impl Registry {
|
||||||
) -> Result<Vec<Arc<Client>>> {
|
) -> Result<Vec<Arc<Client>>> {
|
||||||
language_config
|
language_config
|
||||||
.language_servers
|
.language_servers
|
||||||
.iter()
|
.keys()
|
||||||
.filter_map(|config| {
|
.filter_map(|name| {
|
||||||
let name = config.name().clone();
|
|
||||||
|
|
||||||
#[allow(clippy::map_entry)]
|
#[allow(clippy::map_entry)]
|
||||||
if self.inner.contains_key(&name) {
|
if self.inner.contains_key(name) {
|
||||||
let client = match self.start_client(
|
let client = match self.start_client(
|
||||||
name.clone(),
|
name.clone(),
|
||||||
language_config,
|
language_config,
|
||||||
|
@ -705,7 +703,10 @@ impl Registry {
|
||||||
Ok(client) => client,
|
Ok(client) => client,
|
||||||
error => return Some(error),
|
error => return Some(error),
|
||||||
};
|
};
|
||||||
let old_clients = self.inner.insert(name, vec![client.clone()]).unwrap();
|
let old_clients = self
|
||||||
|
.inner
|
||||||
|
.insert(name.clone(), vec![client.clone()])
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
// TODO what if there are different language servers for different workspaces,
|
// TODO what if there are different language servers for different workspaces,
|
||||||
// I think the language servers will be stopped without being restarted, which is not intended
|
// I think the language servers will be stopped without being restarted, which is not intended
|
||||||
|
@ -742,9 +743,8 @@ impl Registry {
|
||||||
) -> Result<Vec<Arc<Client>>> {
|
) -> Result<Vec<Arc<Client>>> {
|
||||||
language_config
|
language_config
|
||||||
.language_servers
|
.language_servers
|
||||||
.iter()
|
.keys()
|
||||||
.map(|features| {
|
.map(|name| {
|
||||||
let name = features.name();
|
|
||||||
if let Some(clients) = self.inner.get_mut(name) {
|
if let Some(clients) = self.inner.get_mut(name) {
|
||||||
if let Some((_, client)) = clients.iter_mut().enumerate().find(|(i, client)| {
|
if let Some((_, client)) = clients.iter_mut().enumerate().find(|(i, client)| {
|
||||||
client.try_add_doc(&language_config.roots, root_dirs, doc_path, *i == 0)
|
client.try_add_doc(&language_config.roots, root_dirs, doc_path, *i == 0)
|
||||||
|
@ -759,7 +759,7 @@ impl Registry {
|
||||||
root_dirs,
|
root_dirs,
|
||||||
enable_snippets,
|
enable_snippets,
|
||||||
)?;
|
)?;
|
||||||
let clients = self.inner.entry(features.name().clone()).or_default();
|
let clients = self.inner.entry(name.clone()).or_default();
|
||||||
clients.push(client.clone());
|
clients.push(client.clone());
|
||||||
Ok(client)
|
Ok(client)
|
||||||
})
|
})
|
||||||
|
|
|
@ -699,9 +699,10 @@ impl Application {
|
||||||
tokio::spawn(language_server.did_change_configuration(config.clone()));
|
tokio::spawn(language_server.did_change_configuration(config.clone()));
|
||||||
}
|
}
|
||||||
|
|
||||||
let docs = self.editor.documents().filter(|doc| {
|
let docs = self
|
||||||
doc.language_servers().iter().any(|l| l.id() == server_id)
|
.editor
|
||||||
});
|
.documents()
|
||||||
|
.filter(|doc| doc.language_servers().any(|l| l.id() == server_id));
|
||||||
|
|
||||||
// trigger textDocument/didOpen for docs that are already open
|
// trigger textDocument/didOpen for docs that are already open
|
||||||
for doc in docs {
|
for doc in docs {
|
||||||
|
@ -970,7 +971,6 @@ impl Application {
|
||||||
.filter_map(|doc| {
|
.filter_map(|doc| {
|
||||||
if doc
|
if doc
|
||||||
.language_servers()
|
.language_servers()
|
||||||
.iter()
|
|
||||||
.any(|server| server.id() == server_id)
|
.any(|server| server.id() == server_id)
|
||||||
{
|
{
|
||||||
doc.clear_diagnostics(server_id);
|
doc.clear_diagnostics(server_id);
|
||||||
|
|
|
@ -3235,7 +3235,6 @@ pub mod insert {
|
||||||
let doc = doc_mut!(cx.editor);
|
let doc = doc_mut!(cx.editor);
|
||||||
let trigger_completion = doc
|
let trigger_completion = doc
|
||||||
.language_servers_with_feature(LanguageServerFeature::Completion)
|
.language_servers_with_feature(LanguageServerFeature::Completion)
|
||||||
.iter()
|
|
||||||
.any(|ls| {
|
.any(|ls| {
|
||||||
let capabilities = ls.capabilities();
|
let capabilities = ls.capabilities();
|
||||||
|
|
||||||
|
@ -3264,7 +3263,6 @@ pub mod insert {
|
||||||
// TODO support multiple language servers (not just the first that is found)
|
// TODO support multiple language servers (not just the first that is found)
|
||||||
let future = doc
|
let future = doc
|
||||||
.language_servers_with_feature(LanguageServerFeature::SignatureHelp)
|
.language_servers_with_feature(LanguageServerFeature::SignatureHelp)
|
||||||
.iter()
|
|
||||||
.find_map(|ls| {
|
.find_map(|ls| {
|
||||||
let capabilities = ls.capabilities();
|
let capabilities = ls.capabilities();
|
||||||
|
|
||||||
|
@ -4067,10 +4065,8 @@ fn format_selections(cx: &mut Context) {
|
||||||
.set_error("format_selections only supports a single selection for now");
|
.set_error("format_selections only supports a single selection for now");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
let future_offset_encoding = doc
|
||||||
let (future, offset_encoding) = match doc
|
|
||||||
.language_servers_with_feature(LanguageServerFeature::Format)
|
.language_servers_with_feature(LanguageServerFeature::Format)
|
||||||
.iter()
|
|
||||||
.find_map(|language_server| {
|
.find_map(|language_server| {
|
||||||
let offset_encoding = language_server.offset_encoding();
|
let offset_encoding = language_server.offset_encoding();
|
||||||
let ranges: Vec<lsp::Range> = doc
|
let ranges: Vec<lsp::Range> = doc
|
||||||
|
@ -4091,7 +4087,9 @@ fn format_selections(cx: &mut Context) {
|
||||||
None,
|
None,
|
||||||
)?;
|
)?;
|
||||||
Some((future, offset_encoding))
|
Some((future, offset_encoding))
|
||||||
}) {
|
});
|
||||||
|
|
||||||
|
let (future, offset_encoding) = match future_offset_encoding {
|
||||||
Some(future_offset_encoding) => future_offset_encoding,
|
Some(future_offset_encoding) => future_offset_encoding,
|
||||||
None => {
|
None => {
|
||||||
cx.editor
|
cx.editor
|
||||||
|
@ -4247,7 +4245,6 @@ pub fn completion(cx: &mut Context) {
|
||||||
|
|
||||||
let mut futures: FuturesUnordered<_> = doc
|
let mut futures: FuturesUnordered<_> = doc
|
||||||
.language_servers_with_feature(LanguageServerFeature::Completion)
|
.language_servers_with_feature(LanguageServerFeature::Completion)
|
||||||
.iter()
|
|
||||||
// TODO this should probably already been filtered in something like "language_servers_with_feature"
|
// TODO this should probably already been filtered in something like "language_servers_with_feature"
|
||||||
.filter_map(|language_server| {
|
.filter_map(|language_server| {
|
||||||
let language_server_id = language_server.id();
|
let language_server_id = language_server.id();
|
||||||
|
|
|
@ -353,7 +353,6 @@ pub fn symbol_picker(cx: &mut Context) {
|
||||||
|
|
||||||
let mut futures: FuturesUnordered<_> = doc
|
let mut futures: FuturesUnordered<_> = doc
|
||||||
.language_servers_with_feature(LanguageServerFeature::DocumentSymbols)
|
.language_servers_with_feature(LanguageServerFeature::DocumentSymbols)
|
||||||
.iter()
|
|
||||||
.filter_map(|ls| {
|
.filter_map(|ls| {
|
||||||
let request = ls.document_symbols(doc.identifier())?;
|
let request = ls.document_symbols(doc.identifier())?;
|
||||||
Some((request, ls.offset_encoding(), doc.identifier()))
|
Some((request, ls.offset_encoding(), doc.identifier()))
|
||||||
|
@ -420,7 +419,6 @@ pub fn workspace_symbol_picker(cx: &mut Context) {
|
||||||
let doc = doc!(editor);
|
let doc = doc!(editor);
|
||||||
let mut futures: FuturesUnordered<_> = doc
|
let mut futures: FuturesUnordered<_> = doc
|
||||||
.language_servers_with_feature(LanguageServerFeature::WorkspaceSymbols)
|
.language_servers_with_feature(LanguageServerFeature::WorkspaceSymbols)
|
||||||
.iter()
|
|
||||||
.filter_map(|ls| Some((ls.workspace_symbols(pattern.clone())?, ls.offset_encoding())))
|
.filter_map(|ls| Some((ls.workspace_symbols(pattern.clone())?, ls.offset_encoding())))
|
||||||
.map(|(request, offset_encoding)| async move {
|
.map(|(request, offset_encoding)| async move {
|
||||||
let json = request.await?;
|
let json = request.await?;
|
||||||
|
@ -581,7 +579,6 @@ pub fn code_action(cx: &mut Context) {
|
||||||
|
|
||||||
let mut futures: FuturesUnordered<_> = doc
|
let mut futures: FuturesUnordered<_> = doc
|
||||||
.language_servers_with_feature(LanguageServerFeature::CodeAction)
|
.language_servers_with_feature(LanguageServerFeature::CodeAction)
|
||||||
.iter()
|
|
||||||
// TODO this should probably already been filtered in something like "language_servers_with_feature"
|
// TODO this should probably already been filtered in something like "language_servers_with_feature"
|
||||||
.filter_map(|language_server| {
|
.filter_map(|language_server| {
|
||||||
let offset_encoding = language_server.offset_encoding();
|
let offset_encoding = language_server.offset_encoding();
|
||||||
|
@ -1034,15 +1031,15 @@ fn to_locations(definitions: Option<lsp::GotoDefinitionResponse>) -> Vec<lsp::Lo
|
||||||
// TODO find a way to reduce boilerplate of all the goto functions, without unnecessary complexity...
|
// TODO find a way to reduce boilerplate of all the goto functions, without unnecessary complexity...
|
||||||
pub fn goto_declaration(cx: &mut Context) {
|
pub fn goto_declaration(cx: &mut Context) {
|
||||||
let (view, doc) = current!(cx.editor);
|
let (view, doc) = current!(cx.editor);
|
||||||
let (future, offset_encoding) = match doc
|
let future_offset_encoding = doc
|
||||||
.language_servers_with_feature(LanguageServerFeature::GotoDeclaration)
|
.language_servers_with_feature(LanguageServerFeature::GotoDeclaration)
|
||||||
.iter()
|
|
||||||
.find_map(|language_server| {
|
.find_map(|language_server| {
|
||||||
let offset_encoding = language_server.offset_encoding();
|
let offset_encoding = language_server.offset_encoding();
|
||||||
let pos = doc.position(view.id, offset_encoding);
|
let pos = doc.position(view.id, offset_encoding);
|
||||||
let future = language_server.goto_declaration(doc.identifier(), pos, None)?;
|
let future = language_server.goto_declaration(doc.identifier(), pos, None)?;
|
||||||
Some((future, offset_encoding))
|
Some((future, offset_encoding))
|
||||||
}) {
|
});
|
||||||
|
let (future, offset_encoding) = match future_offset_encoding {
|
||||||
Some(future_offset_encoding) => future_offset_encoding,
|
Some(future_offset_encoding) => future_offset_encoding,
|
||||||
None => {
|
None => {
|
||||||
cx.editor
|
cx.editor
|
||||||
|
@ -1062,15 +1059,15 @@ pub fn goto_declaration(cx: &mut Context) {
|
||||||
|
|
||||||
pub fn goto_definition(cx: &mut Context) {
|
pub fn goto_definition(cx: &mut Context) {
|
||||||
let (view, doc) = current!(cx.editor);
|
let (view, doc) = current!(cx.editor);
|
||||||
let (future, offset_encoding) = match doc
|
let future_offset_encoding = doc
|
||||||
.language_servers_with_feature(LanguageServerFeature::GotoDefinition)
|
.language_servers_with_feature(LanguageServerFeature::GotoDefinition)
|
||||||
.iter()
|
|
||||||
.find_map(|language_server| {
|
.find_map(|language_server| {
|
||||||
let offset_encoding = language_server.offset_encoding();
|
let offset_encoding = language_server.offset_encoding();
|
||||||
let pos = doc.position(view.id, offset_encoding);
|
let pos = doc.position(view.id, offset_encoding);
|
||||||
let future = language_server.goto_definition(doc.identifier(), pos, None)?;
|
let future = language_server.goto_definition(doc.identifier(), pos, None)?;
|
||||||
Some((future, offset_encoding))
|
Some((future, offset_encoding))
|
||||||
}) {
|
});
|
||||||
|
let (future, offset_encoding) = match future_offset_encoding {
|
||||||
Some(future_offset_encoding) => future_offset_encoding,
|
Some(future_offset_encoding) => future_offset_encoding,
|
||||||
None => {
|
None => {
|
||||||
cx.editor
|
cx.editor
|
||||||
|
@ -1090,15 +1087,15 @@ pub fn goto_definition(cx: &mut Context) {
|
||||||
|
|
||||||
pub fn goto_type_definition(cx: &mut Context) {
|
pub fn goto_type_definition(cx: &mut Context) {
|
||||||
let (view, doc) = current!(cx.editor);
|
let (view, doc) = current!(cx.editor);
|
||||||
let (future, offset_encoding) = match doc
|
let future_offset_encoding = doc
|
||||||
.language_servers_with_feature(LanguageServerFeature::GotoTypeDefinition)
|
.language_servers_with_feature(LanguageServerFeature::GotoTypeDefinition)
|
||||||
.iter()
|
|
||||||
.find_map(|language_server| {
|
.find_map(|language_server| {
|
||||||
let offset_encoding = language_server.offset_encoding();
|
let offset_encoding = language_server.offset_encoding();
|
||||||
let pos = doc.position(view.id, offset_encoding);
|
let pos = doc.position(view.id, offset_encoding);
|
||||||
let future = language_server.goto_type_definition(doc.identifier(), pos, None)?;
|
let future = language_server.goto_type_definition(doc.identifier(), pos, None)?;
|
||||||
Some((future, offset_encoding))
|
Some((future, offset_encoding))
|
||||||
}) {
|
});
|
||||||
|
let (future, offset_encoding) = match future_offset_encoding {
|
||||||
Some(future_offset_encoding) => future_offset_encoding,
|
Some(future_offset_encoding) => future_offset_encoding,
|
||||||
None => {
|
None => {
|
||||||
cx.editor
|
cx.editor
|
||||||
|
@ -1118,15 +1115,15 @@ pub fn goto_type_definition(cx: &mut Context) {
|
||||||
|
|
||||||
pub fn goto_implementation(cx: &mut Context) {
|
pub fn goto_implementation(cx: &mut Context) {
|
||||||
let (view, doc) = current!(cx.editor);
|
let (view, doc) = current!(cx.editor);
|
||||||
let (future, offset_encoding) = match doc
|
let future_offset_encoding = doc
|
||||||
.language_servers_with_feature(LanguageServerFeature::GotoImplementation)
|
.language_servers_with_feature(LanguageServerFeature::GotoImplementation)
|
||||||
.iter()
|
|
||||||
.find_map(|language_server| {
|
.find_map(|language_server| {
|
||||||
let offset_encoding = language_server.offset_encoding();
|
let offset_encoding = language_server.offset_encoding();
|
||||||
let pos = doc.position(view.id, offset_encoding);
|
let pos = doc.position(view.id, offset_encoding);
|
||||||
let future = language_server.goto_implementation(doc.identifier(), pos, None)?;
|
let future = language_server.goto_implementation(doc.identifier(), pos, None)?;
|
||||||
Some((future, offset_encoding))
|
Some((future, offset_encoding))
|
||||||
}) {
|
});
|
||||||
|
let (future, offset_encoding) = match future_offset_encoding {
|
||||||
Some(future_offset_encoding) => future_offset_encoding,
|
Some(future_offset_encoding) => future_offset_encoding,
|
||||||
None => {
|
None => {
|
||||||
cx.editor
|
cx.editor
|
||||||
|
@ -1147,9 +1144,8 @@ pub fn goto_implementation(cx: &mut Context) {
|
||||||
pub fn goto_reference(cx: &mut Context) {
|
pub fn goto_reference(cx: &mut Context) {
|
||||||
let config = cx.editor.config();
|
let config = cx.editor.config();
|
||||||
let (view, doc) = current!(cx.editor);
|
let (view, doc) = current!(cx.editor);
|
||||||
let (future, offset_encoding) = match doc
|
let future_offset_encoding = doc
|
||||||
.language_servers_with_feature(LanguageServerFeature::GotoReference)
|
.language_servers_with_feature(LanguageServerFeature::GotoReference)
|
||||||
.iter()
|
|
||||||
.find_map(|language_server| {
|
.find_map(|language_server| {
|
||||||
let offset_encoding = language_server.offset_encoding();
|
let offset_encoding = language_server.offset_encoding();
|
||||||
let pos = doc.position(view.id, offset_encoding);
|
let pos = doc.position(view.id, offset_encoding);
|
||||||
|
@ -1160,7 +1156,8 @@ pub fn goto_reference(cx: &mut Context) {
|
||||||
None,
|
None,
|
||||||
)?;
|
)?;
|
||||||
Some((future, offset_encoding))
|
Some((future, offset_encoding))
|
||||||
}) {
|
});
|
||||||
|
let (future, offset_encoding) = match future_offset_encoding {
|
||||||
Some(future_offset_encoding) => future_offset_encoding,
|
Some(future_offset_encoding) => future_offset_encoding,
|
||||||
None => {
|
None => {
|
||||||
cx.editor
|
cx.editor
|
||||||
|
@ -1192,13 +1189,14 @@ pub fn signature_help_impl(cx: &mut Context, invoked: SignatureHelpInvoked) {
|
||||||
let (view, doc) = current!(cx.editor);
|
let (view, doc) = current!(cx.editor);
|
||||||
|
|
||||||
// TODO merge multiple language server signature help into one instead of just taking the first language server that supports it
|
// TODO merge multiple language server signature help into one instead of just taking the first language server that supports it
|
||||||
let future = match doc
|
let future = doc
|
||||||
.language_servers_with_feature(LanguageServerFeature::SignatureHelp)
|
.language_servers_with_feature(LanguageServerFeature::SignatureHelp)
|
||||||
.iter()
|
|
||||||
.find_map(|language_server| {
|
.find_map(|language_server| {
|
||||||
let pos = doc.position(view.id, language_server.offset_encoding());
|
let pos = doc.position(view.id, language_server.offset_encoding());
|
||||||
language_server.text_document_signature_help(doc.identifier(), pos, None)
|
language_server.text_document_signature_help(doc.identifier(), pos, None)
|
||||||
}) {
|
});
|
||||||
|
|
||||||
|
let future = match future {
|
||||||
Some(future) => future.boxed(),
|
Some(future) => future.boxed(),
|
||||||
None => {
|
None => {
|
||||||
// Do not show the message if signature help was invoked
|
// Do not show the message if signature help was invoked
|
||||||
|
@ -1328,7 +1326,6 @@ pub fn hover(cx: &mut Context) {
|
||||||
// TODO: factor out a doc.position_identifier() that returns lsp::TextDocumentPositionIdentifier
|
// TODO: factor out a doc.position_identifier() that returns lsp::TextDocumentPositionIdentifier
|
||||||
let request = doc
|
let request = doc
|
||||||
.language_servers_with_feature(LanguageServerFeature::Hover)
|
.language_servers_with_feature(LanguageServerFeature::Hover)
|
||||||
.iter()
|
|
||||||
.find_map(|language_server| {
|
.find_map(|language_server| {
|
||||||
let pos = doc.position(view.id, language_server.offset_encoding());
|
let pos = doc.position(view.id, language_server.offset_encoding());
|
||||||
language_server.text_document_hover(doc.identifier(), pos, None)
|
language_server.text_document_hover(doc.identifier(), pos, None)
|
||||||
|
@ -1436,7 +1433,6 @@ pub fn rename_symbol(cx: &mut Context) {
|
||||||
let (view, doc) = current!(cx.editor);
|
let (view, doc) = current!(cx.editor);
|
||||||
let request = doc
|
let request = doc
|
||||||
.language_servers_with_feature(LanguageServerFeature::RenameSymbol)
|
.language_servers_with_feature(LanguageServerFeature::RenameSymbol)
|
||||||
.iter()
|
|
||||||
.find_map(|language_server| {
|
.find_map(|language_server| {
|
||||||
if let Some(language_server_id) = language_server_id {
|
if let Some(language_server_id) = language_server_id {
|
||||||
if language_server.id() != language_server_id {
|
if language_server.id() != language_server_id {
|
||||||
|
@ -1475,7 +1471,6 @@ pub fn rename_symbol(cx: &mut Context) {
|
||||||
|
|
||||||
let prepare_rename_request = doc
|
let prepare_rename_request = doc
|
||||||
.language_servers_with_feature(LanguageServerFeature::RenameSymbol)
|
.language_servers_with_feature(LanguageServerFeature::RenameSymbol)
|
||||||
.iter()
|
|
||||||
.find_map(|language_server| {
|
.find_map(|language_server| {
|
||||||
let offset_encoding = language_server.offset_encoding();
|
let offset_encoding = language_server.offset_encoding();
|
||||||
let pos = doc.position(view.id, offset_encoding);
|
let pos = doc.position(view.id, offset_encoding);
|
||||||
|
@ -1516,17 +1511,17 @@ pub fn rename_symbol(cx: &mut Context) {
|
||||||
|
|
||||||
pub fn select_references_to_symbol_under_cursor(cx: &mut Context) {
|
pub fn select_references_to_symbol_under_cursor(cx: &mut Context) {
|
||||||
let (view, doc) = current!(cx.editor);
|
let (view, doc) = current!(cx.editor);
|
||||||
let (future, offset_encoding) = match doc
|
let future_offset_encoding = doc
|
||||||
.language_servers_with_feature(LanguageServerFeature::DocumentHighlight)
|
.language_servers_with_feature(LanguageServerFeature::DocumentHighlight)
|
||||||
.iter()
|
|
||||||
.find_map(|language_server| {
|
.find_map(|language_server| {
|
||||||
let offset_encoding = language_server.offset_encoding();
|
let offset_encoding = language_server.offset_encoding();
|
||||||
let pos = doc.position(view.id, offset_encoding);
|
let pos = doc.position(view.id, offset_encoding);
|
||||||
let future =
|
let future =
|
||||||
language_server.text_document_document_highlight(doc.identifier(), pos, None)?;
|
language_server.text_document_document_highlight(doc.identifier(), pos, None)?;
|
||||||
Some((future, offset_encoding))
|
Some((future, offset_encoding))
|
||||||
}) {
|
});
|
||||||
Some(future) => future,
|
let (future, offset_encoding) = match future_offset_encoding {
|
||||||
|
Some(future_offset_encoding) => future_offset_encoding,
|
||||||
None => {
|
None => {
|
||||||
cx.editor
|
cx.editor
|
||||||
.set_error("No language server supports document-highlight");
|
.set_error("No language server supports document-highlight");
|
||||||
|
@ -1587,8 +1582,8 @@ fn compute_inlay_hints_for_view(
|
||||||
let view_id = view.id;
|
let view_id = view.id;
|
||||||
let doc_id = view.doc;
|
let doc_id = view.doc;
|
||||||
|
|
||||||
let language_servers = doc.language_servers_with_feature(LanguageServerFeature::InlayHints);
|
let mut language_servers = doc.language_servers_with_feature(LanguageServerFeature::InlayHints);
|
||||||
let language_server = language_servers.iter().find(|language_server| {
|
let language_server = language_servers.find(|language_server| {
|
||||||
matches!(
|
matches!(
|
||||||
language_server.capabilities().inlay_hint_provider,
|
language_server.capabilities().inlay_hint_provider,
|
||||||
Some(
|
Some(
|
||||||
|
|
|
@ -1330,14 +1330,16 @@ fn lsp_workspace_command(
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
let doc = doc!(cx.editor);
|
let doc = doc!(cx.editor);
|
||||||
let language_servers =
|
let id_options = doc
|
||||||
doc.language_servers_with_feature(LanguageServerFeature::WorkspaceCommand);
|
.language_servers_with_feature(LanguageServerFeature::WorkspaceCommand)
|
||||||
let (language_server_id, options) = match language_servers.iter().find_map(|ls| {
|
.find_map(|ls| {
|
||||||
ls.capabilities()
|
ls.capabilities()
|
||||||
.execute_command_provider
|
.execute_command_provider
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|options| (ls.id(), options))
|
.map(|options| (ls.id(), options))
|
||||||
}) {
|
});
|
||||||
|
|
||||||
|
let (language_server_id, options) = match id_options {
|
||||||
Some(id_options) => id_options,
|
Some(id_options) => id_options,
|
||||||
None => {
|
None => {
|
||||||
cx.editor.set_status(
|
cx.editor.set_status(
|
||||||
|
@ -1346,6 +1348,7 @@ fn lsp_workspace_command(
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if args.is_empty() {
|
if args.is_empty() {
|
||||||
let commands = options
|
let commands = options
|
||||||
.commands
|
.commands
|
||||||
|
@ -1445,7 +1448,6 @@ fn lsp_stop(
|
||||||
// I'm not sure if this is really what we want
|
// I'm not sure if this is really what we want
|
||||||
let ls_shutdown_names = doc
|
let ls_shutdown_names = doc
|
||||||
.language_servers()
|
.language_servers()
|
||||||
.iter()
|
|
||||||
.map(|ls| ls.name())
|
.map(|ls| ls.name())
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
@ -1459,7 +1461,6 @@ fn lsp_stop(
|
||||||
.filter_map(|doc| {
|
.filter_map(|doc| {
|
||||||
let doc_active_ls_ids: Vec<_> = doc
|
let doc_active_ls_ids: Vec<_> = doc
|
||||||
.language_servers()
|
.language_servers()
|
||||||
.iter()
|
|
||||||
.filter(|ls| !ls_shutdown_names.contains(&ls.name()))
|
.filter(|ls| !ls_shutdown_names.contains(&ls.name()))
|
||||||
.map(|ls| ls.id())
|
.map(|ls| ls.id())
|
||||||
.collect();
|
.collect();
|
||||||
|
@ -1472,7 +1473,7 @@ fn lsp_stop(
|
||||||
.map(Clone::clone)
|
.map(Clone::clone)
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
if active_clients.len() != doc.language_servers().len() {
|
if active_clients.len() != doc.language_servers().count() {
|
||||||
Some((doc.id(), active_clients))
|
Some((doc.id(), active_clients))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
@ -1485,7 +1486,6 @@ fn lsp_stop(
|
||||||
|
|
||||||
let stopped_clients: Vec<_> = doc
|
let stopped_clients: Vec<_> = doc
|
||||||
.language_servers()
|
.language_servers()
|
||||||
.iter()
|
|
||||||
.filter(|ls| {
|
.filter(|ls| {
|
||||||
!active_clients
|
!active_clients
|
||||||
.iter()
|
.iter()
|
||||||
|
|
|
@ -194,10 +194,10 @@ pub fn languages_all() -> std::io::Result<()> {
|
||||||
|
|
||||||
// TODO multiple language servers (check binary for each supported language server, not just the first)
|
// TODO multiple language servers (check binary for each supported language server, not just the first)
|
||||||
|
|
||||||
let lsp = lang.language_servers.first().and_then(|lsp| {
|
let lsp = lang.language_servers.keys().next().and_then(|ls_name| {
|
||||||
syn_loader_conf
|
syn_loader_conf
|
||||||
.language_server
|
.language_server
|
||||||
.get(lsp.name())
|
.get(ls_name)
|
||||||
.map(|config| config.command.clone())
|
.map(|config| config.command.clone())
|
||||||
});
|
});
|
||||||
check_binary(lsp);
|
check_binary(lsp);
|
||||||
|
@ -271,10 +271,10 @@ pub fn language(lang_str: String) -> std::io::Result<()> {
|
||||||
// TODO multiple language servers
|
// TODO multiple language servers
|
||||||
probe_protocol(
|
probe_protocol(
|
||||||
"language server",
|
"language server",
|
||||||
lang.language_servers.first().and_then(|lsp| {
|
lang.language_servers.keys().next().and_then(|ls_name| {
|
||||||
syn_loader_conf
|
syn_loader_conf
|
||||||
.language_server
|
.language_server
|
||||||
.get(lsp.name())
|
.get(ls_name)
|
||||||
.map(|config| config.command.clone())
|
.map(|config| config.command.clone())
|
||||||
}),
|
}),
|
||||||
)?;
|
)?;
|
||||||
|
|
|
@ -394,13 +394,11 @@ pub mod completers {
|
||||||
pub fn lsp_workspace_command(editor: &Editor, input: &str) -> Vec<Completion> {
|
pub fn lsp_workspace_command(editor: &Editor, input: &str) -> Vec<Completion> {
|
||||||
let matcher = Matcher::default();
|
let matcher = Matcher::default();
|
||||||
|
|
||||||
let language_servers =
|
let options = match doc!(editor)
|
||||||
doc!(editor).language_servers_with_feature(LanguageServerFeature::WorkspaceCommand);
|
.language_servers_with_feature(LanguageServerFeature::WorkspaceCommand)
|
||||||
let options = match language_servers
|
|
||||||
.into_iter()
|
|
||||||
.find_map(|ls| ls.capabilities().execute_command_provider.as_ref())
|
.find_map(|ls| ls.capabilities().execute_command_provider.as_ref())
|
||||||
{
|
{
|
||||||
Some(id_options) => id_options,
|
Some(options) => options,
|
||||||
None => {
|
None => {
|
||||||
return vec![];
|
return vec![];
|
||||||
}
|
}
|
||||||
|
|
|
@ -202,11 +202,10 @@ fn render_lsp_spinner<F>(context: &mut RenderContext, write: F)
|
||||||
where
|
where
|
||||||
F: Fn(&mut RenderContext, String, Option<Style>) + Copy,
|
F: Fn(&mut RenderContext, String, Option<Style>) + Copy,
|
||||||
{
|
{
|
||||||
let language_servers = context.doc.language_servers();
|
let language_server = context.doc.language_servers().next();
|
||||||
write(
|
write(
|
||||||
context,
|
context,
|
||||||
language_servers
|
language_server
|
||||||
.first()
|
|
||||||
.and_then(|srv| {
|
.and_then(|srv| {
|
||||||
context
|
context
|
||||||
.spinners
|
.spinners
|
||||||
|
|
|
@ -6,7 +6,7 @@ use futures_util::FutureExt;
|
||||||
use helix_core::auto_pairs::AutoPairs;
|
use helix_core::auto_pairs::AutoPairs;
|
||||||
use helix_core::doc_formatter::TextFormat;
|
use helix_core::doc_formatter::TextFormat;
|
||||||
use helix_core::encoding::Encoding;
|
use helix_core::encoding::Encoding;
|
||||||
use helix_core::syntax::{Highlight, LanguageServerFeature, LanguageServerFeatureConfiguration};
|
use helix_core::syntax::{Highlight, LanguageServerFeature};
|
||||||
use helix_core::text_annotations::{InlineAnnotation, TextAnnotations};
|
use helix_core::text_annotations::{InlineAnnotation, TextAnnotations};
|
||||||
use helix_core::Range;
|
use helix_core::Range;
|
||||||
use helix_vcs::{DiffHandle, DiffProviderRegistry};
|
use helix_vcs::{DiffHandle, DiffProviderRegistry};
|
||||||
|
@ -734,7 +734,6 @@ impl Document {
|
||||||
// finds first language server that supports formatting and then formats
|
// finds first language server that supports formatting and then formats
|
||||||
let (offset_encoding, request) = self
|
let (offset_encoding, request) = self
|
||||||
.language_servers_with_feature(LanguageServerFeature::Format)
|
.language_servers_with_feature(LanguageServerFeature::Format)
|
||||||
.iter()
|
|
||||||
.find_map(|language_server| {
|
.find_map(|language_server| {
|
||||||
let offset_encoding = language_server.offset_encoding();
|
let offset_encoding = language_server.offset_encoding();
|
||||||
let request = language_server.text_document_formatting(
|
let request = language_server.text_document_formatting(
|
||||||
|
@ -1437,54 +1436,24 @@ impl Document {
|
||||||
self.version
|
self.version
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Language servers that have been initialized.
|
pub fn language_servers(&self) -> impl Iterator<Item = &helix_lsp::Client> {
|
||||||
pub fn language_servers(&self) -> Vec<&helix_lsp::Client> {
|
|
||||||
self.language_servers
|
self.language_servers
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|l| if l.is_initialized() { Some(&**l) } else { None })
|
.filter_map(|l| if l.is_initialized() { Some(&**l) } else { None })
|
||||||
.collect()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO filter also based on LSP capabilities?
|
// TODO filter also based on LSP capabilities?
|
||||||
pub fn language_servers_with_feature(
|
pub fn language_servers_with_feature(
|
||||||
&self,
|
&self,
|
||||||
feature: LanguageServerFeature,
|
feature: LanguageServerFeature,
|
||||||
) -> Vec<&helix_lsp::Client> {
|
) -> impl Iterator<Item = &helix_lsp::Client> {
|
||||||
let language_servers = self.language_servers();
|
self.language_servers().filter(move |server| {
|
||||||
|
self.language_config()
|
||||||
let language_config = match self.language_config() {
|
.and_then(|config| config.language_servers.get(server.name()))
|
||||||
Some(language_config) => language_config,
|
.map_or(false, |server_features| {
|
||||||
None => return Vec::new(),
|
server_features.has_feature(feature)
|
||||||
};
|
})
|
||||||
|
})
|
||||||
// O(n^2) but since language_servers will be of very small length,
|
|
||||||
// I don't see the necessity to optimize
|
|
||||||
language_config
|
|
||||||
.language_servers
|
|
||||||
.iter()
|
|
||||||
.filter_map(|c| match c {
|
|
||||||
LanguageServerFeatureConfiguration::Simple(name) => language_servers
|
|
||||||
.iter()
|
|
||||||
.find(|ls| ls.name() == name)
|
|
||||||
.copied(),
|
|
||||||
LanguageServerFeatureConfiguration::Features {
|
|
||||||
only_features,
|
|
||||||
except_features,
|
|
||||||
name,
|
|
||||||
} => {
|
|
||||||
if (only_features.is_empty() || only_features.contains(&feature))
|
|
||||||
&& !except_features.contains(&feature)
|
|
||||||
{
|
|
||||||
language_servers
|
|
||||||
.iter()
|
|
||||||
.find(|ls| ls.name() == name)
|
|
||||||
.copied()
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn diff_handle(&self) -> Option<&DiffHandle> {
|
pub fn diff_handle(&self) -> Option<&DiffHandle> {
|
||||||
|
@ -1610,7 +1579,6 @@ impl Document {
|
||||||
pub fn shown_diagnostics(&self) -> impl Iterator<Item = &Diagnostic> + DoubleEndedIterator {
|
pub fn shown_diagnostics(&self) -> impl Iterator<Item = &Diagnostic> + DoubleEndedIterator {
|
||||||
let ls_ids: HashSet<_> = self
|
let ls_ids: HashSet<_> = self
|
||||||
.language_servers_with_feature(LanguageServerFeature::Diagnostics)
|
.language_servers_with_feature(LanguageServerFeature::Diagnostics)
|
||||||
.iter()
|
|
||||||
.map(|ls| ls.id())
|
.map(|ls| ls.id())
|
||||||
.collect();
|
.collect();
|
||||||
self.diagnostics
|
self.diagnostics
|
||||||
|
|
|
@ -689,7 +689,7 @@ pub struct WhitespaceCharacters {
|
||||||
impl Default for WhitespaceCharacters {
|
impl Default for WhitespaceCharacters {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
space: '·', // U+00B7
|
space: '·', // U+00B7
|
||||||
nbsp: '⍽', // U+237D
|
nbsp: '⍽', // U+237D
|
||||||
tab: '→', // U+2192
|
tab: '→', // U+2192
|
||||||
newline: '⏎', // U+23CE
|
newline: '⏎', // U+23CE
|
||||||
|
@ -1129,7 +1129,8 @@ impl Editor {
|
||||||
|
|
||||||
if let Some(language_servers) = language_servers {
|
if let Some(language_servers) = language_servers {
|
||||||
// only spawn new lang servers if the servers aren't the same
|
// only spawn new lang servers if the servers aren't the same
|
||||||
let doc_language_servers = doc.language_servers();
|
// TODO simplify?
|
||||||
|
let doc_language_servers = doc.language_servers().collect::<Vec<_>>();
|
||||||
let spawn_new_servers = language_servers.len() != doc_language_servers.len()
|
let spawn_new_servers = language_servers.len() != doc_language_servers.len()
|
||||||
|| language_servers
|
|| language_servers
|
||||||
.iter()
|
.iter()
|
||||||
|
|
Loading…
Reference in New Issue