assistant2: Add keybinding to toggle ContextPicker (#22124)

This PR adds an action and associated keybinding
(<kbd>Cmd+Shift+A</kbd>) to toggle the context picker.

This allows for adding context via the keyboard.

Release Notes:

- N/A
This commit is contained in:
Marshall Bowers 2024-12-16 20:20:32 -05:00 committed by GitHub
parent 28087934d1
commit 80431e5518
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 87 additions and 21 deletions

View file

@ -225,7 +225,8 @@
"bindings": { "bindings": {
"cmd-n": "assistant2::NewThread", "cmd-n": "assistant2::NewThread",
"cmd-shift-h": "assistant2::OpenHistory", "cmd-shift-h": "assistant2::OpenHistory",
"cmd-shift-m": "assistant2::ToggleModelSelector" "cmd-shift-m": "assistant2::ToggleModelSelector",
"cmd-shift-a": "assistant2::ToggleContextPicker"
} }
}, },
{ {
@ -618,6 +619,7 @@
"context": "PromptEditor", "context": "PromptEditor",
"use_key_equivalents": true, "use_key_equivalents": true,
"bindings": { "bindings": {
"cmd-shift-a": "assistant2::ToggleContextPicker",
"ctrl-[": "assistant::CyclePreviousInlineAssist", "ctrl-[": "assistant::CyclePreviousInlineAssist",
"ctrl-]": "assistant::CycleNextInlineAssist" "ctrl-]": "assistant::CycleNextInlineAssist"
} }

View file

@ -34,6 +34,7 @@ actions!(
[ [
ToggleFocus, ToggleFocus,
NewThread, NewThread,
ToggleContextPicker,
ToggleModelSelector, ToggleModelSelector,
OpenHistory, OpenHistory,
Chat, Chat,

View file

@ -1,6 +1,6 @@
use std::rc::Rc; use std::rc::Rc;
use gpui::{Model, View, WeakModel, WeakView}; use gpui::{FocusHandle, Model, View, WeakModel, WeakView};
use ui::{prelude::*, PopoverMenu, PopoverMenuHandle, Tooltip}; use ui::{prelude::*, PopoverMenu, PopoverMenuHandle, Tooltip};
use workspace::Workspace; use workspace::Workspace;
@ -8,11 +8,13 @@ use crate::context_picker::ContextPicker;
use crate::context_store::ContextStore; use crate::context_store::ContextStore;
use crate::thread_store::ThreadStore; use crate::thread_store::ThreadStore;
use crate::ui::ContextPill; use crate::ui::ContextPill;
use crate::ToggleContextPicker;
pub struct ContextStrip { pub struct ContextStrip {
context_store: Model<ContextStore>, context_store: Model<ContextStore>,
context_picker: View<ContextPicker>, context_picker: View<ContextPicker>,
pub(crate) context_picker_handle: PopoverMenuHandle<ContextPicker>, context_picker_menu_handle: PopoverMenuHandle<ContextPicker>,
focus_handle: FocusHandle,
} }
impl ContextStrip { impl ContextStrip {
@ -20,6 +22,8 @@ impl ContextStrip {
context_store: Model<ContextStore>, context_store: Model<ContextStore>,
workspace: WeakView<Workspace>, workspace: WeakView<Workspace>,
thread_store: Option<WeakModel<ThreadStore>>, thread_store: Option<WeakModel<ThreadStore>>,
focus_handle: FocusHandle,
context_picker_menu_handle: PopoverMenuHandle<ContextPicker>,
cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
) -> Self { ) -> Self {
Self { Self {
@ -32,7 +36,8 @@ impl ContextStrip {
cx, cx,
) )
}), }),
context_picker_handle: PopoverMenuHandle::default(), context_picker_menu_handle,
focus_handle,
} }
} }
} }
@ -41,6 +46,7 @@ impl Render for ContextStrip {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement { fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let context = self.context_store.read(cx).context(); let context = self.context_store.read(cx).context();
let context_picker = self.context_picker.clone(); let context_picker = self.context_picker.clone();
let focus_handle = self.focus_handle.clone();
h_flex() h_flex()
.flex_wrap() .flex_wrap()
@ -51,7 +57,15 @@ impl Render for ContextStrip {
.trigger( .trigger(
IconButton::new("add-context", IconName::Plus) IconButton::new("add-context", IconName::Plus)
.icon_size(IconSize::Small) .icon_size(IconSize::Small)
.style(ui::ButtonStyle::Filled), .style(ui::ButtonStyle::Filled)
.tooltip(move |cx| {
Tooltip::for_action_in(
"Add Context",
&ToggleContextPicker,
&focus_handle,
cx,
)
}),
) )
.attach(gpui::AnchorCorner::TopLeft) .attach(gpui::AnchorCorner::TopLeft)
.anchor(gpui::AnchorCorner::BottomLeft) .anchor(gpui::AnchorCorner::BottomLeft)
@ -59,7 +73,7 @@ impl Render for ContextStrip {
x: px(0.0), x: px(0.0),
y: px(-16.0), y: px(-16.0),
}) })
.with_handle(self.context_picker_handle.clone()), .with_handle(self.context_picker_menu_handle.clone()),
) )
.children(context.iter().map(|context| { .children(context.iter().map(|context| {
ContextPill::new(context.clone()).on_remove({ ContextPill::new(context.clone()).on_remove({

View file

@ -1,8 +1,8 @@
use crate::context::attach_context_to_message; use crate::context::attach_context_to_message;
use crate::context_picker::ContextPicker;
use crate::context_store::ContextStore; use crate::context_store::ContextStore;
use crate::context_strip::ContextStrip; use crate::context_strip::ContextStrip;
use crate::thread_store::ThreadStore; use crate::thread_store::ThreadStore;
use crate::AssistantPanel;
use crate::{ use crate::{
assistant_settings::AssistantSettings, assistant_settings::AssistantSettings,
prompts::PromptBuilder, prompts::PromptBuilder,
@ -10,6 +10,7 @@ use crate::{
terminal_inline_assistant::TerminalInlineAssistant, terminal_inline_assistant::TerminalInlineAssistant,
CycleNextInlineAssist, CyclePreviousInlineAssist, ToggleInlineAssist, CycleNextInlineAssist, CyclePreviousInlineAssist, ToggleInlineAssist,
}; };
use crate::{AssistantPanel, ToggleContextPicker};
use anyhow::{Context as _, Result}; use anyhow::{Context as _, Result};
use client::{telemetry::Telemetry, ErrorExt}; use client::{telemetry::Telemetry, ErrorExt};
use collections::{hash_map, HashMap, HashSet, VecDeque}; use collections::{hash_map, HashMap, HashSet, VecDeque};
@ -60,7 +61,9 @@ use telemetry_events::{AssistantEvent, AssistantKind, AssistantPhase};
use terminal_view::{terminal_panel::TerminalPanel, TerminalView}; use terminal_view::{terminal_panel::TerminalPanel, TerminalView};
use text::{OffsetRangeExt, ToPoint as _}; use text::{OffsetRangeExt, ToPoint as _};
use theme::ThemeSettings; use theme::ThemeSettings;
use ui::{prelude::*, CheckboxWithLabel, IconButtonShape, KeyBinding, Popover, Tooltip}; use ui::{
prelude::*, CheckboxWithLabel, IconButtonShape, KeyBinding, Popover, PopoverMenuHandle, Tooltip,
};
use util::{RangeExt, ResultExt}; use util::{RangeExt, ResultExt};
use workspace::{dock::Panel, ShowConfiguration}; use workspace::{dock::Panel, ShowConfiguration};
use workspace::{notifications::NotificationId, ItemHandle, Toast, Workspace}; use workspace::{notifications::NotificationId, ItemHandle, Toast, Workspace};
@ -1486,6 +1489,7 @@ struct PromptEditor {
id: InlineAssistId, id: InlineAssistId,
editor: View<Editor>, editor: View<Editor>,
context_strip: View<ContextStrip>, context_strip: View<ContextStrip>,
context_picker_menu_handle: PopoverMenuHandle<ContextPicker>,
language_model_selector: View<LanguageModelSelector>, language_model_selector: View<LanguageModelSelector>,
edited_since_done: bool, edited_since_done: bool,
gutter_dimensions: Arc<Mutex<GutterDimensions>>, gutter_dimensions: Arc<Mutex<GutterDimensions>>,
@ -1607,6 +1611,7 @@ impl Render for PromptEditor {
.bg(cx.theme().colors().editor_background) .bg(cx.theme().colors().editor_background)
.block_mouse_down() .block_mouse_down()
.cursor(CursorStyle::Arrow) .cursor(CursorStyle::Arrow)
.on_action(cx.listener(Self::toggle_context_picker))
.on_action(cx.listener(Self::confirm)) .on_action(cx.listener(Self::confirm))
.on_action(cx.listener(Self::cancel)) .on_action(cx.listener(Self::cancel))
.on_action(cx.listener(Self::move_up)) .on_action(cx.listener(Self::move_up))
@ -1747,13 +1752,22 @@ impl PromptEditor {
editor.set_placeholder_text(Self::placeholder_text(codegen.read(cx)), cx); editor.set_placeholder_text(Self::placeholder_text(codegen.read(cx)), cx);
editor editor
}); });
let context_picker_menu_handle = PopoverMenuHandle::default();
let mut this = Self { let mut this = Self {
id, id,
editor: prompt_editor, editor: prompt_editor.clone(),
context_strip: cx.new_view(|cx| { context_strip: cx.new_view(|cx| {
ContextStrip::new(context_store, workspace.clone(), thread_store.clone(), cx) ContextStrip::new(
context_store,
workspace.clone(),
thread_store.clone(),
prompt_editor.focus_handle(cx),
context_picker_menu_handle.clone(),
cx,
)
}), }),
context_picker_menu_handle,
language_model_selector: cx.new_view(|cx| { language_model_selector: cx.new_view(|cx| {
let fs = fs.clone(); let fs = fs.clone();
LanguageModelSelector::new( LanguageModelSelector::new(
@ -1911,6 +1925,10 @@ impl PromptEditor {
} }
} }
fn toggle_context_picker(&mut self, _: &ToggleContextPicker, cx: &mut ViewContext<Self>) {
self.context_picker_menu_handle.toggle(cx);
}
fn cancel(&mut self, _: &editor::actions::Cancel, cx: &mut ViewContext<Self>) { fn cancel(&mut self, _: &editor::actions::Cancel, cx: &mut ViewContext<Self>) {
match self.codegen.read(cx).status(cx) { match self.codegen.read(cx).status(cx) {
CodegenStatus::Idle | CodegenStatus::Done | CodegenStatus::Error(_) => { CodegenStatus::Idle | CodegenStatus::Done | CodegenStatus::Error(_) => {

View file

@ -14,17 +14,19 @@ use ui::{
use workspace::Workspace; use workspace::Workspace;
use crate::assistant_settings::AssistantSettings; use crate::assistant_settings::AssistantSettings;
use crate::context_picker::ContextPicker;
use crate::context_store::ContextStore; use crate::context_store::ContextStore;
use crate::context_strip::ContextStrip; use crate::context_strip::ContextStrip;
use crate::thread::{RequestKind, Thread}; use crate::thread::{RequestKind, Thread};
use crate::thread_store::ThreadStore; use crate::thread_store::ThreadStore;
use crate::{Chat, ToggleModelSelector}; use crate::{Chat, ToggleContextPicker, ToggleModelSelector};
pub struct MessageEditor { pub struct MessageEditor {
thread: Model<Thread>, thread: Model<Thread>,
editor: View<Editor>, editor: View<Editor>,
context_store: Model<ContextStore>, context_store: Model<ContextStore>,
context_strip: View<ContextStrip>, context_strip: View<ContextStrip>,
context_picker_menu_handle: PopoverMenuHandle<ContextPicker>,
language_model_selector: View<LanguageModelSelector>, language_model_selector: View<LanguageModelSelector>,
language_model_selector_menu_handle: PopoverMenuHandle<LanguageModelSelector>, language_model_selector_menu_handle: PopoverMenuHandle<LanguageModelSelector>,
use_tools: bool, use_tools: bool,
@ -39,25 +41,31 @@ impl MessageEditor {
cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
) -> Self { ) -> Self {
let context_store = cx.new_model(|_cx| ContextStore::new()); let context_store = cx.new_model(|_cx| ContextStore::new());
let context_picker_menu_handle = PopoverMenuHandle::default();
let editor = cx.new_view(|cx| {
let mut editor = Editor::auto_height(80, cx);
editor.set_placeholder_text("Ask anything, @ to add context", cx);
editor.set_show_indent_guides(false, cx);
editor
});
Self { Self {
thread, thread,
editor: cx.new_view(|cx| { editor: editor.clone(),
let mut editor = Editor::auto_height(80, cx);
editor.set_placeholder_text("Ask anything, @ to add context", cx);
editor.set_show_indent_guides(false, cx);
editor
}),
context_store: context_store.clone(), context_store: context_store.clone(),
context_strip: cx.new_view(|cx| { context_strip: cx.new_view(|cx| {
ContextStrip::new( ContextStrip::new(
context_store, context_store,
workspace.clone(), workspace.clone(),
Some(thread_store.clone()), Some(thread_store.clone()),
editor.focus_handle(cx),
context_picker_menu_handle.clone(),
cx, cx,
) )
}), }),
context_picker_menu_handle,
language_model_selector: cx.new_view(|cx| { language_model_selector: cx.new_view(|cx| {
let fs = fs.clone(); let fs = fs.clone();
LanguageModelSelector::new( LanguageModelSelector::new(
@ -80,6 +88,10 @@ impl MessageEditor {
self.language_model_selector_menu_handle.toggle(cx); self.language_model_selector_menu_handle.toggle(cx);
} }
fn toggle_context_picker(&mut self, _: &ToggleContextPicker, cx: &mut ViewContext<Self>) {
self.context_picker_menu_handle.toggle(cx);
}
fn chat(&mut self, _: &Chat, cx: &mut ViewContext<Self>) { fn chat(&mut self, _: &Chat, cx: &mut ViewContext<Self>) {
self.send_to_model(RequestKind::Chat, cx); self.send_to_model(RequestKind::Chat, cx);
} }
@ -193,6 +205,7 @@ impl Render for MessageEditor {
.key_context("MessageEditor") .key_context("MessageEditor")
.on_action(cx.listener(Self::chat)) .on_action(cx.listener(Self::chat))
.on_action(cx.listener(Self::toggle_model_selector)) .on_action(cx.listener(Self::toggle_model_selector))
.on_action(cx.listener(Self::toggle_context_picker))
.size_full() .size_full()
.gap_2() .gap_2()
.p_2() .p_2()

View file

@ -1,9 +1,11 @@
use crate::assistant_settings::AssistantSettings; use crate::assistant_settings::AssistantSettings;
use crate::context::attach_context_to_message; use crate::context::attach_context_to_message;
use crate::context_picker::ContextPicker;
use crate::context_store::ContextStore; use crate::context_store::ContextStore;
use crate::context_strip::ContextStrip; use crate::context_strip::ContextStrip;
use crate::prompts::PromptBuilder; use crate::prompts::PromptBuilder;
use crate::thread_store::ThreadStore; use crate::thread_store::ThreadStore;
use crate::ToggleContextPicker;
use anyhow::{Context as _, Result}; use anyhow::{Context as _, Result};
use client::telemetry::Telemetry; use client::telemetry::Telemetry;
use collections::{HashMap, VecDeque}; use collections::{HashMap, VecDeque};
@ -29,7 +31,7 @@ use telemetry_events::{AssistantEvent, AssistantKind, AssistantPhase};
use terminal::Terminal; use terminal::Terminal;
use terminal_view::TerminalView; use terminal_view::TerminalView;
use theme::ThemeSettings; use theme::ThemeSettings;
use ui::{prelude::*, text_for_action, IconButtonShape, Tooltip}; use ui::{prelude::*, text_for_action, IconButtonShape, PopoverMenuHandle, Tooltip};
use util::ResultExt; use util::ResultExt;
use workspace::{notifications::NotificationId, Toast, Workspace}; use workspace::{notifications::NotificationId, Toast, Workspace};
@ -460,6 +462,7 @@ struct PromptEditor {
height_in_lines: u8, height_in_lines: u8,
editor: View<Editor>, editor: View<Editor>,
context_strip: View<ContextStrip>, context_strip: View<ContextStrip>,
context_picker_menu_handle: PopoverMenuHandle<ContextPicker>,
language_model_selector: View<LanguageModelSelector>, language_model_selector: View<LanguageModelSelector>,
edited_since_done: bool, edited_since_done: bool,
prompt_history: VecDeque<String>, prompt_history: VecDeque<String>,
@ -580,7 +583,9 @@ impl Render for PromptEditor {
.size_full() .size_full()
.child( .child(
h_flex() h_flex()
.key_context("PromptEditor")
.bg(cx.theme().colors().editor_background) .bg(cx.theme().colors().editor_background)
.on_action(cx.listener(Self::toggle_context_picker))
.on_action(cx.listener(Self::confirm)) .on_action(cx.listener(Self::confirm))
.on_action(cx.listener(Self::secondary_confirm)) .on_action(cx.listener(Self::secondary_confirm))
.on_action(cx.listener(Self::cancel)) .on_action(cx.listener(Self::cancel))
@ -674,14 +679,23 @@ impl PromptEditor {
editor.set_placeholder_text(Self::placeholder_text(cx), cx); editor.set_placeholder_text(Self::placeholder_text(cx), cx);
editor editor
}); });
let context_picker_menu_handle = PopoverMenuHandle::default();
let mut this = Self { let mut this = Self {
id, id,
height_in_lines: 1, height_in_lines: 1,
editor: prompt_editor, editor: prompt_editor.clone(),
context_strip: cx.new_view(|cx| { context_strip: cx.new_view(|cx| {
ContextStrip::new(context_store, workspace.clone(), thread_store.clone(), cx) ContextStrip::new(
context_store,
workspace.clone(),
thread_store.clone(),
prompt_editor.focus_handle(cx),
context_picker_menu_handle.clone(),
cx,
)
}), }),
context_picker_menu_handle,
language_model_selector: cx.new_view(|cx| { language_model_selector: cx.new_view(|cx| {
let fs = fs.clone(); let fs = fs.clone();
LanguageModelSelector::new( LanguageModelSelector::new(
@ -790,6 +804,10 @@ impl PromptEditor {
} }
} }
fn toggle_context_picker(&mut self, _: &ToggleContextPicker, cx: &mut ViewContext<Self>) {
self.context_picker_menu_handle.toggle(cx);
}
fn cancel(&mut self, _: &editor::actions::Cancel, cx: &mut ViewContext<Self>) { fn cancel(&mut self, _: &editor::actions::Cancel, cx: &mut ViewContext<Self>) {
match &self.codegen.read(cx).status { match &self.codegen.read(cx).status {
CodegenStatus::Idle | CodegenStatus::Done | CodegenStatus::Error(_) => { CodegenStatus::Idle | CodegenStatus::Done | CodegenStatus::Error(_) => {