From 1d0a3d49d38e9d882fed573b836729f5b1410076 Mon Sep 17 00:00:00 2001 From: Ingrid Date: Tue, 23 Jul 2024 19:54:00 +0200 Subject: [PATCH] Consistently maintain view position (#10559) * replicate t-monaghan's changes * remove View.offset in favour of Document.view_data.view_position * improve access patterns for Document.view_data * better borrow checker wrangling with doc_mut!() * reintroduce ensure_cursor_in_view in handle_config_events since we sorted out the borrow checker issues using partial borrows, there's nothing stopping us from going back to the simpler implementation * introduce helper functions on Document .view_offset, set_view_offset * fix rebase breakage --- helix-term/src/application.rs | 6 +-- helix-term/src/commands.rs | 57 +++++++++++++++++----------- helix-term/src/commands/lsp.rs | 3 +- helix-term/src/commands/typed.rs | 2 +- helix-term/src/ui/editor.rs | 21 +++++++---- helix-term/src/ui/mod.rs | 6 +-- helix-view/src/document.rs | 51 ++++++++++++++++++++++--- helix-view/src/editor.rs | 15 ++++---- helix-view/src/lib.rs | 9 +++-- helix-view/src/view.rs | 64 +++++++++++++++++--------------- 10 files changed, 150 insertions(+), 84 deletions(-) diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index b7123e972..60bc5b7cf 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -395,9 +395,9 @@ impl Application { // reset view position in case softwrap was enabled/disabled let scrolloff = self.editor.config().scrolloff; - for (view, _) in self.editor.tree.views_mut() { - let doc = &self.editor.documents[&view.doc]; - view.ensure_cursor_in_view(doc, scrolloff) + for (view, _) in self.editor.tree.views() { + let doc = doc_mut!(self.editor, &view.doc); + view.ensure_cursor_in_view(doc, scrolloff); } } diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 7e0bee92b..2c5d2783b 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -1032,6 +1032,7 @@ fn goto_window(cx: &mut Context, align: Align) { let count = cx.count() - 1; let config = cx.editor.config(); let (view, doc) = current!(cx.editor); + let view_offset = doc.view_offset(view.id); let height = view.inner_height(); @@ -1044,15 +1045,15 @@ fn goto_window(cx: &mut Context, align: Align) { let last_visual_line = view.last_visual_line(doc); let visual_line = match align { - Align::Top => view.offset.vertical_offset + scrolloff + count, - Align::Center => view.offset.vertical_offset + (last_visual_line / 2), + Align::Top => view_offset.vertical_offset + scrolloff + count, + Align::Center => view_offset.vertical_offset + (last_visual_line / 2), Align::Bottom => { - view.offset.vertical_offset + last_visual_line.saturating_sub(scrolloff + count) + view_offset.vertical_offset + last_visual_line.saturating_sub(scrolloff + count) } }; let visual_line = visual_line - .max(view.offset.vertical_offset + scrolloff) - .min(view.offset.vertical_offset + last_visual_line.saturating_sub(scrolloff)); + .max(view_offset.vertical_offset + scrolloff) + .min(view_offset.vertical_offset + last_visual_line.saturating_sub(scrolloff)); let pos = view .pos_at_visual_coords(doc, visual_line as u16, 0, false) @@ -1665,6 +1666,7 @@ pub fn scroll(cx: &mut Context, offset: usize, direction: Direction, sync_cursor use Direction::*; let config = cx.editor.config(); let (view, doc) = current!(cx.editor); + let mut view_offset = doc.view_offset(view.id); let range = doc.selection(view.id).primary(); let text = doc.text().slice(..); @@ -1681,15 +1683,19 @@ pub fn scroll(cx: &mut Context, offset: usize, direction: Direction, sync_cursor let doc_text = doc.text().slice(..); let viewport = view.inner_area(doc); let text_fmt = doc.text_format(viewport.width, None); - let mut annotations = view.text_annotations(&*doc, None); - (view.offset.anchor, view.offset.vertical_offset) = char_idx_at_visual_offset( + (view_offset.anchor, view_offset.vertical_offset) = char_idx_at_visual_offset( doc_text, - view.offset.anchor, - view.offset.vertical_offset as isize + offset, + view_offset.anchor, + view_offset.vertical_offset as isize + offset, 0, &text_fmt, - &annotations, + // &annotations, + &view.text_annotations(&*doc, None), ); + doc.set_view_offset(view.id, view_offset); + + let doc_text = doc.text().slice(..); + let mut annotations = view.text_annotations(&*doc, None); if sync_cursor { let movement = match cx.editor.mode { @@ -1716,14 +1722,16 @@ pub fn scroll(cx: &mut Context, offset: usize, direction: Direction, sync_cursor return; } + let view_offset = doc.view_offset(view.id); + let mut head; match direction { Forward => { let off; (head, off) = char_idx_at_visual_offset( doc_text, - view.offset.anchor, - (view.offset.vertical_offset + scrolloff) as isize, + view_offset.anchor, + (view_offset.vertical_offset + scrolloff) as isize, 0, &text_fmt, &annotations, @@ -1736,8 +1744,8 @@ pub fn scroll(cx: &mut Context, offset: usize, direction: Direction, sync_cursor Backward => { head = char_idx_at_visual_offset( doc_text, - view.offset.anchor, - (view.offset.vertical_offset + height - scrolloff - 1) as isize, + view_offset.anchor, + (view_offset.vertical_offset + height - scrolloff - 1) as isize, 0, &text_fmt, &annotations, @@ -5124,7 +5132,7 @@ fn split(editor: &mut Editor, action: Action) { let (view, doc) = current!(editor); let id = doc.id(); let selection = doc.selection(view.id).clone(); - let offset = view.offset; + let offset = doc.view_offset(view.id); editor.switch(id, action); @@ -5133,7 +5141,7 @@ fn split(editor: &mut Editor, action: Action) { doc.set_selection(view.id, selection); // match the view scroll offset (switch doesn't handle this fully // since the selection is only matched after the split) - view.offset = offset; + doc.set_view_offset(view.id, offset); } fn hsplit(cx: &mut Context) { @@ -5228,14 +5236,21 @@ fn align_view_middle(cx: &mut Context) { return; } let doc_text = doc.text().slice(..); - let annotations = view.text_annotations(doc, None); let pos = doc.selection(view.id).primary().cursor(doc_text); - let pos = - visual_offset_from_block(doc_text, view.offset.anchor, pos, &text_fmt, &annotations).0; + let pos = visual_offset_from_block( + doc_text, + doc.view_offset(view.id).anchor, + pos, + &text_fmt, + &view.text_annotations(doc, None), + ) + .0; - view.offset.horizontal_offset = pos + let mut offset = doc.view_offset(view.id); + offset.horizontal_offset = pos .col .saturating_sub((view.inner_area(doc).width as usize) / 2); + doc.set_view_offset(view.id, offset); } fn scroll_up(cx: &mut Context) { @@ -6117,7 +6132,7 @@ fn jump_to_word(cx: &mut Context, behaviour: Movement) { // This is not necessarily exact if there is virtual text like soft wrap. // It's ok though because the extra jump labels will not be rendered. - let start = text.line_to_char(text.char_to_line(view.offset.anchor)); + let start = text.line_to_char(text.char_to_line(doc.view_offset(view.id).anchor)); let end = text.line_to_char(view.estimate_last_doc_line(doc) + 1); let primary_selection = doc.selection(view.id).primary(); diff --git a/helix-term/src/commands/lsp.rs b/helix-term/src/commands/lsp.rs index 3b9efb431..9194377c4 100644 --- a/helix-term/src/commands/lsp.rs +++ b/helix-term/src/commands/lsp.rs @@ -1299,7 +1299,8 @@ fn compute_inlay_hints_for_view( // than computing all the hints for the full file (which could be dozens of time // longer than the view is). let view_height = view.inner_height(); - let first_visible_line = doc_text.char_to_line(view.offset.anchor.min(doc_text.len_chars())); + let first_visible_line = + doc_text.char_to_line(doc.view_offset(view_id).anchor.min(doc_text.len_chars())); let first_line = first_visible_line.saturating_sub(view_height); let last_line = first_visible_line .saturating_add(view_height.saturating_mul(2)) diff --git a/helix-term/src/commands/typed.rs b/helix-term/src/commands/typed.rs index 530d78097..720d32ac8 100644 --- a/helix-term/src/commands/typed.rs +++ b/helix-term/src/commands/typed.rs @@ -1587,7 +1587,7 @@ fn tree_sitter_highlight_name( // Query the same range as the one used in syntax highlighting. let range = { // Calculate viewport byte ranges: - let row = text.char_to_line(view.offset.anchor.min(text.len_chars())); + let row = text.char_to_line(doc.view_offset(view.id).anchor.min(text.len_chars())); // Saturating subs to make it inclusive zero indexing. let last_line = text.len_lines().saturating_sub(1); let height = view.inner_area(doc).height; diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index c151a7dd5..f7541fe25 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -93,6 +93,8 @@ impl EditorView { let theme = &editor.theme; let config = editor.config(); + let view_offset = doc.view_offset(view.id); + let text_annotations = view.text_annotations(doc, Some(theme)); let mut decorations = DecorationManager::default(); @@ -119,13 +121,13 @@ impl EditorView { } let syntax_highlights = - Self::doc_syntax_highlights(doc, view.offset.anchor, inner.height, theme); + Self::doc_syntax_highlights(doc, view_offset.anchor, inner.height, theme); let mut overlay_highlights = - Self::empty_highlight_iter(doc, view.offset.anchor, inner.height); + Self::empty_highlight_iter(doc, view_offset.anchor, inner.height); let overlay_syntax_highlights = Self::overlay_syntax_highlights( doc, - view.offset.anchor, + view_offset.anchor, inner.height, &text_annotations, ); @@ -203,7 +205,7 @@ impl EditorView { surface, inner, doc, - view.offset, + view_offset, &text_annotations, syntax_highlights, overlay_highlights, @@ -259,11 +261,13 @@ impl EditorView { .and_then(|config| config.rulers.as_ref()) .unwrap_or(editor_rulers); + let view_offset = doc.view_offset(view.id); + rulers .iter() // View might be horizontally scrolled, convert from absolute distance // from the 1st column to relative distance from left of viewport - .filter_map(|ruler| ruler.checked_sub(1 + view.offset.horizontal_offset as u16)) + .filter_map(|ruler| ruler.checked_sub(1 + view_offset.horizontal_offset as u16)) .filter(|ruler| ruler < &viewport.width) .map(|ruler| viewport.clip_left(ruler).with_width(1)) .for_each(|area| surface.set_style(area, ruler_theme)) @@ -825,6 +829,7 @@ impl EditorView { let inner_area = view.inner_area(doc); let selection = doc.selection(view.id); + let view_offset = doc.view_offset(view.id); let primary = selection.primary(); let text_format = doc.text_format(viewport.width, None); for range in selection.iter() { @@ -835,11 +840,11 @@ impl EditorView { visual_offset_from_block(text, cursor, cursor, &text_format, text_annotations).0; // if the cursor is horizontally in the view - if col >= view.offset.horizontal_offset - && inner_area.width > (col - view.offset.horizontal_offset) as u16 + if col >= view_offset.horizontal_offset + && inner_area.width > (col - view_offset.horizontal_offset) as u16 { let area = Rect::new( - inner_area.x + (col - view.offset.horizontal_offset) as u16, + inner_area.x + (col - view_offset.horizontal_offset) as u16, view.area.y, 1, view.area.height, diff --git a/helix-term/src/ui/mod.rs b/helix-term/src/ui/mod.rs index 10f4104ab..6a3e198c1 100644 --- a/helix-term/src/ui/mod.rs +++ b/helix-term/src/ui/mod.rs @@ -83,7 +83,7 @@ pub fn raw_regex_prompt( let (view, doc) = current!(cx.editor); let doc_id = view.doc; let snapshot = doc.selection(view.id).clone(); - let offset_snapshot = view.offset; + let offset_snapshot = doc.view_offset(view.id); let config = cx.editor.config(); let mut prompt = Prompt::new( @@ -95,7 +95,7 @@ pub fn raw_regex_prompt( PromptEvent::Abort => { let (view, doc) = current!(cx.editor); doc.set_selection(view.id, snapshot.clone()); - view.offset = offset_snapshot; + doc.set_view_offset(view.id, offset_snapshot); } PromptEvent::Update | PromptEvent::Validate => { // skip empty input @@ -136,7 +136,7 @@ pub fn raw_regex_prompt( Err(err) => { let (view, doc) = current!(cx.editor); doc.set_selection(view.id, snapshot.clone()); - view.offset = offset_snapshot; + doc.set_view_offset(view.id, offset_snapshot); if event == PromptEvent::Validate { let callback = async move { diff --git a/helix-view/src/document.rs b/helix-view/src/document.rs index f3ace89e5..532f4c344 100644 --- a/helix-view/src/document.rs +++ b/helix-view/src/document.rs @@ -37,9 +37,12 @@ use helix_core::{ ChangeSet, Diagnostic, LineEnding, Range, Rope, RopeBuilder, Selection, Syntax, Transaction, }; -use crate::editor::Config; -use crate::events::{DocumentDidChange, SelectionDidChange}; -use crate::{DocumentId, Editor, Theme, View, ViewId}; +use crate::{ + editor::Config, + events::{DocumentDidChange, SelectionDidChange}, + view::ViewPosition, + DocumentId, Editor, Theme, View, ViewId, +}; /// 8kB of buffer space for encoding and decoding `Rope`s. const BUF_SIZE: usize = 8192; @@ -130,6 +133,7 @@ pub struct Document { pub(crate) id: DocumentId, text: Rope, selections: HashMap, + view_data: HashMap, /// Inlay hints annotations for the document, by view. /// @@ -265,6 +269,7 @@ impl fmt::Debug for Document { .field("selections", &self.selections) .field("inlay_hints_oudated", &self.inlay_hints_oudated) .field("text_annotations", &self.inlay_hints) + .field("view_data", &self.view_data) .field("path", &self.path) .field("encoding", &self.encoding) .field("restore_cursor", &self.restore_cursor) @@ -656,6 +661,7 @@ impl Document { selections: HashMap::default(), inlay_hints: HashMap::default(), inlay_hints_oudated: false, + view_data: Default::default(), indent_style: DEFAULT_INDENT, line_ending, restore_cursor: false, @@ -1184,12 +1190,14 @@ impl Document { self.set_selection(view_id, Selection::single(origin.anchor, origin.head)); } - /// Initializes a new selection for the given view if it does not - /// already have one. + /// Initializes a new selection and view_data for the given view + /// if it does not already have them. pub fn ensure_view_init(&mut self, view_id: ViewId) { if self.selections.get(&view_id).is_none() { self.reset_selection(view_id); } + + self.view_data_mut(view_id); } /// Mark document as recent used for MRU sorting @@ -1235,6 +1243,12 @@ impl Document { .ensure_invariants(self.text.slice(..)); } + for view_data in self.view_data.values_mut() { + view_data.view_position.anchor = transaction + .changes() + .map_pos(view_data.view_position.anchor, Assoc::Before); + } + // if specified, the current selection should instead be replaced by transaction.selection if let Some(selection) = transaction.selection() { self.selections.insert( @@ -1759,6 +1773,28 @@ impl Document { &self.selections } + fn view_data(&self, view_id: ViewId) -> &ViewData { + self.view_data + .get(&view_id) + .expect("This should only be called after ensure_view_init") + } + + fn view_data_mut(&mut self, view_id: ViewId) -> &mut ViewData { + self.view_data.entry(view_id).or_default() + } + + pub(crate) fn get_view_offset(&self, view_id: ViewId) -> Option { + Some(self.view_data.get(&view_id)?.view_position) + } + + pub fn view_offset(&self, view_id: ViewId) -> ViewPosition { + self.view_data(view_id).view_position + } + + pub fn set_view_offset(&mut self, view_id: ViewId, new_offset: ViewPosition) { + self.view_data_mut(view_id).view_position = new_offset; + } + pub fn relative_path(&self) -> Option> { self.path .as_deref() @@ -2034,6 +2070,11 @@ impl Document { } } +#[derive(Debug, Default)] +pub struct ViewData { + view_position: ViewPosition, +} + #[derive(Clone, Debug)] pub enum FormatterError { SpawningFailed { diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index cead30d7c..ba7337f22 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -1,5 +1,4 @@ use crate::{ - align_view, annotations::diagnostics::{DiagnosticFilter, InlineDiagnosticsConfig}, document::{ DocumentOpenError, DocumentSavedEventFuture, DocumentSavedEventResult, Mode, SavePoint, @@ -11,8 +10,7 @@ use crate::{ register::Registers, theme::{self, Theme}, tree::{self, Tree}, - view::ViewPosition, - Align, Document, DocumentId, View, ViewId, + Document, DocumentId, View, ViewId, }; use dap::StackFrame; use helix_vcs::DiffProviderRegistry; @@ -1530,16 +1528,17 @@ impl Editor { } fn replace_document_in_view(&mut self, current_view: ViewId, doc_id: DocumentId) { + let scrolloff = self.config().scrolloff; let view = self.tree.get_mut(current_view); - view.doc = doc_id; - view.offset = ViewPosition::default(); + view.doc = doc_id; let doc = doc_mut!(self, &doc_id); + doc.ensure_view_init(view.id); view.sync_changes(doc); doc.mark_as_focused(); - align_view(doc, view, Align::Center); + view.ensure_cursor_in_view(doc, scrolloff) } pub fn switch(&mut self, id: DocumentId, action: Action) { @@ -1899,8 +1898,8 @@ impl Editor { pub fn ensure_cursor_in_view(&mut self, id: ViewId) { let config = self.config(); - let view = self.tree.get_mut(id); - let doc = &self.documents[&view.doc]; + let view = self.tree.get(id); + let doc = doc_mut!(self, &view.doc); view.ensure_cursor_in_view(doc, config.scrolloff) } diff --git a/helix-view/src/lib.rs b/helix-view/src/lib.rs index 5628c830c..d54b49ef5 100644 --- a/helix-view/src/lib.rs +++ b/helix-view/src/lib.rs @@ -47,11 +47,12 @@ pub enum Align { Bottom, } -pub fn align_view(doc: &Document, view: &mut View, align: Align) { +pub fn align_view(doc: &mut Document, view: &View, align: Align) { let doc_text = doc.text().slice(..); let cursor = doc.selection(view.id).primary().cursor(doc_text); let viewport = view.inner_area(doc); let last_line_height = viewport.height.saturating_sub(1); + let mut view_offset = doc.view_offset(view.id); let relative = match align { Align::Center => last_line_height / 2, @@ -60,15 +61,15 @@ pub fn align_view(doc: &Document, view: &mut View, align: Align) { }; let text_fmt = doc.text_format(viewport.width, None); - let annotations = view.text_annotations(doc, None); - (view.offset.anchor, view.offset.vertical_offset) = char_idx_at_visual_offset( + (view_offset.anchor, view_offset.vertical_offset) = char_idx_at_visual_offset( doc_text, cursor, -(relative as isize), 0, &text_fmt, - &annotations, + &view.text_annotations(doc, None), ); + doc.set_view_offset(view.id, view_offset); } pub use document::Document; diff --git a/helix-view/src/view.rs b/helix-view/src/view.rs index af4fdfe4e..fb83c4b86 100644 --- a/helix-view/src/view.rs +++ b/helix-view/src/view.rs @@ -128,7 +128,6 @@ pub struct ViewPosition { #[derive(Clone)] pub struct View { pub id: ViewId, - pub offset: ViewPosition, pub area: Rect, pub doc: DocumentId, pub jumps: JumpList, @@ -173,11 +172,6 @@ impl View { Self { id: ViewId::default(), doc, - offset: ViewPosition { - anchor: 0, - horizontal_offset: 0, - vertical_offset: 0, - }, area: Rect::default(), // will get calculated upon inserting into tree jumps: JumpList::new((doc, Selection::point(0))), // TODO: use actual sel docs_access_history: Vec::new(), @@ -240,9 +234,10 @@ impl View { doc: &Document, scrolloff: usize, ) -> Option { + let view_offset = doc.get_view_offset(self.id)?; let doc_text = doc.text().slice(..); let viewport = self.inner_area(doc); - let vertical_viewport_end = self.offset.vertical_offset + viewport.height as usize; + let vertical_viewport_end = view_offset.vertical_offset + viewport.height as usize; let text_fmt = doc.text_format(viewport.width, None); let annotations = self.text_annotations(doc, None); @@ -256,7 +251,7 @@ impl View { }; let cursor = doc.selection(self.id).primary().cursor(doc_text); - let mut offset = self.offset; + let mut offset = view_offset; let off = visual_offset_from_anchor( doc_text, offset.anchor, @@ -321,22 +316,22 @@ impl View { } // if we are not centering return None if view position is unchanged - if !CENTERING && offset == self.offset { + if !CENTERING && offset == view_offset { return None; } Some(offset) } - pub fn ensure_cursor_in_view(&mut self, doc: &Document, scrolloff: usize) { + pub fn ensure_cursor_in_view(&self, doc: &mut Document, scrolloff: usize) { if let Some(offset) = self.offset_coords_to_in_view_center::(doc, scrolloff) { - self.offset = offset; + doc.set_view_offset(self.id, offset); } } - pub fn ensure_cursor_in_view_center(&mut self, doc: &Document, scrolloff: usize) { + pub fn ensure_cursor_in_view_center(&self, doc: &mut Document, scrolloff: usize) { if let Some(offset) = self.offset_coords_to_in_view_center::(doc, scrolloff) { - self.offset = offset; + doc.set_view_offset(self.id, offset); } else { align_view(doc, self, Align::Center); } @@ -354,7 +349,7 @@ impl View { #[inline] pub fn estimate_last_doc_line(&self, doc: &Document) -> usize { let doc_text = doc.text().slice(..); - let line = doc_text.char_to_line(self.offset.anchor.min(doc_text.len_chars())); + let line = doc_text.char_to_line(doc.view_offset(self.id).anchor.min(doc_text.len_chars())); // Saturating subs to make it inclusive zero indexing. (line + self.inner_height()) .min(doc_text.len_lines()) @@ -368,9 +363,10 @@ impl View { let viewport = self.inner_area(doc); let text_fmt = doc.text_format(viewport.width, None); let annotations = self.text_annotations(doc, None); + let view_offset = doc.view_offset(self.id); // last visual line in view is trivial to compute - let visual_height = self.offset.vertical_offset + viewport.height as usize; + let visual_height = doc.view_offset(self.id).vertical_offset + viewport.height as usize; // fast path when the EOF is not visible on the screen, if self.estimate_last_doc_line(doc) < doc_text.len_lines() - 1 { @@ -380,7 +376,7 @@ impl View { // translate to document line let pos = visual_offset_from_anchor( doc_text, - self.offset.anchor, + view_offset.anchor, usize::MAX, &text_fmt, &annotations, @@ -388,7 +384,7 @@ impl View { ); match pos { - Ok((Position { row, .. }, _)) => row.saturating_sub(self.offset.vertical_offset), + Ok((Position { row, .. }, _)) => row.saturating_sub(view_offset.vertical_offset), Err(PosAfterMaxRow) => visual_height.saturating_sub(1), Err(PosBeforeAnchorRow) => 0, } @@ -403,13 +399,15 @@ impl View { text: RopeSlice, pos: usize, ) -> Option { + let view_offset = doc.view_offset(self.id); + let viewport = self.inner_area(doc); let text_fmt = doc.text_format(viewport.width, None); let annotations = self.text_annotations(doc, None); let mut pos = visual_offset_from_anchor( text, - self.offset.anchor, + view_offset.anchor, pos, &text_fmt, &annotations, @@ -417,14 +415,14 @@ impl View { ) .ok()? .0; - if pos.row < self.offset.vertical_offset { + if pos.row < view_offset.vertical_offset { return None; } - pos.row -= self.offset.vertical_offset; + pos.row -= view_offset.vertical_offset; if pos.row >= viewport.height as usize { return None; } - pos.col = pos.col.saturating_sub(self.offset.horizontal_offset); + pos.col = pos.col.saturating_sub(view_offset.horizontal_offset); Some(pos) } @@ -488,7 +486,7 @@ impl View { doc, cursor, width, - self.offset.horizontal_offset, + doc.view_offset(self.id).horizontal_offset, config, )); } @@ -535,13 +533,14 @@ impl View { ignore_virtual_text: bool, ) -> Option { let text = doc.text().slice(..); + let view_offset = doc.view_offset(self.id); - let text_row = row as usize + self.offset.vertical_offset; - let text_col = column as usize + self.offset.horizontal_offset; + let text_row = row as usize + view_offset.vertical_offset; + let text_col = column as usize + view_offset.horizontal_offset; let (char_idx, virt_lines) = char_idx_at_visual_offset( text, - self.offset.anchor, + view_offset.anchor, text_row as isize, text_col, &text_fmt, @@ -689,11 +688,12 @@ mod tests { let mut view = View::new(DocumentId::default(), GutterConfig::default()); view.area = Rect::new(40, 40, 40, 40); let rope = Rope::from_str("abc\n\tdef"); - let doc = Document::from( + let mut doc = Document::from( rope, None, Arc::new(ArcSwap::new(Arc::new(Config::default()))), ); + doc.ensure_view_init(view.id); assert_eq!( view.text_pos_at_screen_coords( @@ -863,11 +863,12 @@ mod tests { ); view.area = Rect::new(40, 40, 40, 40); let rope = Rope::from_str("abc\n\tdef"); - let doc = Document::from( + let mut doc = Document::from( rope, None, Arc::new(ArcSwap::new(Arc::new(Config::default()))), ); + doc.ensure_view_init(view.id); assert_eq!( view.text_pos_at_screen_coords( &doc, @@ -892,11 +893,12 @@ mod tests { ); view.area = Rect::new(40, 40, 40, 40); let rope = Rope::from_str("abc\n\tdef"); - let doc = Document::from( + let mut doc = Document::from( rope, None, Arc::new(ArcSwap::new(Arc::new(Config::default()))), ); + doc.ensure_view_init(view.id); assert_eq!( view.text_pos_at_screen_coords( &doc, @@ -915,11 +917,12 @@ mod tests { let mut view = View::new(DocumentId::default(), GutterConfig::default()); view.area = Rect::new(40, 40, 40, 40); let rope = Rope::from_str("Hi! こんにちは皆さん"); - let doc = Document::from( + let mut doc = Document::from( rope, None, Arc::new(ArcSwap::new(Arc::new(Config::default()))), ); + doc.ensure_view_init(view.id); assert_eq!( view.text_pos_at_screen_coords( @@ -998,11 +1001,12 @@ mod tests { let mut view = View::new(DocumentId::default(), GutterConfig::default()); view.area = Rect::new(40, 40, 40, 40); let rope = Rope::from_str("Hèl̀l̀ò world!"); - let doc = Document::from( + let mut doc = Document::from( rope, None, Arc::new(ArcSwap::new(Arc::new(Config::default()))), ); + doc.ensure_view_init(view.id); assert_eq!( view.text_pos_at_screen_coords(