Revert "Remove semantic search UI" (#2865)
This reverts commit c0f042b39a
, where I
deleted the semantic-search related UI code.
Apologies to @KCaverly for the misunderstanding
Release Notes:
- N/A
This commit is contained in:
commit
29c339e3b4
7 changed files with 305 additions and 74 deletions
|
@ -1,3 +1,5 @@
|
||||||
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
use pathfinder_geometry::{rect::RectF, vector::Vector2F};
|
use pathfinder_geometry::{rect::RectF, vector::Vector2F};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
@ -106,8 +108,7 @@ impl<V: View> Component<V> for ElementAdapter<V> {
|
||||||
pub struct ComponentAdapter<V: View, E> {
|
pub struct ComponentAdapter<V: View, E> {
|
||||||
component: Option<E>,
|
component: Option<E>,
|
||||||
element: Option<AnyElement<V>>,
|
element: Option<AnyElement<V>>,
|
||||||
#[cfg(debug_assertions)]
|
phantom: PhantomData<V>,
|
||||||
_component_name: &'static str,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<E, V: View> ComponentAdapter<V, E> {
|
impl<E, V: View> ComponentAdapter<V, E> {
|
||||||
|
@ -115,8 +116,7 @@ impl<E, V: View> ComponentAdapter<V, E> {
|
||||||
Self {
|
Self {
|
||||||
component: Some(e),
|
component: Some(e),
|
||||||
element: None,
|
element: None,
|
||||||
#[cfg(debug_assertions)]
|
phantom: PhantomData,
|
||||||
_component_name: std::any::type_name::<E>(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -133,8 +133,12 @@ impl<V: View, C: Component<V> + 'static> Element<V> for ComponentAdapter<V, C> {
|
||||||
cx: &mut LayoutContext<V>,
|
cx: &mut LayoutContext<V>,
|
||||||
) -> (Vector2F, Self::LayoutState) {
|
) -> (Vector2F, Self::LayoutState) {
|
||||||
if self.element.is_none() {
|
if self.element.is_none() {
|
||||||
let component = self.component.take().unwrap();
|
let element = self
|
||||||
self.element = Some(component.render(view, cx.view_context()));
|
.component
|
||||||
|
.take()
|
||||||
|
.expect("Component can only be rendered once")
|
||||||
|
.render(view, cx.view_context());
|
||||||
|
self.element = Some(element);
|
||||||
}
|
}
|
||||||
let constraint = self.element.as_mut().unwrap().layout(constraint, view, cx);
|
let constraint = self.element.as_mut().unwrap().layout(constraint, view, cx);
|
||||||
(constraint, ())
|
(constraint, ())
|
||||||
|
@ -151,7 +155,7 @@ impl<V: View, C: Component<V> + 'static> Element<V> for ComponentAdapter<V, C> {
|
||||||
) -> Self::PaintState {
|
) -> Self::PaintState {
|
||||||
self.element
|
self.element
|
||||||
.as_mut()
|
.as_mut()
|
||||||
.unwrap()
|
.expect("Layout should always be called before paint")
|
||||||
.paint(scene, bounds.origin(), visible_bounds, view, cx)
|
.paint(scene, bounds.origin(), visible_bounds, view, cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -167,8 +171,7 @@ impl<V: View, C: Component<V> + 'static> Element<V> for ComponentAdapter<V, C> {
|
||||||
) -> Option<RectF> {
|
) -> Option<RectF> {
|
||||||
self.element
|
self.element
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.unwrap()
|
.and_then(|el| el.rect_for_text_range(range_utf16, view, cx))
|
||||||
.rect_for_text_range(range_utf16, view, cx)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn debug(
|
fn debug(
|
||||||
|
@ -179,16 +182,9 @@ impl<V: View, C: Component<V> + 'static> Element<V> for ComponentAdapter<V, C> {
|
||||||
view: &V,
|
view: &V,
|
||||||
cx: &ViewContext<V>,
|
cx: &ViewContext<V>,
|
||||||
) -> serde_json::Value {
|
) -> serde_json::Value {
|
||||||
#[cfg(debug_assertions)]
|
|
||||||
let component_name = self._component_name;
|
|
||||||
|
|
||||||
#[cfg(not(debug_assertions))]
|
|
||||||
let component_name = "Unknown";
|
|
||||||
|
|
||||||
serde_json::json!({
|
serde_json::json!({
|
||||||
"type": "ComponentAdapter",
|
"type": "ComponentAdapter",
|
||||||
"child": self.element.as_ref().unwrap().debug(view, cx),
|
"child": self.element.as_ref().map(|el| el.debug(view, cx)),
|
||||||
"component_name": component_name
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -523,6 +523,11 @@ impl BufferSearchBar {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn activate_search_mode(&mut self, mode: SearchMode, cx: &mut ViewContext<Self>) {
|
pub fn activate_search_mode(&mut self, mode: SearchMode, cx: &mut ViewContext<Self>) {
|
||||||
|
assert_ne!(
|
||||||
|
mode,
|
||||||
|
SearchMode::Semantic,
|
||||||
|
"Semantic search is not supported in buffer search"
|
||||||
|
);
|
||||||
if mode == self.current_mode {
|
if mode == self.current_mode {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -797,7 +802,7 @@ impl BufferSearchBar {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn cycle_mode(&mut self, _: &CycleMode, cx: &mut ViewContext<Self>) {
|
fn cycle_mode(&mut self, _: &CycleMode, cx: &mut ViewContext<Self>) {
|
||||||
self.activate_search_mode(next_mode(&self.current_mode), cx);
|
self.activate_search_mode(next_mode(&self.current_mode, false), cx);
|
||||||
}
|
}
|
||||||
fn cycle_mode_on_pane(pane: &mut Pane, action: &CycleMode, cx: &mut ViewContext<Pane>) {
|
fn cycle_mode_on_pane(pane: &mut Pane, action: &CycleMode, cx: &mut ViewContext<Pane>) {
|
||||||
let mut should_propagate = true;
|
let mut should_propagate = true;
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
use gpui::Action;
|
use gpui::Action;
|
||||||
|
|
||||||
use crate::{ActivateRegexMode, ActivateTextMode};
|
use crate::{ActivateRegexMode, ActivateSemanticMode, ActivateTextMode};
|
||||||
// TODO: Update the default search mode to get from config
|
// TODO: Update the default search mode to get from config
|
||||||
#[derive(Copy, Clone, Debug, Default, PartialEq)]
|
#[derive(Copy, Clone, Debug, Default, PartialEq)]
|
||||||
pub enum SearchMode {
|
pub enum SearchMode {
|
||||||
#[default]
|
#[default]
|
||||||
Text,
|
Text,
|
||||||
|
Semantic,
|
||||||
Regex,
|
Regex,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,6 +20,7 @@ impl SearchMode {
|
||||||
pub(crate) fn label(&self) -> &'static str {
|
pub(crate) fn label(&self) -> &'static str {
|
||||||
match self {
|
match self {
|
||||||
SearchMode::Text => "Text",
|
SearchMode::Text => "Text",
|
||||||
|
SearchMode::Semantic => "Semantic",
|
||||||
SearchMode::Regex => "Regex",
|
SearchMode::Regex => "Regex",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,6 +28,7 @@ impl SearchMode {
|
||||||
pub(crate) fn region_id(&self) -> usize {
|
pub(crate) fn region_id(&self) -> usize {
|
||||||
match self {
|
match self {
|
||||||
SearchMode::Text => 3,
|
SearchMode::Text => 3,
|
||||||
|
SearchMode::Semantic => 4,
|
||||||
SearchMode::Regex => 5,
|
SearchMode::Regex => 5,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -33,6 +36,7 @@ impl SearchMode {
|
||||||
pub(crate) fn tooltip_text(&self) -> &'static str {
|
pub(crate) fn tooltip_text(&self) -> &'static str {
|
||||||
match self {
|
match self {
|
||||||
SearchMode::Text => "Activate Text Search",
|
SearchMode::Text => "Activate Text Search",
|
||||||
|
SearchMode::Semantic => "Activate Semantic Search",
|
||||||
SearchMode::Regex => "Activate Regex Search",
|
SearchMode::Regex => "Activate Regex Search",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -40,6 +44,7 @@ impl SearchMode {
|
||||||
pub(crate) fn activate_action(&self) -> Box<dyn Action> {
|
pub(crate) fn activate_action(&self) -> Box<dyn Action> {
|
||||||
match self {
|
match self {
|
||||||
SearchMode::Text => Box::new(ActivateTextMode),
|
SearchMode::Text => Box::new(ActivateTextMode),
|
||||||
|
SearchMode::Semantic => Box::new(ActivateSemanticMode),
|
||||||
SearchMode::Regex => Box::new(ActivateRegexMode),
|
SearchMode::Regex => Box::new(ActivateRegexMode),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -48,6 +53,7 @@ impl SearchMode {
|
||||||
match self {
|
match self {
|
||||||
SearchMode::Regex => true,
|
SearchMode::Regex => true,
|
||||||
SearchMode::Text => true,
|
SearchMode::Text => true,
|
||||||
|
SearchMode::Semantic => true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,14 +67,22 @@ impl SearchMode {
|
||||||
pub(crate) fn button_side(&self) -> Option<Side> {
|
pub(crate) fn button_side(&self) -> Option<Side> {
|
||||||
match self {
|
match self {
|
||||||
SearchMode::Text => Some(Side::Left),
|
SearchMode::Text => Some(Side::Left),
|
||||||
|
SearchMode::Semantic => None,
|
||||||
SearchMode::Regex => Some(Side::Right),
|
SearchMode::Regex => Some(Side::Right),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn next_mode(mode: &SearchMode) -> SearchMode {
|
pub(crate) fn next_mode(mode: &SearchMode, semantic_enabled: bool) -> SearchMode {
|
||||||
|
let next_text_state = if semantic_enabled {
|
||||||
|
SearchMode::Semantic
|
||||||
|
} else {
|
||||||
|
SearchMode::Regex
|
||||||
|
};
|
||||||
|
|
||||||
match mode {
|
match mode {
|
||||||
SearchMode::Text => SearchMode::Regex,
|
SearchMode::Text => next_text_state,
|
||||||
|
SearchMode::Semantic => SearchMode::Regex,
|
||||||
SearchMode::Regex => SearchMode::Text,
|
SearchMode::Regex => SearchMode::Text,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,10 +2,10 @@ use crate::{
|
||||||
history::SearchHistory,
|
history::SearchHistory,
|
||||||
mode::SearchMode,
|
mode::SearchMode,
|
||||||
search_bar::{render_nav_button, render_option_button_icon, render_search_mode_button},
|
search_bar::{render_nav_button, render_option_button_icon, render_search_mode_button},
|
||||||
CycleMode, NextHistoryQuery, PreviousHistoryQuery, SearchOptions, SelectNextMatch,
|
ActivateRegexMode, CycleMode, NextHistoryQuery, PreviousHistoryQuery, SearchOptions,
|
||||||
SelectPrevMatch, ToggleCaseSensitive, ToggleWholeWord,
|
SelectNextMatch, SelectPrevMatch, ToggleCaseSensitive, ToggleWholeWord,
|
||||||
};
|
};
|
||||||
use anyhow::Context;
|
use anyhow::{Context, Result};
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
use editor::{
|
use editor::{
|
||||||
items::active_match_index, scroll::autoscroll::Autoscroll, Anchor, Editor, MultiBuffer,
|
items::active_match_index, scroll::autoscroll::Autoscroll, Anchor, Editor, MultiBuffer,
|
||||||
|
@ -13,6 +13,8 @@ use editor::{
|
||||||
};
|
};
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
|
|
||||||
|
use gpui::platform::PromptLevel;
|
||||||
|
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions, elements::*, platform::MouseButton, Action, AnyElement, AnyViewHandle, AppContext,
|
actions, elements::*, platform::MouseButton, Action, AnyElement, AnyViewHandle, AppContext,
|
||||||
Entity, ModelContext, ModelHandle, Subscription, Task, View, ViewContext, ViewHandle,
|
Entity, ModelContext, ModelHandle, Subscription, Task, View, ViewContext, ViewHandle,
|
||||||
|
@ -20,10 +22,12 @@ use gpui::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use menu::Confirm;
|
use menu::Confirm;
|
||||||
|
use postage::stream::Stream;
|
||||||
use project::{
|
use project::{
|
||||||
search::{PathMatcher, SearchQuery},
|
search::{PathMatcher, SearchInputs, SearchQuery},
|
||||||
Entry, Project,
|
Entry, Project,
|
||||||
};
|
};
|
||||||
|
use semantic_index::SemanticIndex;
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
use std::{
|
use std::{
|
||||||
any::{Any, TypeId},
|
any::{Any, TypeId},
|
||||||
|
@ -60,7 +64,7 @@ pub fn init(cx: &mut AppContext) {
|
||||||
cx.add_action(ProjectSearchBar::cycle_mode);
|
cx.add_action(ProjectSearchBar::cycle_mode);
|
||||||
cx.add_action(ProjectSearchBar::next_history_query);
|
cx.add_action(ProjectSearchBar::next_history_query);
|
||||||
cx.add_action(ProjectSearchBar::previous_history_query);
|
cx.add_action(ProjectSearchBar::previous_history_query);
|
||||||
// cx.add_action(ProjectSearchBar::activate_regex_mode);
|
cx.add_action(ProjectSearchBar::activate_regex_mode);
|
||||||
cx.capture_action(ProjectSearchBar::tab);
|
cx.capture_action(ProjectSearchBar::tab);
|
||||||
cx.capture_action(ProjectSearchBar::tab_previous);
|
cx.capture_action(ProjectSearchBar::tab_previous);
|
||||||
add_toggle_option_action::<ToggleCaseSensitive>(SearchOptions::CASE_SENSITIVE, cx);
|
add_toggle_option_action::<ToggleCaseSensitive>(SearchOptions::CASE_SENSITIVE, cx);
|
||||||
|
@ -114,6 +118,8 @@ pub struct ProjectSearchView {
|
||||||
model: ModelHandle<ProjectSearch>,
|
model: ModelHandle<ProjectSearch>,
|
||||||
query_editor: ViewHandle<Editor>,
|
query_editor: ViewHandle<Editor>,
|
||||||
results_editor: ViewHandle<Editor>,
|
results_editor: ViewHandle<Editor>,
|
||||||
|
semantic_state: Option<SemanticSearchState>,
|
||||||
|
semantic_permissioned: Option<bool>,
|
||||||
search_options: SearchOptions,
|
search_options: SearchOptions,
|
||||||
panels_with_errors: HashSet<InputPanel>,
|
panels_with_errors: HashSet<InputPanel>,
|
||||||
active_match_index: Option<usize>,
|
active_match_index: Option<usize>,
|
||||||
|
@ -125,6 +131,12 @@ pub struct ProjectSearchView {
|
||||||
current_mode: SearchMode,
|
current_mode: SearchMode,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct SemanticSearchState {
|
||||||
|
file_count: usize,
|
||||||
|
outstanding_file_count: usize,
|
||||||
|
_progress_task: Task<()>,
|
||||||
|
}
|
||||||
|
|
||||||
pub struct ProjectSearchBar {
|
pub struct ProjectSearchBar {
|
||||||
active_project_search: Option<ViewHandle<ProjectSearchView>>,
|
active_project_search: Option<ViewHandle<ProjectSearchView>>,
|
||||||
subscription: Option<Subscription>,
|
subscription: Option<Subscription>,
|
||||||
|
@ -206,6 +218,60 @@ impl ProjectSearch {
|
||||||
}));
|
}));
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn semantic_search(&mut self, inputs: &SearchInputs, cx: &mut ModelContext<Self>) {
|
||||||
|
let search = SemanticIndex::global(cx).map(|index| {
|
||||||
|
index.update(cx, |semantic_index, cx| {
|
||||||
|
semantic_index.search_project(
|
||||||
|
self.project.clone(),
|
||||||
|
inputs.as_str().to_owned(),
|
||||||
|
10,
|
||||||
|
inputs.files_to_include().to_vec(),
|
||||||
|
inputs.files_to_exclude().to_vec(),
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
});
|
||||||
|
self.search_id += 1;
|
||||||
|
self.match_ranges.clear();
|
||||||
|
self.search_history.add(inputs.as_str().to_string());
|
||||||
|
self.no_results = Some(true);
|
||||||
|
self.pending_search = Some(cx.spawn(|this, mut cx| async move {
|
||||||
|
let results = search?.await.log_err()?;
|
||||||
|
|
||||||
|
let (_task, mut match_ranges) = this.update(&mut cx, |this, cx| {
|
||||||
|
this.excerpts.update(cx, |excerpts, cx| {
|
||||||
|
excerpts.clear(cx);
|
||||||
|
|
||||||
|
let matches = results
|
||||||
|
.into_iter()
|
||||||
|
.map(|result| (result.buffer, vec![result.range.start..result.range.start]))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
excerpts.stream_excerpts_with_context_lines(matches, 3, cx)
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
while let Some(match_range) = match_ranges.next().await {
|
||||||
|
this.update(&mut cx, |this, cx| {
|
||||||
|
this.match_ranges.push(match_range);
|
||||||
|
while let Ok(Some(match_range)) = match_ranges.try_next() {
|
||||||
|
this.match_ranges.push(match_range);
|
||||||
|
}
|
||||||
|
this.no_results = Some(false);
|
||||||
|
cx.notify();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.update(&mut cx, |this, cx| {
|
||||||
|
this.pending_search.take();
|
||||||
|
cx.notify();
|
||||||
|
});
|
||||||
|
|
||||||
|
None
|
||||||
|
}));
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
@ -245,10 +311,27 @@ impl View for ProjectSearchView {
|
||||||
} else {
|
} else {
|
||||||
match current_mode {
|
match current_mode {
|
||||||
SearchMode::Text => Cow::Borrowed("Text search all files and folders"),
|
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"),
|
SearchMode::Regex => Cow::Borrowed("Regex search all files and folders"),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let semantic_status = if let Some(semantic) = &self.semantic_state {
|
||||||
|
if semantic.outstanding_file_count > 0 {
|
||||||
|
format!(
|
||||||
|
"Indexing: {} of {}...",
|
||||||
|
semantic.file_count - semantic.outstanding_file_count,
|
||||||
|
semantic.file_count
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
"Indexing complete".to_string()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
"Indexing: ...".to_string()
|
||||||
|
};
|
||||||
|
|
||||||
let minor_text = if let Some(no_results) = model.no_results {
|
let minor_text = if let Some(no_results) = model.no_results {
|
||||||
if model.pending_search.is_none() && no_results {
|
if model.pending_search.is_none() && no_results {
|
||||||
vec!["No results found in this project for the provided query".to_owned()]
|
vec!["No results found in this project for the provided query".to_owned()]
|
||||||
|
@ -256,11 +339,19 @@ impl View for ProjectSearchView {
|
||||||
vec![]
|
vec![]
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
vec![
|
match current_mode {
|
||||||
"".to_owned(),
|
SearchMode::Semantic => vec![
|
||||||
"Include/exclude specific paths with the filter option.".to_owned(),
|
"".to_owned(),
|
||||||
"Matching exact word and/or casing is available too.".to_owned(),
|
semantic_status,
|
||||||
]
|
"Simply explain the code you are looking to find.".to_owned(),
|
||||||
|
"ex. 'prompt user for permissions to index their project'".to_owned(),
|
||||||
|
],
|
||||||
|
_ => vec![
|
||||||
|
"".to_owned(),
|
||||||
|
"Include/exclude specific paths with the filter option.".to_owned(),
|
||||||
|
"Matching exact word and/or casing is available too.".to_owned(),
|
||||||
|
],
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let previous_query_keystrokes =
|
let previous_query_keystrokes =
|
||||||
|
@ -539,6 +630,49 @@ impl ProjectSearchView {
|
||||||
self.search_options.toggle(option);
|
self.search_options.toggle(option);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn index_project(&mut self, cx: &mut ViewContext<Self>) {
|
||||||
|
if let Some(semantic_index) = SemanticIndex::global(cx) {
|
||||||
|
// Semantic search uses no options
|
||||||
|
self.search_options = SearchOptions::none();
|
||||||
|
|
||||||
|
let project = self.model.read(cx).project.clone();
|
||||||
|
let index_task = semantic_index.update(cx, |semantic_index, cx| {
|
||||||
|
semantic_index.index_project(project, cx)
|
||||||
|
});
|
||||||
|
|
||||||
|
cx.spawn(|search_view, mut cx| async move {
|
||||||
|
let (files_to_index, mut files_remaining_rx) = index_task.await?;
|
||||||
|
|
||||||
|
search_view.update(&mut cx, |search_view, cx| {
|
||||||
|
cx.notify();
|
||||||
|
search_view.semantic_state = Some(SemanticSearchState {
|
||||||
|
file_count: files_to_index,
|
||||||
|
outstanding_file_count: files_to_index,
|
||||||
|
_progress_task: cx.spawn(|search_view, mut cx| async move {
|
||||||
|
while let Some(count) = files_remaining_rx.recv().await {
|
||||||
|
search_view
|
||||||
|
.update(&mut cx, |search_view, cx| {
|
||||||
|
if let Some(semantic_search_state) =
|
||||||
|
&mut search_view.semantic_state
|
||||||
|
{
|
||||||
|
semantic_search_state.outstanding_file_count = count;
|
||||||
|
cx.notify();
|
||||||
|
if count == 0 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
})?;
|
||||||
|
anyhow::Ok(())
|
||||||
|
})
|
||||||
|
.detach_and_log_err(cx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn clear_search(&mut self, cx: &mut ViewContext<Self>) {
|
fn clear_search(&mut self, cx: &mut ViewContext<Self>) {
|
||||||
self.model.update(cx, |model, cx| {
|
self.model.update(cx, |model, cx| {
|
||||||
model.pending_search = None;
|
model.pending_search = None;
|
||||||
|
@ -561,7 +695,61 @@ impl ProjectSearchView {
|
||||||
self.current_mode = mode;
|
self.current_mode = mode;
|
||||||
self.active_match_index = None;
|
self.active_match_index = None;
|
||||||
|
|
||||||
self.search(cx);
|
match mode {
|
||||||
|
SearchMode::Semantic => {
|
||||||
|
let has_permission = self.semantic_permissioned(cx);
|
||||||
|
self.active_match_index = None;
|
||||||
|
cx.spawn(|this, mut cx| async move {
|
||||||
|
let has_permission = has_permission.await?;
|
||||||
|
|
||||||
|
if !has_permission {
|
||||||
|
let mut answer = this.update(&mut cx, |this, cx| {
|
||||||
|
let project = this.model.read(cx).project.clone();
|
||||||
|
let project_name = project
|
||||||
|
.read(cx)
|
||||||
|
.worktree_root_names(cx)
|
||||||
|
.collect::<Vec<&str>>()
|
||||||
|
.join("/");
|
||||||
|
let is_plural =
|
||||||
|
project_name.chars().filter(|letter| *letter == '/').count() > 0;
|
||||||
|
let prompt_text = format!("Would you like to index the '{}' project{} for semantic search? This requires sending code to the OpenAI API", project_name,
|
||||||
|
if is_plural {
|
||||||
|
"s"
|
||||||
|
} else {""});
|
||||||
|
cx.prompt(
|
||||||
|
PromptLevel::Info,
|
||||||
|
prompt_text.as_str(),
|
||||||
|
&["Continue", "Cancel"],
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
if answer.next().await == Some(0) {
|
||||||
|
this.update(&mut cx, |this, _| {
|
||||||
|
this.semantic_permissioned = Some(true);
|
||||||
|
})?;
|
||||||
|
} else {
|
||||||
|
this.update(&mut cx, |this, cx| {
|
||||||
|
this.semantic_permissioned = Some(false);
|
||||||
|
debug_assert_ne!(previous_mode, SearchMode::Semantic, "Tried to re-enable semantic search mode after user modal was rejected");
|
||||||
|
this.activate_search_mode(previous_mode, cx);
|
||||||
|
})?;
|
||||||
|
return anyhow::Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.update(&mut cx, |this, cx| {
|
||||||
|
this.index_project(cx);
|
||||||
|
})?;
|
||||||
|
|
||||||
|
anyhow::Ok(())
|
||||||
|
}).detach_and_log_err(cx);
|
||||||
|
}
|
||||||
|
SearchMode::Regex | SearchMode::Text => {
|
||||||
|
self.semantic_state = None;
|
||||||
|
self.active_match_index = None;
|
||||||
|
self.search(cx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
@ -657,6 +845,8 @@ impl ProjectSearchView {
|
||||||
model,
|
model,
|
||||||
query_editor,
|
query_editor,
|
||||||
results_editor,
|
results_editor,
|
||||||
|
semantic_state: None,
|
||||||
|
semantic_permissioned: None,
|
||||||
search_options: options,
|
search_options: options,
|
||||||
panels_with_errors: HashSet::new(),
|
panels_with_errors: HashSet::new(),
|
||||||
active_match_index: None,
|
active_match_index: None,
|
||||||
|
@ -670,6 +860,18 @@ impl ProjectSearchView {
|
||||||
this
|
this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn semantic_permissioned(&mut self, cx: &mut ViewContext<Self>) -> Task<Result<bool>> {
|
||||||
|
if let Some(value) = self.semantic_permissioned {
|
||||||
|
return Task::ready(Ok(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
SemanticIndex::global(cx)
|
||||||
|
.map(|semantic| {
|
||||||
|
let project = self.model.read(cx).project.clone();
|
||||||
|
semantic.update(cx, |this, cx| this.project_previously_indexed(project, cx))
|
||||||
|
})
|
||||||
|
.unwrap_or(Task::ready(Ok(false)))
|
||||||
|
}
|
||||||
pub fn new_search_in_directory(
|
pub fn new_search_in_directory(
|
||||||
workspace: &mut Workspace,
|
workspace: &mut Workspace,
|
||||||
dir_entry: &Entry,
|
dir_entry: &Entry,
|
||||||
|
@ -745,8 +947,26 @@ impl ProjectSearchView {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn search(&mut self, cx: &mut ViewContext<Self>) {
|
fn search(&mut self, cx: &mut ViewContext<Self>) {
|
||||||
if let Some(query) = self.build_search_query(cx) {
|
let mode = self.current_mode;
|
||||||
self.model.update(cx, |model, cx| model.search(query, cx));
|
match mode {
|
||||||
|
SearchMode::Semantic => {
|
||||||
|
if let Some(semantic) = &mut self.semantic_state {
|
||||||
|
if semantic.outstanding_file_count > 0 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(query) = self.build_search_query(cx) {
|
||||||
|
self.model
|
||||||
|
.update(cx, |model, cx| model.semantic_search(query.as_inner(), cx));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => {
|
||||||
|
if let Some(query) = self.build_search_query(cx) {
|
||||||
|
self.model.update(cx, |model, cx| model.search(query, cx));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -946,7 +1166,8 @@ impl ProjectSearchBar {
|
||||||
.and_then(|item| item.downcast::<ProjectSearchView>())
|
.and_then(|item| item.downcast::<ProjectSearchView>())
|
||||||
{
|
{
|
||||||
search_view.update(cx, |this, cx| {
|
search_view.update(cx, |this, cx| {
|
||||||
let new_mode = crate::mode::next_mode(&this.current_mode);
|
let new_mode =
|
||||||
|
crate::mode::next_mode(&this.current_mode, SemanticIndex::enabled(cx));
|
||||||
this.activate_search_mode(new_mode, cx);
|
this.activate_search_mode(new_mode, cx);
|
||||||
cx.focus(&this.query_editor);
|
cx.focus(&this.query_editor);
|
||||||
})
|
})
|
||||||
|
@ -1071,18 +1292,18 @@ impl ProjectSearchBar {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// fn activate_regex_mode(pane: &mut Pane, _: &ActivateRegexMode, cx: &mut ViewContext<Pane>) {
|
fn activate_regex_mode(pane: &mut Pane, _: &ActivateRegexMode, cx: &mut ViewContext<Pane>) {
|
||||||
// if let Some(search_view) = pane
|
if let Some(search_view) = pane
|
||||||
// .active_item()
|
.active_item()
|
||||||
// .and_then(|item| item.downcast::<ProjectSearchView>())
|
.and_then(|item| item.downcast::<ProjectSearchView>())
|
||||||
// {
|
{
|
||||||
// search_view.update(cx, |view, cx| {
|
search_view.update(cx, |view, cx| {
|
||||||
// view.activate_search_mode(SearchMode::Regex, cx)
|
view.activate_search_mode(SearchMode::Regex, cx)
|
||||||
// });
|
});
|
||||||
// } else {
|
} else {
|
||||||
// cx.propagate_action();
|
cx.propagate_action();
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
|
|
||||||
fn toggle_filters(&mut self, cx: &mut ViewContext<Self>) -> bool {
|
fn toggle_filters(&mut self, cx: &mut ViewContext<Self>) -> bool {
|
||||||
if let Some(search_view) = self.active_project_search.as_ref() {
|
if let Some(search_view) = self.active_project_search.as_ref() {
|
||||||
|
@ -1195,7 +1416,8 @@ impl View for ProjectSearchBar {
|
||||||
},
|
},
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
|
let search = _search.read(cx);
|
||||||
|
let is_semantic_disabled = search.semantic_state.is_none();
|
||||||
let render_option_button_icon = |path, option, cx: &mut ViewContext<Self>| {
|
let render_option_button_icon = |path, option, cx: &mut ViewContext<Self>| {
|
||||||
crate::search_bar::render_option_button_icon(
|
crate::search_bar::render_option_button_icon(
|
||||||
self.is_option_enabled(option, cx),
|
self.is_option_enabled(option, cx),
|
||||||
|
@ -1209,17 +1431,17 @@ impl View for ProjectSearchBar {
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
let case_sensitive = render_option_button_icon(
|
let case_sensitive = is_semantic_disabled.then(|| {
|
||||||
"icons/case_insensitive_12.svg",
|
render_option_button_icon(
|
||||||
SearchOptions::CASE_SENSITIVE,
|
"icons/case_insensitive_12.svg",
|
||||||
cx,
|
SearchOptions::CASE_SENSITIVE,
|
||||||
);
|
cx,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
let whole_word = render_option_button_icon(
|
let whole_word = is_semantic_disabled.then(|| {
|
||||||
"icons/word_search_12.svg",
|
render_option_button_icon("icons/word_search_12.svg", SearchOptions::WHOLE_WORD, cx)
|
||||||
SearchOptions::WHOLE_WORD,
|
});
|
||||||
cx,
|
|
||||||
);
|
|
||||||
|
|
||||||
let search = _search.read(cx);
|
let search = _search.read(cx);
|
||||||
let icon_style = theme.search.editor_icon.clone();
|
let icon_style = theme.search.editor_icon.clone();
|
||||||
|
@ -1235,8 +1457,8 @@ impl View for ProjectSearchBar {
|
||||||
.with_child(
|
.with_child(
|
||||||
Flex::row()
|
Flex::row()
|
||||||
.with_child(filter_button)
|
.with_child(filter_button)
|
||||||
.with_child(case_sensitive)
|
.with_children(case_sensitive)
|
||||||
.with_child(whole_word)
|
.with_children(whole_word)
|
||||||
.flex(1., false)
|
.flex(1., false)
|
||||||
.constrained()
|
.constrained()
|
||||||
.contained(),
|
.contained(),
|
||||||
|
@ -1335,7 +1557,8 @@ impl View for ProjectSearchBar {
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
let is_active = search.active_match_index.is_some();
|
let is_active = search.active_match_index.is_some();
|
||||||
|
let semantic_index = SemanticIndex::enabled(cx)
|
||||||
|
.then(|| search_button_for_mode(SearchMode::Semantic, cx));
|
||||||
let nav_button_for_direction = |label, direction, cx: &mut ViewContext<Self>| {
|
let nav_button_for_direction = |label, direction, cx: &mut ViewContext<Self>| {
|
||||||
render_nav_button(
|
render_nav_button(
|
||||||
label,
|
label,
|
||||||
|
@ -1361,6 +1584,7 @@ impl View for ProjectSearchBar {
|
||||||
.with_child(
|
.with_child(
|
||||||
Flex::row()
|
Flex::row()
|
||||||
.with_child(search_button_for_mode(SearchMode::Text, cx))
|
.with_child(search_button_for_mode(SearchMode::Text, cx))
|
||||||
|
.with_children(semantic_index)
|
||||||
.with_child(search_button_for_mode(SearchMode::Regex, cx))
|
.with_child(search_button_for_mode(SearchMode::Regex, cx))
|
||||||
.contained()
|
.contained()
|
||||||
.with_style(theme.search.modes_container),
|
.with_style(theme.search.modes_container),
|
||||||
|
|
|
@ -8,9 +8,7 @@ use gpui::{
|
||||||
pub use mode::SearchMode;
|
pub use mode::SearchMode;
|
||||||
use project::search::SearchQuery;
|
use project::search::SearchQuery;
|
||||||
pub use project_search::{ProjectSearchBar, ProjectSearchView};
|
pub use project_search::{ProjectSearchBar, ProjectSearchView};
|
||||||
use theme::components::{
|
use theme::components::{action_button::ActionButton, ComponentExt, ToggleIconButtonStyle};
|
||||||
action_button::ActionButton, svg::Svg, ComponentExt, ToggleIconButtonStyle,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub mod buffer_search;
|
pub mod buffer_search;
|
||||||
mod history;
|
mod history;
|
||||||
|
@ -35,6 +33,7 @@ actions!(
|
||||||
NextHistoryQuery,
|
NextHistoryQuery,
|
||||||
PreviousHistoryQuery,
|
PreviousHistoryQuery,
|
||||||
ActivateTextMode,
|
ActivateTextMode,
|
||||||
|
ActivateSemanticMode,
|
||||||
ActivateRegexMode
|
ActivateRegexMode
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
@ -95,7 +94,7 @@ impl SearchOptions {
|
||||||
format!("Toggle {}", self.label()),
|
format!("Toggle {}", self.label()),
|
||||||
tooltip_style,
|
tooltip_style,
|
||||||
)
|
)
|
||||||
.with_contents(Svg::new(self.icon()))
|
.with_contents(theme::components::svg::Svg::new(self.icon()))
|
||||||
.toggleable(active)
|
.toggleable(active)
|
||||||
.with_style(button_style)
|
.with_style(button_style)
|
||||||
.element()
|
.element()
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
#![allow(non_snake_case, non_upper_case_globals)]
|
|
||||||
|
|
||||||
mod keymap_file;
|
mod keymap_file;
|
||||||
mod settings_file;
|
mod settings_file;
|
||||||
mod settings_store;
|
mod settings_store;
|
||||||
|
|
|
@ -175,13 +175,8 @@ pub mod action_button {
|
||||||
.on_click(MouseButton::Left, {
|
.on_click(MouseButton::Left, {
|
||||||
let action = self.action.boxed_clone();
|
let action = self.action.boxed_clone();
|
||||||
move |_, _, cx| {
|
move |_, _, cx| {
|
||||||
let window = cx.window();
|
cx.window()
|
||||||
let view = cx.view_id();
|
.dispatch_action(cx.view_id(), action.as_ref(), cx);
|
||||||
let action = action.boxed_clone();
|
|
||||||
cx.spawn(|_, mut cx| async move {
|
|
||||||
window.dispatch_action(view, action.as_ref(), &mut cx);
|
|
||||||
})
|
|
||||||
.detach();
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.with_cursor_style(CursorStyle::PointingHand)
|
.with_cursor_style(CursorStyle::PointingHand)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue