Add SearchHistory to command palette
This makes vim mode significantly nicer to use
This commit is contained in:
parent
e5402d5464
commit
18c5cbacd6
5 changed files with 136 additions and 2 deletions
|
@ -23,6 +23,7 @@ gpui.workspace = true
|
|||
log.workspace = true
|
||||
picker.workspace = true
|
||||
postage.workspace = true
|
||||
project.workspace = true
|
||||
serde.workspace = true
|
||||
settings.workspace = true
|
||||
theme.workspace = true
|
||||
|
|
|
@ -14,12 +14,13 @@ use command_palette_hooks::{
|
|||
|
||||
use fuzzy::{StringMatch, StringMatchCandidate};
|
||||
use gpui::{
|
||||
Action, App, Context, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable,
|
||||
Action, App, Context, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, Global,
|
||||
ParentElement, Render, Styled, Task, WeakEntity, Window,
|
||||
};
|
||||
use persistence::COMMAND_PALETTE_HISTORY;
|
||||
use picker::{Picker, PickerDelegate};
|
||||
use picker::{Direction, Picker, PickerDelegate};
|
||||
use postage::{sink::Sink, stream::Stream};
|
||||
use project::search_history::{QueryInsertionBehavior, SearchHistory, SearchHistoryCursor};
|
||||
use settings::Settings;
|
||||
use ui::{HighlightedLabel, KeyBinding, ListItem, ListItemSpacing, h_flex, prelude::*, v_flex};
|
||||
use util::ResultExt;
|
||||
|
@ -38,6 +39,21 @@ pub struct CommandPalette {
|
|||
picker: Entity<Picker<CommandPaletteDelegate>>,
|
||||
}
|
||||
|
||||
struct CommandPaletteSearchHistory {
|
||||
history: SearchHistory,
|
||||
}
|
||||
impl Default for CommandPaletteSearchHistory {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
history: SearchHistory::new(
|
||||
Some(500),
|
||||
QueryInsertionBehavior::ReplacePreviousIfContains,
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Global for CommandPaletteSearchHistory {}
|
||||
|
||||
/// Removes subsequent whitespace characters and double colons from the query.
|
||||
///
|
||||
/// This improves the likelihood of a match by either humanized name or keymap-style name.
|
||||
|
@ -145,6 +161,7 @@ impl Render for CommandPalette {
|
|||
|
||||
pub struct CommandPaletteDelegate {
|
||||
latest_query: String,
|
||||
history_cursor: SearchHistoryCursor,
|
||||
command_palette: WeakEntity<CommandPalette>,
|
||||
all_commands: Vec<Command>,
|
||||
commands: Vec<Command>,
|
||||
|
@ -182,6 +199,7 @@ impl CommandPaletteDelegate {
|
|||
all_commands: commands.clone(),
|
||||
matches: vec![],
|
||||
commands,
|
||||
history_cursor: SearchHistoryCursor::default(),
|
||||
selected_ix: 0,
|
||||
previous_focus_handle,
|
||||
latest_query: String::new(),
|
||||
|
@ -378,11 +396,48 @@ impl PickerDelegate for CommandPaletteDelegate {
|
|||
.log_err();
|
||||
}
|
||||
|
||||
fn handle_history(
|
||||
&mut self,
|
||||
direction: Direction,
|
||||
_window: &mut Window,
|
||||
cx: &mut Context<Picker<Self>>,
|
||||
) -> Option<String> {
|
||||
dbg!(self.selected_ix);
|
||||
if self.selected_ix != 0 {
|
||||
return None;
|
||||
}
|
||||
match direction {
|
||||
Direction::Up => {
|
||||
cx.update_default_global(|history: &mut CommandPaletteSearchHistory, _| {
|
||||
history
|
||||
.history
|
||||
.previous(&mut self.history_cursor)
|
||||
.map(|s| s.to_owned())
|
||||
.or(Some("".to_owned()))
|
||||
})
|
||||
}
|
||||
Direction::Down => {
|
||||
cx.update_default_global(|history: &mut CommandPaletteSearchHistory, _| {
|
||||
history
|
||||
.history
|
||||
.previous(&mut self.history_cursor)
|
||||
.map(|s| s.to_owned())
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn confirm(&mut self, _: bool, window: &mut Window, cx: &mut Context<Picker<Self>>) {
|
||||
if self.matches.is_empty() {
|
||||
self.dismissed(window, cx);
|
||||
return;
|
||||
}
|
||||
|
||||
cx.update_default_global(|history: &mut CommandPaletteSearchHistory, _| {
|
||||
history
|
||||
.history
|
||||
.add(&mut self.history_cursor, self.latest_query.clone())
|
||||
});
|
||||
let action_ix = self.matches[self.selected_ix].candidate_id;
|
||||
let command = self.commands.swap_remove(action_ix);
|
||||
telemetry::event!(
|
||||
|
|
|
@ -144,6 +144,16 @@ pub trait PickerDelegate: Sized + 'static {
|
|||
false
|
||||
}
|
||||
|
||||
// Allow intercepting up and down for history navigation in the command palette.
|
||||
fn handle_history(
|
||||
&mut self,
|
||||
_direction: Direction,
|
||||
_window: &mut Window,
|
||||
_cx: &mut Context<Picker<Self>>,
|
||||
) -> Option<String> {
|
||||
None
|
||||
}
|
||||
|
||||
/// Override if you want to have <enter> update the query instead of confirming.
|
||||
fn confirm_update_query(
|
||||
&mut self,
|
||||
|
@ -436,6 +446,10 @@ impl<D: PickerDelegate> Picker<D> {
|
|||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
if let Some(query) = self.delegate.handle_history(Direction::Down, window, cx) {
|
||||
self.set_query(query, window, cx);
|
||||
return;
|
||||
}
|
||||
let count = self.delegate.match_count();
|
||||
if count > 0 {
|
||||
let index = self.delegate.selected_index();
|
||||
|
@ -455,6 +469,10 @@ impl<D: PickerDelegate> Picker<D> {
|
|||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
if let Some(query) = self.delegate.handle_history(Direction::Down, window, cx) {
|
||||
self.set_query(query, window, cx);
|
||||
return;
|
||||
}
|
||||
let count = self.delegate.match_count();
|
||||
if count > 0 {
|
||||
let index = self.delegate.selected_index();
|
||||
|
|
|
@ -2584,4 +2584,46 @@ mod test {
|
|||
assert_active_item(workspace, path!("/root/dir/file_3.rs"), "", cx);
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_command_history(cx: &mut TestAppContext) {
|
||||
let mut cx = NeovimBackedTestContext::new(cx).await;
|
||||
|
||||
cx.set_shared_state(indoc! {"
|
||||
The quick
|
||||
brown fox
|
||||
ˇjumps over
|
||||
the lazy dog
|
||||
"})
|
||||
.await;
|
||||
|
||||
cx.simulate_shared_keystrokes(": s / o / a enter").await;
|
||||
|
||||
cx.shared_state().await.assert_eq(indoc! {"
|
||||
The quick
|
||||
brown fox
|
||||
ˇjumps aver
|
||||
the lazy dog
|
||||
"});
|
||||
|
||||
// n.b ^ fixes a selection mismatch after u. should be removable eventually
|
||||
cx.simulate_shared_keystrokes("u ^").await;
|
||||
|
||||
cx.shared_state().await.assert_eq(indoc! {"
|
||||
The quick
|
||||
brown fox
|
||||
ˇjumps over
|
||||
the lazy dog
|
||||
"});
|
||||
|
||||
cx.simulate_shared_keystrokes(": up backspace e enter")
|
||||
.await;
|
||||
|
||||
cx.shared_state().await.assert_eq(indoc! {"
|
||||
The quick
|
||||
brown fox
|
||||
ˇjumps ever
|
||||
the lazy dog
|
||||
"});
|
||||
}
|
||||
}
|
||||
|
|
18
crates/vim/test_data/test_command_history.json
Normal file
18
crates/vim/test_data/test_command_history.json
Normal file
|
@ -0,0 +1,18 @@
|
|||
{"Put":{"state":"The quick\nbrown fox\nˇjumps over\nthe lazy dog\n"}}
|
||||
{"Key":":"}
|
||||
{"Key":"s"}
|
||||
{"Key":"/"}
|
||||
{"Key":"o"}
|
||||
{"Key":"/"}
|
||||
{"Key":"a"}
|
||||
{"Key":"enter"}
|
||||
{"Get":{"state":"The quick\nbrown fox\nˇjumps aver\nthe lazy dog\n","mode":"Normal"}}
|
||||
{"Key":"u"}
|
||||
{"Key":"^"}
|
||||
{"Get":{"state":"The quick\nbrown fox\nˇjumps over\nthe lazy dog\n","mode":"Normal"}}
|
||||
{"Key":":"}
|
||||
{"Key":"up"}
|
||||
{"Key":"backspace"}
|
||||
{"Key":"e"}
|
||||
{"Key":"enter"}
|
||||
{"Get":{"state":"The quick\nbrown fox\nˇjumps ever\nthe lazy dog\n","mode":"Normal"}}
|
Loading…
Add table
Add a link
Reference in a new issue