Unbork project search focus

This commit is contained in:
Julia 2023-12-18 10:44:34 -05:00
parent 4eb609a954
commit b4042feccd
2 changed files with 117 additions and 103 deletions

View File

@ -13,9 +13,9 @@ use editor::{
use editor::{EditorElement, EditorStyle}; use editor::{EditorElement, EditorStyle};
use gpui::{ use gpui::{
actions, div, AnyElement, AnyView, AppContext, Context as _, Div, Element, EntityId, actions, div, AnyElement, AnyView, AppContext, Context as _, Div, Element, EntityId,
EventEmitter, FocusableView, FontStyle, FontWeight, InteractiveElement, IntoElement, EventEmitter, FocusHandle, FocusableView, FontStyle, FontWeight, InteractiveElement,
KeyContext, Model, ModelContext, ParentElement, PromptLevel, Render, SharedString, Styled, IntoElement, KeyContext, Model, ModelContext, ParentElement, PromptLevel, Render, SharedString,
Subscription, Task, TextStyle, View, ViewContext, VisualContext, WeakModel, WeakView, Styled, Subscription, Task, TextStyle, View, ViewContext, VisualContext, WeakModel, WeakView,
WhiteSpace, WindowContext, WhiteSpace, WindowContext,
}; };
use menu::Confirm; use menu::Confirm;
@ -91,6 +91,7 @@ enum InputPanel {
} }
pub struct ProjectSearchView { pub struct ProjectSearchView {
focus_handle: FocusHandle,
model: Model<ProjectSearch>, model: Model<ProjectSearch>,
query_editor: View<Editor>, query_editor: View<Editor>,
replacement_editor: View<Editor>, replacement_editor: View<Editor>,
@ -107,6 +108,7 @@ pub struct ProjectSearchView {
filters_enabled: bool, filters_enabled: bool,
replace_enabled: bool, replace_enabled: bool,
current_mode: SearchMode, current_mode: SearchMode,
_subscriptions: Vec<Subscription>,
} }
struct SemanticState { struct SemanticState {
@ -284,6 +286,7 @@ impl Render for ProjectSearchView {
div() div()
.flex_1() .flex_1()
.size_full() .size_full()
.track_focus(&self.focus_handle)
.child(self.results_editor.clone()) .child(self.results_editor.clone())
.into_any() .into_any()
} else { } else {
@ -359,10 +362,10 @@ impl Render for ProjectSearchView {
.child(Label::new(text).size(LabelSize::Small)) .child(Label::new(text).size(LabelSize::Small))
}); });
v_stack() v_stack()
.track_focus(&self.query_editor.focus_handle(cx))
.flex_1() .flex_1()
.size_full() .size_full()
.justify_center() .justify_center()
.track_focus(&self.focus_handle)
.child( .child(
h_stack() h_stack()
.size_full() .size_full()
@ -377,12 +380,8 @@ impl Render for ProjectSearchView {
} }
impl FocusableView for ProjectSearchView { impl FocusableView for ProjectSearchView {
fn focus_handle(&self, cx: &AppContext) -> gpui::FocusHandle { fn focus_handle(&self, _: &AppContext) -> gpui::FocusHandle {
if self.has_matches() { self.focus_handle.clone()
self.results_editor.focus_handle(cx)
} else {
self.query_editor.focus_handle(cx)
}
} }
} }
@ -783,6 +782,7 @@ impl ProjectSearchView {
let excerpts; let excerpts;
let mut replacement_text = None; let mut replacement_text = None;
let mut query_text = String::new(); let mut query_text = String::new();
let mut subscriptions = Vec::new();
// Read in settings if available // Read in settings if available
let (mut options, current_mode, filters_enabled) = if let Some(settings) = settings { let (mut options, current_mode, filters_enabled) = if let Some(settings) = settings {
@ -805,8 +805,7 @@ impl ProjectSearchView {
options = SearchOptions::from_query(active_query); options = SearchOptions::from_query(active_query);
} }
} }
cx.observe(&model, |this, _, cx| this.model_changed(cx)) subscriptions.push(cx.observe(&model, |this, _, cx| this.model_changed(cx)));
.detach();
let query_editor = cx.build_view(|cx| { let query_editor = cx.build_view(|cx| {
let mut editor = Editor::single_line(cx); let mut editor = Editor::single_line(cx);
@ -815,10 +814,11 @@ impl ProjectSearchView {
editor editor
}); });
// Subscribe to query_editor in order to reraise editor events for workspace item activation purposes // Subscribe to query_editor in order to reraise editor events for workspace item activation purposes
cx.subscribe(&query_editor, |_, _, event: &EditorEvent, cx| { subscriptions.push(
cx.emit(ViewEvent::EditorEvent(event.clone())) cx.subscribe(&query_editor, |_, _, event: &EditorEvent, cx| {
}) cx.emit(ViewEvent::EditorEvent(event.clone()))
.detach(); }),
);
let replacement_editor = cx.build_view(|cx| { let replacement_editor = cx.build_view(|cx| {
let mut editor = Editor::single_line(cx); let mut editor = Editor::single_line(cx);
editor.set_placeholder_text("Replace in project..", cx); editor.set_placeholder_text("Replace in project..", cx);
@ -832,17 +832,17 @@ impl ProjectSearchView {
editor.set_searchable(false); editor.set_searchable(false);
editor editor
}); });
cx.observe(&results_editor, |_, _, cx| cx.emit(ViewEvent::UpdateTab)) subscriptions.push(cx.observe(&results_editor, |_, _, cx| cx.emit(ViewEvent::UpdateTab)));
.detach();
cx.subscribe(&results_editor, |this, _, event: &EditorEvent, cx| { subscriptions.push(
if matches!(event, editor::EditorEvent::SelectionsChanged { .. }) { cx.subscribe(&results_editor, |this, _, event: &EditorEvent, cx| {
this.update_match_index(cx); if matches!(event, editor::EditorEvent::SelectionsChanged { .. }) {
} this.update_match_index(cx);
// Reraise editor events for workspace item activation purposes }
cx.emit(ViewEvent::EditorEvent(event.clone())); // Reraise editor events for workspace item activation purposes
}) cx.emit(ViewEvent::EditorEvent(event.clone()));
.detach(); }),
);
let included_files_editor = cx.build_view(|cx| { let included_files_editor = cx.build_view(|cx| {
let mut editor = Editor::single_line(cx); let mut editor = Editor::single_line(cx);
@ -851,10 +851,11 @@ impl ProjectSearchView {
editor editor
}); });
// Subscribe to include_files_editor in order to reraise editor events for workspace item activation purposes // Subscribe to include_files_editor in order to reraise editor events for workspace item activation purposes
cx.subscribe(&included_files_editor, |_, _, event: &EditorEvent, cx| { subscriptions.push(
cx.emit(ViewEvent::EditorEvent(event.clone())) cx.subscribe(&included_files_editor, |_, _, event: &EditorEvent, cx| {
}) cx.emit(ViewEvent::EditorEvent(event.clone()))
.detach(); }),
);
let excluded_files_editor = cx.build_view(|cx| { let excluded_files_editor = cx.build_view(|cx| {
let mut editor = Editor::single_line(cx); let mut editor = Editor::single_line(cx);
@ -863,13 +864,26 @@ impl ProjectSearchView {
editor editor
}); });
// Subscribe to excluded_files_editor in order to reraise editor events for workspace item activation purposes // Subscribe to excluded_files_editor in order to reraise editor events for workspace item activation purposes
cx.subscribe(&excluded_files_editor, |_, _, event: &EditorEvent, cx| { subscriptions.push(
cx.emit(ViewEvent::EditorEvent(event.clone())) cx.subscribe(&excluded_files_editor, |_, _, event: &EditorEvent, cx| {
}) cx.emit(ViewEvent::EditorEvent(event.clone()))
.detach(); }),
);
let focus_handle = cx.focus_handle();
subscriptions.push(cx.on_focus_in(&focus_handle, |this, cx| {
if this.focus_handle.is_focused(cx) {
if this.has_matches() {
this.results_editor.focus_handle(cx).focus(cx);
} else {
this.query_editor.focus_handle(cx).focus(cx);
}
}
}));
// Check if Worktrees have all been previously indexed // Check if Worktrees have all been previously indexed
let mut this = ProjectSearchView { let mut this = ProjectSearchView {
focus_handle,
replacement_editor, replacement_editor,
search_id: model.read(cx).search_id, search_id: model.read(cx).search_id,
model, model,
@ -886,6 +900,7 @@ impl ProjectSearchView {
filters_enabled, filters_enabled,
current_mode, current_mode,
replace_enabled: false, replace_enabled: false,
_subscriptions: subscriptions,
}; };
this.model_changed(cx); this.model_changed(cx);
this this
@ -1186,6 +1201,7 @@ impl ProjectSearchBar {
subscription: Default::default(), subscription: Default::default(),
} }
} }
fn cycle_mode(&self, _: &CycleMode, cx: &mut ViewContext<Self>) { fn cycle_mode(&self, _: &CycleMode, cx: &mut ViewContext<Self>) {
if let Some(view) = self.active_project_search.as_ref() { if let Some(view) = self.active_project_search.as_ref() {
view.update(cx, |this, cx| { view.update(cx, |this, cx| {
@ -1197,6 +1213,7 @@ impl ProjectSearchBar {
}); });
} }
} }
fn confirm(&mut self, _: &Confirm, cx: &mut ViewContext<Self>) { fn confirm(&mut self, _: &Confirm, cx: &mut ViewContext<Self>) {
if let Some(search_view) = self.active_project_search.as_ref() { if let Some(search_view) = self.active_project_search.as_ref() {
search_view.update(cx, |search_view, cx| { search_view.update(cx, |search_view, cx| {
@ -1307,6 +1324,7 @@ impl ProjectSearchBar {
false false
} }
} }
fn toggle_replace(&mut self, _: &ToggleReplace, cx: &mut ViewContext<Self>) { fn toggle_replace(&mut self, _: &ToggleReplace, cx: &mut ViewContext<Self>) {
if let Some(search) = &self.active_project_search { if let Some(search) = &self.active_project_search {
search.update(cx, |this, cx| { search.update(cx, |this, cx| {
@ -1400,6 +1418,7 @@ impl ProjectSearchBar {
}); });
} }
} }
fn new_placeholder_text(&self, cx: &mut ViewContext<Self>) -> Option<String> { fn new_placeholder_text(&self, cx: &mut ViewContext<Self>) -> Option<String> {
let previous_query_keystrokes = cx let previous_query_keystrokes = cx
.bindings_for_action(&PreviousHistoryQuery {}) .bindings_for_action(&PreviousHistoryQuery {})
@ -2305,6 +2324,7 @@ pub mod tests {
let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await; let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
let window = cx.add_window(|cx| Workspace::test_new(project, cx)); let window = cx.add_window(|cx| Workspace::test_new(project, cx));
let workspace = window.clone(); let workspace = window.clone();
let search_bar = window.build_view(cx, |_| ProjectSearchBar::new());
let active_item = cx.read(|cx| { let active_item = cx.read(|cx| {
workspace workspace
@ -2320,8 +2340,14 @@ pub mod tests {
"Expected no search panel to be active" "Expected no search panel to be active"
); );
workspace window
.update(cx, |workspace, cx| { .update(cx, move |workspace, cx| {
assert_eq!(workspace.panes().len(), 1);
workspace.panes()[0].update(cx, move |pane, cx| {
pane.toolbar()
.update(cx, |toolbar, cx| toolbar.add_item(search_bar, cx))
});
ProjectSearchView::deploy(workspace, &workspace::NewSearch, cx) ProjectSearchView::deploy(workspace, &workspace::NewSearch, cx)
}) })
.unwrap(); .unwrap();
@ -2600,6 +2626,7 @@ pub mod tests {
}); });
let window = cx.add_window(|cx| Workspace::test_new(project, cx)); let window = cx.add_window(|cx| Workspace::test_new(project, cx));
let workspace = window.root(cx).unwrap(); let workspace = window.root(cx).unwrap();
let search_bar = window.build_view(cx, |_| ProjectSearchBar::new());
let active_item = cx.read(|cx| { let active_item = cx.read(|cx| {
workspace workspace
@ -2614,6 +2641,16 @@ pub mod tests {
"Expected no search panel to be active" "Expected no search panel to be active"
); );
window
.update(cx, move |workspace, cx| {
assert_eq!(workspace.panes().len(), 1);
workspace.panes()[0].update(cx, move |pane, cx| {
pane.toolbar()
.update(cx, |toolbar, cx| toolbar.add_item(search_bar, cx))
});
})
.unwrap();
let one_file_entry = cx.update(|cx| { let one_file_entry = cx.update(|cx| {
workspace workspace
.read(cx) .read(cx)
@ -2734,9 +2771,20 @@ pub mod tests {
let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await; let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await;
let window = cx.add_window(|cx| Workspace::test_new(project, cx)); let window = cx.add_window(|cx| Workspace::test_new(project, cx));
let workspace = window.root(cx).unwrap(); let workspace = window.root(cx).unwrap();
let search_bar = window.build_view(cx, |_| ProjectSearchBar::new());
window window
.update(cx, |workspace, cx| { .update(cx, {
ProjectSearchView::deploy(workspace, &workspace::NewSearch, cx) let search_bar = search_bar.clone();
move |workspace, cx| {
assert_eq!(workspace.panes().len(), 1);
workspace.panes()[0].update(cx, move |pane, cx| {
pane.toolbar()
.update(cx, |toolbar, cx| toolbar.add_item(search_bar, cx))
});
ProjectSearchView::deploy(workspace, &workspace::NewSearch, cx)
}
}) })
.unwrap(); .unwrap();
@ -2750,13 +2798,6 @@ pub mod tests {
.expect("Search view expected to appear after new search event trigger") .expect("Search view expected to appear after new search event trigger")
}); });
let search_bar = window.build_view(cx, |cx| {
let mut search_bar = ProjectSearchBar::new();
search_bar.set_active_pane_item(Some(&search_view), cx);
// search_bar.show(cx);
search_bar
});
// Add 3 search items into the history + another unsubmitted one. // Add 3 search items into the history + another unsubmitted one.
window window
.update(cx, |_, cx| { .update(cx, |_, cx| {

View File

@ -3556,8 +3556,6 @@ impl Render for Workspace {
) )
}; };
let theme = cx.theme().clone();
let colors = theme.colors();
cx.set_rem_size(ui_font_size); cx.set_rem_size(ui_font_size);
self.actions(div(), cx) self.actions(div(), cx)
@ -3570,10 +3568,10 @@ impl Render for Workspace {
.gap_0() .gap_0()
.justify_start() .justify_start()
.items_start() .items_start()
.text_color(colors.text) .text_color(cx.theme().colors().text)
.bg(colors.background) .bg(cx.theme().colors().background)
.border() .border()
.border_color(colors.border) .border_color(cx.theme().colors().border)
.children(self.titlebar_item.clone()) .children(self.titlebar_item.clone())
.child( .child(
div() div()
@ -3586,7 +3584,7 @@ impl Render for Workspace {
.overflow_hidden() .overflow_hidden()
.border_t() .border_t()
.border_b() .border_b()
.border_color(colors.border) .border_color(cx.theme().colors().border)
.child( .child(
canvas(cx.listener(|workspace, bounds, _| { canvas(cx.listener(|workspace, bounds, _| {
workspace.bounds = *bounds; workspace.bounds = *bounds;
@ -3625,15 +3623,13 @@ impl Render for Workspace {
.flex_row() .flex_row()
.h_full() .h_full()
// Left Dock // Left Dock
.children(self.zoomed_position.ne(&Some(DockPosition::Left)).then( .child(
|| { div()
div() .flex()
.flex() .flex_none()
.flex_none() .overflow_hidden()
.overflow_hidden() .child(self.left_dock.clone()),
.child(self.left_dock.clone()) )
},
))
// Panes // Panes
.child( .child(
div() div()
@ -3641,52 +3637,29 @@ impl Render for Workspace {
.flex_col() .flex_col()
.flex_1() .flex_1()
.overflow_hidden() .overflow_hidden()
.child(self.center.render( .child({
&self.project, self.center.render(
&self.follower_states, &self.project,
self.active_call(), &self.follower_states,
&self.active_pane, self.active_call(),
self.zoomed.as_ref(), &self.active_pane,
&self.app_state, self.zoomed.as_ref(),
cx, &self.app_state,
)) cx,
.children( )
self.zoomed_position })
.ne(&Some(DockPosition::Bottom)) .child(self.bottom_dock.clone()),
.then(|| self.bottom_dock.clone()),
),
) )
// Right Dock // Right Dock
.children(self.zoomed_position.ne(&Some(DockPosition::Right)).then( .child(
|| { div()
div() .flex()
.flex() .flex_none()
.flex_none() .overflow_hidden()
.overflow_hidden() .child(self.right_dock.clone()),
.child(self.right_dock.clone()) ),
},
)),
) )
.children(self.render_notifications(cx)) .children(self.render_notifications(cx)),
.children(self.zoomed.as_ref().and_then(|view| {
let zoomed_view = view.upgrade()?;
let div = div()
.z_index(1)
.absolute()
.overflow_hidden()
.border_color(colors.border)
.bg(colors.background)
.child(zoomed_view)
.inset_0()
.shadow_lg();
Some(match self.zoomed_position {
Some(DockPosition::Left) => div.right_2().border_r(),
Some(DockPosition::Right) => div.left_2().border_l(),
Some(DockPosition::Bottom) => div.top_2().border_t(),
None => div.top_2().bottom_2().left_2().right_2().border(),
})
})),
) )
.child(self.status_bar.clone()) .child(self.status_bar.clone())
} }