diff options
Diffstat (limited to 'helix-view')
-rw-r--r-- | helix-view/src/document.rs | 145 | ||||
-rw-r--r-- | helix-view/src/editor.rs | 64 | ||||
-rw-r--r-- | helix-view/src/gutter.rs | 2 |
3 files changed, 141 insertions, 70 deletions
diff --git a/helix-view/src/document.rs b/helix-view/src/document.rs index eb376567..734d76d1 100644 --- a/helix-view/src/document.rs +++ b/helix-view/src/document.rs @@ -6,7 +6,7 @@ use futures_util::FutureExt; use helix_core::auto_pairs::AutoPairs; use helix_core::doc_formatter::TextFormat; use helix_core::encoding::Encoding; -use helix_core::syntax::Highlight; +use helix_core::syntax::{Highlight, LanguageServerFeature, LanguageServerFeatureConfiguration}; use helix_core::text_annotations::{InlineAnnotation, TextAnnotations}; use helix_core::Range; use helix_vcs::{DiffHandle, DiffProviderRegistry}; @@ -16,7 +16,7 @@ use serde::de::{self, Deserialize, Deserializer}; use serde::Serialize; use std::borrow::Cow; use std::cell::Cell; -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use std::fmt::Display; use std::future::Future; use std::path::{Path, PathBuf}; @@ -180,7 +180,7 @@ pub struct Document { pub(crate) modified_since_accessed: bool, diagnostics: Vec<Diagnostic>, - language_server: Option<Arc<helix_lsp::Client>>, + language_servers: Vec<Arc<helix_lsp::Client>>, diff_handle: Option<DiffHandle>, version_control_head: Option<Arc<ArcSwap<Box<str>>>>, @@ -616,7 +616,7 @@ impl Document { last_saved_time: SystemTime::now(), last_saved_revision: 0, modified_since_accessed: false, - language_server: None, + language_servers: Vec::new(), diff_handle: None, config, version_control_head: None, @@ -730,19 +730,24 @@ impl Document { return Some(formatting_future.boxed()); }; - let language_server = self.language_server()?; let text = self.text.clone(); - let offset_encoding = language_server.offset_encoding(); - - let request = language_server.text_document_formatting( - self.identifier(), - lsp::FormattingOptions { - tab_size: self.tab_width() as u32, - insert_spaces: matches!(self.indent_style, IndentStyle::Spaces(_)), - ..Default::default() - }, - None, - )?; + // finds first language server that supports formatting and then formats + let (offset_encoding, request) = self + .language_servers_with_feature(LanguageServerFeature::Format) + .iter() + .find_map(|language_server| { + let offset_encoding = language_server.offset_encoding(); + let request = language_server.text_document_formatting( + self.identifier(), + lsp::FormattingOptions { + tab_size: self.tab_width() as u32, + insert_spaces: matches!(self.indent_style, IndentStyle::Spaces(_)), + ..Default::default() + }, + None, + )?; + Some((offset_encoding, request)) + })?; let fut = async move { let edits = request.await.unwrap_or_else(|e| { @@ -797,13 +802,12 @@ impl Document { if self.path.is_none() { bail!("Can't save with no path set!"); } - self.path.as_ref().unwrap().clone() } }; let identifier = self.path().map(|_| self.identifier()); - let language_server = self.language_server.clone(); + let language_servers = self.language_servers.clone(); // mark changes up to now as saved let current_rev = self.get_current_revision(); @@ -847,14 +851,13 @@ impl Document { text: text.clone(), }; - if let Some(language_server) = language_server { + for language_server in language_servers { if !language_server.is_initialized() { return Ok(event); } - - if let Some(identifier) = identifier { + if let Some(identifier) = &identifier { if let Some(notification) = - language_server.text_document_did_save(identifier, &text) + language_server.text_document_did_save(identifier.clone(), &text) { notification.await?; } @@ -1005,8 +1008,8 @@ impl Document { } /// Set the LSP. - pub fn set_language_server(&mut self, language_server: Option<Arc<helix_lsp::Client>>) { - self.language_server = language_server; + pub fn set_language_servers(&mut self, language_servers: Vec<Arc<helix_lsp::Client>>) { + self.language_servers = language_servers; } /// Select text within the [`Document`]. @@ -1159,7 +1162,7 @@ impl Document { if emit_lsp_notification { // emit lsp notification - if let Some(language_server) = self.language_server() { + for language_server in self.language_servers() { let notify = language_server.text_document_did_change( self.versioned_identifier(), &old_doc, @@ -1415,18 +1418,13 @@ impl Document { .map(|language| language.language_id.as_str()) } - /// Language ID for the document. Either the `language-id` from the - /// `language-server` configuration, or the document language if no - /// `language-id` has been specified. + /// Language ID for the document. Either the `language-id`, + /// or the document language name if no `language-id` has been specified. pub fn language_id(&self) -> Option<&str> { - let language_config = self.language.as_deref()?; - - language_config - .language_server - .as_ref()? - .language_id + self.language_config()? + .language_server_language_id .as_deref() - .or(Some(language_config.language_id.as_str())) + .or_else(|| self.language_name()) } /// Corresponding [`LanguageConfiguration`]. @@ -1439,10 +1437,54 @@ impl Document { self.version } - /// Language server if it has been initialized. - pub fn language_server(&self) -> Option<&helix_lsp::Client> { - let server = self.language_server.as_deref()?; - server.is_initialized().then_some(server) + /// Language servers that have been initialized. + pub fn language_servers(&self) -> Vec<&helix_lsp::Client> { + self.language_servers + .iter() + .filter_map(|l| if l.is_initialized() { Some(&**l) } else { None }) + .collect() + } + + // TODO filter also based on LSP capabilities? + pub fn language_servers_with_feature( + &self, + feature: LanguageServerFeature, + ) -> Vec<&helix_lsp::Client> { + let language_servers = self.language_servers(); + + let language_config = match self.language_config() { + Some(language_config) => language_config, + None => return Vec::new(), + }; + + // 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> { @@ -1565,12 +1607,33 @@ impl Document { &self.diagnostics } - pub fn set_diagnostics(&mut self, diagnostics: Vec<Diagnostic>) { - self.diagnostics = diagnostics; + pub fn shown_diagnostics(&self) -> impl Iterator<Item = &Diagnostic> { + let ls_ids: HashSet<_> = self + .language_servers_with_feature(LanguageServerFeature::Diagnostics) + .iter() + .map(|ls| ls.id()) + .collect(); + self.diagnostics + .iter() + .filter(move |d| ls_ids.contains(&d.language_server_id)) + } + + pub fn replace_diagnostics( + &mut self, + mut diagnostics: Vec<Diagnostic>, + language_server_id: usize, + ) { + self.clear_diagnostics(language_server_id); + self.diagnostics.append(&mut diagnostics); self.diagnostics .sort_unstable_by_key(|diagnostic| diagnostic.range); } + pub fn clear_diagnostics(&mut self, language_server_id: usize) { + self.diagnostics + .retain(|d| d.language_server_id != language_server_id); + } + /// Get the document's auto pairs. If the document has a recognized /// language config with auto pairs configured, returns that; /// otherwise, falls back to the global auto pairs config. If the global diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 9546d460..5ca9aceb 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -48,7 +48,7 @@ use helix_core::{ }; use helix_core::{Position, Selection}; use helix_dap as dap; -use helix_lsp::lsp; +use helix_lsp::{lsp, OffsetEncoding}; use serde::{ser::SerializeMap, Deserialize, Deserializer, Serialize, Serializer}; @@ -689,7 +689,7 @@ pub struct WhitespaceCharacters { impl Default for WhitespaceCharacters { fn default() -> Self { Self { - space: '·', // U+00B7 + space: '·', // U+00B7 nbsp: '⍽', // U+237D tab: '→', // U+2192 newline: '⏎', // U+23CE @@ -818,7 +818,7 @@ pub struct Editor { pub macro_recording: Option<(char, Vec<KeyEvent>)>, pub macro_replaying: Vec<char>, pub language_servers: helix_lsp::Registry, - pub diagnostics: BTreeMap<lsp::Url, Vec<lsp::Diagnostic>>, + pub diagnostics: BTreeMap<lsp::Url, Vec<(lsp::Diagnostic, usize, OffsetEncoding)>>, pub diff_providers: DiffProviderRegistry, pub debugger: Option<dap::Client>, @@ -941,6 +941,7 @@ impl Editor { syn_loader: Arc<syntax::Loader>, config: Arc<dyn DynAccess<Config>>, ) -> Self { + let language_servers = helix_lsp::Registry::new(syn_loader.clone()); let conf = config.load(); let auto_pairs = (&conf.auto_pairs).into(); @@ -960,7 +961,7 @@ impl Editor { macro_recording: None, macro_replaying: Vec::new(), theme: theme_loader.default(), - language_servers: helix_lsp::Registry::new(), + language_servers, diagnostics: BTreeMap::new(), diff_providers: DiffProviderRegistry::default(), debugger: None, @@ -1093,12 +1094,12 @@ impl Editor { } /// Refreshes the language server for a given document - pub fn refresh_language_server(&mut self, doc_id: DocumentId) -> Option<()> { - self.launch_language_server(doc_id) + pub fn refresh_language_servers(&mut self, doc_id: DocumentId) -> Option<()> { + self.launch_language_servers(doc_id) } /// Launch a language server for a given document - fn launch_language_server(&mut self, doc_id: DocumentId) -> Option<()> { + fn launch_language_servers(&mut self, doc_id: DocumentId) -> Option<()> { if !self.config().lsp.enable { return None; } @@ -1109,42 +1110,49 @@ impl Editor { let config = doc.config.load(); let root_dirs = &config.workspace_lsp_roots; - // try to find a language server based on the language name - let language_server = lang.as_ref().and_then(|language| { + // try to find language servers based on the language name + let language_servers = lang.as_ref().and_then(|language| { self.language_servers .get(language, path.as_ref(), root_dirs, config.lsp.snippets) .map_err(|e| { log::error!( - "Failed to initialize the LSP for `{}` {{ {} }}", + "Failed to initialize the language servers for `{}` {{ {} }}", language.scope(), e ) }) .ok() - .flatten() }); let doc = self.document_mut(doc_id)?; let doc_url = doc.url()?; - if let Some(language_server) = language_server { - // only spawn a new lang server if the servers aren't the same - if Some(language_server.id()) != doc.language_server().map(|server| server.id()) { - if let Some(language_server) = doc.language_server() { - tokio::spawn(language_server.text_document_did_close(doc.identifier())); + if let Some(language_servers) = language_servers { + // only spawn new lang servers if the servers aren't the same + let doc_language_servers = doc.language_servers(); + 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(); - // TODO: this now races with on_init code if the init happens too quickly - tokio::spawn(language_server.text_document_did_open( - doc_url, - doc.version(), - doc.text(), - language_id, - )); + for language_server in &language_servers { + // 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.set_language_server(Some(language_server)); + doc.set_language_servers(language_servers); } } Some(()) @@ -1337,10 +1345,10 @@ impl Editor { } doc.set_version_control_head(self.diff_providers.get_current_head_name(&path)); - let id = self.new_document(doc); - let _ = self.launch_language_server(id); + let doc_id = self.new_document(doc); + let _ = self.launch_language_servers(doc_id); - id + doc_id }; self.switch(id, action); @@ -1368,7 +1376,7 @@ impl Editor { // This will also disallow any follow-up writes self.saves.remove(&doc_id); - if let Some(language_server) = doc.language_server() { + for language_server in doc.language_servers() { // TODO: track error tokio::spawn(language_server.text_document_did_close(doc.identifier())); } diff --git a/helix-view/src/gutter.rs b/helix-view/src/gutter.rs index 3ecae919..78f879c9 100644 --- a/helix-view/src/gutter.rs +++ b/helix-view/src/gutter.rs @@ -55,7 +55,7 @@ pub fn diagnostic<'doc>( let error = theme.get("error"); let info = theme.get("info"); let hint = theme.get("hint"); - let diagnostics = doc.diagnostics(); + let diagnostics = doc.shown_diagnostics().collect::<Vec<_>>(); Box::new( move |line: usize, _selected: bool, first_visual_line: bool, out: &mut String| { |