Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'helix-view/src/view.rs')
| -rw-r--r-- | helix-view/src/view.rs | 849 |
1 files changed, 162 insertions, 687 deletions
diff --git a/helix-view/src/view.rs b/helix-view/src/view.rs index aecf09a6..c09d502d 100644 --- a/helix-view/src/view.rs +++ b/helix-view/src/view.rs @@ -1,20 +1,6 @@ -use crate::{ - align_view, - annotations::diagnostics::InlineDiagnostics, - document::{DocumentColorSwatches, DocumentInlayHints}, - editor::{GutterConfig, GutterType}, - graphics::Rect, - handlers::diagnostics::DiagnosticsHandler, - Align, Document, DocumentId, Theme, ViewId, -}; - +use crate::{align_view, editor::GutterType, graphics::Rect, Align, Document, DocumentId, ViewId}; use helix_core::{ - char_idx_at_visual_offset, - doc_formatter::TextFormat, - text_annotations::TextAnnotations, - visual_offset_from_anchor, visual_offset_from_block, Position, RopeSlice, Selection, - Transaction, - VisualOffsetError::{PosAfterMaxRow, PosBeforeAnchorRow}, + pos_at_visual_coords, visual_coords_at_pos, Position, RopeSlice, Selection, Transaction, }; use std::{ @@ -39,25 +25,18 @@ impl JumpList { Self { jumps, current: 0 } } - fn push_impl(&mut self, jump: Jump) -> usize { - let mut num_removed_from_front = 0; + pub fn push(&mut self, jump: Jump) { self.jumps.truncate(self.current); // don't push duplicates if self.jumps.back() != Some(&jump) { // If the jumplist is full, drop the oldest item. while self.jumps.len() >= JUMP_LIST_CAPACITY { self.jumps.pop_front(); - num_removed_from_front += 1; } self.jumps.push_back(jump); self.current = self.jumps.len(); } - num_removed_from_front - } - - pub fn push(&mut self, jump: Jump) { - self.push_impl(jump); } pub fn forward(&mut self, count: usize) -> Option<&Jump> { @@ -71,22 +50,13 @@ impl JumpList { // Taking view and doc to prevent unnecessary cloning when jump is not required. pub fn backward(&mut self, view_id: ViewId, doc: &mut Document, count: usize) -> Option<&Jump> { - if let Some(mut current) = self.current.checked_sub(count) { + if let Some(current) = self.current.checked_sub(count) { if self.current == self.jumps.len() { let jump = (doc.id(), doc.selection(view_id).clone()); - let num_removed = self.push_impl(jump); - current = current.saturating_sub(num_removed); + self.push(jump); } self.current = current; - - // Avoid jumping to the current location. - let jump @ (doc_id, selection) = self.jumps.get(self.current)?; - if doc.id() == *doc_id && doc.selection(view_id) == selection { - self.current = self.current.checked_sub(1)?; - self.jumps.get(self.current) - } else { - Some(jump) - } + self.jumps.get(self.current) } else { None } @@ -96,7 +66,7 @@ impl JumpList { self.jumps.retain(|(other_id, _)| other_id != doc_id); } - pub fn iter(&self) -> impl DoubleEndedIterator<Item = &Jump> { + pub fn iter(&self) -> impl Iterator<Item = &Jump> { self.jumps.iter() } @@ -117,16 +87,10 @@ impl JumpList { } } -#[derive(Clone, Debug, PartialEq, Eq, Copy, Default)] -pub struct ViewPosition { - pub anchor: usize, - pub horizontal_offset: usize, - pub vertical_offset: usize, -} - #[derive(Clone)] pub struct View { pub id: ViewId, + pub offset: Position, pub area: Rect, pub doc: DocumentId, pub jumps: JumpList, @@ -139,21 +103,13 @@ pub struct View { pub last_modified_docs: [Option<DocumentId>; 2], /// used to store previous selections of tree-sitter objects pub object_selections: Vec<Selection>, - /// all gutter-related configuration settings, used primarily for gutter rendering - pub gutters: GutterConfig, + /// GutterTypes used to fetch Gutter (constructor) and width for rendering + gutters: Vec<GutterType>, /// A mapping between documents and the last history revision the view was updated at. /// Changes between documents and views are synced lazily when switching windows. This /// mapping keeps track of the last applied history revision so that only new changes /// are applied. doc_revisions: HashMap<DocumentId, usize>, - // HACKS: there should really only be a global diagnostics handler (the - // non-focused views should just not have different handling for the cursor - // line). For that we would need accces to editor everywhere (we want to use - // the positioning code) so this can only happen by refactoring View and - // Document into entity component like structure. That is a huge refactor - // left to future work. For now we treat all views as focused and give them - // each their own handler. - pub diagnostics_handler: DiagnosticsHandler, } impl fmt::Debug for View { @@ -167,18 +123,18 @@ impl fmt::Debug for View { } impl View { - pub fn new(doc: DocumentId, gutters: GutterConfig) -> Self { + pub fn new(doc: DocumentId, gutter_types: Vec<crate::editor::GutterType>) -> Self { Self { id: ViewId::default(), doc, + offset: Position::new(0, 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(), last_modified_docs: [None, None], object_selections: Vec::new(), - gutters, + gutters: gutter_types, doc_revisions: HashMap::new(), - diagnostics_handler: DiagnosticsHandler::new(), } } @@ -197,26 +153,15 @@ impl View { self.area.clip_bottom(1).height.into() // -1 for statusline } - pub fn inner_width(&self, doc: &Document) -> u16 { - self.area.clip_left(self.gutter_offset(doc)).width - } - pub fn gutters(&self) -> &[GutterType] { - &self.gutters.layout + &self.gutters } pub fn gutter_offset(&self, doc: &Document) -> u16 { - let total_width = self - .gutters - .layout + self.gutters .iter() .map(|gutter| gutter.width(self, doc) as u16) - .sum(); - if total_width < self.area.width { - total_width - } else { - 0 - } + .sum() } // @@ -224,123 +169,84 @@ impl View { &self, doc: &Document, scrolloff: usize, - ) -> Option<ViewPosition> { - self.offset_coords_to_in_view_center::<false>(doc, scrolloff) + ) -> Option<(usize, usize)> { + self.offset_coords_to_in_view_center(doc, scrolloff, false) } - pub fn offset_coords_to_in_view_center<const CENTERING: bool>( + pub fn offset_coords_to_in_view_center( &self, doc: &Document, scrolloff: usize, - ) -> Option<ViewPosition> { - 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 = view_offset.vertical_offset + viewport.height as usize; - let text_fmt = doc.text_format(viewport.width, None); - let annotations = self.text_annotations(doc, None); - - let (scrolloff_top, scrolloff_bottom) = if CENTERING { - (0, 0) - } else { - ( - // - 1 from the top so we have at least one gap in the middle. - scrolloff.min(viewport.height.saturating_sub(1) as usize / 2), - scrolloff.min(viewport.height as usize / 2), - ) - }; - let (scrolloff_left, scrolloff_right) = if CENTERING { - (0, 0) - } else { - ( - // - 1 from the left so we have at least one gap in the middle. - scrolloff.min(viewport.width.saturating_sub(1) as usize / 2), - scrolloff.min(viewport.width as usize / 2), - ) - }; - - let cursor = doc.selection(self.id).primary().cursor(doc_text); - let mut offset = view_offset; - let off = visual_offset_from_anchor( - doc_text, - offset.anchor, - cursor, - &text_fmt, - &annotations, - vertical_viewport_end, - ); - - let (new_anchor, at_top) = match off { - Ok((visual_pos, _)) if visual_pos.row < scrolloff_top + offset.vertical_offset => { - if CENTERING { - // cursor out of view - return None; - } - (true, true) - } - Ok((visual_pos, _)) if visual_pos.row + scrolloff_bottom >= vertical_viewport_end => { - (true, false) - } - Ok((_, _)) => (false, false), - Err(_) if CENTERING => return None, - Err(PosBeforeAnchorRow) => (true, true), - Err(PosAfterMaxRow) => (true, false), - }; - - if new_anchor { - let v_off = if at_top { - scrolloff_top as isize + centering: bool, + ) -> Option<(usize, usize)> { + let cursor = doc + .selection(self.id) + .primary() + .cursor(doc.text().slice(..)); + + let Position { col, row: line } = + visual_coords_at_pos(doc.text().slice(..), cursor, doc.tab_width()); + + let inner_area = self.inner_area(doc); + let last_line = (self.offset.row + inner_area.height as usize).saturating_sub(1); + let last_col = self.offset.col + inner_area.width.saturating_sub(1) as usize; + + let new_offset = |scrolloff: usize| { + // - 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(inner_area.height.saturating_sub(1) as usize / 2); + + let row = if line > last_line.saturating_sub(scrolloff) { + // scroll down + self.offset.row + line - (last_line.saturating_sub(scrolloff)) + } else if line < self.offset.row + scrolloff { + // scroll up + line.saturating_sub(scrolloff) } else { - viewport.height as isize - scrolloff_bottom as isize - 1 + self.offset.row }; - (offset.anchor, offset.vertical_offset) = - char_idx_at_visual_offset(doc_text, cursor, -v_off, 0, &text_fmt, &annotations); - } - if text_fmt.soft_wrap { - offset.horizontal_offset = 0; - } else { - // determine the current visual column of the text - let col = off - .unwrap_or_else(|_| { - visual_offset_from_block( - doc_text, - offset.anchor, - cursor, - &text_fmt, - &annotations, - ) - }) - .0 - .col; - - let last_col = offset.horizontal_offset + viewport.width.saturating_sub(1) as usize; - if col > last_col.saturating_sub(scrolloff_right) { + let col = if col > last_col.saturating_sub(scrolloff) { // scroll right - offset.horizontal_offset += col - (last_col.saturating_sub(scrolloff_right)) - } else if col < offset.horizontal_offset + scrolloff_left { + self.offset.col + col - (last_col.saturating_sub(scrolloff)) + } else if col < self.offset.col + scrolloff { // scroll left - offset.horizontal_offset = col.saturating_sub(scrolloff_left) + col.saturating_sub(scrolloff) + } else { + self.offset.col }; + (row, col) + }; + let current_offset = (self.offset.row, self.offset.col); + if centering { + // return None if cursor is out of view + let offset = new_offset(0); + (offset == current_offset).then(|| { + if scrolloff == 0 { + offset + } else { + new_offset(scrolloff) + } + }) + } else { + // return None if cursor is in (view - scrolloff) + let offset = new_offset(scrolloff); + (offset != current_offset).then(|| offset) // TODO: use 'then_some' when 1.62 <= MSRV } - - // if we are not centering return None if view position is unchanged - if !CENTERING && offset == view_offset { - return None; - } - - Some(offset) } - pub fn ensure_cursor_in_view(&self, doc: &mut Document, scrolloff: usize) { - if let Some(offset) = self.offset_coords_to_in_view_center::<false>(doc, scrolloff) { - doc.set_view_offset(self.id, offset); + pub fn ensure_cursor_in_view(&mut self, doc: &Document, scrolloff: usize) { + if let Some((row, col)) = self.offset_coords_to_in_view_center(doc, scrolloff, false) { + self.offset.row = row; + self.offset.col = col; } } - pub fn ensure_cursor_in_view_center(&self, doc: &mut Document, scrolloff: usize) { - if let Some(offset) = self.offset_coords_to_in_view_center::<true>(doc, scrolloff) { - doc.set_view_offset(self.id, offset); + pub fn ensure_cursor_in_view_center(&mut self, doc: &Document, scrolloff: usize) { + if let Some((row, col)) = self.offset_coords_to_in_view_center(doc, scrolloff, true) { + self.offset.row = row; + self.offset.col = col; } else { align_view(doc, self, Align::Center); } @@ -350,53 +256,14 @@ impl View { self.offset_coords_to_in_view(doc, scrolloff).is_none() } - /// Estimates the last visible document line on screen. - /// This estimate is an upper bound obtained by calculating the first - /// visible line and adding the viewport height. - /// The actual last visible line may be smaller if softwrapping occurs - /// or virtual text lines are visible + /// Calculates the last visible line on screen #[inline] - pub fn estimate_last_doc_line(&self, doc: &Document) -> usize { - let doc_text = doc.text().slice(..); - 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()) - .saturating_sub(1) - } - - /// Calculates the last non-empty visual line on screen - #[inline] - pub fn last_visual_line(&self, doc: &Document) -> usize { - let doc_text = doc.text().slice(..); - 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 = 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 { - return visual_height.saturating_sub(1); - } - - // translate to document line - let pos = visual_offset_from_anchor( - doc_text, - view_offset.anchor, - usize::MAX, - &text_fmt, - &annotations, - visual_height, - ); - - match pos { - Ok((Position { row, .. }, _)) => row.saturating_sub(view_offset.vertical_offset), - Err(PosAfterMaxRow) => visual_height.saturating_sub(1), - Err(PosBeforeAnchorRow) => 0, - } + pub fn last_line(&self, doc: &Document) -> usize { + std::cmp::min( + // Saturating subs to make it inclusive zero indexing. + (self.offset.row + self.inner_height()).saturating_sub(1), + doc.text().len_lines().saturating_sub(1), + ) } /// Translates a document position to an absolute position in the terminal. @@ -408,109 +275,22 @@ impl View { text: RopeSlice, pos: usize, ) -> Option<Position> { - let view_offset = doc.view_offset(self.id); + let line = text.char_to_line(pos); - 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, - view_offset.anchor, - pos, - &text_fmt, - &annotations, - viewport.height as usize, - ) - .ok()? - .0; - if pos.row < view_offset.vertical_offset { - return None; - } - pos.row -= view_offset.vertical_offset; - if pos.row >= viewport.height as usize { + if line < self.offset.row || line > self.last_line(doc) { + // Line is not visible on screen return None; } - pos.col = pos.col.saturating_sub(view_offset.horizontal_offset); - - Some(pos) - } - - /// Get the text annotations to display in the current view for the given document and theme. - pub fn text_annotations<'a>( - &self, - doc: &'a Document, - theme: Option<&Theme>, - ) -> TextAnnotations<'a> { - let mut text_annotations = TextAnnotations::default(); - - if let Some(labels) = doc.jump_labels.get(&self.id) { - let style = theme.and_then(|t| t.find_highlight("ui.virtual.jump-label")); - text_annotations.add_overlay(labels, style); - } - - if let Some(DocumentInlayHints { - id: _, - type_inlay_hints, - parameter_inlay_hints, - other_inlay_hints, - padding_before_inlay_hints, - padding_after_inlay_hints, - }) = doc.inlay_hints.get(&self.id) - { - let type_style = theme.and_then(|t| t.find_highlight("ui.virtual.inlay-hint.type")); - let parameter_style = - theme.and_then(|t| t.find_highlight("ui.virtual.inlay-hint.parameter")); - let other_style = theme.and_then(|t| t.find_highlight("ui.virtual.inlay-hint")); - - // Overlapping annotations are ignored apart from the first so the order here is not random: - // types -> parameters -> others should hopefully be the "correct" order for most use cases, - // with the padding coming before and after as expected. - text_annotations - .add_inline_annotations(padding_before_inlay_hints, None) - .add_inline_annotations(type_inlay_hints, type_style) - .add_inline_annotations(parameter_inlay_hints, parameter_style) - .add_inline_annotations(other_inlay_hints, other_style) - .add_inline_annotations(padding_after_inlay_hints, None); - }; - let config = doc.config.load(); - - if config.lsp.display_color_swatches { - if let Some(DocumentColorSwatches { - color_swatches, - colors, - color_swatches_padding, - }) = &doc.color_swatches - { - for (color_swatch, color) in color_swatches.iter().zip(colors) { - text_annotations - .add_inline_annotations(std::slice::from_ref(color_swatch), Some(*color)); - } - text_annotations.add_inline_annotations(color_swatches_padding, None); - } - } + let tab_width = doc.tab_width(); + // TODO: visual_coords_at_pos also does char_to_line which we ignore, can we reuse the call? + let Position { col, .. } = visual_coords_at_pos(text, pos, tab_width); - let width = self.inner_width(doc); - let enable_cursor_line = self - .diagnostics_handler - .show_cursorline_diagnostics(doc, self.id); - let config = config.inline_diagnostics.prepare(width, enable_cursor_line); - if !config.disabled() { - let cursor = doc - .selection(self.id) - .primary() - .cursor(doc.text().slice(..)); - text_annotations.add_line_annotation(InlineDiagnostics::new( - doc, - cursor, - width, - doc.view_offset(self.id).horizontal_offset, - config, - )); - } + // It is possible for underflow to occur if the buffer length is larger than the terminal width. + let row = line.saturating_sub(self.offset.row); + let col = col.saturating_sub(self.offset.col); - text_annotations + Some(Position::new(row, col)) } pub fn text_pos_at_screen_coords( @@ -518,10 +298,9 @@ impl View { doc: &Document, row: u16, column: u16, - fmt: TextFormat, - annotations: &TextAnnotations, - ignore_virtual_text: bool, + tab_width: usize, ) -> Option<usize> { + let text = doc.text().slice(..); let inner = self.inner_area(doc); // 1 for status if row < inner.top() || row >= inner.bottom() { @@ -532,81 +311,27 @@ impl View { return None; } - self.text_pos_at_visual_coords( - doc, - row - inner.y, - column - inner.x, - fmt, - annotations, - ignore_virtual_text, - ) - } - - pub fn text_pos_at_visual_coords( - &self, - doc: &Document, - row: u16, - column: u16, - text_fmt: TextFormat, - annotations: &TextAnnotations, - ignore_virtual_text: bool, - ) -> Option<usize> { - let text = doc.text().slice(..); - let view_offset = doc.view_offset(self.id); + let text_row = (row - inner.y) as usize + self.offset.row; + if text_row > text.len_lines() - 1 { + return Some(text.len_chars()); + } - let text_row = row as usize + view_offset.vertical_offset; - let text_col = column as usize + view_offset.horizontal_offset; + let text_col = (column - inner.x) as usize + self.offset.col; - let (char_idx, virt_lines) = char_idx_at_visual_offset( + Some(pos_at_visual_coords( text, - view_offset.anchor, - text_row as isize, - text_col, - &text_fmt, - annotations, - ); - - // if the cursor is on a line with only virtual text return None - if virt_lines != 0 && ignore_virtual_text { - return None; - } - Some(char_idx) + Position { + row: text_row, + col: text_col, + }, + tab_width, + )) } /// Translates a screen position to position in the text document. /// Returns a usize typed position in bounds of the text if found in this view, None if out of view. - pub fn pos_at_screen_coords( - &self, - doc: &Document, - row: u16, - column: u16, - ignore_virtual_text: bool, - ) -> Option<usize> { - self.text_pos_at_screen_coords( - doc, - row, - column, - doc.text_format(self.inner_width(doc), None), - &self.text_annotations(doc, None), - ignore_virtual_text, - ) - } - - pub fn pos_at_visual_coords( - &self, - doc: &Document, - row: u16, - column: u16, - ignore_virtual_text: bool, - ) -> Option<usize> { - self.text_pos_at_visual_coords( - doc, - row, - column, - doc.text_format(self.inner_width(doc), None), - &self.text_annotations(doc, None), - ignore_virtual_text, - ) + pub fn pos_at_screen_coords(&self, doc: &Document, row: u16, column: u16) -> Option<usize> { + self.text_pos_at_screen_coords(doc, row, column, doc.tab_width()) } /// Translates screen coordinates into coordinates on the gutter of the view. @@ -655,6 +380,8 @@ impl View { // } /// Applies a [`Transaction`] to the view. + /// Instead of calling this function directly, use [crate::apply_transaction] + /// which applies a transaction to the [`Document`] and view together. pub fn apply(&mut self, transaction: &Transaction, doc: &mut Document) { self.jumps.apply(transaction, doc); self.doc_revisions @@ -687,408 +414,156 @@ impl View { #[cfg(test)] mod tests { - use std::sync::Arc; - use super::*; - use arc_swap::ArcSwap; - use helix_core::{syntax, Rope}; - - // 1 diagnostic + 1 spacer + 3 linenr (< 1000 lines) + 1 spacer + 1 diff - const DEFAULT_GUTTER_OFFSET: u16 = 7; - - // 1 diagnostics + 1 spacer + 1 gutter - const DEFAULT_GUTTER_OFFSET_ONLY_DIAGNOSTICS: u16 = 3; - + use helix_core::Rope; + const OFFSET: u16 = 3; // 1 diagnostic + 2 linenr (< 100 lines) + const OFFSET_WITHOUT_LINE_NUMBERS: u16 = 1; // 1 diagnostic + // const OFFSET: u16 = GUTTERS.iter().map(|(_, width)| *width as u16).sum(); use crate::document::Document; - use crate::editor::{Config, GutterConfig, GutterLineNumbersConfig, GutterType}; + use crate::editor::GutterType; #[test] fn test_text_pos_at_screen_coords() { - let mut view = View::new(DocumentId::default(), GutterConfig::default()); + let mut view = View::new( + DocumentId::default(), + vec![GutterType::Diagnostics, GutterType::LineNumbers], + ); view.area = Rect::new(40, 40, 40, 40); let rope = Rope::from_str("abc\n\tdef"); - let mut doc = Document::from( - rope, - None, - Arc::new(ArcSwap::new(Arc::new(Config::default()))), - Arc::new(ArcSwap::from_pointee(syntax::Loader::default())), - ); - doc.ensure_view_init(view.id); + let doc = Document::from(rope, None); - assert_eq!( - view.text_pos_at_screen_coords( - &doc, - 40, - 2, - TextFormat::default(), - &TextAnnotations::default(), - true - ), - None - ); + assert_eq!(view.text_pos_at_screen_coords(&doc, 40, 2, 4), None); - assert_eq!( - view.text_pos_at_screen_coords( - &doc, - 40, - 41, - TextFormat::default(), - &TextAnnotations::default(), - true - ), - None - ); + assert_eq!(view.text_pos_at_screen_coords(&doc, 40, 41, 4), None); - assert_eq!( - view.text_pos_at_screen_coords( - &doc, - 0, - 2, - TextFormat::default(), - &TextAnnotations::default(), - true - ), - None - ); + assert_eq!(view.text_pos_at_screen_coords(&doc, 0, 2, 4), None); - assert_eq!( - view.text_pos_at_screen_coords( - &doc, - 0, - 49, - TextFormat::default(), - &TextAnnotations::default(), - true - ), - None - ); + assert_eq!(view.text_pos_at_screen_coords(&doc, 0, 49, 4), None); - assert_eq!( - view.text_pos_at_screen_coords( - &doc, - 0, - 41, - TextFormat::default(), - &TextAnnotations::default(), - true - ), - None - ); + assert_eq!(view.text_pos_at_screen_coords(&doc, 0, 41, 4), None); - assert_eq!( - view.text_pos_at_screen_coords( - &doc, - 40, - 81, - TextFormat::default(), - &TextAnnotations::default(), - true - ), - None - ); + assert_eq!(view.text_pos_at_screen_coords(&doc, 40, 81, 4), None); - assert_eq!( - view.text_pos_at_screen_coords( - &doc, - 78, - 41, - TextFormat::default(), - &TextAnnotations::default(), - true - ), - None - ); + assert_eq!(view.text_pos_at_screen_coords(&doc, 78, 41, 4), None); assert_eq!( - view.text_pos_at_screen_coords( - &doc, - 40, - 40 + DEFAULT_GUTTER_OFFSET + 3, - TextFormat::default(), - &TextAnnotations::default(), - true - ), + view.text_pos_at_screen_coords(&doc, 40, 40 + OFFSET + 3, 4), Some(3) ); - assert_eq!( - view.text_pos_at_screen_coords( - &doc, - 40, - 80, - TextFormat::default(), - &TextAnnotations::default(), - true - ), - Some(3) - ); + assert_eq!(view.text_pos_at_screen_coords(&doc, 40, 80, 4), Some(3)); assert_eq!( - view.text_pos_at_screen_coords( - &doc, - 41, - 40 + DEFAULT_GUTTER_OFFSET + 1, - TextFormat::default(), - &TextAnnotations::default(), - true - ), + view.text_pos_at_screen_coords(&doc, 41, 40 + OFFSET + 1, 4), Some(4) ); assert_eq!( - view.text_pos_at_screen_coords( - &doc, - 41, - 40 + DEFAULT_GUTTER_OFFSET + 4, - TextFormat::default(), - &TextAnnotations::default(), - true - ), + view.text_pos_at_screen_coords(&doc, 41, 40 + OFFSET + 4, 4), Some(5) ); assert_eq!( - view.text_pos_at_screen_coords( - &doc, - 41, - 40 + DEFAULT_GUTTER_OFFSET + 7, - TextFormat::default(), - &TextAnnotations::default(), - true - ), + view.text_pos_at_screen_coords(&doc, 41, 40 + OFFSET + 7, 4), Some(8) ); - assert_eq!( - view.text_pos_at_screen_coords( - &doc, - 41, - 80, - TextFormat::default(), - &TextAnnotations::default(), - true - ), - Some(8) - ); + assert_eq!(view.text_pos_at_screen_coords(&doc, 41, 80, 4), Some(8)); } #[test] fn test_text_pos_at_screen_coords_without_line_numbers_gutter() { - let mut view = View::new( - DocumentId::default(), - GutterConfig { - layout: vec![GutterType::Diagnostics], - line_numbers: GutterLineNumbersConfig::default(), - }, - ); + let mut view = View::new(DocumentId::default(), vec![GutterType::Diagnostics]); view.area = Rect::new(40, 40, 40, 40); let rope = Rope::from_str("abc\n\tdef"); - let mut doc = Document::from( - rope, - None, - Arc::new(ArcSwap::new(Arc::new(Config::default()))), - Arc::new(ArcSwap::from_pointee(syntax::Loader::default())), - ); - doc.ensure_view_init(view.id); + let doc = Document::from(rope, None); assert_eq!( - view.text_pos_at_screen_coords( - &doc, - 41, - 40 + DEFAULT_GUTTER_OFFSET_ONLY_DIAGNOSTICS + 1, - TextFormat::default(), - &TextAnnotations::default(), - true - ), + view.text_pos_at_screen_coords(&doc, 41, 40 + OFFSET_WITHOUT_LINE_NUMBERS + 1, 4), Some(4) ); } #[test] fn test_text_pos_at_screen_coords_without_any_gutters() { - let mut view = View::new( - DocumentId::default(), - GutterConfig { - layout: vec![], - line_numbers: GutterLineNumbersConfig::default(), - }, - ); + let mut view = View::new(DocumentId::default(), vec![]); view.area = Rect::new(40, 40, 40, 40); let rope = Rope::from_str("abc\n\tdef"); - let mut doc = Document::from( - rope, - None, - Arc::new(ArcSwap::new(Arc::new(Config::default()))), - Arc::new(ArcSwap::from_pointee(syntax::Loader::default())), - ); - doc.ensure_view_init(view.id); - assert_eq!( - view.text_pos_at_screen_coords( - &doc, - 41, - 40 + 1, - TextFormat::default(), - &TextAnnotations::default(), - true - ), - Some(4) - ); + let doc = Document::from(rope, None); + assert_eq!(view.text_pos_at_screen_coords(&doc, 41, 40 + 1, 4), Some(4)); } #[test] fn test_text_pos_at_screen_coords_cjk() { - let mut view = View::new(DocumentId::default(), GutterConfig::default()); + let mut view = View::new( + DocumentId::default(), + vec![GutterType::Diagnostics, GutterType::LineNumbers], + ); view.area = Rect::new(40, 40, 40, 40); let rope = Rope::from_str("Hi! こんにちは皆さん"); - let mut doc = Document::from( - rope, - None, - Arc::new(ArcSwap::new(Arc::new(Config::default()))), - Arc::new(ArcSwap::from_pointee(syntax::Loader::default())), - ); - doc.ensure_view_init(view.id); + let doc = Document::from(rope, None); assert_eq!( - view.text_pos_at_screen_coords( - &doc, - 40, - 40 + DEFAULT_GUTTER_OFFSET, - TextFormat::default(), - &TextAnnotations::default(), - true - ), + view.text_pos_at_screen_coords(&doc, 40, 40 + OFFSET, 4), Some(0) ); assert_eq!( - view.text_pos_at_screen_coords( - &doc, - 40, - 40 + DEFAULT_GUTTER_OFFSET + 4, - TextFormat::default(), - &TextAnnotations::default(), - true - ), + view.text_pos_at_screen_coords(&doc, 40, 40 + OFFSET + 4, 4), Some(4) ); assert_eq!( - view.text_pos_at_screen_coords( - &doc, - 40, - 40 + DEFAULT_GUTTER_OFFSET + 5, - TextFormat::default(), - &TextAnnotations::default(), - true - ), + view.text_pos_at_screen_coords(&doc, 40, 40 + OFFSET + 5, 4), Some(4) ); assert_eq!( - view.text_pos_at_screen_coords( - &doc, - 40, - 40 + DEFAULT_GUTTER_OFFSET + 6, - TextFormat::default(), - &TextAnnotations::default(), - true - ), + view.text_pos_at_screen_coords(&doc, 40, 40 + OFFSET + 6, 4), Some(5) ); assert_eq!( - view.text_pos_at_screen_coords( - &doc, - 40, - 40 + DEFAULT_GUTTER_OFFSET + 7, - TextFormat::default(), - &TextAnnotations::default(), - true - ), + view.text_pos_at_screen_coords(&doc, 40, 40 + OFFSET + 7, 4), Some(5) ); assert_eq!( - view.text_pos_at_screen_coords( - &doc, - 40, - 40 + DEFAULT_GUTTER_OFFSET + 8, - TextFormat::default(), - &TextAnnotations::default(), - true - ), + view.text_pos_at_screen_coords(&doc, 40, 40 + OFFSET + 8, 4), Some(6) ); } #[test] fn test_text_pos_at_screen_coords_graphemes() { - let mut view = View::new(DocumentId::default(), GutterConfig::default()); + let mut view = View::new( + DocumentId::default(), + vec![GutterType::Diagnostics, GutterType::LineNumbers], + ); view.area = Rect::new(40, 40, 40, 40); let rope = Rope::from_str("Hèl̀l̀ò world!"); - let mut doc = Document::from( - rope, - None, - Arc::new(ArcSwap::new(Arc::new(Config::default()))), - Arc::new(ArcSwap::from_pointee(syntax::Loader::default())), - ); - doc.ensure_view_init(view.id); + let doc = Document::from(rope, None); assert_eq!( - view.text_pos_at_screen_coords( - &doc, - 40, - 40 + DEFAULT_GUTTER_OFFSET, - TextFormat::default(), - &TextAnnotations::default(), - true - ), + view.text_pos_at_screen_coords(&doc, 40, 40 + OFFSET, 4), Some(0) ); assert_eq!( - view.text_pos_at_screen_coords( - &doc, - 40, - 40 + DEFAULT_GUTTER_OFFSET + 1, - TextFormat::default(), - &TextAnnotations::default(), - true - ), + view.text_pos_at_screen_coords(&doc, 40, 40 + OFFSET + 1, 4), Some(1) ); assert_eq!( - view.text_pos_at_screen_coords( - &doc, - 40, - 40 + DEFAULT_GUTTER_OFFSET + 2, - TextFormat::default(), - &TextAnnotations::default(), - true - ), + view.text_pos_at_screen_coords(&doc, 40, 40 + OFFSET + 2, 4), Some(3) ); assert_eq!( - view.text_pos_at_screen_coords( - &doc, - 40, - 40 + DEFAULT_GUTTER_OFFSET + 3, - TextFormat::default(), - &TextAnnotations::default(), - true - ), + view.text_pos_at_screen_coords(&doc, 40, 40 + OFFSET + 3, 4), Some(5) ); assert_eq!( - view.text_pos_at_screen_coords( - &doc, - 40, - 40 + DEFAULT_GUTTER_OFFSET + 4, - TextFormat::default(), - &TextAnnotations::default(), - true - ), + view.text_pos_at_screen_coords(&doc, 40, 40 + OFFSET + 4, 4), Some(7) ); } |