diff --git a/crates/terminal/src/search.rs b/crates/terminal/src/search.rs deleted file mode 100644 index d9d7b076eb..0000000000 --- a/crates/terminal/src/search.rs +++ /dev/null @@ -1,78 +0,0 @@ -use std::{borrow::Cow, ops::Deref}; - -use alacritty_terminal::{ - grid::Dimensions, - index::{Column, Direction, Line, Point}, - term::search::{Match, RegexIter, RegexSearch}, - Term, -}; - -const MAX_SEARCH_LINES: usize = 100; - -///Header and impl fom alacritty/src/display/content.rs HintMatches -#[derive(Default)] -pub struct SearchMatches<'a> { - /// All visible matches. - matches: Cow<'a, [Match]>, - - /// Index of the last match checked. - index: usize, -} - -impl<'a> SearchMatches<'a> { - /// Create new renderable matches iterator.. - fn new(matches: impl Into>) -> Self { - Self { - matches: matches.into(), - index: 0, - } - } - - /// Create from regex matches on term visable part. - pub fn visible_regex_matches(term: &Term, dfas: &RegexSearch) -> Self { - let matches = visible_regex_match_iter(term, dfas).collect::>(); - Self::new(matches) - } - - /// Advance the regex tracker to the next point. - /// - /// This will return `true` if the point passed is part of a regex match. - fn advance(&mut self, point: Point) -> bool { - while let Some(bounds) = self.get(self.index) { - if bounds.start() > &point { - break; - } else if bounds.end() < &point { - self.index += 1; - } else { - return true; - } - } - false - } -} - -impl<'a> Deref for SearchMatches<'a> { - type Target = [Match]; - - fn deref(&self) -> &Self::Target { - self.matches.deref() - } -} - -/// Copied from alacritty/src/display/hint.rs -/// Iterate over all visible regex matches. -fn visible_regex_match_iter<'a, T>( - term: &'a Term, - regex: &'a RegexSearch, -) -> impl Iterator + 'a { - let viewport_start = Line(-(term.grid().display_offset() as i32)); - let viewport_end = viewport_start + term.bottommost_line(); - let mut start = term.line_search_left(Point::new(viewport_start, Column(0))); - let mut end = term.line_search_right(Point::new(viewport_end, Column(0))); - 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) - .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.rs b/crates/terminal/src/terminal.rs index 3b40452ee6..0da7e1cf76 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -1,6 +1,5 @@ pub mod mappings; pub mod modal; -pub mod search; pub mod terminal_container_view; pub mod terminal_element; pub mod terminal_view; @@ -11,10 +10,14 @@ use alacritty_terminal::{ event::{Event as AlacTermEvent, EventListener, Notify, WindowSize}, event_loop::{EventLoop, Msg, Notifier}, grid::{Dimensions, Scroll as AlacScroll}, - index::{Direction, Point}, + index::{Column, Direction, Line, Point}, selection::{Selection, SelectionType}, sync::FairMutex, - term::{color::Rgb, search::RegexSearch, RenderableContent, TermMode}, + term::{ + color::Rgb, + search::{Match, RegexIter, RegexSearch}, + RenderableContent, TermMode, + }, tty::{self, setup_env}, Term, }; @@ -28,6 +31,7 @@ use mappings::mouse::{ alt_scroll, mouse_button_report, mouse_moved_report, mouse_point, mouse_side, scroll_report, }; use modal::deploy_modal; + use settings::{AlternateScroll, Settings, Shell, TerminalBlink}; use std::{ collections::{HashMap, VecDeque}, @@ -66,7 +70,7 @@ pub fn init(cx: &mut MutableAppContext) { 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.; const DEBUG_CELL_WIDTH: f32 = 5.; @@ -528,9 +532,17 @@ impl Terminal { term.scroll_display(*scroll); } InternalEvent::FocusNextMatch => { - if let Some((Some(searcher), origin)) = &self.searcher { - match term.search_next(searcher, *origin, SEARCH_FORWARD, Direction::Left, None) - { + 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()); @@ -657,7 +669,7 @@ impl Terminal { pub fn render_lock(&mut self, cx: &mut ModelContext, f: F) -> T where - F: FnOnce(RenderableContent, char, Option) -> T, + F: FnOnce(RenderableContent, char, Vec) -> T, { let m = self.term.clone(); //Arc clone let mut term = m.lock(); @@ -675,11 +687,12 @@ impl Terminal { let cursor_text = term.grid()[content.cursor.point].c; - f( - content, - cursor_text, - self.searcher.as_ref().and_then(|s| s.0.clone()), - ) + let mut matches = vec![]; + if let Some((Some(r), _)) = &self.searcher { + matches.extend(make_search_matches(&term, &r)); + } + + f(content, cursor_text, matches) } pub fn focus_in(&self) { @@ -854,12 +867,6 @@ impl Terminal { } } -fn make_selection(from: Point, to: Point) -> Selection { - let mut focus = Selection::new(SelectionType::Simple, from, Direction::Left); - focus.update(to, Direction::Right); - focus -} - impl Drop for Terminal { fn drop(&mut self) { self.pty_tx.0.send(Msg::Shutdown).ok(); @@ -870,6 +877,30 @@ 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 +} + +/// Copied from alacritty/src/display/hint.rs HintMatches::visible_regex_matches() +/// Iterate over all visible regex matches. +fn make_search_matches<'a, T>( + term: &'a Term, + regex: &'a RegexSearch, +) -> impl Iterator + 'a { + let viewport_start = Line(-(term.grid().display_offset() as i32)); + let viewport_end = viewport_start + term.bottommost_line(); + let mut start = term.line_search_left(Point::new(viewport_start, Column(0))); + let mut end = term.line_search_right(Point::new(viewport_end, Column(0))); + 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) + .skip_while(move |rm| rm.end().line < viewport_start) + .take_while(move |rm| rm.start().line <= viewport_end) +} + #[cfg(test)] mod tests { pub mod terminal_test_context; diff --git a/crates/terminal/src/terminal_element.rs b/crates/terminal/src/terminal_element.rs index 2f6c010927..9b31160529 100644 --- a/crates/terminal/src/terminal_element.rs +++ b/crates/terminal/src/terminal_element.rs @@ -2,7 +2,6 @@ use alacritty_terminal::{ ansi::{Color as AnsiColor, Color::Named, CursorShape as AlacCursorShape, NamedColor}, grid::Dimensions, index::Point, - selection::SelectionRange, term::{ cell::{Cell, Flags}, TermMode, @@ -27,7 +26,7 @@ use settings::Settings; use theme::TerminalStyle; use util::ResultExt; -use std::fmt::Debug; +use std::{fmt::Debug, ops::RangeInclusive}; use std::{ mem, ops::{Deref, Range}, @@ -43,12 +42,12 @@ use crate::{ pub struct LayoutState { cells: Vec, rects: Vec, - selections: Vec, + relative_highlighted_ranges: Vec<(RangeInclusive, Color)>, cursor: Option, background_color: Color, - selection_color: Color, size: TerminalSize, mode: TermMode, + display_offset: usize, } #[derive(Debug)] @@ -166,30 +165,6 @@ impl LayoutRect { } } -#[derive(Clone, Debug, Default)] -struct RelativeHighlightedRange { - line_index: usize, - range: Range, -} - -impl RelativeHighlightedRange { - fn new(line_index: usize, range: Range) -> Self { - RelativeHighlightedRange { line_index, range } - } - - fn to_highlighted_range_line( - &self, - origin: Vector2F, - layout: &LayoutState, - ) -> HighlightedRangeLine { - let start_x = origin.x() + self.range.start as f32 * layout.size.cell_width; - let end_x = - origin.x() + self.range.end as f32 * layout.size.cell_width + layout.size.cell_width; - - HighlightedRangeLine { start_x, end_x } - } -} - ///The GPUI element that paints the terminal. ///We need to keep a reference to the view for mouse events, do we need it for any other terminal stuff, or can we move that to connection? pub struct TerminalElement { @@ -217,6 +192,8 @@ impl TerminalElement { } } + //Vec> -> Clip out the parts of the ranges + fn layout_grid( grid: Vec, text_style: &TextStyle, @@ -224,41 +201,22 @@ impl TerminalElement { text_layout_cache: &TextLayoutCache, font_cache: &FontCache, modal: bool, - selection_range: Option, - ) -> ( - Vec, - Vec, - Vec, - ) { + ) -> (Vec, Vec) { let mut cells = vec![]; let mut rects = vec![]; - let mut highlight_ranges = vec![]; let mut cur_rect: Option = None; let mut cur_alac_color = None; - let mut highlighted_range = None; let linegroups = grid.into_iter().group_by(|i| i.point.line); for (line_index, (_, line)) in linegroups.into_iter().enumerate() { - for (x_index, cell) in line.enumerate() { + for cell in line { let mut fg = cell.fg; let mut bg = cell.bg; if cell.flags.contains(Flags::INVERSE) { mem::swap(&mut fg, &mut bg); } - //Increase selection range - { - if selection_range - .map(|range| range.contains(cell.point)) - .unwrap_or(false) - { - let mut range = highlighted_range.take().unwrap_or(x_index..x_index); - range.end = range.end.max(x_index); - highlighted_range = Some(range); - } - } - //Expand background rect range { if matches!(bg, Named(NamedColor::Background)) { @@ -324,18 +282,11 @@ impl TerminalElement { }; } - if highlighted_range.is_some() { - highlight_ranges.push(RelativeHighlightedRange::new( - line_index, - highlighted_range.take().unwrap(), - )) - } - if cur_rect.is_some() { rects.push(cur_rect.take().unwrap()); } } - (cells, rects, highlight_ranges) + (cells, rects) } // Compute the cursor position and expected block width, may return a zero width if x_for_index returns @@ -612,6 +563,7 @@ impl Element for TerminalElement { let terminal_theme = settings.theme.terminal.clone(); //TODO: Try to minimize this clone. let text_style = TerminalElement::make_text_style(font_cache, settings); let selection_color = settings.theme.editor.selection.selection; + let match_color = settings.theme.search.match_background; let dimensions = { let line_height = font_cache.line_height(text_style.font_size); let cell_width = font_cache.em_advance(text_style.font_id, text_style.font_size); @@ -624,13 +576,13 @@ impl Element for TerminalElement { terminal_theme.colors.background }; - let (cells, selection, cursor, display_offset, cursor_text, searcher, mode) = self + let (cells, selection, cursor, display_offset, cursor_text, search_matches, mode) = self .terminal .upgrade(cx) .unwrap() - .update(cx.app, |terminal, mcx| { + .update(cx.app, |terminal, cx| { terminal.set_size(dimensions); - terminal.render_lock(mcx, |content, cursor_text, searcher| { + terminal.render_lock(cx, |content, cursor_text, search_matches| { let mut cells = vec![]; cells.extend( content @@ -653,20 +605,30 @@ impl Element for TerminalElement { content.cursor, content.display_offset, cursor_text, - searcher, + search_matches.clone(), content.mode, ) }) }); - let (cells, rects, selections) = TerminalElement::layout_grid( + // 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)) + } + + // then have that representation be converted to the appropriate highlight data structure + + let (cells, rects) = TerminalElement::layout_grid( cells, &text_style, &terminal_theme, cx.text_layout_cache, cx.font_cache(), self.modal, - selection, ); //Layout cursor. Rectangle is used for IME, so we should lay it out even @@ -729,11 +691,11 @@ impl Element for TerminalElement { cells, cursor, background_color, - selection_color, size: dimensions, rects, - selections, + relative_highlighted_ranges, mode, + display_offset, }, ) } @@ -768,30 +730,23 @@ impl Element for TerminalElement { } }); - //Draw Selection + //Draw Highlighted Backgrounds cx.paint_layer(clip_bounds, |cx| { - let start_y = layout.selections.get(0).map(|highlight| { - origin.y() + highlight.line_index as f32 * layout.size.line_height - }); - - if let Some(y) = start_y { - let range_lines = layout - .selections - .iter() - .map(|relative_highlight| { - relative_highlight.to_highlighted_range_line(origin, layout) - }) - .collect::>(); - - let hr = HighlightedRange { - start_y: y, //Need to change this - line_height: layout.size.line_height, - lines: range_lines, - color: layout.selection_color, - //Copied from editor. TODO: move to theme or something - corner_radius: 0.15 * layout.size.line_height, - }; - hr.paint(bounds, cx.scene); + for (relative_highlighted_range, color) in layout.relative_highlighted_ranges.iter() + { + if let Some((start_y, highlighted_range_lines)) = + to_highlighted_range_lines(relative_highlighted_range, layout, origin) + { + let hr = HighlightedRange { + start_y, //Need to change this + line_height: layout.size.line_height, + lines: highlighted_range_lines, + color: color.clone(), + //Copied from editor. TODO: move to theme or something + corner_radius: 0.15 * layout.size.line_height, + }; + hr.paint(bounds, cx.scene); + } } }); @@ -894,3 +849,65 @@ impl Element for TerminalElement { Some(layout.cursor.as_ref()?.bounding_rect(origin)) } } + +fn to_highlighted_range_lines( + range: &RangeInclusive, + layout: &LayoutState, + origin: Vector2F, +) -> Option<(f32, Vec)> { + // Step 1. Normalize the points to be viewport relative. + //When display_offset = 1, here's how the grid is arranged: + //--- Viewport top + //-2,0 -2,1... + //-1,0 -1,1... + //--------- Terminal Top + // 0,0 0,1... + // 1,0 1,1... + //--- Viewport Bottom + // 2,0 2,1... + //--------- Terminal Bottom + + // Normalize to viewport relative, from terminal relative. + // lines are i32s, which are negative above the top left corner of the terminal + // If the user has scrolled, we use the display_offset to tell us which offset + // of the grid data we should be looking at. But for the rendering step, we don't + // want negatives. We want things relative to the 'viewport' (the area of the grid + // which is currently shown according to the display offset) + let unclamped_start = Point::new( + range.start().line + layout.display_offset, + range.start().column, + ); + let unclamped_end = Point::new(range.end().line + layout.display_offset, range.end().column); + + // Step 2. Clamp range to viewport, and return None if it doesn't overlap + if unclamped_end.line.0 < 0 || unclamped_start.line.0 > layout.size.num_lines() as i32 { + return None; + } + + let clamped_start_line = unclamped_start.line.0.max(0) as usize; + let clamped_end_line = unclamped_end.line.0.min(layout.size.num_lines() as i32) as usize; + //Convert the start of the range to pixels + let start_y = origin.y() + clamped_start_line as f32 * layout.size.line_height; + + // Step 3. Expand ranges that cross lines into a collection of single-line ranges. + // (also convert to pixels) + let mut highlighted_range_lines = Vec::new(); + for line in clamped_start_line..=clamped_end_line { + let mut line_start = 0; + let mut line_end = layout.size.columns(); + + if line == clamped_start_line { + line_start = unclamped_start.column.0 as usize; + } + if line == clamped_end_line { + line_end = unclamped_end.column.0 as usize + 1; //+1 for inclusive + } + + highlighted_range_lines.push(HighlightedRangeLine { + start_x: origin.x() + line_start as f32 * layout.size.cell_width, + end_x: origin.x() + line_end as f32 * layout.size.cell_width, + }); + } + + Some((start_y, highlighted_range_lines)) +}