logiguard fork v3: full patch set on verified 8c74db0 tree
Includes prior-session patches (carry forward so the app compiles): - crates/gpui/build.rs: cross-compile manifest fix - crates/gpui/src/platform.rs: PlatformWindow::activate_with_token trait method - crates/gpui/src/window.rs: Window::activate_with_token public API - crates/gpui_linux/src/linux/wayland/window.rs: WaylandWindow::activate_with_token + activate() keyboard-serial fix Plus the focus-serial fix: - serial.rs: SerialKind::KeyboardEnter - client.rs: store wl_keyboard.enter serial; latest_serial_of() Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
61
crates/search/Cargo.toml
Normal file
61
crates/search/Cargo.toml
Normal file
@@ -0,0 +1,61 @@
|
||||
[package]
|
||||
name = "search"
|
||||
version = "0.1.0"
|
||||
edition.workspace = true
|
||||
publish.workspace = true
|
||||
license = "GPL-3.0-or-later"
|
||||
|
||||
[features]
|
||||
test-support = [
|
||||
|
||||
"editor/test-support",
|
||||
"gpui/test-support",
|
||||
"workspace/test-support",
|
||||
]
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[lib]
|
||||
path = "src/search.rs"
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
any_vec.workspace = true
|
||||
bitflags.workspace = true
|
||||
collections.workspace = true
|
||||
editor.workspace = true
|
||||
fs.workspace = true
|
||||
futures-lite.workspace = true
|
||||
futures.workspace = true
|
||||
gpui.workspace = true
|
||||
language.workspace = true
|
||||
menu.workspace = true
|
||||
multi_buffer.workspace = true
|
||||
project.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
settings.workspace = true
|
||||
theme.workspace = true
|
||||
theme_settings.workspace = true
|
||||
ui.workspace = true
|
||||
util.workspace = true
|
||||
util_macros.workspace = true
|
||||
workspace.workspace = true
|
||||
zed_actions.workspace = true
|
||||
itertools.workspace = true
|
||||
ztracing.workspace = true
|
||||
tracing.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
editor = { workspace = true, features = ["test-support"] }
|
||||
gpui = { workspace = true, features = ["test-support"] }
|
||||
language = { workspace = true, features = ["test-support"] }
|
||||
lsp.workspace = true
|
||||
pretty_assertions.workspace = true
|
||||
unindent.workspace = true
|
||||
workspace = { workspace = true, features = ["test-support"] }
|
||||
|
||||
[package.metadata.cargo-machete]
|
||||
ignored = ["tracing"]
|
||||
1
crates/search/LICENSE-GPL
Symbolic link
1
crates/search/LICENSE-GPL
Symbolic link
@@ -0,0 +1 @@
|
||||
../../LICENSE-GPL
|
||||
3912
crates/search/src/buffer_search.rs
Normal file
3912
crates/search/src/buffer_search.rs
Normal file
File diff suppressed because it is too large
Load Diff
233
crates/search/src/buffer_search/registrar.rs
Normal file
233
crates/search/src/buffer_search/registrar.rs
Normal file
@@ -0,0 +1,233 @@
|
||||
use gpui::{Action, App, Context, Div, Entity, InteractiveElement, Window, div};
|
||||
use workspace::{Pane, Workspace};
|
||||
|
||||
use crate::BufferSearchBar;
|
||||
|
||||
/// Registrar inverts the dependency between search and its downstream user, allowing said downstream user to register search action without knowing exactly what those actions are.
|
||||
pub trait SearchActionsRegistrar {
|
||||
fn register_handler<A: Action>(&mut self, callback: impl ActionExecutor<A>);
|
||||
}
|
||||
|
||||
type SearchBarActionCallback<A> =
|
||||
fn(&mut BufferSearchBar, &A, &mut Window, &mut Context<BufferSearchBar>);
|
||||
|
||||
type GetSearchBar<T> =
|
||||
for<'a, 'b> fn(&'a T, &'a mut Window, &mut Context<'b, T>) -> Option<Entity<BufferSearchBar>>;
|
||||
|
||||
/// Registers search actions on a div that can be taken out.
|
||||
pub struct DivRegistrar<'a, 'b, T: 'static> {
|
||||
div: Option<Div>,
|
||||
cx: &'a mut Context<'b, T>,
|
||||
search_getter: GetSearchBar<T>,
|
||||
}
|
||||
|
||||
impl<'a, 'b, T: 'static> DivRegistrar<'a, 'b, T> {
|
||||
pub fn new(search_getter: GetSearchBar<T>, cx: &'a mut Context<'b, T>) -> Self {
|
||||
Self {
|
||||
div: Some(div()),
|
||||
cx,
|
||||
search_getter,
|
||||
}
|
||||
}
|
||||
pub fn into_div(self) -> Div {
|
||||
// This option is always Some; it's an option in the first place because we want to call methods
|
||||
// on div that require ownership.
|
||||
self.div.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: 'static> SearchActionsRegistrar for DivRegistrar<'_, '_, T> {
|
||||
fn register_handler<A: Action>(&mut self, callback: impl ActionExecutor<A>) {
|
||||
let getter = self.search_getter;
|
||||
self.div = self.div.take().map(|div| {
|
||||
div.on_action(self.cx.listener(move |this, action, window, cx| {
|
||||
let should_notify = (getter)(this, window, cx)
|
||||
.map(|search_bar| {
|
||||
search_bar.update(cx, |search_bar, cx| {
|
||||
callback.execute(search_bar, action, window, cx)
|
||||
})
|
||||
})
|
||||
.unwrap_or(false);
|
||||
if should_notify {
|
||||
cx.notify();
|
||||
} else {
|
||||
cx.propagate();
|
||||
}
|
||||
}))
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PaneDivRegistrar {
|
||||
div: Option<Div>,
|
||||
pane: Entity<Pane>,
|
||||
}
|
||||
|
||||
impl PaneDivRegistrar {
|
||||
pub fn new(div: Div, pane: Entity<Pane>) -> Self {
|
||||
Self {
|
||||
div: Some(div),
|
||||
pane,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_div(self) -> Div {
|
||||
self.div.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl SearchActionsRegistrar for PaneDivRegistrar {
|
||||
fn register_handler<A: Action>(&mut self, callback: impl ActionExecutor<A>) {
|
||||
let pane = self.pane.clone();
|
||||
self.div = self.div.take().map(|div| {
|
||||
div.on_action(move |action: &A, window: &mut Window, cx: &mut App| {
|
||||
let search_bar = pane
|
||||
.read(cx)
|
||||
.toolbar()
|
||||
.read(cx)
|
||||
.item_of_type::<BufferSearchBar>();
|
||||
let should_notify = search_bar
|
||||
.map(|search_bar| {
|
||||
search_bar.update(cx, |search_bar, cx| {
|
||||
callback.execute(search_bar, action, window, cx)
|
||||
})
|
||||
})
|
||||
.unwrap_or(false);
|
||||
if should_notify {
|
||||
pane.update(cx, |_, cx| cx.notify());
|
||||
} else {
|
||||
cx.propagate();
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub fn register_pane_search_actions(div: Div, pane: Entity<Pane>) -> Div {
|
||||
let mut registrar = PaneDivRegistrar::new(div, pane);
|
||||
BufferSearchBar::register(&mut registrar);
|
||||
registrar.into_div()
|
||||
}
|
||||
|
||||
/// Register actions for an active pane.
|
||||
impl SearchActionsRegistrar for Workspace {
|
||||
fn register_handler<A: Action>(&mut self, callback: impl ActionExecutor<A>) {
|
||||
self.register_action(move |workspace, action: &A, window, cx| {
|
||||
if workspace.has_active_modal(window, cx) && !workspace.hide_modal(window, cx) {
|
||||
cx.propagate();
|
||||
return;
|
||||
}
|
||||
|
||||
let pane = workspace.active_pane();
|
||||
let callback = callback.clone();
|
||||
pane.update(cx, |this, cx| {
|
||||
this.toolbar().update(cx, move |this, cx| {
|
||||
if let Some(search_bar) = this.item_of_type::<BufferSearchBar>() {
|
||||
let should_notify = search_bar.update(cx, move |search_bar, cx| {
|
||||
callback.execute(search_bar, action, window, cx)
|
||||
});
|
||||
if should_notify {
|
||||
cx.notify();
|
||||
} else {
|
||||
cx.propagate();
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
type DidHandleAction = bool;
|
||||
/// Potentially executes the underlying action if some preconditions are met (e.g. buffer search bar is visible)
|
||||
pub trait ActionExecutor<A: Action>: 'static + Clone {
|
||||
fn execute(
|
||||
&self,
|
||||
search_bar: &mut BufferSearchBar,
|
||||
action: &A,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<BufferSearchBar>,
|
||||
) -> DidHandleAction;
|
||||
}
|
||||
|
||||
/// Run an action when the search bar has been dismissed from the panel.
|
||||
pub struct ForDismissed<A>(pub(super) SearchBarActionCallback<A>);
|
||||
impl<A> Clone for ForDismissed<A> {
|
||||
fn clone(&self) -> Self {
|
||||
Self(self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl<A: Action> ActionExecutor<A> for ForDismissed<A> {
|
||||
fn execute(
|
||||
&self,
|
||||
search_bar: &mut BufferSearchBar,
|
||||
action: &A,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<BufferSearchBar>,
|
||||
) -> DidHandleAction {
|
||||
if search_bar.is_dismissed() {
|
||||
self.0(search_bar, action, window, cx);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Run an action when the search bar is deployed.
|
||||
pub struct ForDeployed<A>(pub(super) SearchBarActionCallback<A>);
|
||||
impl<A> Clone for ForDeployed<A> {
|
||||
fn clone(&self) -> Self {
|
||||
Self(self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl<A: Action> ActionExecutor<A> for ForDeployed<A> {
|
||||
fn execute(
|
||||
&self,
|
||||
search_bar: &mut BufferSearchBar,
|
||||
action: &A,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<BufferSearchBar>,
|
||||
) -> DidHandleAction {
|
||||
if search_bar.is_dismissed() || search_bar.active_searchable_item.is_none() {
|
||||
false
|
||||
} else {
|
||||
self.0(search_bar, action, window, cx);
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Run an action when the search bar has any matches or a pending external query,
|
||||
/// regardless of whether it is visible or not.
|
||||
pub struct WithResultsOrExternalQuery<A>(pub(super) SearchBarActionCallback<A>);
|
||||
impl<A> Clone for WithResultsOrExternalQuery<A> {
|
||||
fn clone(&self) -> Self {
|
||||
Self(self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl<A: Action> ActionExecutor<A> for WithResultsOrExternalQuery<A> {
|
||||
fn execute(
|
||||
&self,
|
||||
search_bar: &mut BufferSearchBar,
|
||||
action: &A,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<BufferSearchBar>,
|
||||
) -> DidHandleAction {
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
let has_external_query = false;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
let has_external_query = search_bar.pending_external_query.is_some();
|
||||
|
||||
if has_external_query || search_bar.active_match_index.is_some() {
|
||||
self.0(search_bar, action, window, cx);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
5590
crates/search/src/project_search.rs
Normal file
5590
crates/search/src/project_search.rs
Normal file
File diff suppressed because it is too large
Load Diff
204
crates/search/src/search.rs
Normal file
204
crates/search/src/search.rs
Normal file
@@ -0,0 +1,204 @@
|
||||
use bitflags::bitflags;
|
||||
pub use buffer_search::BufferSearchBar;
|
||||
pub use editor::HighlightKey;
|
||||
use editor::SearchSettings;
|
||||
use gpui::{Action, App, ClickEvent, FocusHandle, IntoElement, actions};
|
||||
use project::search::SearchQuery;
|
||||
pub use project_search::ProjectSearchView;
|
||||
use ui::{ButtonStyle, IconButton, IconButtonShape};
|
||||
use ui::{Tooltip, prelude::*};
|
||||
use workspace::notifications::NotificationId;
|
||||
use workspace::{Toast, Workspace};
|
||||
pub use zed_actions::search::ToggleIncludeIgnored;
|
||||
|
||||
pub use search_status_button::SEARCH_ICON;
|
||||
|
||||
use crate::project_search::ProjectSearchBar;
|
||||
|
||||
pub mod buffer_search;
|
||||
pub mod project_search;
|
||||
pub(crate) mod search_bar;
|
||||
pub mod search_status_button;
|
||||
|
||||
pub fn init(cx: &mut App) {
|
||||
menu::init();
|
||||
buffer_search::init(cx);
|
||||
project_search::init(cx);
|
||||
}
|
||||
|
||||
actions!(
|
||||
search,
|
||||
[
|
||||
/// Focuses on the search input field.
|
||||
FocusSearch,
|
||||
/// Toggles whole word matching.
|
||||
ToggleWholeWord,
|
||||
/// Toggles case-sensitive search.
|
||||
ToggleCaseSensitive,
|
||||
/// Toggles regular expression mode.
|
||||
ToggleRegex,
|
||||
/// Toggles the replace interface.
|
||||
ToggleReplace,
|
||||
/// Toggles searching within selection only.
|
||||
ToggleSelection,
|
||||
/// Selects the next search match.
|
||||
SelectNextMatch,
|
||||
/// Selects the previous search match.
|
||||
SelectPreviousMatch,
|
||||
/// Selects all search matches.
|
||||
SelectAllMatches,
|
||||
/// Cycles through search modes.
|
||||
CycleMode,
|
||||
/// Navigates to the next query in search history.
|
||||
NextHistoryQuery,
|
||||
/// Navigates to the previous query in search history.
|
||||
PreviousHistoryQuery,
|
||||
/// Replaces all matches.
|
||||
ReplaceAll,
|
||||
/// Replaces the next match.
|
||||
ReplaceNext,
|
||||
]
|
||||
);
|
||||
|
||||
bitflags! {
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy, Default)]
|
||||
pub struct SearchOptions: u8 {
|
||||
const NONE = 0;
|
||||
const WHOLE_WORD = 1 << SearchOption::WholeWord as u8;
|
||||
const CASE_SENSITIVE = 1 << SearchOption::CaseSensitive as u8;
|
||||
const INCLUDE_IGNORED = 1 << SearchOption::IncludeIgnored as u8;
|
||||
const REGEX = 1 << SearchOption::Regex as u8;
|
||||
const ONE_MATCH_PER_LINE = 1 << SearchOption::OneMatchPerLine as u8;
|
||||
/// If set, reverse direction when finding the active match
|
||||
const BACKWARDS = 1 << SearchOption::Backwards as u8;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[repr(u8)]
|
||||
pub enum SearchOption {
|
||||
WholeWord = 0,
|
||||
CaseSensitive,
|
||||
IncludeIgnored,
|
||||
Regex,
|
||||
OneMatchPerLine,
|
||||
Backwards,
|
||||
}
|
||||
|
||||
pub enum SearchSource<'a, 'b> {
|
||||
Buffer,
|
||||
Project(&'a Context<'b, ProjectSearchBar>),
|
||||
}
|
||||
|
||||
impl SearchOption {
|
||||
pub fn as_options(&self) -> SearchOptions {
|
||||
SearchOptions::from_bits(1 << *self as u8).unwrap()
|
||||
}
|
||||
|
||||
pub fn label(&self) -> &'static str {
|
||||
match self {
|
||||
SearchOption::WholeWord => "Match Whole Words",
|
||||
SearchOption::CaseSensitive => "Match Case Sensitivity",
|
||||
SearchOption::IncludeIgnored => "Also search files ignored by configuration",
|
||||
SearchOption::Regex => "Use Regular Expressions",
|
||||
SearchOption::OneMatchPerLine => "One Match Per Line",
|
||||
SearchOption::Backwards => "Search Backwards",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn icon(&self) -> ui::IconName {
|
||||
match self {
|
||||
SearchOption::WholeWord => ui::IconName::WholeWord,
|
||||
SearchOption::CaseSensitive => ui::IconName::CaseSensitive,
|
||||
SearchOption::IncludeIgnored => ui::IconName::Sliders,
|
||||
SearchOption::Regex => ui::IconName::Regex,
|
||||
_ => panic!("{self:?} is not a named SearchOption"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_toggle_action(self) -> &'static dyn Action {
|
||||
match self {
|
||||
SearchOption::WholeWord => &ToggleWholeWord,
|
||||
SearchOption::CaseSensitive => &ToggleCaseSensitive,
|
||||
SearchOption::IncludeIgnored => &ToggleIncludeIgnored,
|
||||
SearchOption::Regex => &ToggleRegex,
|
||||
_ => panic!("{self:?} is not a toggle action"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_button(
|
||||
&self,
|
||||
active: SearchOptions,
|
||||
search_source: SearchSource,
|
||||
focus_handle: FocusHandle,
|
||||
) -> impl IntoElement {
|
||||
let action = self.to_toggle_action();
|
||||
let label = self.label();
|
||||
IconButton::new(
|
||||
(label, matches!(search_source, SearchSource::Buffer) as u32),
|
||||
self.icon(),
|
||||
)
|
||||
.map(|button| match search_source {
|
||||
SearchSource::Buffer => {
|
||||
let focus_handle = focus_handle.clone();
|
||||
button.on_click(move |_: &ClickEvent, window, cx| {
|
||||
if !focus_handle.is_focused(window) {
|
||||
window.focus(&focus_handle, cx);
|
||||
}
|
||||
window.dispatch_action(action.boxed_clone(), cx);
|
||||
})
|
||||
}
|
||||
SearchSource::Project(cx) => {
|
||||
let options = self.as_options();
|
||||
button.on_click(cx.listener(move |this, _: &ClickEvent, window, cx| {
|
||||
this.toggle_search_option(options, window, cx);
|
||||
}))
|
||||
}
|
||||
})
|
||||
.style(ButtonStyle::Subtle)
|
||||
.shape(IconButtonShape::Square)
|
||||
.toggle_state(active.contains(self.as_options()))
|
||||
.tooltip(move |_window, cx| Tooltip::for_action_in(label, action, &focus_handle, cx))
|
||||
}
|
||||
}
|
||||
|
||||
impl SearchOptions {
|
||||
pub fn none() -> SearchOptions {
|
||||
SearchOptions::NONE
|
||||
}
|
||||
|
||||
pub fn from_query(query: &SearchQuery) -> SearchOptions {
|
||||
let mut options = SearchOptions::NONE;
|
||||
options.set(SearchOptions::WHOLE_WORD, query.whole_word());
|
||||
options.set(SearchOptions::CASE_SENSITIVE, query.case_sensitive());
|
||||
options.set(SearchOptions::INCLUDE_IGNORED, query.include_ignored());
|
||||
options.set(SearchOptions::REGEX, query.is_regex());
|
||||
options
|
||||
}
|
||||
|
||||
pub fn from_settings(settings: &SearchSettings) -> SearchOptions {
|
||||
let mut options = SearchOptions::NONE;
|
||||
options.set(SearchOptions::WHOLE_WORD, settings.whole_word);
|
||||
options.set(SearchOptions::CASE_SENSITIVE, settings.case_sensitive);
|
||||
options.set(SearchOptions::INCLUDE_IGNORED, settings.include_ignored);
|
||||
options.set(SearchOptions::REGEX, settings.regex);
|
||||
options
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn show_no_more_matches(window: &mut Window, cx: &mut App) {
|
||||
window.defer(cx, |window, cx| {
|
||||
struct NotifType();
|
||||
let notification_id = NotificationId::unique::<NotifType>();
|
||||
|
||||
let Some(workspace) = Workspace::for_window(window, cx) else {
|
||||
return;
|
||||
};
|
||||
workspace.update(cx, |workspace, cx| {
|
||||
workspace.show_toast(
|
||||
Toast::new(notification_id.clone(), "No more matches").autohide(),
|
||||
cx,
|
||||
);
|
||||
})
|
||||
});
|
||||
}
|
||||
139
crates/search/src/search_bar.rs
Normal file
139
crates/search/src/search_bar.rs
Normal file
@@ -0,0 +1,139 @@
|
||||
use editor::{Editor, EditorElement, EditorStyle, MultiBufferOffset, ToOffset};
|
||||
use gpui::{Action, App, Entity, FocusHandle, Hsla, IntoElement, TextStyle};
|
||||
use settings::Settings;
|
||||
use theme_settings::ThemeSettings;
|
||||
use ui::{IconButton, IconButtonShape};
|
||||
use ui::{Tooltip, prelude::*};
|
||||
|
||||
pub(super) enum HistoryNavigationDirection {
|
||||
Previous,
|
||||
Next,
|
||||
}
|
||||
|
||||
pub(super) fn should_navigate_history(
|
||||
editor: &Entity<Editor>,
|
||||
direction: HistoryNavigationDirection,
|
||||
cx: &App,
|
||||
) -> bool {
|
||||
let editor_ref = editor.read(cx);
|
||||
let snapshot = editor_ref.buffer().read(cx).snapshot(cx);
|
||||
if snapshot.max_point().row == 0 {
|
||||
return true;
|
||||
}
|
||||
let selections = editor_ref.selections.disjoint_anchors();
|
||||
if let [selection] = selections {
|
||||
let offset = selection.end.to_offset(&snapshot);
|
||||
match direction {
|
||||
HistoryNavigationDirection::Previous => offset == MultiBufferOffset(0),
|
||||
HistoryNavigationDirection::Next => offset == snapshot.len(),
|
||||
}
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) enum ActionButtonState {
|
||||
Disabled,
|
||||
Toggled,
|
||||
}
|
||||
|
||||
pub(super) fn render_action_button(
|
||||
id_prefix: &'static str,
|
||||
icon: ui::IconName,
|
||||
button_state: Option<ActionButtonState>,
|
||||
tooltip: &'static str,
|
||||
action: &'static dyn Action,
|
||||
focus_handle: FocusHandle,
|
||||
) -> impl IntoElement {
|
||||
IconButton::new(
|
||||
SharedString::from(format!("{id_prefix}-{}", action.name())),
|
||||
icon,
|
||||
)
|
||||
.shape(IconButtonShape::Square)
|
||||
.on_click({
|
||||
let focus_handle = focus_handle.clone();
|
||||
move |_, window, cx| {
|
||||
if !focus_handle.is_focused(window) {
|
||||
window.focus(&focus_handle, cx);
|
||||
}
|
||||
window.dispatch_action(action.boxed_clone(), cx);
|
||||
}
|
||||
})
|
||||
.tooltip(move |_window, cx| Tooltip::for_action_in(tooltip, action, &focus_handle, cx))
|
||||
.when_some(button_state, |this, state| match state {
|
||||
ActionButtonState::Toggled => this.toggle_state(true),
|
||||
ActionButtonState::Disabled => this.disabled(true),
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn input_base_styles(border_color: Hsla, map: impl FnOnce(Div) -> Div) -> Div {
|
||||
h_flex()
|
||||
.map(map)
|
||||
.min_w_32()
|
||||
.min_h_8()
|
||||
.pl_2()
|
||||
.pr_1()
|
||||
.border_1()
|
||||
.border_color(border_color)
|
||||
.rounded_md()
|
||||
}
|
||||
pub(crate) fn filter_search_results_input(
|
||||
border_color: Hsla,
|
||||
map: impl FnOnce(Div) -> Div,
|
||||
cx: &App,
|
||||
) -> Div {
|
||||
input_base_styles(border_color, map).pl_0().child(
|
||||
h_flex()
|
||||
.mr_2()
|
||||
.px_2()
|
||||
.h_full()
|
||||
.border_r_1()
|
||||
.border_color(cx.theme().colors().border)
|
||||
.bg(cx.theme().colors().text_accent.opacity(0.05))
|
||||
.child(Label::new("Find in Results").color(Color::Muted)),
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn render_text_input(
|
||||
editor: &Entity<Editor>,
|
||||
color_override: Option<Color>,
|
||||
app: &App,
|
||||
) -> impl IntoElement {
|
||||
let (color, use_syntax) = if editor.read(app).read_only(app) {
|
||||
(app.theme().colors().text_disabled, false)
|
||||
} else {
|
||||
match color_override {
|
||||
Some(color_override) => (color_override.color(app), false),
|
||||
None => (app.theme().colors().text, true),
|
||||
}
|
||||
};
|
||||
|
||||
let settings = ThemeSettings::get_global(app);
|
||||
let text_style = TextStyle {
|
||||
color,
|
||||
font_family: settings.buffer_font.family.clone(),
|
||||
font_features: settings.buffer_font.features.clone(),
|
||||
font_fallbacks: settings.buffer_font.fallbacks.clone(),
|
||||
font_size: rems(0.875).into(),
|
||||
font_weight: settings.buffer_font.weight,
|
||||
line_height: relative(1.3),
|
||||
..TextStyle::default()
|
||||
};
|
||||
|
||||
let mut editor_style = EditorStyle {
|
||||
background: app.theme().colors().toolbar_background,
|
||||
local_player: app.theme().players().local(),
|
||||
text: text_style,
|
||||
..EditorStyle::default()
|
||||
};
|
||||
if use_syntax {
|
||||
editor_style.syntax = app.theme().syntax().clone();
|
||||
}
|
||||
|
||||
EditorElement::new(editor, editor_style)
|
||||
}
|
||||
|
||||
/// This element makes all search inputs align as if they were in the same column
|
||||
pub(crate) fn alignment_element() -> Div {
|
||||
div().size_5().flex_none().ml_0p5()
|
||||
}
|
||||
71
crates/search/src/search_status_button.rs
Normal file
71
crates/search/src/search_status_button.rs
Normal file
@@ -0,0 +1,71 @@
|
||||
use editor::EditorSettings;
|
||||
use gpui::{App, FocusHandle};
|
||||
use settings::Settings as _;
|
||||
use ui::{ButtonCommon, Clickable, Context, Render, Tooltip, Window, prelude::*};
|
||||
use workspace::{HideStatusItem, ItemHandle, StatusItemView};
|
||||
|
||||
pub const SEARCH_ICON: IconName = IconName::MagnifyingGlass;
|
||||
|
||||
pub struct SearchButton {
|
||||
pane_item_focus_handle: Option<FocusHandle>,
|
||||
}
|
||||
|
||||
impl SearchButton {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
pane_item_focus_handle: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for SearchButton {
|
||||
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl ui::IntoElement {
|
||||
let button = div();
|
||||
|
||||
if !EditorSettings::get_global(cx).search.button {
|
||||
return button.hidden();
|
||||
}
|
||||
|
||||
let focus_handle = self.pane_item_focus_handle.clone();
|
||||
button.child(
|
||||
IconButton::new("project-search-indicator", SEARCH_ICON)
|
||||
.icon_size(IconSize::Small)
|
||||
.tooltip(move |_window, cx| {
|
||||
if let Some(focus_handle) = &focus_handle {
|
||||
Tooltip::for_action_in(
|
||||
"Project Search",
|
||||
&workspace::DeploySearch::default(),
|
||||
focus_handle,
|
||||
cx,
|
||||
)
|
||||
} else {
|
||||
Tooltip::for_action(
|
||||
"Project Search",
|
||||
&workspace::DeploySearch::default(),
|
||||
cx,
|
||||
)
|
||||
}
|
||||
})
|
||||
.on_click(cx.listener(|_this, _, window, cx| {
|
||||
window.dispatch_action(Box::new(workspace::DeploySearch::default()), cx);
|
||||
})),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl StatusItemView for SearchButton {
|
||||
fn set_active_pane_item(
|
||||
&mut self,
|
||||
active_pane_item: Option<&dyn ItemHandle>,
|
||||
_window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.pane_item_focus_handle = active_pane_item.map(|item| item.item_focus_handle(cx));
|
||||
}
|
||||
|
||||
fn hide_setting(&self, _: &App) -> Option<HideStatusItem> {
|
||||
Some(HideStatusItem::new(|settings| {
|
||||
settings.editor.search.get_or_insert_default().button = Some(false);
|
||||
}))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user