aboutsummaryrefslogtreecommitdiff
path: root/helix-view
diff options
context:
space:
mode:
Diffstat (limited to 'helix-view')
-rw-r--r--helix-view/src/document.rs145
-rw-r--r--helix-view/src/editor.rs64
-rw-r--r--helix-view/src/gutter.rs2
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| {