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.rs1013
1 files changed, 139 insertions, 874 deletions
diff --git a/helix-view/src/view.rs b/helix-view/src/view.rs
index aecf09a6..01f18c71 100644
--- a/helix-view/src/view.rs
+++ b/helix-view/src/view.rs
@@ -1,63 +1,36 @@
-use crate::{
- align_view,
- annotations::diagnostics::InlineDiagnostics,
- document::{DocumentColorSwatches, DocumentInlayHints},
- editor::{GutterConfig, GutterType},
- graphics::Rect,
- handlers::diagnostics::DiagnosticsHandler,
- Align, Document, DocumentId, Theme, ViewId,
-};
+use std::borrow::Cow;
+use crate::{graphics::Rect, 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},
-};
-
-use std::{
- collections::{HashMap, VecDeque},
- fmt,
+ coords_at_pos,
+ graphemes::{grapheme_width, RopeGraphemes},
+ line_ending::line_end_char_index,
+ Position, RopeSlice, Selection,
};
-const JUMP_LIST_CAPACITY: usize = 30;
-
type Jump = (DocumentId, Selection);
#[derive(Debug, Clone)]
pub struct JumpList {
- jumps: VecDeque<Jump>,
+ jumps: Vec<Jump>,
current: usize,
}
impl JumpList {
pub fn new(initial: Jump) -> Self {
- let mut jumps = VecDeque::with_capacity(JUMP_LIST_CAPACITY);
- jumps.push_back(initial);
- Self { jumps, current: 0 }
+ Self {
+ jumps: vec![initial],
+ 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);
+ if self.jumps.last() != Some(&jump) {
+ self.jumps.push(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,332 +44,90 @@ 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
}
}
-
- pub fn remove(&mut self, doc_id: &DocumentId) {
- self.jumps.retain(|(other_id, _)| other_id != doc_id);
- }
-
- pub fn iter(&self) -> impl DoubleEndedIterator<Item = &Jump> {
- self.jumps.iter()
- }
-
- /// Applies a [`Transaction`] of changes to the jumplist.
- /// This is necessary to ensure that changes to documents do not leave jump-list
- /// selections pointing to parts of the text which no longer exist.
- fn apply(&mut self, transaction: &Transaction, doc: &Document) {
- let text = doc.text().slice(..);
-
- for (doc_id, selection) in &mut self.jumps {
- if doc.id() == *doc_id {
- *selection = selection
- .clone()
- .map(transaction.changes())
- .ensure_invariants(text);
- }
- }
- }
-}
-
-#[derive(Clone, Debug, PartialEq, Eq, Copy, Default)]
-pub struct ViewPosition {
- pub anchor: usize,
- pub horizontal_offset: usize,
- pub vertical_offset: usize,
}
-#[derive(Clone)]
+#[derive(Debug)]
pub struct View {
pub id: ViewId,
- pub area: Rect,
pub doc: DocumentId,
+ pub offset: Position,
+ pub area: Rect,
pub jumps: JumpList,
- // documents accessed from this view from the oldest one to last viewed one
- pub docs_access_history: Vec<DocumentId>,
- /// the last modified files before the current one
- /// ordered from most frequent to least frequent
- // uses two docs because we want to be able to swap between the
- // two last modified docs which we need to manually keep track of
- 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,
- /// 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 {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- f.debug_struct("View")
- .field("id", &self.id)
- .field("area", &self.area)
- .field("doc", &self.doc)
- .finish()
- }
+ /// the last accessed file before the current one
+ pub last_accessed_doc: Option<DocumentId>,
}
impl View {
- pub fn new(doc: DocumentId, gutters: GutterConfig) -> Self {
+ pub fn new(doc: DocumentId) -> 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,
- doc_revisions: HashMap::new(),
- diagnostics_handler: DiagnosticsHandler::new(),
+ last_accessed_doc: None,
}
}
- pub fn add_to_history(&mut self, id: DocumentId) {
- if let Some(pos) = self.docs_access_history.iter().position(|&doc| doc == id) {
- self.docs_access_history.remove(pos);
- }
- self.docs_access_history.push(id);
+ pub fn inner_area(&self) -> Rect {
+ // TODO: not ideal
+ const OFFSET: u16 = 7; // 1 diagnostic + 5 linenr + 1 gutter
+ self.area.clip_left(OFFSET).clip_bottom(1) // -1 for statusline
}
- pub fn inner_area(&self, doc: &Document) -> Rect {
- self.area.clip_left(self.gutter_offset(doc)).clip_bottom(1) // -1 for statusline
- }
+ pub fn ensure_cursor_in_view(&mut self, doc: &Document, scrolloff: usize) {
+ let cursor = doc
+ .selection(self.id)
+ .primary()
+ .cursor(doc.text().slice(..));
+ let Position { col, row: line } = coords_at_pos(doc.text().slice(..), cursor);
+ let inner_area = self.inner_area();
+ let last_line = (self.offset.row + inner_area.height as usize).saturating_sub(1);
- pub fn inner_height(&self) -> usize {
- 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
- }
+ // - 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);
- pub fn gutters(&self) -> &[GutterType] {
- &self.gutters.layout
- }
+ let last_col = self.offset.col + inner_area.width.saturating_sub(1) as usize;
- pub fn gutter_offset(&self, doc: &Document) -> u16 {
- let total_width = self
- .gutters
- .layout
- .iter()
- .map(|gutter| gutter.width(self, doc) as u16)
- .sum();
- if total_width < self.area.width {
- total_width
- } else {
- 0
+ 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
+ self.offset.row = line.saturating_sub(scrolloff);
}
- }
- //
- pub fn offset_coords_to_in_view(
- &self,
- doc: &Document,
- scrolloff: usize,
- ) -> Option<ViewPosition> {
- self.offset_coords_to_in_view_center::<false>(doc, scrolloff)
- }
-
- pub fn offset_coords_to_in_view_center<const CENTERING: bool>(
- &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
- } else {
- viewport.height as isize - scrolloff_bottom as isize - 1
- };
- (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) {
- // scroll right
- offset.horizontal_offset += col - (last_col.saturating_sub(scrolloff_right))
- } else if col < offset.horizontal_offset + scrolloff_left {
- // scroll left
- offset.horizontal_offset = col.saturating_sub(scrolloff_left)
- };
- }
-
- // 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);
+ if col > last_col.saturating_sub(scrolloff) {
+ // scroll right
+ self.offset.col += col - (last_col.saturating_sub(scrolloff));
+ } else if col < self.offset.col + scrolloff {
+ // scroll left
+ self.offset.col = col.saturating_sub(scrolloff);
}
}
- 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);
- } else {
- align_view(doc, self, Align::Center);
- }
- }
-
- pub fn is_cursor_in_view(&mut self, doc: &Document, scrolloff: usize) -> bool {
- 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 {
+ let height = self.inner_area().height;
+ std::cmp::min(
+ // Saturating subs to make it inclusive zero indexing.
+ (self.offset.row + height as usize).saturating_sub(1),
+ doc.text().len_lines().saturating_sub(1),
+ )
}
/// Translates a document position to an absolute position in the terminal.
@@ -408,121 +139,42 @@ impl View {
text: RopeSlice,
pos: usize,
) -> Option<Position> {
- 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 line = text.char_to_line(pos);
- 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 {
+ if line < self.offset.row || line > self.last_line(doc) {
+ // Line is not visible on screen
return None;
}
- pos.row -= view_offset.vertical_offset;
- if pos.row >= viewport.height as usize {
- 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));
- }
+ let line_start = text.line_to_char(line);
+ let line_slice = text.slice(line_start..pos);
+ let mut col = 0;
+ let tab_width = doc.tab_width();
- text_annotations.add_inline_annotations(color_swatches_padding, None);
+ for grapheme in RopeGraphemes::new(line_slice) {
+ if grapheme == "\t" {
+ col += tab_width;
+ } else {
+ let grapheme = Cow::from(grapheme);
+ col += grapheme_width(&grapheme);
}
}
- 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(
&self,
- doc: &Document,
+ text: &RopeSlice,
row: u16,
column: u16,
- fmt: TextFormat,
- annotations: &TextAnnotations,
- ignore_virtual_text: bool,
+ tab_width: usize,
) -> Option<usize> {
- let inner = self.inner_area(doc);
+ let inner = self.inner_area();
// 1 for status
if row < inner.top() || row >= inner.bottom() {
return None;
@@ -532,107 +184,40 @@ impl View {
return None;
}
- self.text_pos_at_visual_coords(
- doc,
- row - inner.y,
- column - inner.x,
- fmt,
- annotations,
- ignore_virtual_text,
- )
- }
+ let line_number = (row - inner.y) as usize + self.offset.row;
- 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 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,
- 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;
+ if line_number > text.len_lines() - 1 {
+ return Some(text.len_chars());
}
- Some(char_idx)
- }
- /// 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,
- )
- }
+ let mut pos = text.line_to_char(line_number);
- 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,
- )
- }
+ let current_line = text.line(line_number);
- /// Translates screen coordinates into coordinates on the gutter of the view.
- /// Returns a tuple of usize typed line and column numbers starting with 0.
- /// Returns None if coordinates are not on the gutter.
- pub fn gutter_coords_at_screen_coords(&self, row: u16, column: u16) -> Option<Position> {
- // 1 for status
- if row < self.area.top() || row >= self.area.bottom() {
- return None;
- }
+ let target = (column - inner.x) as usize + self.offset.col;
+ let mut selected = 0;
- if column < self.area.left() || column > self.area.right() {
- return None;
+ for grapheme in RopeGraphemes::new(current_line) {
+ if selected >= target {
+ break;
+ }
+ if grapheme == "\t" {
+ selected += tab_width;
+ } else {
+ let width = grapheme_width(&Cow::from(grapheme));
+ selected += width;
+ }
+ pos += grapheme.chars().count();
}
- Some(Position::new(
- (row - self.area.top()) as usize,
- (column - self.area.left()) as usize,
- ))
+ Some(pos.min(line_end_char_index(&text.slice(..), line_number)))
}
- pub fn remove_document(&mut self, doc_id: &DocumentId) {
- self.jumps.remove(doc_id);
- self.docs_access_history.retain(|doc| doc != doc_id);
+ /// 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) -> Option<usize> {
+ self.text_pos_at_screen_coords(&doc.text().slice(..), row, column, doc.tab_width())
}
-
// pub fn traverse<F>(&self, text: RopeSlice, start: usize, end: usize, fun: F)
// where
// F: Fn(usize, usize),
@@ -653,442 +238,122 @@ impl View {
// (None, None) => return,
// }
// }
-
- /// Applies a [`Transaction`] to the view.
- pub fn apply(&mut self, transaction: &Transaction, doc: &mut Document) {
- self.jumps.apply(transaction, doc);
- self.doc_revisions
- .insert(doc.id(), doc.get_current_revision());
- }
-
- pub fn sync_changes(&mut self, doc: &mut Document) {
- let latest_revision = doc.get_current_revision();
- let current_revision = *self
- .doc_revisions
- .entry(doc.id())
- .or_insert(latest_revision);
-
- if current_revision == latest_revision {
- return;
- }
-
- log::debug!(
- "Syncing view {:?} between {} and {}",
- self.id,
- current_revision,
- latest_revision
- );
-
- if let Some(transaction) = doc.history.get_mut().changes_since(current_revision) {
- self.apply(&transaction, doc);
- }
- }
}
#[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 crate::document::Document;
- use crate::editor::{Config, GutterConfig, GutterLineNumbersConfig, GutterType};
+ use helix_core::Rope;
+ const OFFSET: u16 = 7; // 1 diagnostic + 5 linenr + 1 gutter
#[test]
fn test_text_pos_at_screen_coords() {
- let mut view = View::new(DocumentId::default(), GutterConfig::default());
+ let mut view = View::new(DocumentId::default());
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,
- 40,
- 2,
- TextFormat::default(),
- &TextAnnotations::default(),
- true
- ),
- None
- );
+ let text = rope.slice(..);
- 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(&text, 40, 2, 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(&text, 40, 41, 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(&text, 0, 2, 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(&text, 0, 49, 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(&text, 0, 41, 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(&text, 40, 81, 4), None);
- assert_eq!(
- view.text_pos_at_screen_coords(
- &doc,
- 40,
- 40 + DEFAULT_GUTTER_OFFSET + 3,
- TextFormat::default(),
- &TextAnnotations::default(),
- true
- ),
- Some(3)
- );
+ assert_eq!(view.text_pos_at_screen_coords(&text, 78, 41, 4), None);
assert_eq!(
- view.text_pos_at_screen_coords(
- &doc,
- 40,
- 80,
- TextFormat::default(),
- &TextAnnotations::default(),
- true
- ),
+ view.text_pos_at_screen_coords(&text, 40, 40 + OFFSET + 3, 4),
Some(3)
);
- assert_eq!(
- view.text_pos_at_screen_coords(
- &doc,
- 41,
- 40 + DEFAULT_GUTTER_OFFSET + 1,
- TextFormat::default(),
- &TextAnnotations::default(),
- true
- ),
- Some(4)
- );
+ assert_eq!(view.text_pos_at_screen_coords(&text, 40, 80, 4), Some(3));
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(&text, 41, 40 + OFFSET + 1, 4),
Some(5)
);
assert_eq!(
- view.text_pos_at_screen_coords(
- &doc,
- 41,
- 40 + DEFAULT_GUTTER_OFFSET + 7,
- TextFormat::default(),
- &TextAnnotations::default(),
- true
- ),
- Some(8)
+ view.text_pos_at_screen_coords(&text, 41, 40 + OFFSET + 4, 4),
+ Some(5)
);
assert_eq!(
- view.text_pos_at_screen_coords(
- &doc,
- 41,
- 80,
- TextFormat::default(),
- &TextAnnotations::default(),
- true
- ),
+ view.text_pos_at_screen_coords(&text, 41, 40 + OFFSET + 7, 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(),
- },
- );
- 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 + DEFAULT_GUTTER_OFFSET_ONLY_DIAGNOSTICS + 1,
- TextFormat::default(),
- &TextAnnotations::default(),
- true
- ),
- 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(),
- },
- );
- 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)
- );
+ assert_eq!(view.text_pos_at_screen_coords(&text, 41, 80, 4), Some(8));
}
#[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());
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 text = rope.slice(..);
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(&text, 40, 40 + OFFSET + 0, 4),
Some(0)
);
assert_eq!(
- view.text_pos_at_screen_coords(
- &doc,
- 40,
- 40 + DEFAULT_GUTTER_OFFSET + 4,
- TextFormat::default(),
- &TextAnnotations::default(),
- true
- ),
- Some(4)
- );
- assert_eq!(
- view.text_pos_at_screen_coords(
- &doc,
- 40,
- 40 + DEFAULT_GUTTER_OFFSET + 5,
- TextFormat::default(),
- &TextAnnotations::default(),
- true
- ),
- Some(4)
+ view.text_pos_at_screen_coords(&text, 40, 40 + OFFSET + 5, 4),
+ Some(5)
);
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(&text, 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
- ),
- Some(5)
+ view.text_pos_at_screen_coords(&text, 40, 40 + OFFSET + 7, 4),
+ Some(6)
);
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(&text, 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());
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 text = rope.slice(..);
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(&text, 40, 40 + OFFSET + 0, 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(&text, 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(&text, 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(&text, 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(&text, 40, 40 + OFFSET + 4, 4),
Some(7)
);
}