diff --git a/assets/keymaps/default.json b/assets/keymaps/default.json index 2fb1f010a6..1d95c33cbf 100644 --- a/assets/keymaps/default.json +++ b/assets/keymaps/default.json @@ -424,8 +424,7 @@ "ctrl-cmd-space": "terminal::ShowCharacterPalette", "cmd-c": "terminal::Copy", "cmd-v": "terminal::Paste", - "cmd-k": "terminal::Clear", - "cmd-s": "terminal::SearchTest" + "cmd-k": "terminal::Clear" } }, { diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index b147d2af2c..e731eb98b5 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -513,17 +513,17 @@ impl SearchableItem for Editor { fn to_search_event(event: &Self::Event) -> Option { match event { - Event::BufferEdited => Some(SearchEvent::ContentsUpdated), - Event::SelectionsChanged { .. } => Some(SearchEvent::SelectionsChanged), + Event::BufferEdited => Some(SearchEvent::MatchesInvalidated), + Event::SelectionsChanged { .. } => Some(SearchEvent::ActiveMatchChanged), _ => None, } } - fn clear_highlights(&mut self, cx: &mut ViewContext) { + fn clear_matches(&mut self, cx: &mut ViewContext) { self.clear_background_highlights::(cx); } - fn highlight_matches(&mut self, matches: Vec>, cx: &mut ViewContext) { + fn update_matches(&mut self, matches: Vec>, cx: &mut ViewContext) { self.highlight_background::( matches, |theme| theme.search.match_background, @@ -553,7 +553,7 @@ impl SearchableItem for Editor { } } - fn select_next_match_in_direction( + fn activate_next_match( &mut self, index: usize, direction: Direction, @@ -575,7 +575,7 @@ impl SearchableItem for Editor { }); } - fn select_match_by_index( + fn activate_match_at_index( &mut self, index: usize, matches: Vec>, @@ -586,7 +586,7 @@ impl SearchableItem for Editor { }); } - fn matches( + fn find_matches( &mut self, query: project::search::SearchQuery, cx: &mut ViewContext, diff --git a/crates/search/src/buffer_search.rs b/crates/search/src/buffer_search.rs index a2fb342e3f..1a46526025 100644 --- a/crates/search/src/buffer_search.rs +++ b/crates/search/src/buffer_search.rs @@ -174,7 +174,9 @@ impl ToolbarItemView for BufferSearchBar { cx, Box::new(move |search_event, cx| { if let Some(this) = handle.upgrade(cx) { - this.update(cx, |this, cx| this.on_active_editor_event(search_event, cx)); + this.update(cx, |this, cx| { + this.on_active_searchable_item_event(search_event, cx) + }); } }), )); @@ -461,10 +463,10 @@ impl BufferSearchBar { } } - fn on_active_editor_event(&mut self, event: SearchEvent, cx: &mut ViewContext) { + fn on_active_searchable_item_event(&mut self, event: SearchEvent, cx: &mut ViewContext) { match event { - SearchEvent::ContentsUpdated => self.update_matches(false, cx), - SearchEvent::SelectionsChanged => self.update_match_index(cx), + SearchEvent::MatchesInvalidated => self.update_matches(false, cx), + SearchEvent::ActiveMatchChanged => self.update_match_index(cx), } } diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index 0da7e1cf76..4adaa2140f 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -36,7 +36,7 @@ use settings::{AlternateScroll, Settings, Shell, TerminalBlink}; use std::{ collections::{HashMap, VecDeque}, fmt::Display, - ops::Sub, + ops::{RangeInclusive, Sub}, path::PathBuf, sync::Arc, time::Duration, @@ -48,7 +48,7 @@ use gpui::{ keymap::Keystroke, scene::{ClickRegionEvent, DownRegionEvent, DragRegionEvent, UpRegionEvent}, ClipboardItem, Entity, ModelContext, MouseButton, MouseMovedEvent, MutableAppContext, - ScrollWheelEvent, + ScrollWheelEvent, Task, }; use crate::mappings::{ @@ -68,8 +68,6 @@ pub fn init(cx: &mut MutableAppContext) { ///Scroll multiplier that is set to 3 by default. This will be removed when I ///Implement scroll bars. const ALACRITTY_SCROLL_MULTIPLIER: f32 = 3.; -// const ALACRITTY_SEARCH_LINE_LIMIT: usize = 1000; -const SEARCH_FORWARD: Direction = Direction::Left; const MAX_SEARCH_LINES: usize = 100; const DEBUG_TERMINAL_WIDTH: f32 = 500.; const DEBUG_TERMINAL_HEIGHT: f32 = 30.; @@ -91,7 +89,7 @@ enum InternalEvent { ColorRequest(usize, Arc String + Sync + Send + 'static>), Resize(TerminalSize), Clear, - FocusNextMatch, + // FocusNextMatch, Scroll(AlacScroll), SetSelection(Option), UpdateSelection(Vector2F), @@ -382,7 +380,8 @@ impl TerminalBuilder { cur_size: initial_size, last_mouse: None, last_offset: 0, - searcher: None, + matches: Vec::new(), + selection_text: None, }; Ok(TerminalBuilder { @@ -454,7 +453,8 @@ pub struct Terminal { last_mode: TermMode, last_offset: usize, last_mouse: Option<(Point, Direction)>, - searcher: Option<(Option, Point)>, + pub matches: Vec>, + pub selection_text: Option, } impl Terminal { @@ -531,32 +531,32 @@ 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::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()); - //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; - } - } - } - } + // //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(), InternalEvent::UpdateSelection(position) => { if let Some(mut selection) = term.selection.take() { @@ -594,34 +594,34 @@ impl Terminal { self.events.push_back(InternalEvent::Scroll(scroll)); } - fn focus_next_match(&mut self) { - self.events.push_back(InternalEvent::FocusNextMatch); - } + // 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)), - }; + // 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(); - } - } + // 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)) - } + // 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 end_search(&mut self) { + // self.searcher = None; + // } pub fn copy(&mut self) { self.events.push_back(InternalEvent::Copy); @@ -669,12 +669,12 @@ impl Terminal { pub fn render_lock(&mut self, cx: &mut ModelContext, f: F) -> T where - F: FnOnce(RenderableContent, char, Vec) -> T, + F: FnOnce(RenderableContent, char) -> T, { - let m = self.term.clone(); //Arc clone - let mut term = m.lock(); + let term = self.term.clone(); + let mut term = term.lock(); - //Note that this ordering matters for + //Note that this ordering matters for event processing while let Some(e) = self.events.pop_front() { self.process_terminal_event(&e, &mut term, cx) } @@ -683,16 +683,12 @@ impl Terminal { let content = term.renderable_content(); + self.selection_text = term.selection_to_string(); self.last_offset = content.display_offset; let cursor_text = term.grid()[content.cursor.point].c; - let mut matches = vec![]; - if let Some((Some(r), _)) = &self.searcher { - matches.extend(make_search_matches(&term, &r)); - } - - f(content, cursor_text, matches) + f(content, cursor_text) } pub fn focus_in(&self) { @@ -865,6 +861,33 @@ impl Terminal { } } } + + pub fn find_matches( + &mut self, + query: project::search::SearchQuery, + cx: &mut ModelContext, + ) -> Task>> { + let term = self.term.clone(); + dbg!("Spawning find_matches"); + cx.background().spawn(async move { + let searcher = match query { + project::search::SearchQuery::Text { query, .. } => { + RegexSearch::new(query.as_ref()) + } + project::search::SearchQuery::Regex { query, .. } => { + RegexSearch::new(query.as_ref()) + } + }; + + if searcher.is_err() { + return Vec::new(); + } + let searcher = searcher.unwrap(); + + let term = term.lock(); + dbg!(make_search_matches(&term, &searcher).collect()) + }) + } } impl Drop for Terminal { @@ -877,11 +900,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(from: Point, to: Point) -> Selection { +// let mut focus = Selection::new(SelectionType::Simple, from, Direction::Left); +// focus.update(to, Direction::Right); +// focus +// } /// Copied from alacritty/src/display/hint.rs HintMatches::visible_regex_matches() /// Iterate over all visible regex matches. diff --git a/crates/terminal/src/terminal_container_view.rs b/crates/terminal/src/terminal_container_view.rs index 02c018c82b..d93dfe4a78 100644 --- a/crates/terminal/src/terminal_container_view.rs +++ b/crates/terminal/src/terminal_container_view.rs @@ -1,17 +1,20 @@ use crate::terminal_view::TerminalView; use crate::{Event, Terminal, TerminalBuilder, TerminalError}; +use alacritty_terminal::index::Point; use dirs::home_dir; use gpui::{ - actions, elements::*, AnyViewHandle, AppContext, Entity, ModelHandle, MutableAppContext, View, - ViewContext, ViewHandle, + actions, elements::*, AnyViewHandle, AppContext, Entity, ModelHandle, MutableAppContext, Task, + View, ViewContext, ViewHandle, }; +use workspace::searchable::{Direction, SearchEvent, SearchableItem, SearchableItemHandle}; use workspace::{Item, Workspace}; use crate::TerminalSize; use project::{LocalWorktree, Project, ProjectPath}; use settings::{AlternateScroll, Settings, WorkingDirectory}; use smallvec::SmallVec; +use std::ops::RangeInclusive; use std::path::{Path, PathBuf}; use crate::terminal_element::TerminalElement; @@ -328,6 +331,98 @@ impl Item for TerminalContainer { fn should_close_item_on_event(event: &Self::Event) -> bool { matches!(event, &Event::CloseTerminal) } + + fn as_searchable(&self, handle: &ViewHandle) -> Option> { + Some(Box::new(handle.clone())) + } +} + +impl SearchableItem for TerminalContainer { + type Match = RangeInclusive; + + /// 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 + _ => None, + } + } + + /// Clear stored matches + fn clear_matches(&mut self, cx: &mut ViewContext) { + if let TerminalContent::Connected(connected) = &self.content { + let terminal = connected.read(cx).terminal().clone(); + terminal.update(cx, |term, _| term.matches.clear()) + } + } + + /// Store matches returned from find_matches somewhere for rendering + fn update_matches(&mut self, matches: Vec, cx: &mut ViewContext) { + if let TerminalContent::Connected(connected) = &self.content { + let terminal = connected.read(cx).terminal().clone(); + dbg!(&matches); + terminal.update(cx, |term, _| term.matches = matches) + } + } + + /// Return the selection content to pre-load into this search + fn query_suggestion(&mut self, cx: &mut ViewContext) -> String { + if let TerminalContent::Connected(connected) = &self.content { + let terminal = connected.read(cx).terminal().clone(); + terminal.read(cx).selection_text.clone().unwrap_or_default() + } else { + Default::default() + } + } + + /// 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, + ) { + } + + /// Get all of the matches for this query, should be done on the background + fn find_matches( + &mut self, + query: project::search::SearchQuery, + cx: &mut ViewContext, + ) -> Task> { + if let TerminalContent::Connected(connected) = &self.content { + let terminal = connected.read(cx).terminal().clone(); + terminal.update(cx, |term, cx| term.find_matches(query, cx)) + } else { + Task::ready(Vec::new()) + } + } + + /// Reports back to the search toolbar what the active match should be (the selection) + fn active_match_index( + &mut self, + matches: Vec, + _cx: &mut ViewContext, + ) -> Option { + if matches.len() > 0 { + Some(0) + } else { + None + } + } } ///Get's the working directory for the given workspace, respecting the user's settings. diff --git a/crates/terminal/src/terminal_element.rs b/crates/terminal/src/terminal_element.rs index 9b31160529..fd6c3883d2 100644 --- a/crates/terminal/src/terminal_element.rs +++ b/crates/terminal/src/terminal_element.rs @@ -570,19 +570,25 @@ impl Element for TerminalElement { TerminalSize::new(line_height, cell_width, constraint.max) }; + let search_matches = if let Some(terminal_model) = self.terminal.upgrade(cx) { + terminal_model.read(cx).matches.clone() + } else { + Default::default() + }; + let background_color = if self.modal { terminal_theme.colors.modal_background } else { terminal_theme.colors.background }; - let (cells, selection, cursor, display_offset, cursor_text, search_matches, mode) = self + let (cells, selection, cursor, display_offset, cursor_text, mode) = self .terminal .upgrade(cx) .unwrap() .update(cx.app, |terminal, cx| { terminal.set_size(dimensions); - terminal.render_lock(cx, |content, cursor_text, search_matches| { + terminal.render_lock(cx, |content, cursor_text| { let mut cells = vec![]; cells.extend( content @@ -605,7 +611,6 @@ impl Element for TerminalElement { content.cursor, content.display_offset, cursor_text, - search_matches.clone(), content.mode, ) }) @@ -613,12 +618,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/terminal/src/terminal_view.rs b/crates/terminal/src/terminal_view.rs index 0e067ac0fb..fee84dc859 100644 --- a/crates/terminal/src/terminal_view.rs +++ b/crates/terminal/src/terminal_view.rs @@ -1,6 +1,6 @@ -use std::time::Duration; +use std::{ops::RangeInclusive, time::Duration}; -use alacritty_terminal::term::TermMode; +use alacritty_terminal::{index::Point, term::TermMode}; use context_menu::{ContextMenu, ContextMenuItem}; use gpui::{ actions, @@ -8,8 +8,8 @@ use gpui::{ geometry::vector::Vector2F, impl_internal_actions, keymap::Keystroke, - AnyViewHandle, AppContext, Element, ElementBox, Entity, ModelHandle, MutableAppContext, View, - ViewContext, ViewHandle, + AnyViewHandle, AppContext, Element, ElementBox, Entity, ModelHandle, MutableAppContext, Task, + View, ViewContext, ViewHandle, }; use settings::{Settings, TerminalBlink}; use smol::Timer; @@ -58,8 +58,6 @@ pub fn init(cx: &mut MutableAppContext) { cx.add_action(TerminalView::paste); cx.add_action(TerminalView::clear); cx.add_action(TerminalView::show_character_palette); - - cx.add_action(TerminalView::test_search); } ///A terminal view, maintains the PTY's file handles and communicates with the terminal @@ -162,14 +160,6 @@ impl TerminalView { } } - fn test_search(&mut self, _: &SearchTest, cx: &mut ViewContext) { - let search_string = "ttys"; - self.terminal.update(cx, |term, _| { - term.search(search_string); - }); - cx.notify(); - } - fn clear(&mut self, _: &Clear, cx: &mut ViewContext) { self.terminal.update(cx, |term, _| term.clear()); cx.notify(); @@ -246,6 +236,19 @@ impl TerminalView { .detach(); } + pub fn find_matches( + &mut self, + query: project::search::SearchQuery, + cx: &mut ViewContext, + ) -> Task>> { + self.terminal + .update(cx, |term, cx| term.find_matches(query, cx)) + } + + pub fn terminal(&self) -> &ModelHandle { + &self.terminal + } + fn next_blink_epoch(&mut self) -> usize { self.blink_epoch += 1; self.blink_epoch diff --git a/crates/workspace/src/searchable.rs b/crates/workspace/src/searchable.rs index b0791ff4b3..de0d4f774a 100644 --- a/crates/workspace/src/searchable.rs +++ b/crates/workspace/src/searchable.rs @@ -10,8 +10,8 @@ use crate::{Item, ItemHandle, WeakItemHandle}; #[derive(Debug)] pub enum SearchEvent { - ContentsUpdated, - SelectionsChanged, + MatchesInvalidated, + ActiveMatchChanged, } #[derive(Clone, Copy, PartialEq, Eq)] @@ -24,24 +24,27 @@ pub trait SearchableItem: Item { type Match: Any + Sync + Send + Clone; fn to_search_event(event: &Self::Event) -> Option; - fn clear_highlights(&mut self, cx: &mut ViewContext); - fn highlight_matches(&mut self, matches: Vec, cx: &mut ViewContext); + 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 select_next_match_in_direction( + fn activate_next_match( &mut self, index: usize, direction: Direction, matches: Vec, cx: &mut ViewContext, ); - fn select_match_by_index( + fn activate_match_at_index( &mut self, index: usize, matches: Vec, cx: &mut ViewContext, ); - fn matches(&mut self, query: SearchQuery, cx: &mut ViewContext) - -> Task>; + fn find_matches( + &mut self, + query: SearchQuery, + cx: &mut ViewContext, + ) -> Task>; fn active_match_index( &mut self, matches: Vec, @@ -107,11 +110,11 @@ impl SearchableItemHandle for ViewHandle { } fn clear_highlights(&self, cx: &mut MutableAppContext) { - self.update(cx, |this, cx| this.clear_highlights(cx)); + self.update(cx, |this, cx| this.clear_matches(cx)); } fn highlight_matches(&self, matches: &Vec>, cx: &mut MutableAppContext) { let matches = downcast_matches(matches); - self.update(cx, |this, cx| this.highlight_matches(matches, cx)); + 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)) @@ -125,7 +128,7 @@ impl SearchableItemHandle for ViewHandle { ) { let matches = downcast_matches(matches); self.update(cx, |this, cx| { - this.select_next_match_in_direction(index, direction, matches, cx) + this.activate_next_match(index, direction, matches, cx) }); } fn select_match_by_index( @@ -136,7 +139,7 @@ impl SearchableItemHandle for ViewHandle { ) { let matches = downcast_matches(matches); self.update(cx, |this, cx| { - this.select_match_by_index(index, matches, cx) + this.activate_match_at_index(index, matches, cx) }); } fn matches( @@ -144,7 +147,7 @@ impl SearchableItemHandle for ViewHandle { query: SearchQuery, cx: &mut MutableAppContext, ) -> Task>> { - let matches = self.update(cx, |this, cx| this.matches(query, cx)); + let matches = self.update(cx, |this, cx| this.find_matches(query, cx)); cx.foreground().spawn(async { let matches = matches.await; matches