Detailed changes
@@ -3,20 +3,23 @@ mod registrar;
use crate::{
FocusSearch, NextHistoryQuery, PreviousHistoryQuery, ReplaceAll, ReplaceNext, SearchOptions,
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 anyhow::Context as _;
use collections::HashMap;
use editor::{
- DisplayPoint, Editor, EditorElement, EditorSettings, EditorStyle,
+ DisplayPoint, Editor, EditorSettings,
actions::{Backtab, Tab},
};
use futures::channel::oneshot;
use gpui::{
- Action, App, ClickEvent, Context, Entity, EventEmitter, FocusHandle, Focusable,
- InteractiveElement as _, IntoElement, KeyContext, ParentElement as _, Render, ScrollHandle,
- Styled, Subscription, Task, TextStyle, Window, actions, div,
+ Action, App, ClickEvent, Context, Entity, EventEmitter, Focusable, InteractiveElement as _,
+ IntoElement, KeyContext, ParentElement as _, Render, ScrollHandle, Styled, Subscription, Task,
+ Window, actions, div,
};
use language::{Language, LanguageRegistry};
use project::{
@@ -27,7 +30,6 @@ use schemars::JsonSchema;
use serde::Deserialize;
use settings::Settings;
use std::sync::Arc;
-use theme::ThemeSettings;
use zed_actions::outline::ToggleOutline;
use ui::{
@@ -125,46 +127,6 @@ pub struct 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 {
self.query_editor_focused
}
@@ -185,7 +147,14 @@ impl Render for BufferSearchBar {
let hide_inline_icons = self.editor_needed_width
> 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| {
query_editor.placeholder_text().is_none()
@@ -220,268 +189,205 @@ impl Render for BufferSearchBar {
}
})
.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 mut key_context = KeyContext::new_with_defaults();
- key_context.add("BufferSearchBar");
- if in_replace {
- key_context.add("in_replace");
- }
+ let theme_colors = cx.theme().colors();
let query_border = if self.query_error.is_some() {
Color::Error.color(cx)
} else {
- cx.theme().colors().border
+ theme_colors.border
};
- let replacement_border = cx.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| {
- 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 input_base_styles =
+ |border_color| input_base_styles(border_color, |div| div.w(input_width));
- 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,
- )
- }
+ 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)
}),
- )
- .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(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)
})
- .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)
- })),
- )
+ .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,
+ )
+ }
}),
- );
-
- 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)
- })),
- ),
- )
- });
+ })
+ .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();
+ key_context.add("BufferSearchBar");
+ if in_replace {
+ key_context.add("in_replace");
+ }
let query_error_line = self.query_error.as_ref().map(|error| {
Label::new(error)
@@ -491,10 +397,26 @@ impl Render for BufferSearchBar {
.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()
.id("buffer_search")
.gap_2()
.py(px(1.0))
+ .w_full()
.track_scroll(&self.scroll_handle)
.key_context(key_context)
.capture_action(cx.listener(Self::tab))
@@ -509,43 +431,26 @@ impl Render for BufferSearchBar {
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))
.when(in_replace, |this| {
this.on_action(cx.listener(Self::replace_next))
.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))
})
- .when(self.supported_options(cx).word, |this| {
+ .when(word, |this| {
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))
})
- .when(self.supported_options(cx).selection, |this| {
+ .when(selection, |this| {
this.on_action(cx.listener(Self::toggle_selection))
})
- .child(h_flex().relative().child(search_line.w_full()).when(
- !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()
- },
- ))
+ .child(search_line)
.children(query_error_line)
.children(replace_line)
}
@@ -792,7 +697,7 @@ impl BufferSearchBar {
active_editor.search_bar_visibility_changed(false, window, cx);
active_editor.toggle_filtered_search_ranges(false, window, cx);
let handle = active_editor.item_focus_handle(cx);
- self.focus(&handle, window, cx);
+ self.focus(&handle, window);
}
cx.emit(Event::UpdateLocation);
cx.emit(ToolbarItemEvent::ChangeLocation(
@@ -948,7 +853,7 @@ impl BufferSearchBar {
}
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();
}
@@ -975,16 +880,6 @@ impl BufferSearchBar {
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>) {
if let Some(active_editor) = self.active_searchable_item.as_ref() {
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>) {
- // Search -> Replace -> Editor
- 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();
+ self.cycle_field(Direction::Next, window, cx);
}
fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
- // Search -> Replace -> Search
- let focus_handle = if self.replace_enabled && self.query_editor_focused {
- self.replacement_editor.focus_handle(cx)
- } else if self.replacement_editor_focused {
- self.query_editor.focus_handle(cx)
- } else {
- return;
+ self.cycle_field(Direction::Prev, window, cx);
+ }
+ fn cycle_field(&mut self, direction: Direction, window: &mut Window, cx: &mut Context<Self>) {
+ let mut handles = vec![self.query_editor.focus_handle(cx)];
+ if self.replace_enabled {
+ handles.push(self.replacement_editor.focus_handle(cx));
+ }
+ 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();
}
@@ -1469,10 +1368,8 @@ impl BufferSearchBar {
}
}
- fn focus(&self, handle: &gpui::FocusHandle, window: &mut Window, cx: &mut Context<Self>) {
- cx.on_next_frame(window, |_, window, _| {
- window.invalidate_character_coordinates();
- });
+ fn focus(&self, handle: &gpui::FocusHandle, window: &mut Window) {
+ window.invalidate_character_coordinates();
window.focus(handle);
}
@@ -1484,7 +1381,7 @@ impl BufferSearchBar {
} else {
self.query_editor.focus_handle(cx)
};
- self.focus(&handle, window, cx);
+ self.focus(&handle, window);
cx.notify();
}
}
@@ -1,20 +1,25 @@
use crate::{
BufferSearchBar, FocusSearch, NextHistoryQuery, PreviousHistoryQuery, ReplaceAll, ReplaceNext,
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 collections::{HashMap, HashSet};
+use collections::HashMap;
use editor::{
- Anchor, Editor, EditorElement, EditorEvent, EditorSettings, EditorStyle, MAX_TAB_TITLE_LEN,
- MultiBuffer, SelectionEffects, actions::SelectAll, items::active_match_index,
+ Anchor, Editor, EditorEvent, EditorSettings, MAX_TAB_TITLE_LEN, MultiBuffer, SelectionEffects,
+ actions::{Backtab, SelectAll, Tab},
+ items::active_match_index,
};
use futures::{StreamExt, stream::FuturesOrdered};
use gpui::{
Action, AnyElement, AnyView, App, Axis, Context, Entity, EntityId, EventEmitter, FocusHandle,
Focusable, Global, Hsla, InteractiveElement, IntoElement, KeyContext, ParentElement, Point,
- Render, SharedString, Styled, Subscription, Task, TextStyle, UpdateGlobal, WeakEntity, Window,
- actions, div,
+ Render, SharedString, Styled, Subscription, Task, UpdateGlobal, WeakEntity, Window, actions,
+ div,
};
use language::{Buffer, Language};
use menu::Confirm;
@@ -32,7 +37,6 @@ use std::{
pin::pin,
sync::Arc,
};
-use theme::ThemeSettings;
use ui::{
Icon, IconButton, IconButtonShape, IconName, KeyBinding, Label, LabelCommon, LabelSize,
Toggleable, Tooltip, h_flex, prelude::*, utils::SearchInputWidth, v_flex,
@@ -208,7 +212,7 @@ pub struct ProjectSearchView {
replacement_editor: Entity<Editor>,
results_editor: Entity<Editor>,
search_options: SearchOptions,
- panels_with_errors: HashSet<InputPanel>,
+ panels_with_errors: HashMap<InputPanel, String>,
active_match_index: Option<usize>,
search_id: usize,
included_files_editor: Entity<Editor>,
@@ -218,7 +222,6 @@ pub struct ProjectSearchView {
included_opened_only: bool,
regex_language: Option<Arc<Language>>,
_subscriptions: Vec<Subscription>,
- query_error: Option<String>,
}
#[derive(Debug, Clone)]
@@ -879,7 +882,7 @@ impl ProjectSearchView {
query_editor,
results_editor,
search_options: options,
- panels_with_errors: HashSet::default(),
+ panels_with_errors: HashMap::default(),
active_match_index: None,
included_files_editor,
excluded_files_editor,
@@ -888,7 +891,6 @@ impl ProjectSearchView {
included_opened_only: false,
regex_language: None,
_subscriptions: subscriptions,
- query_error: None,
};
this.entity_changed(window, cx);
this
@@ -1152,14 +1154,16 @@ impl ProjectSearchView {
Ok(included_files) => {
let should_unmark_error =
self.panels_with_errors.remove(&InputPanel::Include);
- if should_unmark_error {
+ if should_unmark_error.is_some() {
cx.notify();
}
included_files
}
- Err(_e) => {
- let should_mark_error = self.panels_with_errors.insert(InputPanel::Include);
- if should_mark_error {
+ Err(e) => {
+ let should_mark_error = self
+ .panels_with_errors
+ .insert(InputPanel::Include, e.to_string());
+ if should_mark_error.is_none() {
cx.notify();
}
PathMatcher::default()
@@ -1174,15 +1178,17 @@ impl ProjectSearchView {
Ok(excluded_files) => {
let should_unmark_error =
self.panels_with_errors.remove(&InputPanel::Exclude);
- if should_unmark_error {
+ if should_unmark_error.is_some() {
cx.notify();
}
excluded_files
}
- Err(_e) => {
- let should_mark_error = self.panels_with_errors.insert(InputPanel::Exclude);
- if should_mark_error {
+ Err(e) => {
+ let should_mark_error = self
+ .panels_with_errors
+ .insert(InputPanel::Exclude, e.to_string());
+ if should_mark_error.is_none() {
cx.notify();
}
PathMatcher::default()
@@ -1219,19 +1225,19 @@ impl ProjectSearchView {
) {
Ok(query) => {
let should_unmark_error = self.panels_with_errors.remove(&InputPanel::Query);
- if should_unmark_error {
+ if should_unmark_error.is_some() {
cx.notify();
}
- self.query_error = None;
Some(query)
}
Err(e) => {
- let should_mark_error = self.panels_with_errors.insert(InputPanel::Query);
- if should_mark_error {
+ let should_mark_error = self
+ .panels_with_errors
+ .insert(InputPanel::Query, e.to_string());
+ if should_mark_error.is_none() {
cx.notify();
}
- self.query_error = Some(e.to_string());
None
}
@@ -1249,15 +1255,17 @@ impl ProjectSearchView {
) {
Ok(query) => {
let should_unmark_error = self.panels_with_errors.remove(&InputPanel::Query);
- if should_unmark_error {
+ if should_unmark_error.is_some() {
cx.notify();
}
Some(query)
}
- Err(_e) => {
- let should_mark_error = self.panels_with_errors.insert(InputPanel::Query);
- if should_mark_error {
+ Err(e) => {
+ let should_mark_error = self
+ .panels_with_errors
+ .insert(InputPanel::Query, e.to_string());
+ if should_mark_error.is_none() {
cx.notify();
}
@@ -1512,7 +1520,7 @@ impl ProjectSearchView {
}
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)
} else {
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);
}
- fn backtab(
- &mut self,
- _: &editor::actions::Backtab,
- window: &mut Window,
- cx: &mut Context<Self>,
- ) {
+ fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
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>) {
let active_project_search = match &self.active_project_search {
Some(active_project_search) => active_project_search,
-
- None => {
- return;
- }
+ None => return,
};
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 {
- views.push(&project_view.replacement_editor);
+ views.push(project_view.replacement_editor.focus_handle(cx));
}
if project_view.filters_enabled {
views.extend([
- &project_view.included_files_editor,
- &project_view.excluded_files_editor,
+ project_view.included_files_editor.focus_handle(cx),
+ project_view.excluded_files_editor.focus_handle(cx),
]);
}
- let current_index = match views
- .iter()
- .enumerate()
- .find(|(_, editor)| editor.focus_handle(cx).is_focused(window))
- {
- Some((index, _)) => index,
+ let current_index = match views.iter().position(|focus| focus.is_focused(window)) {
+ Some(index) => index,
None => return,
};
@@ -1665,8 +1661,8 @@ impl ProjectSearchBar {
Direction::Prev if current_index == 0 => views.len() - 1,
Direction::Prev => (current_index - 1) % views.len(),
};
- let next_focus_handle = views[new_index].focus_handle(cx);
- window.focus(&next_focus_handle);
+ let next_focus_handle = &views[new_index];
+ window.focus(next_focus_handle);
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 {
@@ -1959,28 +1924,43 @@ impl Render for ProjectSearchBar {
let container_width = window.viewport_size().width;
let input_width = SearchInputWidth::calc_width(container_width);
- enum BaseStyle {
- SingleInput,
- MultipleInputs,
- }
-
- 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 input_base_styles = |panel: InputPanel| {
+ input_base_styles(search.border_color_for(panel, cx), |div| match panel {
+ InputPanel::Query | InputPanel::Replacement => div.w(input_width),
+ InputPanel::Include | InputPanel::Exclude => div.flex_grow(),
+ })
+ };
+ let theme_colors = cx.theme().colors();
+ let project_search = search.entity.read(cx);
+ let limit_reached = project_search.limit_reached;
+
+ 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(BaseStyle::SingleInput, InputPanel::Query)
+ 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.previous_history_query(action, window, cx)
@@ -1988,7 +1968,7 @@ impl Render for ProjectSearchBar {
.on_action(
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(
h_flex()
.gap_1()
@@ -2017,6 +1997,7 @@ impl Render for ProjectSearchBar {
let mode_column = h_flex()
.gap_1()
+ .min_w_64()
.child(
IconButton::new("project-search-filter-button", IconName::Filter)
.shape(IconButtonShape::Square)
@@ -2045,109 +2026,46 @@ impl Render for ProjectSearchBar {
}
}),
)
- .child(
- IconButton::new("project-search-toggle-replace", IconName::Replace)
- .shape(IconButtonShape::Square)
- .on_click(cx.listener(|this, _, window, cx| {
- this.toggle_replace(&ToggleReplace, window, cx);
- }))
- .toggle_state(
- self.active_project_search
- .as_ref()
- .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 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());
+ .child(toggle_replace_button(
+ "project-search-toggle-replace",
+ focus_handle.clone(),
+ self.active_project_search
+ .as_ref()
+ .map(|search| search.read(cx).replace_enabled)
+ .unwrap_or_default(),
+ cx.listener(|this, _, window, cx| {
+ this.toggle_replace(&ToggleReplace, window, cx);
+ }),
+ ));
+
+ let query_focus = search.query_editor.focus_handle(cx);
let matches_column = h_flex()
.pl_2()
.ml_2()
.border_l_1()
- .border_color(cx.theme().colors().border_variant)
- .child(
- IconButton::new("project-search-prev-match", IconName::ChevronLeft)
- .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::Prev, window, cx);
- })
- }
- }))
- .tooltip({
- let focus_handle = focus_handle.clone();
- move |window, cx| {
- Tooltip::for_action_in(
- "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,
- )
- }
- }),
- )
+ .border_color(theme_colors.border_variant)
+ .child(render_action_button(
+ "project-search-nav-button",
+ IconName::ChevronLeft,
+ search.active_match_index.is_some(),
+ "Select Previous Match",
+ &SelectPreviousMatch,
+ query_focus.clone(),
+ ))
+ .child(render_action_button(
+ "project-search-nav-button",
+ IconName::ChevronRight,
+ search.active_match_index.is_some(),
+ "Select Next Match",
+ &SelectNextMatch,
+ query_focus,
+ ))
.child(
div()
.id("matches")
- .ml_1()
+ .ml_2()
+ .min_w(rems_from_px(40.))
.child(Label::new(match_text).size(LabelSize::Small).color(
if search.active_match_index.is_some() {
Color::Default
@@ -2169,63 +2087,30 @@ impl Render for ProjectSearchBar {
.child(h_flex().min_w_64().child(mode_column).child(matches_column));
let replace_line = search.replace_enabled.then(|| {
- let replace_column = input_base_styles(BaseStyle::SingleInput, InputPanel::Replacement)
- .child(self.render_text_input(&search.replacement_editor, cx));
+ let replace_column = input_base_styles(InputPanel::Replacement)
+ .child(render_text_input(&search.replacement_editor, None, cx));
let focus_handle = search.replacement_editor.read(cx).focus_handle(cx);
- let replace_actions =
- h_flex()
- .min_w_64()
- .gap_1()
- .when(search.replace_enabled, |this| {
- this.child(
- IconButton::new("project-search-replace-next", IconName::ReplaceNext)
- .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_next(&ReplaceNext, window, cx);
- })
- }
- }))
- .tooltip({
- let focus_handle = focus_handle.clone();
- move |window, cx| {
- 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,
- )
- }
- }),
- )
- });
+ let replace_actions = h_flex()
+ .min_w_64()
+ .gap_1()
+ .child(render_action_button(
+ "project-search-replace-button",
+ IconName::ReplaceNext,
+ true,
+ "Replace Next Match",
+ &ReplaceNext,
+ focus_handle.clone(),
+ ))
+ .child(render_action_button(
+ "project-search-replace-button",
+ IconName::ReplaceAll,
+ true,
+ "Replace All Matches",
+ &ReplaceAll,
+ focus_handle,
+ ));
h_flex()
.w_full()
@@ -2235,6 +2120,45 @@ impl Render for ProjectSearchBar {
});
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()
.w_full()
.gap_2()
@@ -2242,62 +2166,14 @@ impl Render for ProjectSearchBar {
h_flex()
.gap_2()
.w(input_width)
- .child(
- input_base_styles(BaseStyle::MultipleInputs, 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(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(include)
+ .child(exclude),
)
+ .child(mode_column)
});
let mut key_context = KeyContext::default();
-
key_context.add("ProjectSearchBar");
-
if search
.replacement_editor
.focus_handle(cx)
@@ -2306,16 +2182,33 @@ impl Render for ProjectSearchBar {
key_context.add("in_replace");
}
- let query_error_line = search.query_error.as_ref().map(|error| {
- Label::new(error)
- .size(LabelSize::Small)
- .color(Color::Error)
- .mt_neg_1()
- .ml_2()
- });
+ let query_error_line = search
+ .panels_with_errors
+ .get(&InputPanel::Query)
+ .map(|error| {
+ Label::new(error)
+ .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()
+ .gap_2()
.py(px(1.0))
+ .w_full()
.key_context(key_context)
.on_action(cx.listener(|this, _: &ToggleFocus, 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| {
this.toggle_filters(window, cx);
}))
- .capture_action(cx.listener(|this, action, window, cx| {
- this.tab(action, window, cx);
- cx.stop_propagation();
- }))
- .capture_action(cx.listener(|this, action, window, cx| {
- this.backtab(action, window, cx);
- cx.stop_propagation();
- }))
+ .capture_action(cx.listener(Self::tab))
+ .capture_action(cx.listener(Self::backtab))
.on_action(cx.listener(|this, action, window, cx| this.confirm(action, window, cx)))
.on_action(cx.listener(|this, 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_prev_match))
- .gap_2()
- .w_full()
.child(search_line)
.children(query_error_line)
.children(replace_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::{Tooltip, prelude::*};
-pub(super) fn render_nav_button(
+use crate::ToggleReplace;
+
+pub(super) fn render_action_button(
+ id_prefix: &'static str,
icon: ui::IconName,
active: bool,
tooltip: &'static str,
@@ -10,7 +16,7 @@ pub(super) fn render_nav_button(
focus_handle: FocusHandle,
) -> impl IntoElement {
IconButton::new(
- SharedString::from(format!("search-nav-button-{}", action.name())),
+ SharedString::from(format!("{id_prefix}-{}", action.name())),
icon,
)
.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))
.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,
focus_changed,
} => {
- cx.on_next_frame(window, |_, window, _| {
- window.invalidate_character_coordinates();
- });
+ window.invalidate_character_coordinates();
pane.update(cx, |pane, _| {
pane.track_alternate_file_items();
@@ -3921,9 +3919,7 @@ impl Workspace {
}
}
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);
}
pane::Event::ZoomIn => {