diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index 38d524232193b6899aea167b3623b143a1c9cb02..500f3626e348aeaf94cfa0efeff71ce5454ad01d 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -8,6 +8,7 @@ use block_map::{BlockMap, BlockPoint}; use collections::{HashMap, HashSet}; use fold_map::FoldMap; use gpui::{ + color::Color, fonts::{FontId, HighlightStyle}, Entity, ModelContext, ModelHandle, }; @@ -218,6 +219,10 @@ impl DisplayMap { .update(cx, |map, cx| map.set_font(font_id, font_size, cx)) } + pub fn set_fold_ellipses_color(&mut self, color: Color) -> bool { + self.fold_map.set_ellipses_color(color) + } + pub fn set_wrap_width(&self, width: Option, cx: &mut ModelContext) -> bool { self.wrap_map .update(cx, |map, cx| map.set_wrap_width(width, cx)) diff --git a/crates/editor/src/display_map/fold_map.rs b/crates/editor/src/display_map/fold_map.rs index 44de95fe32448e26e6af449cc06f9c8e1c230bb1..6323461987aa58c1e280ee4fb1e869c34ddab4b4 100644 --- a/crates/editor/src/display_map/fold_map.rs +++ b/crates/editor/src/display_map/fold_map.rs @@ -4,7 +4,7 @@ use crate::{ ToOffset, }; use collections::BTreeMap; -use gpui::fonts::HighlightStyle; +use gpui::{color::Color, fonts::HighlightStyle}; use language::{Chunk, Edit, Point, TextSummary}; use parking_lot::Mutex; use std::{ @@ -133,6 +133,7 @@ impl<'a> FoldMapWriter<'a> { folds: self.0.folds.clone(), buffer_snapshot: buffer, version: self.0.version.load(SeqCst), + ellipses_color: self.0.ellipses_color, }; (snapshot, edits) } @@ -182,6 +183,7 @@ impl<'a> FoldMapWriter<'a> { folds: self.0.folds.clone(), buffer_snapshot: buffer, version: self.0.version.load(SeqCst), + ellipses_color: self.0.ellipses_color, }; (snapshot, edits) } @@ -192,6 +194,7 @@ pub struct FoldMap { transforms: Mutex>, folds: SumTree, version: AtomicUsize, + ellipses_color: Option, } impl FoldMap { @@ -209,6 +212,7 @@ impl FoldMap { }, &(), )), + ellipses_color: None, version: Default::default(), }; @@ -217,6 +221,7 @@ impl FoldMap { folds: this.folds.clone(), buffer_snapshot: this.buffer.lock().clone(), version: this.version.load(SeqCst), + ellipses_color: None, }; (this, snapshot) } @@ -233,6 +238,7 @@ impl FoldMap { folds: self.folds.clone(), buffer_snapshot: self.buffer.lock().clone(), version: self.version.load(SeqCst), + ellipses_color: self.ellipses_color, }; (snapshot, edits) } @@ -246,6 +252,15 @@ impl FoldMap { (FoldMapWriter(self), snapshot, edits) } + pub fn set_ellipses_color(&mut self, color: Color) -> bool { + if self.ellipses_color != Some(color) { + self.ellipses_color = Some(color); + true + } else { + false + } + } + fn check_invariants(&self) { if cfg!(test) { assert_eq!( @@ -477,6 +492,7 @@ pub struct FoldSnapshot { folds: SumTree, buffer_snapshot: MultiBufferSnapshot, pub version: usize, + pub ellipses_color: Option, } impl FoldSnapshot { @@ -739,6 +755,7 @@ impl FoldSnapshot { max_output_offset: range.end.0, highlight_endpoints: highlight_endpoints.into_iter().peekable(), active_highlights: Default::default(), + ellipses_color: self.ellipses_color, } } @@ -1029,6 +1046,7 @@ pub struct FoldChunks<'a> { max_output_offset: usize, highlight_endpoints: Peekable>, active_highlights: BTreeMap, HighlightStyle>, + ellipses_color: Option, } impl<'a> Iterator for FoldChunks<'a> { @@ -1058,7 +1076,10 @@ impl<'a> Iterator for FoldChunks<'a> { return Some(Chunk { text: output_text, syntax_highlight_id: None, - highlight_style: None, + highlight_style: self.ellipses_color.map(|color| HighlightStyle { + color: Some(color), + ..Default::default() + }), diagnostic_severity: None, is_unnecessary: false, }); diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 8a3acef99626b696532ae2c2017f02989225fedf..3e7a14d2aea29312bb6b6853f18957ca116eb5f6 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -39,11 +39,10 @@ use gpui::{ impl_actions, impl_internal_actions, keymap_matcher::KeymapContext, platform::CursorStyle, - scene::MouseClick, serde_json::json, AnyViewHandle, AppContext, AsyncAppContext, ClipboardItem, Element, ElementBox, Entity, - EventContext, ModelHandle, MouseButton, MutableAppContext, RenderContext, Subscription, Task, - View, ViewContext, ViewHandle, WeakViewHandle, + 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}; @@ -6448,6 +6447,7 @@ impl View for Editor { fn render(&mut self, cx: &mut RenderContext) -> ElementBox { let style = self.style(cx); let font_changed = self.display_map.update(cx, |map, cx| { + map.set_fold_ellipses_color(style.folds.ellipses.text_color); map.set_font(style.text.font_id, style.text.font_size, cx) }); diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index fac90f2507024b86f2e7cbc93221db4967e61208..f69902e53d993fbe60470a2b42f3d553c93a6c1f 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -50,6 +50,8 @@ use std::{ }; use workspace::item::Item; +enum FoldMarkers {} + struct SelectionLayout { head: DisplayPoint, cursor_shape: CursorShape, @@ -115,7 +117,6 @@ impl EditorElement { fn attach_mouse_handlers( view: &WeakViewHandle, position_map: &Arc, - fold_ranges: Arc<[Range]>, has_popovers: bool, visible_bounds: RectF, text_bounds: RectF, @@ -212,23 +213,6 @@ 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 in fold_ranges.iter() { - // Range -> RangeInclusive - if range.contains(&point) || range.end == point { - cx.dispatch_action(UnfoldAt { - display_row: point.row(), - }) - } - } - } - } }), ); @@ -724,9 +708,24 @@ impl EditorElement { }, }); - for range in layout.fold_ranges.iter() { + let fold_corner_radius = + self.style.folds.ellipses.corner_radius_factor * layout.position_map.line_height; + for (id, range, color) in layout.fold_ranges.iter() { + self.paint_highlighted_range( + range.clone(), + *color, + fold_corner_radius, + fold_corner_radius * 2., + layout, + content_origin, + scroll_top, + scroll_left, + bounds, + cx, + ); + for bound in range_to_bounds( - range, + &range, content_origin, scroll_left, scroll_top, @@ -738,6 +737,16 @@ impl EditorElement { bounds: bound, style: CursorStyle::PointingHand, }); + + let display_row = range.start.row(); + cx.scene.push_mouse_region( + MouseRegion::new::(self.view.id(), *id as usize, bound) + .on_click(MouseButton::Left, move |_, cx| { + cx.dispatch_action(UnfoldAt { display_row }) + }) + .with_notify_on_hover(true) + .with_notify_on_click(true), + ) } } @@ -757,9 +766,10 @@ impl EditorElement { } let mut cursors = SmallVec::<[Cursor; 32]>::new(); + let corner_radius = 0.15 * layout.position_map.line_height; + for (replica_id, selections) in &layout.selections { let selection_style = style.replica_selection_style(*replica_id); - let corner_radius = 0.15 * layout.position_map.line_height; for selection in selections { self.paint_highlighted_range( @@ -1695,8 +1705,12 @@ impl Element for EditorElement { snapshot .folds_in_range(start_anchor..end_anchor) .map(|anchor| { - anchor.start.to_display_point(&snapshot.display_snapshot) - ..anchor.end.to_display_point(&snapshot.display_snapshot) + let start = anchor.start.to_point(&snapshot.buffer_snapshot); + ( + start.row, + start.to_display_point(&snapshot.display_snapshot) + ..anchor.end.to_display_point(&snapshot), + ) }), ); @@ -1768,6 +1782,21 @@ impl Element for EditorElement { .unwrap_or_default() }); + let fold_ranges: Vec<(BufferRow, Range, Color)> = fold_ranges + .into_iter() + .map(|(id, fold)| { + let color = self + .style + .folds + .ellipses + .background + .style_for(&mut cx.mouse_state::(id as usize), false) + .color; + + (id, fold, color) + }) + .collect(); + let line_number_layouts = self.layout_line_numbers(start_row..end_row, &active_rows, &snapshot, cx); @@ -1955,7 +1984,7 @@ impl Element for EditorElement { active_rows, highlighted_rows, highlighted_ranges, - fold_ranges: fold_ranges.into(), + fold_ranges, line_number_layouts, display_hunks, blocks, @@ -1987,7 +2016,6 @@ impl Element for EditorElement { Self::attach_mouse_handlers( &self.view, &layout.position_map, - layout.fold_ranges.clone(), // No need to clone the vec layout.hover_popovers.is_some(), visible_bounds, text_bounds, @@ -2071,6 +2099,8 @@ impl Element for EditorElement { } } +type BufferRow = u32; + pub struct LayoutState { position_map: Arc, gutter_size: Vector2F, @@ -2085,7 +2115,7 @@ pub struct LayoutState { display_hunks: Vec, blocks: Vec, highlighted_ranges: Vec<(Range, Color)>, - fold_ranges: Arc<[Range]>, + fold_ranges: Vec<(BufferRow, Range, Color)>, selections: Vec<(ReplicaId, Vec)>, scrollbar_row_range: Range, show_scrollbars: bool, diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 35c91f1ff233be1ac3fdf4d7ccdc945ef4d67674..31563010b723d88b4f834084fba91cb3ecd9a15c 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -4159,10 +4159,10 @@ pub struct RenderContext<'a, T: View> { #[derive(Debug, Clone, Default)] pub struct MouseState { - hovered: bool, - clicked: Option, - accessed_hovered: bool, - accessed_clicked: bool, + pub(crate) hovered: bool, + pub(crate) clicked: Option, + pub(crate) accessed_hovered: bool, + pub(crate) accessed_clicked: bool, } impl MouseState { diff --git a/crates/gpui/src/presenter.rs b/crates/gpui/src/presenter.rs index c0785e11f3b1a928a2a3bd01bd24bc2ee8128693..5ffed579cfed4baba9a33de0c424b9d488786247 100644 --- a/crates/gpui/src/presenter.rs +++ b/crates/gpui/src/presenter.rs @@ -12,9 +12,9 @@ use crate::{ text_layout::TextLayoutCache, Action, AnyModelHandle, AnyViewHandle, AnyWeakModelHandle, AnyWeakViewHandle, Appearance, AssetCache, ElementBox, Entity, FontSystem, ModelHandle, MouseButton, MouseMovedEvent, - MouseRegion, MouseRegionId, ParentId, ReadModel, ReadView, RenderContext, RenderParams, - SceneBuilder, UpgradeModelHandle, UpgradeViewHandle, View, ViewHandle, WeakModelHandle, - WeakViewHandle, + MouseRegion, MouseRegionId, MouseState, ParentId, ReadModel, ReadView, RenderContext, + RenderParams, SceneBuilder, UpgradeModelHandle, UpgradeViewHandle, View, ViewHandle, + WeakModelHandle, WeakViewHandle, }; use anyhow::bail; use collections::{HashMap, HashSet}; @@ -603,6 +603,24 @@ pub struct LayoutContext<'a> { } impl<'a> LayoutContext<'a> { + pub fn mouse_state(&self, region_id: usize) -> MouseState { + let view_id = self.view_stack.last().unwrap(); + + let region_id = MouseRegionId::new::(*view_id, region_id); + MouseState { + hovered: self.hovered_region_ids.contains(®ion_id), + clicked: self.clicked_region_ids.as_ref().and_then(|(ids, button)| { + if ids.contains(®ion_id) { + Some(*button) + } else { + None + } + }), + accessed_hovered: false, + accessed_clicked: false, + } + } + fn layout(&mut self, view_id: usize, constraint: SizeConstraint) -> Vector2F { let print_error = |view_id| { format!( diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index 06122b9dda91600714d48d0778ffc14dd8a04de1..5e8b5dcbcd819ec36473606ad60e5324bb6e7bad 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -632,16 +632,22 @@ pub struct FieldEditor { pub selection: SelectionStyle, } +#[derive(Clone, Deserialize, Default)] +pub struct InteractiveColor { + pub color: Color, +} + #[derive(Clone, Deserialize, Default)] pub struct CodeActions { #[serde(default)] - pub indicator: Interactive, + pub indicator: Interactive, pub vertical_scale: f32, } #[derive(Clone, Deserialize, Default)] pub struct Folds { - pub indicator: Interactive, + pub indicator: Interactive, + pub ellipses: FoldEllipses, pub fold_background: Color, pub icon_width: f32, pub folded_icon: String, @@ -649,8 +655,10 @@ pub struct Folds { } #[derive(Clone, Deserialize, Default)] -pub struct Indicator { - pub color: Color, +pub struct FoldEllipses { + pub text_color: Color, + pub background: Interactive, + pub corner_radius_factor: f32, } #[derive(Clone, Deserialize, Default)] diff --git a/styles/src/styleTree/editor.ts b/styles/src/styleTree/editor.ts index c8334d1cc0bea5f59a6e7655ad8f2839944fcbfe..799363a349191ffe9bd53748d818c7b0cb33177c 100644 --- a/styles/src/styleTree/editor.ts +++ b/styles/src/styleTree/editor.ts @@ -76,6 +76,22 @@ export default function editor(colorScheme: ColorScheme) { color: foreground(layer, "on"), }, }, + ellipses: { + textColor: colorScheme.ramps.neutral(0.71).hex(), + cornerRadiusFactor: 0.15, + background: { + // Copied from hover_popover highlight + color: colorScheme.ramps.neutral(0.5).alpha(0.0).hex(), + + hover: { + color: colorScheme.ramps.neutral(0.5).alpha(0.5).hex(), + }, + + clicked: { + color: colorScheme.ramps.neutral(0.5).alpha(0.7).hex(), + }, + } + }, foldBackground: foreground(layer, "variant"), }, diff: {