assistant2: Highlight crease on selection (#24358)

Give the inline file crease inside of `assistant2`'s editor a
selection background when there is a selection over it

Release Notes:

- N/A

---------

Co-authored-by: Piotr <piotr@zed.dev>
Co-authored-by: Danilo Leal <daniloleal09@gmail.com>
This commit is contained in:
João Marcos 2025-02-20 13:25:08 -03:00 committed by GitHub
parent 78a8002415
commit f609abb48c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 70 additions and 24 deletions

View file

@ -7,19 +7,19 @@ use std::sync::Arc;
use editor::actions::FoldAt; use editor::actions::FoldAt;
use editor::display_map::{Crease, FoldId}; use editor::display_map::{Crease, FoldId};
use editor::scroll::Autoscroll; use editor::scroll::Autoscroll;
use editor::{Anchor, Editor, FoldPlaceholder, ToPoint}; use editor::{Anchor, AnchorRangeExt, Editor, FoldPlaceholder, ToPoint};
use file_icons::FileIcons; use file_icons::FileIcons;
use fuzzy::PathMatch; use fuzzy::PathMatch;
use gpui::{ use gpui::{
AnyElement, App, DismissEvent, Empty, Entity, FocusHandle, Focusable, Stateful, Task, AnyElement, App, AppContext, DismissEvent, Empty, Entity, FocusHandle, Focusable, Stateful,
WeakEntity, Task, WeakEntity,
}; };
use multi_buffer::{MultiBufferPoint, MultiBufferRow}; use multi_buffer::{MultiBufferPoint, MultiBufferRow};
use picker::{Picker, PickerDelegate}; use picker::{Picker, PickerDelegate};
use project::{PathMatchCandidateSet, ProjectPath, WorktreeId}; use project::{PathMatchCandidateSet, ProjectPath, WorktreeId};
use rope::Point; use rope::Point;
use text::SelectionGoal; use text::SelectionGoal;
use ui::{prelude::*, ButtonLike, Disclosure, ElevationIndex, ListItem, Tooltip}; use ui::{prelude::*, ButtonLike, Disclosure, ListItem, TintColor, Tooltip};
use util::ResultExt as _; use util::ResultExt as _;
use workspace::{notifications::NotifyResultExt, Workspace}; use workspace::{notifications::NotifyResultExt, Workspace};
@ -238,11 +238,11 @@ impl PickerDelegate for FileContextPickerDelegate {
path: mat.path.clone(), path: mat.path.clone(),
}; };
let Some(editor) = self.editor.upgrade() else { let Some(editor_entity) = self.editor.upgrade() else {
return; return;
}; };
editor.update(cx, |editor, cx| { editor_entity.update(cx, |editor, cx| {
editor.transact(window, cx, |editor, window, 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. // 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("")); .unwrap_or_else(|| SharedString::new(""));
let placeholder = FoldPlaceholder { 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() ..Default::default()
}; };
@ -464,11 +468,50 @@ pub fn render_file_context_entry(
fn render_fold_icon_button( fn render_fold_icon_button(
icon: SharedString, icon: SharedString,
label: SharedString, label: SharedString,
) -> Arc<dyn Send + Sync + Fn(FoldId, Range<Anchor>, &mut Window, &mut App) -> AnyElement> { editor: WeakEntity<Editor>,
Arc::new(move |fold_id, _fold_range, _window, _cx| { ) -> Arc<dyn Send + Sync + Fn(FoldId, Range<Anchor>, &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::<usize>(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) ButtonLike::new(fold_id)
.style(ButtonStyle::Filled) .style(ButtonStyle::Filled)
.layer(ElevationIndex::ElevatedSurface) .selected_style(ButtonStyle::Tinted(TintColor::Accent))
.toggle_state(is_in_text_selection)
.child( .child(
h_flex() h_flex()
.gap_1() .gap_1()

View file

@ -634,7 +634,7 @@ impl ContextEditor {
} }
}); });
let placeholder = FoldPlaceholder { let placeholder = FoldPlaceholder {
render: Arc::new(move |_, _, _, _| Empty.into_any()), render: Arc::new(move |_, _, _| Empty.into_any()),
..Default::default() ..Default::default()
}; };
let render_toggle = { let render_toggle = {
@ -2668,8 +2668,8 @@ fn render_fold_icon_button(
editor: WeakEntity<Editor>, editor: WeakEntity<Editor>,
icon: IconName, icon: IconName,
label: SharedString, label: SharedString,
) -> Arc<dyn Send + Sync + Fn(FoldId, Range<Anchor>, &mut Window, &mut App) -> AnyElement> { ) -> Arc<dyn Send + Sync + Fn(FoldId, Range<Anchor>, &mut App) -> AnyElement> {
Arc::new(move |fold_id, fold_range, _window, _cx| { Arc::new(move |fold_id, fold_range, _cx| {
let editor = editor.clone(); let editor = editor.clone();
ButtonLike::new(fold_id) ButtonLike::new(fold_id)
.style(ButtonStyle::Filled) .style(ButtonStyle::Filled)
@ -2729,7 +2729,7 @@ pub fn fold_toggle(
fn quote_selection_fold_placeholder(title: String, editor: WeakEntity<Editor>) -> FoldPlaceholder { fn quote_selection_fold_placeholder(title: String, editor: WeakEntity<Editor>) -> FoldPlaceholder {
FoldPlaceholder { FoldPlaceholder {
render: Arc::new({ render: Arc::new({
move |fold_id, fold_range, _window, _cx| { move |fold_id, fold_range, _cx| {
let editor = editor.clone(); let editor = editor.clone();
ButtonLike::new(fold_id) ButtonLike::new(fold_id)
.style(ButtonStyle::Filled) .style(ButtonStyle::Filled)
@ -3413,7 +3413,7 @@ fn invoked_slash_command_fold_placeholder(
FoldPlaceholder { FoldPlaceholder {
constrain_width: false, constrain_width: false,
merge_adjacent: 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 { let Some(context) = context.upgrade() else {
return Empty.into_any(); return Empty.into_any();
}; };

View file

@ -2,7 +2,7 @@ use super::{
inlay_map::{InlayBufferRows, InlayChunks, InlayEdit, InlayOffset, InlayPoint, InlaySnapshot}, inlay_map::{InlayBufferRows, InlayChunks, InlayEdit, InlayOffset, InlayPoint, InlaySnapshot},
Highlights, Highlights,
}; };
use gpui::{AnyElement, App, ElementId, Window}; use gpui::{AnyElement, App, ElementId};
use language::{Chunk, ChunkRenderer, Edit, Point, TextSummary}; use language::{Chunk, ChunkRenderer, Edit, Point, TextSummary};
use multi_buffer::{ use multi_buffer::{
Anchor, AnchorRangeExt, MultiBufferRow, MultiBufferSnapshot, RowInfo, ToOffset, Anchor, AnchorRangeExt, MultiBufferRow, MultiBufferSnapshot, RowInfo, ToOffset,
@ -21,8 +21,7 @@ use util::post_inc;
#[derive(Clone)] #[derive(Clone)]
pub struct FoldPlaceholder { pub struct FoldPlaceholder {
/// Creates an element to represent this fold's placeholder. /// Creates an element to represent this fold's placeholder.
pub render: pub render: Arc<dyn Send + Sync + Fn(FoldId, Range<Anchor>, &mut App) -> AnyElement>,
Arc<dyn Send + Sync + Fn(FoldId, Range<Anchor>, &mut Window, &mut App) -> AnyElement>,
/// If true, the element is constrained to the shaped width of an ellipsis. /// If true, the element is constrained to the shaped width of an ellipsis.
pub constrain_width: bool, pub constrain_width: bool,
/// If true, merges the fold with an adjacent one. /// If true, merges the fold with an adjacent one.
@ -34,7 +33,7 @@ pub struct FoldPlaceholder {
impl Default for FoldPlaceholder { impl Default for FoldPlaceholder {
fn default() -> Self { fn default() -> Self {
Self { Self {
render: Arc::new(|_, _, _, _| gpui::Empty.into_any_element()), render: Arc::new(|_, _, _| gpui::Empty.into_any_element()),
constrain_width: true, constrain_width: true,
merge_adjacent: true, merge_adjacent: true,
type_tag: None, type_tag: None,
@ -46,7 +45,7 @@ impl FoldPlaceholder {
#[cfg(any(test, feature = "test-support"))] #[cfg(any(test, feature = "test-support"))]
pub fn test() -> Self { pub fn test() -> Self {
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, constrain_width: true,
merge_adjacent: true, merge_adjacent: true,
type_tag: None, type_tag: None,
@ -486,7 +485,6 @@ impl FoldMap {
(fold.placeholder.render)( (fold.placeholder.render)(
fold_id, fold_id,
fold.range.0.clone(), fold.range.0.clone(),
cx.window,
cx.context, cx.context,
) )
}), }),

View file

@ -1142,7 +1142,7 @@ impl Editor {
let editor = cx.entity().downgrade(); let editor = cx.entity().downgrade();
let fold_placeholder = FoldPlaceholder { let fold_placeholder = FoldPlaceholder {
constrain_width: true, 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(); let editor = editor.clone();
div() div()
.id(fold_id) .id(fold_id)

View file

@ -189,8 +189,9 @@ impl ToPoint for Anchor {
} }
pub trait AnchorRangeExt { pub trait AnchorRangeExt {
fn cmp(&self, b: &Range<Anchor>, buffer: &MultiBufferSnapshot) -> Ordering; fn cmp(&self, other: &Range<Anchor>, buffer: &MultiBufferSnapshot) -> Ordering;
fn overlaps(&self, b: &Range<Anchor>, buffer: &MultiBufferSnapshot) -> bool; fn includes(&self, other: &Range<Anchor>, buffer: &MultiBufferSnapshot) -> bool;
fn overlaps(&self, other: &Range<Anchor>, buffer: &MultiBufferSnapshot) -> bool;
fn to_offset(&self, content: &MultiBufferSnapshot) -> Range<usize>; fn to_offset(&self, content: &MultiBufferSnapshot) -> Range<usize>;
fn to_point(&self, content: &MultiBufferSnapshot) -> Range<Point>; fn to_point(&self, content: &MultiBufferSnapshot) -> Range<Point>;
} }
@ -203,6 +204,10 @@ impl AnchorRangeExt for Range<Anchor> {
} }
} }
fn includes(&self, other: &Range<Anchor>, buffer: &MultiBufferSnapshot) -> bool {
self.start.cmp(&other.start, &buffer).is_le() && other.end.cmp(&self.end, &buffer).is_le()
}
fn overlaps(&self, other: &Range<Anchor>, buffer: &MultiBufferSnapshot) -> bool { fn overlaps(&self, other: &Range<Anchor>, buffer: &MultiBufferSnapshot) -> bool {
self.end.cmp(&other.start, buffer).is_ge() && self.start.cmp(&other.end, buffer).is_le() self.end.cmp(&other.start, buffer).is_ge() && self.start.cmp(&other.end, buffer).is_le()
} }