Finished terminal search

This commit is contained in:
Mikayla Maki 2022-09-01 13:45:46 -07:00
parent 25aae1107b
commit ebae991cb2
8 changed files with 288 additions and 263 deletions

View file

@ -553,39 +553,55 @@ impl SearchableItem for Editor {
} }
} }
fn activate_next_match( fn activate_match(
&mut self,
index: usize,
direction: Direction,
matches: Vec<Range<Anchor>>,
cx: &mut ViewContext<Self>,
) {
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(
&mut self, &mut self,
index: usize, index: usize,
matches: Vec<Range<Anchor>>, matches: Vec<Range<Anchor>>,
cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
) { ) {
self.unfold_ranges([matches[index].clone()], false, cx);
self.change_selections(Some(Autoscroll::Fit), cx, |s| { self.change_selections(Some(Autoscroll::Fit), cx, |s| {
s.select_ranges([matches[index].clone()]) s.select_ranges([matches[index].clone()])
}); });
} }
fn match_index_for_direction(
&mut self,
matches: &Vec<Range<Anchor>>,
mut current_index: usize,
direction: Direction,
cx: &mut ViewContext<Self>,
) -> 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( fn find_matches(
&mut self, &mut self,
query: project::search::SearchQuery, query: project::search::SearchQuery,
@ -637,41 +653,6 @@ impl SearchableItem for Editor {
} }
} }
pub fn match_index_for_direction(
ranges: &[Range<Anchor>],
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( pub fn active_match_index(
ranges: &[Range<Anchor>], ranges: &[Range<Anchor>],
cursor: &Anchor, cursor: &Anchor,

View file

@ -233,7 +233,6 @@ impl SyntaxSnapshot {
}; };
let (start_byte, start_point) = layer.range.start.summary::<(usize, Point)>(text); 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 // Ignore edits that end before the start of this layer, and don't consider them
// for any subsequent layers at this same depth. // for any subsequent layers at this same depth.
loop { loop {

View file

@ -95,6 +95,12 @@ impl View for BufferSearchBar {
} else { } else {
theme.search.editor.input.container 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() Flex::row()
.with_child( .with_child(
Flex::row() Flex::row()
@ -143,9 +149,24 @@ impl View for BufferSearchBar {
) )
.with_child( .with_child(
Flex::row() Flex::row()
.with_child(self.render_search_option("Case", SearchOption::CaseSensitive, cx)) .with_children(self.render_search_option(
.with_child(self.render_search_option("Word", SearchOption::WholeWord, cx)) supported_options.case,
.with_child(self.render_search_option("Regex", SearchOption::Regex, cx)) "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() .contained()
.with_style(theme.search.option_button_group) .with_style(theme.search.option_button_group)
.aligned() .aligned()
@ -234,7 +255,7 @@ impl BufferSearchBar {
if let Some(searchable_item) = if let Some(searchable_item) =
WeakSearchableItemHandle::upgrade(searchable_item.as_ref(), cx) 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() { if let Some(active_editor) = self.active_searchable_item.as_ref() {
@ -283,36 +304,43 @@ impl BufferSearchBar {
fn render_search_option( fn render_search_option(
&self, &self,
option_supported: bool,
icon: &str, icon: &str,
option: SearchOption, option: SearchOption,
cx: &mut RenderContext<Self>, cx: &mut RenderContext<Self>,
) -> ElementBox { ) -> Option<ElementBox> {
if !option_supported {
return None;
}
let tooltip_style = cx.global::<Settings>().theme.tooltip.clone(); let tooltip_style = cx.global::<Settings>().theme.tooltip.clone();
let is_active = self.is_search_option_enabled(option); let is_active = self.is_search_option_enabled(option);
MouseEventHandler::new::<Self, _, _>(option as usize, cx, |state, cx| { Some(
let style = &cx MouseEventHandler::new::<Self, _, _>(option as usize, cx, |state, cx| {
.global::<Settings>() let style = &cx
.theme .global::<Settings>()
.search .theme
.option_button .search
.style_for(state, is_active); .option_button
Label::new(icon.to_string(), style.text.clone()) .style_for(state, is_active);
.contained() Label::new(icon.to_string(), style.text.clone())
.with_style(style.container) .contained()
.boxed() .with_style(style.container)
}) .boxed()
.on_click(MouseButton::Left, move |_, cx| { })
cx.dispatch_any_action(option.to_toggle_action()) .on_click(MouseButton::Left, move |_, cx| {
}) cx.dispatch_any_action(option.to_toggle_action())
.with_cursor_style(CursorStyle::PointingHand) })
.with_tooltip::<Self, _>( .with_cursor_style(CursorStyle::PointingHand)
option as usize, .with_tooltip::<Self, _>(
format!("Toggle {}", option.label()), option as usize,
Some(option.to_toggle_action()), format!("Toggle {}", option.label()),
tooltip_style, Some(option.to_toggle_action()),
cx, tooltip_style,
cx,
)
.boxed(),
) )
.boxed()
} }
fn render_nav_button( fn render_nav_button(
@ -422,8 +450,10 @@ impl BufferSearchBar {
.seachable_items_with_matches .seachable_items_with_matches
.get(&searchable_item.downgrade()) .get(&searchable_item.downgrade())
{ {
searchable_item.select_next_match_in_direction(index, direction, matches, cx); let new_match_index =
searchable_item.highlight_matches(matches, cx); 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() { if Some(&searchable_item) == self.active_searchable_item.as_ref() {
active_item_matches = Some((searchable_item.downgrade(), matches)); active_item_matches = Some((searchable_item.downgrade(), matches));
} else { } 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 let Some(active_searchable_item) = self.active_searchable_item.as_ref() {
if query.is_empty() { if query.is_empty() {
self.active_match_index.take(); self.active_match_index.take();
active_searchable_item.clear_highlights(cx); active_searchable_item.clear_matches(cx);
} else { } else {
let query = if self.regex { let query = if self.regex {
match SearchQuery::regex(query, self.whole_word, self.case_sensitive) { 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) 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(); let active_searchable_item = active_searchable_item.downgrade();
self.pending_search = Some(cx.spawn_weak(|this, mut cx| async move { self.pending_search = Some(cx.spawn_weak(|this, mut cx| async move {
@ -529,13 +559,13 @@ impl BufferSearchBar {
.seachable_items_with_matches .seachable_items_with_matches
.get(&active_searchable_item.downgrade()) .get(&active_searchable_item.downgrade())
.unwrap(); .unwrap();
active_searchable_item.update_matches(matches, cx);
if select_closest_match { if select_closest_match {
if let Some(match_ix) = this.active_match_index { if let Some(match_ix) = this.active_match_index {
active_searchable_item 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(); cx.notify();
} }

View file

@ -4,8 +4,8 @@ use crate::{
}; };
use collections::HashMap; use collections::HashMap;
use editor::{ use editor::{
items::{active_match_index, match_index_for_direction}, items::active_match_index, Anchor, Autoscroll, Editor, MultiBuffer, SelectAll,
Anchor, Autoscroll, Editor, MultiBuffer, SelectAll, MAX_TAB_TITLE_LEN, MAX_TAB_TITLE_LEN,
}; };
use gpui::{ use gpui::{
actions, elements::*, platform::CursorStyle, Action, AnyViewHandle, AppContext, ElementBox, actions, elements::*, platform::CursorStyle, Action, AnyViewHandle, AppContext, ElementBox,
@ -23,7 +23,7 @@ use std::{
}; };
use util::ResultExt as _; use util::ResultExt as _;
use workspace::{ use workspace::{
searchable::{Direction, SearchableItemHandle}, searchable::{Direction, SearchableItem, SearchableItemHandle},
Item, ItemHandle, ItemNavHistory, Pane, ToolbarItemLocation, ToolbarItemView, Workspace, Item, ItemHandle, ItemNavHistory, Pane, ToolbarItemLocation, ToolbarItemView, Workspace,
}; };
@ -486,16 +486,12 @@ impl ProjectSearchView {
fn select_match(&mut self, direction: Direction, cx: &mut ViewContext<Self>) { fn select_match(&mut self, direction: Direction, cx: &mut ViewContext<Self>) {
if let Some(index) = self.active_match_index { if let Some(index) = self.active_match_index {
let model = self.model.read(cx); let match_ranges = self.model.read(cx).match_ranges.clone();
let results_editor = self.results_editor.read(cx); let new_index = self.results_editor.update(cx, |editor, cx| {
let new_index = match_index_for_direction( editor.match_index_for_direction(&match_ranges, index, direction, cx)
&model.match_ranges, });
&results_editor.selections.newest_anchor().head(),
index, let range_to_select = match_ranges[new_index].clone();
direction,
&results_editor.buffer().read(cx).snapshot(cx),
);
let range_to_select = model.match_ranges[new_index].clone();
self.results_editor.update(cx, |editor, cx| { self.results_editor.update(cx, |editor, cx| {
editor.unfold_ranges([range_to_select.clone()], false, cx); editor.unfold_ranges([range_to_select.clone()], false, cx);
editor.change_selections(Some(Autoscroll::Fit), cx, |s| { editor.change_selections(Some(Autoscroll::Fit), cx, |s| {

View file

@ -10,7 +10,7 @@ use alacritty_terminal::{
event::{Event as AlacTermEvent, EventListener, Notify, WindowSize}, event::{Event as AlacTermEvent, EventListener, Notify, WindowSize},
event_loop::{EventLoop, Msg, Notifier}, event_loop::{EventLoop, Msg, Notifier},
grid::{Dimensions, Scroll as AlacScroll}, grid::{Dimensions, Scroll as AlacScroll},
index::{Column, Direction, Line, Point}, index::{Column, Direction as AlacDirection, Line, Point},
selection::{Selection, SelectionRange, SelectionType}, selection::{Selection, SelectionRange, SelectionType},
sync::FairMutex, sync::FairMutex,
term::{ term::{
@ -84,6 +84,7 @@ pub enum Event {
Bell, Bell,
Wakeup, Wakeup,
BlinkChanged, BlinkChanged,
SelectionsChanged,
} }
#[derive(Clone)] #[derive(Clone)]
@ -93,7 +94,8 @@ enum InternalEvent {
Clear, Clear,
// FocusNextMatch, // FocusNextMatch,
Scroll(AlacScroll), Scroll(AlacScroll),
SetSelection(Option<Selection>), ScrollToPoint(Point),
SetSelection(Option<(Selection, Point)>),
UpdateSelection(Vector2F), UpdateSelection(Vector2F),
Copy, Copy,
} }
@ -384,6 +386,7 @@ impl TerminalBuilder {
matches: Vec::new(), matches: Vec::new(),
last_synced: Instant::now(), last_synced: Instant::now(),
sync_task: None, sync_task: None,
selection_head: None,
}; };
Ok(TerminalBuilder { Ok(TerminalBuilder {
@ -494,12 +497,13 @@ pub struct Terminal {
events: VecDeque<InternalEvent>, events: VecDeque<InternalEvent>,
default_title: String, default_title: String,
title: String, title: String,
last_mouse: Option<(Point, Direction)>, last_mouse: Option<(Point, AlacDirection)>,
pub matches: Vec<RangeInclusive<Point>>, pub matches: Vec<RangeInclusive<Point>>,
cur_size: TerminalSize, cur_size: TerminalSize,
last_content: TerminalContent, last_content: TerminalContent,
last_synced: Instant, last_synced: Instant,
sync_task: Option<Task<()>>, sync_task: Option<Task<()>>,
selection_head: Option<Point>,
} }
impl Terminal { impl Terminal {
@ -576,33 +580,14 @@ impl Terminal {
InternalEvent::Scroll(scroll) => { InternalEvent::Scroll(scroll) => {
term.scroll_display(*scroll); term.scroll_display(*scroll);
} }
// InternalEvent::FocusNextMatch => { InternalEvent::SetSelection(selection) => {
// if let Some((Some(searcher), _origin)) = &self.searcher { term.selection = selection.as_ref().map(|(sel, _)| sel.clone());
// 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());
// //Focus is done with selections in zed if let Some((_, head)) = selection {
// let focus = make_selection(*regex_match.start(), *regex_match.end()); self.selection_head = Some(*head);
// term.selection = Some(focus); }
// } cx.emit(Event::SelectionsChanged)
// None => { }
// //Clear focused match
// term.selection = None;
// }
// }
// }
// }
InternalEvent::SetSelection(sel) => term.selection = sel.clone(),
InternalEvent::UpdateSelection(position) => { InternalEvent::UpdateSelection(position) => {
if let Some(mut selection) = term.selection.take() { if let Some(mut selection) = term.selection.take() {
let point = mouse_point(*position, self.cur_size, term.grid().display_offset()); let point = mouse_point(*position, self.cur_size, term.grid().display_offset());
@ -610,6 +595,9 @@ impl Terminal {
selection.update(point, side); selection.update(point, side);
term.selection = Some(selection); 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)) cx.write_to_clipboard(ClipboardItem::new(txt))
} }
} }
InternalEvent::ScrollToPoint(point) => term.scroll_to_point(*point),
} }
} }
@ -625,53 +614,24 @@ impl Terminal {
&self.last_content &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 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) { pub fn copy(&mut self) {
self.events.push_back(InternalEvent::Copy); self.events.push_back(InternalEvent::Copy);
} }
@ -691,8 +651,10 @@ impl Terminal {
} }
pub fn input(&mut self, input: String) { pub fn input(&mut self, input: String) {
self.scroll(AlacScroll::Bottom); self.events
self.end_select(); .push_back(InternalEvent::Scroll(AlacScroll::Bottom));
self.events.push_back(InternalEvent::SetSelection(None));
self.write_to_pty(input); 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 { match self.last_mouse {
Some((old_point, old_side)) => { Some((old_point, old_side)) => {
if old_point == point && old_side == side { if old_point == point && old_side == side {
@ -830,7 +792,8 @@ impl Terminal {
if !self.mouse_mode(e.shift) { if !self.mouse_mode(e.shift) {
// Alacritty has the same ordering, of first updating the selection // Alacritty has the same ordering, of first updating the selection
// then scrolling 15ms later // then scrolling 15ms later
self.continue_selection(position); self.events
.push_back(InternalEvent::UpdateSelection(position));
// Doesn't make sense to scroll the alt screen // Doesn't make sense to scroll the alt screen
if !self.last_content.mode.contains(TermMode::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; 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); self.pty_tx.notify(bytes);
} }
} else if e.button == MouseButton::Left { } 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)); selection_type.map(|selection_type| Selection::new(selection_type, point, side));
if let Some(sel) = selection { 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; ((e.delta.y() * ALACRITTY_SCROLL_MULTIPLIER) / self.cur_size.line_height) as i32;
if scroll_lines != 0 { if scroll_lines != 0 {
let scroll = AlacScroll::Delta(scroll_lines); 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; type Event = Event;
} }
// fn make_selection(from: Point, to: Point) -> Selection { fn make_selection(range: &RangeInclusive<Point>) -> Selection {
// let mut focus = Selection::new(SelectionType::Simple, from, Direction::Left); let mut selection = Selection::new(SelectionType::Simple, *range.start(), AlacDirection::Left);
// focus.update(to, Direction::Right); selection.update(*range.end(), AlacDirection::Right);
// focus selection
// } }
/// Copied from alacritty/src/display/hint.rs HintMatches::visible_regex_matches() /// Copied from alacritty/src/display/hint.rs HintMatches::visible_regex_matches()
/// Iterate over all 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); start.line = start.line.max(viewport_start - MAX_SEARCH_LINES);
end.line = end.line.min(viewport_end + 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) .skip_while(move |rm| rm.end().line < viewport_start)
.take_while(move |rm| rm.start().line <= viewport_end) .take_while(move |rm| rm.start().line <= viewport_end)
} }

View file

@ -7,7 +7,7 @@ use gpui::{
actions, elements::*, AnyViewHandle, AppContext, Entity, ModelHandle, MutableAppContext, Task, actions, elements::*, AnyViewHandle, AppContext, Entity, ModelHandle, MutableAppContext, Task,
View, ViewContext, ViewHandle, View, ViewContext, ViewHandle,
}; };
use workspace::searchable::{Direction, SearchEvent, SearchableItem, SearchableItemHandle}; use workspace::searchable::{SearchEvent, SearchOptions, SearchableItem, SearchableItemHandle};
use workspace::{Item, Workspace}; use workspace::{Item, Workspace};
use crate::TerminalSize; use crate::TerminalSize;
@ -340,11 +340,19 @@ impl Item for TerminalContainer {
impl SearchableItem for TerminalContainer { impl SearchableItem for TerminalContainer {
type Match = RangeInclusive<Point>; type Match = RangeInclusive<Point>;
fn supported_options() -> SearchOptions {
SearchOptions {
case: false,
word: false,
regex: false,
}
}
/// Convert events raised by this item into search-relevant events (if applicable) /// Convert events raised by this item into search-relevant events (if applicable)
fn to_search_event(event: &Self::Event) -> Option<SearchEvent> { fn to_search_event(event: &Self::Event) -> Option<SearchEvent> {
match event { match event {
Event::Wakeup => Some(SearchEvent::MatchesInvalidated), Event::Wakeup => Some(SearchEvent::MatchesInvalidated),
//TODO selection changed Event::SelectionsChanged => Some(SearchEvent::ActiveMatchChanged),
_ => None, _ => 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<Self::Match>,
_cx: &mut ViewContext<Self>,
) {
// TODO:
}
/// Focus match at given index into the Vec of matches /// Focus match at given index into the Vec of matches
fn activate_match_at_index( fn activate_match(&mut self, index: usize, _: Vec<Self::Match>, cx: &mut ViewContext<Self>) {
&mut self, if let TerminalContainerContent::Connected(connected) = &self.content {
_index: usize, let terminal = connected.read(cx).terminal().clone();
_matches: Vec<Self::Match>, terminal.update(cx, |term, _| term.activate_match(index));
_cx: &mut ViewContext<Self>, cx.notify();
) { }
} }
/// Get all of the matches for this query, should be done on the background /// 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( fn active_match_index(
&mut self, &mut self,
matches: Vec<Self::Match>, matches: Vec<Self::Match>,
_cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
) -> Option<usize> { ) -> Option<usize> {
if matches.len() > 0 { if let TerminalContainerContent::Connected(connected) = &self.content {
Some(0) 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 { } else {
None None
} }

View file

@ -579,12 +579,12 @@ impl Element for TerminalElement {
// searches, highlights to a single range representations // searches, highlights to a single range representations
let mut relative_highlighted_ranges = Vec::new(); 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 { for search_match in search_matches {
relative_highlighted_ranges.push((search_match, match_color)) 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 // then have that representation be converted to the appropriate highlight data structure

View file

@ -14,32 +14,64 @@ pub enum SearchEvent {
ActiveMatchChanged, ActiveMatchChanged,
} }
#[derive(Clone, Copy, PartialEq, Eq)] #[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum Direction { pub enum Direction {
Prev, Prev,
Next, Next,
} }
#[derive(Clone, Copy, Debug, Default)]
pub struct SearchOptions {
pub case: bool,
pub word: bool,
pub regex: bool,
}
pub trait SearchableItem: Item { pub trait SearchableItem: Item {
type Match: Any + Sync + Send + Clone; 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<SearchEvent>; fn to_search_event(event: &Self::Event) -> Option<SearchEvent>;
fn clear_matches(&mut self, cx: &mut ViewContext<Self>); fn clear_matches(&mut self, cx: &mut ViewContext<Self>);
fn update_matches(&mut self, matches: Vec<Self::Match>, cx: &mut ViewContext<Self>); fn update_matches(&mut self, matches: Vec<Self::Match>, cx: &mut ViewContext<Self>);
fn query_suggestion(&mut self, cx: &mut ViewContext<Self>) -> String; fn query_suggestion(&mut self, cx: &mut ViewContext<Self>) -> String;
fn activate_next_match( fn activate_match(
&mut self, &mut self,
index: usize, index: usize,
matches: Vec<Self::Match>,
cx: &mut ViewContext<Self>,
);
fn match_index_for_direction(
&mut self,
matches: &Vec<Self::Match>,
mut current_index: usize,
direction: Direction, direction: Direction,
matches: Vec<Self::Match>, _: &mut ViewContext<Self>,
cx: &mut ViewContext<Self>, ) -> usize {
); match direction {
fn activate_match_at_index( Direction::Prev => {
&mut self, if current_index == 0 {
index: usize, matches.len() - 1
matches: Vec<Self::Match>, } else {
cx: &mut ViewContext<Self>, current_index - 1
); }
}
Direction::Next => {
current_index += 1;
if current_index == matches.len() {
0
} else {
current_index
}
}
}
}
fn find_matches( fn find_matches(
&mut self, &mut self,
query: SearchQuery, query: SearchQuery,
@ -55,28 +87,29 @@ pub trait SearchableItem: Item {
pub trait SearchableItemHandle: ItemHandle { pub trait SearchableItemHandle: ItemHandle {
fn downgrade(&self) -> Box<dyn WeakSearchableItemHandle>; fn downgrade(&self) -> Box<dyn WeakSearchableItemHandle>;
fn boxed_clone(&self) -> Box<dyn SearchableItemHandle>; fn boxed_clone(&self) -> Box<dyn SearchableItemHandle>;
fn supported_options(&self) -> SearchOptions;
fn subscribe( fn subscribe(
&self, &self,
cx: &mut MutableAppContext, cx: &mut MutableAppContext,
handler: Box<dyn Fn(SearchEvent, &mut MutableAppContext)>, handler: Box<dyn Fn(SearchEvent, &mut MutableAppContext)>,
) -> Subscription; ) -> Subscription;
fn clear_highlights(&self, cx: &mut MutableAppContext); fn clear_matches(&self, cx: &mut MutableAppContext);
fn highlight_matches(&self, matches: &Vec<Box<dyn Any + Send>>, cx: &mut MutableAppContext); fn update_matches(&self, matches: &Vec<Box<dyn Any + Send>>, cx: &mut MutableAppContext);
fn query_suggestion(&self, cx: &mut MutableAppContext) -> String; fn query_suggestion(&self, cx: &mut MutableAppContext) -> String;
fn select_next_match_in_direction( fn activate_match(
&self, &self,
index: usize, index: usize,
matches: &Vec<Box<dyn Any + Send>>,
cx: &mut MutableAppContext,
);
fn match_index_for_direction(
&self,
matches: &Vec<Box<dyn Any + Send>>,
current_index: usize,
direction: Direction, direction: Direction,
matches: &Vec<Box<dyn Any + Send>>,
cx: &mut MutableAppContext, cx: &mut MutableAppContext,
); ) -> usize;
fn select_match_by_index( fn find_matches(
&self,
index: usize,
matches: &Vec<Box<dyn Any + Send>>,
cx: &mut MutableAppContext,
);
fn matches(
&self, &self,
query: SearchQuery, query: SearchQuery,
cx: &mut MutableAppContext, cx: &mut MutableAppContext,
@ -97,6 +130,10 @@ impl<T: SearchableItem> SearchableItemHandle for ViewHandle<T> {
Box::new(self.clone()) Box::new(self.clone())
} }
fn supported_options(&self) -> SearchOptions {
T::supported_options()
}
fn subscribe( fn subscribe(
&self, &self,
cx: &mut MutableAppContext, cx: &mut MutableAppContext,
@ -109,40 +146,38 @@ impl<T: SearchableItem> SearchableItemHandle for ViewHandle<T> {
}) })
} }
fn clear_highlights(&self, cx: &mut MutableAppContext) { fn clear_matches(&self, cx: &mut MutableAppContext) {
self.update(cx, |this, cx| this.clear_matches(cx)); self.update(cx, |this, cx| this.clear_matches(cx));
} }
fn highlight_matches(&self, matches: &Vec<Box<dyn Any + Send>>, cx: &mut MutableAppContext) { fn update_matches(&self, matches: &Vec<Box<dyn Any + Send>>, cx: &mut MutableAppContext) {
let matches = downcast_matches(matches); let matches = downcast_matches(matches);
self.update(cx, |this, cx| this.update_matches(matches, cx)); self.update(cx, |this, cx| this.update_matches(matches, cx));
} }
fn query_suggestion(&self, cx: &mut MutableAppContext) -> String { fn query_suggestion(&self, cx: &mut MutableAppContext) -> String {
self.update(cx, |this, cx| this.query_suggestion(cx)) self.update(cx, |this, cx| this.query_suggestion(cx))
} }
fn select_next_match_in_direction( fn activate_match(
&self, &self,
index: usize, index: usize,
matches: &Vec<Box<dyn Any + Send>>,
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<Box<dyn Any + Send>>,
current_index: usize,
direction: Direction, direction: Direction,
matches: &Vec<Box<dyn Any + Send>>,
cx: &mut MutableAppContext, cx: &mut MutableAppContext,
) { ) -> usize {
let matches = downcast_matches(matches); let matches = downcast_matches(matches);
self.update(cx, |this, cx| { 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( fn find_matches(
&self,
index: usize,
matches: &Vec<Box<dyn Any + Send>>,
cx: &mut MutableAppContext,
) {
let matches = downcast_matches(matches);
self.update(cx, |this, cx| {
this.activate_match_at_index(index, matches, cx)
});
}
fn matches(
&self, &self,
query: SearchQuery, query: SearchQuery,
cx: &mut MutableAppContext, cx: &mut MutableAppContext,