From ab050d18901e47d41fdcd11da3e950688ae2e665 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Tue, 3 Oct 2023 19:10:01 -0600 Subject: [PATCH] Use Horizontal ranges everywhere --- crates/editor/src/display_map.rs | 49 +-------- crates/editor/src/editor.rs | 28 +++-- crates/editor/src/element.rs | 5 +- crates/editor/src/movement.rs | 6 +- crates/editor/src/selections_collection.rs | 23 ++-- crates/text/src/selection.rs | 4 +- crates/vim/src/motion.rs | 120 ++++++++++++--------- crates/vim/src/normal.rs | 32 ++++-- crates/vim/src/normal/substitute.rs | 6 +- crates/vim/src/test.rs | 56 ++++++++++ crates/vim/src/vim.rs | 2 +- crates/vim/src/visual.rs | 44 ++++++-- crates/vim/test_data/test_j.json | 3 + 13 files changed, 229 insertions(+), 149 deletions(-) diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index 424ff1518a..0f2b5665c6 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -15,7 +15,7 @@ use gpui::{ color::Color, fonts::{FontId, HighlightStyle, Underline}, text_layout::{Line, RunStyle}, - AppContext, Entity, FontCache, ModelContext, ModelHandle, TextLayoutCache, + Entity, ModelContext, ModelHandle, }; use inlay_map::InlayMap; use language::{ @@ -576,7 +576,6 @@ impl DisplaySnapshot { let range = display_row..display_row + 1; for chunk in self.highlighted_chunks(range, editor_style) { - dbg!(chunk.chunk); line.push_str(chunk.chunk); let text_style = if let Some(style) = chunk.style { @@ -600,7 +599,6 @@ impl DisplaySnapshot { )); } - dbg!(&line, &editor_style.text.font_size, &styles); text_layout_cache.layout_str(&line, editor_style.text.font_size, &styles) } @@ -623,49 +621,6 @@ impl DisplaySnapshot { layout_line.closest_index_for_x(x_coordinate) as u32 } - // column_for_x(row, x) - - fn point( - &self, - display_point: DisplayPoint, - text_layout_cache: &TextLayoutCache, - editor_style: &EditorStyle, - cx: &AppContext, - ) -> f32 { - let mut styles = Vec::new(); - let mut line = String::new(); - - let range = display_point.row()..display_point.row() + 1; - for chunk in self.highlighted_chunks(range, editor_style) { - dbg!(chunk.chunk); - line.push_str(chunk.chunk); - - let text_style = if let Some(style) = chunk.style { - editor_style - .text - .clone() - .highlight(style, cx.font_cache()) - .map(Cow::Owned) - .unwrap_or_else(|_| Cow::Borrowed(&editor_style.text)) - } else { - Cow::Borrowed(&editor_style.text) - }; - - styles.push(( - chunk.chunk.len(), - RunStyle { - font_id: text_style.font_id, - color: text_style.color, - underline: text_style.underline, - }, - )); - } - - dbg!(&line, &editor_style.text.font_size, &styles); - let layout_line = text_layout_cache.layout_str(&line, editor_style.text.font_size, &styles); - layout_line.x_for_index(display_point.column() as usize) - } - pub fn chars_at( &self, mut point: DisplayPoint, @@ -1374,7 +1329,6 @@ pub mod tests { ); let x = snapshot.x_for_point(DisplayPoint::new(1, 10), &text_layout_details); - dbg!(x); assert_eq!( movement::up( &snapshot, @@ -1401,7 +1355,6 @@ pub mod tests { SelectionGoal::HorizontalPosition(x) ) ); - dbg!("starting down..."); assert_eq!( movement::down( &snapshot, diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index e68b1f008f..88db2f1dfe 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -48,9 +48,9 @@ use gpui::{ impl_actions, keymap_matcher::KeymapContext, platform::{CursorStyle, MouseButton}, - serde_json, text_layout, AnyElement, AnyViewHandle, AppContext, AsyncAppContext, ClipboardItem, - Element, Entity, ModelHandle, Subscription, Task, View, ViewContext, ViewHandle, - WeakViewHandle, WindowContext, + serde_json, AnyElement, AnyViewHandle, AppContext, AsyncAppContext, ClipboardItem, Element, + Entity, ModelHandle, Subscription, Task, View, ViewContext, ViewHandle, WeakViewHandle, + WindowContext, }; use highlight_matching_bracket::refresh_matching_bracket_highlights; use hover_popover::{hide_hover, HoverState}; @@ -5953,11 +5953,14 @@ impl Editor { fn add_selection(&mut self, above: bool, cx: &mut ViewContext) { let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); let mut selections = self.selections.all::(cx); + let text_layout_details = TextLayoutDetails::new(self, cx); let mut state = self.add_selections_state.take().unwrap_or_else(|| { let oldest_selection = selections.iter().min_by_key(|s| s.id).unwrap().clone(); let range = oldest_selection.display_range(&display_map).sorted(); - let columns = cmp::min(range.start.column(), range.end.column()) - ..cmp::max(range.start.column(), range.end.column()); + + let start_x = display_map.x_for_point(range.start, &text_layout_details); + let end_x = display_map.x_for_point(range.end, &text_layout_details); + let positions = start_x.min(end_x)..start_x.max(end_x); selections.clear(); let mut stack = Vec::new(); @@ -5965,8 +5968,9 @@ impl Editor { if let Some(selection) = self.selections.build_columnar_selection( &display_map, row, - &columns, + &positions, oldest_selection.reversed, + &text_layout_details, ) { stack.push(selection.id); selections.push(selection); @@ -5994,12 +5998,15 @@ impl Editor { let range = selection.display_range(&display_map).sorted(); debug_assert_eq!(range.start.row(), range.end.row()); let mut row = range.start.row(); - let columns = if let SelectionGoal::ColumnRange { start, end } = selection.goal + let positions = if let SelectionGoal::HorizontalRange { start, end } = + selection.goal { start..end } else { - cmp::min(range.start.column(), range.end.column()) - ..cmp::max(range.start.column(), range.end.column()) + let start_x = display_map.x_for_point(range.start, &text_layout_details); + let end_x = display_map.x_for_point(range.end, &text_layout_details); + + start_x.min(end_x)..start_x.max(end_x) }; while row != end_row { @@ -6012,8 +6019,9 @@ impl Editor { if let Some(new_selection) = self.selections.build_columnar_selection( &display_map, row, - &columns, + &positions, selection.reversed, + &text_layout_details, ) { state.stack.push(new_selection.id); if above { diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 24cbadfd37..30015eb760 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -22,7 +22,7 @@ use git::diff::DiffHunkStatus; use gpui::{ color::Color, elements::*, - fonts::{HighlightStyle, TextStyle, Underline}, + fonts::TextStyle, geometry::{ rect::RectF, vector::{vec2f, Vector2F}, @@ -37,8 +37,7 @@ use gpui::{ use itertools::Itertools; use json::json; use language::{ - language_settings::ShowWhitespaceSetting, Bias, CursorShape, DiagnosticSeverity, OffsetUtf16, - Selection, + language_settings::ShowWhitespaceSetting, Bias, CursorShape, OffsetUtf16, Selection, }; use project::{ project_settings::{GitGutterSetting, ProjectSettings}, diff --git a/crates/editor/src/movement.rs b/crates/editor/src/movement.rs index 38cf5cd6c1..7e75ae5e5d 100644 --- a/crates/editor/src/movement.rs +++ b/crates/editor/src/movement.rs @@ -1,6 +1,6 @@ use super::{Bias, DisplayPoint, DisplaySnapshot, SelectionGoal, ToDisplayPoint}; use crate::{char_kind, CharKind, Editor, EditorStyle, ToOffset, ToPoint}; -use gpui::{text_layout, FontCache, TextLayoutCache, WindowContext}; +use gpui::{FontCache, TextLayoutCache, WindowContext}; use language::Point; use std::{ops::Range, sync::Arc}; @@ -105,7 +105,9 @@ pub fn up_by_rows( ) -> (DisplayPoint, SelectionGoal) { let mut goal_x = match goal { SelectionGoal::HorizontalPosition(x) => x, + SelectionGoal::WrappedHorizontalPosition((_, x)) => x, SelectionGoal::HorizontalRange { end, .. } => end, + SelectionGoal::WrappedHorizontalRange { end: (_, end), .. } => end, _ => map.x_for_point(start, text_layout_details), }; @@ -140,7 +142,9 @@ pub fn down_by_rows( ) -> (DisplayPoint, SelectionGoal) { let mut goal_x = match goal { SelectionGoal::HorizontalPosition(x) => x, + SelectionGoal::WrappedHorizontalPosition((_, x)) => x, SelectionGoal::HorizontalRange { end, .. } => end, + SelectionGoal::WrappedHorizontalRange { end: (_, end), .. } => end, _ => map.x_for_point(start, text_layout_details), }; diff --git a/crates/editor/src/selections_collection.rs b/crates/editor/src/selections_collection.rs index 6a21c898ef..2fa8ffe408 100644 --- a/crates/editor/src/selections_collection.rs +++ b/crates/editor/src/selections_collection.rs @@ -1,6 +1,6 @@ use std::{ cell::Ref, - cmp, iter, mem, + iter, mem, ops::{Deref, DerefMut, Range, Sub}, sync::Arc, }; @@ -13,6 +13,7 @@ use util::post_inc; use crate::{ display_map::{DisplayMap, DisplaySnapshot, ToDisplayPoint}, + movement::TextLayoutDetails, Anchor, DisplayPoint, ExcerptId, MultiBuffer, MultiBufferSnapshot, SelectMode, ToOffset, }; @@ -305,23 +306,27 @@ impl SelectionsCollection { &mut self, display_map: &DisplaySnapshot, row: u32, - columns: &Range, + positions: &Range, reversed: bool, + text_layout_details: &TextLayoutDetails, ) -> Option> { - let is_empty = columns.start == columns.end; + let is_empty = positions.start == positions.end; let line_len = display_map.line_len(row); - if columns.start < line_len || (is_empty && columns.start == line_len) { - let start = DisplayPoint::new(row, columns.start); - let end = DisplayPoint::new(row, cmp::min(columns.end, line_len)); + + let start_col = display_map.column_for_x(row, positions.start, text_layout_details); + if start_col < line_len || (is_empty && start_col == line_len) { + let start = DisplayPoint::new(row, start_col); + let end_col = display_map.column_for_x(row, positions.end, text_layout_details); + let end = DisplayPoint::new(row, end_col); Some(Selection { id: post_inc(&mut self.next_selection_id), start: start.to_point(display_map), end: end.to_point(display_map), reversed, - goal: SelectionGoal::ColumnRange { - start: columns.start, - end: columns.end, + goal: SelectionGoal::HorizontalRange { + start: positions.start, + end: positions.end, }, }) } else { diff --git a/crates/text/src/selection.rs b/crates/text/src/selection.rs index 38831f92c2..e127083112 100644 --- a/crates/text/src/selection.rs +++ b/crates/text/src/selection.rs @@ -7,8 +7,8 @@ pub enum SelectionGoal { None, HorizontalPosition(f32), HorizontalRange { start: f32, end: f32 }, - Column(u32), - ColumnRange { start: u32, end: u32 }, + WrappedHorizontalPosition((u32, f32)), + WrappedHorizontalRange { start: (u32, f32), end: (u32, f32) }, } #[derive(Clone, Debug, PartialEq)] diff --git a/crates/vim/src/motion.rs b/crates/vim/src/motion.rs index 2f1e376c3e..36514f8cc4 100644 --- a/crates/vim/src/motion.rs +++ b/crates/vim/src/motion.rs @@ -1,5 +1,3 @@ -use std::cmp; - use editor::{ char_kind, display_map::{DisplaySnapshot, FoldPoint, ToDisplayPoint}, @@ -371,13 +369,13 @@ impl Motion { Backspace => (backspace(map, point, times), SelectionGoal::None), Down { display_lines: false, - } => down(map, point, goal, times), + } => up_down_buffer_rows(map, point, goal, times as isize, &text_layout_details), Down { display_lines: true, } => down_display(map, point, goal, times, &text_layout_details), Up { display_lines: false, - } => up(map, point, goal, times), + } => up_down_buffer_rows(map, point, goal, 0 - times as isize, &text_layout_details), Up { display_lines: true, } => up_display(map, point, goal, times, &text_layout_details), @@ -536,35 +534,86 @@ fn backspace(map: &DisplaySnapshot, mut point: DisplayPoint, times: usize) -> Di point } -fn down( +pub(crate) fn start_of_relative_buffer_row( + map: &DisplaySnapshot, + point: DisplayPoint, + times: isize, +) -> DisplayPoint { + let start = map.display_point_to_fold_point(point, Bias::Left); + let target = start.row() as isize + times; + let new_row = (target.max(0) as u32).min(map.fold_snapshot.max_point().row()); + + map.clip_point( + map.fold_point_to_display_point( + map.fold_snapshot + .clip_point(FoldPoint::new(new_row, 0), Bias::Right), + ), + Bias::Right, + ) +} + +fn up_down_buffer_rows( map: &DisplaySnapshot, point: DisplayPoint, mut goal: SelectionGoal, - times: usize, + times: isize, + text_layout_details: &TextLayoutDetails, ) -> (DisplayPoint, SelectionGoal) { let start = map.display_point_to_fold_point(point, Bias::Left); + let begin_folded_line = map.fold_point_to_display_point( + map.fold_snapshot + .clip_point(FoldPoint::new(start.row(), 0), Bias::Left), + ); + let select_nth_wrapped_row = point.row() - begin_folded_line.row(); - let goal_column = match goal { - SelectionGoal::Column(column) => column, - SelectionGoal::ColumnRange { end, .. } => end, + let (goal_wrap, goal_x) = match goal { + SelectionGoal::WrappedHorizontalPosition((row, x)) => (row, x), + SelectionGoal::WrappedHorizontalRange { end: (row, x), .. } => (row, x), + SelectionGoal::HorizontalRange { end, .. } => (select_nth_wrapped_row, end), + SelectionGoal::HorizontalPosition(x) => (select_nth_wrapped_row, x), _ => { - goal = SelectionGoal::Column(start.column()); - start.column() + let x = map.x_for_point(point, text_layout_details); + goal = SelectionGoal::WrappedHorizontalPosition((select_nth_wrapped_row, x)); + (select_nth_wrapped_row, x) } }; - let new_row = cmp::min( - start.row() + times as u32, - map.fold_snapshot.max_point().row(), - ); - let new_col = cmp::min(goal_column, map.fold_snapshot.line_len(new_row)); - let point = map.fold_point_to_display_point( + let target = start.row() as isize + times; + let new_row = (target.max(0) as u32).min(map.fold_snapshot.max_point().row()); + + let mut begin_folded_line = map.fold_point_to_display_point( map.fold_snapshot - .clip_point(FoldPoint::new(new_row, new_col), Bias::Left), + .clip_point(FoldPoint::new(new_row, 0), Bias::Left), ); - // clip twice to "clip at end of line" - (map.clip_point(point, Bias::Left), goal) + let mut i = 0; + while i < goal_wrap && begin_folded_line.row() < map.max_point().row() { + let next_folded_line = DisplayPoint::new(begin_folded_line.row() + 1, 0); + if map + .display_point_to_fold_point(next_folded_line, Bias::Right) + .row() + == new_row + { + i += 1; + begin_folded_line = next_folded_line; + } else { + break; + } + } + + let new_col = if i == goal_wrap { + map.column_for_x(begin_folded_line.row(), goal_x, text_layout_details) + } else { + map.line_len(begin_folded_line.row()) + }; + + ( + map.clip_point( + DisplayPoint::new(begin_folded_line.row(), new_col), + Bias::Left, + ), + goal, + ) } fn down_display( @@ -581,33 +630,6 @@ fn down_display( (point, goal) } -pub(crate) fn up( - map: &DisplaySnapshot, - point: DisplayPoint, - mut goal: SelectionGoal, - times: usize, -) -> (DisplayPoint, SelectionGoal) { - let start = map.display_point_to_fold_point(point, Bias::Left); - - let goal_column = match goal { - SelectionGoal::Column(column) => column, - SelectionGoal::ColumnRange { end, .. } => end, - _ => { - goal = SelectionGoal::Column(start.column()); - start.column() - } - }; - - let new_row = start.row().saturating_sub(times as u32); - let new_col = cmp::min(goal_column, map.fold_snapshot.line_len(new_row)); - let point = map.fold_point_to_display_point( - map.fold_snapshot - .clip_point(FoldPoint::new(new_row, new_col), Bias::Left), - ); - - (map.clip_point(point, Bias::Left), goal) -} - fn up_display( map: &DisplaySnapshot, mut point: DisplayPoint, @@ -894,7 +916,7 @@ fn find_backward( } fn next_line_start(map: &DisplaySnapshot, point: DisplayPoint, times: usize) -> DisplayPoint { - let correct_line = down(map, point, SelectionGoal::None, times).0; + let correct_line = start_of_relative_buffer_row(map, point, times as isize); first_non_whitespace(map, false, correct_line) } @@ -904,7 +926,7 @@ pub(crate) fn next_line_end( times: usize, ) -> DisplayPoint { if times > 1 { - point = down(map, point, SelectionGoal::None, times - 1).0; + point = start_of_relative_buffer_row(map, point, times as isize - 1); } end_of_line(map, false, point) } diff --git a/crates/vim/src/normal.rs b/crates/vim/src/normal.rs index 9c93f19fc7..0e883cd758 100644 --- a/crates/vim/src/normal.rs +++ b/crates/vim/src/normal.rs @@ -194,9 +194,7 @@ fn insert_after(_: &mut Workspace, _: &InsertAfter, cx: &mut ViewContext, cx: &mut WindowContext) { Vim::update(cx, |vim, cx| { if vim.enabled && vim.state().mode == Mode::Normal && !newest.is_empty() { - if matches!(newest.goal, SelectionGoal::ColumnRange { .. }) { + if matches!(newest.goal, SelectionGoal::HorizontalRange { .. }) { vim.switch_mode(Mode::VisualBlock, false, cx); } else { vim.switch_mode(Mode::Visual, false, cx) diff --git a/crates/vim/src/visual.rs b/crates/vim/src/visual.rs index bec91007e3..ac4c5478a1 100644 --- a/crates/vim/src/visual.rs +++ b/crates/vim/src/visual.rs @@ -140,17 +140,21 @@ pub fn visual_block_motion( SelectionGoal, ) -> Option<(DisplayPoint, SelectionGoal)>, ) { + let text_layout_details = TextLayoutDetails::new(editor, cx); editor.change_selections(Some(Autoscroll::fit()), cx, |s| { let map = &s.display_map(); let mut head = s.newest_anchor().head().to_display_point(map); let mut tail = s.oldest_anchor().tail().to_display_point(map); let (start, end) = match s.newest_anchor().goal { - SelectionGoal::ColumnRange { start, end } if preserve_goal => (start, end), - SelectionGoal::Column(start) if preserve_goal => (start, start + 1), - _ => (tail.column(), head.column()), + SelectionGoal::HorizontalRange { start, end } if preserve_goal => (start, end), + SelectionGoal::HorizontalPosition(start) if preserve_goal => (start, start + 10.0), + _ => ( + map.x_for_point(tail, &text_layout_details), + map.x_for_point(head, &text_layout_details), + ), }; - let goal = SelectionGoal::ColumnRange { start, end }; + let goal = SelectionGoal::HorizontalRange { start, end }; let was_reversed = tail.column() > head.column(); if !was_reversed && !preserve_goal { @@ -172,21 +176,39 @@ pub fn visual_block_motion( head = movement::saturating_right(map, head) } - let columns = if is_reversed { - head.column()..tail.column() + let positions = if is_reversed { + map.x_for_point(head, &text_layout_details)..map.x_for_point(tail, &text_layout_details) } else if head.column() == tail.column() { - head.column()..(head.column() + 1) + map.x_for_point(head, &text_layout_details) + ..map.x_for_point(head, &text_layout_details) + 10.0 } else { - tail.column()..head.column() + map.x_for_point(tail, &text_layout_details)..map.x_for_point(head, &text_layout_details) }; let mut selections = Vec::new(); let mut row = tail.row(); loop { - let start = map.clip_point(DisplayPoint::new(row, columns.start), Bias::Left); - let end = map.clip_point(DisplayPoint::new(row, columns.end), Bias::Left); - if columns.start <= map.line_len(row) { + let start = map.clip_point( + DisplayPoint::new( + row, + map.column_for_x(row, positions.start, &text_layout_details), + ), + Bias::Left, + ); + let end = map.clip_point( + DisplayPoint::new( + row, + map.column_for_x(row, positions.end, &text_layout_details), + ), + Bias::Left, + ); + if positions.start + <= map.x_for_point( + DisplayPoint::new(row, map.line_len(row)), + &text_layout_details, + ) + { let selection = Selection { id: s.new_selection_id(), start: start.to_point(map), diff --git a/crates/vim/test_data/test_j.json b/crates/vim/test_data/test_j.json index 64aaf65ef8..703f69d22c 100644 --- a/crates/vim/test_data/test_j.json +++ b/crates/vim/test_data/test_j.json @@ -1,3 +1,6 @@ +{"Put":{"state":"aaˇaa\n😃😃"}} +{"Key":"j"} +{"Get":{"state":"aaaa\n😃ˇ😃","mode":"Normal"}} {"Put":{"state":"ˇThe quick brown\nfox jumps"}} {"Key":"j"} {"Get":{"state":"The quick brown\nˇfox jumps","mode":"Normal"}}