mirror of
https://github.com/helix-editor/helix.git
synced 2024-11-22 09:26:19 +04:00
Refactor doc language servers to a HashMap, and the config to use a Vec to retain order
This commit is contained in:
parent
b1199c552b
commit
2eeac10755
@ -112,11 +112,11 @@ pub struct LanguageConfiguration {
|
|||||||
// 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(
|
#[serde(
|
||||||
default,
|
default,
|
||||||
skip_serializing_if = "HashMap::is_empty",
|
skip_serializing_if = "Vec::is_empty",
|
||||||
serialize_with = "serialize_lang_features",
|
serialize_with = "serialize_lang_features",
|
||||||
deserialize_with = "deserialize_lang_features"
|
deserialize_with = "deserialize_lang_features"
|
||||||
)]
|
)]
|
||||||
pub language_servers: HashMap<String, LanguageServerFeatures>,
|
pub language_servers: Vec<LanguageServerFeatures>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub indent: Option<IndentationConfiguration>,
|
pub indent: Option<IndentationConfiguration>,
|
||||||
|
|
||||||
@ -282,19 +282,20 @@ enum LanguageServerFeatureConfiguration {
|
|||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct LanguageServerFeatures {
|
pub struct LanguageServerFeatures {
|
||||||
|
pub name: String,
|
||||||
pub only: HashSet<LanguageServerFeature>,
|
pub only: HashSet<LanguageServerFeature>,
|
||||||
pub excluded: HashSet<LanguageServerFeature>,
|
pub excluded: HashSet<LanguageServerFeature>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LanguageServerFeatures {
|
impl LanguageServerFeatures {
|
||||||
pub fn has_feature(&self, feature: LanguageServerFeature) -> bool {
|
pub fn has_feature(&self, feature: LanguageServerFeature) -> bool {
|
||||||
self.only.is_empty() || self.only.contains(&feature) && !self.excluded.contains(&feature)
|
(self.only.is_empty() || self.only.contains(&feature)) && !self.excluded.contains(&feature)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn deserialize_lang_features<'de, D>(
|
fn deserialize_lang_features<'de, D>(
|
||||||
deserializer: D,
|
deserializer: D,
|
||||||
) -> Result<HashMap<String, LanguageServerFeatures>, D::Error>
|
) -> Result<Vec<LanguageServerFeatures>, D::Error>
|
||||||
where
|
where
|
||||||
D: serde::Deserializer<'de>,
|
D: serde::Deserializer<'de>,
|
||||||
{
|
{
|
||||||
@ -302,40 +303,39 @@ fn deserialize_lang_features<'de, D>(
|
|||||||
let res = raw
|
let res = raw
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|config| match config {
|
.map(|config| match config {
|
||||||
LanguageServerFeatureConfiguration::Simple(name) => {
|
LanguageServerFeatureConfiguration::Simple(name) => LanguageServerFeatures {
|
||||||
(name, LanguageServerFeatures::default())
|
name,
|
||||||
}
|
..Default::default()
|
||||||
|
},
|
||||||
LanguageServerFeatureConfiguration::Features {
|
LanguageServerFeatureConfiguration::Features {
|
||||||
only_features,
|
only_features,
|
||||||
except_features,
|
except_features,
|
||||||
name,
|
name,
|
||||||
} => (
|
} => LanguageServerFeatures {
|
||||||
name,
|
name,
|
||||||
LanguageServerFeatures {
|
only: only_features,
|
||||||
only: only_features,
|
excluded: except_features,
|
||||||
excluded: except_features,
|
},
|
||||||
},
|
|
||||||
),
|
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
fn serialize_lang_features<S>(
|
fn serialize_lang_features<S>(
|
||||||
map: &HashMap<String, LanguageServerFeatures>,
|
map: &Vec<LanguageServerFeatures>,
|
||||||
serializer: S,
|
serializer: S,
|
||||||
) -> Result<S::Ok, S::Error>
|
) -> Result<S::Ok, S::Error>
|
||||||
where
|
where
|
||||||
S: serde::Serializer,
|
S: serde::Serializer,
|
||||||
{
|
{
|
||||||
let mut serializer = serializer.serialize_seq(Some(map.len()))?;
|
let mut serializer = serializer.serialize_seq(Some(map.len()))?;
|
||||||
for (name, features) in map {
|
for features in map {
|
||||||
let features = if features.only.is_empty() && features.excluded.is_empty() {
|
let features = if features.only.is_empty() && features.excluded.is_empty() {
|
||||||
LanguageServerFeatureConfiguration::Simple(name.to_owned())
|
LanguageServerFeatureConfiguration::Simple(features.name.to_owned())
|
||||||
} else {
|
} else {
|
||||||
LanguageServerFeatureConfiguration::Features {
|
LanguageServerFeatureConfiguration::Features {
|
||||||
only_features: features.only.clone(),
|
only_features: features.only.clone(),
|
||||||
except_features: features.excluded.clone(),
|
except_features: features.excluded.clone(),
|
||||||
name: name.to_owned(),
|
name: features.name.to_owned(),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
serializer.serialize_element(&features)?;
|
serializer.serialize_element(&features)?;
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
use futures_util::stream::select_all::SelectAll;
|
use futures_util::stream::select_all::SelectAll;
|
||||||
use helix_core::{
|
use helix_core::{
|
||||||
path,
|
path,
|
||||||
syntax::{LanguageConfiguration, LanguageServerConfiguration},
|
syntax::{LanguageConfiguration, LanguageServerConfiguration, LanguageServerFeatures},
|
||||||
};
|
};
|
||||||
use tokio::sync::mpsc::UnboundedReceiver;
|
use tokio::sync::mpsc::UnboundedReceiver;
|
||||||
|
|
||||||
@ -26,7 +26,7 @@
|
|||||||
use tokio_stream::wrappers::UnboundedReceiverStream;
|
use tokio_stream::wrappers::UnboundedReceiverStream;
|
||||||
|
|
||||||
pub type Result<T> = core::result::Result<T, Error>;
|
pub type Result<T> = core::result::Result<T, Error>;
|
||||||
type LanguageServerName = String;
|
pub type LanguageServerName = String;
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
@ -689,9 +689,9 @@ pub fn restart(
|
|||||||
) -> Result<Vec<Arc<Client>>> {
|
) -> Result<Vec<Arc<Client>>> {
|
||||||
language_config
|
language_config
|
||||||
.language_servers
|
.language_servers
|
||||||
.keys()
|
.iter()
|
||||||
.filter_map(|name| {
|
.filter_map(|LanguageServerFeatures { name, .. }| {
|
||||||
#[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(),
|
||||||
@ -740,17 +740,20 @@ pub fn get(
|
|||||||
doc_path: Option<&std::path::PathBuf>,
|
doc_path: Option<&std::path::PathBuf>,
|
||||||
root_dirs: &[PathBuf],
|
root_dirs: &[PathBuf],
|
||||||
enable_snippets: bool,
|
enable_snippets: bool,
|
||||||
) -> Result<Vec<Arc<Client>>> {
|
) -> Result<HashMap<LanguageServerName, Arc<Client>>> {
|
||||||
language_config
|
language_config
|
||||||
.language_servers
|
.language_servers
|
||||||
.keys()
|
.iter()
|
||||||
.map(|name| {
|
.map(|LanguageServerFeatures { name, .. }| {
|
||||||
if let Some(clients) = self.inner.get_mut(name) {
|
if let Some(clients) = self.inner.get_mut(name) {
|
||||||
|
// clients.find(
|
||||||
|
|
||||||
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)
|
||||||
}) {
|
}) {
|
||||||
return Ok(client.clone());
|
return Ok((name.to_owned(), client.clone()));
|
||||||
}
|
}
|
||||||
|
// return Ok((name.clone(), clients.clone()));
|
||||||
}
|
}
|
||||||
let client = self.start_client(
|
let client = self.start_client(
|
||||||
name.clone(),
|
name.clone(),
|
||||||
@ -761,7 +764,7 @@ pub fn get(
|
|||||||
)?;
|
)?;
|
||||||
let clients = self.inner.entry(name.clone()).or_default();
|
let clients = self.inner.entry(name.clone()).or_default();
|
||||||
clients.push(client.clone());
|
clients.push(client.clone());
|
||||||
Ok(client)
|
Ok((name.clone(), client))
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
@ -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.keys().next().and_then(|ls_name| {
|
let lsp = lang.language_servers.first().and_then(|ls| {
|
||||||
syn_loader_conf
|
syn_loader_conf
|
||||||
.language_server
|
.language_server
|
||||||
.get(ls_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.keys().next().and_then(|ls_name| {
|
lang.language_servers.first().and_then(|ls| {
|
||||||
syn_loader_conf
|
syn_loader_conf
|
||||||
.language_server
|
.language_server
|
||||||
.get(ls_name)
|
.get(&ls.name)
|
||||||
.map(|config| config.command.clone())
|
.map(|config| config.command.clone())
|
||||||
}),
|
}),
|
||||||
)?;
|
)?;
|
||||||
|
@ -180,7 +180,7 @@ pub struct Document {
|
|||||||
pub(crate) modified_since_accessed: bool,
|
pub(crate) modified_since_accessed: bool,
|
||||||
|
|
||||||
diagnostics: Vec<Diagnostic>,
|
diagnostics: Vec<Diagnostic>,
|
||||||
language_servers: Vec<Arc<helix_lsp::Client>>,
|
pub(crate) language_servers: HashMap<LanguageServerName, Arc<Client>>,
|
||||||
|
|
||||||
diff_handle: Option<DiffHandle>,
|
diff_handle: Option<DiffHandle>,
|
||||||
version_control_head: Option<Arc<ArcSwap<Box<str>>>>,
|
version_control_head: Option<Arc<ArcSwap<Box<str>>>>,
|
||||||
@ -580,7 +580,7 @@ fn take_with<T, F>(mut_ref: &mut T, f: F)
|
|||||||
*mut_ref = f(mem::take(mut_ref));
|
*mut_ref = f(mem::take(mut_ref));
|
||||||
}
|
}
|
||||||
|
|
||||||
use helix_lsp::{lsp, Client, OffsetEncoding};
|
use helix_lsp::{lsp, Client, LanguageServerName, OffsetEncoding};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
impl Document {
|
impl Document {
|
||||||
@ -616,7 +616,7 @@ pub fn from(
|
|||||||
last_saved_time: SystemTime::now(),
|
last_saved_time: SystemTime::now(),
|
||||||
last_saved_revision: 0,
|
last_saved_revision: 0,
|
||||||
modified_since_accessed: false,
|
modified_since_accessed: false,
|
||||||
language_servers: Vec::new(),
|
language_servers: HashMap::new(),
|
||||||
diff_handle: None,
|
diff_handle: None,
|
||||||
config,
|
config,
|
||||||
version_control_head: None,
|
version_control_head: None,
|
||||||
@ -850,7 +850,7 @@ impl Future<Output = Result<DocumentSavedEvent, anyhow::Error>> + 'static + Send
|
|||||||
text: text.clone(),
|
text: text.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
for language_server in language_servers {
|
for (_, language_server) in language_servers {
|
||||||
if !language_server.is_initialized() {
|
if !language_server.is_initialized() {
|
||||||
return Ok(event);
|
return Ok(event);
|
||||||
}
|
}
|
||||||
@ -1006,11 +1006,6 @@ pub fn set_language_by_language_id(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the LSP.
|
|
||||||
pub fn set_language_servers(&mut self, language_servers: Vec<Arc<helix_lsp::Client>>) {
|
|
||||||
self.language_servers = language_servers;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Select text within the [`Document`].
|
/// Select text within the [`Document`].
|
||||||
pub fn set_selection(&mut self, view_id: ViewId, selection: Selection) {
|
pub fn set_selection(&mut self, view_id: ViewId, selection: Selection) {
|
||||||
// TODO: use a transaction?
|
// TODO: use a transaction?
|
||||||
@ -1437,16 +1432,17 @@ pub fn version(&self) -> i32 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn language_servers(&self) -> impl Iterator<Item = &helix_lsp::Client> {
|
pub fn language_servers(&self) -> impl Iterator<Item = &helix_lsp::Client> {
|
||||||
self.language_servers
|
self.language_servers.values().filter_map(|l| {
|
||||||
.iter()
|
if l.is_initialized() {
|
||||||
.filter_map(|l| if l.is_initialized() { Some(&**l) } else { None })
|
Some(&**l)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn remove_language_server_by_name(&mut self, name: &str) -> Option<Arc<Client>> {
|
pub fn remove_language_server_by_name(&mut self, name: &str) -> Option<Arc<Client>> {
|
||||||
match self.language_servers.iter().position(|l| l.name() == name) {
|
self.language_servers.remove(name)
|
||||||
Some(index) => Some(self.language_servers.remove(index)),
|
|
||||||
None => None,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO filter also based on LSP capabilities?
|
// TODO filter also based on LSP capabilities?
|
||||||
@ -1454,12 +1450,15 @@ pub fn language_servers_with_feature(
|
|||||||
&self,
|
&self,
|
||||||
feature: LanguageServerFeature,
|
feature: LanguageServerFeature,
|
||||||
) -> impl Iterator<Item = &helix_lsp::Client> {
|
) -> impl Iterator<Item = &helix_lsp::Client> {
|
||||||
self.language_servers().filter(move |server| {
|
self.language_config().into_iter().flat_map(move |config| {
|
||||||
self.language_config()
|
config.language_servers.iter().filter_map(move |features| {
|
||||||
.and_then(|config| config.language_servers.get(server.name()))
|
let ls = &**self.language_servers.get(&features.name)?;
|
||||||
.map_or(false, |server_features| {
|
if ls.is_initialized() && features.has_feature(feature) {
|
||||||
server_features.has_feature(feature)
|
Some(ls)
|
||||||
})
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1610,7 +1609,10 @@ pub fn shown_diagnostics(&self) -> impl Iterator<Item = &Diagnostic> + DoubleEnd
|
|||||||
.find(|ls| ls.id() == d.language_server_id)
|
.find(|ls| ls.id() == d.language_server_id)
|
||||||
.and_then(|ls| {
|
.and_then(|ls| {
|
||||||
let config = self.language_config()?;
|
let config = self.language_config()?;
|
||||||
let features = config.language_servers.get(ls.name())?;
|
let features = config
|
||||||
|
.language_servers
|
||||||
|
.iter()
|
||||||
|
.find(|features| features.name == ls.name())?;
|
||||||
Some(features.has_feature(LanguageServerFeature::Diagnostics))
|
Some(features.has_feature(LanguageServerFeature::Diagnostics))
|
||||||
})
|
})
|
||||||
== Some(true)
|
== Some(true)
|
||||||
|
@ -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
|
||||||
@ -1103,9 +1103,9 @@ fn launch_language_servers(&mut self, doc_id: DocumentId) -> Option<()> {
|
|||||||
if !self.config().lsp.enable {
|
if !self.config().lsp.enable {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
// if doc doesn't have a URL it's a scratch buffer, ignore it
|
// if doc doesn't have a URL it's a scratch buffer, ignore it
|
||||||
let doc = self.document(doc_id)?;
|
let doc = self.documents.get_mut(&doc_id)?;
|
||||||
|
let doc_url = doc.url()?;
|
||||||
let (lang, path) = (doc.language.clone(), doc.path().cloned());
|
let (lang, path) = (doc.language.clone(), doc.path().cloned());
|
||||||
let config = doc.config.load();
|
let config = doc.config.load();
|
||||||
let root_dirs = &config.workspace_lsp_roots;
|
let root_dirs = &config.workspace_lsp_roots;
|
||||||
@ -1124,37 +1124,37 @@ fn launch_language_servers(&mut self, doc_id: DocumentId) -> Option<()> {
|
|||||||
.ok()
|
.ok()
|
||||||
});
|
});
|
||||||
|
|
||||||
let doc = self.document_mut(doc_id)?;
|
|
||||||
let doc_url = doc.url()?;
|
|
||||||
|
|
||||||
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
|
let language_id = doc.language_id().map(ToOwned::to_owned).unwrap_or_default();
|
||||||
// TODO simplify?
|
|
||||||
let doc_language_servers = doc.language_servers().collect::<Vec<_>>();
|
|
||||||
let spawn_new_servers = language_servers.len() != doc_language_servers.len()
|
|
||||||
|| language_servers
|
|
||||||
.iter()
|
|
||||||
.zip(doc_language_servers.iter())
|
|
||||||
.any(|(l, dl)| l.id() != dl.id());
|
|
||||||
if spawn_new_servers {
|
|
||||||
for doc_language_server in doc_language_servers {
|
|
||||||
tokio::spawn(doc_language_server.text_document_did_close(doc.identifier()));
|
|
||||||
}
|
|
||||||
|
|
||||||
let language_id = doc.language_id().map(ToOwned::to_owned).unwrap_or_default();
|
// only spawn new language servers if the servers aren't the same
|
||||||
|
|
||||||
for language_server in &language_servers {
|
let doc_language_servers_not_in_registry =
|
||||||
// TODO: this now races with on_init code if the init happens too quickly
|
doc.language_servers.iter().filter(|(name, doc_ls)| {
|
||||||
tokio::spawn(language_server.text_document_did_open(
|
!language_servers.contains_key(*name)
|
||||||
doc_url.clone(),
|
|| language_servers[*name].id() != doc_ls.id()
|
||||||
doc.version(),
|
});
|
||||||
doc.text(),
|
|
||||||
language_id.clone(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
doc.set_language_servers(language_servers);
|
for (_, language_server) in doc_language_servers_not_in_registry {
|
||||||
|
tokio::spawn(language_server.text_document_did_close(doc.identifier()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let language_servers_not_in_doc = language_servers.iter().filter(|(name, ls)| {
|
||||||
|
!doc.language_servers.contains_key(*name)
|
||||||
|
|| doc.language_servers[*name].id() != ls.id()
|
||||||
|
});
|
||||||
|
|
||||||
|
for (_, language_server) in language_servers_not_in_doc {
|
||||||
|
// TODO: this now races with on_init code if the init happens too quickly
|
||||||
|
tokio::spawn(language_server.text_document_did_open(
|
||||||
|
doc_url.clone(),
|
||||||
|
doc.version(),
|
||||||
|
doc.text(),
|
||||||
|
language_id.clone(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
doc.language_servers = language_servers;
|
||||||
}
|
}
|
||||||
Some(())
|
Some(())
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user