Remove headers from prompt library picker (#12889)

Also, as a drive-by, we're fixing up/down not working in inline
assistant editor.

Release Notes:

- N/A
This commit is contained in:
Antonio Scandurra 2024-06-11 15:59:30 +02:00 committed by GitHub
parent b6ea393d14
commit 53b0720d54
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 136 additions and 174 deletions

View File

@ -1026,8 +1026,16 @@ impl InlineAssistEditor {
) { ) {
match event { match event {
EditorEvent::Edited => { EditorEvent::Edited => {
let prompt = self.prompt_editor.read(cx).text(cx);
if self
.prompt_history_ix
.map_or(true, |ix| self.prompt_history[ix] != prompt)
{
self.prompt_history_ix.take();
self.pending_prompt = prompt;
}
self.edited_since_done = true; self.edited_since_done = true;
self.pending_prompt = self.prompt_editor.read(cx).text(cx);
cx.notify(); cx.notify();
} }
EditorEvent::Blurred => { EditorEvent::Blurred => {
@ -1102,13 +1110,19 @@ impl InlineAssistEditor {
if let Some(ix) = self.prompt_history_ix { if let Some(ix) = self.prompt_history_ix {
if ix > 0 { if ix > 0 {
self.prompt_history_ix = Some(ix - 1); self.prompt_history_ix = Some(ix - 1);
let prompt = self.prompt_history[ix - 1].clone(); let prompt = self.prompt_history[ix - 1].as_str();
self.set_prompt(&prompt, cx); self.prompt_editor.update(cx, |editor, cx| {
editor.set_text(prompt, cx);
editor.move_to_beginning(&Default::default(), cx);
});
} }
} else if !self.prompt_history.is_empty() { } else if !self.prompt_history.is_empty() {
self.prompt_history_ix = Some(self.prompt_history.len() - 1); self.prompt_history_ix = Some(self.prompt_history.len() - 1);
let prompt = self.prompt_history[self.prompt_history.len() - 1].clone(); let prompt = self.prompt_history[self.prompt_history.len() - 1].as_str();
self.set_prompt(&prompt, cx); self.prompt_editor.update(cx, |editor, cx| {
editor.set_text(prompt, cx);
editor.move_to_beginning(&Default::default(), cx);
});
} }
} }
@ -1116,24 +1130,21 @@ impl InlineAssistEditor {
if let Some(ix) = self.prompt_history_ix { if let Some(ix) = self.prompt_history_ix {
if ix < self.prompt_history.len() - 1 { if ix < self.prompt_history.len() - 1 {
self.prompt_history_ix = Some(ix + 1); self.prompt_history_ix = Some(ix + 1);
let prompt = self.prompt_history[ix + 1].clone(); let prompt = self.prompt_history[ix + 1].as_str();
self.set_prompt(&prompt, cx); self.prompt_editor.update(cx, |editor, cx| {
editor.set_text(prompt, cx);
editor.move_to_end(&Default::default(), cx)
});
} else { } else {
self.prompt_history_ix = None; self.prompt_history_ix = None;
let pending_prompt = self.pending_prompt.clone(); let prompt = self.pending_prompt.as_str();
self.set_prompt(&pending_prompt, cx);
}
}
}
fn set_prompt(&mut self, prompt: &str, cx: &mut ViewContext<Self>) {
self.prompt_editor.update(cx, |editor, cx| { self.prompt_editor.update(cx, |editor, cx| {
editor.buffer().update(cx, |buffer, cx| { editor.set_text(prompt, cx);
let len = buffer.len(cx); editor.move_to_end(&Default::default(), cx)
buffer.edit([(0..len, prompt)], None, cx);
});
}); });
} }
}
}
fn render_prompt_editor(&self, cx: &mut ViewContext<Self>) -> impl IntoElement { fn render_prompt_editor(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let settings = ThemeSettings::get_global(cx); let settings = ThemeSettings::get_global(cx);

View File

@ -13,10 +13,9 @@ use futures::{
}; };
use fuzzy::StringMatchCandidate; use fuzzy::StringMatchCandidate;
use gpui::{ use gpui::{
actions, percentage, point, size, Animation, AnimationExt, AnyElement, AppContext, actions, percentage, point, size, Animation, AnimationExt, AppContext, BackgroundExecutor,
BackgroundExecutor, Bounds, DevicePixels, EventEmitter, Global, PromptLevel, ReadGlobal, Bounds, DevicePixels, EventEmitter, Global, PromptLevel, ReadGlobal, Subscription, Task,
Subscription, Task, TitlebarOptions, Transformation, UpdateGlobal, View, WindowBounds, TitlebarOptions, Transformation, UpdateGlobal, View, WindowBounds, WindowHandle, WindowOptions,
WindowHandle, WindowOptions,
}; };
use heed::{types::SerdeBincode, Database, RoTxn}; use heed::{types::SerdeBincode, Database, RoTxn};
use language::{language_settings::SoftWrap, Buffer, LanguageRegistry}; use language::{language_settings::SoftWrap, Buffer, LanguageRegistry};
@ -26,6 +25,7 @@ use rope::Rope;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use settings::Settings; use settings::Settings;
use std::{ use std::{
cmp::Reverse,
future::Future, future::Future,
path::PathBuf, path::PathBuf,
sync::{atomic::AtomicBool, Arc}, sync::{atomic::AtomicBool, Arc},
@ -33,8 +33,8 @@ use std::{
}; };
use theme::ThemeSettings; use theme::ThemeSettings;
use ui::{ use ui::{
div, prelude::*, IconButtonShape, ListHeader, ListItem, ListItemSpacing, ListSubHeader, div, prelude::*, IconButtonShape, ListItem, ListItemSpacing, ParentElement, Render,
ParentElement, Render, SharedString, Styled, TitleBar, Tooltip, ViewContext, VisualContext, SharedString, Styled, TitleBar, Tooltip, ViewContext, VisualContext,
}; };
use util::{paths::PROMPTS_DIR, ResultExt, TryFutureExt}; use util::{paths::PROMPTS_DIR, ResultExt, TryFutureExt};
use uuid::Uuid; use uuid::Uuid;
@ -124,41 +124,23 @@ struct PromptEditor {
struct PromptPickerDelegate { struct PromptPickerDelegate {
store: Arc<PromptStore>, store: Arc<PromptStore>,
selected_index: usize, selected_index: usize,
entries: Vec<PromptPickerEntry>, matches: Vec<PromptMetadata>,
} }
enum PromptPickerEvent { enum PromptPickerEvent {
Selected { prompt_id: Option<PromptId> }, Selected { prompt_id: PromptId },
Confirmed { prompt_id: PromptId }, Confirmed { prompt_id: PromptId },
Deleted { prompt_id: PromptId }, Deleted { prompt_id: PromptId },
ToggledDefault { prompt_id: PromptId }, ToggledDefault { prompt_id: PromptId },
} }
#[derive(Debug)]
enum PromptPickerEntry {
DefaultPromptsHeader,
DefaultPromptsEmpty,
AllPromptsHeader,
AllPromptsEmpty,
Prompt(PromptMetadata),
}
impl PromptPickerEntry {
fn prompt_id(&self) -> Option<PromptId> {
match self {
PromptPickerEntry::Prompt(metadata) => Some(metadata.id),
_ => None,
}
}
}
impl EventEmitter<PromptPickerEvent> for Picker<PromptPickerDelegate> {} impl EventEmitter<PromptPickerEvent> for Picker<PromptPickerDelegate> {}
impl PickerDelegate for PromptPickerDelegate { impl PickerDelegate for PromptPickerDelegate {
type ListItem = AnyElement; type ListItem = ListItem;
fn match_count(&self) -> usize { fn match_count(&self) -> usize {
self.entries.len() self.matches.len()
} }
fn selected_index(&self) -> usize { fn selected_index(&self) -> usize {
@ -167,14 +149,11 @@ impl PickerDelegate for PromptPickerDelegate {
fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext<Picker<Self>>) { fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext<Picker<Self>>) {
self.selected_index = ix; self.selected_index = ix;
let prompt_id = if let Some(PromptPickerEntry::Prompt(prompt)) = if let Some(prompt) = self.matches.get(self.selected_index) {
self.entries.get(self.selected_index) cx.emit(PromptPickerEvent::Selected {
{ prompt_id: prompt.id,
Some(prompt.id) });
} else { }
None
};
cx.emit(PromptPickerEvent::Selected { prompt_id });
} }
fn placeholder_text(&self, _cx: &mut WindowContext) -> Arc<str> { fn placeholder_text(&self, _cx: &mut WindowContext) -> Arc<str> {
@ -183,48 +162,24 @@ impl PickerDelegate for PromptPickerDelegate {
fn update_matches(&mut self, query: String, cx: &mut ViewContext<Picker<Self>>) -> Task<()> { fn update_matches(&mut self, query: String, cx: &mut ViewContext<Picker<Self>>) -> Task<()> {
let search = self.store.search(query); let search = self.store.search(query);
let prev_prompt_id = self let prev_prompt_id = self.matches.get(self.selected_index).map(|mat| mat.id);
.entries
.get(self.selected_index)
.and_then(|mat| mat.prompt_id());
cx.spawn(|this, mut cx| async move { cx.spawn(|this, mut cx| async move {
let (entries, selected_index) = cx let (matches, selected_index) = cx
.background_executor() .background_executor()
.spawn(async move { .spawn(async move {
let prompts = search.await; let matches = search.await;
let (default_prompts, prompts) = prompts
.into_iter()
.partition::<Vec<_>, _>(|prompt| prompt.default);
let mut entries = Vec::new();
entries.push(PromptPickerEntry::DefaultPromptsHeader);
if default_prompts.is_empty() {
entries.push(PromptPickerEntry::DefaultPromptsEmpty);
} else {
entries.extend(default_prompts.into_iter().map(PromptPickerEntry::Prompt));
}
entries.push(PromptPickerEntry::AllPromptsHeader);
if prompts.is_empty() {
entries.push(PromptPickerEntry::AllPromptsEmpty);
} else {
entries.extend(prompts.into_iter().map(PromptPickerEntry::Prompt));
}
let selected_index = prev_prompt_id let selected_index = prev_prompt_id
.and_then(|prev_prompt_id| { .and_then(|prev_prompt_id| {
entries matches.iter().position(|entry| entry.id == prev_prompt_id)
.iter()
.position(|entry| entry.prompt_id() == Some(prev_prompt_id))
}) })
.or_else(|| entries.iter().position(|entry| entry.prompt_id().is_some()))
.unwrap_or(0); .unwrap_or(0);
(entries, selected_index) (matches, selected_index)
}) })
.await; .await;
this.update(&mut cx, |this, cx| { this.update(&mut cx, |this, cx| {
this.delegate.entries = entries; this.delegate.matches = matches;
this.delegate.set_selected_index(selected_index, cx); this.delegate.set_selected_index(selected_index, cx);
cx.notify(); cx.notify();
}) })
@ -233,7 +188,7 @@ impl PickerDelegate for PromptPickerDelegate {
} }
fn confirm(&mut self, _secondary: bool, cx: &mut ViewContext<Picker<Self>>) { fn confirm(&mut self, _secondary: bool, cx: &mut ViewContext<Picker<Self>>) {
if let Some(PromptPickerEntry::Prompt(prompt)) = self.entries.get(self.selected_index) { if let Some(prompt) = self.matches.get(self.selected_index) {
cx.emit(PromptPickerEvent::Confirmed { cx.emit(PromptPickerEvent::Confirmed {
prompt_id: prompt.id, prompt_id: prompt.id,
}); });
@ -248,46 +203,26 @@ impl PickerDelegate for PromptPickerDelegate {
selected: bool, selected: bool,
cx: &mut ViewContext<Picker<Self>>, cx: &mut ViewContext<Picker<Self>>,
) -> Option<Self::ListItem> { ) -> Option<Self::ListItem> {
let prompt = self.entries.get(ix)?; let prompt = self.matches.get(ix)?;
let element = match prompt {
PromptPickerEntry::DefaultPromptsHeader => ListHeader::new("Default Prompts")
.inset(true)
.start_slot(
Icon::new(IconName::Sparkle)
.color(Color::Muted)
.size(IconSize::XSmall),
)
.selected(selected)
.into_any_element(),
PromptPickerEntry::DefaultPromptsEmpty => {
ListSubHeader::new("Star a prompt to add it to the default context")
.inset(true)
.selected(selected)
.into_any_element()
}
PromptPickerEntry::AllPromptsHeader => ListHeader::new("All Prompts")
.inset(true)
.start_slot(
Icon::new(IconName::Library)
.color(Color::Muted)
.size(IconSize::XSmall),
)
.selected(selected)
.into_any_element(),
PromptPickerEntry::AllPromptsEmpty => ListSubHeader::new("No prompts")
.inset(true)
.selected(selected)
.into_any_element(),
PromptPickerEntry::Prompt(prompt) => {
let default = prompt.default; let default = prompt.default;
let prompt_id = prompt.id; let prompt_id = prompt.id;
ListItem::new(ix) let element = ListItem::new(ix)
.inset(true) .inset(true)
.spacing(ListItemSpacing::Sparse) .spacing(ListItemSpacing::Sparse)
.selected(selected) .selected(selected)
.child(h_flex().h_5().line_height(relative(1.)).child(Label::new( .child(h_flex().h_5().line_height(relative(1.)).child(Label::new(
prompt.title.clone().unwrap_or("Untitled".into()), prompt.title.clone().unwrap_or("Untitled".into()),
))) )))
.end_slot::<IconButton>(default.then(|| {
IconButton::new("toggle-default-prompt", IconName::SparkleFilled)
.selected(true)
.icon_color(Color::Accent)
.shape(IconButtonShape::Square)
.tooltip(move |cx| Tooltip::text("Remove from Default Prompt", cx))
.on_click(cx.listener(move |_, _, cx| {
cx.emit(PromptPickerEvent::ToggledDefault { prompt_id })
}))
}))
.end_hover_slot( .end_hover_slot(
h_flex() h_flex()
.gap_2() .gap_2()
@ -320,10 +255,7 @@ impl PickerDelegate for PromptPickerDelegate {
cx.emit(PromptPickerEvent::ToggledDefault { prompt_id }) cx.emit(PromptPickerEvent::ToggledDefault { prompt_id })
})), })),
), ),
) );
.into_any_element()
}
};
Some(element) Some(element)
} }
@ -349,11 +281,13 @@ impl PromptLibrary {
let delegate = PromptPickerDelegate { let delegate = PromptPickerDelegate {
store: store.clone(), store: store.clone(),
selected_index: 0, selected_index: 0,
entries: Vec::new(), matches: Vec::new(),
}; };
let picker = cx.new_view(|cx| { let picker = cx.new_view(|cx| {
let picker = Picker::list(delegate, cx).modal(false).max_height(None); let picker = Picker::uniform_list(delegate, cx)
.modal(false)
.max_height(None);
picker.focus(cx); picker.focus(cx);
picker picker
}); });
@ -376,11 +310,7 @@ impl PromptLibrary {
) { ) {
match event { match event {
PromptPickerEvent::Selected { prompt_id } => { PromptPickerEvent::Selected { prompt_id } => {
if let Some(prompt_id) = *prompt_id { self.load_prompt(*prompt_id, false, cx);
self.load_prompt(prompt_id, false, cx);
} else {
self.focus_picker(&Default::default(), cx);
}
} }
PromptPickerEvent::Confirmed { prompt_id } => { PromptPickerEvent::Confirmed { prompt_id } => {
self.load_prompt(*prompt_id, true, cx); self.load_prompt(*prompt_id, true, cx);
@ -567,21 +497,23 @@ impl PromptLibrary {
if let Some(prompt_id) = prompt_id { if let Some(prompt_id) = prompt_id {
if picker if picker
.delegate .delegate
.entries .matches
.get(picker.delegate.selected_index()) .get(picker.delegate.selected_index())
.map_or(true, |old_selected_prompt| { .map_or(true, |old_selected_prompt| {
old_selected_prompt.prompt_id() != Some(prompt_id) old_selected_prompt.id != prompt_id
}) })
{ {
if let Some(ix) = picker if let Some(ix) = picker
.delegate .delegate
.entries .matches
.iter() .iter()
.position(|mat| mat.prompt_id() == Some(prompt_id)) .position(|mat| mat.id == prompt_id)
{ {
picker.set_selected_index(ix, true, cx); picker.set_selected_index(ix, true, cx);
} }
} }
} else {
picker.focus(cx);
} }
}); });
cx.notify(); cx.notify();
@ -1105,7 +1037,7 @@ impl PromptStore {
let cached_metadata = self.metadata_cache.read().metadata.clone(); let cached_metadata = self.metadata_cache.read().metadata.clone();
let executor = self.executor.clone(); let executor = self.executor.clone();
self.executor.spawn(async move { self.executor.spawn(async move {
if query.is_empty() { let mut matches = if query.is_empty() {
cached_metadata cached_metadata
} else { } else {
let candidates = cached_metadata let candidates = cached_metadata
@ -1131,7 +1063,9 @@ impl PromptStore {
.into_iter() .into_iter()
.map(|mat| cached_metadata[mat.candidate_id].clone()) .map(|mat| cached_metadata[mat.candidate_id].clone())
.collect() .collect()
} };
matches.sort_by_key(|metadata| Reverse(metadata.default));
matches
}) })
} }

View File

@ -6540,6 +6540,8 @@ impl Editor {
} }
let text_layout_details = &self.text_layout_details(cx); let text_layout_details = &self.text_layout_details(cx);
let selection_count = self.selections.count();
let first_selection = self.selections.first_anchor();
self.change_selections(Some(Autoscroll::fit()), cx, |s| { self.change_selections(Some(Autoscroll::fit()), cx, |s| {
let line_mode = s.line_mode; let line_mode = s.line_mode;
@ -6556,7 +6558,12 @@ impl Editor {
); );
selection.collapse_to(cursor, goal); selection.collapse_to(cursor, goal);
}); });
}) });
if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
{
cx.propagate();
}
} }
pub fn move_up_by_lines(&mut self, action: &MoveUpByLines, cx: &mut ViewContext<Self>) { pub fn move_up_by_lines(&mut self, action: &MoveUpByLines, cx: &mut ViewContext<Self>) {
@ -6700,6 +6707,9 @@ impl Editor {
} }
let text_layout_details = &self.text_layout_details(cx); let text_layout_details = &self.text_layout_details(cx);
let selection_count = self.selections.count();
let first_selection = self.selections.first_anchor();
self.change_selections(Some(Autoscroll::fit()), cx, |s| { self.change_selections(Some(Autoscroll::fit()), cx, |s| {
let line_mode = s.line_mode; let line_mode = s.line_mode;
s.move_with(|map, selection| { s.move_with(|map, selection| {
@ -6716,6 +6726,11 @@ impl Editor {
selection.collapse_to(cursor, goal); selection.collapse_to(cursor, goal);
}); });
}); });
if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
{
cx.propagate();
}
} }
pub fn move_page_down(&mut self, action: &MovePageDown, cx: &mut ViewContext<Self>) { pub fn move_page_down(&mut self, action: &MovePageDown, cx: &mut ViewContext<Self>) {

View File

@ -84,7 +84,9 @@ impl<T: Copy + Ord> Selection<T> {
} }
self.goal = new_goal; self.goal = new_goal;
} }
}
impl<T: Copy> Selection<T> {
pub fn range(&self) -> Range<T> { pub fn range(&self) -> Range<T> {
self.start..self.end self.start..self.end
} }