mirror of
https://github.com/zed-industries/zed.git
synced 2025-01-16 15:11:25 +00:00
Bring back semantic search UI
This commit is contained in:
parent
19e842b860
commit
a13468e6ad
1 changed files with 128 additions and 212 deletions
|
@ -1,7 +1,8 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
history::SearchHistory, mode::SearchMode, ActivateRegexMode, ActivateTextMode, CycleMode,
|
history::SearchHistory, mode::SearchMode, ActivateRegexMode, ActivateSemanticMode,
|
||||||
NextHistoryQuery, PreviousHistoryQuery, ReplaceAll, ReplaceNext, SearchOptions,
|
ActivateTextMode, CycleMode, NextHistoryQuery, PreviousHistoryQuery, ReplaceAll, ReplaceNext,
|
||||||
SelectNextMatch, SelectPrevMatch, ToggleCaseSensitive, ToggleReplace, ToggleWholeWord,
|
SearchOptions, SelectNextMatch, SelectPrevMatch, ToggleCaseSensitive, ToggleReplace,
|
||||||
|
ToggleWholeWord,
|
||||||
};
|
};
|
||||||
use anyhow::{Context as _, Result};
|
use anyhow::{Context as _, Result};
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
|
@ -29,7 +30,7 @@ use std::{
|
||||||
mem,
|
mem,
|
||||||
ops::{Not, Range},
|
ops::{Not, Range},
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
time::Duration,
|
time::{Duration, Instant},
|
||||||
};
|
};
|
||||||
|
|
||||||
use ui::{
|
use ui::{
|
||||||
|
@ -283,196 +284,86 @@ impl Render for ProjectSearchView {
|
||||||
let model = self.model.read(cx);
|
let model = self.model.read(cx);
|
||||||
let has_no_results = model.no_results.unwrap_or(false);
|
let has_no_results = model.no_results.unwrap_or(false);
|
||||||
let is_search_underway = model.pending_search.is_some();
|
let is_search_underway = model.pending_search.is_some();
|
||||||
let major_text = if is_search_underway {
|
let mut major_text = if is_search_underway {
|
||||||
Label::new("Searching...")
|
Label::new("Searching...")
|
||||||
} else if has_no_results {
|
} else if has_no_results {
|
||||||
Label::new("No results for a given query")
|
Label::new("No results")
|
||||||
} else {
|
} else {
|
||||||
Label::new(format!("{} search all files", self.current_mode.label()))
|
Label::new(format!("{} search all files", self.current_mode.label()))
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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 = Label::new("Not Authenticated");
|
||||||
|
show_minor_text = false;
|
||||||
|
Some(
|
||||||
|
"API Key Missing: Please set 'OPENAI_API_KEY' in Environment Variables. If you authenticated using the Assistant Panel, please restart Zed to Authenticate.".to_string())
|
||||||
|
}
|
||||||
|
SemanticIndexStatus::Indexed => Some("Indexing complete".to_string()),
|
||||||
|
SemanticIndexStatus::Indexing {
|
||||||
|
remaining_files,
|
||||||
|
rate_limit_expiry,
|
||||||
|
} => {
|
||||||
|
if remaining_files == 0 {
|
||||||
|
Some("Indexing...".to_string())
|
||||||
|
} 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(format!(
|
||||||
|
"Remaining files to index (rate limit resets in {}s): {}",
|
||||||
|
remaining_seconds.as_secs(),
|
||||||
|
remaining_files
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
Some(format!("Remaining files to index: {}", remaining_files))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Some(format!("Remaining files to index: {}", remaining_files))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SemanticIndexStatus::NotIndexed => None,
|
||||||
|
}
|
||||||
|
});
|
||||||
let major_text = div().justify_center().max_w_96().child(major_text);
|
let major_text = div().justify_center().max_w_96().child(major_text);
|
||||||
let middle_text = div()
|
|
||||||
.items_center()
|
let minor_text: Option<SharedString> = if let Some(no_results) = model.no_results {
|
||||||
.max_w_96()
|
if model.pending_search.is_none() && no_results {
|
||||||
.child(Label::new(self.landing_text_minor()).size(LabelSize::Small));
|
Some("No results found in this project for the provided query".into())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if let Some(mut semantic_status) = semantic_status {
|
||||||
|
semantic_status.extend(self.landing_text_minor().chars());
|
||||||
|
Some(semantic_status.into())
|
||||||
|
} else {
|
||||||
|
Some(self.landing_text_minor())
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let minor_text = minor_text.map(|text| {
|
||||||
|
div()
|
||||||
|
.items_center()
|
||||||
|
.max_w_96()
|
||||||
|
.child(Label::new(text).size(LabelSize::Small))
|
||||||
|
});
|
||||||
v_stack().flex_1().size_full().justify_center().child(
|
v_stack().flex_1().size_full().justify_center().child(
|
||||||
h_stack()
|
h_stack()
|
||||||
.size_full()
|
.size_full()
|
||||||
.justify_center()
|
.justify_center()
|
||||||
.child(h_stack().flex_1())
|
.child(h_stack().flex_1())
|
||||||
.child(v_stack().child(major_text).child(middle_text))
|
.child(v_stack().child(major_text).children(minor_text))
|
||||||
.child(h_stack().flex_1()),
|
.child(h_stack().flex_1()),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 {
|
impl FocusableView for ProjectSearchView {
|
||||||
fn focus_handle(&self, cx: &AppContext) -> gpui::FocusHandle {
|
fn focus_handle(&self, cx: &AppContext) -> gpui::FocusHandle {
|
||||||
self.results_editor.focus_handle(cx)
|
self.results_editor.focus_handle(cx)
|
||||||
|
@ -1256,7 +1147,7 @@ impl ProjectSearchView {
|
||||||
fn landing_text_minor(&self) -> SharedString {
|
fn landing_text_minor(&self) -> SharedString {
|
||||||
match self.current_mode {
|
match self.current_mode {
|
||||||
SearchMode::Text | SearchMode::Regex => "Include/exclude specific paths with the filter option. Matching exact word and/or casing is available too.".into(),
|
SearchMode::Text | SearchMode::Regex => "Include/exclude specific paths with the filter option. Matching exact word and/or casing is available too.".into(),
|
||||||
SearchMode::Semantic => ".Simply explain the code you are looking to find. ex. 'prompt user for permissions to index their project'".into()
|
SearchMode::Semantic => "\nSimply explain the code you are looking to find. ex. 'prompt user for permissions to index their project'".into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1277,8 +1168,8 @@ impl ProjectSearchBar {
|
||||||
fn cycle_mode(&self, _: &CycleMode, cx: &mut ViewContext<Self>) {
|
fn cycle_mode(&self, _: &CycleMode, cx: &mut ViewContext<Self>) {
|
||||||
if let Some(view) = self.active_project_search.as_ref() {
|
if let Some(view) = self.active_project_search.as_ref() {
|
||||||
view.update(cx, |this, cx| {
|
view.update(cx, |this, cx| {
|
||||||
// todo: po: 2nd argument of `next_mode` should be `SemanticIndex::enabled(cx))`, but we need to flesh out port of semantic_index first.
|
let new_mode =
|
||||||
let new_mode = crate::mode::next_mode(&this.current_mode, false);
|
crate::mode::next_mode(&this.current_mode, SemanticIndex::enabled(cx));
|
||||||
this.activate_search_mode(new_mode, cx);
|
this.activate_search_mode(new_mode, cx);
|
||||||
let editor_handle = this.query_editor.focus_handle(cx);
|
let editor_handle = this.query_editor.focus_handle(cx);
|
||||||
cx.focus(&editor_handle);
|
cx.focus(&editor_handle);
|
||||||
|
@ -1548,7 +1439,7 @@ impl Render for ProjectSearchBar {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
let search = search.read(cx);
|
let search = search.read(cx);
|
||||||
|
let semantic_is_available = SemanticIndex::enabled(cx);
|
||||||
let query_column = v_stack()
|
let query_column = v_stack()
|
||||||
//.flex_1()
|
//.flex_1()
|
||||||
.child(
|
.child(
|
||||||
|
@ -1578,42 +1469,51 @@ impl Render for ProjectSearchBar {
|
||||||
.unwrap_or_default(),
|
.unwrap_or_default(),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.child(
|
.when(search.current_mode != SearchMode::Semantic, |this| {
|
||||||
IconButton::new(
|
this.child(
|
||||||
"project-search-case-sensitive",
|
IconButton::new(
|
||||||
Icon::CaseSensitive,
|
"project-search-case-sensitive",
|
||||||
)
|
Icon::CaseSensitive,
|
||||||
.tooltip(|cx| {
|
|
||||||
Tooltip::for_action(
|
|
||||||
"Toggle case sensitive",
|
|
||||||
&ToggleCaseSensitive,
|
|
||||||
cx,
|
|
||||||
)
|
)
|
||||||
})
|
|
||||||
.selected(self.is_option_enabled(SearchOptions::CASE_SENSITIVE, cx))
|
|
||||||
.on_click(cx.listener(
|
|
||||||
|this, _, cx| {
|
|
||||||
this.toggle_search_option(
|
|
||||||
SearchOptions::CASE_SENSITIVE,
|
|
||||||
cx,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
)),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
IconButton::new("project-search-whole-word", Icon::WholeWord)
|
|
||||||
.tooltip(|cx| {
|
.tooltip(|cx| {
|
||||||
Tooltip::for_action(
|
Tooltip::for_action(
|
||||||
"Toggle whole word",
|
"Toggle case sensitive",
|
||||||
&ToggleWholeWord,
|
&ToggleCaseSensitive,
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.selected(self.is_option_enabled(SearchOptions::WHOLE_WORD, cx))
|
.selected(
|
||||||
.on_click(cx.listener(|this, _, cx| {
|
self.is_option_enabled(SearchOptions::CASE_SENSITIVE, cx),
|
||||||
this.toggle_search_option(SearchOptions::WHOLE_WORD, cx);
|
)
|
||||||
})),
|
.on_click(cx.listener(
|
||||||
),
|
|this, _, cx| {
|
||||||
|
this.toggle_search_option(
|
||||||
|
SearchOptions::CASE_SENSITIVE,
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
IconButton::new("project-search-whole-word", Icon::WholeWord)
|
||||||
|
.tooltip(|cx| {
|
||||||
|
Tooltip::for_action(
|
||||||
|
"Toggle whole word",
|
||||||
|
&ToggleWholeWord,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.selected(
|
||||||
|
self.is_option_enabled(SearchOptions::WHOLE_WORD, cx),
|
||||||
|
)
|
||||||
|
.on_click(cx.listener(|this, _, cx| {
|
||||||
|
this.toggle_search_option(
|
||||||
|
SearchOptions::WHOLE_WORD,
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
})),
|
||||||
|
)
|
||||||
|
}),
|
||||||
)
|
)
|
||||||
.border_2()
|
.border_2()
|
||||||
.bg(white())
|
.bg(white())
|
||||||
|
@ -1668,7 +1568,23 @@ impl Render for ProjectSearchBar {
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
),
|
)
|
||||||
|
.when(semantic_is_available, |this| {
|
||||||
|
this.child(
|
||||||
|
Button::new("project-search-semantic-button", "Semantic")
|
||||||
|
.selected(search.current_mode == SearchMode::Semantic)
|
||||||
|
.on_click(cx.listener(|this, _, cx| {
|
||||||
|
this.activate_search_mode(SearchMode::Semantic, cx)
|
||||||
|
}))
|
||||||
|
.tooltip(|cx| {
|
||||||
|
Tooltip::for_action(
|
||||||
|
"Toggle semantic search",
|
||||||
|
&ActivateSemanticMode,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}),
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
IconButton::new("project-search-toggle-replace", Icon::Replace)
|
IconButton::new("project-search-toggle-replace", Icon::Replace)
|
||||||
|
|
Loading…
Reference in a new issue