Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'helix-view/src/document.rs')
-rw-r--r--helix-view/src/document.rs212
1 files changed, 69 insertions, 143 deletions
diff --git a/helix-view/src/document.rs b/helix-view/src/document.rs
index e52dbe0f..7dde7985 100644
--- a/helix-view/src/document.rs
+++ b/helix-view/src/document.rs
@@ -5,12 +5,11 @@ use futures_util::future::BoxFuture;
use futures_util::FutureExt;
use helix_core::auto_pairs::AutoPairs;
use helix_core::chars::char_is_word;
-use helix_core::command_line::Token;
use helix_core::diagnostic::DiagnosticProvider;
use helix_core::doc_formatter::TextFormat;
use helix_core::encoding::Encoding;
use helix_core::snippets::{ActiveSnippet, SnippetRenderCtx};
-use helix_core::syntax::config::LanguageServerFeature;
+use helix_core::syntax::{Highlight, LanguageServerFeature};
use helix_core::text_annotations::{InlineAnnotation, Overlay};
use helix_event::TaskController;
use helix_lsp::util::lsp_pos_to_pos;
@@ -39,14 +38,13 @@ use helix_core::{
history::{History, State, UndoKind},
indent::{auto_detect_indent_style, IndentStyle},
line_ending::auto_detect_line_ending,
- syntax::{self, config::LanguageConfiguration},
+ syntax::{self, LanguageConfiguration},
ChangeSet, Diagnostic, LineEnding, Range, Rope, RopeBuilder, Selection, Syntax, Transaction,
};
use crate::{
editor::Config,
events::{DocumentDidChange, SelectionDidChange},
- expansion,
view::ViewPosition,
DocumentId, Editor, Theme, View, ViewId,
};
@@ -150,9 +148,6 @@ pub struct Document {
/// To know if they're up-to-date, check the `id` field in `DocumentInlayHints`.
pub(crate) inlay_hints: HashMap<ViewId, DocumentInlayHints>,
pub(crate) jump_labels: HashMap<ViewId, Vec<Overlay>>,
- /// Set to `true` when the document is updated, reset to `false` on the next inlay hints
- /// update from the LSP
- pub inlay_hints_oudated: bool,
path: Option<PathBuf>,
relative_path: OnceCell<Option<PathBuf>>,
@@ -204,25 +199,19 @@ pub struct Document {
pub readonly: bool,
- pub previous_diagnostic_id: Option<String>,
-
/// Annotations for LSP document color swatches
pub color_swatches: Option<DocumentColorSwatches>,
// NOTE: ideally this would live on the handler for color swatches. This is blocked on a
// large refactor that would make `&mut Editor` available on the `DocumentDidChange` event.
pub color_swatch_controller: TaskController,
- pub pull_diagnostic_controller: TaskController,
-
- // NOTE: this field should eventually go away - we should use the Editor's syn_loader instead
- // of storing a copy on every doc. Then we can remove the surrounding `Arc` and use the
- // `ArcSwap` directly.
- syn_loader: Arc<ArcSwap<syntax::Loader>>,
+ // NOTE: ideally this would live on the handler for inlay hints, see the comment above.
+ pub inlay_hint_controllers: HashMap<ViewId, TaskController>,
}
#[derive(Debug, Clone, Default)]
pub struct DocumentColorSwatches {
pub color_swatches: Vec<InlineAnnotation>,
- pub colors: Vec<syntax::Highlight>,
+ pub colors: Vec<Highlight>,
pub color_swatches_padding: Vec<InlineAnnotation>,
}
@@ -300,7 +289,6 @@ impl fmt::Debug for Document {
.field("id", &self.id)
.field("text", &self.text)
.field("selections", &self.selections)
- .field("inlay_hints_oudated", &self.inlay_hints_oudated)
.field("text_annotations", &self.inlay_hints)
.field("view_data", &self.view_data)
.field("path", &self.path)
@@ -687,7 +675,6 @@ impl Document {
text: Rope,
encoding_with_bom_info: Option<(&'static Encoding, bool)>,
config: Arc<dyn DynAccess<Config>>,
- syn_loader: Arc<ArcSwap<syntax::Loader>>,
) -> Self {
let (encoding, has_bom) = encoding_with_bom_info.unwrap_or((encoding::UTF_8, false));
let line_ending = config.load().default_line_ending.into();
@@ -704,7 +691,7 @@ impl Document {
text,
selections: HashMap::default(),
inlay_hints: HashMap::default(),
- inlay_hints_oudated: false,
+ inlay_hint_controllers: HashMap::default(),
view_data: Default::default(),
indent_style: DEFAULT_INDENT,
editor_config: EditorConfig::default(),
@@ -730,19 +717,13 @@ impl Document {
jump_labels: HashMap::new(),
color_swatches: None,
color_swatch_controller: TaskController::new(),
- syn_loader,
- previous_diagnostic_id: None,
- pull_diagnostic_controller: TaskController::new(),
}
}
- pub fn default(
- config: Arc<dyn DynAccess<Config>>,
- syn_loader: Arc<ArcSwap<syntax::Loader>>,
- ) -> Self {
+ pub fn default(config: Arc<dyn DynAccess<Config>>) -> Self {
let line_ending: LineEnding = config.load().default_line_ending.into();
let text = Rope::from(line_ending.as_str());
- Self::from(text, None, config, syn_loader)
+ Self::from(text, None, config)
}
// TODO: async fn?
@@ -751,9 +732,8 @@ impl Document {
pub fn open(
path: &Path,
mut encoding: Option<&'static Encoding>,
- detect_language: bool,
+ config_loader: Option<Arc<ArcSwap<syntax::Loader>>>,
config: Arc<dyn DynAccess<Config>>,
- syn_loader: Arc<ArcSwap<syntax::Loader>>,
) -> Result<Self, DocumentOpenError> {
// If the path is not a regular file (e.g.: /dev/random) it should not be opened.
if path.metadata().is_ok_and(|metadata| !metadata.is_file()) {
@@ -779,13 +759,12 @@ impl Document {
(Rope::from(line_ending.as_str()), encoding, false)
};
- let loader = syn_loader.load();
- let mut doc = Self::from(rope, Some((encoding, has_bom)), config, syn_loader);
+ let mut doc = Self::from(rope, Some((encoding, has_bom)), config);
// set the path and try detecting the language
doc.set_path(Some(path));
- if detect_language {
- doc.detect_language(&loader);
+ if let Some(loader) = config_loader {
+ doc.detect_language(loader);
}
doc.editor_config = editor_config;
@@ -796,12 +775,9 @@ impl Document {
/// The same as [`format`], but only returns formatting changes if auto-formatting
/// is configured.
- pub fn auto_format(
- &self,
- editor: &Editor,
- ) -> Option<BoxFuture<'static, Result<Transaction, FormatterError>>> {
+ pub fn auto_format(&self) -> Option<BoxFuture<'static, Result<Transaction, FormatterError>>> {
if self.language_config()?.auto_format {
- self.format(editor)
+ self.format()
} else {
None
}
@@ -811,10 +787,7 @@ impl Document {
/// to format it nicely.
// We can't use anyhow::Result here since the output of the future has to be
// clonable to be used as shared future. So use a custom error type.
- pub fn format(
- &self,
- editor: &Editor,
- ) -> Option<BoxFuture<'static, Result<Transaction, FormatterError>>> {
+ pub fn format(&self) -> Option<BoxFuture<'static, Result<Transaction, FormatterError>>> {
if let Some((fmt_cmd, fmt_args)) = self
.language_config()
.and_then(|c| c.formatter.as_ref())
@@ -839,20 +812,8 @@ impl Document {
process.current_dir(doc_dir);
}
- let args = match fmt_args
- .iter()
- .map(|content| expansion::expand(editor, Token::expand(content)))
- .collect::<Result<Vec<_>, _>>()
- {
- Ok(args) => args,
- Err(err) => {
- log::error!("Failed to expand formatter arguments: {err}");
- return None;
- }
- };
-
process
- .args(args.iter().map(AsRef::as_ref))
+ .args(fmt_args)
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped());
@@ -891,7 +852,7 @@ impl Document {
} else if !output.stderr.is_empty() {
log::debug!(
"Formatter printed to stderr: {}",
- String::from_utf8_lossy(&output.stderr)
+ String::from_utf8_lossy(&output.stderr).to_string()
);
}
@@ -980,12 +941,11 @@ impl Document {
};
let identifier = self.path().map(|_| self.identifier());
- let language_servers: Vec<_> = self.language_servers.values().cloned().collect();
+ let language_servers = self.language_servers.clone();
// mark changes up to now as saved
let current_rev = self.get_current_revision();
let doc_id = self.id();
- let atomic_save = self.config.load().atomic_save;
let encoding_with_bom_info = (self.encoding, self.has_bom);
let last_saved_time = self.last_saved_time;
@@ -1035,7 +995,7 @@ impl Document {
// Assume it is a hardlink to prevent data loss if the metadata cant be read (e.g. on certain Windows configurations)
let is_hardlink = helix_stdx::faccess::hardlink_count(&write_path).unwrap_or(2) > 1;
- let backup = if path.exists() && atomic_save {
+ let backup = if path.exists() {
let path_ = write_path.clone();
// hacks: we use tempfile to handle the complex task of creating
// non clobbered temporary path for us we don't want
@@ -1124,7 +1084,7 @@ impl Document {
text: text.clone(),
};
- for language_server in language_servers {
+ for (_, language_server) in language_servers {
if !language_server.is_initialized() {
continue;
}
@@ -1140,20 +1100,22 @@ impl Document {
}
/// Detect the programming language based on the file type.
- pub fn detect_language(&mut self, loader: &syntax::Loader) {
- self.set_language(self.detect_language_config(loader), loader);
+ pub fn detect_language(&mut self, config_loader: Arc<ArcSwap<syntax::Loader>>) {
+ let loader = config_loader.load();
+ self.set_language(
+ self.detect_language_config(&loader),
+ Some(Arc::clone(&config_loader)),
+ );
}
/// Detect the programming language based on the file type.
pub fn detect_language_config(
&self,
- loader: &syntax::Loader,
- ) -> Option<Arc<syntax::config::LanguageConfiguration>> {
- let language = loader
- .language_for_filename(self.path.as_ref()?)
- .or_else(|| loader.language_for_shebang(self.text().slice(..)))?;
-
- Some(loader.language(language).config().clone())
+ config_loader: &syntax::Loader,
+ ) -> Option<Arc<helix_core::syntax::LanguageConfiguration>> {
+ config_loader
+ .language_config_for_file_name(self.path.as_ref()?)
+ .or_else(|| config_loader.language_config_for_shebang(self.text().slice(..)))
}
/// Detect the indentation used in the file, or otherwise defaults to the language indentation
@@ -1178,7 +1140,7 @@ impl Document {
}
}
- pub fn detect_editor_config(&mut self) {
+ pub(crate) fn detect_editor_config(&mut self) {
if self.config.load().editor_config {
if let Some(path) = self.path.as_ref() {
self.editor_config = EditorConfig::find(path);
@@ -1292,36 +1254,35 @@ impl Document {
/// if it exists.
pub fn set_language(
&mut self,
- language_config: Option<Arc<syntax::config::LanguageConfiguration>>,
- loader: &syntax::Loader,
+ language_config: Option<Arc<helix_core::syntax::LanguageConfiguration>>,
+ loader: Option<Arc<ArcSwap<helix_core::syntax::Loader>>>,
) {
- self.language = language_config;
- self.syntax = self.language.as_ref().and_then(|config| {
- Syntax::new(self.text.slice(..), config.language(), loader)
- .map_err(|err| {
- // `NoRootConfig` means that there was an issue loading the language/syntax
- // config for the root language of the document. An error must have already
- // been logged by `LanguageData::syntax_config`.
- if err != syntax::HighlighterError::NoRootConfig {
- log::warn!("Error building syntax for '{}': {err}", self.display_name());
- }
- })
- .ok()
- });
+ if let (Some(language_config), Some(loader)) = (language_config, loader) {
+ if let Some(highlight_config) =
+ language_config.highlight_config(&(*loader).load().scopes())
+ {
+ self.syntax = Syntax::new(self.text.slice(..), highlight_config, loader);
+ }
+
+ self.language = Some(language_config);
+ } else {
+ self.syntax = None;
+ self.language = None;
+ };
}
/// Set the programming language for the file if you know the language but don't have the
- /// [`syntax::config::LanguageConfiguration`] for it.
+ /// [`syntax::LanguageConfiguration`] for it.
pub fn set_language_by_language_id(
&mut self,
language_id: &str,
- loader: &syntax::Loader,
+ config_loader: Arc<ArcSwap<syntax::Loader>>,
) -> anyhow::Result<()> {
- let language = loader
- .language_for_name(language_id)
+ let language_config = (*config_loader)
+ .load()
+ .language_config_for_language_id(language_id)
.ok_or_else(|| anyhow!("invalid language id: {}", language_id))?;
- let config = loader.language(language).config().clone();
- self.set_language(Some(config), loader);
+ self.set_language(Some(language_config), Some(config_loader));
Ok(())
}
@@ -1440,14 +1401,14 @@ impl Document {
// update tree-sitter syntax tree
if let Some(syntax) = &mut self.syntax {
- let loader = self.syn_loader.load();
- if let Err(err) = syntax.update(
+ // TODO: no unwrap
+ let res = syntax.update(
old_doc.slice(..),
self.text.slice(..),
transaction.changes(),
- &loader,
- ) {
- log::error!("TS parser failed, disabling TS for the current buffer: {err}");
+ );
+ if res.is_err() {
+ log::error!("TS parser failed, disabling TS for the current buffer: {res:?}");
self.syntax = None;
}
}
@@ -1498,33 +1459,6 @@ impl Document {
)
});
- // Update the inlay hint annotations' positions, helping ensure they are displayed in the proper place
- let apply_inlay_hint_changes = |annotations: &mut Vec<InlineAnnotation>| {
- changes.update_positions(
- annotations
- .iter_mut()
- .map(|annotation| (&mut annotation.char_idx, Assoc::After)),
- );
- };
-
- self.inlay_hints_oudated = true;
- for text_annotation in self.inlay_hints.values_mut() {
- let DocumentInlayHints {
- id: _,
- type_inlay_hints,
- parameter_inlay_hints,
- other_inlay_hints,
- padding_before_inlay_hints,
- padding_after_inlay_hints,
- } = text_annotation;
-
- apply_inlay_hint_changes(padding_before_inlay_hints);
- apply_inlay_hint_changes(type_inlay_hints);
- apply_inlay_hint_changes(parameter_inlay_hints);
- apply_inlay_hint_changes(other_inlay_hints);
- apply_inlay_hint_changes(padding_after_inlay_hints);
- }
-
helix_event::dispatch(DocumentDidChange {
doc: self,
view: view_id,
@@ -1660,7 +1594,7 @@ impl Document {
let savepoint_idx = self
.savepoints
.iter()
- .position(|savepoint_ref| std::ptr::eq(savepoint_ref.as_ptr(), savepoint))
+ .position(|savepoint_ref| savepoint_ref.as_ptr() == savepoint as *const _)
.expect("Savepoint must belong to this document");
let savepoint_ref = self.savepoints.remove(savepoint_idx);
@@ -1815,12 +1749,6 @@ impl Document {
self.version
}
- pub fn word_completion_enabled(&self) -> bool {
- self.language_config()
- .and_then(|lang_config| lang_config.word_completion.and_then(|c| c.enable))
- .unwrap_or_else(|| self.config.load().word_completion.enable)
- }
-
pub fn path_completion_enabled(&self) -> bool {
self.language_config()
.and_then(|lang_config| lang_config.path_completion)
@@ -2261,7 +2189,8 @@ impl Document {
viewport_width,
wrap_indicator: wrap_indicator.into_boxed_str(),
wrap_indicator_highlight: theme
- .and_then(|theme| theme.find_highlight("ui.virtual.wrap")),
+ .and_then(|theme| theme.find_scope_index("ui.virtual.wrap"))
+ .map(Highlight),
soft_wrap_at_text_width,
}
}
@@ -2284,15 +2213,17 @@ impl Document {
self.inlay_hints.get(&view_id)
}
+ pub fn inlay_hints_mut(&mut self) -> impl Iterator<Item = (ViewId, &mut DocumentInlayHints)> {
+ self.inlay_hints
+ .iter_mut()
+ .map(|(view_id, hints)| (*view_id, hints))
+ }
+
/// Completely removes all the inlay hints saved for the document, dropping them to free memory
/// (since it often means inlay hints have been fully deactivated).
pub fn reset_all_inlay_hints(&mut self) {
self.inlay_hints = Default::default();
}
-
- pub fn has_language_server_with_feature(&self, feature: LanguageServerFeature) -> bool {
- self.language_servers_with_feature(feature).next().is_some()
- }
}
#[derive(Debug, Default)]
@@ -2345,7 +2276,6 @@ mod test {
text,
None,
Arc::new(ArcSwap::new(Arc::new(Config::default()))),
- Arc::new(ArcSwap::from_pointee(syntax::Loader::default())),
);
let view = ViewId::default();
doc.set_selection(view, Selection::single(0, 0));
@@ -2384,7 +2314,6 @@ mod test {
text,
None,
Arc::new(ArcSwap::new(Arc::new(Config::default()))),
- Arc::new(ArcSwap::from_pointee(syntax::Loader::default())),
);
let view = ViewId::default();
doc.set_selection(view, Selection::single(5, 5));
@@ -2498,12 +2427,9 @@ mod test {
#[test]
fn test_line_ending() {
assert_eq!(
- Document::default(
- Arc::new(ArcSwap::new(Arc::new(Config::default()))),
- Arc::new(ArcSwap::from_pointee(syntax::Loader::default()))
- )
- .text()
- .to_string(),
+ Document::default(Arc::new(ArcSwap::new(Arc::new(Config::default()))))
+ .text()
+ .to_string(),
helix_core::NATIVE_LINE_ENDING.as_str()
);
}