Select the second item in the file finder by default (#6947)
This PR completes the first task of the Tabless editing feature (#6424). It makes file finder select the previously opened file by default which allows the user to quickly switch between two last opened files by clicking `Cmd-P + Enter`. This feature was also requested in #4663 comments. Release Notes: * Improved file finder selection: currently opened item is not selected now
This commit is contained in:
parent
e65a76f0ec
commit
0edffd9248
2 changed files with 206 additions and 12 deletions
|
@ -61,7 +61,7 @@ impl FileFinder {
|
||||||
let abs_path = project
|
let abs_path = project
|
||||||
.worktree_for_id(project_path.worktree_id, cx)
|
.worktree_for_id(project_path.worktree_id, cx)
|
||||||
.map(|worktree| worktree.read(cx).abs_path().join(&project_path.path));
|
.map(|worktree| worktree.read(cx).abs_path().join(&project_path.path));
|
||||||
FoundPath::new(project_path, abs_path)
|
FoundPath::new_opened_file(project_path, abs_path)
|
||||||
});
|
});
|
||||||
|
|
||||||
// if exists, bubble the currently opened path to the top
|
// if exists, bubble the currently opened path to the top
|
||||||
|
@ -139,7 +139,7 @@ pub struct FileFinderDelegate {
|
||||||
latest_search_query: Option<PathLikeWithPosition<FileSearchQuery>>,
|
latest_search_query: Option<PathLikeWithPosition<FileSearchQuery>>,
|
||||||
currently_opened_path: Option<FoundPath>,
|
currently_opened_path: Option<FoundPath>,
|
||||||
matches: Matches,
|
matches: Matches,
|
||||||
selected_index: Option<usize>,
|
selected_index: usize,
|
||||||
cancel_flag: Arc<AtomicBool>,
|
cancel_flag: Arc<AtomicBool>,
|
||||||
history_items: Vec<FoundPath>,
|
history_items: Vec<FoundPath>,
|
||||||
}
|
}
|
||||||
|
@ -231,7 +231,15 @@ impl Matches {
|
||||||
&mut self.history,
|
&mut self.history,
|
||||||
history_items_to_show,
|
history_items_to_show,
|
||||||
100,
|
100,
|
||||||
|(_, a), (_, b)| b.cmp(a),
|
|(ap, a), (bp, b)| {
|
||||||
|
if ap.opened_file {
|
||||||
|
return cmp::Ordering::Less;
|
||||||
|
}
|
||||||
|
if bp.opened_file {
|
||||||
|
return cmp::Ordering::Greater;
|
||||||
|
}
|
||||||
|
b.cmp(a)
|
||||||
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
if extend_old_matches {
|
if extend_old_matches {
|
||||||
|
@ -305,11 +313,24 @@ fn matching_history_item_paths(
|
||||||
struct FoundPath {
|
struct FoundPath {
|
||||||
project: ProjectPath,
|
project: ProjectPath,
|
||||||
absolute: Option<PathBuf>,
|
absolute: Option<PathBuf>,
|
||||||
|
opened_file: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FoundPath {
|
impl FoundPath {
|
||||||
fn new(project: ProjectPath, absolute: Option<PathBuf>) -> Self {
|
fn new(project: ProjectPath, absolute: Option<PathBuf>) -> Self {
|
||||||
Self { project, absolute }
|
Self {
|
||||||
|
project,
|
||||||
|
absolute,
|
||||||
|
opened_file: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_opened_file(project: ProjectPath, absolute: Option<PathBuf>) -> Self {
|
||||||
|
Self {
|
||||||
|
project,
|
||||||
|
absolute,
|
||||||
|
opened_file: true,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -372,7 +393,7 @@ impl FileFinderDelegate {
|
||||||
latest_search_query: None,
|
latest_search_query: None,
|
||||||
currently_opened_path,
|
currently_opened_path,
|
||||||
matches: Matches::default(),
|
matches: Matches::default(),
|
||||||
selected_index: None,
|
selected_index: 0,
|
||||||
cancel_flag: Arc::new(AtomicBool::new(false)),
|
cancel_flag: Arc::new(AtomicBool::new(false)),
|
||||||
history_items,
|
history_items,
|
||||||
}
|
}
|
||||||
|
@ -427,7 +448,6 @@ impl FileFinderDelegate {
|
||||||
let did_cancel = cancel_flag.load(atomic::Ordering::Relaxed);
|
let did_cancel = cancel_flag.load(atomic::Ordering::Relaxed);
|
||||||
picker
|
picker
|
||||||
.update(&mut cx, |picker, cx| {
|
.update(&mut cx, |picker, cx| {
|
||||||
picker.delegate.selected_index.take();
|
|
||||||
picker
|
picker
|
||||||
.delegate
|
.delegate
|
||||||
.set_search_matches(search_id, did_cancel, query, matches, cx)
|
.set_search_matches(search_id, did_cancel, query, matches, cx)
|
||||||
|
@ -460,6 +480,7 @@ impl FileFinderDelegate {
|
||||||
);
|
);
|
||||||
self.latest_search_query = Some(query);
|
self.latest_search_query = Some(query);
|
||||||
self.latest_search_did_cancel = did_cancel;
|
self.latest_search_did_cancel = did_cancel;
|
||||||
|
self.selected_index = self.calculate_selected_index();
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -630,6 +651,20 @@ impl FileFinderDelegate {
|
||||||
.log_err();
|
.log_err();
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Calculates selection index after the user performed search.
|
||||||
|
/// Prefers to return 1 if the top visible item corresponds to the currently opened file, otherwise returns 0.
|
||||||
|
fn calculate_selected_index(&self) -> usize {
|
||||||
|
let first = self.matches.history.get(0);
|
||||||
|
if let Some(first) = first {
|
||||||
|
if !first.0.opened_file {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
(self.matches.len() - 1).min(1)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PickerDelegate for FileFinderDelegate {
|
impl PickerDelegate for FileFinderDelegate {
|
||||||
|
@ -644,11 +679,11 @@ impl PickerDelegate for FileFinderDelegate {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn selected_index(&self) -> usize {
|
fn selected_index(&self) -> usize {
|
||||||
self.selected_index.unwrap_or(0)
|
self.selected_index
|
||||||
}
|
}
|
||||||
|
|
||||||
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 = Some(ix);
|
self.selected_index = ix;
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -671,7 +706,6 @@ impl PickerDelegate for FileFinderDelegate {
|
||||||
if raw_query.is_empty() {
|
if raw_query.is_empty() {
|
||||||
let project = self.project.read(cx);
|
let project = self.project.read(cx);
|
||||||
self.latest_search_id = post_inc(&mut self.search_count);
|
self.latest_search_id = post_inc(&mut self.search_count);
|
||||||
self.selected_index.take();
|
|
||||||
self.matches = Matches {
|
self.matches = Matches {
|
||||||
history: self
|
history: self
|
||||||
.history_items
|
.history_items
|
||||||
|
@ -687,6 +721,7 @@ impl PickerDelegate for FileFinderDelegate {
|
||||||
.collect(),
|
.collect(),
|
||||||
search: Vec::new(),
|
search: Vec::new(),
|
||||||
};
|
};
|
||||||
|
self.selected_index = self.calculate_selected_index();
|
||||||
cx.notify();
|
cx.notify();
|
||||||
Task::ready(())
|
Task::ready(())
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -1062,6 +1062,120 @@ async fn test_search_sorts_history_items(cx: &mut gpui::TestAppContext) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
async fn test_select_current_open_file_when_no_history(cx: &mut gpui::TestAppContext) {
|
||||||
|
let app_state = init_test(cx);
|
||||||
|
|
||||||
|
app_state
|
||||||
|
.fs
|
||||||
|
.as_fake()
|
||||||
|
.insert_tree(
|
||||||
|
"/root",
|
||||||
|
json!({
|
||||||
|
"test": {
|
||||||
|
"1_qw": "",
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
|
||||||
|
let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
|
||||||
|
// Open new buffer
|
||||||
|
open_queried_buffer("1", 1, "1_qw", &workspace, cx).await;
|
||||||
|
|
||||||
|
let picker = open_file_picker(&workspace, cx);
|
||||||
|
picker.update(cx, |finder, _| {
|
||||||
|
assert_match_selection(&finder, 0, "1_qw");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
async fn test_keep_opened_file_on_top_of_search_results_and_select_next_one(
|
||||||
|
cx: &mut TestAppContext,
|
||||||
|
) {
|
||||||
|
let app_state = init_test(cx);
|
||||||
|
|
||||||
|
app_state
|
||||||
|
.fs
|
||||||
|
.as_fake()
|
||||||
|
.insert_tree(
|
||||||
|
"/src",
|
||||||
|
json!({
|
||||||
|
"test": {
|
||||||
|
"bar.rs": "// Bar file",
|
||||||
|
"lib.rs": "// Lib file",
|
||||||
|
"maaa.rs": "// Maaaaaaa",
|
||||||
|
"main.rs": "// Main file",
|
||||||
|
"moo.rs": "// Moooooo",
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).await;
|
||||||
|
let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
|
||||||
|
|
||||||
|
open_close_queried_buffer("bar", 1, "bar.rs", &workspace, cx).await;
|
||||||
|
open_close_queried_buffer("lib", 1, "lib.rs", &workspace, cx).await;
|
||||||
|
open_queried_buffer("main", 1, "main.rs", &workspace, cx).await;
|
||||||
|
|
||||||
|
// main.rs is on top, previously used is selected
|
||||||
|
let picker = open_file_picker(&workspace, cx);
|
||||||
|
picker.update(cx, |finder, _| {
|
||||||
|
assert_eq!(finder.delegate.matches.len(), 3);
|
||||||
|
assert_match_at_position(finder, 0, "main.rs");
|
||||||
|
assert_match_selection(finder, 1, "lib.rs");
|
||||||
|
});
|
||||||
|
|
||||||
|
// all files match, main.rs is still on top
|
||||||
|
picker
|
||||||
|
.update(cx, |finder, cx| {
|
||||||
|
finder.delegate.update_matches(".rs".to_string(), cx)
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
picker.update(cx, |finder, _| {
|
||||||
|
assert_eq!(finder.delegate.matches.len(), 5);
|
||||||
|
assert_match_at_position(finder, 0, "main.rs");
|
||||||
|
assert_match_selection(finder, 1, "bar.rs");
|
||||||
|
});
|
||||||
|
|
||||||
|
// main.rs is not among matches, select top item
|
||||||
|
picker
|
||||||
|
.update(cx, |finder, cx| {
|
||||||
|
finder.delegate.update_matches("b".to_string(), cx)
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
picker.update(cx, |finder, _| {
|
||||||
|
assert_eq!(finder.delegate.matches.len(), 2);
|
||||||
|
assert_match_at_position(finder, 0, "bar.rs");
|
||||||
|
});
|
||||||
|
|
||||||
|
// main.rs is back, put it on top and select next item
|
||||||
|
picker
|
||||||
|
.update(cx, |finder, cx| {
|
||||||
|
finder.delegate.update_matches("m".to_string(), cx)
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
picker.update(cx, |finder, _| {
|
||||||
|
assert_eq!(finder.delegate.matches.len(), 3);
|
||||||
|
assert_match_at_position(finder, 0, "main.rs");
|
||||||
|
assert_match_selection(finder, 1, "moo.rs");
|
||||||
|
});
|
||||||
|
|
||||||
|
// get back to the initial state
|
||||||
|
picker
|
||||||
|
.update(cx, |finder, cx| {
|
||||||
|
finder.delegate.update_matches("".to_string(), cx)
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
picker.update(cx, |finder, _| {
|
||||||
|
assert_eq!(finder.delegate.matches.len(), 3);
|
||||||
|
assert_match_at_position(finder, 0, "main.rs");
|
||||||
|
assert_match_selection(finder, 1, "lib.rs");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_history_items_vs_very_good_external_match(cx: &mut gpui::TestAppContext) {
|
async fn test_history_items_vs_very_good_external_match(cx: &mut gpui::TestAppContext) {
|
||||||
let app_state = init_test(cx);
|
let app_state = init_test(cx);
|
||||||
|
@ -1172,6 +1286,27 @@ async fn open_close_queried_buffer(
|
||||||
expected_editor_title: &str,
|
expected_editor_title: &str,
|
||||||
workspace: &View<Workspace>,
|
workspace: &View<Workspace>,
|
||||||
cx: &mut gpui::VisualTestContext,
|
cx: &mut gpui::VisualTestContext,
|
||||||
|
) -> Vec<FoundPath> {
|
||||||
|
let history_items = open_queried_buffer(
|
||||||
|
input,
|
||||||
|
expected_matches,
|
||||||
|
expected_editor_title,
|
||||||
|
workspace,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
cx.dispatch_action(workspace::CloseActiveItem { save_intent: None });
|
||||||
|
|
||||||
|
history_items
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn open_queried_buffer(
|
||||||
|
input: &str,
|
||||||
|
expected_matches: usize,
|
||||||
|
expected_editor_title: &str,
|
||||||
|
workspace: &View<Workspace>,
|
||||||
|
cx: &mut gpui::VisualTestContext,
|
||||||
) -> Vec<FoundPath> {
|
) -> Vec<FoundPath> {
|
||||||
let picker = open_file_picker(&workspace, cx);
|
let picker = open_file_picker(&workspace, cx);
|
||||||
cx.simulate_input(input);
|
cx.simulate_input(input);
|
||||||
|
@ -1186,7 +1321,6 @@ async fn open_close_queried_buffer(
|
||||||
finder.delegate.history_items.clone()
|
finder.delegate.history_items.clone()
|
||||||
});
|
});
|
||||||
|
|
||||||
cx.dispatch_action(SelectNext);
|
|
||||||
cx.dispatch_action(Confirm);
|
cx.dispatch_action(Confirm);
|
||||||
|
|
||||||
cx.read(|cx| {
|
cx.read(|cx| {
|
||||||
|
@ -1198,8 +1332,6 @@ async fn open_close_queried_buffer(
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
cx.dispatch_action(workspace::CloseActiveItem { save_intent: None });
|
|
||||||
|
|
||||||
history_items
|
history_items
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1313,3 +1445,30 @@ fn collect_search_matches(picker: &Picker<FileFinderDelegate>) -> SearchEntries
|
||||||
.collect(),
|
.collect(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn assert_match_selection(
|
||||||
|
finder: &Picker<FileFinderDelegate>,
|
||||||
|
expected_selection_index: usize,
|
||||||
|
expected_file_name: &str,
|
||||||
|
) {
|
||||||
|
assert_eq!(finder.delegate.selected_index(), expected_selection_index);
|
||||||
|
assert_match_at_position(finder, expected_selection_index, expected_file_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn assert_match_at_position(
|
||||||
|
finder: &Picker<FileFinderDelegate>,
|
||||||
|
match_index: usize,
|
||||||
|
expected_file_name: &str,
|
||||||
|
) {
|
||||||
|
let match_item = finder
|
||||||
|
.delegate
|
||||||
|
.matches
|
||||||
|
.get(match_index)
|
||||||
|
.expect("Finder should have a match item with the given index");
|
||||||
|
let match_file_name = match match_item {
|
||||||
|
Match::History(found_path, _) => found_path.absolute.as_deref().unwrap().file_name(),
|
||||||
|
Match::Search(path_match) => path_match.0.path.file_name(),
|
||||||
|
};
|
||||||
|
let match_file_name = match_file_name.unwrap().to_string_lossy().to_string();
|
||||||
|
assert_eq!(match_file_name.as_str(), expected_file_name);
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue