Search rendering and basic regex stuff complete

Mikayla Maki created

Change summary

crates/terminal/src/search.rs           |  78 ----------
crates/terminal/src/terminal.rs         |  69 ++++++--
crates/terminal/src/terminal_element.rs | 193 ++++++++++++++------------
3 files changed, 155 insertions(+), 185 deletions(-)

Detailed changes

crates/terminal/src/search.rs 🔗

@@ -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<Cow<'a, [Match]>>) -> Self {
-        Self {
-            matches: matches.into(),
-            index: 0,
-        }
-    }
-
-    /// Create from regex matches on term visable part.
-    pub fn visible_regex_matches<T>(term: &Term<T>, dfas: &RegexSearch) -> Self {
-        let matches = visible_regex_match_iter(term, dfas).collect::<Vec<_>>();
-        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<T>,
-    regex: &'a RegexSearch,
-) -> impl Iterator<Item = Match> + '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)
-}

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<F, T>(&mut self, cx: &mut ModelContext<Self>, f: F) -> T
     where
-        F: FnOnce(RenderableContent, char, Option<RegexSearch>) -> T,
+        F: FnOnce(RenderableContent, char, Vec<Match>) -> 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<T>,
+    regex: &'a RegexSearch,
+) -> impl Iterator<Item = Match> + '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;

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<LayoutCell>,
     rects: Vec<LayoutRect>,
-    selections: Vec<RelativeHighlightedRange>,
+    relative_highlighted_ranges: Vec<(RangeInclusive<Point>, Color)>,
     cursor: Option<Cursor>,
     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<usize>,
-}
-
-impl RelativeHighlightedRange {
-    fn new(line_index: usize, range: Range<usize>) -> 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<Range<Point>> -> Clip out the parts of the ranges
+
     fn layout_grid(
         grid: Vec<IndexedCell>,
         text_style: &TextStyle,
@@ -224,41 +201,22 @@ impl TerminalElement {
         text_layout_cache: &TextLayoutCache,
         font_cache: &FontCache,
         modal: bool,
-        selection_range: Option<SelectionRange>,
-    ) -> (
-        Vec<LayoutCell>,
-        Vec<LayoutRect>,
-        Vec<RelativeHighlightedRange>,
-    ) {
+    ) -> (Vec<LayoutCell>, Vec<LayoutRect>) {
         let mut cells = vec![];
         let mut rects = vec![];
-        let mut highlight_ranges = vec![];
 
         let mut cur_rect: Option<LayoutRect> = 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::<Vec<HighlightedRangeLine>>();
-
-                    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<Point>,
+    layout: &LayoutState,
+    origin: Vector2F,
+) -> Option<(f32, Vec<HighlightedRangeLine>)> {
+    // 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))
+}