ZIm/crates/workspace/src/searchable.rs
Conrad Irwin f327118e06
vim: Allow search with operators & visual mode (#10226)
Fixes: #4346

Release Notes:

- vim: Add search motions (`/,?,n,N,*,#`) in visual modes and as targets
for operators like `d`,`c`,`y`
([#4346](https://github.com/zed-industries/zed/issues/4346)).
2024-04-08 15:20:14 -06:00

280 lines
8.3 KiB
Rust

use std::{any::Any, sync::Arc};
use any_vec::AnyVec;
use gpui::{
AnyView, AnyWeakView, AppContext, EventEmitter, Subscription, Task, View, ViewContext,
WeakView, WindowContext,
};
use project::search::SearchQuery;
use crate::{
item::{Item, WeakItemHandle},
ItemHandle,
};
#[derive(Clone, Debug)]
pub enum SearchEvent {
MatchesInvalidated,
ActiveMatchChanged,
}
#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
pub enum Direction {
Prev,
#[default]
Next,
}
impl Direction {
pub fn opposite(&self) -> Self {
match self {
Direction::Prev => Direction::Next,
Direction::Next => Direction::Prev,
}
}
}
#[derive(Clone, Copy, Debug, Default)]
pub struct SearchOptions {
pub case: bool,
pub word: bool,
pub regex: bool,
/// Specifies whether the item supports search & replace.
pub replacement: bool,
}
pub trait SearchableItem: Item + EventEmitter<SearchEvent> {
type Match: Any + Sync + Send + Clone;
fn supported_options() -> SearchOptions {
SearchOptions {
case: true,
word: true,
regex: true,
replacement: true,
}
}
fn clear_matches(&mut self, cx: &mut ViewContext<Self>);
fn update_matches(&mut self, matches: &[Self::Match], cx: &mut ViewContext<Self>);
fn query_suggestion(&mut self, cx: &mut ViewContext<Self>) -> String;
fn activate_match(&mut self, index: usize, matches: &[Self::Match], cx: &mut ViewContext<Self>);
fn select_matches(&mut self, matches: &[Self::Match], cx: &mut ViewContext<Self>);
fn replace(&mut self, _: &Self::Match, _: &SearchQuery, _: &mut ViewContext<Self>);
fn match_index_for_direction(
&mut self,
matches: &[Self::Match],
current_index: usize,
direction: Direction,
count: usize,
_: &mut ViewContext<Self>,
) -> usize {
match direction {
Direction::Prev => {
let count = count % matches.len();
if current_index >= count {
current_index - count
} else {
matches.len() - (count - current_index)
}
}
Direction::Next => (current_index + count) % matches.len(),
}
}
fn find_matches(
&mut self,
query: Arc<SearchQuery>,
cx: &mut ViewContext<Self>,
) -> Task<Vec<Self::Match>>;
fn active_match_index(
&mut self,
matches: &[Self::Match],
cx: &mut ViewContext<Self>,
) -> Option<usize>;
}
pub trait SearchableItemHandle: ItemHandle {
fn downgrade(&self) -> Box<dyn WeakSearchableItemHandle>;
fn boxed_clone(&self) -> Box<dyn SearchableItemHandle>;
fn supported_options(&self) -> SearchOptions;
fn subscribe_to_search_events(
&self,
cx: &mut WindowContext,
handler: Box<dyn Fn(&SearchEvent, &mut WindowContext) + Send>,
) -> Subscription;
fn clear_matches(&self, cx: &mut WindowContext);
fn update_matches(&self, matches: &AnyVec<dyn Send>, cx: &mut WindowContext);
fn query_suggestion(&self, cx: &mut WindowContext) -> String;
fn activate_match(&self, index: usize, matches: &AnyVec<dyn Send>, cx: &mut WindowContext);
fn select_matches(&self, matches: &AnyVec<dyn Send>, cx: &mut WindowContext);
fn replace(
&self,
_: any_vec::element::ElementRef<'_, dyn Send>,
_: &SearchQuery,
_: &mut WindowContext,
);
fn match_index_for_direction(
&self,
matches: &AnyVec<dyn Send>,
current_index: usize,
direction: Direction,
count: usize,
cx: &mut WindowContext,
) -> usize;
fn find_matches(
&self,
query: Arc<SearchQuery>,
cx: &mut WindowContext,
) -> Task<AnyVec<dyn Send>>;
fn active_match_index(
&self,
matches: &AnyVec<dyn Send>,
cx: &mut WindowContext,
) -> Option<usize>;
}
impl<T: SearchableItem> SearchableItemHandle for View<T> {
fn downgrade(&self) -> Box<dyn WeakSearchableItemHandle> {
Box::new(self.downgrade())
}
fn boxed_clone(&self) -> Box<dyn SearchableItemHandle> {
Box::new(self.clone())
}
fn supported_options(&self) -> SearchOptions {
T::supported_options()
}
fn subscribe_to_search_events(
&self,
cx: &mut WindowContext,
handler: Box<dyn Fn(&SearchEvent, &mut WindowContext) + Send>,
) -> Subscription {
cx.subscribe(self, move |_, event: &SearchEvent, cx| handler(event, cx))
}
fn clear_matches(&self, cx: &mut WindowContext) {
self.update(cx, |this, cx| this.clear_matches(cx));
}
fn update_matches(&self, matches: &AnyVec<dyn Send>, cx: &mut WindowContext) {
let matches = matches.downcast_ref().unwrap();
self.update(cx, |this, cx| this.update_matches(matches.as_slice(), cx));
}
fn query_suggestion(&self, cx: &mut WindowContext) -> String {
self.update(cx, |this, cx| this.query_suggestion(cx))
}
fn activate_match(&self, index: usize, matches: &AnyVec<dyn Send>, cx: &mut WindowContext) {
let matches = matches.downcast_ref().unwrap();
self.update(cx, |this, cx| {
this.activate_match(index, matches.as_slice(), cx)
});
}
fn select_matches(&self, matches: &AnyVec<dyn Send>, cx: &mut WindowContext) {
let matches = matches.downcast_ref().unwrap();
self.update(cx, |this, cx| this.select_matches(matches.as_slice(), cx));
}
fn match_index_for_direction(
&self,
matches: &AnyVec<dyn Send>,
current_index: usize,
direction: Direction,
count: usize,
cx: &mut WindowContext,
) -> usize {
let matches = matches.downcast_ref().unwrap();
self.update(cx, |this, cx| {
this.match_index_for_direction(matches.as_slice(), current_index, direction, count, cx)
})
}
fn find_matches(
&self,
query: Arc<SearchQuery>,
cx: &mut WindowContext,
) -> Task<AnyVec<dyn Send>> {
let matches = self.update(cx, |this, cx| this.find_matches(query, cx));
cx.spawn(|_| async {
let matches = matches.await;
let mut any_matches = AnyVec::with_capacity::<T::Match>(matches.len());
{
let mut any_matches = any_matches.downcast_mut::<T::Match>().unwrap();
for mat in matches {
any_matches.push(mat);
}
}
any_matches
})
}
fn active_match_index(
&self,
matches: &AnyVec<dyn Send>,
cx: &mut WindowContext,
) -> Option<usize> {
let matches = matches.downcast_ref()?;
self.update(cx, |this, cx| {
this.active_match_index(matches.as_slice(), cx)
})
}
fn replace(
&self,
mat: any_vec::element::ElementRef<'_, dyn Send>,
query: &SearchQuery,
cx: &mut WindowContext,
) {
let mat = mat.downcast_ref().unwrap();
self.update(cx, |this, cx| this.replace(mat, query, cx))
}
}
impl From<Box<dyn SearchableItemHandle>> for AnyView {
fn from(this: Box<dyn SearchableItemHandle>) -> Self {
this.to_any().clone()
}
}
impl From<&Box<dyn SearchableItemHandle>> for AnyView {
fn from(this: &Box<dyn SearchableItemHandle>) -> Self {
this.to_any().clone()
}
}
impl PartialEq for Box<dyn SearchableItemHandle> {
fn eq(&self, other: &Self) -> bool {
self.item_id() == other.item_id()
}
}
impl Eq for Box<dyn SearchableItemHandle> {}
pub trait WeakSearchableItemHandle: WeakItemHandle {
fn upgrade(&self, cx: &AppContext) -> Option<Box<dyn SearchableItemHandle>>;
fn into_any(self) -> AnyWeakView;
}
impl<T: SearchableItem> WeakSearchableItemHandle for WeakView<T> {
fn upgrade(&self, _cx: &AppContext) -> Option<Box<dyn SearchableItemHandle>> {
Some(Box::new(self.upgrade()?))
}
fn into_any(self) -> AnyWeakView {
self.into()
}
}
impl PartialEq for Box<dyn WeakSearchableItemHandle> {
fn eq(&self, other: &Self) -> bool {
self.id() == other.id()
}
}
impl Eq for Box<dyn WeakSearchableItemHandle> {}
impl std::hash::Hash for Box<dyn WeakSearchableItemHandle> {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.id().hash(state)
}
}