on master help sign in
fix view anchors not at start of a visual line
72b9311Pascal Kuthe
helix-core/src/lib.rs+1−1
@@ -98,7 +98,7 @@ pub use {regex, tree_sitter};
98pub use graphemes::RopeGraphemes;
98pub use graphemes::RopeGraphemes;
99pub use position::{
99pub use position::{
100 char_idx_at_visual_offset, coords_at_pos, pos_at_coords, visual_offset_from_anchor,
100 char_idx_at_visual_offset, coords_at_pos, pos_at_coords, visual_offset_from_anchor,
101− visual_offset_from_block, Position,
101+ visual_offset_from_block, Position, VisualOffsetError,
102};
102};
103#[allow(deprecated)]
103#[allow(deprecated)]
104pub use position::{pos_at_visual_coords, visual_coords_at_pos};
104pub use position::{pos_at_visual_coords, visual_coords_at_pos};
helix-core/src/position.rs+32−8
@@ -137,6 +137,12 @@ pub fn visual_offset_from_block(
137 (last_pos, block_start)
137 (last_pos, block_start)
138}
138}
139
139
140+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
141+pub enum VisualOffsetError {
142+ PosBeforeAnchorRow,
143+ PosAfterMaxRow,
144+}
145+
140/// Returns the visual offset from the start of the visual line
146/// Returns the visual offset from the start of the visual line
141/// that contains anchor.
147/// that contains anchor.
142pub fn visual_offset_from_anchor(
148pub fn visual_offset_from_anchor(
@@ -146,28 +152,46 @@ pub fn visual_offset_from_anchor(
146 text_fmt: &TextFormat,
152 text_fmt: &TextFormat,
147 annotations: &TextAnnotations,
153 annotations: &TextAnnotations,
148 max_rows: usize,
154 max_rows: usize,
149−) -> Option<(Position, usize)> {
155+) -> Result<(Position, usize), VisualOffsetError> {
150 let (formatter, block_start) =
156 let (formatter, block_start) =
151 DocumentFormatter::new_at_prev_checkpoint(text, text_fmt, annotations, anchor);
157 DocumentFormatter::new_at_prev_checkpoint(text, text_fmt, annotations, anchor);
152 let mut char_pos = block_start;
158 let mut char_pos = block_start;
153 let mut anchor_line = None;
159 let mut anchor_line = None;
160+ let mut found_pos = None;
154 let mut last_pos = Position::default();
161 let mut last_pos = Position::default();
155
162
163+ if pos < block_start {
164+ return Err(VisualOffsetError::PosBeforeAnchorRow);
165+ }
166+
156 for (grapheme, vpos) in formatter {
167 for (grapheme, vpos) in formatter {
157 last_pos = vpos;
168 last_pos = vpos;
158 char_pos += grapheme.doc_chars();
169 char_pos += grapheme.doc_chars();
159
170
160− if char_pos > anchor && anchor_line.is_none() {
161− anchor_line = Some(last_pos.row);
162− }
163 if char_pos > pos {
171 if char_pos > pos {
172+ if let Some(anchor_line) = anchor_line {
164− last_pos.row -= anchor_line.unwrap();
173+ last_pos.row -= anchor_line;
174+ return Ok((last_pos, block_start));
175+ } else {
176+ found_pos = Some(last_pos);
177+ }
178+ }
179+ if char_pos > anchor && anchor_line.is_none() {
180+ if let Some(mut found_pos) = found_pos {
181+ return if found_pos.row == last_pos.row {
182+ found_pos.row = 0;
165− return Some((last_pos, block_start));
183+ Ok((found_pos, block_start))
184+ } else {
185+ Err(VisualOffsetError::PosBeforeAnchorRow)
186+ };
187+ } else {
188+ anchor_line = Some(last_pos.row);
189+ }
166 }
190 }
167
191
168 if let Some(anchor_line) = anchor_line {
192 if let Some(anchor_line) = anchor_line {
169 if vpos.row >= anchor_line + max_rows {
193 if vpos.row >= anchor_line + max_rows {
170− return None;
194+ return Err(VisualOffsetError::PosAfterMaxRow);
171 }
195 }
172 }
196 }
173 }
197 }
@@ -175,7 +199,7 @@ pub fn visual_offset_from_anchor(
175 let anchor_line = anchor_line.unwrap_or(last_pos.row);
199 let anchor_line = anchor_line.unwrap_or(last_pos.row);
176 last_pos.row -= anchor_line;
200 last_pos.row -= anchor_line;
177
201
178− Some((last_pos, block_start))
202+ Ok((last_pos, block_start))
179}
203}
180
204
181/// Convert (line, column) coordinates to a character index.
205/// Convert (line, column) coordinates to a character index.
helix-view/src/view.rs+37−39
@@ -7,9 +7,13 @@ use crate::{
7};
7};
8
8
9use helix_core::{
9use helix_core::{
10− char_idx_at_visual_offset, doc_formatter::TextFormat, syntax::Highlight,
10+ char_idx_at_visual_offset,
11+ doc_formatter::TextFormat,
12+ syntax::Highlight,
13+ text_annotations::TextAnnotations,
11− text_annotations::TextAnnotations, visual_offset_from_anchor, visual_offset_from_block,
14+ visual_offset_from_anchor, visual_offset_from_block, Position, RopeSlice, Selection,
12− Position, RopeSlice, Selection, Transaction,
15+ Transaction,
16+ VisualOffsetError::{PosAfterMaxRow, PosBeforeAnchorRow},
13};
17};
14
18
15use std::{
19use std::{
@@ -213,46 +217,38 @@ impl View {
213 // - 1 so we have at least one gap in the middle.
217 // - 1 so we have at least one gap in the middle.
214 // a height of 6 with padding of 3 on each side will keep shifting the view back and forth
218 // a height of 6 with padding of 3 on each side will keep shifting the view back and forth
215 // as we type
219 // as we type
220+ let scrolloff = if CENTERING {
221+ 0
222+ } else {
216− let scrolloff = scrolloff.min(viewport.height.saturating_sub(1) as usize / 2);
223+ scrolloff.min(viewport.height.saturating_sub(1) as usize / 2)
224+ };
217
225
218 let cursor = doc.selection(self.id).primary().cursor(doc_text);
226 let cursor = doc.selection(self.id).primary().cursor(doc_text);
219 let mut offset = self.offset;
227 let mut offset = self.offset;
228+ let off = visual_offset_from_anchor(
229+ doc_text,
230+ offset.anchor,
231+ cursor,
232+ &text_fmt,
233+ &annotations,
234+ vertical_viewport_end,
235+ );
220
236
221− let (visual_off, mut at_top) = if cursor >= offset.anchor {
237+ let (new_anchor, at_top) = match off {
222− let off = visual_offset_from_anchor(
223− doc_text,
224− offset.anchor,
225− cursor,
226− &text_fmt,
227− &annotations,
228− vertical_viewport_end,
229− );
230− (off, false)
231− } else if CENTERING {
232− // cursor out of view
233− return None;
234− } else {
235− (None, true)
236− };
237−
238− let new_anchor = match visual_off {
239− Some((visual_pos, _)) if visual_pos.row < scrolloff + offset.vertical_offset => {
238+ Ok((visual_pos, _)) if visual_pos.row < scrolloff + offset.vertical_offset => {
240− if CENTERING && visual_pos.row < offset.vertical_offset {
239+ if CENTERING {
241 // cursor out of view
240 // cursor out of view
242 return None;
241 return None;
243 }
242 }
244− at_top = true;
245− true
243+ (true, true)
246 }
244 }
247− Some((visual_pos, _)) if visual_pos.row + scrolloff + 1 >= vertical_viewport_end => {
245+ Ok((visual_pos, _)) if visual_pos.row + scrolloff >= vertical_viewport_end => {
248− if CENTERING && visual_pos.row >= vertical_viewport_end {
249− // cursor out of view
250− return None;
251− }
252− true
246+ (true, false)
253 }
247 }
254− Some(_) => false,
248+ Ok((_, _)) => (false, false),
255− None => true,
249+ Err(_) if CENTERING => return None,
250+ Err(PosBeforeAnchorRow) => (true, true),
251+ Err(PosAfterMaxRow) => (true, false),
256 };
252 };
257
253
258 if new_anchor {
254 if new_anchor {
@@ -269,8 +265,8 @@ impl View {
269 offset.horizontal_offset = 0;
265 offset.horizontal_offset = 0;
270 } else {
266 } else {
271 // determine the current visual column of the text
267 // determine the current visual column of the text
272− let col = visual_off
268+ let col = off
273− .unwrap_or_else(|| {
269+ .unwrap_or_else(|_| {
274 visual_offset_from_block(
270 visual_offset_from_block(
275 doc_text,
271 doc_text,
276 offset.anchor,
272 offset.anchor,
@@ -360,8 +356,9 @@ impl View {
360 );
356 );
361
357
362 match pos {
358 match pos {
363− Some((Position { row, .. }, _)) => row.saturating_sub(self.offset.vertical_offset),
359+ Ok((Position { row, .. }, _)) => row.saturating_sub(self.offset.vertical_offset),
364− None => visual_height.saturating_sub(1),
360+ Err(PosAfterMaxRow) => visual_height.saturating_sub(1),
361+ Err(PosBeforeAnchorRow) => 0,
365 }
362 }
366 }
363 }
367
364
@@ -390,7 +387,8 @@ impl View {
390 &text_fmt,
387 &text_fmt,
391 &annotations,
388 &annotations,
392 viewport.height as usize,
389 viewport.height as usize,
393− )?
390+ )
391+ .ok()?
394 .0;
392 .0;
395 if pos.row < self.offset.vertical_offset {
393 if pos.row < self.offset.vertical_offset {
396 return None;
394 return None;