From 2b9db911c704ad3d6e9b8c432a9f2ee067f621e3 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 23 Nov 2021 18:01:05 -0700 Subject: [PATCH 1/4] WIP --- crates/editor/src/element.rs | 4 +- crates/editor/src/lib.rs | 75 +++++++++++++++++++++++++++++++++++- 2 files changed, 76 insertions(+), 3 deletions(-) diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 87b8001b38e4e712c0296d986bc7b3b395c30296..544889cb7571d46e3c3a6ff64041f09d93bf646a 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -71,7 +71,9 @@ impl EditorElement { let snapshot = self.snapshot(cx.app); let position = paint.point_for_position(&snapshot, layout, position); - if shift { + if shift && alt { + cx.dispatch_action(Select(SelectPhase::BeginColumnar { position })); + } else if shift { cx.dispatch_action(Select(SelectPhase::Extend { position, click_count, diff --git a/crates/editor/src/lib.rs b/crates/editor/src/lib.rs index 0c84de1d964ddd6c32ccc6b58dcdc9b88198f5e5..5b9603dcbd3372eed9724a1a54ba86cffe771c51 100644 --- a/crates/editor/src/lib.rs +++ b/crates/editor/src/lib.rs @@ -282,6 +282,9 @@ pub enum SelectPhase { add: bool, click_count: usize, }, + BeginColumnar { + position: DisplayPoint, + }, Extend { position: DisplayPoint, click_count: usize, @@ -320,6 +323,7 @@ pub struct Editor { display_map: ModelHandle, selection_set_id: SelectionSetId, pending_selection: Option, + columnar_selection_tail: Option, next_selection_id: usize, add_selections_state: Option, autoclose_stack: Vec, @@ -453,6 +457,7 @@ impl Editor { display_map, selection_set_id, pending_selection: None, + columnar_selection_tail: None, next_selection_id, add_selections_state: None, autoclose_stack: Default::default(), @@ -656,6 +661,7 @@ impl Editor { add, click_count, } => self.begin_selection(*position, *add, *click_count, cx), + SelectPhase::BeginColumnar { position } => self.begin_columnar_selection(*position, cx), SelectPhase::Extend { position, click_count, @@ -774,14 +780,40 @@ impl Editor { cx.notify(); } + fn begin_columnar_selection(&mut self, position: DisplayPoint, cx: &mut ViewContext) { + if !self.focused { + cx.focus_self(); + cx.emit(Event::Activate); + } + + let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); + let buffer = self.buffer.read(cx); + + let tail = self.newest_selection::(cx).tail(); + self.columnar_selection_tail = Some(buffer.anchor_before(tail)); + + self.select_columns( + tail.to_display_point(&display_map), + position, + &display_map, + cx, + ); + } + fn update_selection( &mut self, position: DisplayPoint, scroll_position: Vector2F, cx: &mut ViewContext, ) { + log::info!("update selection"); let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); - if let Some(PendingSelection { selection, mode }) = self.pending_selection.as_mut() { + + if let Some(tail) = self.columnar_selection_tail.as_ref() { + let tail = tail.to_display_point(&display_map); + log::info!("columnar tail {:?}", tail); + self.select_columns(tail, position, &display_map, cx); + } else if let Some(PendingSelection { selection, mode }) = self.pending_selection.as_mut() { let buffer = self.buffer.read(cx); let head; let tail; @@ -861,14 +893,53 @@ impl Editor { } fn end_selection(&mut self, cx: &mut ViewContext) { + self.columnar_selection_tail.take(); if self.pending_selection.is_some() { let selections = self.selections::(cx).collect::>(); self.update_selections(selections, false, cx); } } + fn select_columns( + &mut self, + tail: DisplayPoint, + head: DisplayPoint, + display_map: &DisplayMapSnapshot, + cx: &mut ViewContext, + ) { + let start_row = cmp::min(tail.row(), head.row()); + let end_row = cmp::max(tail.row(), head.row()); + let start_column = cmp::min(tail.column(), head.column()); + let end_column = cmp::max(tail.column(), head.column()); + + let selections = (start_row..=end_row) + .filter_map(|row| { + if start_column <= display_map.line_len(row) { + let start = display_map + .clip_point(DisplayPoint::new(row, start_column), Bias::Left) + .to_point(&display_map); + let end = display_map + .clip_point(DisplayPoint::new(row, end_column), Bias::Right) + .to_point(&display_map); + Some(Selection { + id: post_inc(&mut self.next_selection_id), + start, + end, + reversed: false, + goal: SelectionGoal::None, + }) + } else { + None + } + }) + .collect::>(); + + self.update_selections(selections, false, cx); + cx.notify(); + } + pub fn is_selecting(&self) -> bool { - self.pending_selection.is_some() + self.pending_selection.is_some() || self.columnar_selection_tail.is_some() } pub fn cancel(&mut self, _: &Cancel, cx: &mut ViewContext) { From d19d3bbe4513c5a6466369f1cfe8c5d48bc79fc9 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 24 Nov 2021 10:28:32 +0100 Subject: [PATCH 2/4] Calculate an overshoot when mousing to build columnar selections --- crates/editor/src/element.rs | 20 ++++++++---- crates/editor/src/lib.rs | 44 +++++++++++++++++---------- crates/gpui/src/font_cache.rs | 11 +++++++ crates/gpui/src/fonts.rs | 4 +++ crates/gpui/src/platform.rs | 1 + crates/gpui/src/platform/mac/fonts.rs | 8 +++++ 6 files changed, 66 insertions(+), 22 deletions(-) diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 544889cb7571d46e3c3a6ff64041f09d93bf646a..489f6f0a408f8e885c22b10abc9371ddd7e1312e 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -69,10 +69,13 @@ impl EditorElement { } let snapshot = self.snapshot(cx.app); - let position = paint.point_for_position(&snapshot, layout, position); + let (position, overshoot) = paint.point_for_position(&snapshot, layout, position); if shift && alt { - cx.dispatch_action(Select(SelectPhase::BeginColumnar { position })); + cx.dispatch_action(Select(SelectPhase::BeginColumnar { + position, + overshoot, + })); } else if shift { cx.dispatch_action(Select(SelectPhase::Extend { position, @@ -138,10 +141,11 @@ impl EditorElement { let font_cache = cx.font_cache.clone(); let text_layout_cache = cx.text_layout_cache.clone(); let snapshot = self.snapshot(cx.app); - let position = paint.point_for_position(&snapshot, layout, position); + let (position, overshoot) = paint.point_for_position(&snapshot, layout, position); cx.dispatch_action(Select(SelectPhase::Update { position, + overshoot, scroll_position: (snapshot.scroll_position() + scroll_delta).clamp( Vector2F::zero(), layout.scroll_max(&font_cache, &text_layout_cache), @@ -687,6 +691,7 @@ impl Element for EditorElement { let text_width = size.x() - gutter_width; let text_offset = vec2f(-style.text.descent(cx.font_cache), 0.); let em_width = style.text.em_width(cx.font_cache); + let em_advance = style.text.em_advance(cx.font_cache); let overscroll = vec2f(em_width, 0.); let wrap_width = text_width - text_offset.x() - overscroll.x() - em_width; let snapshot = self.update_view(cx.app, |view, cx| { @@ -786,6 +791,7 @@ impl Element for EditorElement { block_layouts, line_height, em_width, + em_advance, selections, max_visible_line_width, }; @@ -914,6 +920,7 @@ pub struct LayoutState { block_layouts: Vec<(Range, BlockStyle)>, line_height: f32, em_width: f32, + em_advance: f32, selections: HashMap>>, overscroll: Vector2F, text_offset: Vector2F, @@ -982,7 +989,7 @@ impl PaintState { snapshot: &Snapshot, layout: &LayoutState, position: Vector2F, - ) -> DisplayPoint { + ) -> (DisplayPoint, u32) { let scroll_position = snapshot.scroll_position(); let position = position - self.text_bounds.origin(); let y = position.y().max(0.0).min(layout.size.y()); @@ -994,12 +1001,13 @@ impl PaintState { let column = if x >= 0.0 { line.index_for_x(x) .map(|ix| ix as u32) - .unwrap_or(snapshot.line_len(row)) + .unwrap_or_else(|| snapshot.line_len(row)) } else { 0 }; + let overshoot = (0f32.max(x - line.width()) / layout.em_advance) as u32; - DisplayPoint::new(row, column) + (DisplayPoint::new(row, column), overshoot) } } diff --git a/crates/editor/src/lib.rs b/crates/editor/src/lib.rs index 5b9603dcbd3372eed9724a1a54ba86cffe771c51..93024d6c2f9676788f67c66e8dee50b73bdfa673 100644 --- a/crates/editor/src/lib.rs +++ b/crates/editor/src/lib.rs @@ -284,6 +284,7 @@ pub enum SelectPhase { }, BeginColumnar { position: DisplayPoint, + overshoot: u32, }, Extend { position: DisplayPoint, @@ -291,6 +292,7 @@ pub enum SelectPhase { }, Update { position: DisplayPoint, + overshoot: u32, scroll_position: Vector2F, }, End, @@ -661,15 +663,19 @@ impl Editor { add, click_count, } => self.begin_selection(*position, *add, *click_count, cx), - SelectPhase::BeginColumnar { position } => self.begin_columnar_selection(*position, cx), + SelectPhase::BeginColumnar { + position, + overshoot, + } => self.begin_columnar_selection(*position, *overshoot, cx), SelectPhase::Extend { position, click_count, } => self.extend_selection(*position, *click_count, cx), SelectPhase::Update { position, + overshoot, scroll_position, - } => self.update_selection(*position, *scroll_position, cx), + } => self.update_selection(*position, *overshoot, *scroll_position, cx), SelectPhase::End => self.end_selection(cx), } } @@ -780,7 +786,12 @@ impl Editor { cx.notify(); } - fn begin_columnar_selection(&mut self, position: DisplayPoint, cx: &mut ViewContext) { + fn begin_columnar_selection( + &mut self, + position: DisplayPoint, + overshoot: u32, + cx: &mut ViewContext, + ) { if !self.focused { cx.focus_self(); cx.emit(Event::Activate); @@ -795,6 +806,7 @@ impl Editor { self.select_columns( tail.to_display_point(&display_map), position, + overshoot, &display_map, cx, ); @@ -803,16 +815,15 @@ impl Editor { fn update_selection( &mut self, position: DisplayPoint, + overshoot: u32, scroll_position: Vector2F, cx: &mut ViewContext, ) { - log::info!("update selection"); let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); if let Some(tail) = self.columnar_selection_tail.as_ref() { let tail = tail.to_display_point(&display_map); - log::info!("columnar tail {:?}", tail); - self.select_columns(tail, position, &display_map, cx); + self.select_columns(tail, position, overshoot, &display_map, cx); } else if let Some(PendingSelection { selection, mode }) = self.pending_selection.as_mut() { let buffer = self.buffer.read(cx); let head; @@ -904,13 +915,14 @@ impl Editor { &mut self, tail: DisplayPoint, head: DisplayPoint, + overshoot: u32, display_map: &DisplayMapSnapshot, cx: &mut ViewContext, ) { let start_row = cmp::min(tail.row(), head.row()); let end_row = cmp::max(tail.row(), head.row()); - let start_column = cmp::min(tail.column(), head.column()); - let end_column = cmp::max(tail.column(), head.column()); + let start_column = cmp::min(tail.column(), head.column() + overshoot); + let end_column = cmp::max(tail.column(), head.column() + overshoot); let selections = (start_row..=end_row) .filter_map(|row| { @@ -3554,7 +3566,7 @@ mod tests { ); editor.update(cx, |view, cx| { - view.update_selection(DisplayPoint::new(3, 3), Vector2F::zero(), cx); + view.update_selection(DisplayPoint::new(3, 3), 0, Vector2F::zero(), cx); }); assert_eq!( @@ -3563,7 +3575,7 @@ mod tests { ); editor.update(cx, |view, cx| { - view.update_selection(DisplayPoint::new(1, 1), Vector2F::zero(), cx); + view.update_selection(DisplayPoint::new(1, 1), 0, Vector2F::zero(), cx); }); assert_eq!( @@ -3573,7 +3585,7 @@ mod tests { editor.update(cx, |view, cx| { view.end_selection(cx); - view.update_selection(DisplayPoint::new(3, 3), Vector2F::zero(), cx); + view.update_selection(DisplayPoint::new(3, 3), 0, Vector2F::zero(), cx); }); assert_eq!( @@ -3583,7 +3595,7 @@ mod tests { editor.update(cx, |view, cx| { view.begin_selection(DisplayPoint::new(3, 3), true, 1, cx); - view.update_selection(DisplayPoint::new(0, 0), Vector2F::zero(), cx); + view.update_selection(DisplayPoint::new(0, 0), 0, Vector2F::zero(), cx); }); assert_eq!( @@ -3619,7 +3631,7 @@ mod tests { }); view.update(cx, |view, cx| { - view.update_selection(DisplayPoint::new(3, 3), Vector2F::zero(), cx); + view.update_selection(DisplayPoint::new(3, 3), 0, Vector2F::zero(), cx); assert_eq!( view.selection_ranges(cx), [DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3)] @@ -3628,7 +3640,7 @@ mod tests { view.update(cx, |view, cx| { view.cancel(&Cancel, cx); - view.update_selection(DisplayPoint::new(1, 1), Vector2F::zero(), cx); + view.update_selection(DisplayPoint::new(1, 1), 0, Vector2F::zero(), cx); assert_eq!( view.selection_ranges(cx), [DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3)] @@ -3644,11 +3656,11 @@ mod tests { view.update(cx, |view, cx| { view.begin_selection(DisplayPoint::new(3, 4), false, 1, cx); - view.update_selection(DisplayPoint::new(1, 1), Vector2F::zero(), cx); + view.update_selection(DisplayPoint::new(1, 1), 0, Vector2F::zero(), cx); view.end_selection(cx); view.begin_selection(DisplayPoint::new(0, 1), true, 1, cx); - view.update_selection(DisplayPoint::new(0, 3), Vector2F::zero(), cx); + view.update_selection(DisplayPoint::new(0, 3), 0, Vector2F::zero(), cx); view.end_selection(cx); assert_eq!( view.selection_ranges(cx), diff --git a/crates/gpui/src/font_cache.rs b/crates/gpui/src/font_cache.rs index 0509ecd437d134bd05f3f3c22cf9063286ae1140..8a6ae62ff9ff52182e1321830d8e9f99c0405662 100644 --- a/crates/gpui/src/font_cache.rs +++ b/crates/gpui/src/font_cache.rs @@ -157,6 +157,17 @@ impl FontCache { bounds.width() * self.em_scale(font_id, font_size) } + pub fn em_advance(&self, font_id: FontId, font_size: f32) -> f32 { + let glyph_id; + let advance; + { + let state = self.0.read(); + glyph_id = state.fonts.glyph_for_char(font_id, 'm').unwrap(); + advance = state.fonts.advance(font_id, glyph_id).unwrap(); + } + advance.x() * self.em_scale(font_id, font_size) + } + pub fn line_height(&self, font_id: FontId, font_size: f32) -> f32 { let height = self.metric(font_id, |m| m.bounding_box.height()); (height * self.em_scale(font_id, font_size)).ceil() diff --git a/crates/gpui/src/fonts.rs b/crates/gpui/src/fonts.rs index 3dbfd660344faabcb8352b9f8fe22f6c36f779a3..6509360a626a9e8342afa1adbb90c9cedee327ce 100644 --- a/crates/gpui/src/fonts.rs +++ b/crates/gpui/src/fonts.rs @@ -151,6 +151,10 @@ impl TextStyle { font_cache.em_width(self.font_id, self.font_size) } + pub fn em_advance(&self, font_cache: &FontCache) -> f32 { + font_cache.em_advance(self.font_id, self.font_size) + } + pub fn descent(&self, font_cache: &FontCache) -> f32 { font_cache.metric(self.font_id, |m| m.descent) * self.em_scale(font_cache) } diff --git a/crates/gpui/src/platform.rs b/crates/gpui/src/platform.rs index 7a55916043f3d0a60e8e3c71260bdaf68aa88c8e..0e1bb3fca97f9861a1caa8eaa6d07df62a54f1c1 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -148,6 +148,7 @@ pub trait FontSystem: Send + Sync { ) -> anyhow::Result; fn font_metrics(&self, font_id: FontId) -> FontMetrics; fn typographic_bounds(&self, font_id: FontId, glyph_id: GlyphId) -> anyhow::Result; + fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> anyhow::Result; fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option; fn rasterize_glyph( &self, diff --git a/crates/gpui/src/platform/mac/fonts.rs b/crates/gpui/src/platform/mac/fonts.rs index c7f03689ee677bb017bcd42224e182c84f9b2bf2..2453660abc92a2d3fe84b31634f7f6461006afdf 100644 --- a/crates/gpui/src/platform/mac/fonts.rs +++ b/crates/gpui/src/platform/mac/fonts.rs @@ -69,6 +69,10 @@ impl platform::FontSystem for FontSystem { self.0.read().typographic_bounds(font_id, glyph_id) } + fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> anyhow::Result { + self.0.read().advance(font_id, glyph_id) + } + fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option { self.0.read().glyph_for_char(font_id, ch) } @@ -137,6 +141,10 @@ impl FontSystemState { Ok(self.fonts[font_id.0].typographic_bounds(glyph_id)?) } + fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> anyhow::Result { + Ok(self.fonts[font_id.0].advance(glyph_id)?) + } + fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option { self.fonts[font_id.0].glyph_for_char(ch) } From 3a9b69077ede723f73af133c672886a5b5d508d5 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 24 Nov 2021 10:37:35 +0100 Subject: [PATCH 3/4] Reverse columnar selections when head moves before tail's column --- crates/editor/src/lib.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/editor/src/lib.rs b/crates/editor/src/lib.rs index 93024d6c2f9676788f67c66e8dee50b73bdfa673..7653517fad7a81415a64999402fd9e58ec46c536 100644 --- a/crates/editor/src/lib.rs +++ b/crates/editor/src/lib.rs @@ -923,6 +923,7 @@ impl Editor { let end_row = cmp::max(tail.row(), head.row()); let start_column = cmp::min(tail.column(), head.column() + overshoot); let end_column = cmp::max(tail.column(), head.column() + overshoot); + let reversed = start_column < tail.column(); let selections = (start_row..=end_row) .filter_map(|row| { @@ -937,7 +938,7 @@ impl Editor { id: post_inc(&mut self.next_selection_id), start, end, - reversed: false, + reversed, goal: SelectionGoal::None, }) } else { From a7186c643f9ccd62669b0b0d917e046d367e3336 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 24 Nov 2021 10:40:06 +0100 Subject: [PATCH 4/4] Skip over block lines when building columnar selections --- crates/editor/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/editor/src/lib.rs b/crates/editor/src/lib.rs index 7653517fad7a81415a64999402fd9e58ec46c536..056703f523aee0c3515de4aa83333f424bc73059 100644 --- a/crates/editor/src/lib.rs +++ b/crates/editor/src/lib.rs @@ -927,7 +927,7 @@ impl Editor { let selections = (start_row..=end_row) .filter_map(|row| { - if start_column <= display_map.line_len(row) { + if start_column <= display_map.line_len(row) && !display_map.is_block_line(row) { let start = display_map .clip_point(DisplayPoint::new(row, start_column), Bias::Left) .to_point(&display_map);