diff options
author | Pascal Kuthe | 2023-01-31 17:03:19 +0000 |
---|---|---|
committer | GitHub | 2023-01-31 17:03:19 +0000 |
commit | 4dcf1fe66ba30a78edc054780d9b65c2f826530f (patch) | |
tree | ffb84ea94f07ceb52494a955b1bd78f115395dc0 /helix-view/src/document.rs | |
parent | 4eca4b3079bf53de874959270d0b3471d320debc (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.rs | 74 |
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() ); } |