Wire up @mention
ed files in assistant2 (#23389)
`@mention`ed files in assistant2 now get replaced by the full path of the file in what gets sent to the model, while rendering visually as just the filename (in a crease, so they can only be selected/deleted as a whole unit, not character by character). https://github.com/user-attachments/assets/a5867a93-d656-4a17-aced-58424c6e8cf6 Release Notes: - N/A --------- Co-authored-by: João Marcos <joao@zed.dev> Co-authored-by: Conrad <conrad@zed.dev>
This commit is contained in:
parent
aacd80ee4a
commit
8f87b5637a
6 changed files with 147 additions and 9 deletions
|
@ -43,6 +43,7 @@ enum ContextPickerMode {
|
||||||
pub(super) struct ContextPicker {
|
pub(super) struct ContextPicker {
|
||||||
mode: ContextPickerMode,
|
mode: ContextPickerMode,
|
||||||
workspace: WeakView<Workspace>,
|
workspace: WeakView<Workspace>,
|
||||||
|
editor: WeakView<Editor>,
|
||||||
context_store: WeakModel<ContextStore>,
|
context_store: WeakModel<ContextStore>,
|
||||||
thread_store: Option<WeakModel<ThreadStore>>,
|
thread_store: Option<WeakModel<ThreadStore>>,
|
||||||
confirm_behavior: ConfirmBehavior,
|
confirm_behavior: ConfirmBehavior,
|
||||||
|
@ -53,6 +54,7 @@ impl ContextPicker {
|
||||||
workspace: WeakView<Workspace>,
|
workspace: WeakView<Workspace>,
|
||||||
thread_store: Option<WeakModel<ThreadStore>>,
|
thread_store: Option<WeakModel<ThreadStore>>,
|
||||||
context_store: WeakModel<ContextStore>,
|
context_store: WeakModel<ContextStore>,
|
||||||
|
editor: WeakView<Editor>,
|
||||||
confirm_behavior: ConfirmBehavior,
|
confirm_behavior: ConfirmBehavior,
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
|
@ -61,6 +63,7 @@ impl ContextPicker {
|
||||||
workspace,
|
workspace,
|
||||||
context_store,
|
context_store,
|
||||||
thread_store,
|
thread_store,
|
||||||
|
editor,
|
||||||
confirm_behavior,
|
confirm_behavior,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -131,6 +134,7 @@ impl ContextPicker {
|
||||||
FileContextPicker::new(
|
FileContextPicker::new(
|
||||||
context_picker.clone(),
|
context_picker.clone(),
|
||||||
self.workspace.clone(),
|
self.workspace.clone(),
|
||||||
|
self.editor.clone(),
|
||||||
self.context_store.clone(),
|
self.context_store.clone(),
|
||||||
self.confirm_behavior,
|
self.confirm_behavior,
|
||||||
cx,
|
cx,
|
||||||
|
|
|
@ -1,15 +1,25 @@
|
||||||
|
use std::collections::BTreeSet;
|
||||||
|
use std::ops::Range;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::sync::atomic::AtomicBool;
|
use std::sync::atomic::AtomicBool;
|
||||||
use std::sync::Arc;
|
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 file_icons::FileIcons;
|
use file_icons::FileIcons;
|
||||||
use fuzzy::PathMatch;
|
use fuzzy::PathMatch;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
AppContext, DismissEvent, FocusHandle, FocusableView, Stateful, Task, View, WeakModel, WeakView,
|
AnyElement, AppContext, DismissEvent, Empty, FocusHandle, FocusableView, Stateful, Task, View,
|
||||||
|
WeakModel, WeakView,
|
||||||
};
|
};
|
||||||
|
use multi_buffer::{MultiBufferPoint, MultiBufferRow};
|
||||||
use picker::{Picker, PickerDelegate};
|
use picker::{Picker, PickerDelegate};
|
||||||
use project::{PathMatchCandidateSet, ProjectPath, WorktreeId};
|
use project::{PathMatchCandidateSet, ProjectPath, WorktreeId};
|
||||||
use ui::{prelude::*, ListItem, Tooltip};
|
use rope::Point;
|
||||||
|
use text::SelectionGoal;
|
||||||
|
use ui::{prelude::*, ButtonLike, Disclosure, ElevationIndex, ListItem, Tooltip};
|
||||||
use util::ResultExt as _;
|
use util::ResultExt as _;
|
||||||
use workspace::{notifications::NotifyResultExt, Workspace};
|
use workspace::{notifications::NotifyResultExt, Workspace};
|
||||||
|
|
||||||
|
@ -24,6 +34,7 @@ impl FileContextPicker {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
context_picker: WeakView<ContextPicker>,
|
context_picker: WeakView<ContextPicker>,
|
||||||
workspace: WeakView<Workspace>,
|
workspace: WeakView<Workspace>,
|
||||||
|
editor: WeakView<Editor>,
|
||||||
context_store: WeakModel<ContextStore>,
|
context_store: WeakModel<ContextStore>,
|
||||||
confirm_behavior: ConfirmBehavior,
|
confirm_behavior: ConfirmBehavior,
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
|
@ -31,6 +42,7 @@ impl FileContextPicker {
|
||||||
let delegate = FileContextPickerDelegate::new(
|
let delegate = FileContextPickerDelegate::new(
|
||||||
context_picker,
|
context_picker,
|
||||||
workspace,
|
workspace,
|
||||||
|
editor,
|
||||||
context_store,
|
context_store,
|
||||||
confirm_behavior,
|
confirm_behavior,
|
||||||
);
|
);
|
||||||
|
@ -55,6 +67,7 @@ impl Render for FileContextPicker {
|
||||||
pub struct FileContextPickerDelegate {
|
pub struct FileContextPickerDelegate {
|
||||||
context_picker: WeakView<ContextPicker>,
|
context_picker: WeakView<ContextPicker>,
|
||||||
workspace: WeakView<Workspace>,
|
workspace: WeakView<Workspace>,
|
||||||
|
editor: WeakView<Editor>,
|
||||||
context_store: WeakModel<ContextStore>,
|
context_store: WeakModel<ContextStore>,
|
||||||
confirm_behavior: ConfirmBehavior,
|
confirm_behavior: ConfirmBehavior,
|
||||||
matches: Vec<PathMatch>,
|
matches: Vec<PathMatch>,
|
||||||
|
@ -65,12 +78,14 @@ impl FileContextPickerDelegate {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
context_picker: WeakView<ContextPicker>,
|
context_picker: WeakView<ContextPicker>,
|
||||||
workspace: WeakView<Workspace>,
|
workspace: WeakView<Workspace>,
|
||||||
|
editor: WeakView<Editor>,
|
||||||
context_store: WeakModel<ContextStore>,
|
context_store: WeakModel<ContextStore>,
|
||||||
confirm_behavior: ConfirmBehavior,
|
confirm_behavior: ConfirmBehavior,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
context_picker,
|
context_picker,
|
||||||
workspace,
|
workspace,
|
||||||
|
editor,
|
||||||
context_store,
|
context_store,
|
||||||
confirm_behavior,
|
confirm_behavior,
|
||||||
matches: Vec::new(),
|
matches: Vec::new(),
|
||||||
|
@ -196,11 +211,100 @@ impl PickerDelegate for FileContextPickerDelegate {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let Some(file_name) = mat
|
||||||
|
.path
|
||||||
|
.file_name()
|
||||||
|
.map(|os_str| os_str.to_string_lossy().into_owned())
|
||||||
|
else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let full_path = mat.path.display().to_string();
|
||||||
|
|
||||||
let project_path = ProjectPath {
|
let project_path = ProjectPath {
|
||||||
worktree_id: WorktreeId::from_usize(mat.worktree_id),
|
worktree_id: WorktreeId::from_usize(mat.worktree_id),
|
||||||
path: mat.path.clone(),
|
path: mat.path.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let Some(editor) = self.editor.upgrade() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
editor.update(cx, |editor, cx| {
|
||||||
|
editor.transact(cx, |editor, cx| {
|
||||||
|
// Move empty selections left by 1 column to select the `@`s, so they get overwritten when we insert.
|
||||||
|
{
|
||||||
|
let mut selections = editor.selections.all::<MultiBufferPoint>(cx);
|
||||||
|
|
||||||
|
for selection in selections.iter_mut() {
|
||||||
|
if selection.is_empty() {
|
||||||
|
let old_head = selection.head();
|
||||||
|
let new_head = MultiBufferPoint::new(
|
||||||
|
old_head.row,
|
||||||
|
old_head.column.saturating_sub(1),
|
||||||
|
);
|
||||||
|
selection.set_head(new_head, SelectionGoal::None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
editor.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(selections));
|
||||||
|
}
|
||||||
|
|
||||||
|
let start_anchors = {
|
||||||
|
let snapshot = editor.buffer().read(cx).snapshot(cx);
|
||||||
|
editor
|
||||||
|
.selections
|
||||||
|
.all::<Point>(cx)
|
||||||
|
.into_iter()
|
||||||
|
.map(|selection| snapshot.anchor_before(selection.start))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
};
|
||||||
|
|
||||||
|
editor.insert(&full_path, cx);
|
||||||
|
|
||||||
|
let end_anchors = {
|
||||||
|
let snapshot = editor.buffer().read(cx).snapshot(cx);
|
||||||
|
editor
|
||||||
|
.selections
|
||||||
|
.all::<Point>(cx)
|
||||||
|
.into_iter()
|
||||||
|
.map(|selection| snapshot.anchor_after(selection.end))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
};
|
||||||
|
|
||||||
|
editor.insert("\n", cx); // Needed to end the fold
|
||||||
|
|
||||||
|
let placeholder = FoldPlaceholder {
|
||||||
|
render: render_fold_icon_button(IconName::File, file_name.into()),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let render_trailer = move |_row, _unfold, _cx: &mut WindowContext| Empty.into_any();
|
||||||
|
|
||||||
|
let buffer = editor.buffer().read(cx).snapshot(cx);
|
||||||
|
let mut rows_to_fold = BTreeSet::new();
|
||||||
|
let crease_iter = start_anchors
|
||||||
|
.into_iter()
|
||||||
|
.zip(end_anchors)
|
||||||
|
.map(|(start, end)| {
|
||||||
|
rows_to_fold.insert(MultiBufferRow(start.to_point(&buffer).row));
|
||||||
|
|
||||||
|
Crease::inline(
|
||||||
|
start..end,
|
||||||
|
placeholder.clone(),
|
||||||
|
fold_toggle("tool-use"),
|
||||||
|
render_trailer,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
editor.insert_creases(crease_iter, cx);
|
||||||
|
|
||||||
|
for buffer_row in rows_to_fold {
|
||||||
|
editor.fold_at(&FoldAt { buffer_row }, cx);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
let Some(task) = self
|
let Some(task) = self
|
||||||
.context_store
|
.context_store
|
||||||
.update(cx, |context_store, cx| {
|
.update(cx, |context_store, cx| {
|
||||||
|
@ -334,3 +438,33 @@ pub fn render_file_context_entry(
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn render_fold_icon_button(
|
||||||
|
icon: IconName,
|
||||||
|
label: SharedString,
|
||||||
|
) -> Arc<dyn Send + Sync + Fn(FoldId, Range<Anchor>, &mut WindowContext) -> AnyElement> {
|
||||||
|
Arc::new(move |fold_id, _fold_range, _cx| {
|
||||||
|
ButtonLike::new(fold_id)
|
||||||
|
.style(ButtonStyle::Filled)
|
||||||
|
.layer(ElevationIndex::ElevatedSurface)
|
||||||
|
.child(Icon::new(icon))
|
||||||
|
.child(Label::new(label.clone()).single_line())
|
||||||
|
.into_any_element()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fold_toggle(
|
||||||
|
name: &'static str,
|
||||||
|
) -> impl Fn(
|
||||||
|
MultiBufferRow,
|
||||||
|
bool,
|
||||||
|
Arc<dyn Fn(bool, &mut WindowContext) + Send + Sync>,
|
||||||
|
&mut WindowContext,
|
||||||
|
) -> AnyElement {
|
||||||
|
move |row, is_folded, fold, _cx| {
|
||||||
|
Disclosure::new((name, row.0 as u64), !is_folded)
|
||||||
|
.toggle_state(is_folded)
|
||||||
|
.on_click(move |_e, cx| fold(!is_folded, cx))
|
||||||
|
.into_any_element()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -39,6 +39,7 @@ impl ContextStrip {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
context_store: Model<ContextStore>,
|
context_store: Model<ContextStore>,
|
||||||
workspace: WeakView<Workspace>,
|
workspace: WeakView<Workspace>,
|
||||||
|
editor: WeakView<Editor>,
|
||||||
thread_store: Option<WeakModel<ThreadStore>>,
|
thread_store: Option<WeakModel<ThreadStore>>,
|
||||||
context_picker_menu_handle: PopoverMenuHandle<ContextPicker>,
|
context_picker_menu_handle: PopoverMenuHandle<ContextPicker>,
|
||||||
suggest_context_kind: SuggestContextKind,
|
suggest_context_kind: SuggestContextKind,
|
||||||
|
@ -49,6 +50,7 @@ impl ContextStrip {
|
||||||
workspace.clone(),
|
workspace.clone(),
|
||||||
thread_store.clone(),
|
thread_store.clone(),
|
||||||
context_store.downgrade(),
|
context_store.downgrade(),
|
||||||
|
editor.clone(),
|
||||||
ConfirmBehavior::KeepOpen,
|
ConfirmBehavior::KeepOpen,
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
|
|
|
@ -834,6 +834,7 @@ impl PromptEditor<BufferCodegen> {
|
||||||
ContextStrip::new(
|
ContextStrip::new(
|
||||||
context_store.clone(),
|
context_store.clone(),
|
||||||
workspace.clone(),
|
workspace.clone(),
|
||||||
|
prompt_editor.downgrade(),
|
||||||
thread_store.clone(),
|
thread_store.clone(),
|
||||||
context_picker_menu_handle.clone(),
|
context_picker_menu_handle.clone(),
|
||||||
SuggestContextKind::Thread,
|
SuggestContextKind::Thread,
|
||||||
|
@ -985,6 +986,7 @@ impl PromptEditor<TerminalCodegen> {
|
||||||
ContextStrip::new(
|
ContextStrip::new(
|
||||||
context_store.clone(),
|
context_store.clone(),
|
||||||
workspace.clone(),
|
workspace.clone(),
|
||||||
|
prompt_editor.downgrade(),
|
||||||
thread_store.clone(),
|
thread_store.clone(),
|
||||||
context_picker_menu_handle.clone(),
|
context_picker_menu_handle.clone(),
|
||||||
SuggestContextKind::Thread,
|
SuggestContextKind::Thread,
|
||||||
|
|
|
@ -64,6 +64,7 @@ impl MessageEditor {
|
||||||
workspace.clone(),
|
workspace.clone(),
|
||||||
Some(thread_store.clone()),
|
Some(thread_store.clone()),
|
||||||
context_store.downgrade(),
|
context_store.downgrade(),
|
||||||
|
editor.downgrade(),
|
||||||
ConfirmBehavior::Close,
|
ConfirmBehavior::Close,
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
|
@ -73,6 +74,7 @@ impl MessageEditor {
|
||||||
ContextStrip::new(
|
ContextStrip::new(
|
||||||
context_store.clone(),
|
context_store.clone(),
|
||||||
workspace.clone(),
|
workspace.clone(),
|
||||||
|
editor.downgrade(),
|
||||||
Some(thread_store.clone()),
|
Some(thread_store.clone()),
|
||||||
context_picker_menu_handle.clone(),
|
context_picker_menu_handle.clone(),
|
||||||
SuggestContextKind::File,
|
SuggestContextKind::File,
|
||||||
|
|
|
@ -8,7 +8,7 @@ pub use crate::slash_command_working_set::*;
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use futures::stream::{self, BoxStream};
|
use futures::stream::{self, BoxStream};
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use gpui::{AnyElement, AppContext, ElementId, SharedString, Task, WeakView, WindowContext};
|
use gpui::{AppContext, SharedString, Task, WeakView, WindowContext};
|
||||||
use language::{BufferSnapshot, CodeLabel, LspAdapterDelegate, OffsetRangeExt};
|
use language::{BufferSnapshot, CodeLabel, LspAdapterDelegate, OffsetRangeExt};
|
||||||
pub use language_model::Role;
|
pub use language_model::Role;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
@ -103,12 +103,6 @@ pub trait SlashCommand: 'static + Send + Sync {
|
||||||
) -> Task<SlashCommandResult>;
|
) -> Task<SlashCommandResult>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type RenderFoldPlaceholder = Arc<
|
|
||||||
dyn Send
|
|
||||||
+ Sync
|
|
||||||
+ Fn(ElementId, Arc<dyn Fn(&mut WindowContext)>, &mut WindowContext) -> AnyElement,
|
|
||||||
>;
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub enum SlashCommandContent {
|
pub enum SlashCommandContent {
|
||||||
Text {
|
Text {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue