aboutsummaryrefslogtreecommitdiff
path: root/helix-view/src/view.rs
diff options
context:
space:
mode:
authorPascal Kuthe2023-03-06 15:25:41 +0000
committerBlaž Hrastnik2023-03-27 00:54:40 +0000
commit72b93116784ec944f49c7f6a335b0aa663f1430e (patch)
treecee2a5ceac008625f5bd749eb634fbbdaf89d951 /helix-view/src/view.rs
parent0ab96cc2576cf8d78d54bcc42e0e7f5285321030 (diff)
fix view anchors not at start of a visual line
The top of a view is marked by a char idx anchor. That char idx is usually the first character of the visual line it's on. We use a char index instead of a line index because the view may start in the middle of a line with soft wrapping. However, it's possible to temporarily endup in a state where this anchor is not the first character of the first visual line. This is pretty rare because edits usually happen inside/after the view. In most cases we handle this case correctly. However, if the cursor is before the anchor (but still in view) there can be crashes or visual artifacts. This is caused by the fact that visual_offset_from_anchor (and the positioning code in view.rs) incorrectly assumed that the (cursor) position is always after the view anchor if the cursor is in view. But if the anchor is not the first character of the first visual line this is not the case anymore. In that case crashes and visual artifacts are possible. This commit fixes that problem by changing `visual_offset_from_anchor` (and callsites) to properly consider that case.
Diffstat (limited to 'helix-view/src/view.rs')
-rw-r--r--helix-view/src/view.rs76
1 files changed, 37 insertions, 39 deletions
diff --git a/helix-view/src/view.rs b/helix-view/src/view.rs
index 0ac7ca3b..ee6fc127 100644
--- a/helix-view/src/view.rs
+++ b/helix-view/src/view.rs
@@ -7,9 +7,13 @@ use crate::{
};
use helix_core::{
- char_idx_at_visual_offset, doc_formatter::TextFormat, syntax::Highlight,
- text_annotations::TextAnnotations, visual_offset_from_anchor, visual_offset_from_block,
- Position, RopeSlice, Selection, Transaction,
+ char_idx_at_visual_offset,
+ doc_formatter::TextFormat,
+ syntax::Highlight,
+ text_annotations::TextAnnotations,
+ visual_offset_from_anchor, visual_offset_from_block, Position, RopeSlice, Selection,
+ Transaction,
+ VisualOffsetError::{PosAfterMaxRow, PosBeforeAnchorRow},
};
use std::{
@@ -213,46 +217,38 @@ impl View {
// - 1 so we have at least one gap in the middle.
// a height of 6 with padding of 3 on each side will keep shifting the view back and forth
// as we type
- let scrolloff = scrolloff.min(viewport.height.saturating_sub(1) as usize / 2);
+ let scrolloff = if CENTERING {
+ 0
+ } else {
+ scrolloff.min(viewport.height.saturating_sub(1) as usize / 2)
+ };
let cursor = doc.selection(self.id).primary().cursor(doc_text);
let mut offset = self.offset;
+ let off = visual_offset_from_anchor(
+ doc_text,
+ offset.anchor,
+ cursor,
+ &text_fmt,
+ &annotations,
+ vertical_viewport_end,
+ );
- let (visual_off, mut at_top) = if cursor >= offset.anchor {
- let off = visual_offset_from_anchor(
- doc_text,
- offset.anchor,
- cursor,
- &text_fmt,
- &annotations,
- vertical_viewport_end,
- );
- (off, false)
- } else if CENTERING {
- // cursor out of view
- return None;
- } else {
- (None, true)
- };
-
- let new_anchor = match visual_off {
- Some((visual_pos, _)) if visual_pos.row < scrolloff + offset.vertical_offset => {
- if CENTERING && visual_pos.row < offset.vertical_offset {
+ let (new_anchor, at_top) = match off {
+ Ok((visual_pos, _)) if visual_pos.row < scrolloff + offset.vertical_offset => {
+ if CENTERING {
// cursor out of view
return None;
}
- at_top = true;
- true
+ (true, true)
}
- Some((visual_pos, _)) if visual_pos.row + scrolloff + 1 >= vertical_viewport_end => {
- if CENTERING && visual_pos.row >= vertical_viewport_end {
- // cursor out of view
- return None;
- }
- true
+ Ok((visual_pos, _)) if visual_pos.row + scrolloff >= vertical_viewport_end => {
+ (true, false)
}
- Some(_) => false,
- None => true,
+ Ok((_, _)) => (false, false),
+ Err(_) if CENTERING => return None,
+ Err(PosBeforeAnchorRow) => (true, true),
+ Err(PosAfterMaxRow) => (true, false),
};
if new_anchor {
@@ -269,8 +265,8 @@ impl View {
offset.horizontal_offset = 0;
} else {
// determine the current visual column of the text
- let col = visual_off
- .unwrap_or_else(|| {
+ let col = off
+ .unwrap_or_else(|_| {
visual_offset_from_block(
doc_text,
offset.anchor,
@@ -360,8 +356,9 @@ impl View {
);
match pos {
- Some((Position { row, .. }, _)) => row.saturating_sub(self.offset.vertical_offset),
- None => visual_height.saturating_sub(1),
+ Ok((Position { row, .. }, _)) => row.saturating_sub(self.offset.vertical_offset),
+ Err(PosAfterMaxRow) => visual_height.saturating_sub(1),
+ Err(PosBeforeAnchorRow) => 0,
}
}
@@ -390,7 +387,8 @@ impl View {
&text_fmt,
&annotations,
viewport.height as usize,
- )?
+ )
+ .ok()?
.0;
if pos.row < self.offset.vertical_offset {
return None;