@@ -1,24 +1,19 @@
use crate::{
- history::SearchHistory,
- mode::{SearchMode, Side},
- search_bar::{render_nav_button, render_option_button_icon, render_search_mode_button},
- ActivateRegexMode, ActivateSemanticMode, ActivateTextMode, CycleMode, NextHistoryQuery,
- PreviousHistoryQuery, ReplaceAll, ReplaceNext, SearchOptions, SelectNextMatch, SelectPrevMatch,
- ToggleCaseSensitive, ToggleReplace, ToggleWholeWord,
+ history::SearchHistory, mode::SearchMode, ActivateRegexMode, ActivateTextMode, CycleMode,
+ NextHistoryQuery, PreviousHistoryQuery, ReplaceAll, ReplaceNext, SearchOptions,
+ SelectNextMatch, SelectPrevMatch, ToggleCaseSensitive, ToggleReplace, ToggleWholeWord,
};
-use anyhow::{Context, Result};
+use anyhow::{Context as _, Result};
use collections::HashMap;
use editor::{
- items::active_match_index, scroll::autoscroll::Autoscroll, Anchor, Editor, MultiBuffer,
- SelectAll, MAX_TAB_TITLE_LEN,
+ items::active_match_index, scroll::autoscroll::Autoscroll, Anchor, Editor, EditorEvent,
+ MultiBuffer, SelectAll, MAX_TAB_TITLE_LEN,
};
-use futures::StreamExt;
use gpui::{
- actions,
- elements::*,
- platform::{MouseButton, PromptLevel},
- Action, AnyElement, AnyViewHandle, AppContext, Entity, ModelContext, ModelHandle, Subscription,
- Task, View, ViewContext, ViewHandle, WeakModelHandle, WeakViewHandle,
+ actions, div, white, AnyElement, AnyView, AppContext, Context as _, Div, Element, EntityId,
+ EventEmitter, FocusableView, InteractiveElement, IntoElement, KeyContext, Model, ModelContext,
+ ParentElement, PromptLevel, Render, SharedString, Styled, Subscription, Task, View,
+ ViewContext, VisualContext, WeakModel, WeakView, WindowContext,
};
use menu::Confirm;
use project::{
@@ -26,96 +21,55 @@ use project::{
Entry, Project,
};
use semantic_index::{SemanticIndex, SemanticIndexStatus};
-use smallvec::SmallVec;
+
+use smol::stream::StreamExt;
use std::{
any::{Any, TypeId},
- borrow::Cow,
collections::HashSet,
mem,
ops::{Not, Range},
path::PathBuf,
- sync::Arc,
- time::{Duration, Instant},
+ time::Duration,
+};
+
+use ui::{
+ h_stack, v_stack, Button, ButtonCommon, Clickable, Disableable, Icon, IconButton, IconElement,
+ Label, LabelCommon, LabelSize, Selectable, Tooltip,
};
use util::{paths::PathMatcher, ResultExt as _};
use workspace::{
item::{BreadcrumbText, Item, ItemEvent, ItemHandle},
searchable::{Direction, SearchableItem, SearchableItemHandle},
- ItemNavHistory, Pane, ToolbarItemLocation, ToolbarItemView, Workspace, WorkspaceId,
+ ItemNavHistory, Pane, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace,
+ WorkspaceId,
};
actions!(
project_search,
- [SearchInNew, ToggleFocus, NextField, ToggleFilters,]
+ [SearchInNew, ToggleFocus, NextField, ToggleFilters]
);
#[derive(Default)]
-struct ActiveSearches(HashMap<WeakModelHandle<Project>, WeakViewHandle<ProjectSearchView>>);
+struct ActiveSearches(HashMap<WeakModel<Project>, WeakView<ProjectSearchView>>);
#[derive(Default)]
-struct ActiveSettings(HashMap<WeakModelHandle<Project>, ProjectSearchSettings>);
+struct ActiveSettings(HashMap<WeakModel<Project>, ProjectSearchSettings>);
pub fn init(cx: &mut AppContext) {
+ // todo!() po
cx.set_global(ActiveSearches::default());
cx.set_global(ActiveSettings::default());
- cx.add_action(ProjectSearchView::deploy);
- cx.add_action(ProjectSearchView::move_focus_to_results);
- cx.add_action(ProjectSearchBar::confirm);
- cx.add_action(ProjectSearchBar::search_in_new);
- cx.add_action(ProjectSearchBar::select_next_match);
- cx.add_action(ProjectSearchBar::select_prev_match);
- cx.add_action(ProjectSearchBar::replace_next);
- cx.add_action(ProjectSearchBar::replace_all);
- cx.add_action(ProjectSearchBar::cycle_mode);
- cx.add_action(ProjectSearchBar::next_history_query);
- cx.add_action(ProjectSearchBar::previous_history_query);
- cx.add_action(ProjectSearchBar::activate_regex_mode);
- cx.add_action(ProjectSearchBar::toggle_replace);
- cx.add_action(ProjectSearchBar::toggle_replace_on_a_pane);
- cx.add_action(ProjectSearchBar::activate_text_mode);
-
- // This action should only be registered if the semantic index is enabled
- // We are registering it all the time, as I dont want to introduce a dependency
- // for Semantic Index Settings globally whenever search is tested.
- cx.add_action(ProjectSearchBar::activate_semantic_mode);
-
- cx.capture_action(ProjectSearchBar::tab);
- cx.capture_action(ProjectSearchBar::tab_previous);
- cx.capture_action(ProjectSearchView::replace_all);
- cx.capture_action(ProjectSearchView::replace_next);
- add_toggle_option_action::<ToggleCaseSensitive>(SearchOptions::CASE_SENSITIVE, cx);
- add_toggle_option_action::<ToggleWholeWord>(SearchOptions::WHOLE_WORD, cx);
- add_toggle_option_action::<ToggleIncludeIgnored>(SearchOptions::INCLUDE_IGNORED, cx);
- add_toggle_filters_action::<ToggleFilters>(cx);
-}
-
-fn add_toggle_filters_action<A: Action>(cx: &mut AppContext) {
- cx.add_action(move |pane: &mut Pane, _: &A, cx: &mut ViewContext<Pane>| {
- if let Some(search_bar) = pane.toolbar().read(cx).item_of_type::<ProjectSearchBar>() {
- if search_bar.update(cx, |search_bar, cx| search_bar.toggle_filters(cx)) {
- return;
- }
- }
- cx.propagate_action();
- });
-}
-
-fn add_toggle_option_action<A: Action>(option: SearchOptions, cx: &mut AppContext) {
- cx.add_action(move |pane: &mut Pane, _: &A, cx: &mut ViewContext<Pane>| {
- if let Some(search_bar) = pane.toolbar().read(cx).item_of_type::<ProjectSearchBar>() {
- if search_bar.update(cx, |search_bar, cx| {
- search_bar.toggle_search_option(option, cx)
- }) {
- return;
- }
- }
- cx.propagate_action();
- });
+ cx.observe_new_views(|workspace: &mut Workspace, _cx| {
+ workspace
+ .register_action(ProjectSearchView::deploy)
+ .register_action(ProjectSearchBar::search_in_new);
+ })
+ .detach();
}
struct ProjectSearch {
- project: ModelHandle<Project>,
- excerpts: ModelHandle<MultiBuffer>,
+ project: Model<Project>,
+ excerpts: Model<MultiBuffer>,
pending_search: Option<Task<Option<()>>>,
match_ranges: Vec<Range<Anchor>>,
active_query: Option<SearchQuery>,
@@ -132,10 +86,10 @@ enum InputPanel {
}
pub struct ProjectSearchView {
- model: ModelHandle<ProjectSearch>,
- query_editor: ViewHandle<Editor>,
- replacement_editor: ViewHandle<Editor>,
- results_editor: ViewHandle<Editor>,
+ model: Model<ProjectSearch>,
+ query_editor: View<Editor>,
+ replacement_editor: View<Editor>,
+ results_editor: View<Editor>,
semantic_state: Option<SemanticState>,
semantic_permissioned: Option<bool>,
search_options: SearchOptions,
@@ -143,8 +97,8 @@ pub struct ProjectSearchView {
active_match_index: Option<usize>,
search_id: usize,
query_editor_was_focused: bool,
- included_files_editor: ViewHandle<Editor>,
- excluded_files_editor: ViewHandle<Editor>,
+ included_files_editor: View<Editor>,
+ excluded_files_editor: View<Editor>,
filters_enabled: bool,
replace_enabled: bool,
current_mode: SearchMode,
@@ -164,20 +118,16 @@ struct ProjectSearchSettings {
}
pub struct ProjectSearchBar {
- active_project_search: Option<ViewHandle<ProjectSearchView>>,
+ active_project_search: Option<View<ProjectSearchView>>,
subscription: Option<Subscription>,
}
-impl Entity for ProjectSearch {
- type Event = ();
-}
-
impl ProjectSearch {
- fn new(project: ModelHandle<Project>, cx: &mut ModelContext<Self>) -> Self {
+ fn new(project: Model<Project>, cx: &mut ModelContext<Self>) -> Self {
let replica_id = project.read(cx).replica_id();
Self {
project,
- excerpts: cx.add_model(|_| MultiBuffer::new(replica_id)),
+ excerpts: cx.build_model(|_| MultiBuffer::new(replica_id)),
pending_search: Default::default(),
match_ranges: Default::default(),
active_query: None,
@@ -187,12 +137,12 @@ impl ProjectSearch {
}
}
- fn clone(&self, cx: &mut ModelContext<Self>) -> ModelHandle<Self> {
- cx.add_model(|cx| Self {
+ fn clone(&self, cx: &mut ModelContext<Self>) -> Model<Self> {
+ cx.build_model(|cx| Self {
project: self.project.clone(),
excerpts: self
.excerpts
- .update(cx, |excerpts, cx| cx.add_model(|cx| excerpts.clone(cx))),
+ .update(cx, |excerpts, cx| cx.build_model(|cx| excerpts.clone(cx))),
pending_search: Default::default(),
match_ranges: self.match_ranges.clone(),
active_query: self.active_query.clone(),
@@ -210,33 +160,38 @@ impl ProjectSearch {
self.search_history.add(query.as_str().to_string());
self.active_query = Some(query);
self.match_ranges.clear();
- self.pending_search = Some(cx.spawn_weak(|this, mut cx| async move {
+ self.pending_search = Some(cx.spawn(|this, mut cx| async move {
let mut matches = search;
- let this = this.upgrade(&cx)?;
+ let this = this.upgrade()?;
this.update(&mut cx, |this, cx| {
this.match_ranges.clear();
this.excerpts.update(cx, |this, cx| this.clear(cx));
this.no_results = Some(true);
- });
+ })
+ .ok()?;
while let Some((buffer, anchors)) = matches.next().await {
- let mut ranges = this.update(&mut cx, |this, cx| {
- this.no_results = Some(false);
- this.excerpts.update(cx, |excerpts, cx| {
- excerpts.stream_excerpts_with_context_lines(buffer, anchors, 1, cx)
+ let mut ranges = this
+ .update(&mut cx, |this, cx| {
+ this.no_results = Some(false);
+ this.excerpts.update(cx, |excerpts, cx| {
+ excerpts.stream_excerpts_with_context_lines(buffer, anchors, 1, cx)
+ })
})
- });
+ .ok()?;
while let Some(range) = ranges.next().await {
- this.update(&mut cx, |this, _| this.match_ranges.push(range));
+ this.update(&mut cx, |this, _| this.match_ranges.push(range))
+ .ok()?;
}
- this.update(&mut cx, |_, cx| cx.notify());
+ this.update(&mut cx, |_, cx| cx.notify()).ok()?;
}
this.update(&mut cx, |this, cx| {
this.pending_search.take();
cx.notify();
- });
+ })
+ .ok()?;
None
}));
@@ -271,14 +226,17 @@ impl ProjectSearch {
this.excerpts.update(cx, |excerpts, cx| {
excerpts.clear(cx);
});
- });
+ })
+ .ok()?;
for (buffer, ranges) in matches {
- let mut match_ranges = this.update(&mut cx, |this, cx| {
- this.no_results = Some(false);
- this.excerpts.update(cx, |excerpts, cx| {
- excerpts.stream_excerpts_with_context_lines(buffer, ranges, 3, cx)
+ let mut match_ranges = this
+ .update(&mut cx, |this, cx| {
+ this.no_results = Some(false);
+ this.excerpts.update(cx, |excerpts, cx| {
+ excerpts.stream_excerpts_with_context_lines(buffer, ranges, 3, cx)
+ })
})
- });
+ .ok()?;
while let Some(match_range) = match_ranges.next().await {
this.update(&mut cx, |this, cx| {
this.match_ranges.push(match_range);
@@ -286,14 +244,16 @@ impl ProjectSearch {
this.match_ranges.push(match_range);
}
cx.notify();
- });
+ })
+ .ok()?;
}
}
this.update(&mut cx, |this, cx| {
this.pending_search.take();
cx.notify();
- });
+ })
+ .ok()?;
None
}));
@@ -305,221 +265,223 @@ impl ProjectSearch {
pub enum ViewEvent {
UpdateTab,
Activate,
- EditorEvent(editor::Event),
+ EditorEvent(editor::EditorEvent),
Dismiss,
}
-impl Entity for ProjectSearchView {
- type Event = ViewEvent;
-}
-
-impl View for ProjectSearchView {
- fn ui_name() -> &'static str {
- "ProjectSearchView"
- }
-
- fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
- let model = &self.model.read(cx);
- if model.match_ranges.is_empty() {
- enum Status {}
-
- let theme = theme::current(cx).clone();
-
- // If Search is Active -> Major: Searching..., Minor: None
- // If Semantic -> Major: "Search using Natural Language", Minor: {Status}/n{ex...}/n{ex...}
- // If Regex -> Major: "Search using Regex", Minor: {ex...}
- // If Text -> Major: "Text search all files and folders", Minor: {...}
-
- let current_mode = self.current_mode;
- let mut major_text = if model.pending_search.is_some() {
- Cow::Borrowed("Searching...")
- } else if model.no_results.is_some_and(|v| v) {
- Cow::Borrowed("No Results")
- } else {
- match current_mode {
- SearchMode::Text => Cow::Borrowed("Text search all files and folders"),
- SearchMode::Semantic => {
- Cow::Borrowed("Search all code objects using Natural Language")
- }
- SearchMode::Regex => Cow::Borrowed("Regex search all files and folders"),
- }
- };
-
- let mut show_minor_text = true;
- let semantic_status = self.semantic_state.as_ref().and_then(|semantic| {
- let status = semantic.index_status;
- match status {
- SemanticIndexStatus::NotAuthenticated => {
- major_text = Cow::Borrowed("Not Authenticated");
- show_minor_text = false;
- Some(vec![
- "API Key Missing: Please set 'OPENAI_API_KEY' in Environment Variables."
- .to_string(), "If you authenticated using the Assistant Panel, please restart Zed to Authenticate.".to_string()])
- }
- SemanticIndexStatus::Indexed => Some(vec!["Indexing complete".to_string()]),
- SemanticIndexStatus::Indexing {
- remaining_files,
- rate_limit_expiry,
- } => {
- if remaining_files == 0 {
- Some(vec![format!("Indexing...")])
- } else {
- if let Some(rate_limit_expiry) = rate_limit_expiry {
- let remaining_seconds =
- rate_limit_expiry.duration_since(Instant::now());
- if remaining_seconds > Duration::from_secs(0) {
- Some(vec![format!(
- "Remaining files to index (rate limit resets in {}s): {}",
- remaining_seconds.as_secs(),
- remaining_files
- )])
- } else {
- Some(vec![format!("Remaining files to index: {}", remaining_files)])
- }
- } else {
- Some(vec![format!("Remaining files to index: {}", remaining_files)])
- }
- }
- }
- SemanticIndexStatus::NotIndexed => None,
- }
- });
+impl EventEmitter<ViewEvent> for ProjectSearchView {}
- let minor_text = if let Some(no_results) = model.no_results {
- if model.pending_search.is_none() && no_results {
- vec!["No results found in this project for the provided query".to_owned()]
- } else {
- vec![]
- }
+impl Render for ProjectSearchView {
+ type Element = Div;
+ fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
+ if self.has_matches() {
+ div()
+ .flex_1()
+ .size_full()
+ .child(self.results_editor.clone())
+ } else {
+ let model = self.model.read(cx);
+ let has_no_results = model.no_results.unwrap_or(false);
+ let is_search_underway = model.pending_search.is_some();
+ let major_text = if is_search_underway {
+ Label::new("Searching...")
+ } else if has_no_results {
+ Label::new("No results for a given query")
} else {
- match current_mode {
- SearchMode::Semantic => {
- let mut minor_text: Vec<String> = Vec::new();
- minor_text.push("".into());
- if let Some(semantic_status) = semantic_status {
- minor_text.extend(semantic_status);
- }
- if show_minor_text {
- minor_text
- .push("Simply explain the code you are looking to find.".into());
- minor_text.push(
- "ex. 'prompt user for permissions to index their project'".into(),
- );
- }
- minor_text
- }
- _ => vec![
- "".to_owned(),
- "Include/exclude specific paths with the filter option.".to_owned(),
- "Matching exact word and/or casing is available too.".to_owned(),
- ],
- }
+ Label::new(format!("{} search all files", self.current_mode.label()))
};
-
- let previous_query_keystrokes =
- cx.binding_for_action(&PreviousHistoryQuery {})
- .map(|binding| {
- binding
- .keystrokes()
- .iter()
- .map(|k| k.to_string())
- .collect::<Vec<_>>()
- });
- let next_query_keystrokes =
- cx.binding_for_action(&NextHistoryQuery {}).map(|binding| {
- binding
- .keystrokes()
- .iter()
- .map(|k| k.to_string())
- .collect::<Vec<_>>()
- });
- let new_placeholder_text = match (previous_query_keystrokes, next_query_keystrokes) {
- (Some(previous_query_keystrokes), Some(next_query_keystrokes)) => {
- format!(
- "Search ({}/{} for previous/next query)",
- previous_query_keystrokes.join(" "),
- next_query_keystrokes.join(" ")
- )
- }
- (None, Some(next_query_keystrokes)) => {
- format!(
- "Search ({} for next query)",
- next_query_keystrokes.join(" ")
- )
- }
- (Some(previous_query_keystrokes), None) => {
- format!(
- "Search ({} for previous query)",
- previous_query_keystrokes.join(" ")
- )
- }
- (None, None) => String::new(),
- };
- self.query_editor.update(cx, |editor, cx| {
- editor.set_placeholder_text(new_placeholder_text, cx);
- });
-
- MouseEventHandler::new::<Status, _>(0, cx, |_, _| {
- Flex::column()
- .with_child(Flex::column().contained().flex(1., true))
- .with_child(
- Flex::column()
- .align_children_center()
- .with_child(Label::new(
- major_text,
- theme.search.major_results_status.clone(),
- ))
- .with_children(
- minor_text.into_iter().map(|x| {
- Label::new(x, theme.search.minor_results_status.clone())
- }),
- )
- .aligned()
- .top()
- .contained()
- .flex(7., true),
- )
- .contained()
- .with_background_color(theme.editor.background)
- })
- .on_down(MouseButton::Left, |_, _, cx| {
- cx.focus_parent();
- })
- .into_any_named("project search view")
- } else {
- ChildView::new(&self.results_editor, cx)
- .flex(1., true)
- .into_any_named("project search view")
+ let major_text = div().justify_center().max_w_96().child(major_text);
+ let middle_text = div()
+ .items_center()
+ .max_w_96()
+ .child(Label::new(self.landing_text_minor()).size(LabelSize::Small));
+ v_stack().flex_1().size_full().justify_center().child(
+ h_stack()
+ .size_full()
+ .justify_center()
+ .child(h_stack().flex_1())
+ .child(v_stack().child(major_text).child(middle_text))
+ .child(h_stack().flex_1()),
+ )
}
}
+}
- fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
- let handle = cx.weak_handle();
- cx.update_global(|state: &mut ActiveSearches, cx| {
- state
- .0
- .insert(self.model.read(cx).project.downgrade(), handle)
- });
-
- cx.update_global(|state: &mut ActiveSettings, cx| {
- state.0.insert(
- self.model.read(cx).project.downgrade(),
- self.current_settings(),
- );
- });
-
- if cx.is_self_focused() {
- if self.query_editor_was_focused {
- cx.focus(&self.query_editor);
- } else {
- cx.focus(&self.results_editor);
- }
- }
+// impl Entity for ProjectSearchView {
+// type Event = ViewEvent;
+// }
+
+// impl View for ProjectSearchView {
+// fn ui_name() -> &'static str {
+// "ProjectSearchView"
+// }
+
+// fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
+// let model = &self.model.read(cx);
+// if model.match_ranges.is_empty() {
+// enum Status {}
+
+// let theme = theme::current(cx).clone();
+
+// // If Search is Active -> Major: Searching..., Minor: None
+// // If Semantic -> Major: "Search using Natural Language", Minor: {Status}/n{ex...}/n{ex...}
+// // If Regex -> Major: "Search using Regex", Minor: {ex...}
+// // If Text -> Major: "Text search all files and folders", Minor: {...}
+
+// let current_mode = self.current_mode;
+// let mut major_text = if model.pending_search.is_some() {
+// Cow::Borrowed("Searching...")
+// } else if model.no_results.is_some_and(|v| v) {
+// Cow::Borrowed("No Results")
+// } else {
+// match current_mode {
+// SearchMode::Text => Cow::Borrowed("Text search all files and folders"),
+// SearchMode::Semantic => {
+// Cow::Borrowed("Search all code objects using Natural Language")
+// }
+// SearchMode::Regex => Cow::Borrowed("Regex search all files and folders"),
+// }
+// };
+
+// let mut show_minor_text = true;
+// let semantic_status = self.semantic_state.as_ref().and_then(|semantic| {
+// let status = semantic.index_status;
+// match status {
+// SemanticIndexStatus::NotAuthenticated => {
+// major_text = Cow::Borrowed("Not Authenticated");
+// show_minor_text = false;
+// Some(vec![
+// "API Key Missing: Please set 'OPENAI_API_KEY' in Environment Variables."
+// .to_string(), "If you authenticated using the Assistant Panel, please restart Zed to Authenticate.".to_string()])
+// }
+// SemanticIndexStatus::Indexed => Some(vec!["Indexing complete".to_string()]),
+// SemanticIndexStatus::Indexing {
+// remaining_files,
+// rate_limit_expiry,
+// } => {
+// if remaining_files == 0 {
+// Some(vec![format!("Indexing...")])
+// } else {
+// if let Some(rate_limit_expiry) = rate_limit_expiry {
+// let remaining_seconds =
+// rate_limit_expiry.duration_since(Instant::now());
+// if remaining_seconds > Duration::from_secs(0) {
+// Some(vec![format!(
+// "Remaining files to index (rate limit resets in {}s): {}",
+// remaining_seconds.as_secs(),
+// remaining_files
+// )])
+// } else {
+// Some(vec![format!("Remaining files to index: {}", remaining_files)])
+// }
+// } else {
+// Some(vec![format!("Remaining files to index: {}", remaining_files)])
+// }
+// }
+// }
+// SemanticIndexStatus::NotIndexed => None,
+// }
+// });
+
+// let minor_text = if let Some(no_results) = model.no_results {
+// if model.pending_search.is_none() && no_results {
+// vec!["No results found in this project for the provided query".to_owned()]
+// } else {
+// vec![]
+// }
+// } else {
+// match current_mode {
+// SearchMode::Semantic => {
+// let mut minor_text: Vec<String> = Vec::new();
+// minor_text.push("".into());
+// if let Some(semantic_status) = semantic_status {
+// minor_text.extend(semantic_status);
+// }
+// if show_minor_text {
+// minor_text
+// .push("Simply explain the code you are looking to find.".into());
+// minor_text.push(
+// "ex. 'prompt user for permissions to index their project'".into(),
+// );
+// }
+// minor_text
+// }
+// _ => vec![
+// "".to_owned(),
+// "Include/exclude specific paths with the filter option.".to_owned(),
+// "Matching exact word and/or casing is available too.".to_owned(),
+// ],
+// }
+// };
+
+// MouseEventHandler::new::<Status, _>(0, cx, |_, _| {
+// Flex::column()
+// .with_child(Flex::column().contained().flex(1., true))
+// .with_child(
+// Flex::column()
+// .align_children_center()
+// .with_child(Label::new(
+// major_text,
+// theme.search.major_results_status.clone(),
+// ))
+// .with_children(
+// minor_text.into_iter().map(|x| {
+// Label::new(x, theme.search.minor_results_status.clone())
+// }),
+// )
+// .aligned()
+// .top()
+// .contained()
+// .flex(7., true),
+// )
+// .contained()
+// .with_background_color(theme.editor.background)
+// })
+// .on_down(MouseButton::Left, |_, _, cx| {
+// cx.focus_parent();
+// })
+// .into_any_named("project search view")
+// } else {
+// ChildView::new(&self.results_editor, cx)
+// .flex(1., true)
+// .into_any_named("project search view")
+// }
+// }
+
+// fn focus_in(&mut self, _: AnyView, cx: &mut ViewContext<Self>) {
+// let handle = cx.weak_handle();
+// cx.update_global(|state: &mut ActiveSearches, cx| {
+// state
+// .0
+// .insert(self.model.read(cx).project.downgrade(), handle)
+// });
+
+// cx.update_global(|state: &mut ActiveSettings, cx| {
+// state.0.insert(
+// self.model.read(cx).project.downgrade(),
+// self.current_settings(),
+// );
+// });
+
+// if cx.is_self_focused() {
+// if self.query_editor_was_focused {
+// cx.focus(&self.query_editor);
+// } else {
+// cx.focus(&self.results_editor);
+// }
+// }
+// }
+// }
+
+impl FocusableView for ProjectSearchView {
+ fn focus_handle(&self, cx: &AppContext) -> gpui::FocusHandle {
+ self.results_editor.focus_handle(cx)
}
}
impl Item for ProjectSearchView {
- fn tab_tooltip_text(&self, cx: &AppContext) -> Option<Cow<str>> {
+ type Event = ViewEvent;
+ fn tab_tooltip_text(&self, cx: &AppContext) -> Option<SharedString> {
let query_text = self.query_editor.read(cx).text(cx);
query_text
@@ -528,20 +490,17 @@ impl Item for ProjectSearchView {
.then(|| query_text.into())
.or_else(|| Some("Project Search".into()))
}
- fn should_close_item_on_event(event: &Self::Event) -> bool {
- event == &Self::Event::Dismiss
- }
fn act_as_type<'a>(
&'a self,
type_id: TypeId,
- self_handle: &'a ViewHandle<Self>,
+ self_handle: &'a View<Self>,
_: &'a AppContext,
- ) -> Option<&'a AnyViewHandle> {
+ ) -> Option<AnyView> {
if type_id == TypeId::of::<Self>() {
- Some(self_handle)
+ Some(self_handle.clone().into())
} else if type_id == TypeId::of::<Editor>() {
- Some(&self.results_editor)
+ Some(self.results_editor.clone().into())
} else {
None
}
@@ -552,45 +511,31 @@ impl Item for ProjectSearchView {
.update(cx, |editor, cx| editor.deactivated(cx));
}
- fn tab_content<T: 'static>(
- &self,
- _detail: Option<usize>,
- tab_theme: &theme::Tab,
- cx: &AppContext,
- ) -> AnyElement<T> {
- Flex::row()
- .with_child(
- Svg::new("icons/magnifying_glass.svg")
- .with_color(tab_theme.label.text.color)
- .constrained()
- .with_width(tab_theme.type_icon_width)
- .aligned()
- .contained()
- .with_margin_right(tab_theme.spacing),
- )
- .with_child({
- let tab_name: Option<Cow<_>> = self
- .model
- .read(cx)
- .search_history
- .current()
- .as_ref()
- .map(|query| {
- let query_text = util::truncate_and_trailoff(query, MAX_TAB_TITLE_LEN);
- query_text.into()
- });
- Label::new(
- tab_name
- .filter(|name| !name.is_empty())
- .unwrap_or("Project search".into()),
- tab_theme.label.clone(),
- )
- .aligned()
- })
+ fn tab_content(&self, _: Option<usize>, cx: &WindowContext<'_>) -> AnyElement {
+ let last_query: Option<SharedString> = self
+ .model
+ .read(cx)
+ .search_history
+ .current()
+ .as_ref()
+ .map(|query| {
+ let query_text = util::truncate_and_trailoff(query, MAX_TAB_TITLE_LEN);
+ query_text.into()
+ });
+ let tab_name = last_query
+ .filter(|query| !query.is_empty())
+ .unwrap_or_else(|| "Project search".into());
+ h_stack()
+ .child(IconElement::new(Icon::MagnifyingGlass))
+ .child(Label::new(tab_name))
.into_any()
}
- fn for_each_project_item(&self, cx: &AppContext, f: &mut dyn FnMut(usize, &dyn project::Item)) {
+ fn for_each_project_item(
+ &self,
+ cx: &AppContext,
+ f: &mut dyn FnMut(EntityId, &dyn project::Item),
+ ) {
self.results_editor.for_each_project_item(cx, f)
}
@@ -612,7 +557,7 @@ impl Item for ProjectSearchView {
fn save(
&mut self,
- project: ModelHandle<Project>,
+ project: Model<Project>,
cx: &mut ViewContext<Self>,
) -> Task<anyhow::Result<()>> {
self.results_editor
@@ -621,7 +566,7 @@ impl Item for ProjectSearchView {
fn save_as(
&mut self,
- _: ModelHandle<Project>,
+ _: Model<Project>,
_: PathBuf,
_: &mut ViewContext<Self>,
) -> Task<anyhow::Result<()>> {
@@ -630,19 +575,23 @@ impl Item for ProjectSearchView {
fn reload(
&mut self,
- project: ModelHandle<Project>,
+ project: Model<Project>,
cx: &mut ViewContext<Self>,
) -> Task<anyhow::Result<()>> {
self.results_editor
.update(cx, |editor, cx| editor.reload(project, cx))
}
- fn clone_on_split(&self, _workspace_id: WorkspaceId, cx: &mut ViewContext<Self>) -> Option<Self>
+ fn clone_on_split(
+ &self,
+ _workspace_id: WorkspaceId,
+ cx: &mut ViewContext<Self>,
+ ) -> Option<View<Self>>
where
Self: Sized,
{
let model = self.model.update(cx, |model, cx| model.clone(cx));
- Some(Self::new(model, cx, None))
+ Some(cx.build_view(|cx| Self::new(model, cx, None)))
}
fn added_to_workspace(&mut self, workspace: &mut Workspace, cx: &mut ViewContext<Self>) {
@@ -661,14 +610,17 @@ impl Item for ProjectSearchView {
.update(cx, |editor, cx| editor.navigate(data, cx))
}
- fn to_item_events(event: &Self::Event) -> SmallVec<[ItemEvent; 2]> {
+ fn to_item_events(event: &Self::Event, mut f: impl FnMut(ItemEvent)) {
match event {
ViewEvent::UpdateTab => {
- smallvec::smallvec![ItemEvent::UpdateBreadcrumbs, ItemEvent::UpdateTab]
+ f(ItemEvent::UpdateBreadcrumbs);
+ f(ItemEvent::UpdateTab);
+ }
+ ViewEvent::EditorEvent(editor_event) => {
+ Editor::to_item_events(editor_event, f);
}
- ViewEvent::EditorEvent(editor_event) => Editor::to_item_events(editor_event),
- ViewEvent::Dismiss => smallvec::smallvec![ItemEvent::CloseItem],
- _ => SmallVec::new(),
+ ViewEvent::Dismiss => f(ItemEvent::CloseItem),
+ _ => {}
}
}
@@ -689,12 +641,12 @@ impl Item for ProjectSearchView {
}
fn deserialize(
- _project: ModelHandle<Project>,
- _workspace: WeakViewHandle<Workspace>,
+ _project: Model<Project>,
+ _workspace: WeakView<Workspace>,
_workspace_id: workspace::WorkspaceId,
_item_id: workspace::ItemId,
_cx: &mut ViewContext<Pane>,
- ) -> Task<anyhow::Result<ViewHandle<Self>>> {
+ ) -> Task<anyhow::Result<View<Self>>> {
unimplemented!()
}
}
@@ -751,7 +703,7 @@ impl ProjectSearchView {
fn semantic_index_changed(
&mut self,
- semantic_index: ModelHandle<SemanticIndex>,
+ semantic_index: Model<SemanticIndex>,
cx: &mut ViewContext<Self>,
) {
let project = self.model.read(cx).project.clone();
@@ -767,7 +719,7 @@ impl ProjectSearchView {
semantic_state.maintain_rate_limit =
Some(cx.spawn(|this, mut cx| async move {
loop {
- cx.background().timer(Duration::from_secs(1)).await;
+ cx.background_executor().timer(Duration::from_secs(1)).await;
this.update(&mut cx, |_, cx| cx.notify()).log_err();
}
}));
@@ -809,7 +761,7 @@ impl ProjectSearchView {
let has_permission = has_permission.await?;
if !has_permission {
- let mut answer = this.update(&mut cx, |this, cx| {
+ let answer = this.update(&mut cx, |this, cx| {
let project = this.model.read(cx).project.clone();
let project_name = project
.read(cx)
@@ -829,7 +781,7 @@ impl ProjectSearchView {
)
})?;
- if answer.next().await == Some(0) {
+ if answer.await? == 0 {
this.update(&mut cx, |this, _| {
this.semantic_permissioned = Some(true);
})?;
@@ -907,7 +859,7 @@ impl ProjectSearchView {
}
fn new(
- model: ModelHandle<ProjectSearch>,
+ model: Model<ProjectSearch>,
cx: &mut ViewContext<Self>,
settings: Option<ProjectSearchSettings>,
) -> Self {
@@ -940,32 +892,26 @@ impl ProjectSearchView {
cx.observe(&model, |this, _, cx| this.model_changed(cx))
.detach();
- let query_editor = cx.add_view(|cx| {
- let mut editor = Editor::single_line(
- Some(Arc::new(|theme| theme.search.editor.input.clone())),
- cx,
- );
+ let query_editor = cx.build_view(|cx| {
+ let mut editor = Editor::single_line(cx);
editor.set_placeholder_text("Text search all files", cx);
editor.set_text(query_text, cx);
editor
});
// Subscribe to query_editor in order to reraise editor events for workspace item activation purposes
- cx.subscribe(&query_editor, |_, _, event, cx| {
+ cx.subscribe(&query_editor, |_, _, event: &EditorEvent, cx| {
cx.emit(ViewEvent::EditorEvent(event.clone()))
})
.detach();
- let replacement_editor = cx.add_view(|cx| {
- let mut editor = Editor::single_line(
- Some(Arc::new(|theme| theme.search.editor.input.clone())),
- cx,
- );
+ let replacement_editor = cx.build_view(|cx| {
+ let mut editor = Editor::single_line(cx);
editor.set_placeholder_text("Replace in project..", cx);
if let Some(text) = replacement_text {
editor.set_text(text, cx);
}
editor
});
- let results_editor = cx.add_view(|cx| {
+ let results_editor = cx.build_view(|cx| {
let mut editor = Editor::for_multibuffer(excerpts, Some(project.clone()), cx);
editor.set_searchable(false);
editor
@@ -973,8 +919,8 @@ impl ProjectSearchView {
cx.observe(&results_editor, |_, _, cx| cx.emit(ViewEvent::UpdateTab))
.detach();
- cx.subscribe(&results_editor, |this, _, event, cx| {
- if matches!(event, editor::Event::SelectionsChanged { .. }) {
+ cx.subscribe(&results_editor, |this, _, event: &EditorEvent, cx| {
+ if matches!(event, editor::EditorEvent::SelectionsChanged { .. }) {
this.update_match_index(cx);
}
// Reraise editor events for workspace item activation purposes