aboutsummaryrefslogtreecommitdiff
path: root/helix-view/src/document.rs
diff options
context:
space:
mode:
authorPascal Kuthe2023-01-31 17:03:19 +0000
committerGitHub2023-01-31 17:03:19 +0000
commit4dcf1fe66ba30a78edc054780d9b65c2f826530f (patch)
treeffb84ea94f07ceb52494a955b1bd78f115395dc0 /helix-view/src/document.rs
parent4eca4b3079bf53de874959270d0b3471d320debc (diff)
rework positioning/rendering and enable softwrap/virtual text (#5420)
* rework positioning/rendering, enables softwrap/virtual text This commit is a large rework of the core text positioning and rendering code in helix to remove the assumption that on-screen columns/lines correspond to text columns/lines. A generic `DocFormatter` is introduced that positions graphemes on and is used both for rendering and for movements/scrolling. Both virtual text support (inline, grapheme overlay and multi-line) and a capable softwrap implementation is included. fix picker highlight cleanup doc formatter, use word bondaries for wrapping make visual vertical movement a seperate commnad estimate line gutter width to improve performance cache cursor position cleanup and optimize doc formatter cleanup documentation fix typos Co-authored-by: Daniel Hines <d4hines@gmail.com> update documentation fix panic in last_visual_line funciton improve soft-wrap documentation add extend_visual_line_up/down commands fix non-visual vertical movement streamline virtual text highlighting, add softwrap indicator fix cursor position if softwrap is disabled improve documentation of text_annotations module avoid crashes if view anchor is out of bounds fix: consider horizontal offset when traslation char_idx -> vpos improve default configuration fix: mixed up horizontal and vertical offset reset view position after config reload apply suggestions from review disabled softwrap for very small screens to avoid endless spin fix wrap_indicator setting fix bar cursor disappearring on the EOF character add keybinding for linewise vertical movement fix: inconsistent gutter highlights improve virtual text API make scope idx lookup more ergonomic allow overlapping overlays correctly track char_pos for virtual text adjust configuration deprecate old position fucntions fix infinite loop in highlight lookup fix gutter style fix formatting document max-line-width interaction with softwrap change wrap-indicator example to use empty string fix: rare panic when view is in invalid state (bis) * Apply suggestions from code review Co-authored-by: Michael Davis <mcarsondavis@gmail.com> * improve documentation for positoning functions * simplify tests * fix documentation of Grapheme::width * Apply suggestions from code review Co-authored-by: Michael Davis <mcarsondavis@gmail.com> * add explicit drop invocation * Add explicit MoveFn type alias * add docuntation to Editor::cursor_cache * fix a few typos * explain use of allow(deprecated) * make gj and gk extend in select mode * remove unneded debug and TODO * mark tab_width_at #[inline] * add fast-path to move_vertically_visual in case softwrap is disabled * rename first_line to first_visual_line * simplify duplicate if/else --------- Co-authored-by: Michael Davis <mcarsondavis@gmail.com>
Diffstat (limited to 'helix-view/src/document.rs')
-rw-r--r--helix-view/src/document.rs74
1 files changed, 61 insertions, 13 deletions
diff --git a/helix-view/src/document.rs b/helix-view/src/document.rs
index 6b33ea6a..798b5400 100644
--- a/helix-view/src/document.rs
+++ b/helix-view/src/document.rs
@@ -1,7 +1,11 @@
use anyhow::{anyhow, bail, Context, Error};
+use arc_swap::access::DynAccess;
use futures_util::future::BoxFuture;
use futures_util::FutureExt;
use helix_core::auto_pairs::AutoPairs;
+use helix_core::doc_formatter::TextFormat;
+use helix_core::syntax::Highlight;
+use helix_core::text_annotations::TextAnnotations;
use helix_core::Range;
use helix_vcs::{DiffHandle, DiffProviderRegistry};
@@ -26,8 +30,8 @@ use helix_core::{
DEFAULT_LINE_ENDING,
};
-use crate::editor::RedrawHandle;
-use crate::{DocumentId, Editor, View, ViewId};
+use crate::editor::{Config, RedrawHandle};
+use crate::{DocumentId, Editor, Theme, View, ViewId};
/// 8kB of buffer space for encoding and decoding `Rope`s.
const BUF_SIZE: usize = 8192;
@@ -127,6 +131,7 @@ pub struct Document {
// it back as it separated from the edits. We could split out the parts manually but that will
// be more troublesome.
pub history: Cell<History>,
+ pub config: Arc<dyn DynAccess<Config>>,
pub savepoint: Option<Transaction>,
@@ -351,7 +356,11 @@ use helix_lsp::lsp;
use url::Url;
impl Document {
- pub fn from(text: Rope, encoding: Option<&'static encoding::Encoding>) -> Self {
+ pub fn from(
+ text: Rope,
+ encoding: Option<&'static encoding::Encoding>,
+ config: Arc<dyn DynAccess<Config>>,
+ ) -> Self {
let encoding = encoding.unwrap_or(encoding::UTF_8);
let changes = ChangeSet::new(&text);
let old_state = None;
@@ -377,9 +386,13 @@ impl Document {
modified_since_accessed: false,
language_server: None,
diff_handle: None,
+ config,
}
}
-
+ pub fn default(config: Arc<dyn DynAccess<Config>>) -> Self {
+ let text = Rope::from(DEFAULT_LINE_ENDING.as_str());
+ Self::from(text, None, config)
+ }
// TODO: async fn?
/// Create a new document from `path`. Encoding is auto-detected, but it can be manually
/// overwritten with the `encoding` parameter.
@@ -387,6 +400,7 @@ impl Document {
path: &Path,
encoding: Option<&'static encoding::Encoding>,
config_loader: Option<Arc<syntax::Loader>>,
+ config: Arc<dyn DynAccess<Config>>,
) -> Result<Self, Error> {
// Open the file if it exists, otherwise assume it is a new file (and thus empty).
let (rope, encoding) = if path.exists() {
@@ -398,7 +412,7 @@ impl Document {
(Rope::from(DEFAULT_LINE_ENDING.as_str()), encoding)
};
- let mut doc = Self::from(rope, Some(encoding));
+ let mut doc = Self::from(rope, Some(encoding), config);
// set the path and try detecting the language
doc.set_path(Some(path))?;
@@ -1192,12 +1206,34 @@ impl Document {
None => global_config,
}
}
-}
-impl Default for Document {
- fn default() -> Self {
- let text = Rope::from(DEFAULT_LINE_ENDING.as_str());
- Self::from(text, None)
+ pub fn text_format(&self, mut viewport_width: u16, theme: Option<&Theme>) -> TextFormat {
+ if let Some(max_line_len) = self
+ .language_config()
+ .and_then(|config| config.max_line_length)
+ {
+ viewport_width = viewport_width.min(max_line_len as u16)
+ }
+ let config = self.config.load();
+ let soft_wrap = &config.soft_wrap;
+ let tab_width = self.tab_width() as u16;
+ TextFormat {
+ soft_wrap: soft_wrap.enable && viewport_width > 10,
+ tab_width,
+ max_wrap: soft_wrap.max_wrap.min(viewport_width / 4),
+ max_indent_retain: soft_wrap.max_indent_retain.min(viewport_width * 2 / 5),
+ // avoid spinning forever when the window manager
+ // sets the size to something tiny
+ viewport_width,
+ wrap_indicator: soft_wrap.wrap_indicator.clone().into_boxed_str(),
+ wrap_indicator_highlight: theme
+ .and_then(|theme| theme.find_scope_index("ui.virtual.wrap"))
+ .map(Highlight),
+ }
+ }
+
+ pub fn text_annotations(&self, _theme: Option<&Theme>) -> TextAnnotations {
+ TextAnnotations::default()
}
}
@@ -1236,13 +1272,19 @@ impl Display for FormatterError {
#[cfg(test)]
mod test {
+ use arc_swap::ArcSwap;
+
use super::*;
#[test]
fn changeset_to_changes_ignore_line_endings() {
use helix_lsp::{lsp, Client, OffsetEncoding};
let text = Rope::from("hello\r\nworld");
- let mut doc = Document::from(text, None);
+ let mut doc = Document::from(
+ text,
+ None,
+ Arc::new(ArcSwap::new(Arc::new(Config::default()))),
+ );
let view = ViewId::default();
doc.set_selection(view, Selection::single(0, 0));
@@ -1276,7 +1318,11 @@ mod test {
fn changeset_to_changes() {
use helix_lsp::{lsp, Client, OffsetEncoding};
let text = Rope::from("hello");
- let mut doc = Document::from(text, None);
+ let mut doc = Document::from(
+ text,
+ None,
+ Arc::new(ArcSwap::new(Arc::new(Config::default()))),
+ );
let view = ViewId::default();
doc.set_selection(view, Selection::single(5, 5));
@@ -1389,7 +1435,9 @@ mod test {
#[test]
fn test_line_ending() {
assert_eq!(
- Document::default().text().to_string(),
+ Document::default(Arc::new(ArcSwap::new(Arc::new(Config::default()))))
+ .text()
+ .to_string(),
DEFAULT_LINE_ENDING.as_str()
);
}