From e3061066c923c5bd6f9b07b3567d4f3dd05ebd0b Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Sat, 25 Feb 2023 20:10:20 -0800 Subject: [PATCH 01/14] Add code folding indicators into the gutter. --- crates/editor/src/display_map.rs | 57 +++++++++ crates/editor/src/editor.rs | 202 ++++++++++++++++++------------ crates/editor/src/element.rs | 44 ++++++- crates/editor/src/multi_buffer.rs | 4 + crates/gpui/src/elements.rs | 5 +- crates/theme/src/theme.rs | 8 ++ styles/src/styleTree/editor.ts | 4 + 7 files changed, 239 insertions(+), 85 deletions(-) diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index 99a74fe7f2..2097f594eb 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -23,6 +23,12 @@ pub use block_map::{ BlockDisposition, BlockId, BlockProperties, BlockStyle, RenderBlock, TransformBlock, }; +#[derive(Copy, Clone, Debug)] +pub enum FoldStatus { + Folded, + Foldable, +} + pub trait ToDisplayPoint { fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint; } @@ -591,6 +597,57 @@ impl DisplaySnapshot { self.blocks_snapshot.longest_row() } + pub fn fold_for_line(self: &Self, display_row: u32) -> Option { + if self.is_line_foldable(display_row) { + Some(FoldStatus::Foldable) + } else if self.is_line_folded(display_row) { + Some(FoldStatus::Folded) + } else { + None + } + } + + pub fn is_line_foldable(self: &Self, display_row: u32) -> bool { + let max_point = self.max_point(); + if display_row >= max_point.row() { + false + } else { + let (start_indent, is_blank) = self.line_indent(display_row); + if is_blank { + false + } else { + for display_row in display_row + 1..=max_point.row() { + let (indent, is_blank) = self.line_indent(display_row); + if !is_blank { + return indent > start_indent; + } + } + false + } + } + } + + pub fn foldable_range_for_line(self: &Self, start_row: u32) -> Option> { + if self.is_line_foldable(start_row) && !self.is_line_folded(start_row) { + let max_point = self.max_point(); + let (start_indent, _) = self.line_indent(start_row); + let start = DisplayPoint::new(start_row, self.line_len(start_row)); + let mut end = None; + for row in start_row + 1..=max_point.row() { + let (indent, is_blank) = self.line_indent(row); + if !is_blank && indent <= start_indent { + end = Some(DisplayPoint::new(row - 1, self.line_len(row - 1))); + break; + } + } + + let end = end.unwrap_or(max_point); + Some(start.to_point(self)..end.to_point(self)) + } else { + return None; + } + } + #[cfg(any(test, feature = "test-support"))] pub fn highlight_ranges( &self, diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 2cc26c26ed..76ce6f1e4b 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -1,6 +1,7 @@ mod blink_manager; pub mod display_map; mod element; + mod git; mod highlight_matching_bracket; mod hover_popover; @@ -160,6 +161,16 @@ pub struct ToggleComments { pub advance_downwards: bool, } +#[derive(Clone, Default, Deserialize, PartialEq)] +pub struct FoldAt { + pub display_row: u32, +} + +#[derive(Clone, Default, Deserialize, PartialEq)] +pub struct UnfoldAt { + pub display_row: u32, +} + actions!( editor, [ @@ -258,6 +269,8 @@ impl_actions!( ConfirmCompletion, ConfirmCodeAction, ToggleComments, + FoldAt, + UnfoldAt ] ); @@ -348,7 +361,9 @@ pub fn init(cx: &mut MutableAppContext) { cx.add_action(Editor::go_to_definition); cx.add_action(Editor::go_to_type_definition); cx.add_action(Editor::fold); + cx.add_action(Editor::fold_at); cx.add_action(Editor::unfold_lines); + cx.add_action(Editor::unfold_at); cx.add_action(Editor::fold_selected_ranges); cx.add_action(Editor::show_completions); cx.add_action(Editor::toggle_code_actions); @@ -2648,9 +2663,9 @@ impl Editor { cx: &mut RenderContext, ) -> Option { if self.available_code_actions.is_some() { - enum Tag {} + enum CodeActions {} Some( - MouseEventHandler::::new(0, cx, |_, _| { + MouseEventHandler::::new(0, cx, |_, _| { Svg::new("icons/bolt_8.svg") .with_color(style.code_actions.indicator) .boxed() @@ -2669,6 +2684,51 @@ impl Editor { } } + pub fn render_fold_indicators( + &self, + fold_data: Vec<(u32, FoldStatus)>, + fold_indicators: &mut Vec<(u32, ElementBox)>, + style: &EditorStyle, + cx: &mut RenderContext, + ) { + enum FoldIndicators {} + + for (fold_location, fold_status) in fold_data.iter() { + fold_indicators.push(( + *fold_location, + MouseEventHandler::::new( + *fold_location as usize, + cx, + |_, _| -> ElementBox { + Svg::new(match *fold_status { + FoldStatus::Folded => "icons/chevron_right_8.svg", + FoldStatus::Foldable => "icons/chevron_down_8.svg", + }) + .with_color(style.folds.indicator) + .boxed() + }, + ) + .with_cursor_style(CursorStyle::PointingHand) + .with_padding(Padding::uniform(3.)) + .on_down(MouseButton::Left, { + let fold_location = *fold_location; + let fold_status = *fold_status; + move |_, cx| { + cx.dispatch_any_action(match fold_status { + FoldStatus::Folded => Box::new(UnfoldAt { + display_row: fold_location, + }), + FoldStatus::Foldable => Box::new(FoldAt { + display_row: fold_location, + }), + }); + } + }) + .boxed(), + )) + } + } + pub fn context_menu_visible(&self) -> bool { self.context_menu .as_ref() @@ -3251,26 +3311,12 @@ impl Editor { while let Some(selection) = selections.next() { // Find all the selections that span a contiguous row range - contiguous_row_selections.push(selection.clone()); - let start_row = selection.start.row; - let mut end_row = if selection.end.column > 0 || selection.is_empty() { - display_map.next_line_boundary(selection.end).0.row + 1 - } else { - selection.end.row - }; - - while let Some(next_selection) = selections.peek() { - if next_selection.start.row <= end_row { - end_row = if next_selection.end.column > 0 || next_selection.is_empty() { - display_map.next_line_boundary(next_selection.end).0.row + 1 - } else { - next_selection.end.row - }; - contiguous_row_selections.push(selections.next().unwrap().clone()); - } else { - break; - } - } + let (start_row, end_row) = consume_contiguous_rows( + &mut contiguous_row_selections, + selection, + &display_map, + &mut selections, + ); // Move the text spanned by the row range to be before the line preceding the row range if start_row > 0 { @@ -3363,26 +3409,12 @@ impl Editor { while let Some(selection) = selections.next() { // Find all the selections that span a contiguous row range - contiguous_row_selections.push(selection.clone()); - let start_row = selection.start.row; - let mut end_row = if selection.end.column > 0 || selection.is_empty() { - display_map.next_line_boundary(selection.end).0.row + 1 - } else { - selection.end.row - }; - - while let Some(next_selection) = selections.peek() { - if next_selection.start.row <= end_row { - end_row = if next_selection.end.column > 0 || next_selection.is_empty() { - display_map.next_line_boundary(next_selection.end).0.row + 1 - } else { - next_selection.end.row - }; - contiguous_row_selections.push(selections.next().unwrap().clone()); - } else { - break; - } - } + let (start_row, end_row) = consume_contiguous_rows( + &mut contiguous_row_selections, + selection, + &display_map, + &mut selections, + ); // Move the text spanned by the row range to be after the last line of the row range if end_row <= buffer.max_point().row { @@ -5676,14 +5708,14 @@ impl Editor { let mut fold_ranges = Vec::new(); let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); + let selections = self.selections.all::(cx); for selection in selections { let range = selection.display_range(&display_map).sorted(); let buffer_start_row = range.start.to_point(&display_map).row; for row in (0..=range.end.row()).rev() { - if self.is_line_foldable(&display_map, row) && !display_map.is_line_folded(row) { - let fold_range = self.foldable_range_for_line(&display_map, row); + if let Some(fold_range) = display_map.foldable_range_for_line(row) { if fold_range.end.row >= buffer_start_row { fold_ranges.push(fold_range); if row <= range.start.row() { @@ -5697,6 +5729,16 @@ impl Editor { self.fold_ranges(fold_ranges, cx); } + pub fn fold_at(&mut self, fold_at: &FoldAt, cx: &mut ViewContext) { + let display_row = fold_at.display_row; + + let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); + + if let Some(fold_range) = display_map.foldable_range_for_line(display_row) { + self.fold_ranges(std::iter::once(fold_range), cx); + } + } + pub fn unfold_lines(&mut self, _: &UnfoldLines, cx: &mut ViewContext) { let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); let buffer = &display_map.buffer_snapshot; @@ -5715,46 +5757,11 @@ impl Editor { self.unfold_ranges(ranges, true, cx); } - fn is_line_foldable(&self, display_map: &DisplaySnapshot, display_row: u32) -> bool { - let max_point = display_map.max_point(); - if display_row >= max_point.row() { - false - } else { - let (start_indent, is_blank) = display_map.line_indent(display_row); - if is_blank { - false - } else { - for display_row in display_row + 1..=max_point.row() { - let (indent, is_blank) = display_map.line_indent(display_row); - if !is_blank { - return indent > start_indent; - } - } - false - } - } - } + pub fn unfold_at(&mut self, fold_at: &UnfoldAt, cx: &mut ViewContext) { + let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); + let unfold_range = display_map.buffer_snapshot.row_span(fold_at.display_row); - fn foldable_range_for_line( - &self, - display_map: &DisplaySnapshot, - start_row: u32, - ) -> Range { - let max_point = display_map.max_point(); - - let (start_indent, _) = display_map.line_indent(start_row); - let start = DisplayPoint::new(start_row, display_map.line_len(start_row)); - let mut end = None; - for row in start_row + 1..=max_point.row() { - let (indent, is_blank) = display_map.line_indent(row); - if !is_blank && indent <= start_indent { - end = Some(DisplayPoint::new(row - 1, display_map.line_len(row - 1))); - break; - } - } - - let end = end.unwrap_or(max_point); - start.to_point(display_map)..end.to_point(display_map) + self.unfold_ranges(std::iter::once(unfold_range), true, cx) } pub fn fold_selected_ranges(&mut self, _: &FoldSelectedRanges, cx: &mut ViewContext) { @@ -6252,6 +6259,35 @@ impl Editor { } } +fn consume_contiguous_rows( + contiguous_row_selections: &mut Vec>, + selection: &Selection, + display_map: &DisplaySnapshot, + selections: &mut std::iter::Peekable>>, +) -> (u32, u32) { + contiguous_row_selections.push(selection.clone()); + let start_row = selection.start.row; + let mut end_row = ending_row(selection, display_map); + + while let Some(next_selection) = selections.peek() { + if next_selection.start.row <= end_row { + end_row = ending_row(next_selection, display_map); + contiguous_row_selections.push(selections.next().unwrap().clone()); + } else { + break; + } + } + (start_row, end_row) +} + +fn ending_row(next_selection: &Selection, display_map: &DisplaySnapshot) -> u32 { + if next_selection.end.column > 0 || next_selection.is_empty() { + display_map.next_line_boundary(next_selection.end).0.row + 1 + } else { + next_selection.end.row + } +} + impl EditorSnapshot { pub fn language_at(&self, position: T) -> Option<&Arc> { self.display_snapshot.buffer_snapshot.language_at(position) diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 57e0f18035..68f136bddb 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -4,7 +4,7 @@ use super::{ ToPoint, MAX_LINE_LEN, }; use crate::{ - display_map::{BlockStyle, DisplaySnapshot, TransformBlock}, + display_map::{BlockStyle, DisplaySnapshot, FoldStatus, TransformBlock}, git::{diff_hunk_to_display, DisplayDiffHunk}, hover_popover::{ HideHover, HoverAt, HOVER_POPOVER_GAP, MIN_POPOVER_CHARACTER_WIDTH, MIN_POPOVER_LINE_HEIGHT, @@ -575,6 +575,16 @@ impl EditorElement { y += (line_height - indicator.size().y()) / 2.; indicator.paint(bounds.origin() + vec2f(x, y), visible_bounds, cx); } + + for (line, fold_indicator) in layout.fold_indicators.iter_mut() { + let mut x = bounds.width() - layout.gutter_padding; + let mut y = *line as f32 * line_height - scroll_top; + + x += ((layout.gutter_padding + layout.gutter_margin) - fold_indicator.size().x()) / 2.; + y += (line_height - fold_indicator.size().y()) / 2.; + + fold_indicator.paint(bounds.origin() + vec2f(x, y), visible_bounds, cx); + } } fn paint_diff_hunks(bounds: RectF, layout: &mut LayoutState, cx: &mut PaintContext) { @@ -1118,6 +1128,21 @@ impl EditorElement { .width() } + fn get_fold_indicators( + &self, + display_rows: Range, + snapshot: &EditorSnapshot, + ) -> Vec<(u32, FoldStatus)> { + display_rows + .into_iter() + .filter_map(|display_row| { + snapshot + .fold_for_line(display_row) + .map(|fold_status| (display_row, fold_status)) + }) + .collect() + } + //Folds contained in a hunk are ignored apart from shrinking visual size //If a fold contains any hunks then that fold line is marked as modified fn layout_git_gutters( @@ -1689,6 +1714,8 @@ impl Element for EditorElement { let display_hunks = self.layout_git_gutters(start_row..end_row, &snapshot); + let folds = self.get_fold_indicators(start_row..end_row, &snapshot); + let scrollbar_row_range = scroll_position.y()..(scroll_position.y() + height_in_lines); let mut max_visible_line_width = 0.0; @@ -1751,6 +1778,7 @@ impl Element for EditorElement { } }); + let mut fold_indicators = Vec::with_capacity(folds.len()); let mut context_menu = None; let mut code_actions_indicator = None; let mut hover = None; @@ -1774,6 +1802,8 @@ impl Element for EditorElement { .map(|indicator| (newest_selection_head.row(), indicator)); } + view.render_fold_indicators(folds, &mut fold_indicators, &style, cx); + let visible_rows = start_row..start_row + line_layouts.len() as u32; hover = view.hover_state.render(&snapshot, &style, visible_rows, cx); mode = view.mode; @@ -1802,6 +1832,16 @@ impl Element for EditorElement { ); } + for (_, indicator) in fold_indicators.iter_mut() { + indicator.layout( + SizeConstraint::strict_along( + Axis::Vertical, + line_height * style.code_actions.vertical_scale, + ), + cx, + ); + } + if let Some((_, hover_popovers)) = hover.as_mut() { for hover_popover in hover_popovers.iter_mut() { hover_popover.layout( @@ -1851,6 +1891,7 @@ impl Element for EditorElement { selections, context_menu, code_actions_indicator, + fold_indicators, hover_popovers: hover, }, ) @@ -1979,6 +2020,7 @@ pub struct LayoutState { context_menu: Option<(DisplayPoint, ElementBox)>, code_actions_indicator: Option<(u32, ElementBox)>, hover_popovers: Option<(DisplayPoint, Vec)>, + fold_indicators: Vec<(u32, ElementBox)>, } pub struct PositionMap { diff --git a/crates/editor/src/multi_buffer.rs b/crates/editor/src/multi_buffer.rs index da3c6bc4bd..541c63ff1c 100644 --- a/crates/editor/src/multi_buffer.rs +++ b/crates/editor/src/multi_buffer.rs @@ -1916,6 +1916,10 @@ impl MultiBufferSnapshot { } } + pub fn row_span(&self, display_row: u32) -> Range { + Point::new(display_row, 0)..Point::new(display_row, self.line_len(display_row)) + } + pub fn buffer_rows(&self, start_row: u32) -> MultiBufferRows { let mut result = MultiBufferRows { buffer_row_range: 0..0, diff --git a/crates/gpui/src/elements.rs b/crates/gpui/src/elements.rs index b77d46536d..a42dc1cfa8 100644 --- a/crates/gpui/src/elements.rs +++ b/crates/gpui/src/elements.rs @@ -296,7 +296,10 @@ impl AnyElement for Lifecycle { paint, } } - _ => panic!("invalid element lifecycle state"), + Lifecycle::Empty => panic!("invalid element lifecycle state"), + Lifecycle::Init { .. } => { + panic!("invalid element lifecycle state, paint called before layout") + } } } diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index efe64cbc5c..c9695d4f36 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -562,6 +562,7 @@ pub struct Editor { pub invalid_hint_diagnostic: DiagnosticStyle, pub autocomplete: AutocompleteStyle, pub code_actions: CodeActions, + pub folds: Folds, pub unnecessary_code_fade: f32, pub hover_popover: HoverPopover, pub link_definition: HighlightStyle, @@ -638,6 +639,13 @@ pub struct CodeActions { pub vertical_scale: f32, } +#[derive(Clone, Deserialize, Default)] +pub struct Folds { + #[serde(default)] + pub indicator: Color, + pub fold_background: Color, +} + #[derive(Clone, Deserialize, Default)] pub struct DiffStyle { pub inserted: Color, diff --git a/styles/src/styleTree/editor.ts b/styles/src/styleTree/editor.ts index 85c9ccec95..3518c2aba5 100644 --- a/styles/src/styleTree/editor.ts +++ b/styles/src/styleTree/editor.ts @@ -47,6 +47,10 @@ export default function editor(colorScheme: ColorScheme) { indicator: foreground(layer, "variant"), verticalScale: 0.55, }, + folds: { + indicator: foreground(layer, "variant"), + fold_background: foreground(layer, "variant"), + }, diff: { deleted: foreground(layer, "negative"), modified: foreground(layer, "warning"), From 637e8ada42e409d8a57d90aa0a584c2d22e83fab Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Sun, 26 Feb 2023 11:25:58 -0800 Subject: [PATCH 02/14] Fix bugs in code folding --- crates/editor/src/display_map.rs | 27 +++++++++++++++ crates/editor/src/editor.rs | 52 +++++++++++++++++------------ crates/editor/src/editor_tests.rs | 5 +++ crates/editor/src/items.rs | 2 +- crates/editor/src/multi_buffer.rs | 4 --- crates/search/src/project_search.rs | 2 +- crates/util/src/util.rs | 17 ++++++++++ 7 files changed, 82 insertions(+), 27 deletions(-) diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index 2097f594eb..8f9decbcb3 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -12,6 +12,7 @@ use gpui::{ Entity, ModelContext, ModelHandle, }; use language::{OffsetUtf16, Point, Subscription as BufferSubscription}; +use serde::{Deserialize, Serialize}; use settings::Settings; use std::{any::TypeId, fmt::Debug, num::NonZeroU32, ops::Range, sync::Arc}; use sum_tree::{Bias, TreeMap}; @@ -660,6 +661,32 @@ impl DisplaySnapshot { #[derive(Copy, Clone, Default, Eq, Ord, PartialOrd, PartialEq)] pub struct DisplayPoint(BlockPoint); +#[derive(Copy, Clone, Default, Eq, Ord, PartialOrd, PartialEq, Deserialize, Serialize)] +#[repr(transparent)] +pub struct DisplayRow(pub u32); + +impl DisplayRow { + pub fn new(display_row: u32) -> Self { + DisplayRow(display_row) + } + + pub fn to_span(self, display_map: &DisplaySnapshot) -> Range { + let row_start = DisplayPoint::new(self.0, 0); + let row_end = DisplayPoint::new( + self.0, + display_map.buffer_snapshot.line_len(row_start.row()), + ); + + row_start..row_end + } +} + +impl From for DisplayRow { + fn from(value: DisplayPoint) -> Self { + DisplayRow(value.row()) + } +} + impl Debug for DisplayPoint { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_fmt(format_args!( diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 76ce6f1e4b..6b86ec9afa 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -85,7 +85,7 @@ use std::{ }; pub use sum_tree::Bias; use theme::{DiagnosticStyle, Theme}; -use util::{post_inc, RangeExt, ResultExt, TryFutureExt}; +use util::{post_inc, MapRangeEndsExt, RangeExt, ResultExt, TryFutureExt}; use workspace::{ItemNavHistory, ViewId, Workspace, WorkspaceId}; use crate::git::diff_hunk_to_display; @@ -163,12 +163,12 @@ pub struct ToggleComments { #[derive(Clone, Default, Deserialize, PartialEq)] pub struct FoldAt { - pub display_row: u32, + pub display_row: DisplayRow, } #[derive(Clone, Default, Deserialize, PartialEq)] pub struct UnfoldAt { - pub display_row: u32, + pub display_row: DisplayRow, } actions!( @@ -2716,10 +2716,10 @@ impl Editor { move |_, cx| { cx.dispatch_any_action(match fold_status { FoldStatus::Folded => Box::new(UnfoldAt { - display_row: fold_location, + display_row: DisplayRow::new(fold_location), }), FoldStatus::Foldable => Box::new(FoldAt { - display_row: fold_location, + display_row: DisplayRow::new(fold_location), }), }); } @@ -3381,13 +3381,13 @@ impl Editor { } self.transact(cx, |this, cx| { - this.unfold_ranges(unfold_ranges, true, cx); + this.unfold_ranges(unfold_ranges, true, true, cx); this.buffer.update(cx, |buffer, cx| { for (range, text) in edits { buffer.edit([(range, text)], None, cx); } }); - this.fold_ranges(refold_ranges, cx); + this.fold_ranges(refold_ranges, true, cx); this.change_selections(Some(Autoscroll::fit()), cx, |s| { s.select(new_selections); }) @@ -3472,13 +3472,13 @@ impl Editor { } self.transact(cx, |this, cx| { - this.unfold_ranges(unfold_ranges, true, cx); + this.unfold_ranges(unfold_ranges, true, true, cx); this.buffer.update(cx, |buffer, cx| { for (range, text) in edits { buffer.edit([(range, text)], None, cx); } }); - this.fold_ranges(refold_ranges, cx); + this.fold_ranges(refold_ranges, true, cx); this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(new_selections)); }); } @@ -4306,7 +4306,7 @@ impl Editor { to_unfold.push(selection.start..selection.end); } } - self.unfold_ranges(to_unfold, true, cx); + self.unfold_ranges(to_unfold, true, true, cx); self.change_selections(Some(Autoscroll::fit()), cx, |s| { s.select_ranges(new_selection_ranges); }); @@ -4455,7 +4455,7 @@ impl Editor { } if let Some(next_selected_range) = next_selected_range { - self.unfold_ranges([next_selected_range.clone()], false, cx); + self.unfold_ranges([next_selected_range.clone()], false, true, cx); self.change_selections(Some(Autoscroll::newest()), cx, |s| { if action.replace_newest { s.delete(s.newest_anchor().id); @@ -4488,7 +4488,7 @@ impl Editor { wordwise: true, done: false, }; - self.unfold_ranges([selection.start..selection.end], false, cx); + self.unfold_ranges([selection.start..selection.end], false, true, cx); self.change_selections(Some(Autoscroll::newest()), cx, |s| { s.select(selections); }); @@ -5726,7 +5726,7 @@ impl Editor { } } - self.fold_ranges(fold_ranges, cx); + self.fold_ranges(fold_ranges, true, cx); } pub fn fold_at(&mut self, fold_at: &FoldAt, cx: &mut ViewContext) { @@ -5734,8 +5734,8 @@ impl Editor { let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); - if let Some(fold_range) = display_map.foldable_range_for_line(display_row) { - self.fold_ranges(std::iter::once(fold_range), cx); + if let Some(fold_range) = display_map.foldable_range_for_line(display_row.0) { + self.fold_ranges(std::iter::once(fold_range), true, cx); } } @@ -5754,31 +5754,38 @@ impl Editor { start..end }) .collect::>(); - self.unfold_ranges(ranges, true, cx); + self.unfold_ranges(ranges, true, true, cx); } pub fn unfold_at(&mut self, fold_at: &UnfoldAt, cx: &mut ViewContext) { let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); - let unfold_range = display_map.buffer_snapshot.row_span(fold_at.display_row); - self.unfold_ranges(std::iter::once(unfold_range), true, cx) + let display_range = fold_at + .display_row + .to_span(&display_map) + .map_endpoints(|endpoint| endpoint.to_point(&display_map)); + + self.unfold_ranges(std::iter::once(display_range), true, true, cx) } pub fn fold_selected_ranges(&mut self, _: &FoldSelectedRanges, cx: &mut ViewContext) { let selections = self.selections.all::(cx); let ranges = selections.into_iter().map(|s| s.start..s.end); - self.fold_ranges(ranges, cx); + self.fold_ranges(ranges, true, cx); } pub fn fold_ranges( &mut self, ranges: impl IntoIterator>, + auto_scroll: bool, cx: &mut ViewContext, ) { let mut ranges = ranges.into_iter().peekable(); if ranges.peek().is_some() { self.display_map.update(cx, |map, cx| map.fold(ranges, cx)); - self.request_autoscroll(Autoscroll::fit(), cx); + if auto_scroll { + self.request_autoscroll(Autoscroll::fit(), cx); + } cx.notify(); } } @@ -5787,13 +5794,16 @@ impl Editor { &mut self, ranges: impl IntoIterator>, inclusive: bool, + auto_scroll: bool, cx: &mut ViewContext, ) { let mut ranges = ranges.into_iter().peekable(); if ranges.peek().is_some() { self.display_map .update(cx, |map, cx| map.unfold(ranges, inclusive, cx)); - self.request_autoscroll(Autoscroll::fit(), cx); + if auto_scroll { + self.request_autoscroll(Autoscroll::fit(), cx); + } cx.notify(); } } diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index b0ce4a68b9..48f696eb98 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -447,6 +447,7 @@ fn test_clone(cx: &mut gpui::MutableAppContext) { Point::new(1, 0)..Point::new(2, 0), Point::new(3, 0)..Point::new(4, 0), ], + true, cx, ); }); @@ -807,6 +808,7 @@ fn test_move_cursor_multibyte(cx: &mut gpui::MutableAppContext) { Point::new(1, 2)..Point::new(1, 4), Point::new(2, 4)..Point::new(2, 8), ], + true, cx, ); assert_eq!(view.display_text(cx), "ⓐⓑ…ⓔ\nab…e\nαβ…ε\n"); @@ -2119,6 +2121,7 @@ fn test_move_line_up_down(cx: &mut gpui::MutableAppContext) { Point::new(2, 3)..Point::new(4, 1), Point::new(7, 0)..Point::new(8, 4), ], + true, cx, ); view.change_selections(None, cx, |s| { @@ -2586,6 +2589,7 @@ fn test_split_selection_into_lines(cx: &mut gpui::MutableAppContext) { Point::new(2, 3)..Point::new(4, 1), Point::new(7, 0)..Point::new(8, 4), ], + true, cx, ); view.change_selections(None, cx, |s| { @@ -2983,6 +2987,7 @@ async fn test_select_larger_smaller_syntax_node(cx: &mut gpui::TestAppContext) { Point::new(0, 21)..Point::new(0, 24), Point::new(3, 20)..Point::new(3, 22), ], + true, cx, ); view.select_larger_syntax_node(&SelectLargerSyntaxNode, cx); diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index b0b368acd6..0d594a66ef 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -886,7 +886,7 @@ impl SearchableItem for Editor { matches: Vec>, cx: &mut ViewContext, ) { - self.unfold_ranges([matches[index].clone()], false, cx); + self.unfold_ranges([matches[index].clone()], false, true, cx); self.change_selections(Some(Autoscroll::fit()), cx, |s| { s.select_ranges([matches[index].clone()]) }); diff --git a/crates/editor/src/multi_buffer.rs b/crates/editor/src/multi_buffer.rs index 541c63ff1c..da3c6bc4bd 100644 --- a/crates/editor/src/multi_buffer.rs +++ b/crates/editor/src/multi_buffer.rs @@ -1916,10 +1916,6 @@ impl MultiBufferSnapshot { } } - pub fn row_span(&self, display_row: u32) -> Range { - Point::new(display_row, 0)..Point::new(display_row, self.line_len(display_row)) - } - pub fn buffer_rows(&self, start_row: u32) -> MultiBufferRows { let mut result = MultiBufferRows { buffer_row_range: 0..0, diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index 04f4b1470a..e48d78e84f 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -538,7 +538,7 @@ impl ProjectSearchView { let range_to_select = match_ranges[new_index].clone(); self.results_editor.update(cx, |editor, cx| { - editor.unfold_ranges([range_to_select.clone()], false, cx); + editor.unfold_ranges([range_to_select.clone()], false, true, cx); editor.change_selections(Some(Autoscroll::fit()), cx, |s| { s.select_ranges([range_to_select]) }); diff --git a/crates/util/src/util.rs b/crates/util/src/util.rs index 37e1f29ce2..339cc53bf8 100644 --- a/crates/util/src/util.rs +++ b/crates/util/src/util.rs @@ -145,6 +145,23 @@ where } } +pub trait MapRangeEndsExt { + fn map_endpoints(self, f: F) -> Range + where + Self: Sized, + F: Fn(R) -> T; +} + +impl MapRangeEndsExt for Range { + fn map_endpoints(self, f: F) -> Range + where + Self: Sized, + F: Fn(R) -> T, + { + f(self.start)..f(self.end) + } +} + pub struct LogErrorFuture(F, log::Level); impl Future for LogErrorFuture From da78abd99fe41e120aed1f7685cf58a1de20395d Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Sun, 26 Feb 2023 14:17:18 -0800 Subject: [PATCH 03/14] Added DisplayRow abstraction to make folding code more readable --- crates/editor/src/display_map.rs | 119 ++++++++++++++++++++++--------- crates/editor/src/editor.rs | 6 +- crates/vim/src/normal.rs | 8 ++- 3 files changed, 95 insertions(+), 38 deletions(-) diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index 8f9decbcb3..936f4697c5 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -576,10 +576,10 @@ impl DisplaySnapshot { result } - pub fn line_indent(&self, display_row: u32) -> (u32, bool) { + pub fn line_indent(&self, display_row: DisplayRow) -> (u32, bool) { let mut indent = 0; let mut is_blank = true; - for (c, _) in self.chars_at(DisplayPoint::new(display_row, 0)) { + for (c, _) in self.chars_at(display_row.start(self)) { if c == ' ' { indent += 1; } else { @@ -599,7 +599,8 @@ impl DisplaySnapshot { } pub fn fold_for_line(self: &Self, display_row: u32) -> Option { - if self.is_line_foldable(display_row) { + let display_row_typed = DisplayRow::new(display_row); + if self.is_foldable(display_row_typed) { Some(FoldStatus::Foldable) } else if self.is_line_folded(display_row) { Some(FoldStatus::Folded) @@ -608,44 +609,43 @@ impl DisplaySnapshot { } } - pub fn is_line_foldable(self: &Self, display_row: u32) -> bool { - let max_point = self.max_point(); - if display_row >= max_point.row() { - false - } else { - let (start_indent, is_blank) = self.line_indent(display_row); + pub fn is_foldable(self: &Self, row: DisplayRow) -> bool { + if let Some(next_lines) = row.next_rows(self) { + let (start_indent, is_blank) = self.line_indent(row); if is_blank { - false - } else { - for display_row in display_row + 1..=max_point.row() { - let (indent, is_blank) = self.line_indent(display_row); - if !is_blank { - return indent > start_indent; - } + return false; + } + + for display_row in next_lines { + let (indent, is_blank) = self.line_indent(display_row); + if !is_blank { + return indent > start_indent; } - false } } + return false; } - pub fn foldable_range_for_line(self: &Self, start_row: u32) -> Option> { - if self.is_line_foldable(start_row) && !self.is_line_folded(start_row) { + pub fn foldable_range(self: &Self, row: DisplayRow) -> Option> { + let start = row.end(&self); + + if self.is_foldable(row) && !self.is_line_folded(start.row()) { + let (start_indent, _) = self.line_indent(row); let max_point = self.max_point(); - let (start_indent, _) = self.line_indent(start_row); - let start = DisplayPoint::new(start_row, self.line_len(start_row)); let mut end = None; - for row in start_row + 1..=max_point.row() { + + for row in row.next_rows(self).unwrap() { let (indent, is_blank) = self.line_indent(row); if !is_blank && indent <= start_indent { - end = Some(DisplayPoint::new(row - 1, self.line_len(row - 1))); + end = row.previous_row(self); break; } } - let end = end.unwrap_or(max_point); + let end = end.map(|end_row| end_row.end(self)).unwrap_or(max_point); Some(start.to_point(self)..end.to_point(self)) } else { - return None; + None } } @@ -665,19 +665,74 @@ pub struct DisplayPoint(BlockPoint); #[repr(transparent)] pub struct DisplayRow(pub u32); +// TODO: Move display_map check into new, and then remove it from everywhere else impl DisplayRow { pub fn new(display_row: u32) -> Self { DisplayRow(display_row) } - pub fn to_span(self, display_map: &DisplaySnapshot) -> Range { - let row_start = DisplayPoint::new(self.0, 0); - let row_end = DisplayPoint::new( - self.0, - display_map.buffer_snapshot.line_len(row_start.row()), - ); + pub fn to_line_span(&self, display_map: &DisplaySnapshot) -> Range { + self.start(display_map)..self.end(&display_map) + } - row_start..row_end + pub fn previous_row(&self, _display_map: &DisplaySnapshot) -> Option { + self.0.checked_sub(1).map(|prev_row| DisplayRow(prev_row)) + } + + pub fn next_row(&self, display_map: &DisplaySnapshot) -> Option { + if self.0 + 1 > display_map.max_point().row() { + None + } else { + Some(DisplayRow(self.0 + 1)) + } + } + + // TODO: Remove and use next_row + fn next_unchecked(&self) -> Option { + Some(DisplayRow(self.0 + 1)) + } + + pub fn next_rows( + &self, + display_map: &DisplaySnapshot, + ) -> Option> { + self.next_row(display_map) + .and_then(|next_row| next_row.span_to(display_map.max_point())) + } + + pub fn span_to>( + &self, + end_row: I, + ) -> Option> { + let end_row = end_row.into(); + if self.0 <= end_row.0 { + let start = *self; + let mut current = None; + + Some(std::iter::from_fn(move || { + if current == None { + current = Some(start); + } else { + current = current.unwrap().next_unchecked(); + } + if current.unwrap().0 > end_row.0 { + None + } else { + current + } + })) + } else { + None + } + } + + // Force users to think about the display map when using this type + pub fn start(&self, _display_map: &DisplaySnapshot) -> DisplayPoint { + DisplayPoint::new(self.0, 0) + } + + pub fn end(&self, display_map: &DisplaySnapshot) -> DisplayPoint { + DisplayPoint::new(self.0, display_map.line_len(self.0)) } } diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 6b86ec9afa..05075636aa 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -5715,7 +5715,7 @@ impl Editor { let buffer_start_row = range.start.to_point(&display_map).row; for row in (0..=range.end.row()).rev() { - if let Some(fold_range) = display_map.foldable_range_for_line(row) { + if let Some(fold_range) = display_map.foldable_range(DisplayRow::new(row)) { if fold_range.end.row >= buffer_start_row { fold_ranges.push(fold_range); if row <= range.start.row() { @@ -5734,7 +5734,7 @@ impl Editor { let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); - if let Some(fold_range) = display_map.foldable_range_for_line(display_row.0) { + if let Some(fold_range) = display_map.foldable_range(display_row) { self.fold_ranges(std::iter::once(fold_range), true, cx); } } @@ -5762,7 +5762,7 @@ impl Editor { let display_range = fold_at .display_row - .to_span(&display_map) + .to_line_span(&display_map) .map_endpoints(|endpoint| endpoint.to_point(&display_map)); self.unfold_ranges(std::iter::once(display_range), true, true, cx) diff --git a/crates/vim/src/normal.rs b/crates/vim/src/normal.rs index 55134e2149..92ff9e5fb9 100644 --- a/crates/vim/src/normal.rs +++ b/crates/vim/src/normal.rs @@ -12,7 +12,7 @@ use crate::{ }; use collections::{HashMap, HashSet}; use editor::{ - display_map::ToDisplayPoint, + display_map::{DisplayRow, ToDisplayPoint}, scroll::{autoscroll::Autoscroll, scroll_amount::ScrollAmount}, Anchor, Bias, ClipboardSelection, DisplayPoint, Editor, }; @@ -195,7 +195,7 @@ fn insert_line_above(_: &mut Workspace, _: &InsertLineAbove, cx: &mut ViewContex .map(|selection| selection.start.row()) .collect(); let edits = selection_start_rows.into_iter().map(|row| { - let (indent, _) = map.line_indent(row); + let (indent, _) = map.line_indent(DisplayRow::new(row)); let start_of_line = map .clip_point(DisplayPoint::new(row, 0), Bias::Left) .to_point(&map); @@ -216,6 +216,8 @@ fn insert_line_above(_: &mut Workspace, _: &InsertLineAbove, cx: &mut ViewContex }); } +// TODO: FIGURE OUT WHY PANIC WHEN CLICKING ON FOLDS + fn insert_line_below(_: &mut Workspace, _: &InsertLineBelow, cx: &mut ViewContext) { Vim::update(cx, |vim, cx| { vim.switch_mode(Mode::Insert, false, cx); @@ -227,7 +229,7 @@ fn insert_line_below(_: &mut Workspace, _: &InsertLineBelow, cx: &mut ViewContex .map(|selection| selection.end.row()) .collect(); let edits = selection_end_rows.into_iter().map(|row| { - let (indent, _) = map.line_indent(row); + let (indent, _) = map.line_indent(DisplayRow::new(row)); let end_of_line = map .clip_point(DisplayPoint::new(row, map.line_len(row)), Bias::Left) .to_point(&map); From cb3e873a67fb4c4511b429fefb6b662e8423b43d Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Sun, 26 Feb 2023 17:02:05 -0800 Subject: [PATCH 04/14] Fixed autoscroll timing --- crates/editor/src/display_map.rs | 170 +++++++++++++++---------------- crates/editor/src/editor.rs | 114 +++++++++++++-------- crates/editor/src/element.rs | 73 +++++++------ crates/util/src/util.rs | 4 +- 4 files changed, 201 insertions(+), 160 deletions(-) diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index 936f4697c5..4aed3e5f5c 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -579,7 +579,7 @@ impl DisplaySnapshot { pub fn line_indent(&self, display_row: DisplayRow) -> (u32, bool) { let mut indent = 0; let mut is_blank = true; - for (c, _) in self.chars_at(display_row.start(self)) { + for (c, _) in self.chars_at(display_row.start()) { if c == ' ' { indent += 1; } else { @@ -626,7 +626,7 @@ impl DisplaySnapshot { return false; } - pub fn foldable_range(self: &Self, row: DisplayRow) -> Option> { + pub fn foldable_range(self: &Self, row: DisplayRow) -> Option> { let start = row.end(&self); if self.is_foldable(row) && !self.is_line_folded(start.row()) { @@ -637,13 +637,13 @@ impl DisplaySnapshot { for row in row.next_rows(self).unwrap() { let (indent, is_blank) = self.line_indent(row); if !is_blank && indent <= start_indent { - end = row.previous_row(self); + end = row.previous_row(); break; } } let end = end.map(|end_row| end_row.end(self)).unwrap_or(max_point); - Some(start.to_point(self)..end.to_point(self)) + Some(start..end) } else { None } @@ -661,87 +661,6 @@ impl DisplaySnapshot { #[derive(Copy, Clone, Default, Eq, Ord, PartialOrd, PartialEq)] pub struct DisplayPoint(BlockPoint); -#[derive(Copy, Clone, Default, Eq, Ord, PartialOrd, PartialEq, Deserialize, Serialize)] -#[repr(transparent)] -pub struct DisplayRow(pub u32); - -// TODO: Move display_map check into new, and then remove it from everywhere else -impl DisplayRow { - pub fn new(display_row: u32) -> Self { - DisplayRow(display_row) - } - - pub fn to_line_span(&self, display_map: &DisplaySnapshot) -> Range { - self.start(display_map)..self.end(&display_map) - } - - pub fn previous_row(&self, _display_map: &DisplaySnapshot) -> Option { - self.0.checked_sub(1).map(|prev_row| DisplayRow(prev_row)) - } - - pub fn next_row(&self, display_map: &DisplaySnapshot) -> Option { - if self.0 + 1 > display_map.max_point().row() { - None - } else { - Some(DisplayRow(self.0 + 1)) - } - } - - // TODO: Remove and use next_row - fn next_unchecked(&self) -> Option { - Some(DisplayRow(self.0 + 1)) - } - - pub fn next_rows( - &self, - display_map: &DisplaySnapshot, - ) -> Option> { - self.next_row(display_map) - .and_then(|next_row| next_row.span_to(display_map.max_point())) - } - - pub fn span_to>( - &self, - end_row: I, - ) -> Option> { - let end_row = end_row.into(); - if self.0 <= end_row.0 { - let start = *self; - let mut current = None; - - Some(std::iter::from_fn(move || { - if current == None { - current = Some(start); - } else { - current = current.unwrap().next_unchecked(); - } - if current.unwrap().0 > end_row.0 { - None - } else { - current - } - })) - } else { - None - } - } - - // Force users to think about the display map when using this type - pub fn start(&self, _display_map: &DisplaySnapshot) -> DisplayPoint { - DisplayPoint::new(self.0, 0) - } - - pub fn end(&self, display_map: &DisplaySnapshot) -> DisplayPoint { - DisplayPoint::new(self.0, display_map.line_len(self.0)) - } -} - -impl From for DisplayRow { - fn from(value: DisplayPoint) -> Self { - DisplayRow(value.row()) - } -} - impl Debug for DisplayPoint { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_fmt(format_args!( @@ -817,6 +736,87 @@ impl ToDisplayPoint for Anchor { } } +#[derive(Copy, Clone, Default, Eq, Ord, PartialOrd, PartialEq, Deserialize, Serialize)] +#[repr(transparent)] +pub struct DisplayRow(pub u32); + +// TODO: Move display_map check into new, and then remove it from everywhere else +impl DisplayRow { + pub fn new(display_row: u32) -> Self { + DisplayRow(display_row) + } + + pub fn to_line_span(&self, display_map: &DisplaySnapshot) -> Range { + self.start()..self.end(&display_map) + } + + pub fn previous_row(&self) -> Option { + self.0.checked_sub(1).map(|prev_row| DisplayRow(prev_row)) + } + + pub fn next_row(&self, display_map: &DisplaySnapshot) -> Option { + if self.0 + 1 > display_map.max_point().row() { + None + } else { + Some(DisplayRow(self.0 + 1)) + } + } + + // TODO: Remove and use next_row + fn next_unchecked(&self) -> Option { + Some(DisplayRow(self.0 + 1)) + } + + pub fn next_rows( + &self, + display_map: &DisplaySnapshot, + ) -> Option> { + self.next_row(display_map) + .and_then(|next_row| next_row.span_to(display_map.max_point())) + } + + pub fn span_to>( + &self, + end_row: I, + ) -> Option> { + let end_row = end_row.into(); + if self.0 <= end_row.0 { + let start = *self; + let mut current = None; + + Some(std::iter::from_fn(move || { + if current == None { + current = Some(start); + } else { + current = current.unwrap().next_unchecked(); + } + if current.unwrap().0 > end_row.0 { + None + } else { + current + } + })) + } else { + None + } + } + + // Force users to think about the display map when using this type + pub fn start(&self) -> DisplayPoint { + DisplayPoint::new(self.0, 0) + } + + pub fn end(&self, display_map: &DisplaySnapshot) -> DisplayPoint { + DisplayPoint::new(self.0, display_map.line_len(self.0)) + } +} + +impl From for DisplayRow { + fn from(value: DisplayPoint) -> Self { + DisplayRow(value.row()) + } +} + #[cfg(test)] pub mod tests { use super::*; diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 05075636aa..0586669624 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -2686,47 +2686,51 @@ impl Editor { pub fn render_fold_indicators( &self, - fold_data: Vec<(u32, FoldStatus)>, - fold_indicators: &mut Vec<(u32, ElementBox)>, + fold_data: Option>, style: &EditorStyle, cx: &mut RenderContext, - ) { + ) -> Option> { enum FoldIndicators {} - for (fold_location, fold_status) in fold_data.iter() { - fold_indicators.push(( - *fold_location, - MouseEventHandler::::new( - *fold_location as usize, - cx, - |_, _| -> ElementBox { - Svg::new(match *fold_status { - FoldStatus::Folded => "icons/chevron_right_8.svg", - FoldStatus::Foldable => "icons/chevron_down_8.svg", + fold_data.map(|fold_data| { + fold_data + .iter() + .map(|(fold_location, fold_status)| { + ( + *fold_location, + MouseEventHandler::::new( + *fold_location as usize, + cx, + |_, _| -> ElementBox { + Svg::new(match *fold_status { + FoldStatus::Folded => "icons/chevron_right_8.svg", + FoldStatus::Foldable => "icons/chevron_down_8.svg", + }) + .with_color(style.folds.indicator) + .boxed() + }, + ) + .with_cursor_style(CursorStyle::PointingHand) + .with_padding(Padding::uniform(3.)) + .on_down(MouseButton::Left, { + let fold_location = *fold_location; + let fold_status = *fold_status; + move |_, cx| { + cx.dispatch_any_action(match fold_status { + FoldStatus::Folded => Box::new(UnfoldAt { + display_row: DisplayRow::new(fold_location), + }), + FoldStatus::Foldable => Box::new(FoldAt { + display_row: DisplayRow::new(fold_location), + }), + }); + } }) - .with_color(style.folds.indicator) - .boxed() - }, - ) - .with_cursor_style(CursorStyle::PointingHand) - .with_padding(Padding::uniform(3.)) - .on_down(MouseButton::Left, { - let fold_location = *fold_location; - let fold_status = *fold_status; - move |_, cx| { - cx.dispatch_any_action(match fold_status { - FoldStatus::Folded => Box::new(UnfoldAt { - display_row: DisplayRow::new(fold_location), - }), - FoldStatus::Foldable => Box::new(FoldAt { - display_row: DisplayRow::new(fold_location), - }), - }); - } + .boxed(), + ) }) - .boxed(), - )) - } + .collect() + }) } pub fn context_menu_visible(&self) -> bool { @@ -5715,7 +5719,13 @@ impl Editor { let buffer_start_row = range.start.to_point(&display_map).row; for row in (0..=range.end.row()).rev() { - if let Some(fold_range) = display_map.foldable_range(DisplayRow::new(row)) { + let fold_range = display_map + .foldable_range(DisplayRow::new(row)) + .map(|range| { + range.map_range(|display_point| display_point.to_point(&display_map)) + }); + + if let Some(fold_range) = fold_range { if fold_range.end.row >= buffer_start_row { fold_ranges.push(fold_range); if row <= range.start.row() { @@ -5735,10 +5745,29 @@ impl Editor { let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); if let Some(fold_range) = display_map.foldable_range(display_row) { - self.fold_ranges(std::iter::once(fold_range), true, cx); + let autoscroll = self.selections_intersect(&fold_range, &display_map, cx); + + let point_range = + fold_range.map_range(|display_point| display_point.to_point(&display_map)); + + self.fold_ranges(std::iter::once(point_range), autoscroll, cx); } } + fn selections_intersect( + &mut self, + range: &Range, + display_map: &DisplaySnapshot, + cx: &mut ViewContext, + ) -> bool { + let selections = self.selections.all::(cx); + + selections.iter().any(|selection| { + let display_range = selection.display_range(display_map); + range.contains(&display_range.start) || range.contains(&display_range.end) + }) + } + pub fn unfold_lines(&mut self, _: &UnfoldLines, cx: &mut ViewContext) { let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); let buffer = &display_map.buffer_snapshot; @@ -5760,12 +5789,13 @@ impl Editor { pub fn unfold_at(&mut self, fold_at: &UnfoldAt, cx: &mut ViewContext) { let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); - let display_range = fold_at - .display_row - .to_line_span(&display_map) - .map_endpoints(|endpoint| endpoint.to_point(&display_map)); + let unfold_range = fold_at.display_row.to_line_span(&display_map); - self.unfold_ranges(std::iter::once(display_range), true, true, cx) + let autoscroll = self.selections_intersect(&unfold_range, &display_map, cx); + + let unfold_range = unfold_range.map_range(|endpoint| endpoint.to_point(&display_map)); + + self.unfold_ranges(std::iter::once(unfold_range), true, autoscroll, cx) } pub fn fold_selected_ranges(&mut self, _: &FoldSelectedRanges, cx: &mut ViewContext) { diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 68f136bddb..5b1e1273c6 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -48,6 +48,7 @@ use std::{ ops::{DerefMut, Range}, sync::Arc, }; +use workspace::item::Item; struct SelectionLayout { head: DisplayPoint, @@ -576,15 +577,18 @@ impl EditorElement { indicator.paint(bounds.origin() + vec2f(x, y), visible_bounds, cx); } - for (line, fold_indicator) in layout.fold_indicators.iter_mut() { - let mut x = bounds.width() - layout.gutter_padding; - let mut y = *line as f32 * line_height - scroll_top; + layout.fold_indicators.as_mut().map(|fold_indicators| { + for (line, fold_indicator) in fold_indicators.iter_mut() { + let mut x = bounds.width() - layout.gutter_padding; + let mut y = *line as f32 * line_height - scroll_top; - x += ((layout.gutter_padding + layout.gutter_margin) - fold_indicator.size().x()) / 2.; - y += (line_height - fold_indicator.size().y()) / 2.; + x += ((layout.gutter_padding + layout.gutter_margin) - fold_indicator.size().x()) + / 2.; + y += (line_height - fold_indicator.size().y()) / 2.; - fold_indicator.paint(bounds.origin() + vec2f(x, y), visible_bounds, cx); - } + fold_indicator.paint(bounds.origin() + vec2f(x, y), visible_bounds, cx); + } + }); } fn paint_diff_hunks(bounds: RectF, layout: &mut LayoutState, cx: &mut PaintContext) { @@ -1130,17 +1134,20 @@ impl EditorElement { fn get_fold_indicators( &self, + is_singleton: bool, display_rows: Range, snapshot: &EditorSnapshot, - ) -> Vec<(u32, FoldStatus)> { - display_rows - .into_iter() - .filter_map(|display_row| { - snapshot - .fold_for_line(display_row) - .map(|fold_status| (display_row, fold_status)) - }) - .collect() + ) -> Option> { + is_singleton.then(|| { + display_rows + .into_iter() + .filter_map(|display_row| { + snapshot + .fold_for_line(display_row) + .map(|fold_status| (display_row, fold_status)) + }) + .collect() + }) } //Folds contained in a hunk are ignored apart from shrinking visual size @@ -1633,7 +1640,10 @@ impl Element for EditorElement { let mut highlighted_ranges = Vec::new(); let mut show_scrollbars = false; let mut include_root = false; + let mut is_singleton = false; self.update_view(cx.app, |view, cx| { + is_singleton = view.is_singleton(cx); + let display_map = view.display_map.update(cx, |map, cx| map.snapshot(cx)); highlighted_rows = view.highlighted_rows(); @@ -1714,7 +1724,7 @@ impl Element for EditorElement { let display_hunks = self.layout_git_gutters(start_row..end_row, &snapshot); - let folds = self.get_fold_indicators(start_row..end_row, &snapshot); + let folds = self.get_fold_indicators(is_singleton, start_row..end_row, &snapshot); let scrollbar_row_range = scroll_position.y()..(scroll_position.y() + height_in_lines); @@ -1778,12 +1788,11 @@ impl Element for EditorElement { } }); - let mut fold_indicators = Vec::with_capacity(folds.len()); let mut context_menu = None; let mut code_actions_indicator = None; let mut hover = None; let mut mode = EditorMode::Full; - cx.render(&self.view.upgrade(cx).unwrap(), |view, cx| { + let mut fold_indicators = cx.render(&self.view.upgrade(cx).unwrap(), |view, cx| { let newest_selection_head = view .selections .newest::(cx) @@ -1802,11 +1811,11 @@ impl Element for EditorElement { .map(|indicator| (newest_selection_head.row(), indicator)); } - view.render_fold_indicators(folds, &mut fold_indicators, &style, cx); - let visible_rows = start_row..start_row + line_layouts.len() as u32; hover = view.hover_state.render(&snapshot, &style, visible_rows, cx); mode = view.mode; + + view.render_fold_indicators(folds, &style, cx) }); if let Some((_, context_menu)) = context_menu.as_mut() { @@ -1832,15 +1841,17 @@ impl Element for EditorElement { ); } - for (_, indicator) in fold_indicators.iter_mut() { - indicator.layout( - SizeConstraint::strict_along( - Axis::Vertical, - line_height * style.code_actions.vertical_scale, - ), - cx, - ); - } + fold_indicators.as_mut().map(|fold_indicators| { + for (_, indicator) in fold_indicators.iter_mut() { + indicator.layout( + SizeConstraint::strict_along( + Axis::Vertical, + line_height * style.code_actions.vertical_scale, + ), + cx, + ); + } + }); if let Some((_, hover_popovers)) = hover.as_mut() { for hover_popover in hover_popovers.iter_mut() { @@ -2020,7 +2031,7 @@ pub struct LayoutState { context_menu: Option<(DisplayPoint, ElementBox)>, code_actions_indicator: Option<(u32, ElementBox)>, hover_popovers: Option<(DisplayPoint, Vec)>, - fold_indicators: Vec<(u32, ElementBox)>, + fold_indicators: Option>, } pub struct PositionMap { diff --git a/crates/util/src/util.rs b/crates/util/src/util.rs index 339cc53bf8..c0f08ac32e 100644 --- a/crates/util/src/util.rs +++ b/crates/util/src/util.rs @@ -146,14 +146,14 @@ where } pub trait MapRangeEndsExt { - fn map_endpoints(self, f: F) -> Range + fn map_range(self, f: F) -> Range where Self: Sized, F: Fn(R) -> T; } impl MapRangeEndsExt for Range { - fn map_endpoints(self, f: F) -> Range + fn map_range(self, f: F) -> Range where Self: Sized, F: Fn(R) -> T, From 2036fc48b5806ea53a3890214654ee2d8d812f6d Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Sun, 26 Feb 2023 17:08:11 -0800 Subject: [PATCH 05/14] moved code action indicator to the left --- crates/editor/src/display_map.rs | 1 - crates/editor/src/element.rs | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index 4aed3e5f5c..0a59d7e835 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -801,7 +801,6 @@ impl DisplayRow { } } - // Force users to think about the display map when using this type pub fn start(&self) -> DisplayPoint { DisplayPoint::new(self.0, 0) } diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 5b1e1273c6..9a205b39fc 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -570,7 +570,7 @@ impl EditorElement { } if let Some((row, indicator)) = layout.code_actions_indicator.as_mut() { - let mut x = bounds.width() - layout.gutter_padding; + let mut x = 0.; let mut y = *row as f32 * line_height - scroll_top; x += ((layout.gutter_padding + layout.gutter_margin) - indicator.size().x()) / 2.; y += (line_height - indicator.size().y()) / 2.; From 89b93d4f6fd1988fb2a1028965cae18904734e27 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Sun, 26 Feb 2023 18:32:13 -0800 Subject: [PATCH 06/14] Added fold changes on gutter hover --- crates/editor/src/display_map.rs | 2 +- crates/editor/src/editor.rs | 38 ++++++++++++++++++++++++++------ crates/editor/src/element.rs | 16 ++++++++++++-- crates/theme/src/theme.rs | 1 + styles/src/styleTree/editor.ts | 3 ++- 5 files changed, 49 insertions(+), 11 deletions(-) diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index 0a59d7e835..ced92c6e13 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -24,7 +24,7 @@ pub use block_map::{ BlockDisposition, BlockId, BlockProperties, BlockStyle, RenderBlock, TransformBlock, }; -#[derive(Copy, Clone, Debug)] +#[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum FoldStatus { Folded, Foldable, diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 0586669624..5f2f27a15e 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -171,6 +171,11 @@ pub struct UnfoldAt { pub display_row: DisplayRow, } +#[derive(Clone, Default, Deserialize, PartialEq)] +pub struct GutterHover { + pub hovered: bool, +} + actions!( editor, [ @@ -270,7 +275,8 @@ impl_actions!( ConfirmCodeAction, ToggleComments, FoldAt, - UnfoldAt + UnfoldAt, + GutterHover ] ); @@ -364,6 +370,7 @@ pub fn init(cx: &mut MutableAppContext) { cx.add_action(Editor::fold_at); cx.add_action(Editor::unfold_lines); cx.add_action(Editor::unfold_at); + cx.add_action(Editor::gutter_hover); cx.add_action(Editor::fold_selected_ranges); cx.add_action(Editor::show_completions); cx.add_action(Editor::toggle_code_actions); @@ -495,6 +502,7 @@ pub struct Editor { leader_replica_id: Option, remote_id: Option, hover_state: HoverState, + gutter_hovered: bool, link_go_to_definition_state: LinkGoToDefinitionState, _subscriptions: Vec, } @@ -1166,6 +1174,7 @@ impl Editor { remote_id: None, hover_state: Default::default(), link_go_to_definition_state: Default::default(), + gutter_hovered: false, _subscriptions: vec![ cx.observe(&buffer, Self::on_buffer_changed), cx.subscribe(&buffer, Self::on_buffer_event), @@ -2688,6 +2697,7 @@ impl Editor { &self, fold_data: Option>, style: &EditorStyle, + gutter_hovered: bool, cx: &mut RenderContext, ) -> Option> { enum FoldIndicators {} @@ -2695,26 +2705,31 @@ impl Editor { fold_data.map(|fold_data| { fold_data .iter() + .copied() .map(|(fold_location, fold_status)| { ( - *fold_location, + fold_location, MouseEventHandler::::new( - *fold_location as usize, + fold_location as usize, cx, |_, _| -> ElementBox { - Svg::new(match *fold_status { + Svg::new(match fold_status { FoldStatus::Folded => "icons/chevron_right_8.svg", FoldStatus::Foldable => "icons/chevron_down_8.svg", }) - .with_color(style.folds.indicator) + .with_color( + if gutter_hovered || fold_status == FoldStatus::Folded { + style.folds.indicator + } else { + style.folds.faded_indicator + }, + ) .boxed() }, ) .with_cursor_style(CursorStyle::PointingHand) .with_padding(Padding::uniform(3.)) .on_down(MouseButton::Left, { - let fold_location = *fold_location; - let fold_status = *fold_status; move |_, cx| { cx.dispatch_any_action(match fold_status { FoldStatus::Folded => Box::new(UnfoldAt { @@ -5838,6 +5853,15 @@ impl Editor { } } + pub fn gutter_hover( + &mut self, + GutterHover { hovered }: &GutterHover, + cx: &mut ViewContext, + ) { + self.gutter_hovered = *hovered; + cx.notify(); + } + pub fn insert_blocks( &mut self, blocks: impl IntoIterator>, diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 9a205b39fc..98cec4fdac 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -14,7 +14,7 @@ use crate::{ }, mouse_context_menu::DeployMouseContextMenu, scroll::actions::Scroll, - EditorStyle, + EditorStyle, GutterHover, }; use clock::ReplicaId; use collections::{BTreeMap, HashMap}; @@ -213,6 +213,17 @@ impl EditorElement { } }), ); + + enum GutterHandlers {} + cx.scene.push_mouse_region( + MouseRegion::new::(view.id(), view.id() + 1, gutter_bounds).on_hover( + |hover, cx| { + cx.dispatch_action(GutterHover { + hovered: hover.started, + }) + }, + ), + ) } fn mouse_down( @@ -419,6 +430,7 @@ impl EditorElement { }); cx.dispatch_action(HoverAt { point }); + true } @@ -1815,7 +1827,7 @@ impl Element for EditorElement { hover = view.hover_state.render(&snapshot, &style, visible_rows, cx); mode = view.mode; - view.render_fold_indicators(folds, &style, cx) + view.render_fold_indicators(folds, &style, view.gutter_hovered, cx) }); if let Some((_, context_menu)) = context_menu.as_mut() { diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index c9695d4f36..87e0224286 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -643,6 +643,7 @@ pub struct CodeActions { pub struct Folds { #[serde(default)] pub indicator: Color, + pub faded_indicator: Color, pub fold_background: Color, } diff --git a/styles/src/styleTree/editor.ts b/styles/src/styleTree/editor.ts index 3518c2aba5..c50c368f26 100644 --- a/styles/src/styleTree/editor.ts +++ b/styles/src/styleTree/editor.ts @@ -49,7 +49,8 @@ export default function editor(colorScheme: ColorScheme) { }, folds: { indicator: foreground(layer, "variant"), - fold_background: foreground(layer, "variant"), + fadedIndicator: background(layer, "on"), + foldBackground: foreground(layer, "variant"), }, diff: { deleted: foreground(layer, "negative"), From 37a2ef9d41657aac83ef170f38affc330d52f55f Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Sun, 26 Feb 2023 19:20:16 -0800 Subject: [PATCH 07/14] Make chevrons and lightning bolt interactive --- crates/editor/src/editor.rs | 21 +++++++++++---------- crates/editor/src/element.rs | 4 +++- crates/theme/src/theme.rs | 11 +++++++---- styles/src/styleTree/editor.ts | 29 ++++++++++++++++++++++++++--- 4 files changed, 47 insertions(+), 18 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 5f2f27a15e..b185ddef92 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -2669,14 +2669,15 @@ impl Editor { pub fn render_code_actions_indicator( &self, style: &EditorStyle, + active: bool, cx: &mut RenderContext, ) -> Option { if self.available_code_actions.is_some() { enum CodeActions {} Some( - MouseEventHandler::::new(0, cx, |_, _| { + MouseEventHandler::::new(0, cx, |state, _| { Svg::new("icons/bolt_8.svg") - .with_color(style.code_actions.indicator) + .with_color(style.code_actions.indicator.style_for(state, active).color) .boxed() }) .with_cursor_style(CursorStyle::PointingHand) @@ -2697,7 +2698,7 @@ impl Editor { &self, fold_data: Option>, style: &EditorStyle, - gutter_hovered: bool, + _gutter_hovered: bool, cx: &mut RenderContext, ) -> Option> { enum FoldIndicators {} @@ -2712,24 +2713,24 @@ impl Editor { MouseEventHandler::::new( fold_location as usize, cx, - |_, _| -> ElementBox { + |mouse_state, _| -> ElementBox { Svg::new(match fold_status { FoldStatus::Folded => "icons/chevron_right_8.svg", FoldStatus::Foldable => "icons/chevron_down_8.svg", }) .with_color( - if gutter_hovered || fold_status == FoldStatus::Folded { - style.folds.indicator - } else { - style.folds.faded_indicator - }, + style + .folds + .indicator + .style_for(mouse_state, fold_status == FoldStatus::Folded) + .color, ) .boxed() }, ) .with_cursor_style(CursorStyle::PointingHand) .with_padding(Padding::uniform(3.)) - .on_down(MouseButton::Left, { + .on_click(MouseButton::Left, { move |_, cx| { cx.dispatch_any_action(match fold_status { FoldStatus::Folded => Box::new(UnfoldAt { diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 98cec4fdac..5b2e72999a 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -1818,8 +1818,10 @@ impl Element for EditorElement { view.render_context_menu(newest_selection_head, style.clone(), cx); } + let active = matches!(view.context_menu, Some(crate::ContextMenu::CodeActions(_))); + code_actions_indicator = view - .render_code_actions_indicator(&style, cx) + .render_code_actions_indicator(&style, active, cx) .map(|indicator| (newest_selection_head.row(), indicator)); } diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index 87e0224286..44379299ff 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -635,18 +635,21 @@ pub struct FieldEditor { #[derive(Clone, Deserialize, Default)] pub struct CodeActions { #[serde(default)] - pub indicator: Color, + pub indicator: Interactive, pub vertical_scale: f32, } #[derive(Clone, Deserialize, Default)] pub struct Folds { - #[serde(default)] - pub indicator: Color, - pub faded_indicator: Color, + pub indicator: Interactive, pub fold_background: Color, } +#[derive(Clone, Deserialize, Default)] +pub struct Indicator { + pub color: Color, +} + #[derive(Clone, Deserialize, Default)] pub struct DiffStyle { pub inserted: Color, diff --git a/styles/src/styleTree/editor.ts b/styles/src/styleTree/editor.ts index c50c368f26..14090d39e9 100644 --- a/styles/src/styleTree/editor.ts +++ b/styles/src/styleTree/editor.ts @@ -44,12 +44,35 @@ export default function editor(colorScheme: ColorScheme) { activeLineBackground: withOpacity(background(layer, "on"), 0.75), highlightedLineBackground: background(layer, "on"), codeActions: { - indicator: foreground(layer, "variant"), + indicator: { + color: foreground(layer, "variant"), + + clicked: { + color: foreground(layer, "base"), + }, + hover: { + color: foreground(layer, "on"), + }, + active: { + color: foreground(layer, "on"), + }, + }, verticalScale: 0.55, }, folds: { - indicator: foreground(layer, "variant"), - fadedIndicator: background(layer, "on"), + indicator: { + color: foreground(layer, "variant"), + + clicked: { + color: foreground(layer, "base"), + }, + hover: { + color: foreground(layer, "on"), + }, + active: { + color: foreground(layer, "on"), + }, + }, foldBackground: foreground(layer, "variant"), }, diff: { From e0f553c0f5454f9f7395dde9d9d68b535c8c91de Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Mon, 27 Feb 2023 11:38:09 -0800 Subject: [PATCH 08/14] WIp --- crates/editor/src/display_map.rs | 9 ++---- crates/editor/src/editor.rs | 47 ++++++++++++++++---------------- crates/util/src/util.rs | 29 ++------------------ 3 files changed, 29 insertions(+), 56 deletions(-) diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index ced92c6e13..45ecc37b77 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -746,7 +746,7 @@ impl DisplayRow { DisplayRow(display_row) } - pub fn to_line_span(&self, display_map: &DisplaySnapshot) -> Range { + pub fn to_points(&self, display_map: &DisplaySnapshot) -> Range { self.start()..self.end(&display_map) } @@ -762,11 +762,6 @@ impl DisplayRow { } } - // TODO: Remove and use next_row - fn next_unchecked(&self) -> Option { - Some(DisplayRow(self.0 + 1)) - } - pub fn next_rows( &self, display_map: &DisplaySnapshot, @@ -788,7 +783,7 @@ impl DisplayRow { if current == None { current = Some(start); } else { - current = current.unwrap().next_unchecked(); + current = Some(DisplayRow(current.unwrap().0 + 1)) } if current.unwrap().0 > end_row.0 { None diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index b185ddef92..3d88c69ebd 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -85,7 +85,7 @@ use std::{ }; pub use sum_tree::Bias; use theme::{DiagnosticStyle, Theme}; -use util::{post_inc, MapRangeEndsExt, RangeExt, ResultExt, TryFutureExt}; +use util::{post_inc, RangeExt, ResultExt, TryFutureExt}; use workspace::{ItemNavHistory, ViewId, Workspace, WorkspaceId}; use crate::git::diff_hunk_to_display; @@ -5738,7 +5738,7 @@ impl Editor { let fold_range = display_map .foldable_range(DisplayRow::new(row)) .map(|range| { - range.map_range(|display_point| display_point.to_point(&display_map)) + range.start.to_point(&display_map)..range.end.to_point(&display_map) }); if let Some(fold_range) = fold_range { @@ -5761,29 +5761,23 @@ impl Editor { let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); if let Some(fold_range) = display_map.foldable_range(display_row) { - let autoscroll = self.selections_intersect(&fold_range, &display_map, cx); + let autoscroll = { + let selections = self.selections.all::(cx); - let point_range = - fold_range.map_range(|display_point| display_point.to_point(&display_map)); + selections.iter().any(|selection| { + let display_range = selection.display_range(&display_map); - self.fold_ranges(std::iter::once(point_range), autoscroll, cx); + fold_range.overlaps(&display_range) + }) + }; + + let fold_range = + fold_range.start.to_point(&display_map)..fold_range.end.to_point(&display_map); + + self.fold_ranges(std::iter::once(fold_range), autoscroll, cx); } } - fn selections_intersect( - &mut self, - range: &Range, - display_map: &DisplaySnapshot, - cx: &mut ViewContext, - ) -> bool { - let selections = self.selections.all::(cx); - - selections.iter().any(|selection| { - let display_range = selection.display_range(display_map); - range.contains(&display_range.start) || range.contains(&display_range.end) - }) - } - pub fn unfold_lines(&mut self, _: &UnfoldLines, cx: &mut ViewContext) { let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); let buffer = &display_map.buffer_snapshot; @@ -5805,12 +5799,19 @@ impl Editor { pub fn unfold_at(&mut self, fold_at: &UnfoldAt, cx: &mut ViewContext) { let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); - let unfold_range = fold_at.display_row.to_line_span(&display_map); + let unfold_range = fold_at.display_row.to_points(&display_map); - let autoscroll = self.selections_intersect(&unfold_range, &display_map, cx); + let autoscroll = { + let selections = self.selections.all::(cx); + selections.iter().any(|selection| { + let display_range = selection.display_range(&display_map); - let unfold_range = unfold_range.map_range(|endpoint| endpoint.to_point(&display_map)); + unfold_range.overlaps(&display_range) + }) + }; + let unfold_range = + unfold_range.start.to_point(&display_map)..unfold_range.end.to_point(&display_map); self.unfold_ranges(std::iter::once(unfold_range), true, autoscroll, cx) } diff --git a/crates/util/src/util.rs b/crates/util/src/util.rs index c0f08ac32e..5ecf889f9b 100644 --- a/crates/util/src/util.rs +++ b/crates/util/src/util.rs @@ -145,23 +145,6 @@ where } } -pub trait MapRangeEndsExt { - fn map_range(self, f: F) -> Range - where - Self: Sized, - F: Fn(R) -> T; -} - -impl MapRangeEndsExt for Range { - fn map_range(self, f: F) -> Range - where - Self: Sized, - F: Fn(R) -> T, - { - f(self.start)..f(self.end) - } -} - pub struct LogErrorFuture(F, log::Level); impl Future for LogErrorFuture @@ -254,7 +237,7 @@ macro_rules! iife { }; } -/// Async lImmediately invoked function expression. Good for using the ? operator +/// Async Immediately invoked function expression. Good for using the ? operator /// in functions which do not return an Option or Result. Async version of above #[macro_export] macro_rules! async_iife { @@ -279,10 +262,7 @@ impl RangeExt for Range { } fn overlaps(&self, other: &Range) -> bool { - self.contains(&other.start) - || self.contains(&other.end) - || other.contains(&self.start) - || other.contains(&self.end) + self.start < other.end && other.start < self.end } } @@ -296,10 +276,7 @@ impl RangeExt for RangeInclusive { } fn overlaps(&self, other: &Range) -> bool { - self.contains(&other.start) - || self.contains(&other.end) - || other.contains(&self.start()) - || other.contains(&self.end()) + self.start() < &other.end && &other.start <= self.end() } } From 9b8adecf05001092c3682b12b4d0bbfbb8a56c91 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Mon, 27 Feb 2023 14:46:25 -0800 Subject: [PATCH 09/14] Adjusted code-folding behavior --- crates/editor/src/editor.rs | 89 ++++++++++++++++++++-------------- crates/editor/src/element.rs | 9 +++- crates/theme/src/theme.rs | 3 ++ styles/src/styleTree/editor.ts | 3 ++ 4 files changed, 66 insertions(+), 38 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 3d88c69ebd..73de63bed2 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -2698,52 +2698,67 @@ impl Editor { &self, fold_data: Option>, style: &EditorStyle, - _gutter_hovered: bool, + gutter_hovered: bool, + line_height: f32, + gutter_margin: f32, cx: &mut RenderContext, ) -> Option> { enum FoldIndicators {} + let style = style.folds.clone(); + fold_data.map(|fold_data| { fold_data .iter() .copied() - .map(|(fold_location, fold_status)| { - ( - fold_location, - MouseEventHandler::::new( - fold_location as usize, - cx, - |mouse_state, _| -> ElementBox { - Svg::new(match fold_status { - FoldStatus::Folded => "icons/chevron_right_8.svg", - FoldStatus::Foldable => "icons/chevron_down_8.svg", - }) - .with_color( - style - .folds - .indicator - .style_for(mouse_state, fold_status == FoldStatus::Folded) - .color, - ) - .boxed() - }, + .filter_map(|(fold_location, fold_status)| { + (gutter_hovered || fold_status == FoldStatus::Folded).then(|| { + ( + fold_location, + MouseEventHandler::::new( + fold_location as usize, + cx, + |mouse_state, _| -> ElementBox { + Svg::new(match fold_status { + FoldStatus::Folded => style.folded_icon.clone(), + FoldStatus::Foldable => style.foldable_icon.clone(), + }) + .with_color( + style + .indicator + .style_for( + mouse_state, + fold_status == FoldStatus::Folded, + ) + .color, + ) + .constrained() + .with_width(style.icon_width) + .aligned() + .constrained() + .with_height(line_height) + .with_width(gutter_margin) + .aligned() + .boxed() + }, + ) + .with_cursor_style(CursorStyle::PointingHand) + .with_padding(Padding::uniform(3.)) + .on_click(MouseButton::Left, { + move |_, cx| { + cx.dispatch_any_action(match fold_status { + FoldStatus::Folded => Box::new(UnfoldAt { + display_row: DisplayRow::new(fold_location), + }), + FoldStatus::Foldable => Box::new(FoldAt { + display_row: DisplayRow::new(fold_location), + }), + }); + } + }) + .boxed(), ) - .with_cursor_style(CursorStyle::PointingHand) - .with_padding(Padding::uniform(3.)) - .on_click(MouseButton::Left, { - move |_, cx| { - cx.dispatch_any_action(match fold_status { - FoldStatus::Folded => Box::new(UnfoldAt { - display_row: DisplayRow::new(fold_location), - }), - FoldStatus::Foldable => Box::new(FoldAt { - display_row: DisplayRow::new(fold_location), - }), - }); - } - }) - .boxed(), - ) + }) }) .collect() }) diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 5b2e72999a..5f2a3670fe 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -1829,7 +1829,14 @@ impl Element for EditorElement { hover = view.hover_state.render(&snapshot, &style, visible_rows, cx); mode = view.mode; - view.render_fold_indicators(folds, &style, view.gutter_hovered, cx) + view.render_fold_indicators( + folds, + &style, + view.gutter_hovered, + line_height, + gutter_margin, + cx, + ) }); if let Some((_, context_menu)) = context_menu.as_mut() { diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index 44379299ff..06122b9dda 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -643,6 +643,9 @@ pub struct CodeActions { pub struct Folds { pub indicator: Interactive, pub fold_background: Color, + pub icon_width: f32, + pub folded_icon: String, + pub foldable_icon: String, } #[derive(Clone, Deserialize, Default)] diff --git a/styles/src/styleTree/editor.ts b/styles/src/styleTree/editor.ts index 14090d39e9..c8334d1cc0 100644 --- a/styles/src/styleTree/editor.ts +++ b/styles/src/styleTree/editor.ts @@ -60,6 +60,9 @@ export default function editor(colorScheme: ColorScheme) { verticalScale: 0.55, }, folds: { + iconWidth: 8, + foldedIcon: "icons/chevron_right_8.svg", + foldableIcon: "icons/chevron_down_8.svg", indicator: { color: foreground(layer, "variant"), From c397fd9a715c6443fd8b7dcc1e0b05bbbfe6e685 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Tue, 28 Feb 2023 15:49:34 -0800 Subject: [PATCH 10/14] Added click regions and cursor styles --- crates/editor/src/display_map/fold_map.rs | 2 +- crates/editor/src/editor.rs | 162 ++++++++++++++++++---- crates/editor/src/element.rs | 148 ++++++++++++++++++-- 3 files changed, 274 insertions(+), 38 deletions(-) diff --git a/crates/editor/src/display_map/fold_map.rs b/crates/editor/src/display_map/fold_map.rs index a2f7005e3d..0b8721b1a4 100644 --- a/crates/editor/src/display_map/fold_map.rs +++ b/crates/editor/src/display_map/fold_map.rs @@ -370,7 +370,7 @@ impl FoldMap { } if fold.end > fold.start { - let output_text = "…"; + let output_text = "⋯"; new_transforms.push( Transform { summary: TransformSummary { diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 73de63bed2..92338d7b37 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -39,10 +39,11 @@ use gpui::{ impl_actions, impl_internal_actions, keymap_matcher::KeymapContext, platform::CursorStyle, + scene::MouseClick, serde_json::json, AnyViewHandle, AppContext, AsyncAppContext, ClipboardItem, Element, ElementBox, Entity, - ModelHandle, MouseButton, MutableAppContext, RenderContext, Subscription, Task, View, - ViewContext, ViewHandle, WeakViewHandle, + EventContext, ModelHandle, MouseButton, MutableAppContext, RenderContext, Subscription, Task, + View, ViewContext, ViewHandle, WeakViewHandle, }; use highlight_matching_bracket::refresh_matching_bracket_highlights; use hover_popover::{hide_hover, HideHover, HoverState}; @@ -457,6 +458,8 @@ type CompletionId = usize; type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor; type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option; +type TextClickedCallback = + fn(&MouseClick, &Range, &EditorSnapshot, &mut EventContext); pub struct Editor { handle: WeakViewHandle, @@ -485,6 +488,7 @@ pub struct Editor { highlighted_rows: Option>, #[allow(clippy::type_complexity)] background_highlights: BTreeMap Color, Vec>)>, + clickable_text: BTreeMap>)>, nav_history: Option, context_menu: Option, mouse_context_menu: ViewHandle, @@ -1155,6 +1159,7 @@ impl Editor { placeholder_text: None, highlighted_rows: None, background_highlights: Default::default(), + clickable_text: Default::default(), nav_history: None, context_menu: None, mouse_context_menu: cx.add_view(context_menu::ContextMenu::new), @@ -5776,15 +5781,11 @@ impl Editor { let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); if let Some(fold_range) = display_map.foldable_range(display_row) { - let autoscroll = { - let selections = self.selections.all::(cx); - - selections.iter().any(|selection| { - let display_range = selection.display_range(&display_map); - - fold_range.overlaps(&display_range) - }) - }; + let autoscroll = self + .selections + .all::(cx) + .iter() + .any(|selection| fold_range.overlaps(&selection.display_range(&display_map))); let fold_range = fold_range.start.to_point(&display_map)..fold_range.end.to_point(&display_map); @@ -5816,14 +5817,11 @@ impl Editor { let unfold_range = fold_at.display_row.to_points(&display_map); - let autoscroll = { - let selections = self.selections.all::(cx); - selections.iter().any(|selection| { - let display_range = selection.display_range(&display_map); - - unfold_range.overlaps(&display_range) - }) - }; + let autoscroll = self + .selections + .all::(cx) + .iter() + .any(|selection| unfold_range.overlaps(&selection.display_range(&display_map))); let unfold_range = unfold_range.start.to_point(&display_map)..unfold_range.end.to_point(&display_map); @@ -5836,7 +5834,7 @@ impl Editor { self.fold_ranges(ranges, true, cx); } - pub fn fold_ranges( + pub fn fold_ranges( &mut self, ranges: impl IntoIterator>, auto_scroll: bool, @@ -5844,15 +5842,33 @@ impl Editor { ) { let mut ranges = ranges.into_iter().peekable(); if ranges.peek().is_some() { - self.display_map.update(cx, |map, cx| map.fold(ranges, cx)); + let ranges = ranges.collect_vec(); + + self.display_map + .update(cx, |map, cx| map.fold(ranges.iter().cloned(), cx)); + if auto_scroll { self.request_autoscroll(Autoscroll::fit(), cx); } + + let snapshot = self.snapshot(cx); + let anchor_ranges = offset_to_anchors(ranges, &snapshot); + + self.change_click_ranges::(cx, |click_ranges| { + for range in anchor_ranges { + if let Err(idx) = click_ranges.binary_search_by(|click_range| { + click_range.cmp(&range, &snapshot.buffer_snapshot) + }) { + click_ranges.insert(idx, range) + } + } + }); + cx.notify(); } } - pub fn unfold_ranges( + pub fn unfold_ranges( &mut self, ranges: impl IntoIterator>, inclusive: bool, @@ -5861,11 +5877,35 @@ impl Editor { ) { let mut ranges = ranges.into_iter().peekable(); if ranges.peek().is_some() { - self.display_map - .update(cx, |map, cx| map.unfold(ranges, inclusive, cx)); + let ranges = ranges.collect_vec(); + + self.display_map.update(cx, |map, cx| { + map.unfold(ranges.iter().cloned(), inclusive, cx) + }); if auto_scroll { self.request_autoscroll(Autoscroll::fit(), cx); } + + let snapshot = self.snapshot(cx); + let anchor_ranges = offset_to_anchors(ranges, &snapshot); + + self.change_click_ranges::(cx, |click_ranges| { + for range in anchor_ranges { + let range_point = range.start.to_point(&snapshot.buffer_snapshot); + // Fold and unfold ranges start at different points in the row. + // But their rows do match, so we can use that to detect sameness. + if let Ok(idx) = click_ranges.binary_search_by(|click_range| { + click_range + .start + .to_point(&snapshot.buffer_snapshot) + .row + .cmp(&range_point.row) + }) { + click_ranges.remove(idx); + } + } + }); + cx.notify(); } } @@ -5991,6 +6031,61 @@ impl Editor { } } + pub fn change_click_ranges( + &mut self, + cx: &mut ViewContext, + change: impl FnOnce(&mut Vec>), + ) { + let mut ranges = self + .clickable_text + .remove(&TypeId::of::()) + .map(|click_range| click_range.1) + .unwrap_or_default(); + + change(&mut ranges); + + self.clickable_text + .insert(TypeId::of::(), (T::click_handler, ranges)); + + cx.notify(); + } + + pub fn click_ranges_in_range( + &self, + search_range: Range, + display_snapshot: &DisplaySnapshot, + ) -> Vec<(Range, TextClickedCallback)> { + let mut results = Vec::new(); + let buffer = &display_snapshot.buffer_snapshot; + for (callback, ranges) in self.clickable_text.values() { + let start_ix = match ranges.binary_search_by(|probe| { + let cmp = probe.end.cmp(&search_range.start, buffer); + if cmp.is_gt() { + Ordering::Greater + } else { + Ordering::Less + } + }) { + Ok(i) | Err(i) => i, + }; + for range in &ranges[start_ix..] { + if range.start.cmp(&search_range.end, buffer).is_ge() { + break; + } + let start = range + .start + .to_point(buffer) + .to_display_point(display_snapshot); + let end = range + .end + .to_point(buffer) + .to_display_point(display_snapshot); + results.push((start..end, *callback)) + } + } + results + } + pub fn highlight_rows(&mut self, rows: Option>) { self.highlighted_rows = rows; } @@ -6369,6 +6464,25 @@ fn ending_row(next_selection: &Selection, display_map: &DisplaySnapshot) } } +fn offset_to_anchors< + 'snapshot, + 'iter: 'snapshot, + T: ToOffset, + I: IntoIterator> + 'iter, +>( + ranges: I, + snapshot: &'snapshot EditorSnapshot, +) -> impl Iterator> + 'snapshot { + ranges.into_iter().map(|range| { + snapshot + .buffer_snapshot + .anchor_at(range.start.to_offset(&snapshot.buffer_snapshot), Bias::Left) + ..snapshot + .buffer_snapshot + .anchor_at(range.end.to_offset(&snapshot.buffer_snapshot), Bias::Right) + }) +} + impl EditorSnapshot { pub fn language_at(&self, position: T) -> Option<&Arc> { self.display_snapshot.buffer_snapshot.language_at(position) diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 5f2a3670fe..4cc1347fd7 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -4,7 +4,7 @@ use super::{ ToPoint, MAX_LINE_LEN, }; use crate::{ - display_map::{BlockStyle, DisplaySnapshot, FoldStatus, TransformBlock}, + display_map::{BlockStyle, DisplayRow, DisplaySnapshot, FoldStatus, TransformBlock}, git::{diff_hunk_to_display, DisplayDiffHunk}, hover_popover::{ HideHover, HoverAt, HOVER_POPOVER_GAP, MIN_POPOVER_CHARACTER_WIDTH, MIN_POPOVER_LINE_HEIGHT, @@ -14,7 +14,7 @@ use crate::{ }, mouse_context_menu::DeployMouseContextMenu, scroll::actions::Scroll, - EditorStyle, GutterHover, + EditorStyle, GutterHover, TextClickedCallback, UnfoldAt, }; use clock::ReplicaId; use collections::{BTreeMap, HashMap}; @@ -30,6 +30,7 @@ use gpui::{ }, json::{self, ToJson}, platform::CursorStyle, + scene::MouseClick, text_layout::{self, Line, RunStyle, TextLayoutCache}, AppContext, Axis, Border, CursorRegion, Element, ElementBox, EventContext, LayoutContext, Modifiers, MouseButton, MouseButtonEvent, MouseMovedEvent, MouseRegion, MutableAppContext, @@ -115,6 +116,7 @@ impl EditorElement { fn attach_mouse_handlers( view: &WeakViewHandle, position_map: &Arc, + click_ranges: Arc, TextClickedCallback)>>, has_popovers: bool, visible_bounds: RectF, text_bounds: RectF, @@ -211,6 +213,20 @@ impl EditorElement { cx.propagate_event() } } + }) + .on_click(MouseButton::Left, { + let position_map = position_map.clone(); + move |e, cx| { + let point = + position_to_display_point(e.position, text_bounds, &position_map); + if let Some(point) = point { + for (range, callback) in click_ranges.iter() { + if range.contains(&point) { + callback(&e, range, &position_map.snapshot, cx) + } + } + } + } }), ); @@ -412,16 +428,7 @@ impl EditorElement { ) -> bool { // This will be handled more correctly once https://github.com/zed-industries/zed/issues/1218 is completed // Don't trigger hover popover if mouse is hovering over context menu - let point = if text_bounds.contains_point(position) { - let (point, target_point) = position_map.point_for_position(text_bounds, position); - if point == target_point { - Some(point) - } else { - None - } - } else { - None - }; + let point = position_to_display_point(position, text_bounds, position_map); cx.dispatch_action(UpdateGoToDefinitionLink { point, @@ -702,6 +709,7 @@ impl EditorElement { let max_glyph_width = layout.position_map.em_width; let scroll_left = scroll_position.x() * max_glyph_width; let content_origin = bounds.origin() + vec2f(layout.gutter_margin, 0.); + let line_end_overshoot = 0.15 * layout.position_map.line_height; cx.scene.push_layer(Some(bounds)); @@ -714,12 +722,29 @@ impl EditorElement { }, }); + for (range, _) in layout.click_ranges.iter() { + for bound in range_to_bounds( + range, + content_origin, + scroll_left, + scroll_top, + &layout.visible_display_row_range, + line_end_overshoot, + &layout.position_map, + ) { + cx.scene.push_cursor_region(CursorRegion { + bounds: bound, + style: CursorStyle::PointingHand, + }); + } + } + for (range, color) in &layout.highlighted_ranges { self.paint_highlighted_range( range.clone(), *color, 0., - 0.15 * layout.position_map.line_height, + line_end_overshoot, layout, content_origin, scroll_top, @@ -1650,6 +1675,7 @@ impl Element for EditorElement { let mut active_rows = BTreeMap::new(); let mut highlighted_rows = None; let mut highlighted_ranges = Vec::new(); + let mut click_ranges = Vec::new(); let mut show_scrollbars = false; let mut include_root = false; let mut is_singleton = false; @@ -1662,6 +1688,7 @@ impl Element for EditorElement { let theme = cx.global::().theme.as_ref(); highlighted_ranges = view.background_highlights_in_range(start_anchor..end_anchor, &display_map, theme); + click_ranges = view.click_ranges_in_range(start_anchor..end_anchor, &display_map); let mut remote_selections = HashMap::default(); for (replica_id, line_mode, cursor_shape, selection) in display_map @@ -1917,6 +1944,7 @@ impl Element for EditorElement { active_rows, highlighted_rows, highlighted_ranges, + click_ranges: Arc::new(click_ranges), line_number_layouts, display_hunks, blocks, @@ -1948,6 +1976,7 @@ impl Element for EditorElement { Self::attach_mouse_handlers( &self.view, &layout.position_map, + layout.click_ranges.clone(), // No need to clone the vec layout.hover_popovers.is_some(), visible_bounds, text_bounds, @@ -2045,6 +2074,7 @@ pub struct LayoutState { display_hunks: Vec, blocks: Vec, highlighted_ranges: Vec<(Range, Color)>, + click_ranges: Arc, TextClickedCallback)>>, selections: Vec<(ReplicaId, Vec)>, scrollbar_row_range: Range, show_scrollbars: bool, @@ -2351,6 +2381,98 @@ impl HighlightedRange { } } +pub trait ClickRange: 'static { + fn click_handler( + click: &MouseClick, + range: &Range, + snapshot: &EditorSnapshot, + cx: &mut EventContext, + ); +} + +pub enum FoldMarker {} +impl ClickRange for FoldMarker { + fn click_handler( + _click: &MouseClick, + range: &Range, + _snapshot: &EditorSnapshot, + cx: &mut EventContext, + ) { + cx.dispatch_action(UnfoldAt { + display_row: DisplayRow(range.start.row()), + }) + } +} + +pub fn position_to_display_point( + position: Vector2F, + text_bounds: RectF, + position_map: &PositionMap, +) -> Option { + if text_bounds.contains_point(position) { + let (point, target_point) = position_map.point_for_position(text_bounds, position); + if point == target_point { + Some(point) + } else { + None + } + } else { + None + } +} + +pub fn range_to_bounds( + range: &Range, + content_origin: Vector2F, + scroll_left: f32, + scroll_top: f32, + visible_row_range: &Range, + line_end_overshoot: f32, + position_map: &PositionMap, +) -> impl Iterator { + let mut bounds: SmallVec<[RectF; 1]> = SmallVec::new(); + + if range.start == range.end { + return bounds.into_iter(); + } + + let start_row = visible_row_range.start; + let end_row = visible_row_range.end; + + let row_range = if range.end.column() == 0 { + cmp::max(range.start.row(), start_row)..cmp::min(range.end.row(), end_row) + } else { + cmp::max(range.start.row(), start_row)..cmp::min(range.end.row() + 1, end_row) + }; + + let first_y = + content_origin.y() + row_range.start as f32 * position_map.line_height - scroll_top; + + for (idx, row) in row_range.enumerate() { + let line_layout = &position_map.line_layouts[(row - start_row) as usize]; + + let start_x = if row == range.start.row() { + content_origin.x() + line_layout.x_for_index(range.start.column() as usize) + - scroll_left + } else { + content_origin.x() - scroll_left + }; + + let end_x = if row == range.end.row() { + content_origin.x() + line_layout.x_for_index(range.end.column() as usize) - scroll_left + } else { + content_origin.x() + line_layout.width() + line_end_overshoot - scroll_left + }; + + bounds.push(RectF::from_points( + vec2f(start_x, first_y + position_map.line_height * idx as f32), + vec2f(end_x, first_y + position_map.line_height * (idx + 1) as f32), + )) + } + + bounds.into_iter() +} + pub fn scale_vertical_mouse_autoscroll_delta(delta: f32) -> f32 { delta.powf(1.5) / 100.0 } From b53d1eef713d86fc59e63a4519bdcf8d9fa21f5d Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Tue, 28 Feb 2023 16:33:37 -0800 Subject: [PATCH 11/14] Added background styling of the ... --- crates/editor/src/editor.rs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 92338d7b37..a366b7333f 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -5863,6 +5863,12 @@ impl Editor { } } }); + let click_ranges = self.clone_click_ranges::(); + self.highlight_background::( + click_ranges, + |theme| theme.editor.document_highlight_write_background, + cx, + ); cx.notify(); } @@ -5905,6 +5911,12 @@ impl Editor { } } }); + let click_ranges = self.clone_click_ranges::(); + self.highlight_background::( + click_ranges, + |theme| theme.editor.document_highlight_write_background, + cx, + ); cx.notify(); } @@ -6031,6 +6043,13 @@ impl Editor { } } + pub fn clone_click_ranges(&self) -> Vec> { + self.clickable_text + .get(&TypeId::of::()) + .map(|click_range| click_range.1.clone()) + .unwrap_or_default() + } + pub fn change_click_ranges( &mut self, cx: &mut ViewContext, From f8401394f5c52ac7a5737a17b872b4ce29dffcd9 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Tue, 28 Feb 2023 17:12:48 -0800 Subject: [PATCH 12/14] Removed DisplayRow abstraction --- crates/editor/src/display_map.rs | 127 +++++++++---------------------- crates/editor/src/editor.rs | 45 ++++++----- crates/editor/src/element.rs | 4 +- crates/vim/src/normal.rs | 6 +- 4 files changed, 67 insertions(+), 115 deletions(-) diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index 45ecc37b77..974ade03f2 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -12,7 +12,6 @@ use gpui::{ Entity, ModelContext, ModelHandle, }; use language::{OffsetUtf16, Point, Subscription as BufferSubscription}; -use serde::{Deserialize, Serialize}; use settings::Settings; use std::{any::TypeId, fmt::Debug, num::NonZeroU32, ops::Range, sync::Arc}; use sum_tree::{Bias, TreeMap}; @@ -576,10 +575,10 @@ impl DisplaySnapshot { result } - pub fn line_indent(&self, display_row: DisplayRow) -> (u32, bool) { + pub fn line_indent(&self, display_row: u32) -> (u32, bool) { let mut indent = 0; let mut is_blank = true; - for (c, _) in self.chars_at(display_row.start()) { + for (c, _) in self.chars_at(DisplayPoint::new(display_row, 0)) { if c == ' ' { indent += 1; } else { @@ -599,8 +598,7 @@ impl DisplaySnapshot { } pub fn fold_for_line(self: &Self, display_row: u32) -> Option { - let display_row_typed = DisplayRow::new(display_row); - if self.is_foldable(display_row_typed) { + if self.is_foldable(display_row) { Some(FoldStatus::Foldable) } else if self.is_line_folded(display_row) { Some(FoldStatus::Folded) @@ -609,40 +607,44 @@ impl DisplaySnapshot { } } - pub fn is_foldable(self: &Self, row: DisplayRow) -> bool { - if let Some(next_lines) = row.next_rows(self) { - let (start_indent, is_blank) = self.line_indent(row); - if is_blank { - return false; - } + pub fn is_foldable(self: &Self, row: u32) -> bool { + let max_point = self.max_point(); + if row >= max_point.row() { + return false; + } - for display_row in next_lines { - let (indent, is_blank) = self.line_indent(display_row); - if !is_blank { - return indent > start_indent; - } + let (start_indent, is_blank) = self.line_indent(row); + if is_blank { + return false; + } + + for display_row in next_rows(row, self) { + let (indent, is_blank) = self.line_indent(display_row); + if !is_blank { + return indent > start_indent; } } + return false; } - pub fn foldable_range(self: &Self, row: DisplayRow) -> Option> { - let start = row.end(&self); + pub fn foldable_range(self: &Self, row: u32) -> Option> { + let start = DisplayPoint::new(row, self.line_len(row)); if self.is_foldable(row) && !self.is_line_folded(start.row()) { let (start_indent, _) = self.line_indent(row); let max_point = self.max_point(); let mut end = None; - for row in row.next_rows(self).unwrap() { + for row in next_rows(row, self) { let (indent, is_blank) = self.line_indent(row); if !is_blank && indent <= start_indent { - end = row.previous_row(); + end = Some(DisplayPoint::new(row - 1, self.line_len(row - 1))); break; } } - let end = end.map(|end_row| end_row.end(self)).unwrap_or(max_point); + let end = end.unwrap_or(max_point); Some(start..end) } else { None @@ -736,79 +738,22 @@ impl ToDisplayPoint for Anchor { } } -#[derive(Copy, Clone, Default, Eq, Ord, PartialOrd, PartialEq, Deserialize, Serialize)] -#[repr(transparent)] -pub struct DisplayRow(pub u32); - -// TODO: Move display_map check into new, and then remove it from everywhere else -impl DisplayRow { - pub fn new(display_row: u32) -> Self { - DisplayRow(display_row) - } - - pub fn to_points(&self, display_map: &DisplaySnapshot) -> Range { - self.start()..self.end(&display_map) - } - - pub fn previous_row(&self) -> Option { - self.0.checked_sub(1).map(|prev_row| DisplayRow(prev_row)) - } - - pub fn next_row(&self, display_map: &DisplaySnapshot) -> Option { - if self.0 + 1 > display_map.max_point().row() { +pub fn next_rows(display_row: u32, display_map: &DisplaySnapshot) -> impl Iterator { + let max_row = display_map.max_point().row(); + let start_row = display_row + 1; + let mut current = None; + std::iter::from_fn(move || { + if current == None { + current = Some(start_row); + } else { + current = Some(current.unwrap() + 1) + } + if current.unwrap() > max_row { None } else { - Some(DisplayRow(self.0 + 1)) + current } - } - - pub fn next_rows( - &self, - display_map: &DisplaySnapshot, - ) -> Option> { - self.next_row(display_map) - .and_then(|next_row| next_row.span_to(display_map.max_point())) - } - - pub fn span_to>( - &self, - end_row: I, - ) -> Option> { - let end_row = end_row.into(); - if self.0 <= end_row.0 { - let start = *self; - let mut current = None; - - Some(std::iter::from_fn(move || { - if current == None { - current = Some(start); - } else { - current = Some(DisplayRow(current.unwrap().0 + 1)) - } - if current.unwrap().0 > end_row.0 { - None - } else { - current - } - })) - } else { - None - } - } - - pub fn start(&self) -> DisplayPoint { - DisplayPoint::new(self.0, 0) - } - - pub fn end(&self, display_map: &DisplaySnapshot) -> DisplayPoint { - DisplayPoint::new(self.0, display_map.line_len(self.0)) - } -} - -impl From for DisplayRow { - fn from(value: DisplayPoint) -> Self { - DisplayRow(value.row()) - } + }) } #[cfg(test)] diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index a366b7333f..fd68bc9a32 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -164,12 +164,12 @@ pub struct ToggleComments { #[derive(Clone, Default, Deserialize, PartialEq)] pub struct FoldAt { - pub display_row: DisplayRow, + pub display_row: u32, } #[derive(Clone, Default, Deserialize, PartialEq)] pub struct UnfoldAt { - pub display_row: DisplayRow, + pub display_row: u32, } #[derive(Clone, Default, Deserialize, PartialEq)] @@ -2753,10 +2753,10 @@ impl Editor { move |_, cx| { cx.dispatch_any_action(match fold_status { FoldStatus::Folded => Box::new(UnfoldAt { - display_row: DisplayRow::new(fold_location), + display_row: fold_location, }), FoldStatus::Foldable => Box::new(FoldAt { - display_row: DisplayRow::new(fold_location), + display_row: fold_location, }), }); } @@ -5755,11 +5755,9 @@ impl Editor { let buffer_start_row = range.start.to_point(&display_map).row; for row in (0..=range.end.row()).rev() { - let fold_range = display_map - .foldable_range(DisplayRow::new(row)) - .map(|range| { - range.start.to_point(&display_map)..range.end.to_point(&display_map) - }); + let fold_range = display_map.foldable_range(row).map(|range| { + range.start.to_point(&display_map)..range.end.to_point(&display_map) + }); if let Some(fold_range) = fold_range { if fold_range.end.row >= buffer_start_row { @@ -5809,23 +5807,32 @@ impl Editor { start..end }) .collect::>(); + self.unfold_ranges(ranges, true, true, cx); } - pub fn unfold_at(&mut self, fold_at: &UnfoldAt, cx: &mut ViewContext) { + pub fn unfold_at(&mut self, unfold_at: &UnfoldAt, cx: &mut ViewContext) { let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); - let unfold_range = fold_at.display_row.to_points(&display_map); + let intersection_range = DisplayPoint::new(unfold_at.display_row, 0) + ..DisplayPoint::new( + unfold_at.display_row, + display_map.line_len(unfold_at.display_row), + ); - let autoscroll = self - .selections - .all::(cx) - .iter() - .any(|selection| unfold_range.overlaps(&selection.display_range(&display_map))); + let autoscroll = + self.selections.all::(cx).iter().any(|selection| { + intersection_range.overlaps(&selection.display_range(&display_map)) + }); - let unfold_range = - unfold_range.start.to_point(&display_map)..unfold_range.end.to_point(&display_map); - self.unfold_ranges(std::iter::once(unfold_range), true, autoscroll, cx) + let display_point = DisplayPoint::new(unfold_at.display_row, 0).to_point(&display_map); + + let mut point_range = display_point..display_point; + + point_range.start.column = 0; + point_range.end.column = display_map.buffer_snapshot.line_len(point_range.end.row); + + self.unfold_ranges(std::iter::once(point_range), true, autoscroll, cx) } pub fn fold_selected_ranges(&mut self, _: &FoldSelectedRanges, cx: &mut ViewContext) { diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 4cc1347fd7..c777c61f41 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -4,7 +4,7 @@ use super::{ ToPoint, MAX_LINE_LEN, }; use crate::{ - display_map::{BlockStyle, DisplayRow, DisplaySnapshot, FoldStatus, TransformBlock}, + display_map::{BlockStyle, DisplaySnapshot, FoldStatus, TransformBlock}, git::{diff_hunk_to_display, DisplayDiffHunk}, hover_popover::{ HideHover, HoverAt, HOVER_POPOVER_GAP, MIN_POPOVER_CHARACTER_WIDTH, MIN_POPOVER_LINE_HEIGHT, @@ -2399,7 +2399,7 @@ impl ClickRange for FoldMarker { cx: &mut EventContext, ) { cx.dispatch_action(UnfoldAt { - display_row: DisplayRow(range.start.row()), + display_row: range.start.row(), }) } } diff --git a/crates/vim/src/normal.rs b/crates/vim/src/normal.rs index 92ff9e5fb9..c48a00f56d 100644 --- a/crates/vim/src/normal.rs +++ b/crates/vim/src/normal.rs @@ -12,7 +12,7 @@ use crate::{ }; use collections::{HashMap, HashSet}; use editor::{ - display_map::{DisplayRow, ToDisplayPoint}, + display_map::ToDisplayPoint, scroll::{autoscroll::Autoscroll, scroll_amount::ScrollAmount}, Anchor, Bias, ClipboardSelection, DisplayPoint, Editor, }; @@ -195,7 +195,7 @@ fn insert_line_above(_: &mut Workspace, _: &InsertLineAbove, cx: &mut ViewContex .map(|selection| selection.start.row()) .collect(); let edits = selection_start_rows.into_iter().map(|row| { - let (indent, _) = map.line_indent(DisplayRow::new(row)); + let (indent, _) = map.line_indent(row); let start_of_line = map .clip_point(DisplayPoint::new(row, 0), Bias::Left) .to_point(&map); @@ -229,7 +229,7 @@ fn insert_line_below(_: &mut Workspace, _: &InsertLineBelow, cx: &mut ViewContex .map(|selection| selection.end.row()) .collect(); let edits = selection_end_rows.into_iter().map(|row| { - let (indent, _) = map.line_indent(DisplayRow::new(row)); + let (indent, _) = map.line_indent(row); let end_of_line = map .clip_point(DisplayPoint::new(row, map.line_len(row)), Bias::Left) .to_point(&map); From 6cf62a5b02ba0aa59e25984182fb6535820a9365 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Tue, 28 Feb 2023 17:17:29 -0800 Subject: [PATCH 13/14] Update tests to use new fold indicator --- crates/editor/src/display_map.rs | 4 +-- crates/editor/src/display_map/fold_map.rs | 28 ++++++++-------- crates/editor/src/editor_tests.rs | 40 +++++++++++------------ crates/editor/src/element.rs | 2 +- 4 files changed, 37 insertions(+), 37 deletions(-) diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index 974ade03f2..229ecc34fe 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -1245,7 +1245,7 @@ pub mod tests { vec![ ("fn ".to_string(), None), ("out".to_string(), Some(Color::blue())), - ("…".to_string(), None), + ("⋯".to_string(), None), (" fn ".to_string(), Some(Color::red())), ("inner".to_string(), Some(Color::blue())), ("() {}\n}".to_string(), Some(Color::red())), @@ -1326,7 +1326,7 @@ pub mod tests { cx.update(|cx| syntax_chunks(1..4, &map, &theme, cx)), [ ("out".to_string(), Some(Color::blue())), - ("…\n".to_string(), None), + ("⋯\n".to_string(), None), (" \nfn ".to_string(), Some(Color::red())), ("i\n".to_string(), Some(Color::blue())) ] diff --git a/crates/editor/src/display_map/fold_map.rs b/crates/editor/src/display_map/fold_map.rs index 0b8721b1a4..44de95fe32 100644 --- a/crates/editor/src/display_map/fold_map.rs +++ b/crates/editor/src/display_map/fold_map.rs @@ -1214,7 +1214,7 @@ mod tests { Point::new(0, 2)..Point::new(2, 2), Point::new(2, 4)..Point::new(4, 1), ]); - assert_eq!(snapshot2.text(), "aa…cc…eeeee"); + assert_eq!(snapshot2.text(), "aa⋯cc⋯eeeee"); assert_eq!( edits, &[ @@ -1241,7 +1241,7 @@ mod tests { buffer.snapshot(cx) }); let (snapshot3, edits) = map.read(buffer_snapshot, subscription.consume().into_inner()); - assert_eq!(snapshot3.text(), "123a…c123c…eeeee"); + assert_eq!(snapshot3.text(), "123a⋯c123c⋯eeeee"); assert_eq!( edits, &[ @@ -1261,12 +1261,12 @@ mod tests { buffer.snapshot(cx) }); let (snapshot4, _) = map.read(buffer_snapshot.clone(), subscription.consume().into_inner()); - assert_eq!(snapshot4.text(), "123a…c123456eee"); + assert_eq!(snapshot4.text(), "123a⋯c123456eee"); let (mut writer, _, _) = map.write(buffer_snapshot.clone(), vec![]); writer.unfold(Some(Point::new(0, 4)..Point::new(0, 4)), false); let (snapshot5, _) = map.read(buffer_snapshot.clone(), vec![]); - assert_eq!(snapshot5.text(), "123a…c123456eee"); + assert_eq!(snapshot5.text(), "123a⋯c123456eee"); let (mut writer, _, _) = map.write(buffer_snapshot.clone(), vec![]); writer.unfold(Some(Point::new(0, 4)..Point::new(0, 4)), true); @@ -1287,19 +1287,19 @@ mod tests { let (mut writer, _, _) = map.write(buffer_snapshot.clone(), vec![]); writer.fold(vec![5..8]); let (snapshot, _) = map.read(buffer_snapshot.clone(), vec![]); - assert_eq!(snapshot.text(), "abcde…ijkl"); + assert_eq!(snapshot.text(), "abcde⋯ijkl"); // Create an fold adjacent to the start of the first fold. let (mut writer, _, _) = map.write(buffer_snapshot.clone(), vec![]); writer.fold(vec![0..1, 2..5]); let (snapshot, _) = map.read(buffer_snapshot.clone(), vec![]); - assert_eq!(snapshot.text(), "…b…ijkl"); + assert_eq!(snapshot.text(), "⋯b⋯ijkl"); // Create an fold adjacent to the end of the first fold. let (mut writer, _, _) = map.write(buffer_snapshot.clone(), vec![]); writer.fold(vec![11..11, 8..10]); let (snapshot, _) = map.read(buffer_snapshot.clone(), vec![]); - assert_eq!(snapshot.text(), "…b…kl"); + assert_eq!(snapshot.text(), "⋯b⋯kl"); } { @@ -1309,7 +1309,7 @@ mod tests { let (mut writer, _, _) = map.write(buffer_snapshot.clone(), vec![]); writer.fold(vec![0..2, 2..5]); let (snapshot, _) = map.read(buffer_snapshot, vec![]); - assert_eq!(snapshot.text(), "…fghijkl"); + assert_eq!(snapshot.text(), "⋯fghijkl"); // Edit within one of the folds. let buffer_snapshot = buffer.update(cx, |buffer, cx| { @@ -1317,7 +1317,7 @@ mod tests { buffer.snapshot(cx) }); let (snapshot, _) = map.read(buffer_snapshot, subscription.consume().into_inner()); - assert_eq!(snapshot.text(), "12345…fghijkl"); + assert_eq!(snapshot.text(), "12345⋯fghijkl"); } } @@ -1334,7 +1334,7 @@ mod tests { Point::new(3, 1)..Point::new(4, 1), ]); let (snapshot, _) = map.read(buffer_snapshot, vec![]); - assert_eq!(snapshot.text(), "aa…eeeee"); + assert_eq!(snapshot.text(), "aa⋯eeeee"); } #[gpui::test] @@ -1351,14 +1351,14 @@ mod tests { Point::new(3, 1)..Point::new(4, 1), ]); let (snapshot, _) = map.read(buffer_snapshot, vec![]); - assert_eq!(snapshot.text(), "aa…cccc\nd…eeeee"); + assert_eq!(snapshot.text(), "aa⋯cccc\nd⋯eeeee"); let buffer_snapshot = buffer.update(cx, |buffer, cx| { buffer.edit([(Point::new(2, 2)..Point::new(3, 1), "")], None, cx); buffer.snapshot(cx) }); let (snapshot, _) = map.read(buffer_snapshot, subscription.consume().into_inner()); - assert_eq!(snapshot.text(), "aa…eeeee"); + assert_eq!(snapshot.text(), "aa⋯eeeee"); } #[gpui::test] @@ -1450,7 +1450,7 @@ mod tests { let mut expected_text: String = buffer_snapshot.text().to_string(); for fold_range in map.merged_fold_ranges().into_iter().rev() { - expected_text.replace_range(fold_range.start..fold_range.end, "…"); + expected_text.replace_range(fold_range.start..fold_range.end, "⋯"); } assert_eq!(snapshot.text(), expected_text); @@ -1655,7 +1655,7 @@ mod tests { ]); let (snapshot, _) = map.read(buffer_snapshot, vec![]); - assert_eq!(snapshot.text(), "aa…cccc\nd…eeeee\nffffff\n"); + assert_eq!(snapshot.text(), "aa⋯cccc\nd⋯eeeee\nffffff\n"); assert_eq!( snapshot.buffer_rows(0).collect::>(), [Some(0), Some(3), Some(5), Some(6)] diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index 48f696eb98..8589bbcc79 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -670,10 +670,10 @@ fn test_fold(cx: &mut gpui::MutableAppContext) { 1 } - fn b() {… + fn b() {⋯ } - fn c() {… + fn c() {⋯ } } " @@ -684,7 +684,7 @@ fn test_fold(cx: &mut gpui::MutableAppContext) { assert_eq!( view.display_text(cx), " - impl Foo {… + impl Foo {⋯ } " .unindent(), @@ -701,10 +701,10 @@ fn test_fold(cx: &mut gpui::MutableAppContext) { 1 } - fn b() {… + fn b() {⋯ } - fn c() {… + fn c() {⋯ } } " @@ -811,7 +811,7 @@ fn test_move_cursor_multibyte(cx: &mut gpui::MutableAppContext) { true, cx, ); - assert_eq!(view.display_text(cx), "ⓐⓑ…ⓔ\nab…e\nαβ…ε\n"); + assert_eq!(view.display_text(cx), "ⓐⓑ⋯ⓔ\nab⋯e\nαβ⋯ε\n"); view.move_right(&MoveRight, cx); assert_eq!( @@ -826,13 +826,13 @@ fn test_move_cursor_multibyte(cx: &mut gpui::MutableAppContext) { view.move_right(&MoveRight, cx); assert_eq!( view.selections.display_ranges(cx), - &[empty_range(0, "ⓐⓑ…".len())] + &[empty_range(0, "ⓐⓑ⋯".len())] ); view.move_down(&MoveDown, cx); assert_eq!( view.selections.display_ranges(cx), - &[empty_range(1, "ab…".len())] + &[empty_range(1, "ab⋯".len())] ); view.move_left(&MoveLeft, cx); assert_eq!( @@ -858,28 +858,28 @@ fn test_move_cursor_multibyte(cx: &mut gpui::MutableAppContext) { view.move_right(&MoveRight, cx); assert_eq!( view.selections.display_ranges(cx), - &[empty_range(2, "αβ…".len())] + &[empty_range(2, "αβ⋯".len())] ); view.move_right(&MoveRight, cx); assert_eq!( view.selections.display_ranges(cx), - &[empty_range(2, "αβ…ε".len())] + &[empty_range(2, "αβ⋯ε".len())] ); view.move_up(&MoveUp, cx); assert_eq!( view.selections.display_ranges(cx), - &[empty_range(1, "ab…e".len())] + &[empty_range(1, "ab⋯e".len())] ); view.move_up(&MoveUp, cx); assert_eq!( view.selections.display_ranges(cx), - &[empty_range(0, "ⓐⓑ…ⓔ".len())] + &[empty_range(0, "ⓐⓑ⋯ⓔ".len())] ); view.move_left(&MoveLeft, cx); assert_eq!( view.selections.display_ranges(cx), - &[empty_range(0, "ⓐⓑ…".len())] + &[empty_range(0, "ⓐⓑ⋯".len())] ); view.move_left(&MoveLeft, cx); assert_eq!( @@ -2134,13 +2134,13 @@ fn test_move_line_up_down(cx: &mut gpui::MutableAppContext) { }); assert_eq!( view.display_text(cx), - "aa…bbb\nccc…eeee\nfffff\nggggg\n…i\njjjjj" + "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj" ); view.move_line_up(&MoveLineUp, cx); assert_eq!( view.display_text(cx), - "aa…bbb\nccc…eeee\nggggg\n…i\njjjjj\nfffff" + "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff" ); assert_eq!( view.selections.display_ranges(cx), @@ -2157,7 +2157,7 @@ fn test_move_line_up_down(cx: &mut gpui::MutableAppContext) { view.move_line_down(&MoveLineDown, cx); assert_eq!( view.display_text(cx), - "ccc…eeee\naa…bbb\nfffff\nggggg\n…i\njjjjj" + "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj" ); assert_eq!( view.selections.display_ranges(cx), @@ -2174,7 +2174,7 @@ fn test_move_line_up_down(cx: &mut gpui::MutableAppContext) { view.move_line_down(&MoveLineDown, cx); assert_eq!( view.display_text(cx), - "ccc…eeee\nfffff\naa…bbb\nggggg\n…i\njjjjj" + "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj" ); assert_eq!( view.selections.display_ranges(cx), @@ -2191,7 +2191,7 @@ fn test_move_line_up_down(cx: &mut gpui::MutableAppContext) { view.move_line_up(&MoveLineUp, cx); assert_eq!( view.display_text(cx), - "ccc…eeee\naa…bbb\nggggg\n…i\njjjjj\nfffff" + "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff" ); assert_eq!( view.selections.display_ranges(cx), @@ -2600,14 +2600,14 @@ fn test_split_selection_into_lines(cx: &mut gpui::MutableAppContext) { DisplayPoint::new(4, 4)..DisplayPoint::new(4, 4), ]) }); - assert_eq!(view.display_text(cx), "aa…bbb\nccc…eeee\nfffff\nggggg\n…i"); + assert_eq!(view.display_text(cx), "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i"); }); view.update(cx, |view, cx| { view.split_selection_into_lines(&SplitSelectionIntoLines, cx); assert_eq!( view.display_text(cx), - "aaaaa\nbbbbb\nccc…eeee\nfffff\nggggg\n…i" + "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i" ); assert_eq!( view.selections.display_ranges(cx), diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index c777c61f41..78c3518d81 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -1507,7 +1507,7 @@ impl EditorElement { } else { let text_style = self.style.text.clone(); Flex::row() - .with_child(Label::new("…", text_style).boxed()) + .with_child(Label::new("⋯", text_style).boxed()) .with_children(jump_icon) .contained() .with_padding_left(gutter_padding) From 2198c295b3f151e365648331d93ca5f1f234f78d Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Tue, 28 Feb 2023 17:25:35 -0800 Subject: [PATCH 14/14] Fix comments --- crates/editor/src/editor.rs | 1 + crates/vim/src/normal.rs | 2 -- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index fd68bc9a32..69bb298c22 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -6050,6 +6050,7 @@ impl Editor { } } + // FIXME: Consolidate the range styling APIs so that this clone isn't nescessary pub fn clone_click_ranges(&self) -> Vec> { self.clickable_text .get(&TypeId::of::()) diff --git a/crates/vim/src/normal.rs b/crates/vim/src/normal.rs index c48a00f56d..55134e2149 100644 --- a/crates/vim/src/normal.rs +++ b/crates/vim/src/normal.rs @@ -216,8 +216,6 @@ fn insert_line_above(_: &mut Workspace, _: &InsertLineAbove, cx: &mut ViewContex }); } -// TODO: FIGURE OUT WHY PANIC WHEN CLICKING ON FOLDS - fn insert_line_below(_: &mut Workspace, _: &InsertLineBelow, cx: &mut ViewContext) { Vim::update(cx, |vim, cx| { vim.switch_mode(Mode::Insert, false, cx);