diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index e731eb98b5..3d412c423e 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -553,39 +553,55 @@ impl SearchableItem for Editor { } } - fn activate_next_match( - &mut self, - index: usize, - direction: Direction, - matches: Vec>, - cx: &mut ViewContext, - ) { - let new_index: usize = match_index_for_direction( - matches.as_slice(), - &self.selections.newest_anchor().head(), - index, - direction, - &self.buffer().read(cx).snapshot(cx), - ); - - let range_to_select = matches[new_index].clone(); - self.unfold_ranges([range_to_select.clone()], false, cx); - self.change_selections(Some(Autoscroll::Fit), cx, |s| { - s.select_ranges([range_to_select]) - }); - } - - fn activate_match_at_index( + fn activate_match( &mut self, index: usize, matches: Vec>, cx: &mut ViewContext, ) { + self.unfold_ranges([matches[index].clone()], false, cx); self.change_selections(Some(Autoscroll::Fit), cx, |s| { s.select_ranges([matches[index].clone()]) }); } + fn match_index_for_direction( + &mut self, + matches: &Vec>, + mut current_index: usize, + direction: Direction, + cx: &mut ViewContext, + ) -> usize { + let buffer = self.buffer().read(cx).snapshot(cx); + let cursor = self.selections.newest_anchor().head(); + if matches[current_index].start.cmp(&cursor, &buffer).is_gt() { + if direction == Direction::Prev { + if current_index == 0 { + current_index = matches.len() - 1; + } else { + current_index -= 1; + } + } + } else if matches[current_index].end.cmp(&cursor, &buffer).is_lt() { + if direction == Direction::Next { + current_index = 0; + } + } else if direction == Direction::Prev { + if current_index == 0 { + current_index = matches.len() - 1; + } else { + current_index -= 1; + } + } else if direction == Direction::Next { + if current_index == matches.len() - 1 { + current_index = 0 + } else { + current_index += 1; + } + }; + current_index + } + fn find_matches( &mut self, query: project::search::SearchQuery, @@ -637,41 +653,6 @@ impl SearchableItem for Editor { } } -pub fn match_index_for_direction( - ranges: &[Range], - cursor: &Anchor, - mut index: usize, - direction: Direction, - buffer: &MultiBufferSnapshot, -) -> usize { - if ranges[index].start.cmp(cursor, buffer).is_gt() { - if direction == Direction::Prev { - if index == 0 { - index = ranges.len() - 1; - } else { - index -= 1; - } - } - } else if ranges[index].end.cmp(cursor, buffer).is_lt() { - if direction == Direction::Next { - index = 0; - } - } else if direction == Direction::Prev { - if index == 0 { - index = ranges.len() - 1; - } else { - index -= 1; - } - } else if direction == Direction::Next { - if index == ranges.len() - 1 { - index = 0 - } else { - index += 1; - } - }; - index -} - pub fn active_match_index( ranges: &[Range], cursor: &Anchor, diff --git a/crates/language/src/syntax_map.rs b/crates/language/src/syntax_map.rs index d1bf698e52..a8cac76ac7 100644 --- a/crates/language/src/syntax_map.rs +++ b/crates/language/src/syntax_map.rs @@ -233,7 +233,6 @@ impl SyntaxSnapshot { }; let (start_byte, start_point) = layer.range.start.summary::<(usize, Point)>(text); - // Ignore edits that end before the start of this layer, and don't consider them // for any subsequent layers at this same depth. loop { diff --git a/crates/search/src/buffer_search.rs b/crates/search/src/buffer_search.rs index 1a46526025..22574b9b71 100644 --- a/crates/search/src/buffer_search.rs +++ b/crates/search/src/buffer_search.rs @@ -95,6 +95,12 @@ impl View for BufferSearchBar { } else { theme.search.editor.input.container }; + let supported_options = self + .active_searchable_item + .as_ref() + .map(|active_searchable_item| active_searchable_item.supported_options()) + .unwrap_or_default(); + Flex::row() .with_child( Flex::row() @@ -143,9 +149,24 @@ impl View for BufferSearchBar { ) .with_child( Flex::row() - .with_child(self.render_search_option("Case", SearchOption::CaseSensitive, cx)) - .with_child(self.render_search_option("Word", SearchOption::WholeWord, cx)) - .with_child(self.render_search_option("Regex", SearchOption::Regex, cx)) + .with_children(self.render_search_option( + supported_options.case, + "Case", + SearchOption::CaseSensitive, + cx, + )) + .with_children(self.render_search_option( + supported_options.word, + "Word", + SearchOption::WholeWord, + cx, + )) + .with_children(self.render_search_option( + supported_options.regex, + "Regex", + SearchOption::Regex, + cx, + )) .contained() .with_style(theme.search.option_button_group) .aligned() @@ -234,7 +255,7 @@ impl BufferSearchBar { if let Some(searchable_item) = WeakSearchableItemHandle::upgrade(searchable_item.as_ref(), cx) { - searchable_item.clear_highlights(cx); + searchable_item.clear_matches(cx); } } if let Some(active_editor) = self.active_searchable_item.as_ref() { @@ -283,36 +304,43 @@ impl BufferSearchBar { fn render_search_option( &self, + option_supported: bool, icon: &str, option: SearchOption, cx: &mut RenderContext, - ) -> ElementBox { + ) -> Option { + if !option_supported { + return None; + } + let tooltip_style = cx.global::().theme.tooltip.clone(); let is_active = self.is_search_option_enabled(option); - MouseEventHandler::new::(option as usize, cx, |state, cx| { - let style = &cx - .global::() - .theme - .search - .option_button - .style_for(state, is_active); - Label::new(icon.to_string(), style.text.clone()) - .contained() - .with_style(style.container) - .boxed() - }) - .on_click(MouseButton::Left, move |_, cx| { - cx.dispatch_any_action(option.to_toggle_action()) - }) - .with_cursor_style(CursorStyle::PointingHand) - .with_tooltip::( - option as usize, - format!("Toggle {}", option.label()), - Some(option.to_toggle_action()), - tooltip_style, - cx, + Some( + MouseEventHandler::new::(option as usize, cx, |state, cx| { + let style = &cx + .global::() + .theme + .search + .option_button + .style_for(state, is_active); + Label::new(icon.to_string(), style.text.clone()) + .contained() + .with_style(style.container) + .boxed() + }) + .on_click(MouseButton::Left, move |_, cx| { + cx.dispatch_any_action(option.to_toggle_action()) + }) + .with_cursor_style(CursorStyle::PointingHand) + .with_tooltip::( + option as usize, + format!("Toggle {}", option.label()), + Some(option.to_toggle_action()), + tooltip_style, + cx, + ) + .boxed(), ) - .boxed() } fn render_nav_button( @@ -422,8 +450,10 @@ impl BufferSearchBar { .seachable_items_with_matches .get(&searchable_item.downgrade()) { - searchable_item.select_next_match_in_direction(index, direction, matches, cx); - searchable_item.highlight_matches(matches, cx); + let new_match_index = + searchable_item.match_index_for_direction(matches, index, direction, cx); + searchable_item.update_matches(matches, cx); + searchable_item.activate_match(new_match_index, matches, cx); } } } @@ -479,7 +509,7 @@ impl BufferSearchBar { if Some(&searchable_item) == self.active_searchable_item.as_ref() { active_item_matches = Some((searchable_item.downgrade(), matches)); } else { - searchable_item.clear_highlights(cx); + searchable_item.clear_matches(cx); } } } @@ -494,7 +524,7 @@ impl BufferSearchBar { if let Some(active_searchable_item) = self.active_searchable_item.as_ref() { if query.is_empty() { self.active_match_index.take(); - active_searchable_item.clear_highlights(cx); + active_searchable_item.clear_matches(cx); } else { let query = if self.regex { match SearchQuery::regex(query, self.whole_word, self.case_sensitive) { @@ -509,7 +539,7 @@ impl BufferSearchBar { SearchQuery::text(query, self.whole_word, self.case_sensitive) }; - let matches = active_searchable_item.matches(query, cx); + let matches = active_searchable_item.find_matches(query, cx); let active_searchable_item = active_searchable_item.downgrade(); self.pending_search = Some(cx.spawn_weak(|this, mut cx| async move { @@ -529,13 +559,13 @@ impl BufferSearchBar { .seachable_items_with_matches .get(&active_searchable_item.downgrade()) .unwrap(); + active_searchable_item.update_matches(matches, cx); if select_closest_match { if let Some(match_ix) = this.active_match_index { active_searchable_item - .select_match_by_index(match_ix, matches, cx); + .activate_match(match_ix, matches, cx); } } - active_searchable_item.highlight_matches(matches, cx); } cx.notify(); } diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index 8c5723c30b..8caa7bf71d 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -4,8 +4,8 @@ use crate::{ }; use collections::HashMap; use editor::{ - items::{active_match_index, match_index_for_direction}, - Anchor, Autoscroll, Editor, MultiBuffer, SelectAll, MAX_TAB_TITLE_LEN, + items::active_match_index, Anchor, Autoscroll, Editor, MultiBuffer, SelectAll, + MAX_TAB_TITLE_LEN, }; use gpui::{ actions, elements::*, platform::CursorStyle, Action, AnyViewHandle, AppContext, ElementBox, @@ -23,7 +23,7 @@ use std::{ }; use util::ResultExt as _; use workspace::{ - searchable::{Direction, SearchableItemHandle}, + searchable::{Direction, SearchableItem, SearchableItemHandle}, Item, ItemHandle, ItemNavHistory, Pane, ToolbarItemLocation, ToolbarItemView, Workspace, }; @@ -486,16 +486,12 @@ impl ProjectSearchView { fn select_match(&mut self, direction: Direction, cx: &mut ViewContext) { if let Some(index) = self.active_match_index { - let model = self.model.read(cx); - let results_editor = self.results_editor.read(cx); - let new_index = match_index_for_direction( - &model.match_ranges, - &results_editor.selections.newest_anchor().head(), - index, - direction, - &results_editor.buffer().read(cx).snapshot(cx), - ); - let range_to_select = model.match_ranges[new_index].clone(); + let match_ranges = self.model.read(cx).match_ranges.clone(); + let new_index = self.results_editor.update(cx, |editor, cx| { + editor.match_index_for_direction(&match_ranges, index, direction, cx) + }); + + let range_to_select = match_ranges[new_index].clone(); self.results_editor.update(cx, |editor, cx| { editor.unfold_ranges([range_to_select.clone()], false, cx); editor.change_selections(Some(Autoscroll::Fit), cx, |s| { diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index c375babf84..f542477b4c 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -10,7 +10,7 @@ use alacritty_terminal::{ event::{Event as AlacTermEvent, EventListener, Notify, WindowSize}, event_loop::{EventLoop, Msg, Notifier}, grid::{Dimensions, Scroll as AlacScroll}, - index::{Column, Direction, Line, Point}, + index::{Column, Direction as AlacDirection, Line, Point}, selection::{Selection, SelectionRange, SelectionType}, sync::FairMutex, term::{ @@ -84,6 +84,7 @@ pub enum Event { Bell, Wakeup, BlinkChanged, + SelectionsChanged, } #[derive(Clone)] @@ -93,7 +94,8 @@ enum InternalEvent { Clear, // FocusNextMatch, Scroll(AlacScroll), - SetSelection(Option), + ScrollToPoint(Point), + SetSelection(Option<(Selection, Point)>), UpdateSelection(Vector2F), Copy, } @@ -384,6 +386,7 @@ impl TerminalBuilder { matches: Vec::new(), last_synced: Instant::now(), sync_task: None, + selection_head: None, }; Ok(TerminalBuilder { @@ -494,12 +497,13 @@ pub struct Terminal { events: VecDeque, default_title: String, title: String, - last_mouse: Option<(Point, Direction)>, + last_mouse: Option<(Point, AlacDirection)>, pub matches: Vec>, cur_size: TerminalSize, last_content: TerminalContent, last_synced: Instant, sync_task: Option>, + selection_head: Option, } impl Terminal { @@ -576,33 +580,14 @@ impl Terminal { InternalEvent::Scroll(scroll) => { term.scroll_display(*scroll); } - // InternalEvent::FocusNextMatch => { - // if let Some((Some(searcher), _origin)) = &self.searcher { - // match term.search_next( - // searcher, - // Point { - // line: Line(0), - // column: Column(0), - // }, - // SEARCH_FORWARD, - // Direction::Left, - // None, - // ) { - // Some(regex_match) => { - // term.scroll_to_point(*regex_match.start()); + InternalEvent::SetSelection(selection) => { + term.selection = selection.as_ref().map(|(sel, _)| sel.clone()); - // //Focus is done with selections in zed - // let focus = make_selection(*regex_match.start(), *regex_match.end()); - // term.selection = Some(focus); - // } - // None => { - // //Clear focused match - // term.selection = None; - // } - // } - // } - // } - InternalEvent::SetSelection(sel) => term.selection = sel.clone(), + if let Some((_, head)) = selection { + self.selection_head = Some(*head); + } + cx.emit(Event::SelectionsChanged) + } InternalEvent::UpdateSelection(position) => { if let Some(mut selection) = term.selection.take() { let point = mouse_point(*position, self.cur_size, term.grid().display_offset()); @@ -610,6 +595,9 @@ impl Terminal { selection.update(point, side); term.selection = Some(selection); + + self.selection_head = Some(point); + cx.emit(Event::SelectionsChanged) } } @@ -618,6 +606,7 @@ impl Terminal { cx.write_to_clipboard(ClipboardItem::new(txt)) } } + InternalEvent::ScrollToPoint(point) => term.scroll_to_point(*point), } } @@ -625,53 +614,24 @@ impl Terminal { &self.last_content } - fn begin_select(&mut self, sel: Selection) { + //To test: + //- Activate match on terminal (scrolling and selection) + //- Editor search snapping behavior + + pub fn activate_match(&mut self, index: usize) { + if let Some(search_match) = self.matches.get(index).cloned() { + self.set_selection(Some((make_selection(&search_match), *search_match.end()))); + + self.events + .push_back(InternalEvent::ScrollToPoint(*search_match.start())); + } + } + + fn set_selection(&mut self, selection: Option<(Selection, Point)>) { self.events - .push_back(InternalEvent::SetSelection(Some(sel))); + .push_back(InternalEvent::SetSelection(selection)); } - fn continue_selection(&mut self, location: Vector2F) { - self.events - .push_back(InternalEvent::UpdateSelection(location)) - } - - fn end_select(&mut self) { - self.events.push_back(InternalEvent::SetSelection(None)); - } - - fn scroll(&mut self, scroll: AlacScroll) { - self.events.push_back(InternalEvent::Scroll(scroll)); - } - - // fn focus_next_match(&mut self) { - // self.events.push_back(InternalEvent::FocusNextMatch); - // } - - // pub fn search(&mut self, search: &str) { - // let new_searcher = RegexSearch::new(search).ok(); - // self.searcher = match (new_searcher, &self.searcher) { - // //Nothing to do :( - // (None, None) => None, - // //No existing search, start a new one - // (Some(new_searcher), None) => Some((Some(new_searcher), self.viewport_origin())), - // //Existing search, carry over origin - // (new_searcher, Some((_, origin))) => Some((new_searcher, *origin)), - // }; - - // if let Some((Some(_), _)) = self.searcher { - // self.focus_next_match(); - // } - // } - - // fn viewport_origin(&mut self) -> Point { - // let viewport_top = alacritty_terminal::index::Line(-(self.last_offset as i32)) - 1; - // Point::new(viewport_top, alacritty_terminal::index::Column(0)) - // } - - // pub fn end_search(&mut self) { - // self.searcher = None; - // } - pub fn copy(&mut self) { self.events.push_back(InternalEvent::Copy); } @@ -691,8 +651,10 @@ impl Terminal { } pub fn input(&mut self, input: String) { - self.scroll(AlacScroll::Bottom); - self.end_select(); + self.events + .push_back(InternalEvent::Scroll(AlacScroll::Bottom)); + self.events.push_back(InternalEvent::SetSelection(None)); + self.write_to_pty(input); } @@ -790,7 +752,7 @@ impl Terminal { } } - pub fn mouse_changed(&mut self, point: Point, side: Direction) -> bool { + pub fn mouse_changed(&mut self, point: Point, side: AlacDirection) -> bool { match self.last_mouse { Some((old_point, old_side)) => { if old_point == point && old_side == side { @@ -830,7 +792,8 @@ impl Terminal { if !self.mouse_mode(e.shift) { // Alacritty has the same ordering, of first updating the selection // then scrolling 15ms later - self.continue_selection(position); + self.events + .push_back(InternalEvent::UpdateSelection(position)); // Doesn't make sense to scroll the alt screen if !self.last_content.mode.contains(TermMode::ALT_SCREEN) { @@ -840,8 +803,11 @@ impl Terminal { }; let scroll_lines = (scroll_delta / self.cur_size.line_height) as i32; - self.scroll(AlacScroll::Delta(scroll_lines)); - self.continue_selection(position) + + self.events + .push_back(InternalEvent::Scroll(AlacScroll::Delta(scroll_lines))); + self.events + .push_back(InternalEvent::UpdateSelection(position)) } } } @@ -870,7 +836,10 @@ impl Terminal { self.pty_tx.notify(bytes); } } else if e.button == MouseButton::Left { - self.begin_select(Selection::new(SelectionType::Simple, point, side)); + self.events.push_back(InternalEvent::SetSelection(Some(( + Selection::new(SelectionType::Simple, point, side), + point, + )))); } } @@ -893,7 +862,8 @@ impl Terminal { selection_type.map(|selection_type| Selection::new(selection_type, point, side)); if let Some(sel) = selection { - self.begin_select(sel); + self.events + .push_back(InternalEvent::SetSelection(Some((sel, point)))); } } } @@ -951,7 +921,8 @@ impl Terminal { ((e.delta.y() * ALACRITTY_SCROLL_MULTIPLIER) / self.cur_size.line_height) as i32; if scroll_lines != 0 { let scroll = AlacScroll::Delta(scroll_lines); - self.scroll(scroll); + + self.events.push_back(InternalEvent::Scroll(scroll)); } } } @@ -994,11 +965,11 @@ impl Entity for Terminal { type Event = Event; } -// fn make_selection(from: Point, to: Point) -> Selection { -// let mut focus = Selection::new(SelectionType::Simple, from, Direction::Left); -// focus.update(to, Direction::Right); -// focus -// } +fn make_selection(range: &RangeInclusive) -> Selection { + let mut selection = Selection::new(SelectionType::Simple, *range.start(), AlacDirection::Left); + selection.update(*range.end(), AlacDirection::Right); + selection +} /// Copied from alacritty/src/display/hint.rs HintMatches::visible_regex_matches() /// Iterate over all visible regex matches. @@ -1013,7 +984,7 @@ fn make_search_matches<'a, T>( start.line = start.line.max(viewport_start - MAX_SEARCH_LINES); end.line = end.line.min(viewport_end + MAX_SEARCH_LINES); - RegexIter::new(start, end, Direction::Right, term, regex) + RegexIter::new(start, end, AlacDirection::Right, term, regex) .skip_while(move |rm| rm.end().line < viewport_start) .take_while(move |rm| rm.start().line <= viewport_end) } diff --git a/crates/terminal/src/terminal_container_view.rs b/crates/terminal/src/terminal_container_view.rs index 204ff24112..f6d60bd964 100644 --- a/crates/terminal/src/terminal_container_view.rs +++ b/crates/terminal/src/terminal_container_view.rs @@ -7,7 +7,7 @@ use gpui::{ actions, elements::*, AnyViewHandle, AppContext, Entity, ModelHandle, MutableAppContext, Task, View, ViewContext, ViewHandle, }; -use workspace::searchable::{Direction, SearchEvent, SearchableItem, SearchableItemHandle}; +use workspace::searchable::{SearchEvent, SearchOptions, SearchableItem, SearchableItemHandle}; use workspace::{Item, Workspace}; use crate::TerminalSize; @@ -340,11 +340,19 @@ impl Item for TerminalContainer { impl SearchableItem for TerminalContainer { type Match = RangeInclusive; + fn supported_options() -> SearchOptions { + SearchOptions { + case: false, + word: false, + regex: false, + } + } + /// Convert events raised by this item into search-relevant events (if applicable) fn to_search_event(event: &Self::Event) -> Option { match event { Event::Wakeup => Some(SearchEvent::MatchesInvalidated), - //TODO selection changed + Event::SelectionsChanged => Some(SearchEvent::ActiveMatchChanged), _ => None, } } @@ -380,25 +388,13 @@ impl SearchableItem for TerminalContainer { } } - /// Given an index, a set of matches for this index, and a direction, - /// get the next match (clicking the arrow) - fn activate_next_match( - &mut self, - _index: usize, - _direction: Direction, - _matches: Vec, - _cx: &mut ViewContext, - ) { - // TODO: - } - /// Focus match at given index into the Vec of matches - fn activate_match_at_index( - &mut self, - _index: usize, - _matches: Vec, - _cx: &mut ViewContext, - ) { + fn activate_match(&mut self, index: usize, _: Vec, cx: &mut ViewContext) { + if let TerminalContainerContent::Connected(connected) = &self.content { + let terminal = connected.read(cx).terminal().clone(); + terminal.update(cx, |term, _| term.activate_match(index)); + cx.notify(); + } } /// Get all of the matches for this query, should be done on the background @@ -419,10 +415,27 @@ impl SearchableItem for TerminalContainer { fn active_match_index( &mut self, matches: Vec, - _cx: &mut ViewContext, + cx: &mut ViewContext, ) -> Option { - if matches.len() > 0 { - Some(0) + if let TerminalContainerContent::Connected(connected) = &self.content { + if let Some(selection_head) = connected.read(cx).terminal().read(cx).selection_head { + // If selection head is contained in a match. Return that match + for (ix, search_match) in matches.iter().enumerate() { + if search_match.contains(&selection_head) { + return Some(ix); + } + + // If not contained, return the next match after the selection head + if search_match.start() > &selection_head { + return Some(ix); + } + } + + // If no selection after selection head, return the last match + return Some(matches.len() - 1); + } else { + Some(0) + } } else { None } diff --git a/crates/terminal/src/terminal_element.rs b/crates/terminal/src/terminal_element.rs index c5dc80d62b..6c6d95aacd 100644 --- a/crates/terminal/src/terminal_element.rs +++ b/crates/terminal/src/terminal_element.rs @@ -579,12 +579,12 @@ impl Element for TerminalElement { // searches, highlights to a single range representations let mut relative_highlighted_ranges = Vec::new(); - if let Some(selection) = selection { - relative_highlighted_ranges.push((selection.start..=selection.end, selection_color)); - } for search_match in search_matches { relative_highlighted_ranges.push((search_match, match_color)) } + if let Some(selection) = selection { + relative_highlighted_ranges.push((selection.start..=selection.end, selection_color)); + } // then have that representation be converted to the appropriate highlight data structure diff --git a/crates/workspace/src/searchable.rs b/crates/workspace/src/searchable.rs index de0d4f774a..f566d1136e 100644 --- a/crates/workspace/src/searchable.rs +++ b/crates/workspace/src/searchable.rs @@ -14,32 +14,64 @@ pub enum SearchEvent { ActiveMatchChanged, } -#[derive(Clone, Copy, PartialEq, Eq)] +#[derive(Clone, Copy, PartialEq, Eq, Debug)] pub enum Direction { Prev, Next, } +#[derive(Clone, Copy, Debug, Default)] +pub struct SearchOptions { + pub case: bool, + pub word: bool, + pub regex: bool, +} + pub trait SearchableItem: Item { type Match: Any + Sync + Send + Clone; + fn supported_options() -> SearchOptions { + SearchOptions { + case: true, + word: true, + regex: true, + } + } fn to_search_event(event: &Self::Event) -> Option; fn clear_matches(&mut self, cx: &mut ViewContext); fn update_matches(&mut self, matches: Vec, cx: &mut ViewContext); fn query_suggestion(&mut self, cx: &mut ViewContext) -> String; - fn activate_next_match( + fn activate_match( &mut self, index: usize, + matches: Vec, + cx: &mut ViewContext, + ); + fn match_index_for_direction( + &mut self, + matches: &Vec, + mut current_index: usize, direction: Direction, - matches: Vec, - cx: &mut ViewContext, - ); - fn activate_match_at_index( - &mut self, - index: usize, - matches: Vec, - cx: &mut ViewContext, - ); + _: &mut ViewContext, + ) -> usize { + match direction { + Direction::Prev => { + if current_index == 0 { + matches.len() - 1 + } else { + current_index - 1 + } + } + Direction::Next => { + current_index += 1; + if current_index == matches.len() { + 0 + } else { + current_index + } + } + } + } fn find_matches( &mut self, query: SearchQuery, @@ -55,28 +87,29 @@ pub trait SearchableItem: Item { pub trait SearchableItemHandle: ItemHandle { fn downgrade(&self) -> Box; fn boxed_clone(&self) -> Box; + fn supported_options(&self) -> SearchOptions; fn subscribe( &self, cx: &mut MutableAppContext, handler: Box, ) -> Subscription; - fn clear_highlights(&self, cx: &mut MutableAppContext); - fn highlight_matches(&self, matches: &Vec>, cx: &mut MutableAppContext); + fn clear_matches(&self, cx: &mut MutableAppContext); + fn update_matches(&self, matches: &Vec>, cx: &mut MutableAppContext); fn query_suggestion(&self, cx: &mut MutableAppContext) -> String; - fn select_next_match_in_direction( + fn activate_match( &self, index: usize, + matches: &Vec>, + cx: &mut MutableAppContext, + ); + fn match_index_for_direction( + &self, + matches: &Vec>, + current_index: usize, direction: Direction, - matches: &Vec>, cx: &mut MutableAppContext, - ); - fn select_match_by_index( - &self, - index: usize, - matches: &Vec>, - cx: &mut MutableAppContext, - ); - fn matches( + ) -> usize; + fn find_matches( &self, query: SearchQuery, cx: &mut MutableAppContext, @@ -97,6 +130,10 @@ impl SearchableItemHandle for ViewHandle { Box::new(self.clone()) } + fn supported_options(&self) -> SearchOptions { + T::supported_options() + } + fn subscribe( &self, cx: &mut MutableAppContext, @@ -109,40 +146,38 @@ impl SearchableItemHandle for ViewHandle { }) } - fn clear_highlights(&self, cx: &mut MutableAppContext) { + fn clear_matches(&self, cx: &mut MutableAppContext) { self.update(cx, |this, cx| this.clear_matches(cx)); } - fn highlight_matches(&self, matches: &Vec>, cx: &mut MutableAppContext) { + fn update_matches(&self, matches: &Vec>, cx: &mut MutableAppContext) { let matches = downcast_matches(matches); self.update(cx, |this, cx| this.update_matches(matches, cx)); } fn query_suggestion(&self, cx: &mut MutableAppContext) -> String { self.update(cx, |this, cx| this.query_suggestion(cx)) } - fn select_next_match_in_direction( + fn activate_match( &self, index: usize, + matches: &Vec>, + cx: &mut MutableAppContext, + ) { + let matches = downcast_matches(matches); + self.update(cx, |this, cx| this.activate_match(index, matches, cx)); + } + fn match_index_for_direction( + &self, + matches: &Vec>, + current_index: usize, direction: Direction, - matches: &Vec>, cx: &mut MutableAppContext, - ) { + ) -> usize { let matches = downcast_matches(matches); self.update(cx, |this, cx| { - this.activate_next_match(index, direction, matches, cx) - }); + this.match_index_for_direction(&matches, current_index, direction, cx) + }) } - fn select_match_by_index( - &self, - index: usize, - matches: &Vec>, - cx: &mut MutableAppContext, - ) { - let matches = downcast_matches(matches); - self.update(cx, |this, cx| { - this.activate_match_at_index(index, matches, cx) - }); - } - fn matches( + fn find_matches( &self, query: SearchQuery, cx: &mut MutableAppContext,