search: Fix some inconsistencies between project and buffer search bars (#36103)
- project search query string now turns red when no results are found matching buffer search behavior - General code deduplication as well as more consistent layout between the two bars, as some minor details have drifted apart - Tab cycling in buffer search now ends up in editor focus when cycling backwards, matching forward cycling - Report parse errors in filter include and exclude editors Release Notes: - N/A
This commit is contained in:
parent
23d0433158
commit
8d6982e78f
4 changed files with 545 additions and 689 deletions
|
@ -3,20 +3,23 @@ mod registrar;
|
||||||
use crate::{
|
use crate::{
|
||||||
FocusSearch, NextHistoryQuery, PreviousHistoryQuery, ReplaceAll, ReplaceNext, SearchOptions,
|
FocusSearch, NextHistoryQuery, PreviousHistoryQuery, ReplaceAll, ReplaceNext, SearchOptions,
|
||||||
SelectAllMatches, SelectNextMatch, SelectPreviousMatch, ToggleCaseSensitive, ToggleRegex,
|
SelectAllMatches, SelectNextMatch, SelectPreviousMatch, ToggleCaseSensitive, ToggleRegex,
|
||||||
ToggleReplace, ToggleSelection, ToggleWholeWord, search_bar::render_nav_button,
|
ToggleReplace, ToggleSelection, ToggleWholeWord,
|
||||||
|
search_bar::{
|
||||||
|
input_base_styles, render_action_button, render_text_input, toggle_replace_button,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
use any_vec::AnyVec;
|
use any_vec::AnyVec;
|
||||||
use anyhow::Context as _;
|
use anyhow::Context as _;
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
use editor::{
|
use editor::{
|
||||||
DisplayPoint, Editor, EditorElement, EditorSettings, EditorStyle,
|
DisplayPoint, Editor, EditorSettings,
|
||||||
actions::{Backtab, Tab},
|
actions::{Backtab, Tab},
|
||||||
};
|
};
|
||||||
use futures::channel::oneshot;
|
use futures::channel::oneshot;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
Action, App, ClickEvent, Context, Entity, EventEmitter, FocusHandle, Focusable,
|
Action, App, ClickEvent, Context, Entity, EventEmitter, Focusable, InteractiveElement as _,
|
||||||
InteractiveElement as _, IntoElement, KeyContext, ParentElement as _, Render, ScrollHandle,
|
IntoElement, KeyContext, ParentElement as _, Render, ScrollHandle, Styled, Subscription, Task,
|
||||||
Styled, Subscription, Task, TextStyle, Window, actions, div,
|
Window, actions, div,
|
||||||
};
|
};
|
||||||
use language::{Language, LanguageRegistry};
|
use language::{Language, LanguageRegistry};
|
||||||
use project::{
|
use project::{
|
||||||
|
@ -27,7 +30,6 @@ use schemars::JsonSchema;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use settings::Settings;
|
use settings::Settings;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use theme::ThemeSettings;
|
|
||||||
use zed_actions::outline::ToggleOutline;
|
use zed_actions::outline::ToggleOutline;
|
||||||
|
|
||||||
use ui::{
|
use ui::{
|
||||||
|
@ -125,46 +127,6 @@ pub struct BufferSearchBar {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BufferSearchBar {
|
impl BufferSearchBar {
|
||||||
fn render_text_input(
|
|
||||||
&self,
|
|
||||||
editor: &Entity<Editor>,
|
|
||||||
color_override: Option<Color>,
|
|
||||||
cx: &mut Context<Self>,
|
|
||||||
) -> impl IntoElement {
|
|
||||||
let (color, use_syntax) = if editor.read(cx).read_only(cx) {
|
|
||||||
(cx.theme().colors().text_disabled, false)
|
|
||||||
} else {
|
|
||||||
match color_override {
|
|
||||||
Some(color_override) => (color_override.color(cx), false),
|
|
||||||
None => (cx.theme().colors().text, true),
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let settings = ThemeSettings::get_global(cx);
|
|
||||||
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: cx.theme().colors().toolbar_background,
|
|
||||||
local_player: cx.theme().players().local(),
|
|
||||||
text: text_style,
|
|
||||||
..EditorStyle::default()
|
|
||||||
};
|
|
||||||
if use_syntax {
|
|
||||||
editor_style.syntax = cx.theme().syntax().clone();
|
|
||||||
}
|
|
||||||
|
|
||||||
EditorElement::new(editor, editor_style)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn query_editor_focused(&self) -> bool {
|
pub fn query_editor_focused(&self) -> bool {
|
||||||
self.query_editor_focused
|
self.query_editor_focused
|
||||||
}
|
}
|
||||||
|
@ -185,7 +147,14 @@ impl Render for BufferSearchBar {
|
||||||
let hide_inline_icons = self.editor_needed_width
|
let hide_inline_icons = self.editor_needed_width
|
||||||
> self.editor_scroll_handle.bounds().size.width - window.rem_size() * 6.;
|
> self.editor_scroll_handle.bounds().size.width - window.rem_size() * 6.;
|
||||||
|
|
||||||
let supported_options = self.supported_options(cx);
|
let workspace::searchable::SearchOptions {
|
||||||
|
case,
|
||||||
|
word,
|
||||||
|
regex,
|
||||||
|
replacement,
|
||||||
|
selection,
|
||||||
|
find_in_results,
|
||||||
|
} = self.supported_options(cx);
|
||||||
|
|
||||||
if self.query_editor.update(cx, |query_editor, _cx| {
|
if self.query_editor.update(cx, |query_editor, _cx| {
|
||||||
query_editor.placeholder_text().is_none()
|
query_editor.placeholder_text().is_none()
|
||||||
|
@ -220,268 +189,205 @@ impl Render for BufferSearchBar {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.unwrap_or_else(|| "0/0".to_string());
|
.unwrap_or_else(|| "0/0".to_string());
|
||||||
let should_show_replace_input = self.replace_enabled && supported_options.replacement;
|
let should_show_replace_input = self.replace_enabled && replacement;
|
||||||
let in_replace = self.replacement_editor.focus_handle(cx).is_focused(window);
|
let in_replace = self.replacement_editor.focus_handle(cx).is_focused(window);
|
||||||
|
|
||||||
|
let theme_colors = cx.theme().colors();
|
||||||
|
let query_border = if self.query_error.is_some() {
|
||||||
|
Color::Error.color(cx)
|
||||||
|
} else {
|
||||||
|
theme_colors.border
|
||||||
|
};
|
||||||
|
let replacement_border = theme_colors.border;
|
||||||
|
|
||||||
|
let container_width = window.viewport_size().width;
|
||||||
|
let input_width = SearchInputWidth::calc_width(container_width);
|
||||||
|
|
||||||
|
let input_base_styles =
|
||||||
|
|border_color| input_base_styles(border_color, |div| div.w(input_width));
|
||||||
|
|
||||||
|
let query_column = input_base_styles(query_border)
|
||||||
|
.id("editor-scroll")
|
||||||
|
.track_scroll(&self.editor_scroll_handle)
|
||||||
|
.child(render_text_input(&self.query_editor, color_override, cx))
|
||||||
|
.when(!hide_inline_icons, |div| {
|
||||||
|
div.child(
|
||||||
|
h_flex()
|
||||||
|
.gap_1()
|
||||||
|
.when(case, |div| {
|
||||||
|
div.child(SearchOptions::CASE_SENSITIVE.as_button(
|
||||||
|
self.search_options.contains(SearchOptions::CASE_SENSITIVE),
|
||||||
|
focus_handle.clone(),
|
||||||
|
cx.listener(|this, _, window, cx| {
|
||||||
|
this.toggle_case_sensitive(&ToggleCaseSensitive, window, cx)
|
||||||
|
}),
|
||||||
|
))
|
||||||
|
})
|
||||||
|
.when(word, |div| {
|
||||||
|
div.child(SearchOptions::WHOLE_WORD.as_button(
|
||||||
|
self.search_options.contains(SearchOptions::WHOLE_WORD),
|
||||||
|
focus_handle.clone(),
|
||||||
|
cx.listener(|this, _, window, cx| {
|
||||||
|
this.toggle_whole_word(&ToggleWholeWord, window, cx)
|
||||||
|
}),
|
||||||
|
))
|
||||||
|
})
|
||||||
|
.when(regex, |div| {
|
||||||
|
div.child(SearchOptions::REGEX.as_button(
|
||||||
|
self.search_options.contains(SearchOptions::REGEX),
|
||||||
|
focus_handle.clone(),
|
||||||
|
cx.listener(|this, _, window, cx| {
|
||||||
|
this.toggle_regex(&ToggleRegex, window, cx)
|
||||||
|
}),
|
||||||
|
))
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
let mode_column = h_flex()
|
||||||
|
.gap_1()
|
||||||
|
.min_w_64()
|
||||||
|
.when(replacement, |this| {
|
||||||
|
this.child(toggle_replace_button(
|
||||||
|
"buffer-search-bar-toggle-replace-button",
|
||||||
|
focus_handle.clone(),
|
||||||
|
self.replace_enabled,
|
||||||
|
cx.listener(|this, _: &ClickEvent, window, cx| {
|
||||||
|
this.toggle_replace(&ToggleReplace, window, cx);
|
||||||
|
}),
|
||||||
|
))
|
||||||
|
})
|
||||||
|
.when(selection, |this| {
|
||||||
|
this.child(
|
||||||
|
IconButton::new(
|
||||||
|
"buffer-search-bar-toggle-search-selection-button",
|
||||||
|
IconName::Quote,
|
||||||
|
)
|
||||||
|
.style(ButtonStyle::Subtle)
|
||||||
|
.shape(IconButtonShape::Square)
|
||||||
|
.when(self.selection_search_enabled, |button| {
|
||||||
|
button.style(ButtonStyle::Filled)
|
||||||
|
})
|
||||||
|
.on_click(cx.listener(|this, _: &ClickEvent, window, cx| {
|
||||||
|
this.toggle_selection(&ToggleSelection, window, cx);
|
||||||
|
}))
|
||||||
|
.toggle_state(self.selection_search_enabled)
|
||||||
|
.tooltip({
|
||||||
|
let focus_handle = focus_handle.clone();
|
||||||
|
move |window, cx| {
|
||||||
|
Tooltip::for_action_in(
|
||||||
|
"Toggle Search Selection",
|
||||||
|
&ToggleSelection,
|
||||||
|
&focus_handle,
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.when(!find_in_results, |el| {
|
||||||
|
let query_focus = self.query_editor.focus_handle(cx);
|
||||||
|
let matches_column = h_flex()
|
||||||
|
.pl_2()
|
||||||
|
.ml_2()
|
||||||
|
.border_l_1()
|
||||||
|
.border_color(theme_colors.border_variant)
|
||||||
|
.child(render_action_button(
|
||||||
|
"buffer-search-nav-button",
|
||||||
|
ui::IconName::ChevronLeft,
|
||||||
|
self.active_match_index.is_some(),
|
||||||
|
"Select Previous Match",
|
||||||
|
&SelectPreviousMatch,
|
||||||
|
query_focus.clone(),
|
||||||
|
))
|
||||||
|
.child(render_action_button(
|
||||||
|
"buffer-search-nav-button",
|
||||||
|
ui::IconName::ChevronRight,
|
||||||
|
self.active_match_index.is_some(),
|
||||||
|
"Select Next Match",
|
||||||
|
&SelectNextMatch,
|
||||||
|
query_focus.clone(),
|
||||||
|
))
|
||||||
|
.when(!narrow_mode, |this| {
|
||||||
|
this.child(div().ml_2().min_w(rems_from_px(40.)).child(
|
||||||
|
Label::new(match_text).size(LabelSize::Small).color(
|
||||||
|
if self.active_match_index.is_some() {
|
||||||
|
Color::Default
|
||||||
|
} else {
|
||||||
|
Color::Disabled
|
||||||
|
},
|
||||||
|
),
|
||||||
|
))
|
||||||
|
});
|
||||||
|
|
||||||
|
el.child(render_action_button(
|
||||||
|
"buffer-search-nav-button",
|
||||||
|
IconName::SelectAll,
|
||||||
|
true,
|
||||||
|
"Select All Matches",
|
||||||
|
&SelectAllMatches,
|
||||||
|
query_focus,
|
||||||
|
))
|
||||||
|
.child(matches_column)
|
||||||
|
})
|
||||||
|
.when(find_in_results, |el| {
|
||||||
|
el.child(render_action_button(
|
||||||
|
"buffer-search",
|
||||||
|
IconName::Close,
|
||||||
|
true,
|
||||||
|
"Close Search Bar",
|
||||||
|
&Dismiss,
|
||||||
|
focus_handle.clone(),
|
||||||
|
))
|
||||||
|
});
|
||||||
|
|
||||||
|
let search_line = h_flex()
|
||||||
|
.w_full()
|
||||||
|
.gap_2()
|
||||||
|
.when(find_in_results, |el| {
|
||||||
|
el.child(Label::new("Find in results").color(Color::Hint))
|
||||||
|
})
|
||||||
|
.child(query_column)
|
||||||
|
.child(mode_column);
|
||||||
|
|
||||||
|
let replace_line =
|
||||||
|
should_show_replace_input.then(|| {
|
||||||
|
let replace_column = input_base_styles(replacement_border)
|
||||||
|
.child(render_text_input(&self.replacement_editor, None, cx));
|
||||||
|
let focus_handle = self.replacement_editor.read(cx).focus_handle(cx);
|
||||||
|
|
||||||
|
let replace_actions = h_flex()
|
||||||
|
.min_w_64()
|
||||||
|
.gap_1()
|
||||||
|
.child(render_action_button(
|
||||||
|
"buffer-search-replace-button",
|
||||||
|
IconName::ReplaceNext,
|
||||||
|
true,
|
||||||
|
"Replace Next Match",
|
||||||
|
&ReplaceNext,
|
||||||
|
focus_handle.clone(),
|
||||||
|
))
|
||||||
|
.child(render_action_button(
|
||||||
|
"buffer-search-replace-button",
|
||||||
|
IconName::ReplaceAll,
|
||||||
|
true,
|
||||||
|
"Replace All Matches",
|
||||||
|
&ReplaceAll,
|
||||||
|
focus_handle,
|
||||||
|
));
|
||||||
|
h_flex()
|
||||||
|
.w_full()
|
||||||
|
.gap_2()
|
||||||
|
.child(replace_column)
|
||||||
|
.child(replace_actions)
|
||||||
|
});
|
||||||
|
|
||||||
let mut key_context = KeyContext::new_with_defaults();
|
let mut key_context = KeyContext::new_with_defaults();
|
||||||
key_context.add("BufferSearchBar");
|
key_context.add("BufferSearchBar");
|
||||||
if in_replace {
|
if in_replace {
|
||||||
key_context.add("in_replace");
|
key_context.add("in_replace");
|
||||||
}
|
}
|
||||||
let query_border = if self.query_error.is_some() {
|
|
||||||
Color::Error.color(cx)
|
|
||||||
} else {
|
|
||||||
cx.theme().colors().border
|
|
||||||
};
|
|
||||||
let replacement_border = cx.theme().colors().border;
|
|
||||||
|
|
||||||
let container_width = window.viewport_size().width;
|
|
||||||
let input_width = SearchInputWidth::calc_width(container_width);
|
|
||||||
|
|
||||||
let input_base_styles = |border_color| {
|
|
||||||
h_flex()
|
|
||||||
.min_w_32()
|
|
||||||
.w(input_width)
|
|
||||||
.h_8()
|
|
||||||
.pl_2()
|
|
||||||
.pr_1()
|
|
||||||
.py_1()
|
|
||||||
.border_1()
|
|
||||||
.border_color(border_color)
|
|
||||||
.rounded_lg()
|
|
||||||
};
|
|
||||||
|
|
||||||
let search_line = h_flex()
|
|
||||||
.gap_2()
|
|
||||||
.when(supported_options.find_in_results, |el| {
|
|
||||||
el.child(Label::new("Find in results").color(Color::Hint))
|
|
||||||
})
|
|
||||||
.child(
|
|
||||||
input_base_styles(query_border)
|
|
||||||
.id("editor-scroll")
|
|
||||||
.track_scroll(&self.editor_scroll_handle)
|
|
||||||
.child(self.render_text_input(&self.query_editor, color_override, cx))
|
|
||||||
.when(!hide_inline_icons, |div| {
|
|
||||||
div.child(
|
|
||||||
h_flex()
|
|
||||||
.gap_1()
|
|
||||||
.children(supported_options.case.then(|| {
|
|
||||||
self.render_search_option_button(
|
|
||||||
SearchOptions::CASE_SENSITIVE,
|
|
||||||
focus_handle.clone(),
|
|
||||||
cx.listener(|this, _, window, cx| {
|
|
||||||
this.toggle_case_sensitive(
|
|
||||||
&ToggleCaseSensitive,
|
|
||||||
window,
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
}))
|
|
||||||
.children(supported_options.word.then(|| {
|
|
||||||
self.render_search_option_button(
|
|
||||||
SearchOptions::WHOLE_WORD,
|
|
||||||
focus_handle.clone(),
|
|
||||||
cx.listener(|this, _, window, cx| {
|
|
||||||
this.toggle_whole_word(&ToggleWholeWord, window, cx)
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
}))
|
|
||||||
.children(supported_options.regex.then(|| {
|
|
||||||
self.render_search_option_button(
|
|
||||||
SearchOptions::REGEX,
|
|
||||||
focus_handle.clone(),
|
|
||||||
cx.listener(|this, _, window, cx| {
|
|
||||||
this.toggle_regex(&ToggleRegex, window, cx)
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
})),
|
|
||||||
)
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
h_flex()
|
|
||||||
.gap_1()
|
|
||||||
.min_w_64()
|
|
||||||
.when(supported_options.replacement, |this| {
|
|
||||||
this.child(
|
|
||||||
IconButton::new(
|
|
||||||
"buffer-search-bar-toggle-replace-button",
|
|
||||||
IconName::Replace,
|
|
||||||
)
|
|
||||||
.style(ButtonStyle::Subtle)
|
|
||||||
.shape(IconButtonShape::Square)
|
|
||||||
.when(self.replace_enabled, |button| {
|
|
||||||
button.style(ButtonStyle::Filled)
|
|
||||||
})
|
|
||||||
.on_click(cx.listener(|this, _: &ClickEvent, window, cx| {
|
|
||||||
this.toggle_replace(&ToggleReplace, window, cx);
|
|
||||||
}))
|
|
||||||
.toggle_state(self.replace_enabled)
|
|
||||||
.tooltip({
|
|
||||||
let focus_handle = focus_handle.clone();
|
|
||||||
move |window, cx| {
|
|
||||||
Tooltip::for_action_in(
|
|
||||||
"Toggle Replace",
|
|
||||||
&ToggleReplace,
|
|
||||||
&focus_handle,
|
|
||||||
window,
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.when(supported_options.selection, |this| {
|
|
||||||
this.child(
|
|
||||||
IconButton::new(
|
|
||||||
"buffer-search-bar-toggle-search-selection-button",
|
|
||||||
IconName::Quote,
|
|
||||||
)
|
|
||||||
.style(ButtonStyle::Subtle)
|
|
||||||
.shape(IconButtonShape::Square)
|
|
||||||
.when(self.selection_search_enabled, |button| {
|
|
||||||
button.style(ButtonStyle::Filled)
|
|
||||||
})
|
|
||||||
.on_click(cx.listener(|this, _: &ClickEvent, window, cx| {
|
|
||||||
this.toggle_selection(&ToggleSelection, window, cx);
|
|
||||||
}))
|
|
||||||
.toggle_state(self.selection_search_enabled)
|
|
||||||
.tooltip({
|
|
||||||
let focus_handle = focus_handle.clone();
|
|
||||||
move |window, cx| {
|
|
||||||
Tooltip::for_action_in(
|
|
||||||
"Toggle Search Selection",
|
|
||||||
&ToggleSelection,
|
|
||||||
&focus_handle,
|
|
||||||
window,
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.when(!supported_options.find_in_results, |el| {
|
|
||||||
el.child(
|
|
||||||
IconButton::new("select-all", ui::IconName::SelectAll)
|
|
||||||
.on_click(|_, window, cx| {
|
|
||||||
window.dispatch_action(SelectAllMatches.boxed_clone(), cx)
|
|
||||||
})
|
|
||||||
.shape(IconButtonShape::Square)
|
|
||||||
.tooltip({
|
|
||||||
let focus_handle = focus_handle.clone();
|
|
||||||
move |window, cx| {
|
|
||||||
Tooltip::for_action_in(
|
|
||||||
"Select All Matches",
|
|
||||||
&SelectAllMatches,
|
|
||||||
&focus_handle,
|
|
||||||
window,
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
h_flex()
|
|
||||||
.pl_2()
|
|
||||||
.ml_1()
|
|
||||||
.border_l_1()
|
|
||||||
.border_color(cx.theme().colors().border_variant)
|
|
||||||
.child(render_nav_button(
|
|
||||||
ui::IconName::ChevronLeft,
|
|
||||||
self.active_match_index.is_some(),
|
|
||||||
"Select Previous Match",
|
|
||||||
&SelectPreviousMatch,
|
|
||||||
focus_handle.clone(),
|
|
||||||
))
|
|
||||||
.child(render_nav_button(
|
|
||||||
ui::IconName::ChevronRight,
|
|
||||||
self.active_match_index.is_some(),
|
|
||||||
"Select Next Match",
|
|
||||||
&SelectNextMatch,
|
|
||||||
focus_handle.clone(),
|
|
||||||
)),
|
|
||||||
)
|
|
||||||
.when(!narrow_mode, |this| {
|
|
||||||
this.child(h_flex().ml_2().min_w(rems_from_px(40.)).child(
|
|
||||||
Label::new(match_text).size(LabelSize::Small).color(
|
|
||||||
if self.active_match_index.is_some() {
|
|
||||||
Color::Default
|
|
||||||
} else {
|
|
||||||
Color::Disabled
|
|
||||||
},
|
|
||||||
),
|
|
||||||
))
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.when(supported_options.find_in_results, |el| {
|
|
||||||
el.child(
|
|
||||||
IconButton::new(SharedString::from("Close"), IconName::Close)
|
|
||||||
.shape(IconButtonShape::Square)
|
|
||||||
.tooltip(move |window, cx| {
|
|
||||||
Tooltip::for_action("Close Search Bar", &Dismiss, window, cx)
|
|
||||||
})
|
|
||||||
.on_click(cx.listener(|this, _: &ClickEvent, window, cx| {
|
|
||||||
this.dismiss(&Dismiss, window, cx)
|
|
||||||
})),
|
|
||||||
)
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
let replace_line = should_show_replace_input.then(|| {
|
|
||||||
h_flex()
|
|
||||||
.gap_2()
|
|
||||||
.child(
|
|
||||||
input_base_styles(replacement_border).child(self.render_text_input(
|
|
||||||
&self.replacement_editor,
|
|
||||||
None,
|
|
||||||
cx,
|
|
||||||
)),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
h_flex()
|
|
||||||
.min_w_64()
|
|
||||||
.gap_1()
|
|
||||||
.child(
|
|
||||||
IconButton::new("search-replace-next", ui::IconName::ReplaceNext)
|
|
||||||
.shape(IconButtonShape::Square)
|
|
||||||
.tooltip({
|
|
||||||
let focus_handle = focus_handle.clone();
|
|
||||||
move |window, cx| {
|
|
||||||
Tooltip::for_action_in(
|
|
||||||
"Replace Next Match",
|
|
||||||
&ReplaceNext,
|
|
||||||
&focus_handle,
|
|
||||||
window,
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.on_click(cx.listener(|this, _, window, cx| {
|
|
||||||
this.replace_next(&ReplaceNext, window, cx)
|
|
||||||
})),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
IconButton::new("search-replace-all", ui::IconName::ReplaceAll)
|
|
||||||
.shape(IconButtonShape::Square)
|
|
||||||
.tooltip({
|
|
||||||
let focus_handle = focus_handle.clone();
|
|
||||||
move |window, cx| {
|
|
||||||
Tooltip::for_action_in(
|
|
||||||
"Replace All Matches",
|
|
||||||
&ReplaceAll,
|
|
||||||
&focus_handle,
|
|
||||||
window,
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.on_click(cx.listener(|this, _, window, cx| {
|
|
||||||
this.replace_all(&ReplaceAll, window, cx)
|
|
||||||
})),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
});
|
|
||||||
|
|
||||||
let query_error_line = self.query_error.as_ref().map(|error| {
|
let query_error_line = self.query_error.as_ref().map(|error| {
|
||||||
Label::new(error)
|
Label::new(error)
|
||||||
|
@ -491,10 +397,26 @@ impl Render for BufferSearchBar {
|
||||||
.ml_2()
|
.ml_2()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let search_line =
|
||||||
|
h_flex()
|
||||||
|
.relative()
|
||||||
|
.child(search_line)
|
||||||
|
.when(!narrow_mode && !find_in_results, |div| {
|
||||||
|
div.child(h_flex().absolute().right_0().child(render_action_button(
|
||||||
|
"buffer-search",
|
||||||
|
IconName::Close,
|
||||||
|
true,
|
||||||
|
"Close Search Bar",
|
||||||
|
&Dismiss,
|
||||||
|
focus_handle.clone(),
|
||||||
|
)))
|
||||||
|
.w_full()
|
||||||
|
});
|
||||||
v_flex()
|
v_flex()
|
||||||
.id("buffer_search")
|
.id("buffer_search")
|
||||||
.gap_2()
|
.gap_2()
|
||||||
.py(px(1.0))
|
.py(px(1.0))
|
||||||
|
.w_full()
|
||||||
.track_scroll(&self.scroll_handle)
|
.track_scroll(&self.scroll_handle)
|
||||||
.key_context(key_context)
|
.key_context(key_context)
|
||||||
.capture_action(cx.listener(Self::tab))
|
.capture_action(cx.listener(Self::tab))
|
||||||
|
@ -509,43 +431,26 @@ impl Render for BufferSearchBar {
|
||||||
active_searchable_item.relay_action(Box::new(ToggleOutline), window, cx);
|
active_searchable_item.relay_action(Box::new(ToggleOutline), window, cx);
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
.when(self.supported_options(cx).replacement, |this| {
|
.when(replacement, |this| {
|
||||||
this.on_action(cx.listener(Self::toggle_replace))
|
this.on_action(cx.listener(Self::toggle_replace))
|
||||||
.when(in_replace, |this| {
|
.when(in_replace, |this| {
|
||||||
this.on_action(cx.listener(Self::replace_next))
|
this.on_action(cx.listener(Self::replace_next))
|
||||||
.on_action(cx.listener(Self::replace_all))
|
.on_action(cx.listener(Self::replace_all))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.when(self.supported_options(cx).case, |this| {
|
.when(case, |this| {
|
||||||
this.on_action(cx.listener(Self::toggle_case_sensitive))
|
this.on_action(cx.listener(Self::toggle_case_sensitive))
|
||||||
})
|
})
|
||||||
.when(self.supported_options(cx).word, |this| {
|
.when(word, |this| {
|
||||||
this.on_action(cx.listener(Self::toggle_whole_word))
|
this.on_action(cx.listener(Self::toggle_whole_word))
|
||||||
})
|
})
|
||||||
.when(self.supported_options(cx).regex, |this| {
|
.when(regex, |this| {
|
||||||
this.on_action(cx.listener(Self::toggle_regex))
|
this.on_action(cx.listener(Self::toggle_regex))
|
||||||
})
|
})
|
||||||
.when(self.supported_options(cx).selection, |this| {
|
.when(selection, |this| {
|
||||||
this.on_action(cx.listener(Self::toggle_selection))
|
this.on_action(cx.listener(Self::toggle_selection))
|
||||||
})
|
})
|
||||||
.child(h_flex().relative().child(search_line.w_full()).when(
|
.child(search_line)
|
||||||
!narrow_mode && !supported_options.find_in_results,
|
|
||||||
|div| {
|
|
||||||
div.child(
|
|
||||||
h_flex().absolute().right_0().child(
|
|
||||||
IconButton::new(SharedString::from("Close"), IconName::Close)
|
|
||||||
.shape(IconButtonShape::Square)
|
|
||||||
.tooltip(move |window, cx| {
|
|
||||||
Tooltip::for_action("Close Search Bar", &Dismiss, window, cx)
|
|
||||||
})
|
|
||||||
.on_click(cx.listener(|this, _: &ClickEvent, window, cx| {
|
|
||||||
this.dismiss(&Dismiss, window, cx)
|
|
||||||
})),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.w_full()
|
|
||||||
},
|
|
||||||
))
|
|
||||||
.children(query_error_line)
|
.children(query_error_line)
|
||||||
.children(replace_line)
|
.children(replace_line)
|
||||||
}
|
}
|
||||||
|
@ -792,7 +697,7 @@ impl BufferSearchBar {
|
||||||
active_editor.search_bar_visibility_changed(false, window, cx);
|
active_editor.search_bar_visibility_changed(false, window, cx);
|
||||||
active_editor.toggle_filtered_search_ranges(false, window, cx);
|
active_editor.toggle_filtered_search_ranges(false, window, cx);
|
||||||
let handle = active_editor.item_focus_handle(cx);
|
let handle = active_editor.item_focus_handle(cx);
|
||||||
self.focus(&handle, window, cx);
|
self.focus(&handle, window);
|
||||||
}
|
}
|
||||||
cx.emit(Event::UpdateLocation);
|
cx.emit(Event::UpdateLocation);
|
||||||
cx.emit(ToolbarItemEvent::ChangeLocation(
|
cx.emit(ToolbarItemEvent::ChangeLocation(
|
||||||
|
@ -948,7 +853,7 @@ impl BufferSearchBar {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn focus_replace(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
pub fn focus_replace(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
self.focus(&self.replacement_editor.focus_handle(cx), window, cx);
|
self.focus(&self.replacement_editor.focus_handle(cx), window);
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -975,16 +880,6 @@ impl BufferSearchBar {
|
||||||
self.update_matches(!updated, window, cx)
|
self.update_matches(!updated, window, cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_search_option_button<Action: Fn(&ClickEvent, &mut Window, &mut App) + 'static>(
|
|
||||||
&self,
|
|
||||||
option: SearchOptions,
|
|
||||||
focus_handle: FocusHandle,
|
|
||||||
action: Action,
|
|
||||||
) -> impl IntoElement + use<Action> {
|
|
||||||
let is_active = self.search_options.contains(option);
|
|
||||||
option.as_button(is_active, focus_handle, action)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn focus_editor(&mut self, _: &FocusEditor, window: &mut Window, cx: &mut Context<Self>) {
|
pub fn focus_editor(&mut self, _: &FocusEditor, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
if let Some(active_editor) = self.active_searchable_item.as_ref() {
|
if let Some(active_editor) = self.active_searchable_item.as_ref() {
|
||||||
let handle = active_editor.item_focus_handle(cx);
|
let handle = active_editor.item_focus_handle(cx);
|
||||||
|
@ -1400,28 +1295,32 @@ impl BufferSearchBar {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
|
fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
// Search -> Replace -> Editor
|
self.cycle_field(Direction::Next, window, cx);
|
||||||
let focus_handle = if self.replace_enabled && self.query_editor_focused {
|
|
||||||
self.replacement_editor.focus_handle(cx)
|
|
||||||
} else if let Some(item) = self.active_searchable_item.as_ref() {
|
|
||||||
item.item_focus_handle(cx)
|
|
||||||
} else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
self.focus(&focus_handle, window, cx);
|
|
||||||
cx.stop_propagation();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
|
fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
// Search -> Replace -> Search
|
self.cycle_field(Direction::Prev, window, cx);
|
||||||
let focus_handle = if self.replace_enabled && self.query_editor_focused {
|
}
|
||||||
self.replacement_editor.focus_handle(cx)
|
fn cycle_field(&mut self, direction: Direction, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
} else if self.replacement_editor_focused {
|
let mut handles = vec![self.query_editor.focus_handle(cx)];
|
||||||
self.query_editor.focus_handle(cx)
|
if self.replace_enabled {
|
||||||
} else {
|
handles.push(self.replacement_editor.focus_handle(cx));
|
||||||
return;
|
}
|
||||||
|
if let Some(item) = self.active_searchable_item.as_ref() {
|
||||||
|
handles.push(item.item_focus_handle(cx));
|
||||||
|
}
|
||||||
|
let current_index = match handles.iter().position(|focus| focus.is_focused(window)) {
|
||||||
|
Some(index) => index,
|
||||||
|
None => return,
|
||||||
};
|
};
|
||||||
self.focus(&focus_handle, window, cx);
|
|
||||||
|
let new_index = match direction {
|
||||||
|
Direction::Next => (current_index + 1) % handles.len(),
|
||||||
|
Direction::Prev if current_index == 0 => handles.len() - 1,
|
||||||
|
Direction::Prev => (current_index - 1) % handles.len(),
|
||||||
|
};
|
||||||
|
let next_focus_handle = &handles[new_index];
|
||||||
|
self.focus(next_focus_handle, window);
|
||||||
cx.stop_propagation();
|
cx.stop_propagation();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1469,10 +1368,8 @@ impl BufferSearchBar {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn focus(&self, handle: &gpui::FocusHandle, window: &mut Window, cx: &mut Context<Self>) {
|
fn focus(&self, handle: &gpui::FocusHandle, window: &mut Window) {
|
||||||
cx.on_next_frame(window, |_, window, _| {
|
window.invalidate_character_coordinates();
|
||||||
window.invalidate_character_coordinates();
|
|
||||||
});
|
|
||||||
window.focus(handle);
|
window.focus(handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1484,7 +1381,7 @@ impl BufferSearchBar {
|
||||||
} else {
|
} else {
|
||||||
self.query_editor.focus_handle(cx)
|
self.query_editor.focus_handle(cx)
|
||||||
};
|
};
|
||||||
self.focus(&handle, window, cx);
|
self.focus(&handle, window);
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,20 +1,25 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
BufferSearchBar, FocusSearch, NextHistoryQuery, PreviousHistoryQuery, ReplaceAll, ReplaceNext,
|
BufferSearchBar, FocusSearch, NextHistoryQuery, PreviousHistoryQuery, ReplaceAll, ReplaceNext,
|
||||||
SearchOptions, SelectNextMatch, SelectPreviousMatch, ToggleCaseSensitive, ToggleIncludeIgnored,
|
SearchOptions, SelectNextMatch, SelectPreviousMatch, ToggleCaseSensitive, ToggleIncludeIgnored,
|
||||||
ToggleRegex, ToggleReplace, ToggleWholeWord, buffer_search::Deploy,
|
ToggleRegex, ToggleReplace, ToggleWholeWord,
|
||||||
|
buffer_search::Deploy,
|
||||||
|
search_bar::{
|
||||||
|
input_base_styles, render_action_button, render_text_input, toggle_replace_button,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
use anyhow::Context as _;
|
use anyhow::Context as _;
|
||||||
use collections::{HashMap, HashSet};
|
use collections::HashMap;
|
||||||
use editor::{
|
use editor::{
|
||||||
Anchor, Editor, EditorElement, EditorEvent, EditorSettings, EditorStyle, MAX_TAB_TITLE_LEN,
|
Anchor, Editor, EditorEvent, EditorSettings, MAX_TAB_TITLE_LEN, MultiBuffer, SelectionEffects,
|
||||||
MultiBuffer, SelectionEffects, actions::SelectAll, items::active_match_index,
|
actions::{Backtab, SelectAll, Tab},
|
||||||
|
items::active_match_index,
|
||||||
};
|
};
|
||||||
use futures::{StreamExt, stream::FuturesOrdered};
|
use futures::{StreamExt, stream::FuturesOrdered};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
Action, AnyElement, AnyView, App, Axis, Context, Entity, EntityId, EventEmitter, FocusHandle,
|
Action, AnyElement, AnyView, App, Axis, Context, Entity, EntityId, EventEmitter, FocusHandle,
|
||||||
Focusable, Global, Hsla, InteractiveElement, IntoElement, KeyContext, ParentElement, Point,
|
Focusable, Global, Hsla, InteractiveElement, IntoElement, KeyContext, ParentElement, Point,
|
||||||
Render, SharedString, Styled, Subscription, Task, TextStyle, UpdateGlobal, WeakEntity, Window,
|
Render, SharedString, Styled, Subscription, Task, UpdateGlobal, WeakEntity, Window, actions,
|
||||||
actions, div,
|
div,
|
||||||
};
|
};
|
||||||
use language::{Buffer, Language};
|
use language::{Buffer, Language};
|
||||||
use menu::Confirm;
|
use menu::Confirm;
|
||||||
|
@ -32,7 +37,6 @@ use std::{
|
||||||
pin::pin,
|
pin::pin,
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
};
|
};
|
||||||
use theme::ThemeSettings;
|
|
||||||
use ui::{
|
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,
|
||||||
|
@ -208,7 +212,7 @@ pub struct ProjectSearchView {
|
||||||
replacement_editor: Entity<Editor>,
|
replacement_editor: Entity<Editor>,
|
||||||
results_editor: Entity<Editor>,
|
results_editor: Entity<Editor>,
|
||||||
search_options: SearchOptions,
|
search_options: SearchOptions,
|
||||||
panels_with_errors: HashSet<InputPanel>,
|
panels_with_errors: HashMap<InputPanel, String>,
|
||||||
active_match_index: Option<usize>,
|
active_match_index: Option<usize>,
|
||||||
search_id: usize,
|
search_id: usize,
|
||||||
included_files_editor: Entity<Editor>,
|
included_files_editor: Entity<Editor>,
|
||||||
|
@ -218,7 +222,6 @@ pub struct ProjectSearchView {
|
||||||
included_opened_only: bool,
|
included_opened_only: bool,
|
||||||
regex_language: Option<Arc<Language>>,
|
regex_language: Option<Arc<Language>>,
|
||||||
_subscriptions: Vec<Subscription>,
|
_subscriptions: Vec<Subscription>,
|
||||||
query_error: Option<String>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
@ -879,7 +882,7 @@ impl ProjectSearchView {
|
||||||
query_editor,
|
query_editor,
|
||||||
results_editor,
|
results_editor,
|
||||||
search_options: options,
|
search_options: options,
|
||||||
panels_with_errors: HashSet::default(),
|
panels_with_errors: HashMap::default(),
|
||||||
active_match_index: None,
|
active_match_index: None,
|
||||||
included_files_editor,
|
included_files_editor,
|
||||||
excluded_files_editor,
|
excluded_files_editor,
|
||||||
|
@ -888,7 +891,6 @@ impl ProjectSearchView {
|
||||||
included_opened_only: false,
|
included_opened_only: false,
|
||||||
regex_language: None,
|
regex_language: None,
|
||||||
_subscriptions: subscriptions,
|
_subscriptions: subscriptions,
|
||||||
query_error: None,
|
|
||||||
};
|
};
|
||||||
this.entity_changed(window, cx);
|
this.entity_changed(window, cx);
|
||||||
this
|
this
|
||||||
|
@ -1152,14 +1154,16 @@ impl ProjectSearchView {
|
||||||
Ok(included_files) => {
|
Ok(included_files) => {
|
||||||
let should_unmark_error =
|
let should_unmark_error =
|
||||||
self.panels_with_errors.remove(&InputPanel::Include);
|
self.panels_with_errors.remove(&InputPanel::Include);
|
||||||
if should_unmark_error {
|
if should_unmark_error.is_some() {
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
included_files
|
included_files
|
||||||
}
|
}
|
||||||
Err(_e) => {
|
Err(e) => {
|
||||||
let should_mark_error = self.panels_with_errors.insert(InputPanel::Include);
|
let should_mark_error = self
|
||||||
if should_mark_error {
|
.panels_with_errors
|
||||||
|
.insert(InputPanel::Include, e.to_string());
|
||||||
|
if should_mark_error.is_none() {
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
PathMatcher::default()
|
PathMatcher::default()
|
||||||
|
@ -1174,15 +1178,17 @@ impl ProjectSearchView {
|
||||||
Ok(excluded_files) => {
|
Ok(excluded_files) => {
|
||||||
let should_unmark_error =
|
let should_unmark_error =
|
||||||
self.panels_with_errors.remove(&InputPanel::Exclude);
|
self.panels_with_errors.remove(&InputPanel::Exclude);
|
||||||
if should_unmark_error {
|
if should_unmark_error.is_some() {
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
excluded_files
|
excluded_files
|
||||||
}
|
}
|
||||||
Err(_e) => {
|
Err(e) => {
|
||||||
let should_mark_error = self.panels_with_errors.insert(InputPanel::Exclude);
|
let should_mark_error = self
|
||||||
if should_mark_error {
|
.panels_with_errors
|
||||||
|
.insert(InputPanel::Exclude, e.to_string());
|
||||||
|
if should_mark_error.is_none() {
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
PathMatcher::default()
|
PathMatcher::default()
|
||||||
|
@ -1219,19 +1225,19 @@ impl ProjectSearchView {
|
||||||
) {
|
) {
|
||||||
Ok(query) => {
|
Ok(query) => {
|
||||||
let should_unmark_error = self.panels_with_errors.remove(&InputPanel::Query);
|
let should_unmark_error = self.panels_with_errors.remove(&InputPanel::Query);
|
||||||
if should_unmark_error {
|
if should_unmark_error.is_some() {
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
self.query_error = None;
|
|
||||||
|
|
||||||
Some(query)
|
Some(query)
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
let should_mark_error = self.panels_with_errors.insert(InputPanel::Query);
|
let should_mark_error = self
|
||||||
if should_mark_error {
|
.panels_with_errors
|
||||||
|
.insert(InputPanel::Query, e.to_string());
|
||||||
|
if should_mark_error.is_none() {
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
self.query_error = Some(e.to_string());
|
|
||||||
|
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
@ -1249,15 +1255,17 @@ impl ProjectSearchView {
|
||||||
) {
|
) {
|
||||||
Ok(query) => {
|
Ok(query) => {
|
||||||
let should_unmark_error = self.panels_with_errors.remove(&InputPanel::Query);
|
let should_unmark_error = self.panels_with_errors.remove(&InputPanel::Query);
|
||||||
if should_unmark_error {
|
if should_unmark_error.is_some() {
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(query)
|
Some(query)
|
||||||
}
|
}
|
||||||
Err(_e) => {
|
Err(e) => {
|
||||||
let should_mark_error = self.panels_with_errors.insert(InputPanel::Query);
|
let should_mark_error = self
|
||||||
if should_mark_error {
|
.panels_with_errors
|
||||||
|
.insert(InputPanel::Query, e.to_string());
|
||||||
|
if should_mark_error.is_none() {
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1512,7 +1520,7 @@ impl ProjectSearchView {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn border_color_for(&self, panel: InputPanel, cx: &App) -> Hsla {
|
fn border_color_for(&self, panel: InputPanel, cx: &App) -> Hsla {
|
||||||
if self.panels_with_errors.contains(&panel) {
|
if self.panels_with_errors.contains_key(&panel) {
|
||||||
Color::Error.color(cx)
|
Color::Error.color(cx)
|
||||||
} else {
|
} else {
|
||||||
cx.theme().colors().border
|
cx.theme().colors().border
|
||||||
|
@ -1610,16 +1618,11 @@ impl ProjectSearchBar {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tab(&mut self, _: &editor::actions::Tab, window: &mut Window, cx: &mut Context<Self>) {
|
fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
self.cycle_field(Direction::Next, window, cx);
|
self.cycle_field(Direction::Next, window, cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn backtab(
|
fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
&mut self,
|
|
||||||
_: &editor::actions::Backtab,
|
|
||||||
window: &mut Window,
|
|
||||||
cx: &mut Context<Self>,
|
|
||||||
) {
|
|
||||||
self.cycle_field(Direction::Prev, window, cx);
|
self.cycle_field(Direction::Prev, window, cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1634,29 +1637,22 @@ impl ProjectSearchBar {
|
||||||
fn cycle_field(&mut self, direction: Direction, window: &mut Window, cx: &mut Context<Self>) {
|
fn cycle_field(&mut self, direction: Direction, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
let active_project_search = match &self.active_project_search {
|
let active_project_search = match &self.active_project_search {
|
||||||
Some(active_project_search) => active_project_search,
|
Some(active_project_search) => active_project_search,
|
||||||
|
None => return,
|
||||||
None => {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
active_project_search.update(cx, |project_view, cx| {
|
active_project_search.update(cx, |project_view, cx| {
|
||||||
let mut views = vec![&project_view.query_editor];
|
let mut views = vec![project_view.query_editor.focus_handle(cx)];
|
||||||
if project_view.replace_enabled {
|
if project_view.replace_enabled {
|
||||||
views.push(&project_view.replacement_editor);
|
views.push(project_view.replacement_editor.focus_handle(cx));
|
||||||
}
|
}
|
||||||
if project_view.filters_enabled {
|
if project_view.filters_enabled {
|
||||||
views.extend([
|
views.extend([
|
||||||
&project_view.included_files_editor,
|
project_view.included_files_editor.focus_handle(cx),
|
||||||
&project_view.excluded_files_editor,
|
project_view.excluded_files_editor.focus_handle(cx),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
let current_index = match views
|
let current_index = match views.iter().position(|focus| focus.is_focused(window)) {
|
||||||
.iter()
|
Some(index) => index,
|
||||||
.enumerate()
|
|
||||||
.find(|(_, editor)| editor.focus_handle(cx).is_focused(window))
|
|
||||||
{
|
|
||||||
Some((index, _)) => index,
|
|
||||||
None => return,
|
None => return,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1665,8 +1661,8 @@ impl ProjectSearchBar {
|
||||||
Direction::Prev if current_index == 0 => views.len() - 1,
|
Direction::Prev if current_index == 0 => views.len() - 1,
|
||||||
Direction::Prev => (current_index - 1) % views.len(),
|
Direction::Prev => (current_index - 1) % views.len(),
|
||||||
};
|
};
|
||||||
let next_focus_handle = views[new_index].focus_handle(cx);
|
let next_focus_handle = &views[new_index];
|
||||||
window.focus(&next_focus_handle);
|
window.focus(next_focus_handle);
|
||||||
cx.stop_propagation();
|
cx.stop_propagation();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1915,37 +1911,6 @@ impl ProjectSearchBar {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_text_input(&self, editor: &Entity<Editor>, cx: &Context<Self>) -> impl IntoElement {
|
|
||||||
let (color, use_syntax) = if editor.read(cx).read_only(cx) {
|
|
||||||
(cx.theme().colors().text_disabled, false)
|
|
||||||
} else {
|
|
||||||
(cx.theme().colors().text, true)
|
|
||||||
};
|
|
||||||
let settings = ThemeSettings::get_global(cx);
|
|
||||||
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: cx.theme().colors().toolbar_background,
|
|
||||||
local_player: cx.theme().players().local(),
|
|
||||||
text: text_style,
|
|
||||||
..EditorStyle::default()
|
|
||||||
};
|
|
||||||
if use_syntax {
|
|
||||||
editor_style.syntax = cx.theme().syntax().clone();
|
|
||||||
}
|
|
||||||
|
|
||||||
EditorElement::new(editor, editor_style)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Render for ProjectSearchBar {
|
impl Render for ProjectSearchBar {
|
||||||
|
@ -1959,28 +1924,43 @@ impl Render for ProjectSearchBar {
|
||||||
let container_width = window.viewport_size().width;
|
let container_width = window.viewport_size().width;
|
||||||
let input_width = SearchInputWidth::calc_width(container_width);
|
let input_width = SearchInputWidth::calc_width(container_width);
|
||||||
|
|
||||||
enum BaseStyle {
|
let input_base_styles = |panel: InputPanel| {
|
||||||
SingleInput,
|
input_base_styles(search.border_color_for(panel, cx), |div| match panel {
|
||||||
MultipleInputs,
|
InputPanel::Query | InputPanel::Replacement => div.w(input_width),
|
||||||
}
|
InputPanel::Include | InputPanel::Exclude => div.flex_grow(),
|
||||||
|
})
|
||||||
let input_base_styles = |base_style: BaseStyle, panel: InputPanel| {
|
|
||||||
h_flex()
|
|
||||||
.min_w_32()
|
|
||||||
.map(|div| match base_style {
|
|
||||||
BaseStyle::SingleInput => div.w(input_width),
|
|
||||||
BaseStyle::MultipleInputs => div.flex_grow(),
|
|
||||||
})
|
|
||||||
.h_8()
|
|
||||||
.pl_2()
|
|
||||||
.pr_1()
|
|
||||||
.py_1()
|
|
||||||
.border_1()
|
|
||||||
.border_color(search.border_color_for(panel, cx))
|
|
||||||
.rounded_lg()
|
|
||||||
};
|
};
|
||||||
|
let theme_colors = cx.theme().colors();
|
||||||
|
let project_search = search.entity.read(cx);
|
||||||
|
let limit_reached = project_search.limit_reached;
|
||||||
|
|
||||||
let query_column = input_base_styles(BaseStyle::SingleInput, InputPanel::Query)
|
let color_override = match (
|
||||||
|
project_search.no_results,
|
||||||
|
&project_search.active_query,
|
||||||
|
&project_search.last_search_query_text,
|
||||||
|
) {
|
||||||
|
(Some(true), Some(q), Some(p)) if q.as_str() == p => Some(Color::Error),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
let match_text = search
|
||||||
|
.active_match_index
|
||||||
|
.and_then(|index| {
|
||||||
|
let index = index + 1;
|
||||||
|
let match_quantity = project_search.match_ranges.len();
|
||||||
|
if match_quantity > 0 {
|
||||||
|
debug_assert!(match_quantity >= index);
|
||||||
|
if limit_reached {
|
||||||
|
Some(format!("{index}/{match_quantity}+"))
|
||||||
|
} else {
|
||||||
|
Some(format!("{index}/{match_quantity}"))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.unwrap_or_else(|| "0/0".to_string());
|
||||||
|
|
||||||
|
let query_column = input_base_styles(InputPanel::Query)
|
||||||
.on_action(cx.listener(|this, action, window, cx| this.confirm(action, window, cx)))
|
.on_action(cx.listener(|this, action, window, cx| this.confirm(action, window, cx)))
|
||||||
.on_action(cx.listener(|this, action, window, cx| {
|
.on_action(cx.listener(|this, action, window, cx| {
|
||||||
this.previous_history_query(action, window, cx)
|
this.previous_history_query(action, window, cx)
|
||||||
|
@ -1988,7 +1968,7 @@ impl Render for ProjectSearchBar {
|
||||||
.on_action(
|
.on_action(
|
||||||
cx.listener(|this, action, window, cx| this.next_history_query(action, window, cx)),
|
cx.listener(|this, action, window, cx| this.next_history_query(action, window, cx)),
|
||||||
)
|
)
|
||||||
.child(self.render_text_input(&search.query_editor, cx))
|
.child(render_text_input(&search.query_editor, color_override, cx))
|
||||||
.child(
|
.child(
|
||||||
h_flex()
|
h_flex()
|
||||||
.gap_1()
|
.gap_1()
|
||||||
|
@ -2017,6 +1997,7 @@ impl Render for ProjectSearchBar {
|
||||||
|
|
||||||
let mode_column = h_flex()
|
let mode_column = h_flex()
|
||||||
.gap_1()
|
.gap_1()
|
||||||
|
.min_w_64()
|
||||||
.child(
|
.child(
|
||||||
IconButton::new("project-search-filter-button", IconName::Filter)
|
IconButton::new("project-search-filter-button", IconName::Filter)
|
||||||
.shape(IconButtonShape::Square)
|
.shape(IconButtonShape::Square)
|
||||||
|
@ -2045,109 +2026,46 @@ impl Render for ProjectSearchBar {
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.child(
|
.child(toggle_replace_button(
|
||||||
IconButton::new("project-search-toggle-replace", IconName::Replace)
|
"project-search-toggle-replace",
|
||||||
.shape(IconButtonShape::Square)
|
focus_handle.clone(),
|
||||||
.on_click(cx.listener(|this, _, window, cx| {
|
self.active_project_search
|
||||||
this.toggle_replace(&ToggleReplace, window, cx);
|
.as_ref()
|
||||||
}))
|
.map(|search| search.read(cx).replace_enabled)
|
||||||
.toggle_state(
|
.unwrap_or_default(),
|
||||||
self.active_project_search
|
cx.listener(|this, _, window, cx| {
|
||||||
.as_ref()
|
this.toggle_replace(&ToggleReplace, window, cx);
|
||||||
.map(|search| search.read(cx).replace_enabled)
|
}),
|
||||||
.unwrap_or_default(),
|
));
|
||||||
)
|
|
||||||
.tooltip({
|
|
||||||
let focus_handle = focus_handle.clone();
|
|
||||||
move |window, cx| {
|
|
||||||
Tooltip::for_action_in(
|
|
||||||
"Toggle Replace",
|
|
||||||
&ToggleReplace,
|
|
||||||
&focus_handle,
|
|
||||||
window,
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
let limit_reached = search.entity.read(cx).limit_reached;
|
let query_focus = search.query_editor.focus_handle(cx);
|
||||||
|
|
||||||
let match_text = search
|
|
||||||
.active_match_index
|
|
||||||
.and_then(|index| {
|
|
||||||
let index = index + 1;
|
|
||||||
let match_quantity = search.entity.read(cx).match_ranges.len();
|
|
||||||
if match_quantity > 0 {
|
|
||||||
debug_assert!(match_quantity >= index);
|
|
||||||
if limit_reached {
|
|
||||||
Some(format!("{index}/{match_quantity}+"))
|
|
||||||
} else {
|
|
||||||
Some(format!("{index}/{match_quantity}"))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.unwrap_or_else(|| "0/0".to_string());
|
|
||||||
|
|
||||||
let matches_column = h_flex()
|
let matches_column = h_flex()
|
||||||
.pl_2()
|
.pl_2()
|
||||||
.ml_2()
|
.ml_2()
|
||||||
.border_l_1()
|
.border_l_1()
|
||||||
.border_color(cx.theme().colors().border_variant)
|
.border_color(theme_colors.border_variant)
|
||||||
.child(
|
.child(render_action_button(
|
||||||
IconButton::new("project-search-prev-match", IconName::ChevronLeft)
|
"project-search-nav-button",
|
||||||
.shape(IconButtonShape::Square)
|
IconName::ChevronLeft,
|
||||||
.disabled(search.active_match_index.is_none())
|
search.active_match_index.is_some(),
|
||||||
.on_click(cx.listener(|this, _, window, cx| {
|
"Select Previous Match",
|
||||||
if let Some(search) = this.active_project_search.as_ref() {
|
&SelectPreviousMatch,
|
||||||
search.update(cx, |this, cx| {
|
query_focus.clone(),
|
||||||
this.select_match(Direction::Prev, window, cx);
|
))
|
||||||
})
|
.child(render_action_button(
|
||||||
}
|
"project-search-nav-button",
|
||||||
}))
|
IconName::ChevronRight,
|
||||||
.tooltip({
|
search.active_match_index.is_some(),
|
||||||
let focus_handle = focus_handle.clone();
|
"Select Next Match",
|
||||||
move |window, cx| {
|
&SelectNextMatch,
|
||||||
Tooltip::for_action_in(
|
query_focus,
|
||||||
"Go To Previous Match",
|
))
|
||||||
&SelectPreviousMatch,
|
|
||||||
&focus_handle,
|
|
||||||
window,
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
IconButton::new("project-search-next-match", IconName::ChevronRight)
|
|
||||||
.shape(IconButtonShape::Square)
|
|
||||||
.disabled(search.active_match_index.is_none())
|
|
||||||
.on_click(cx.listener(|this, _, window, cx| {
|
|
||||||
if let Some(search) = this.active_project_search.as_ref() {
|
|
||||||
search.update(cx, |this, cx| {
|
|
||||||
this.select_match(Direction::Next, window, cx);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
.tooltip({
|
|
||||||
let focus_handle = focus_handle.clone();
|
|
||||||
move |window, cx| {
|
|
||||||
Tooltip::for_action_in(
|
|
||||||
"Go To Next Match",
|
|
||||||
&SelectNextMatch,
|
|
||||||
&focus_handle,
|
|
||||||
window,
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
.id("matches")
|
.id("matches")
|
||||||
.ml_1()
|
.ml_2()
|
||||||
|
.min_w(rems_from_px(40.))
|
||||||
.child(Label::new(match_text).size(LabelSize::Small).color(
|
.child(Label::new(match_text).size(LabelSize::Small).color(
|
||||||
if search.active_match_index.is_some() {
|
if search.active_match_index.is_some() {
|
||||||
Color::Default
|
Color::Default
|
||||||
|
@ -2169,63 +2087,30 @@ impl Render for ProjectSearchBar {
|
||||||
.child(h_flex().min_w_64().child(mode_column).child(matches_column));
|
.child(h_flex().min_w_64().child(mode_column).child(matches_column));
|
||||||
|
|
||||||
let replace_line = search.replace_enabled.then(|| {
|
let replace_line = search.replace_enabled.then(|| {
|
||||||
let replace_column = input_base_styles(BaseStyle::SingleInput, InputPanel::Replacement)
|
let replace_column = input_base_styles(InputPanel::Replacement)
|
||||||
.child(self.render_text_input(&search.replacement_editor, cx));
|
.child(render_text_input(&search.replacement_editor, None, cx));
|
||||||
|
|
||||||
let focus_handle = search.replacement_editor.read(cx).focus_handle(cx);
|
let focus_handle = search.replacement_editor.read(cx).focus_handle(cx);
|
||||||
|
|
||||||
let replace_actions =
|
let replace_actions = h_flex()
|
||||||
h_flex()
|
.min_w_64()
|
||||||
.min_w_64()
|
.gap_1()
|
||||||
.gap_1()
|
.child(render_action_button(
|
||||||
.when(search.replace_enabled, |this| {
|
"project-search-replace-button",
|
||||||
this.child(
|
IconName::ReplaceNext,
|
||||||
IconButton::new("project-search-replace-next", IconName::ReplaceNext)
|
true,
|
||||||
.shape(IconButtonShape::Square)
|
"Replace Next Match",
|
||||||
.on_click(cx.listener(|this, _, window, cx| {
|
&ReplaceNext,
|
||||||
if let Some(search) = this.active_project_search.as_ref() {
|
focus_handle.clone(),
|
||||||
search.update(cx, |this, cx| {
|
))
|
||||||
this.replace_next(&ReplaceNext, window, cx);
|
.child(render_action_button(
|
||||||
})
|
"project-search-replace-button",
|
||||||
}
|
IconName::ReplaceAll,
|
||||||
}))
|
true,
|
||||||
.tooltip({
|
"Replace All Matches",
|
||||||
let focus_handle = focus_handle.clone();
|
&ReplaceAll,
|
||||||
move |window, cx| {
|
focus_handle,
|
||||||
Tooltip::for_action_in(
|
));
|
||||||
"Replace Next Match",
|
|
||||||
&ReplaceNext,
|
|
||||||
&focus_handle,
|
|
||||||
window,
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
IconButton::new("project-search-replace-all", IconName::ReplaceAll)
|
|
||||||
.shape(IconButtonShape::Square)
|
|
||||||
.on_click(cx.listener(|this, _, window, cx| {
|
|
||||||
if let Some(search) = this.active_project_search.as_ref() {
|
|
||||||
search.update(cx, |this, cx| {
|
|
||||||
this.replace_all(&ReplaceAll, window, cx);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
.tooltip({
|
|
||||||
let focus_handle = focus_handle.clone();
|
|
||||||
move |window, cx| {
|
|
||||||
Tooltip::for_action_in(
|
|
||||||
"Replace All Matches",
|
|
||||||
&ReplaceAll,
|
|
||||||
&focus_handle,
|
|
||||||
window,
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
});
|
|
||||||
|
|
||||||
h_flex()
|
h_flex()
|
||||||
.w_full()
|
.w_full()
|
||||||
|
@ -2235,6 +2120,45 @@ impl Render for ProjectSearchBar {
|
||||||
});
|
});
|
||||||
|
|
||||||
let filter_line = search.filters_enabled.then(|| {
|
let filter_line = search.filters_enabled.then(|| {
|
||||||
|
let include = input_base_styles(InputPanel::Include)
|
||||||
|
.on_action(cx.listener(|this, action, window, cx| {
|
||||||
|
this.previous_history_query(action, window, cx)
|
||||||
|
}))
|
||||||
|
.on_action(cx.listener(|this, action, window, cx| {
|
||||||
|
this.next_history_query(action, window, cx)
|
||||||
|
}))
|
||||||
|
.child(render_text_input(&search.included_files_editor, None, cx));
|
||||||
|
let exclude = input_base_styles(InputPanel::Exclude)
|
||||||
|
.on_action(cx.listener(|this, action, window, cx| {
|
||||||
|
this.previous_history_query(action, window, cx)
|
||||||
|
}))
|
||||||
|
.on_action(cx.listener(|this, action, window, cx| {
|
||||||
|
this.next_history_query(action, window, cx)
|
||||||
|
}))
|
||||||
|
.child(render_text_input(&search.excluded_files_editor, None, cx));
|
||||||
|
let mode_column = h_flex()
|
||||||
|
.gap_1()
|
||||||
|
.min_w_64()
|
||||||
|
.child(
|
||||||
|
IconButton::new("project-search-opened-only", IconName::FolderSearch)
|
||||||
|
.shape(IconButtonShape::Square)
|
||||||
|
.toggle_state(self.is_opened_only_enabled(cx))
|
||||||
|
.tooltip(Tooltip::text("Only Search Open Files"))
|
||||||
|
.on_click(cx.listener(|this, _, window, cx| {
|
||||||
|
this.toggle_opened_only(window, cx);
|
||||||
|
})),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
SearchOptions::INCLUDE_IGNORED.as_button(
|
||||||
|
search
|
||||||
|
.search_options
|
||||||
|
.contains(SearchOptions::INCLUDE_IGNORED),
|
||||||
|
focus_handle.clone(),
|
||||||
|
cx.listener(|this, _, window, cx| {
|
||||||
|
this.toggle_search_option(SearchOptions::INCLUDE_IGNORED, window, cx);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
h_flex()
|
h_flex()
|
||||||
.w_full()
|
.w_full()
|
||||||
.gap_2()
|
.gap_2()
|
||||||
|
@ -2242,62 +2166,14 @@ impl Render for ProjectSearchBar {
|
||||||
h_flex()
|
h_flex()
|
||||||
.gap_2()
|
.gap_2()
|
||||||
.w(input_width)
|
.w(input_width)
|
||||||
.child(
|
.child(include)
|
||||||
input_base_styles(BaseStyle::MultipleInputs, InputPanel::Include)
|
.child(exclude),
|
||||||
.on_action(cx.listener(|this, action, window, cx| {
|
|
||||||
this.previous_history_query(action, window, cx)
|
|
||||||
}))
|
|
||||||
.on_action(cx.listener(|this, action, window, cx| {
|
|
||||||
this.next_history_query(action, window, cx)
|
|
||||||
}))
|
|
||||||
.child(self.render_text_input(&search.included_files_editor, cx)),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
input_base_styles(BaseStyle::MultipleInputs, InputPanel::Exclude)
|
|
||||||
.on_action(cx.listener(|this, action, window, cx| {
|
|
||||||
this.previous_history_query(action, window, cx)
|
|
||||||
}))
|
|
||||||
.on_action(cx.listener(|this, action, window, cx| {
|
|
||||||
this.next_history_query(action, window, cx)
|
|
||||||
}))
|
|
||||||
.child(self.render_text_input(&search.excluded_files_editor, cx)),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
h_flex()
|
|
||||||
.min_w_64()
|
|
||||||
.gap_1()
|
|
||||||
.child(
|
|
||||||
IconButton::new("project-search-opened-only", IconName::FolderSearch)
|
|
||||||
.shape(IconButtonShape::Square)
|
|
||||||
.toggle_state(self.is_opened_only_enabled(cx))
|
|
||||||
.tooltip(Tooltip::text("Only Search Open Files"))
|
|
||||||
.on_click(cx.listener(|this, _, window, cx| {
|
|
||||||
this.toggle_opened_only(window, cx);
|
|
||||||
})),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
SearchOptions::INCLUDE_IGNORED.as_button(
|
|
||||||
search
|
|
||||||
.search_options
|
|
||||||
.contains(SearchOptions::INCLUDE_IGNORED),
|
|
||||||
focus_handle.clone(),
|
|
||||||
cx.listener(|this, _, window, cx| {
|
|
||||||
this.toggle_search_option(
|
|
||||||
SearchOptions::INCLUDE_IGNORED,
|
|
||||||
window,
|
|
||||||
cx,
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
.child(mode_column)
|
||||||
});
|
});
|
||||||
|
|
||||||
let mut key_context = KeyContext::default();
|
let mut key_context = KeyContext::default();
|
||||||
|
|
||||||
key_context.add("ProjectSearchBar");
|
key_context.add("ProjectSearchBar");
|
||||||
|
|
||||||
if search
|
if search
|
||||||
.replacement_editor
|
.replacement_editor
|
||||||
.focus_handle(cx)
|
.focus_handle(cx)
|
||||||
|
@ -2306,16 +2182,33 @@ impl Render for ProjectSearchBar {
|
||||||
key_context.add("in_replace");
|
key_context.add("in_replace");
|
||||||
}
|
}
|
||||||
|
|
||||||
let query_error_line = search.query_error.as_ref().map(|error| {
|
let query_error_line = search
|
||||||
Label::new(error)
|
.panels_with_errors
|
||||||
.size(LabelSize::Small)
|
.get(&InputPanel::Query)
|
||||||
.color(Color::Error)
|
.map(|error| {
|
||||||
.mt_neg_1()
|
Label::new(error)
|
||||||
.ml_2()
|
.size(LabelSize::Small)
|
||||||
});
|
.color(Color::Error)
|
||||||
|
.mt_neg_1()
|
||||||
|
.ml_2()
|
||||||
|
});
|
||||||
|
|
||||||
|
let filter_error_line = search
|
||||||
|
.panels_with_errors
|
||||||
|
.get(&InputPanel::Include)
|
||||||
|
.or_else(|| search.panels_with_errors.get(&InputPanel::Exclude))
|
||||||
|
.map(|error| {
|
||||||
|
Label::new(error)
|
||||||
|
.size(LabelSize::Small)
|
||||||
|
.color(Color::Error)
|
||||||
|
.mt_neg_1()
|
||||||
|
.ml_2()
|
||||||
|
});
|
||||||
|
|
||||||
v_flex()
|
v_flex()
|
||||||
|
.gap_2()
|
||||||
.py(px(1.0))
|
.py(px(1.0))
|
||||||
|
.w_full()
|
||||||
.key_context(key_context)
|
.key_context(key_context)
|
||||||
.on_action(cx.listener(|this, _: &ToggleFocus, window, cx| {
|
.on_action(cx.listener(|this, _: &ToggleFocus, window, cx| {
|
||||||
this.move_focus_to_results(window, cx)
|
this.move_focus_to_results(window, cx)
|
||||||
|
@ -2323,14 +2216,8 @@ impl Render for ProjectSearchBar {
|
||||||
.on_action(cx.listener(|this, _: &ToggleFilters, window, cx| {
|
.on_action(cx.listener(|this, _: &ToggleFilters, window, cx| {
|
||||||
this.toggle_filters(window, cx);
|
this.toggle_filters(window, cx);
|
||||||
}))
|
}))
|
||||||
.capture_action(cx.listener(|this, action, window, cx| {
|
.capture_action(cx.listener(Self::tab))
|
||||||
this.tab(action, window, cx);
|
.capture_action(cx.listener(Self::backtab))
|
||||||
cx.stop_propagation();
|
|
||||||
}))
|
|
||||||
.capture_action(cx.listener(|this, action, window, cx| {
|
|
||||||
this.backtab(action, window, cx);
|
|
||||||
cx.stop_propagation();
|
|
||||||
}))
|
|
||||||
.on_action(cx.listener(|this, action, window, cx| this.confirm(action, window, cx)))
|
.on_action(cx.listener(|this, action, window, cx| this.confirm(action, window, cx)))
|
||||||
.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);
|
||||||
|
@ -2362,12 +2249,11 @@ impl Render for ProjectSearchBar {
|
||||||
})
|
})
|
||||||
.on_action(cx.listener(Self::select_next_match))
|
.on_action(cx.listener(Self::select_next_match))
|
||||||
.on_action(cx.listener(Self::select_prev_match))
|
.on_action(cx.listener(Self::select_prev_match))
|
||||||
.gap_2()
|
|
||||||
.w_full()
|
|
||||||
.child(search_line)
|
.child(search_line)
|
||||||
.children(query_error_line)
|
.children(query_error_line)
|
||||||
.children(replace_line)
|
.children(replace_line)
|
||||||
.children(filter_line)
|
.children(filter_line)
|
||||||
|
.children(filter_error_line)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,14 @@
|
||||||
use gpui::{Action, FocusHandle, IntoElement};
|
use editor::{Editor, EditorElement, EditorStyle};
|
||||||
|
use gpui::{Action, Entity, FocusHandle, Hsla, IntoElement, TextStyle};
|
||||||
|
use settings::Settings;
|
||||||
|
use theme::ThemeSettings;
|
||||||
use ui::{IconButton, IconButtonShape};
|
use ui::{IconButton, IconButtonShape};
|
||||||
use ui::{Tooltip, prelude::*};
|
use ui::{Tooltip, prelude::*};
|
||||||
|
|
||||||
pub(super) fn render_nav_button(
|
use crate::ToggleReplace;
|
||||||
|
|
||||||
|
pub(super) fn render_action_button(
|
||||||
|
id_prefix: &'static str,
|
||||||
icon: ui::IconName,
|
icon: ui::IconName,
|
||||||
active: bool,
|
active: bool,
|
||||||
tooltip: &'static str,
|
tooltip: &'static str,
|
||||||
|
@ -10,7 +16,7 @@ pub(super) fn render_nav_button(
|
||||||
focus_handle: FocusHandle,
|
focus_handle: FocusHandle,
|
||||||
) -> impl IntoElement {
|
) -> impl IntoElement {
|
||||||
IconButton::new(
|
IconButton::new(
|
||||||
SharedString::from(format!("search-nav-button-{}", action.name())),
|
SharedString::from(format!("{id_prefix}-{}", action.name())),
|
||||||
icon,
|
icon,
|
||||||
)
|
)
|
||||||
.shape(IconButtonShape::Square)
|
.shape(IconButtonShape::Square)
|
||||||
|
@ -26,3 +32,74 @@ pub(super) fn render_nav_button(
|
||||||
.tooltip(move |window, cx| Tooltip::for_action_in(tooltip, action, &focus_handle, window, cx))
|
.tooltip(move |window, cx| Tooltip::for_action_in(tooltip, action, &focus_handle, window, cx))
|
||||||
.disabled(!active)
|
.disabled(!active)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn input_base_styles(border_color: Hsla, map: impl FnOnce(Div) -> Div) -> Div {
|
||||||
|
h_flex()
|
||||||
|
.min_w_32()
|
||||||
|
.map(map)
|
||||||
|
.h_8()
|
||||||
|
.pl_2()
|
||||||
|
.pr_1()
|
||||||
|
.py_1()
|
||||||
|
.border_1()
|
||||||
|
.border_color(border_color)
|
||||||
|
.rounded_lg()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn toggle_replace_button(
|
||||||
|
id: &'static str,
|
||||||
|
focus_handle: FocusHandle,
|
||||||
|
replace_enabled: bool,
|
||||||
|
on_click: impl Fn(&gpui::ClickEvent, &mut Window, &mut App) + 'static,
|
||||||
|
) -> IconButton {
|
||||||
|
IconButton::new(id, IconName::Replace)
|
||||||
|
.shape(IconButtonShape::Square)
|
||||||
|
.style(ButtonStyle::Subtle)
|
||||||
|
.when(replace_enabled, |button| button.style(ButtonStyle::Filled))
|
||||||
|
.on_click(on_click)
|
||||||
|
.toggle_state(replace_enabled)
|
||||||
|
.tooltip({
|
||||||
|
move |window, cx| {
|
||||||
|
Tooltip::for_action_in("Toggle Replace", &ToggleReplace, &focus_handle, window, cx)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
|
@ -3878,9 +3878,7 @@ impl Workspace {
|
||||||
local,
|
local,
|
||||||
focus_changed,
|
focus_changed,
|
||||||
} => {
|
} => {
|
||||||
cx.on_next_frame(window, |_, window, _| {
|
window.invalidate_character_coordinates();
|
||||||
window.invalidate_character_coordinates();
|
|
||||||
});
|
|
||||||
|
|
||||||
pane.update(cx, |pane, _| {
|
pane.update(cx, |pane, _| {
|
||||||
pane.track_alternate_file_items();
|
pane.track_alternate_file_items();
|
||||||
|
@ -3921,9 +3919,7 @@ impl Workspace {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pane::Event::Focus => {
|
pane::Event::Focus => {
|
||||||
cx.on_next_frame(window, |_, window, _| {
|
window.invalidate_character_coordinates();
|
||||||
window.invalidate_character_coordinates();
|
|
||||||
});
|
|
||||||
self.handle_pane_focused(pane.clone(), window, cx);
|
self.handle_pane_focused(pane.clone(), window, cx);
|
||||||
}
|
}
|
||||||
pane::Event::ZoomIn => {
|
pane::Event::ZoomIn => {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue