Fix project search unsaved edits (#30864)
Closes #30820 Release Notes: - Fixed an issue where entering a new search in the project search would drop unsaved edits in the project search buffer --------- Co-authored-by: Mark Janssen <20283+praseodym@users.noreply.github.com>
This commit is contained in:
parent
4d827924f0
commit
f56960ab5b
3 changed files with 151 additions and 54 deletions
|
@ -37,7 +37,7 @@ use ui::{
|
||||||
Icon, IconButton, IconButtonShape, IconName, KeyBinding, Label, LabelCommon, LabelSize,
|
Icon, IconButton, IconButtonShape, IconName, KeyBinding, Label, LabelCommon, LabelSize,
|
||||||
Toggleable, Tooltip, h_flex, prelude::*, utils::SearchInputWidth, v_flex,
|
Toggleable, Tooltip, h_flex, prelude::*, utils::SearchInputWidth, v_flex,
|
||||||
};
|
};
|
||||||
use util::paths::PathMatcher;
|
use util::{ResultExt as _, paths::PathMatcher};
|
||||||
use workspace::{
|
use workspace::{
|
||||||
DeploySearch, ItemNavHistory, NewSearch, ToolbarItemEvent, ToolbarItemLocation,
|
DeploySearch, ItemNavHistory, NewSearch, ToolbarItemEvent, ToolbarItemLocation,
|
||||||
ToolbarItemView, Workspace, WorkspaceId,
|
ToolbarItemView, Workspace, WorkspaceId,
|
||||||
|
@ -72,15 +72,18 @@ pub fn init(cx: &mut App) {
|
||||||
);
|
);
|
||||||
register_workspace_action(
|
register_workspace_action(
|
||||||
workspace,
|
workspace,
|
||||||
move |search_bar, _: &ToggleCaseSensitive, _, cx| {
|
move |search_bar, _: &ToggleCaseSensitive, window, cx| {
|
||||||
search_bar.toggle_search_option(SearchOptions::CASE_SENSITIVE, cx);
|
search_bar.toggle_search_option(SearchOptions::CASE_SENSITIVE, window, cx);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
register_workspace_action(workspace, move |search_bar, _: &ToggleWholeWord, _, cx| {
|
register_workspace_action(
|
||||||
search_bar.toggle_search_option(SearchOptions::WHOLE_WORD, cx);
|
workspace,
|
||||||
});
|
move |search_bar, _: &ToggleWholeWord, window, cx| {
|
||||||
register_workspace_action(workspace, move |search_bar, _: &ToggleRegex, _, cx| {
|
search_bar.toggle_search_option(SearchOptions::WHOLE_WORD, window, cx);
|
||||||
search_bar.toggle_search_option(SearchOptions::REGEX, cx);
|
},
|
||||||
|
);
|
||||||
|
register_workspace_action(workspace, move |search_bar, _: &ToggleRegex, window, cx| {
|
||||||
|
search_bar.toggle_search_option(SearchOptions::REGEX, window, cx);
|
||||||
});
|
});
|
||||||
register_workspace_action(
|
register_workspace_action(
|
||||||
workspace,
|
workspace,
|
||||||
|
@ -1032,6 +1035,61 @@ impl ProjectSearchView {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn prompt_to_save_if_dirty_then_search(
|
||||||
|
&mut self,
|
||||||
|
window: &mut Window,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) -> Task<anyhow::Result<()>> {
|
||||||
|
use workspace::AutosaveSetting;
|
||||||
|
|
||||||
|
let project = self.entity.read(cx).project.clone();
|
||||||
|
|
||||||
|
let can_autosave = self.results_editor.can_autosave(cx);
|
||||||
|
let autosave_setting = self.results_editor.workspace_settings(cx).autosave;
|
||||||
|
|
||||||
|
let will_autosave = can_autosave
|
||||||
|
&& matches!(
|
||||||
|
autosave_setting,
|
||||||
|
AutosaveSetting::OnFocusChange | AutosaveSetting::OnWindowChange
|
||||||
|
);
|
||||||
|
|
||||||
|
let is_dirty = self.is_dirty(cx);
|
||||||
|
|
||||||
|
let should_confirm_save = !will_autosave && is_dirty;
|
||||||
|
|
||||||
|
cx.spawn_in(window, async move |this, cx| {
|
||||||
|
let should_search = if should_confirm_save {
|
||||||
|
let options = &["Save", "Don't Save", "Cancel"];
|
||||||
|
let result_channel = this.update_in(cx, |_, window, cx| {
|
||||||
|
window.prompt(
|
||||||
|
gpui::PromptLevel::Warning,
|
||||||
|
"Project search buffer contains unsaved edits. Do you want to save it?",
|
||||||
|
None,
|
||||||
|
options,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
let result = result_channel.await?;
|
||||||
|
let should_save = result == 0;
|
||||||
|
if should_save {
|
||||||
|
this.update_in(cx, |this, window, cx| this.save(true, project, window, cx))?
|
||||||
|
.await
|
||||||
|
.log_err();
|
||||||
|
}
|
||||||
|
let should_search = result != 2;
|
||||||
|
should_search
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
};
|
||||||
|
if should_search {
|
||||||
|
this.update(cx, |this, cx| {
|
||||||
|
this.search(cx);
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
anyhow::Ok(())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fn search(&mut self, cx: &mut Context<Self>) {
|
fn search(&mut self, cx: &mut Context<Self>) {
|
||||||
if let Some(query) = self.build_search_query(cx) {
|
if let Some(query) = self.build_search_query(cx) {
|
||||||
self.entity.update(cx, |model, cx| model.search(query, cx));
|
self.entity.update(cx, |model, cx| model.search(query, cx));
|
||||||
|
@ -1503,7 +1561,9 @@ impl ProjectSearchBar {
|
||||||
.is_focused(window)
|
.is_focused(window)
|
||||||
{
|
{
|
||||||
cx.stop_propagation();
|
cx.stop_propagation();
|
||||||
search_view.search(cx);
|
search_view
|
||||||
|
.prompt_to_save_if_dirty_then_search(window, cx)
|
||||||
|
.detach_and_log_err(cx);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1570,19 +1630,39 @@ impl ProjectSearchBar {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn toggle_search_option(&mut self, option: SearchOptions, cx: &mut Context<Self>) -> bool {
|
fn toggle_search_option(
|
||||||
if let Some(search_view) = self.active_project_search.as_ref() {
|
&mut self,
|
||||||
search_view.update(cx, |search_view, cx| {
|
option: SearchOptions,
|
||||||
search_view.toggle_search_option(option, cx);
|
window: &mut Window,
|
||||||
if search_view.entity.read(cx).active_query.is_some() {
|
cx: &mut Context<Self>,
|
||||||
search_view.search(cx);
|
) -> bool {
|
||||||
}
|
if self.active_project_search.is_none() {
|
||||||
});
|
return false;
|
||||||
cx.notify();
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cx.spawn_in(window, async move |this, cx| {
|
||||||
|
let task = this.update_in(cx, |this, window, cx| {
|
||||||
|
let search_view = this.active_project_search.as_ref()?;
|
||||||
|
search_view.update(cx, |search_view, cx| {
|
||||||
|
search_view.toggle_search_option(option, cx);
|
||||||
|
search_view
|
||||||
|
.entity
|
||||||
|
.read(cx)
|
||||||
|
.active_query
|
||||||
|
.is_some()
|
||||||
|
.then(|| search_view.prompt_to_save_if_dirty_then_search(window, cx))
|
||||||
|
})
|
||||||
|
})?;
|
||||||
|
if let Some(task) = task {
|
||||||
|
task.await?;
|
||||||
|
}
|
||||||
|
this.update(cx, |_, cx| {
|
||||||
|
cx.notify();
|
||||||
|
})?;
|
||||||
|
anyhow::Ok(())
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
fn toggle_replace(&mut self, _: &ToggleReplace, window: &mut Window, cx: &mut Context<Self>) {
|
fn toggle_replace(&mut self, _: &ToggleReplace, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
|
@ -1621,19 +1701,33 @@ impl ProjectSearchBar {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn toggle_opened_only(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
|
fn toggle_opened_only(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
|
||||||
if let Some(search_view) = self.active_project_search.as_ref() {
|
if self.active_project_search.is_none() {
|
||||||
search_view.update(cx, |search_view, cx| {
|
return false;
|
||||||
search_view.toggle_opened_only(window, cx);
|
|
||||||
if search_view.entity.read(cx).active_query.is_some() {
|
|
||||||
search_view.search(cx);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
cx.notify();
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cx.spawn_in(window, async move |this, cx| {
|
||||||
|
let task = this.update_in(cx, |this, window, cx| {
|
||||||
|
let search_view = this.active_project_search.as_ref()?;
|
||||||
|
search_view.update(cx, |search_view, cx| {
|
||||||
|
search_view.toggle_opened_only(window, cx);
|
||||||
|
search_view
|
||||||
|
.entity
|
||||||
|
.read(cx)
|
||||||
|
.active_query
|
||||||
|
.is_some()
|
||||||
|
.then(|| search_view.prompt_to_save_if_dirty_then_search(window, cx))
|
||||||
|
})
|
||||||
|
})?;
|
||||||
|
if let Some(task) = task {
|
||||||
|
task.await?;
|
||||||
|
}
|
||||||
|
this.update(cx, |_, cx| {
|
||||||
|
cx.notify();
|
||||||
|
})?;
|
||||||
|
anyhow::Ok(())
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_opened_only_enabled(&self, cx: &App) -> bool {
|
fn is_opened_only_enabled(&self, cx: &App) -> bool {
|
||||||
|
@ -1860,22 +1954,22 @@ impl Render for ProjectSearchBar {
|
||||||
.child(SearchOptions::CASE_SENSITIVE.as_button(
|
.child(SearchOptions::CASE_SENSITIVE.as_button(
|
||||||
self.is_option_enabled(SearchOptions::CASE_SENSITIVE, cx),
|
self.is_option_enabled(SearchOptions::CASE_SENSITIVE, cx),
|
||||||
focus_handle.clone(),
|
focus_handle.clone(),
|
||||||
cx.listener(|this, _, _, cx| {
|
cx.listener(|this, _, window, cx| {
|
||||||
this.toggle_search_option(SearchOptions::CASE_SENSITIVE, cx);
|
this.toggle_search_option(SearchOptions::CASE_SENSITIVE, window, cx);
|
||||||
}),
|
}),
|
||||||
))
|
))
|
||||||
.child(SearchOptions::WHOLE_WORD.as_button(
|
.child(SearchOptions::WHOLE_WORD.as_button(
|
||||||
self.is_option_enabled(SearchOptions::WHOLE_WORD, cx),
|
self.is_option_enabled(SearchOptions::WHOLE_WORD, cx),
|
||||||
focus_handle.clone(),
|
focus_handle.clone(),
|
||||||
cx.listener(|this, _, _, cx| {
|
cx.listener(|this, _, window, cx| {
|
||||||
this.toggle_search_option(SearchOptions::WHOLE_WORD, cx);
|
this.toggle_search_option(SearchOptions::WHOLE_WORD, window, cx);
|
||||||
}),
|
}),
|
||||||
))
|
))
|
||||||
.child(SearchOptions::REGEX.as_button(
|
.child(SearchOptions::REGEX.as_button(
|
||||||
self.is_option_enabled(SearchOptions::REGEX, cx),
|
self.is_option_enabled(SearchOptions::REGEX, cx),
|
||||||
focus_handle.clone(),
|
focus_handle.clone(),
|
||||||
cx.listener(|this, _, _, cx| {
|
cx.listener(|this, _, window, cx| {
|
||||||
this.toggle_search_option(SearchOptions::REGEX, cx);
|
this.toggle_search_option(SearchOptions::REGEX, window, cx);
|
||||||
}),
|
}),
|
||||||
)),
|
)),
|
||||||
);
|
);
|
||||||
|
@ -2147,8 +2241,12 @@ impl Render for ProjectSearchBar {
|
||||||
.search_options
|
.search_options
|
||||||
.contains(SearchOptions::INCLUDE_IGNORED),
|
.contains(SearchOptions::INCLUDE_IGNORED),
|
||||||
focus_handle.clone(),
|
focus_handle.clone(),
|
||||||
cx.listener(|this, _, _, cx| {
|
cx.listener(|this, _, window, cx| {
|
||||||
this.toggle_search_option(SearchOptions::INCLUDE_IGNORED, cx);
|
this.toggle_search_option(
|
||||||
|
SearchOptions::INCLUDE_IGNORED,
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
);
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -2188,11 +2286,11 @@ impl Render for ProjectSearchBar {
|
||||||
.on_action(cx.listener(|this, action, window, cx| {
|
.on_action(cx.listener(|this, action, window, cx| {
|
||||||
this.toggle_replace(action, window, cx);
|
this.toggle_replace(action, window, cx);
|
||||||
}))
|
}))
|
||||||
.on_action(cx.listener(|this, _: &ToggleWholeWord, _, cx| {
|
.on_action(cx.listener(|this, _: &ToggleWholeWord, window, cx| {
|
||||||
this.toggle_search_option(SearchOptions::WHOLE_WORD, cx);
|
this.toggle_search_option(SearchOptions::WHOLE_WORD, window, cx);
|
||||||
}))
|
}))
|
||||||
.on_action(cx.listener(|this, _: &ToggleCaseSensitive, _, cx| {
|
.on_action(cx.listener(|this, _: &ToggleCaseSensitive, window, cx| {
|
||||||
this.toggle_search_option(SearchOptions::CASE_SENSITIVE, cx);
|
this.toggle_search_option(SearchOptions::CASE_SENSITIVE, window, cx);
|
||||||
}))
|
}))
|
||||||
.on_action(cx.listener(|this, action, window, cx| {
|
.on_action(cx.listener(|this, action, window, cx| {
|
||||||
if let Some(search) = this.active_project_search.as_ref() {
|
if let Some(search) = this.active_project_search.as_ref() {
|
||||||
|
@ -2209,8 +2307,8 @@ impl Render for ProjectSearchBar {
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
.when(search.filters_enabled, |this| {
|
.when(search.filters_enabled, |this| {
|
||||||
this.on_action(cx.listener(|this, _: &ToggleIncludeIgnored, _, cx| {
|
this.on_action(cx.listener(|this, _: &ToggleIncludeIgnored, window, cx| {
|
||||||
this.toggle_search_option(SearchOptions::INCLUDE_IGNORED, cx);
|
this.toggle_search_option(SearchOptions::INCLUDE_IGNORED, window, cx);
|
||||||
}))
|
}))
|
||||||
})
|
})
|
||||||
.on_action(cx.listener(Self::select_next_match))
|
.on_action(cx.listener(Self::select_next_match))
|
||||||
|
|
|
@ -564,6 +564,10 @@ pub trait ItemHandle: 'static + Send {
|
||||||
fn preserve_preview(&self, cx: &App) -> bool;
|
fn preserve_preview(&self, cx: &App) -> bool;
|
||||||
fn include_in_nav_history(&self) -> bool;
|
fn include_in_nav_history(&self) -> bool;
|
||||||
fn relay_action(&self, action: Box<dyn Action>, window: &mut Window, cx: &mut App);
|
fn relay_action(&self, action: Box<dyn Action>, window: &mut Window, cx: &mut App);
|
||||||
|
fn can_autosave(&self, cx: &App) -> bool {
|
||||||
|
let is_deleted = self.project_entry_ids(cx).is_empty();
|
||||||
|
self.is_dirty(cx) && !self.has_conflict(cx) && self.can_save(cx) && !is_deleted
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait WeakItemHandle: Send + Sync {
|
pub trait WeakItemHandle: Send + Sync {
|
||||||
|
|
|
@ -1857,7 +1857,7 @@ impl Pane {
|
||||||
matches!(
|
matches!(
|
||||||
item.workspace_settings(cx).autosave,
|
item.workspace_settings(cx).autosave,
|
||||||
AutosaveSetting::OnFocusChange | AutosaveSetting::OnWindowChange
|
AutosaveSetting::OnFocusChange | AutosaveSetting::OnWindowChange
|
||||||
) && Self::can_autosave_item(item, cx)
|
) && item.can_autosave(cx)
|
||||||
})?;
|
})?;
|
||||||
if !will_autosave {
|
if !will_autosave {
|
||||||
let item_id = item.item_id();
|
let item_id = item.item_id();
|
||||||
|
@ -1945,11 +1945,6 @@ impl Pane {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn can_autosave_item(item: &dyn ItemHandle, cx: &App) -> bool {
|
|
||||||
let is_deleted = item.project_entry_ids(cx).is_empty();
|
|
||||||
item.is_dirty(cx) && !item.has_conflict(cx) && item.can_save(cx) && !is_deleted
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn autosave_item(
|
pub fn autosave_item(
|
||||||
item: &dyn ItemHandle,
|
item: &dyn ItemHandle,
|
||||||
project: Entity<Project>,
|
project: Entity<Project>,
|
||||||
|
@ -1960,7 +1955,7 @@ impl Pane {
|
||||||
item.workspace_settings(cx).autosave,
|
item.workspace_settings(cx).autosave,
|
||||||
AutosaveSetting::AfterDelay { .. }
|
AutosaveSetting::AfterDelay { .. }
|
||||||
);
|
);
|
||||||
if Self::can_autosave_item(item, cx) {
|
if item.can_autosave(cx) {
|
||||||
item.save(format, project, window, cx)
|
item.save(format, project, window, cx)
|
||||||
} else {
|
} else {
|
||||||
Task::ready(Ok(()))
|
Task::ready(Ok(()))
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue