aboutsummaryrefslogtreecommitdiff
path: root/helix-core/src/doc_formatter
diff options
context:
space:
mode:
authorPascal Kuthe2023-01-31 17:03:19 +0000
committerGitHub2023-01-31 17:03:19 +0000
commit4dcf1fe66ba30a78edc054780d9b65c2f826530f (patch)
treeffb84ea94f07ceb52494a955b1bd78f115395dc0 /helix-core/src/doc_formatter
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-core/src/doc_formatter')
-rw-r--r--helix-core/src/doc_formatter/test.rs222
1 files changed, 222 insertions, 0 deletions
diff --git a/helix-core/src/doc_formatter/test.rs b/helix-core/src/doc_formatter/test.rs
new file mode 100644
index 00000000..e68b31fd
--- /dev/null
+++ b/helix-core/src/doc_formatter/test.rs
@@ -0,0 +1,222 @@
+use std::rc::Rc;
+
+use crate::doc_formatter::{DocumentFormatter, TextFormat};
+use crate::text_annotations::{InlineAnnotation, Overlay, TextAnnotations};
+
+impl TextFormat {
+ fn new_test(softwrap: bool) -> Self {
+ TextFormat {
+ soft_wrap: softwrap,
+ tab_width: 2,
+ max_wrap: 3,
+ max_indent_retain: 4,
+ wrap_indicator: ".".into(),
+ wrap_indicator_highlight: None,
+ // use a prime number to allow lining up too often with repeat
+ viewport_width: 17,
+ }
+ }
+}
+
+impl<'t> DocumentFormatter<'t> {
+ fn collect_to_str(&mut self) -> String {
+ use std::fmt::Write;
+ let mut res = String::new();
+ let viewport_width = self.text_fmt.viewport_width;
+ let mut line = 0;
+
+ for (grapheme, pos) in self {
+ if pos.row != line {
+ line += 1;
+ assert_eq!(pos.row, line);
+ write!(res, "\n{}", ".".repeat(pos.col)).unwrap();
+ assert!(
+ pos.col <= viewport_width as usize,
+ "softwrapped failed {}<={viewport_width}",
+ pos.col
+ );
+ }
+ write!(res, "{}", grapheme.grapheme).unwrap();
+ }
+
+ res
+ }
+}
+
+fn softwrap_text(text: &str) -> String {
+ DocumentFormatter::new_at_prev_checkpoint(
+ text.into(),
+ &TextFormat::new_test(true),
+ &TextAnnotations::default(),
+ 0,
+ )
+ .0
+ .collect_to_str()
+}
+
+#[test]
+fn basic_softwrap() {
+ assert_eq!(
+ softwrap_text(&"foo ".repeat(10)),
+ "foo foo foo foo \n.foo foo foo foo \n.foo foo "
+ );
+ assert_eq!(
+ softwrap_text(&"fooo ".repeat(10)),
+ "fooo fooo fooo \n.fooo fooo fooo \n.fooo fooo fooo \n.fooo "
+ );
+
+ // check that we don't wrap unnecessarily
+ assert_eq!(softwrap_text("\t\txxxx1xxxx2xx\n"), " xxxx1xxxx2xx \n ");
+}
+
+#[test]
+fn softwrap_indentation() {
+ assert_eq!(
+ softwrap_text("\t\tfoo1 foo2 foo3 foo4 foo5 foo6\n"),
+ " foo1 foo2 \n.....foo3 foo4 \n.....foo5 foo6 \n "
+ );
+ assert_eq!(
+ softwrap_text("\t\t\tfoo1 foo2 foo3 foo4 foo5 foo6\n"),
+ " foo1 foo2 \n.foo3 foo4 foo5 \n.foo6 \n "
+ );
+}
+
+#[test]
+fn long_word_softwrap() {
+ assert_eq!(
+ softwrap_text("\t\txxxx1xxxx2xxxx3xxxx4xxxx5xxxx6xxxx7xxxx8xxxx9xxx\n"),
+ " xxxx1xxxx2xxx\n.....x3xxxx4xxxx5\n.....xxxx6xxxx7xx\n.....xx8xxxx9xxx \n "
+ );
+ assert_eq!(
+ softwrap_text("xxxxxxxx1xxxx2xxx\n"),
+ "xxxxxxxx1xxxx2xxx\n. \n "
+ );
+ assert_eq!(
+ softwrap_text("\t\txxxx1xxxx 2xxxx3xxxx4xxxx5xxxx6xxxx7xxxx8xxxx9xxx\n"),
+ " xxxx1xxxx \n.....2xxxx3xxxx4x\n.....xxx5xxxx6xxx\n.....x7xxxx8xxxx9\n.....xxx \n "
+ );
+ assert_eq!(
+ softwrap_text("\t\txxxx1xxx 2xxxx3xxxx4xxxx5xxxx6xxxx7xxxx8xxxx9xxx\n"),
+ " xxxx1xxx 2xxx\n.....x3xxxx4xxxx5\n.....xxxx6xxxx7xx\n.....xx8xxxx9xxx \n "
+ );
+}
+
+fn overlay_text(text: &str, char_pos: usize, softwrap: bool, overlays: &[Overlay]) -> String {
+ DocumentFormatter::new_at_prev_checkpoint(
+ text.into(),
+ &TextFormat::new_test(softwrap),
+ TextAnnotations::default().add_overlay(overlays.into(), None),
+ char_pos,
+ )
+ .0
+ .collect_to_str()
+}
+
+#[test]
+fn overlay() {
+ assert_eq!(
+ overlay_text(
+ "foobar",
+ 0,
+ false,
+ &[
+ Overlay {
+ char_idx: 0,
+ grapheme: "X".into(),
+ },
+ Overlay {
+ char_idx: 2,
+ grapheme: "\t".into(),
+ },
+ ]
+ ),
+ "Xo bar "
+ );
+ assert_eq!(
+ overlay_text(
+ &"foo ".repeat(10),
+ 0,
+ true,
+ &[
+ Overlay {
+ char_idx: 2,
+ grapheme: "\t".into(),
+ },
+ Overlay {
+ char_idx: 5,
+ grapheme: "\t".into(),
+ },
+ Overlay {
+ char_idx: 16,
+ grapheme: "X".into(),
+ },
+ ]
+ ),
+ "fo f o foo \n.foo Xoo foo foo \n.foo foo foo "
+ );
+}
+
+fn annotate_text(text: &str, softwrap: bool, annotations: &[InlineAnnotation]) -> String {
+ DocumentFormatter::new_at_prev_checkpoint(
+ text.into(),
+ &TextFormat::new_test(softwrap),
+ TextAnnotations::default().add_inline_annotations(annotations.into(), None),
+ 0,
+ )
+ .0
+ .collect_to_str()
+}
+
+#[test]
+fn annotation() {
+ assert_eq!(
+ annotate_text(
+ "bar",
+ false,
+ &[InlineAnnotation {
+ char_idx: 0,
+ text: "foo".into(),
+ }]
+ ),
+ "foobar "
+ );
+ assert_eq!(
+ annotate_text(
+ &"foo ".repeat(10),
+ true,
+ &[InlineAnnotation {
+ char_idx: 0,
+ text: "foo ".into(),
+ }]
+ ),
+ "foo foo foo foo \n.foo foo foo foo \n.foo foo foo "
+ );
+}
+#[test]
+fn annotation_and_overlay() {
+ assert_eq!(
+ DocumentFormatter::new_at_prev_checkpoint(
+ "bbar".into(),
+ &TextFormat::new_test(false),
+ TextAnnotations::default()
+ .add_inline_annotations(
+ Rc::new([InlineAnnotation {
+ char_idx: 0,
+ text: "fooo".into(),
+ }]),
+ None
+ )
+ .add_overlay(
+ Rc::new([Overlay {
+ char_idx: 0,
+ grapheme: "\t".into(),
+ }]),
+ None
+ ),
+ 0,
+ )
+ .0
+ .collect_to_str(),
+ "fooo bar "
+ );
+}