aboutsummaryrefslogtreecommitdiff
path: root/helix-view/src/editor.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/editor.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/editor.rs')
-rw-r--r--helix-view/src/editor.rs85
1 files changed, 76 insertions, 9 deletions
diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs
index 1029c14f..46511c62 100644
--- a/helix-view/src/editor.rs
+++ b/helix-view/src/editor.rs
@@ -7,6 +7,7 @@ use crate::{
input::KeyEvent,
theme::{self, Theme},
tree::{self, Tree},
+ view::ViewPosition,
Align, Document, DocumentId, View, ViewId,
};
use helix_vcs::DiffProviderRegistry;
@@ -18,6 +19,7 @@ use tokio_stream::wrappers::UnboundedReceiverStream;
use std::{
borrow::Cow,
+ cell::Cell,
collections::{BTreeMap, HashMap},
io::stdin,
num::NonZeroUsize,
@@ -268,6 +270,44 @@ pub struct Config {
pub indent_guides: IndentGuidesConfig,
/// Whether to color modes with different colors. Defaults to `false`.
pub color_modes: bool,
+ pub soft_wrap: SoftWrap,
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
+#[serde(default, rename_all = "kebab-case", deny_unknown_fields)]
+pub struct SoftWrap {
+ /// Soft wrap lines that exceed viewport width. Default to off
+ pub enable: bool,
+ /// Maximum space left free at the end of the line.
+ /// This space is used to wrap text at word boundaries. If that is not possible within this limit
+ /// the word is simply split at the end of the line.
+ ///
+ /// This is automatically hard-limited to a quarter of the viewport to ensure correct display on small views.
+ ///
+ /// Default to 20
+ pub max_wrap: u16,
+ /// Maximum number of indentation that can be carried over from the previous line when softwrapping.
+ /// If a line is indented further then this limit it is rendered at the start of the viewport instead.
+ ///
+ /// This is automatically hard-limited to a quarter of the viewport to ensure correct display on small views.
+ ///
+ /// Default to 40
+ pub max_indent_retain: u16,
+ /// Indicator placed at the beginning of softwrapped lines
+ ///
+ /// Defaults to ↪
+ pub wrap_indicator: String,
+}
+
+impl Default for SoftWrap {
+ fn default() -> Self {
+ SoftWrap {
+ enable: false,
+ max_wrap: 20,
+ max_indent_retain: 40,
+ wrap_indicator: "↪ ".into(),
+ }
+ }
}
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
@@ -717,6 +757,7 @@ impl Default for Config {
bufferline: BufferLine::default(),
indent_guides: IndentGuidesConfig::default(),
color_modes: false,
+ soft_wrap: SoftWrap::default(),
}
}
}
@@ -797,7 +838,7 @@ pub struct Editor {
pub status_msg: Option<(Cow<'static, str>, Severity)>,
pub autoinfo: Option<Info>,
- pub config: Box<dyn DynAccess<Config>>,
+ pub config: Arc<dyn DynAccess<Config>>,
pub auto_pairs: Option<AutoPairs>,
pub idle_timer: Pin<Box<Sleep>>,
@@ -813,6 +854,19 @@ pub struct Editor {
/// The `RwLock` blocks the editor from performing the render until an exclusive lock can be aquired
pub redraw_handle: RedrawHandle,
pub needs_redraw: bool,
+ /// Cached position of the cursor calculated during rendering.
+ /// The content of `cursor_cache` is returned by `Editor::cursor` if
+ /// set to `Some(_)`. The value will be cleared after it's used.
+ /// If `cursor_cache` is `None` then the `Editor::cursor` function will
+ /// calculate the cursor position.
+ ///
+ /// `Some(None)` represents a cursor position outside of the visible area.
+ /// This will just cause `Editor::cursor` to return `None`.
+ ///
+ /// This cache is only a performance optimization to
+ /// avoid calculating the cursor position multiple
+ /// times during rendering and should not be set by other functions.
+ pub cursor_cache: Cell<Option<Option<Position>>>,
}
pub type RedrawHandle = (Arc<Notify>, Arc<RwLock<()>>);
@@ -866,7 +920,7 @@ impl Editor {
mut area: Rect,
theme_loader: Arc<theme::Loader>,
syn_loader: Arc<syntax::Loader>,
- config: Box<dyn DynAccess<Config>>,
+ config: Arc<dyn DynAccess<Config>>,
) -> Self {
let conf = config.load();
let auto_pairs = (&conf.auto_pairs).into();
@@ -910,6 +964,7 @@ impl Editor {
config_events: unbounded_channel(),
redraw_handle: Default::default(),
needs_redraw: false,
+ cursor_cache: Cell::new(None),
}
}
@@ -994,7 +1049,7 @@ impl Editor {
fn set_theme_impl(&mut self, theme: Theme, preview: ThemeAction) {
// `ui.selection` is the only scope required to be able to render a theme.
- if theme.find_scope_index("ui.selection").is_none() {
+ if theme.find_scope_index_exact("ui.selection").is_none() {
self.set_error("Invalid theme: `ui.selection` required");
return;
}
@@ -1077,7 +1132,7 @@ impl Editor {
fn replace_document_in_view(&mut self, current_view: ViewId, doc_id: DocumentId) {
let view = self.tree.get_mut(current_view);
view.doc = doc_id;
- view.offset = Position::default();
+ view.offset = ViewPosition::default();
let doc = doc_mut!(self, &doc_id);
doc.ensure_view_init(view.id);
@@ -1204,12 +1259,15 @@ impl Editor {
}
pub fn new_file(&mut self, action: Action) -> DocumentId {
- self.new_file_from_document(action, Document::default())
+ self.new_file_from_document(action, Document::default(self.config.clone()))
}
pub fn new_file_from_stdin(&mut self, action: Action) -> Result<DocumentId, Error> {
let (rope, encoding) = crate::document::from_reader(&mut stdin(), None)?;
- Ok(self.new_file_from_document(action, Document::from(rope, Some(encoding))))
+ Ok(self.new_file_from_document(
+ action,
+ Document::from(rope, Some(encoding), self.config.clone()),
+ ))
}
// ??? possible use for integration tests
@@ -1220,7 +1278,12 @@ impl Editor {
let id = if let Some(id) = id {
id
} else {
- let mut doc = Document::open(&path, None, Some(self.syn_loader.clone()))?;
+ let mut doc = Document::open(
+ &path,
+ None,
+ Some(self.syn_loader.clone()),
+ self.config.clone(),
+ )?;
let _ = Self::launch_language_server(&mut self.language_servers, &mut doc);
if let Some(diff_base) = self.diff_providers.get_diff_base(&path) {
@@ -1306,7 +1369,7 @@ impl Editor {
.iter()
.map(|(&doc_id, _)| doc_id)
.next()
- .unwrap_or_else(|| self.new_document(Document::default()));
+ .unwrap_or_else(|| self.new_document(Document::default(self.config.clone())));
let view = View::new(doc_id, self.config().gutters.clone());
let view_id = self.tree.insert(view);
let doc = doc_mut!(self, &doc_id);
@@ -1440,7 +1503,11 @@ impl Editor {
.selection(view.id)
.primary()
.cursor(doc.text().slice(..));
- if let Some(mut pos) = view.screen_coords_at_pos(doc, doc.text().slice(..), cursor) {
+ let pos = self
+ .cursor_cache
+ .get()
+ .unwrap_or_else(|| view.screen_coords_at_pos(doc, doc.text().slice(..), cursor));
+ if let Some(mut pos) = pos {
let inner = view.inner_area(doc);
pos.col += inner.x as usize;
pos.row += inner.y as usize;