diff --git a/crates/assistant2/src/context_picker/file_context_picker.rs b/crates/assistant2/src/context_picker/file_context_picker.rs index cab317a1ea..1a301f18b9 100644 --- a/crates/assistant2/src/context_picker/file_context_picker.rs +++ b/crates/assistant2/src/context_picker/file_context_picker.rs @@ -7,19 +7,19 @@ use std::sync::Arc; use editor::actions::FoldAt; use editor::display_map::{Crease, FoldId}; use editor::scroll::Autoscroll; -use editor::{Anchor, Editor, FoldPlaceholder, ToPoint}; +use editor::{Anchor, AnchorRangeExt, Editor, FoldPlaceholder, ToPoint}; use file_icons::FileIcons; use fuzzy::PathMatch; use gpui::{ - AnyElement, App, DismissEvent, Empty, Entity, FocusHandle, Focusable, Stateful, Task, - WeakEntity, + AnyElement, App, AppContext, DismissEvent, Empty, Entity, FocusHandle, Focusable, Stateful, + Task, WeakEntity, }; use multi_buffer::{MultiBufferPoint, MultiBufferRow}; use picker::{Picker, PickerDelegate}; use project::{PathMatchCandidateSet, ProjectPath, WorktreeId}; use rope::Point; use text::SelectionGoal; -use ui::{prelude::*, ButtonLike, Disclosure, ElevationIndex, ListItem, Tooltip}; +use ui::{prelude::*, ButtonLike, Disclosure, ListItem, TintColor, Tooltip}; use util::ResultExt as _; use workspace::{notifications::NotifyResultExt, Workspace}; @@ -238,11 +238,11 @@ impl PickerDelegate for FileContextPickerDelegate { path: mat.path.clone(), }; - let Some(editor) = self.editor.upgrade() else { + let Some(editor_entity) = self.editor.upgrade() else { return; }; - editor.update(cx, |editor, cx| { + editor_entity.update(cx, |editor, cx| { editor.transact(window, cx, |editor, window, cx| { // Move empty selections left by 1 column to select the `@`s, so they get overwritten when we insert. { @@ -292,7 +292,11 @@ impl PickerDelegate for FileContextPickerDelegate { .unwrap_or_else(|| SharedString::new("")); let placeholder = FoldPlaceholder { - render: render_fold_icon_button(file_icon, file_name.into()), + render: render_fold_icon_button( + file_icon, + file_name.into(), + editor_entity.downgrade(), + ), ..Default::default() }; @@ -464,11 +468,50 @@ pub fn render_file_context_entry( fn render_fold_icon_button( icon: SharedString, label: SharedString, -) -> Arc, &mut Window, &mut App) -> AnyElement> { - Arc::new(move |fold_id, _fold_range, _window, _cx| { + editor: WeakEntity, +) -> Arc, &mut App) -> AnyElement> { + Arc::new(move |fold_id, fold_range, cx| { + let is_in_text_selection = editor.upgrade().is_some_and(|editor| { + editor.update(cx, |editor, cx| { + let snapshot = editor + .buffer() + .update(cx, |multi_buffer, cx| multi_buffer.snapshot(cx)); + + let is_in_pending_selection = || { + editor + .selections + .pending + .as_ref() + .is_some_and(|pending_selection| { + pending_selection + .selection + .range() + .includes(&fold_range, &snapshot) + }) + }; + + let mut is_in_complete_selection = || { + editor + .selections + .disjoint_in_range::(fold_range.clone(), cx) + .into_iter() + .any(|selection| { + // This is needed to cover a corner case, if we just check for an existing + // selection in the fold range, having a cursor at the start of the fold + // marks it as selected. Non-empty selections don't cause this. + let length = selection.end - selection.start; + length > 0 + }) + }; + + is_in_pending_selection() || is_in_complete_selection() + }) + }); + ButtonLike::new(fold_id) .style(ButtonStyle::Filled) - .layer(ElevationIndex::ElevatedSurface) + .selected_style(ButtonStyle::Tinted(TintColor::Accent)) + .toggle_state(is_in_text_selection) .child( h_flex() .gap_1() diff --git a/crates/assistant_context_editor/src/context_editor.rs b/crates/assistant_context_editor/src/context_editor.rs index 3c56270971..e3be2eec77 100644 --- a/crates/assistant_context_editor/src/context_editor.rs +++ b/crates/assistant_context_editor/src/context_editor.rs @@ -634,7 +634,7 @@ impl ContextEditor { } }); let placeholder = FoldPlaceholder { - render: Arc::new(move |_, _, _, _| Empty.into_any()), + render: Arc::new(move |_, _, _| Empty.into_any()), ..Default::default() }; let render_toggle = { @@ -2668,8 +2668,8 @@ fn render_fold_icon_button( editor: WeakEntity, icon: IconName, label: SharedString, -) -> Arc, &mut Window, &mut App) -> AnyElement> { - Arc::new(move |fold_id, fold_range, _window, _cx| { +) -> Arc, &mut App) -> AnyElement> { + Arc::new(move |fold_id, fold_range, _cx| { let editor = editor.clone(); ButtonLike::new(fold_id) .style(ButtonStyle::Filled) @@ -2729,7 +2729,7 @@ pub fn fold_toggle( fn quote_selection_fold_placeholder(title: String, editor: WeakEntity) -> FoldPlaceholder { FoldPlaceholder { render: Arc::new({ - move |fold_id, fold_range, _window, _cx| { + move |fold_id, fold_range, _cx| { let editor = editor.clone(); ButtonLike::new(fold_id) .style(ButtonStyle::Filled) @@ -3413,7 +3413,7 @@ fn invoked_slash_command_fold_placeholder( FoldPlaceholder { constrain_width: false, merge_adjacent: false, - render: Arc::new(move |fold_id, _, _window, cx| { + render: Arc::new(move |fold_id, _, cx| { let Some(context) = context.upgrade() else { return Empty.into_any(); }; diff --git a/crates/editor/src/display_map/fold_map.rs b/crates/editor/src/display_map/fold_map.rs index 6ec334c975..5ea5c5a173 100644 --- a/crates/editor/src/display_map/fold_map.rs +++ b/crates/editor/src/display_map/fold_map.rs @@ -2,7 +2,7 @@ use super::{ inlay_map::{InlayBufferRows, InlayChunks, InlayEdit, InlayOffset, InlayPoint, InlaySnapshot}, Highlights, }; -use gpui::{AnyElement, App, ElementId, Window}; +use gpui::{AnyElement, App, ElementId}; use language::{Chunk, ChunkRenderer, Edit, Point, TextSummary}; use multi_buffer::{ Anchor, AnchorRangeExt, MultiBufferRow, MultiBufferSnapshot, RowInfo, ToOffset, @@ -21,8 +21,7 @@ use util::post_inc; #[derive(Clone)] pub struct FoldPlaceholder { /// Creates an element to represent this fold's placeholder. - pub render: - Arc, &mut Window, &mut App) -> AnyElement>, + pub render: Arc, &mut App) -> AnyElement>, /// If true, the element is constrained to the shaped width of an ellipsis. pub constrain_width: bool, /// If true, merges the fold with an adjacent one. @@ -34,7 +33,7 @@ pub struct FoldPlaceholder { impl Default for FoldPlaceholder { fn default() -> Self { Self { - render: Arc::new(|_, _, _, _| gpui::Empty.into_any_element()), + render: Arc::new(|_, _, _| gpui::Empty.into_any_element()), constrain_width: true, merge_adjacent: true, type_tag: None, @@ -46,7 +45,7 @@ impl FoldPlaceholder { #[cfg(any(test, feature = "test-support"))] pub fn test() -> Self { Self { - render: Arc::new(|_id, _range, _window, _cx| gpui::Empty.into_any_element()), + render: Arc::new(|_id, _range, _cx| gpui::Empty.into_any_element()), constrain_width: true, merge_adjacent: true, type_tag: None, @@ -486,7 +485,6 @@ impl FoldMap { (fold.placeholder.render)( fold_id, fold.range.0.clone(), - cx.window, cx.context, ) }), diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 6d88c2787d..70c541c736 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -1142,7 +1142,7 @@ impl Editor { let editor = cx.entity().downgrade(); let fold_placeholder = FoldPlaceholder { constrain_width: true, - render: Arc::new(move |fold_id, fold_range, _, cx| { + render: Arc::new(move |fold_id, fold_range, cx| { let editor = editor.clone(); div() .id(fold_id) diff --git a/crates/multi_buffer/src/anchor.rs b/crates/multi_buffer/src/anchor.rs index 4357dbc7ac..4d77478a4e 100644 --- a/crates/multi_buffer/src/anchor.rs +++ b/crates/multi_buffer/src/anchor.rs @@ -189,8 +189,9 @@ impl ToPoint for Anchor { } pub trait AnchorRangeExt { - fn cmp(&self, b: &Range, buffer: &MultiBufferSnapshot) -> Ordering; - fn overlaps(&self, b: &Range, buffer: &MultiBufferSnapshot) -> bool; + fn cmp(&self, other: &Range, buffer: &MultiBufferSnapshot) -> Ordering; + fn includes(&self, other: &Range, buffer: &MultiBufferSnapshot) -> bool; + fn overlaps(&self, other: &Range, buffer: &MultiBufferSnapshot) -> bool; fn to_offset(&self, content: &MultiBufferSnapshot) -> Range; fn to_point(&self, content: &MultiBufferSnapshot) -> Range; } @@ -203,6 +204,10 @@ impl AnchorRangeExt for Range { } } + fn includes(&self, other: &Range, buffer: &MultiBufferSnapshot) -> bool { + self.start.cmp(&other.start, &buffer).is_le() && other.end.cmp(&self.end, &buffer).is_le() + } + fn overlaps(&self, other: &Range, buffer: &MultiBufferSnapshot) -> bool { self.end.cmp(&other.start, buffer).is_ge() && self.start.cmp(&other.end, buffer).is_le() }