diff options
Diffstat (limited to 'alacritty_terminal/src/term/mod.rs')
-rw-r--r-- | alacritty_terminal/src/term/mod.rs | 362 |
1 files changed, 110 insertions, 252 deletions
diff --git a/alacritty_terminal/src/term/mod.rs b/alacritty_terminal/src/term/mod.rs index bc7235cb..63a5e040 100644 --- a/alacritty_terminal/src/term/mod.rs +++ b/alacritty_terminal/src/term/mod.rs @@ -14,12 +14,11 @@ // //! Exports the `Term` type which is a high-level API for the Grid use std::cmp::{max, min}; -use std::ops::{Index, IndexMut, Range, RangeInclusive}; +use std::ops::{Index, IndexMut, Range}; use std::time::{Duration, Instant}; use std::{io, mem, ptr}; use log::{debug, trace}; -use rfind_url::{Parser, ParserState}; use serde::{Deserialize, Serialize}; use unicode_width::UnicodeWidthChar; @@ -33,13 +32,12 @@ use crate::event::{Event, EventListener}; use crate::grid::{ BidirectionalIterator, DisplayIter, Grid, GridCell, IndexRegion, Indexed, Scroll, }; -use crate::index::{self, Column, Contains, IndexRange, Line, Linear, Point}; +use crate::index::{self, Column, IndexRange, Line, Point}; use crate::selection::{self, Selection, SelectionRange, Span}; use crate::term::cell::{Cell, Flags, LineLength}; use crate::term::color::Rgb; #[cfg(windows)] use crate::tty; -use crate::url::Url; pub mod cell; pub mod color; @@ -76,7 +74,7 @@ impl<T> Search for Term<T> { break; } - if iter.point().col == last_col && !cell.flags.contains(cell::Flags::WRAPLINE) { + if iter.point().col == last_col && !cell.flags.contains(Flags::WRAPLINE) { break; // cut off if on new line or hit escape char } @@ -100,7 +98,7 @@ impl<T> Search for Term<T> { point = iter.point(); - if point.col == last_col && !cell.flags.contains(cell::Flags::WRAPLINE) { + if point.col == last_col && !cell.flags.contains(Flags::WRAPLINE) { break; // cut off if on new line or hit escape char } } @@ -181,7 +179,6 @@ pub struct RenderableCellsIter<'a, C> { config: &'a Config<C>, colors: &'a color::List, selection: Option<SelectionRange>, - url_highlight: &'a Option<RangeInclusive<index::Linear>>, } impl<'a, C> RenderableCellsIter<'a, C> { @@ -202,7 +199,7 @@ impl<'a, C> RenderableCellsIter<'a, C> { let selection_range = selection.map(|span| { let (limit_start, limit_end) = if span.is_block { - (span.end.col, span.start.col) + (span.start.col, span.end.col) } else { (Column(0), term.cols() - 1) }; @@ -211,11 +208,6 @@ impl<'a, C> RenderableCellsIter<'a, C> { let mut start = term.buffer_to_visible(span.start); let mut end = term.buffer_to_visible(span.end); - // Start and end lines are swapped as we switch from buffer to line coordinates - if start > end { - mem::swap(&mut start, &mut end); - } - // Trim start/end with partially visible block selection start.col = max(limit_start, start.col); end.col = min(limit_end, end.col); @@ -227,8 +219,8 @@ impl<'a, C> RenderableCellsIter<'a, C> { let cursor = &term.cursor.point; let cursor_visible = term.mode.contains(TermMode::SHOW_CURSOR) && grid.contains(cursor); let cursor_key = if cursor_visible { - let is_wide = grid[cursor].flags.contains(cell::Flags::WIDE_CHAR) - && (cursor.col + 1) < grid.num_cols(); + let is_wide = + grid[cursor].flags.contains(Flags::WIDE_CHAR) && (cursor.col + 1) < grid.num_cols(); Some(CursorKey { style: cursor_style, is_wide }) } else { // Use hidden cursor so text will not get inverted @@ -242,7 +234,6 @@ impl<'a, C> RenderableCellsIter<'a, C> { grid, inner, selection: selection_range, - url_highlight: &grid.url_highlight, config, colors: &term.colors, cursor_key, @@ -266,7 +257,7 @@ pub struct RenderableCell { pub fg: Rgb, pub bg: Rgb, pub bg_alpha: f32, - pub flags: cell::Flags, + pub flags: Flags, } impl RenderableCell { @@ -315,27 +306,22 @@ impl RenderableCell { } } - fn compute_fg_rgb<C>( - config: &Config<C>, - colors: &color::List, - fg: Color, - flags: cell::Flags, - ) -> Rgb { + fn compute_fg_rgb<C>(config: &Config<C>, colors: &color::List, fg: Color, flags: Flags) -> Rgb { match fg { Color::Spec(rgb) => rgb, Color::Named(ansi) => { match (config.draw_bold_text_with_bright_colors(), flags & Flags::DIM_BOLD) { // If no bright foreground is set, treat it like the BOLD flag doesn't exist - (_, cell::Flags::DIM_BOLD) + (_, Flags::DIM_BOLD) if ansi == NamedColor::Foreground && config.colors.primary.bright_foreground.is_none() => { colors[NamedColor::DimForeground] }, // Draw bold text in bright colors *and* contains bold flag. - (true, cell::Flags::BOLD) => colors[ansi.to_bright()], + (true, Flags::BOLD) => colors[ansi.to_bright()], // Cell is marked as dim and not bold - (_, cell::Flags::DIM) | (false, cell::Flags::DIM_BOLD) => colors[ansi.to_dim()], + (_, Flags::DIM) | (false, Flags::DIM_BOLD) => colors[ansi.to_dim()], // None of the above, keep original color. _ => colors[ansi], } @@ -346,9 +332,9 @@ impl RenderableCell { flags & Flags::DIM_BOLD, idx, ) { - (true, cell::Flags::BOLD, 0..=7) => idx as usize + 8, - (false, cell::Flags::DIM, 8..=15) => idx as usize - 8, - (false, cell::Flags::DIM, 0..=7) => idx as usize + 260, + (true, Flags::BOLD, 0..=7) => idx as usize + 8, + (false, Flags::DIM, 8..=15) => idx as usize - 8, + (false, Flags::DIM, 0..=7) => idx as usize + 260, _ => idx as usize, }; @@ -429,7 +415,7 @@ impl<'a, C> Iterator for RenderableCellsIter<'a, C> { return Some(cell); } } else { - let mut cell = self.inner.next()?; + let cell = self.inner.next()?; let selected = self .selection @@ -437,13 +423,6 @@ impl<'a, C> Iterator for RenderableCellsIter<'a, C> { .map(|range| range.contains(cell.column, cell.line)) .unwrap_or(false); - // Underline URL highlights - let index = Linear::new(self.grid.num_cols(), cell.column, cell.line); - if self.url_highlight.as_ref().map(|range| range.contains_(index)).unwrap_or(false) - { - cell.inner.flags.insert(Flags::UNDERLINE); - } - if !cell.is_empty() || selected { return Some(RenderableCell::new(self.config, self.colors, cell, selected)); } @@ -471,6 +450,7 @@ pub mod mode { const FOCUS_IN_OUT = 0b000_1000_0000_0000; const ALT_SCREEN = 0b001_0000_0000_0000; const MOUSE_DRAG = 0b010_0000_0000_0000; + const MOUSE_MODE = 0b010_0000_0100_1000; const ALTERNATE_SCROLL = 0b100_0000_0000_0000; const ANY = 0b111_1111_1111_1111; const NONE = 0; @@ -688,6 +668,9 @@ impl VisualBell { } pub struct Term<T> { + /// Terminal focus + pub is_focused: bool, + /// The grid grid: Grid<Cell>, @@ -762,9 +745,6 @@ pub struct Term<T> { /// Proxy for sending events to the event loop event_proxy: T, - /// Terminal focus - pub is_focused: bool, - /// Current title of the window title: String, @@ -810,15 +790,14 @@ impl SizeInfo { Column(((self.width - 2. * self.padding_x) / self.cell_width) as usize) } - pub fn contains_point(&self, x: usize, y: usize, include_padding: bool) -> bool { - if include_padding { - x < self.width as usize && y < self.height as usize - } else { - x < (self.width - self.padding_x) as usize - && x >= self.padding_x as usize - && y < (self.height - self.padding_y) as usize - && y >= self.padding_y as usize - } + /// Check if coordinates are inside the terminal grid. + /// + /// The padding is not counted as part of the grid. + pub fn contains_point(&self, x: usize, y: usize) -> bool { + x < (self.width - self.padding_x) as usize + && x >= self.padding_x as usize + && y < (self.height - self.padding_y) as usize + && y >= self.padding_y as usize } pub fn pixels_to_coords(&self, x: usize, y: usize) -> Point { @@ -848,7 +827,6 @@ impl<T> Term<T> { { self.event_proxy.send_event(Event::MouseCursorDirty); self.grid.scroll_display(scroll); - self.reset_url_highlight(); self.dirty = true; } @@ -923,118 +901,84 @@ impl<T> Term<T> { self.grid.update_history(config.scrolling.history() as usize, &self.cursor.template); } + /// Convert the active selection to a String. pub fn selection_to_string(&self) -> Option<String> { - trait Append { - fn append( - &mut self, - append_newline: bool, - grid: &Grid<Cell>, - tabs: &TabStops, - line: usize, - cols: Range<Column>, - ); - } - - impl Append for String { - fn append( - &mut self, - append_newline: bool, - grid: &Grid<Cell>, - tabs: &TabStops, - mut line: usize, - cols: Range<Column>, - ) { - // Select until last line still within the buffer - line = min(line, grid.len() - 1); - - let grid_line = &grid[line]; - let line_length = grid_line.line_length(); - let line_end = min(line_length, cols.end + 1); - - if cols.start < line_end { - let mut tab_mode = false; - - for col in IndexRange::from(cols.start..line_end) { - let cell = grid_line[col]; - - if tab_mode { - // Skip over whitespace until next tab-stop once a tab was found - if tabs[col] { - tab_mode = false; - } else if cell.c == ' ' { - continue; - } - } - - if !cell.flags.contains(cell::Flags::WIDE_CHAR_SPACER) { - self.push(cell.c); - for c in (&cell.chars()[1..]).iter().filter(|c| **c != ' ') { - self.push(*c); - } - } + let selection = self.grid.selection.clone()?; + let Span { start, end, is_block } = selection.to_span(self)?; - if cell.c == '\t' { - tab_mode = true; - } - } - } + let mut res = String::new(); - if append_newline - || (cols.end >= grid.num_cols() - 1 - && (line_end == Column(0) - || !grid[line][line_end - 1].flags.contains(cell::Flags::WRAPLINE))) - { - self.push('\n'); - } + if is_block { + for line in (end.line + 1..=start.line).rev() { + res += &(self.line_to_string(line, start.col..end.col) + "\n"); } + res += &self.line_to_string(end.line, start.col..end.col); + } else { + res = self.bounds_to_string(start, end); } - let selection = self.grid.selection.clone()?; - let Span { mut start, mut end, is_block } = selection.to_span(self)?; + Some(res) + } + /// Convert range between two points to a String. + pub fn bounds_to_string(&self, start: Point<usize>, end: Point<usize>) -> String { let mut res = String::new(); - if start > end { - ::std::mem::swap(&mut start, &mut end); + for line in (end.line..=start.line).rev() { + let start_col = if line == start.line { start.col } else { Column(0) }; + let end_col = if line == end.line { end.col } else { self.cols() - 1 }; + + res += &self.line_to_string(line, start_col..end_col); } - let line_count = end.line - start.line; + res + } - // Setup block selection start/end point limits - let (limit_start, limit_end) = - if is_block { (end.col, start.col) } else { (Column(0), self.grid.num_cols()) }; + /// Convert a single line in the grid to a String. + fn line_to_string(&self, line: usize, cols: Range<Column>) -> String { + let mut text = String::new(); - match line_count { - // Selection within single line - 0 => { - res.append(false, &self.grid, &self.tabs, start.line, start.col..end.col); - }, + let grid_line = &self.grid[line]; + let line_length = grid_line.line_length(); + let line_end = min(line_length, cols.end + 1); - // Selection ends on line following start - 1 => { - // Ending line - res.append(is_block, &self.grid, &self.tabs, end.line, end.col..limit_end); + let mut tab_mode = false; - // Starting line - res.append(false, &self.grid, &self.tabs, start.line, limit_start..start.col); - }, + for col in IndexRange::from(cols.start..line_end) { + let cell = grid_line[col]; - // Multi line selection - _ => { - // Ending line - res.append(is_block, &self.grid, &self.tabs, end.line, end.col..limit_end); + // Skip over cells until next tab-stop once a tab was found + if tab_mode { + if self.tabs[col] { + tab_mode = false; + } else { + continue; + } + } + + if cell.c == '\t' { + tab_mode = true; + } - let middle_range = (start.line + 1)..(end.line); - for line in middle_range.rev() { - res.append(is_block, &self.grid, &self.tabs, line, limit_start..limit_end); + if !cell.flags.contains(cell::Flags::WIDE_CHAR_SPACER) { + // Push cells primary character + text.push(cell.c); + + // Push zero-width characters + for c in (&cell.chars()[1..]).iter().take_while(|c| **c != ' ') { + text.push(*c); } + } + } - // Starting line - res.append(false, &self.grid, &self.tabs, start.line, limit_start..start.col); - }, + if cols.end >= self.cols() - 1 + && (line_end == Column(0) + || !self.grid[line][line_end - 1].flags.contains(cell::Flags::WRAPLINE)) + { + text.push('\n'); } - Some(res) + text } pub fn visible_to_buffer(&self, point: Point) -> Point<usize> { @@ -1097,7 +1041,6 @@ impl<T> Term<T> { self.grid.selection = None; self.alt_grid.selection = None; - self.grid.url_highlight = None; // Should not allow less than 1 col, causes all sorts of checks to be required. if num_cols <= Column(1) { @@ -1233,92 +1176,9 @@ impl<T> Term<T> { self.event_proxy.send_event(Event::Exit); } - #[inline] - pub fn set_url_highlight(&mut self, hl: RangeInclusive<index::Linear>) { - self.grid.url_highlight = Some(hl); - self.dirty = true; - } - - #[inline] - pub fn reset_url_highlight(&mut self) { - self.grid.url_highlight = None; - self.dirty = true; - } - pub fn clipboard(&mut self) -> &mut Clipboard { &mut self.clipboard } - - pub fn urls(&self) -> Vec<Url> { - let display_offset = self.grid.display_offset(); - let num_cols = self.grid.num_cols().0; - let last_col = Column(num_cols - 1); - let last_line = self.grid.num_lines() - 1; - - let grid_end_point = Point::new(0, last_col); - let mut iter = self.grid.iter_from(grid_end_point); - - let mut parser = Parser::new(); - let mut extra_url_len = 0; - let mut urls = Vec::new(); - - let mut c = Some(iter.cell()); - while let Some(cell) = c { - let point = iter.point(); - c = iter.prev(); - - // Skip double-width cell but extend URL length - if cell.flags.contains(cell::Flags::WIDE_CHAR_SPACER) { - extra_url_len += 1; - continue; - } - - // Interrupt URLs on line break - if point.col == last_col && !cell.flags.contains(cell::Flags::WRAPLINE) { - extra_url_len = 0; - parser.reset(); - } - - // Advance parser - match parser.advance(cell.c) { - ParserState::Url(length) => { - urls.push(Url::new(point, length + extra_url_len, num_cols)) - }, - ParserState::NoUrl => { - extra_url_len = 0; - - // Stop searching for URLs once the viewport has been left without active URL - if point.line > last_line.0 + display_offset { - break; - } - }, - _ => (), - } - } - - urls - } - - pub fn url_to_string(&self, url: Url) -> String { - let mut url_text = String::new(); - - let mut iter = self.grid.iter_from(url.start); - - let mut c = Some(iter.cell()); - while let Some(cell) = c { - if !cell.flags.contains(cell::Flags::WIDE_CHAR_SPACER) { - url_text.push(cell.c); - } - - if iter.point() == url.end { - break; - } - - c = iter.next(); - } - - url_text - } } impl<T> TermInfo for Term<T> { @@ -1387,7 +1247,7 @@ impl<T: EventListener> ansi::Handler for Term<T> { let location = Point { line: self.cursor.point.line, col: self.cursor.point.col }; let cell = &mut self.grid[&location]; - cell.flags.insert(cell::Flags::WRAPLINE); + cell.flags.insert(Flags::WRAPLINE); } if (self.cursor.point.line + 1) >= self.scroll_region.end { @@ -1422,7 +1282,7 @@ impl<T: EventListener> ansi::Handler for Term<T> { if width == 0 { let mut col = self.cursor.point.col.0.saturating_sub(1); let line = self.cursor.point.line; - if self.grid[line][Column(col)].flags.contains(cell::Flags::WIDE_CHAR_SPACER) { + if self.grid[line][Column(col)].flags.contains(Flags::WIDE_CHAR_SPACER) { col = col.saturating_sub(1); } self.grid[line][Column(col)].push_extra(c); @@ -1435,13 +1295,13 @@ impl<T: EventListener> ansi::Handler for Term<T> { // Handle wide chars if width == 2 { - cell.flags.insert(cell::Flags::WIDE_CHAR); + cell.flags.insert(Flags::WIDE_CHAR); if self.cursor.point.col + 1 < num_cols { self.cursor.point.col += 1; let spacer = &mut self.grid[&self.cursor.point]; *spacer = self.cursor.template; - spacer.flags.insert(cell::Flags::WIDE_CHAR_SPACER); + spacer.flags.insert(Flags::WIDE_CHAR_SPACER); } } } @@ -1702,7 +1562,7 @@ impl<T: EventListener> ansi::Handler for Term<T> { #[inline] fn insert_blank_lines(&mut self, lines: Line) { trace!("Inserting blank {} lines", lines); - if self.scroll_region.contains_(self.cursor.point.line) { + if self.scroll_region.contains(&self.cursor.point.line) { let origin = self.cursor.point.line; self.scroll_down_relative(origin, lines); } @@ -1711,7 +1571,7 @@ impl<T: EventListener> ansi::Handler for Term<T> { #[inline] fn delete_lines(&mut self, lines: Line) { trace!("Deleting {} lines", lines); - if self.scroll_region.contains_(self.cursor.point.line) { + if self.scroll_region.contains(&self.cursor.point.line) { let origin = self.cursor.point.line; self.scroll_up_relative(origin, lines); } @@ -1868,9 +1728,8 @@ impl<T: EventListener> ansi::Handler for Term<T> { let mut template = self.cursor.template; template.flags ^= template.flags; - // Remove active selections and URL highlights + // Remove active selections self.grid.selection = None; - self.grid.url_highlight = None; match mode { ansi::ClearMode::Below => { @@ -1959,24 +1818,22 @@ impl<T: EventListener> ansi::Handler for Term<T> { Attr::Reset => { self.cursor.template.fg = Color::Named(NamedColor::Foreground); self.cursor.template.bg = Color::Named(NamedColor::Background); - self.cursor.template.flags = cell::Flags::empty(); - }, - Attr::Reverse => self.cursor.template.flags.insert(cell::Flags::INVERSE), - Attr::CancelReverse => self.cursor.template.flags.remove(cell::Flags::INVERSE), - Attr::Bold => self.cursor.template.flags.insert(cell::Flags::BOLD), - Attr::CancelBold => self.cursor.template.flags.remove(cell::Flags::BOLD), - Attr::Dim => self.cursor.template.flags.insert(cell::Flags::DIM), - Attr::CancelBoldDim => { - self.cursor.template.flags.remove(cell::Flags::BOLD | cell::Flags::DIM) + self.cursor.template.flags = Flags::empty(); }, - Attr::Italic => self.cursor.template.flags.insert(cell::Flags::ITALIC), - Attr::CancelItalic => self.cursor.template.flags.remove(cell::Flags::ITALIC), - Attr::Underscore => self.cursor.template.flags.insert(cell::Flags::UNDERLINE), - Attr::CancelUnderline => self.cursor.template.flags.remove(cell::Flags::UNDERLINE), - Attr::Hidden => self.cursor.template.flags.insert(cell::Flags::HIDDEN), - Attr::CancelHidden => self.cursor.template.flags.remove(cell::Flags::HIDDEN), - Attr::Strike => self.cursor.template.flags.insert(cell::Flags::STRIKEOUT), - Attr::CancelStrike => self.cursor.template.flags.remove(cell::Flags::STRIKEOUT), + Attr::Reverse => self.cursor.template.flags.insert(Flags::INVERSE), + Attr::CancelReverse => self.cursor.template.flags.remove(Flags::INVERSE), + Attr::Bold => self.cursor.template.flags.insert(Flags::BOLD), + Attr::CancelBold => self.cursor.template.flags.remove(Flags::BOLD), + Attr::Dim => self.cursor.template.flags.insert(Flags::DIM), + Attr::CancelBoldDim => self.cursor.template.flags.remove(Flags::BOLD | Flags::DIM), + Attr::Italic => self.cursor.template.flags.insert(Flags::ITALIC), + Attr::CancelItalic => self.cursor.template.flags.remove(Flags::ITALIC), + Attr::Underline => self.cursor.template.flags.insert(Flags::UNDERLINE), + Attr::CancelUnderline => self.cursor.template.flags.remove(Flags::UNDERLINE), + Attr::Hidden => self.cursor.template.flags.insert(Flags::HIDDEN), + Attr::CancelHidden => self.cursor.template.flags.remove(Flags::HIDDEN), + Attr::Strike => self.cursor.template.flags.insert(Flags::STRIKEOUT), + Attr::CancelStrike => self.cursor.template.flags.remove(Flags::STRIKEOUT), _ => { debug!("Term got unhandled attr: {:?}", attr); }, @@ -2189,7 +2046,8 @@ mod tests { use crate::grid::{Grid, Scroll}; use crate::index::{Column, Line, Point, Side}; use crate::selection::Selection; - use crate::term::{cell, Cell, SizeInfo, Term}; + use crate::term::cell::{Cell, Flags}; + use crate::term::{SizeInfo, Term}; struct Mock; impl EventListener for Mock { @@ -2217,7 +2075,7 @@ mod tests { grid[Line(0)][Column(0)].c = '"'; grid[Line(0)][Column(3)].c = '"'; grid[Line(1)][Column(2)].c = '"'; - grid[Line(0)][Column(4)].flags.insert(cell::Flags::WRAPLINE); + grid[Line(0)][Column(4)].flags.insert(Flags::WRAPLINE); let mut escape_chars = String::from("\""); |