Bring back semantic search UI

This commit is contained in:
Piotr Osiewicz 2023-12-13 12:53:53 +01:00 committed by Max Brunsfeld
parent 801125974a
commit 0e19da3107

View File

@ -1,7 +1,8 @@
use crate::{
history::SearchHistory, mode::SearchMode, ActivateRegexMode, ActivateTextMode, CycleMode,
NextHistoryQuery, PreviousHistoryQuery, ReplaceAll, ReplaceNext, SearchOptions,
SelectNextMatch, SelectPrevMatch, ToggleCaseSensitive, ToggleReplace, ToggleWholeWord,
history::SearchHistory, mode::SearchMode, ActivateRegexMode, ActivateSemanticMode,
ActivateTextMode, CycleMode, NextHistoryQuery, PreviousHistoryQuery, ReplaceAll, ReplaceNext,
SearchOptions, SelectNextMatch, SelectPrevMatch, ToggleCaseSensitive, ToggleReplace,
ToggleWholeWord,
};
use anyhow::{Context as _, Result};
use collections::HashMap;
@ -29,7 +30,7 @@ use std::{
mem,
ops::{Not, Range},
path::PathBuf,
time::Duration,
time::{Duration, Instant},
};
use ui::{
@ -283,196 +284,86 @@ impl Render for ProjectSearchView {
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 {
let mut major_text = if is_search_underway {
Label::new("Searching...")
} else if has_no_results {
Label::new("No results for a given query")
Label::new("No results")
} else {
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 middle_text = div()
.items_center()
.max_w_96()
.child(Label::new(self.landing_text_minor()).size(LabelSize::Small));
let minor_text: Option<SharedString> = if let Some(no_results) = model.no_results {
if model.pending_search.is_none() && no_results {
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(
h_stack()
.size_full()
.justify_center()
.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()),
)
}
}
}
// 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)
@ -1256,7 +1147,7 @@ impl ProjectSearchView {
fn landing_text_minor(&self) -> SharedString {
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::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>) {
if let Some(view) = self.active_project_search.as_ref() {
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 = crate::mode::next_mode(&this.current_mode, false);
let new_mode =
crate::mode::next_mode(&this.current_mode, SemanticIndex::enabled(cx));
this.activate_search_mode(new_mode, cx);
let editor_handle = this.query_editor.focus_handle(cx);
cx.focus(&editor_handle);
@ -1548,7 +1439,7 @@ impl Render for ProjectSearchBar {
});
}
let search = search.read(cx);
let semantic_is_available = SemanticIndex::enabled(cx);
let query_column = v_stack()
//.flex_1()
.child(
@ -1578,42 +1469,51 @@ impl Render for ProjectSearchBar {
.unwrap_or_default(),
),
)
.child(
IconButton::new(
"project-search-case-sensitive",
Icon::CaseSensitive,
)
.tooltip(|cx| {
Tooltip::for_action(
"Toggle case sensitive",
&ToggleCaseSensitive,
cx,
.when(search.current_mode != SearchMode::Semantic, |this| {
this.child(
IconButton::new(
"project-search-case-sensitive",
Icon::CaseSensitive,
)
})
.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::for_action(
"Toggle whole word",
&ToggleWholeWord,
"Toggle case sensitive",
&ToggleCaseSensitive,
cx,
)
})
.selected(self.is_option_enabled(SearchOptions::WHOLE_WORD, cx))
.on_click(cx.listener(|this, _, cx| {
this.toggle_search_option(SearchOptions::WHOLE_WORD, 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::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()
.bg(white())
@ -1668,7 +1568,23 @@ impl Render for ProjectSearchBar {
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(
IconButton::new("project-search-toggle-replace", Icon::Replace)