diff options
author | Joe Wilm <joe@jwilm.com> | 2016-11-28 14:13:11 -0800 |
---|---|---|
committer | Joe Wilm <joe@jwilm.com> | 2016-12-11 20:23:41 -0800 |
commit | 30bee80a6902eb09c51bed9c9f54c7617c4d53db (patch) | |
tree | 187853fdf17d3382595be8a416e604c6f2bea0f0 | |
parent | 941818d88ebc1f0d90ef9b1ef7d1313174afb36b (diff) |
Refactor cell selection out of renderer
The terminal now has a `renderable_cells()` function that returns a
`RenderableCellIter` iterator. This allows reuse of the cell selection
code by multiple renderers, makes it testable, and makes it
independently optimizable.
The render API now takes an `Iterator<Item=IndexedCell>` to support both
the new renderable cells iterator and the `render_string()` method which
generates its own iterator.
The `vim_large_window_scoll` ref test was added here because it provides
a nice large and busy grid to benchmark the cell selection with.
-rw-r--r-- | src/lib.rs | 2 | ||||
-rw-r--r-- | src/main.rs | 4 | ||||
-rw-r--r-- | src/meter.rs | 2 | ||||
-rw-r--r-- | src/renderer/mod.rs | 144 | ||||
-rw-r--r-- | src/term.rs | 207 | ||||
-rw-r--r-- | tests/ref.rs | 3 | ||||
-rw-r--r-- | tests/ref/vim_large_window_scroll/alacritty.recording | 295 | ||||
-rw-r--r-- | tests/ref/vim_large_window_scroll/grid.json | 1 | ||||
-rw-r--r-- | tests/ref/vim_large_window_scroll/size.json | 1 |
9 files changed, 546 insertions, 113 deletions
@@ -19,8 +19,8 @@ #![feature(drop_types_in_const)] #![feature(unicode)] #![feature(step_trait)] +#![cfg_attr(test, feature(test))] #![feature(core_intrinsics)] -#![feature(test)] #![allow(stable_features)] // lying about question_mark because 1.14.0 isn't released! #![feature(proc_macro)] diff --git a/src/main.rs b/src/main.rs index fbf1eaca..aaa20953 100644 --- a/src/main.rs +++ b/src/main.rs @@ -361,8 +361,10 @@ impl Display { let size_info = terminal.size_info().clone(); self.renderer.with_api(&size_info, |mut api| { + api.clear(); + // Draw the grid - api.render_grid(&terminal.render_grid(), glyph_cache); + api.render_grid(terminal.renderable_cells(), glyph_cache); }); } diff --git a/src/meter.rs b/src/meter.rs index b73c1004..b4bab5ff 100644 --- a/src/meter.rs +++ b/src/meter.rs @@ -33,7 +33,7 @@ use std::time::{Instant, Duration}; -const NUM_SAMPLES: usize = 60; +const NUM_SAMPLES: usize = 10; /// The meter pub struct Meter { diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index dfaf6b30..2868a226 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -24,10 +24,10 @@ use font::{self, Rasterizer, RasterizedGlyph, FontDesc, GlyphKey, FontKey}; use gl::types::*; use gl; use notify::{Watcher as WatcherApi, RecommendedWatcher as Watcher, op}; +use index::{Line, Column}; use config::Config; -use grid::Grid; -use term::{self, cell, Cell}; +use term::{self, cell, IndexedCell, Cell}; use super::Rgb; @@ -286,7 +286,7 @@ impl Batch { } } - pub fn add_item(&mut self, row: f32, col: f32, cell: &Cell, glyph: &Glyph) { + pub fn add_item(&mut self, cell: &IndexedCell, glyph: &Glyph) { if self.is_empty() { self.tex = glyph.tex_id; } @@ -310,9 +310,9 @@ impl Batch { ::term::cell::Color::Ansi(ansi) => self.colors[ansi as usize], }; - let mut instance = InstanceData { - col: col, - row: row, + self.instances.push(InstanceData { + col: cell.column.0 as f32, + row: cell.line.0 as f32, top: glyph.top, left: glyph.left, @@ -331,19 +331,7 @@ impl Batch { bg_r: bg.r as f32, bg_g: bg.g as f32, bg_b: bg.b as f32, - }; - - if cell.flags.contains(cell::INVERSE) { - instance.r = bg.r as f32; - instance.g = bg.g as f32; - instance.b = bg.b as f32; - - instance.bg_r = fg.r as f32; - instance.bg_g = fg.g as f32; - instance.bg_b = fg.b as f32; - } - - self.instances.push(instance); + }); } #[inline] @@ -631,6 +619,19 @@ impl QuadRenderer { } impl<'a> RenderApi<'a> { + pub fn clear(&self) { + let color = self.colors[::ansi::Color::Background as usize]; + unsafe { + gl::ClearColor( + color.r as f32 / 255.0, + color.g as f32 / 255.0, + color.b as f32 / 255.0, + 1.0 + ); + gl::Clear(gl::COLOR_BUFFER_BIT); + } + } + fn render_batch(&mut self) { unsafe { gl::BufferSubData(gl::ARRAY_BUFFER, 0, self.batch.size() as isize, @@ -663,36 +664,32 @@ impl<'a> RenderApi<'a> { /// optimization. pub fn render_string( &mut self, - s: &str, + string: &str, glyph_cache: &mut GlyphCache, color: &::term::cell::Color, ) { - let row = 40.0; - let mut col = 100.0; - - for c in s.chars() { - let glyph_key = GlyphKey { - font_key: glyph_cache.font_key, - size: glyph_cache.font_size, - c: c - }; - - if let Some(glyph) = glyph_cache.get(&glyph_key, self) { - let cell = Cell { + let line = Line(23); + let col = Column(0); + + let cells = string.chars() + .enumerate() + .map(|(i, c)| IndexedCell { + line: line, + column: col + i, + inner: Cell { c: c, - fg: color.clone(), - bg: cell::Color::Rgb(Rgb { r: 0, g: 0, b: 0}), - flags: cell::INVERSE, - }; - self.add_render_item(row, col, &cell, glyph); - } + bg: *color, + fg: cell::Color::Rgb(Rgb { r: 0, g: 0, b: 0}), + flags: cell::Flags::empty(), + } + }) + .collect::<Vec<_>>(); - col += 1.0; - } + self.render_grid(cells.into_iter(), glyph_cache); } #[inline] - fn add_render_item(&mut self, row: f32, col: f32, cell: &Cell, glyph: &Glyph) { + fn add_render_item(&mut self, cell: &IndexedCell, glyph: &Glyph) { // Flush batch if tex changing if !self.batch.is_empty() { if self.batch.tex != glyph.tex_id { @@ -700,7 +697,7 @@ impl<'a> RenderApi<'a> { } } - self.batch.add_item(row, col, cell, glyph); + self.batch.add_item(cell, glyph); // Render batch and clear if it's full if self.batch.full() { @@ -708,51 +705,32 @@ impl<'a> RenderApi<'a> { } } - pub fn render_grid( + pub fn render_grid<I>( &mut self, - grid: &Grid<Cell>, + occupied_cells: I, glyph_cache: &mut GlyphCache - ) { - // TODO should be built into renderer - let color = self.colors[::ansi::Color::Background as usize]; - unsafe { - gl::ClearColor( - color.r as f32 / 255.0, - color.g as f32 / 255.0, - color.b as f32 / 255.0, - 1.0 - ); - gl::Clear(gl::COLOR_BUFFER_BIT); - } - - for (i, line) in grid.lines().enumerate() { - for (j, cell) in line.cells().enumerate() { - // Skip empty cells - if cell.c == ' ' && cell.bg == cell::Color::Ansi(::ansi::Color::Background) && - !cell.flags.contains(cell::INVERSE) - { - continue; - } - - // Get font key for cell - // FIXME this is super inefficient. - let mut font_key = glyph_cache.font_key; - if cell.flags.contains(cell::BOLD) { - font_key = glyph_cache.bold_key; - } else if cell.flags.contains(cell::ITALIC) { - font_key = glyph_cache.italic_key; - } + ) + where I: Iterator<Item=::term::IndexedCell> + { + for cell in occupied_cells { + // Get font key for cell + // FIXME this is super inefficient. + let mut font_key = glyph_cache.font_key; + if cell.flags.contains(cell::BOLD) { + font_key = glyph_cache.bold_key; + } else if cell.flags.contains(cell::ITALIC) { + font_key = glyph_cache.italic_key; + } - let glyph_key = GlyphKey { - font_key: font_key, - size: glyph_cache.font_size, - c: cell.c - }; + let glyph_key = GlyphKey { + font_key: font_key, + size: glyph_cache.font_size, + c: cell.c + }; - // Add cell to batch if glyph available - if let Some(glyph) = glyph_cache.get(&glyph_key, self) { - self.add_render_item(i as f32, j as f32, cell, glyph); - } + // Add cell to batch if glyph available + if let Some(glyph) = glyph_cache.get(&glyph_key, self) { + self.add_render_item(&cell, glyph); } } } diff --git a/src/term.rs b/src/term.rs index 430a68ea..a877d260 100644 --- a/src/term.rs +++ b/src/term.rs @@ -13,7 +13,6 @@ // limitations under the License. // //! Exports the `Term` type which is a high-level API for the Grid -use std::mem; use std::ops::{Deref, Range}; use std::ptr; use std::cmp; @@ -23,45 +22,129 @@ use grid::{Grid, ClearRegion}; use index::{Cursor, Column, Line}; use ansi::Color; -/// RAII type which manages grid state for render +/// Iterator that yields cells needing render +/// +/// Yields cells that require work to be displayed (that is, not a an empty +/// background cell). Additionally, this manages some state of the grid only +/// relevant for rendering like temporarily changing the cell with the cursor. /// /// This manages the cursor during a render. The cursor location is inverted to /// draw it, and reverted after drawing to maintain state. -pub struct RenderGrid<'a> { - inner: &'a mut Grid<Cell>, +pub struct RenderableCellsIter<'a> { + grid: &'a mut Grid<Cell>, cursor: &'a Cursor, mode: TermMode, + line: Line, + column: Column, } -impl<'a> RenderGrid<'a> { - fn new<'b>(grid: &'b mut Grid<Cell>, cursor: &'b Cursor, mode: TermMode) -> RenderGrid<'b> { - if mode.contains(mode::SHOW_CURSOR) && grid.contains(cursor) { - let cell = &mut grid[cursor]; - mem::swap(&mut cell.fg, &mut cell.bg); - } - RenderGrid { - inner: grid, +impl<'a> RenderableCellsIter<'a> { + /// Create the renderable cells iterator + /// + /// The cursor and terminal mode are required for properly displaying the + /// cursor. + fn new<'b>( + grid: &'b mut Grid<Cell>, + cursor: &'b Cursor, + mode: TermMode + ) -> RenderableCellsIter<'b> { + RenderableCellsIter { + grid: grid, cursor: cursor, mode: mode, + line: Line(0), + column: Column(0), + }.initialize() + } + + fn initialize(self) -> Self { + if self.cursor_is_visible() { + self.grid[self.cursor].swap_fg_and_bg(); } + + self + } + + /// Check if the cursor should be rendered. + #[inline] + fn cursor_is_visible(&self) -> bool { + self.mode.contains(mode::SHOW_CURSOR) && self.grid.contains(self.cursor) } } -impl<'a> Drop for RenderGrid<'a> { +impl<'a> Drop for RenderableCellsIter<'a> { + /// Resets temporary render state on the grid fn drop(&mut self) { - if self.mode.contains(mode::SHOW_CURSOR) && self.inner.contains(self.cursor) { - let cell = &mut self.inner[self.cursor]; - mem::swap(&mut cell.fg, &mut cell.bg); + if self.cursor_is_visible() { + self.grid[self.cursor].swap_fg_and_bg(); } } } -impl<'a> Deref for RenderGrid<'a> { - type Target = Grid<Cell>; +pub struct IndexedCell { + pub line: Line, + pub column: Column, + pub inner: Cell +} - fn deref(&self) -> &Self::Target { - self.inner +impl Deref for IndexedCell { + type Target = Cell; + + #[inline(always)] + fn deref(&self) -> &Cell { + &self.inner + } +} + +impl<'a> Iterator for RenderableCellsIter<'a> { + type Item = IndexedCell; + + /// Gets the next renderable cell + /// + /// Skips empty (background) cells and applies any flags to the cell state + /// (eg. invert fg and bg colors). + #[inline(always)] + fn next(&mut self) -> Option<Self::Item> { + while self.line < self.grid.num_lines() { + while self.column < self.grid.num_cols() { + // Grab current state for this iteration + let line = self.line; + let column = self.column; + let cell = &self.grid[line][column]; + + // Update state for next iteration + self.column += 1; + + // Skip empty cells + if cell.is_empty() { + continue; + } + + // fg, bg are dependent on INVERSE flag + let (fg, bg) = if cell.flags.contains(cell::INVERSE) { + (&cell.bg, &cell.fg) + } else { + (&cell.fg, &cell.bg) + }; + + return Some(IndexedCell { + line: line, + column: column, + inner: Cell { + flags: cell.flags, + c: cell.c, + fg: *fg, + bg: *bg, + } + }) + } + + self.column = Column(0); + self.line += 1; + } + + None } } @@ -72,6 +155,9 @@ fn limit<T: PartialOrd + Ord>(val: T, min: T, max: T) -> T { } pub mod cell { + use std::mem; + + use ansi; use ::Rgb; bitflags! { @@ -84,10 +170,10 @@ pub mod cell { } } - #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] + #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub enum Color { Rgb(Rgb), - Ansi(::ansi::Color), + Ansi(ansi::Color), } #[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)] @@ -113,10 +199,22 @@ pub mod cell { } #[inline] + pub fn is_empty(&self) -> bool { + self.c == ' ' && + self.bg == Color::Ansi(ansi::Color::Background) && + !self.flags.contains(INVERSE) + } + + #[inline] pub fn reset(&mut self, template: &Cell) { // memcpy template to self *self = template.clone(); } + + #[inline] + pub fn swap_fg_and_bg(&mut self) { + mem::swap(&mut self.fg, &mut self.bg); + } } #[cfg(test)] @@ -256,8 +354,6 @@ impl Term { let num_cols = size.cols(); let num_lines = size.lines(); - println!("num_cols, num_lines = {}, {}", num_cols, num_lines); - let grid = Grid::new(num_lines, num_cols, &template); let mut tabs = (Column(0)..grid.num_cols()) @@ -307,8 +403,8 @@ impl Term { &self.grid } - pub fn render_grid<'a>(&'a mut self) -> RenderGrid<'a> { - RenderGrid::new(&mut self.grid, &self.cursor, self.mode) + pub fn renderable_cells<'a>(&'a mut self) -> RenderableCellsIter<'a> { + RenderableCellsIter::new(&mut self.grid, &self.cursor, self.mode) } /// Resize terminal to new dimensions @@ -910,3 +1006,62 @@ mod tests { assert_eq!(limit(5, 1, 4), 4); } } + +#[cfg(test)] +mod bench { + extern crate test; + extern crate serde_json as json; + + use std::io::Read; + use std::fs::File; + use std::mem; + use std::path::Path; + + use grid::Grid; + + use super::{SizeInfo, Term}; + use super::cell::Cell; + + fn read_string<P>(path: P) -> String + where P: AsRef<Path> + { + let mut res = String::new(); + File::open(path.as_ref()).unwrap() + .read_to_string(&mut res).unwrap(); + + res + } + + /// Benchmark for the renderable cells iterator + /// + /// The renderable cells iterator yields cells that require work to be displayed (that is, not a + /// an empty background cell). This benchmark measures how long it takes to process the whole + /// iterator. + /// + /// When this benchmark was first added, it averaged ~78usec on my macbook pro. The total + /// render time for this grid is anywhere between ~1500 and ~2000usec (measured imprecisely with + /// the visual meter). + #[bench] + fn render_iter(b: &mut test::Bencher) { + // Need some realistic grid state; using one of the ref files. + let serialized_grid = read_string( + concat!(env!("CARGO_MANIFEST_DIR"), "/tests/ref/vim_large_window_scroll/grid.json") + ); + let serialized_size = read_string( + concat!(env!("CARGO_MANIFEST_DIR"), "/tests/ref/vim_large_window_scroll/size.json") + ); + + let mut grid: Grid<Cell> = json::from_str(&serialized_grid).unwrap(); + let size: SizeInfo = json::from_str(&serialized_size).unwrap(); + + let mut terminal = Term::new(size); + mem::swap(&mut terminal.grid, &mut grid); + + b.iter(|| { + let iter = terminal.renderable_cells(); + for cell in iter { + test::black_box(cell); + } + }) + } +} diff --git a/tests/ref.rs b/tests/ref.rs index 349b565e..a92da05f 100644 --- a/tests/ref.rs +++ b/tests/ref.rs @@ -78,6 +78,7 @@ mod reference { ll, vim_simple_edit, tmux_htop, - tmux_git_log + tmux_git_log, + vim_large_window_scroll } } diff --git a/tests/ref/vim_large_window_scroll/alacritty.recording b/tests/ref/vim_large_window_scroll/alacritty.recording new file mode 100644 index 00000000..a0569538 --- /dev/null +++ b/tests/ref/vim_large_window_scroll/alacritty.recording @@ -0,0 +1,295 @@ +[1m[7m%[27m[1m[0m
[0m[27m[24m[Jjwilm@kurast.local [01;32m➜ [01;32m [36m~/code/alacritty [00m [K[?1h=[?2004h[1m[31mv[0m[39m[0m[35mv[35mi[39m[35mv[35mi[35mm[39m[?1l>[?2004l
+[?1049h[?1h=[2;1H▽[6n[2;1H [1;1H[1;57r[?12;25h[?12l[?25h[27m[m[38;2;234;234;234m[48;2;0;0;0m[H[2J[>c[?25l[1;1H[38;2;66;66;66m 1 [m[38;2;234;234;234m[48;2;0;0;0m[80C[48;2;42;42;42m [m[38;2;234;234;234m[48;2;0;0;0m
+[38;2;66;66;66m~ [3;1H~ [4;1H~ [5;1H~ [6;1H~ [7;1H~ [8;1H~ [9;1H~ [10;1H~ [11;1H~ [12;1H~ [13;1H~ [14;1H~ [15;1H~ [16;1H~ [17;1H~ [18;1H~ [19;1H~ [20;1H~ [21;1H~ [22;1H~ [23;1H~ [24;1H~ [25;1H~ [26;1H~ [27;1H~ [28;1H~ [29;1H~ [30;1H~ [31;1H~ [32;1H~ [33;1H~ [34;1H~ [35;1H~ [36;1H~ [37;1H~ [38;1H~ [39;1H~ [40;1H~ [41;1H~ [42;1H~ [43;1H~ [44;1H~ [45;1H~ [46;1H~ [47;1H~ [48;1H~ [49;1H~ [50;1H~ [51;1H~ [52;1H~ [53;1H~ [54;1H~ [55;1H~ [56;1H~ [m[38;2;234;234;234m[48;2;0;0;0m[57;165H0,0-1[9CAll[23;83HVIM - Vi IMproved[25;84Hversion 7.4.1832[26;80Hby Bram Moolenaar et al.[27;70HVim is open source and freely distributable[29;77HHelp poor children in Uganda![30;69Htype :help iccf[38;2;66;66;66m<Enter>[m[38;2;234;234;234m[48;2;0;0;0m for information [32;69Htype :q[38;2;66;66;66m<Enter>[m[38;2;234;234;234m[48;2;0;0;0m to exit [33;69Htype :help[38;2;66;66;66m<Enter>[m[38;2;234;234;234m[48;2;0;0;0m or [38;2;66;66;66m<F1>[m[38;2;234;234;234m[48;2;0;0;0m for on-line help[34;69Htype :help version7[38;2;66;66;66m<Enter>[m[38;2;234;234;234m[48;2;0;0;0m for version info[1;5H[?12l[?25h[?25l[57;165H[K[57;1H:CommandT
[23;83H[38;2;66;66;66m [25;84H [26;80H [27;70H [29;77H [30;69H [32;69H [33;69H [34;69H [m[38;2;234;234;234m[48;2;0;0;0m[54;1H[7m[38;2;77;80;87m[48;2;234;234;234m[No Name] [Git(mouse-reporting)] 0,0-1 All[m[38;2;234;234;234m[48;2;0;0;0m[55;1H[K[56;1H[7m[38;2;77;80;87m[48;2;236;206;88mCommand-T [Files] [-][Git(mouse-reporting)] 0,0-1 All[m[38;2;234;234;234m[48;2;0;0;0m[57;1H[38;2;118;120;118m>>[m[38;2;234;234;234m[48;2;0;0;0m[57;3H[K[57;4H[4m[38;2;106;90;205m [m[38;2;234;234;234m[48;2;0;0;0m[57;4H[K[57;4H[4m[38;2;106;90;205m [m[38;2;234;234;234m[48;2;0;0;0m[40;1H[7m[38;2;77;80;87m[48;2;234;234;234m[No Name] [Git(mouse-reporting)] 0,0-1 All[m[38;2;234;234;234m[48;2;0;0;0m[41;1H copypasta/Cargo.lock[41;23H[K[42;1H build.rs[42;11H[K[43;1H alacritty.yml[43;16H[K[44;1H alacritty.recording[44;22H[K[45;1H alacritty-pre-eloop[45;22H[K[46;1H TASKS.md[46;11H[K[47;1H README.md[47;12H[K[48;1H Makefile[48;11H[K[49;1H LICENSE-APACHE[49;17H[K[50;1H Cargo.toml[50;13H[K[51;1H Cargo.lock[51;13H[K[52;1H Alacritty.app/Contents/Resources/Alacritty.icns[52;50H[K[53;1H Alacritty.app/Contents/PkgInfo[53;33H[K[54;1H Alacritty.app/Contents/MacOS/Alacritty[54;41H[K[55;1H[7m[38;2;234;234;234m[48;2;66;66;66m> Alacritty.app/Contents/Info.plist[m[38;2;234;234;234m[48;2;0;0;0m[7m[38;2;234;234;234m[48;2;223;101;102m [m[38;2;234;234;234m[48;2;0;0;0m[56;165H[7m[38;2;77;80;87m[48;2;236;206;88m15,1 [m[38;2;234;234;234m[48;2;0;0;0m[57;4H[K[57;4H[4m[38;2;106;90;205m [55;1H[?12l[?25h[?25l[m[38;2;234;234;234m[48;2;0;0;0m[57;4H[K[57;4Hl[4m[38;2;106;90;205m [m[38;2;234;234;234m[48;2;0;0;0m[41;3Hfont/src/ft/[1m[4ml[m[38;2;234;234;234m[48;2;0;0;0mist_fonts.rs
+ copypasta/[1m[4mL[m[38;2;234;234;234m[48;2;0;0;0mICENSE-APACHE
+ tests/ref/[1m[4ml[m[38;2;234;234;234m[48;2;0;0;0ml/size.json
+ tests/ref/[1m[4ml[m[38;2;234;234;234m[48;2;0;0;0ml/grid.json
+ copypasta/src/[1m[4ml[m[38;2;234;234;234m[48;2;0;0;0mib.rs
+ font/src/[1m[4ml[m[38;2;234;234;234m[48;2;0;0;0mib.rs
+ src/[1m[4ml[m[38;2;234;234;234m[48;2;0;0;0mib.rs
+ A[1m[4ml[m[38;2;234;234;234m[48;2;0;0;0macritty.app/Contents/Resources/Alacritty.icns
+ A[1m[4ml[m[38;2;234;234;234m[48;2;0;0;0macritty.app/Contents/MacOS/Alacritty
+ A[1m[4ml[m[38;2;234;234;234m[48;2;0;0;0macritty.app/Contents/Info.plist
+ A[1m[4ml[m[38;2;234;234;234m[48;2;0;0;0macritty.app/Contents/PkgInfo
+ a[1m[4ml[m[38;2;234;234;234m[48;2;0;0;0m[8Crecording[52;22H[K[53;3Ha[1m[4ml[m[38;2;234;234;234m[48;2;0;0;0m[7C-pre-eloop[53;22H[K[54;3H[1m[4mL[m[38;2;234;234;234m[48;2;0;0;0mICENSE-APACHE[54;17H[K[55;3H[7m[38;2;234;234;234m[48;2;66;66;66ma[9Cyml[m[38;2;234;234;234m[48;2;0;0;0m[7m[38;2;234;234;234m[48;2;223;101;102m [m[38;2;234;234;234m[48;2;0;0;0m[57;4H[K[57;4Hl[4m[38;2;106;90;205m [55;1H[?12l[?25h[?25l[m[38;2;234;234;234m[48;2;0;0;0m[57;4H[K[57;4Hli[4m[38;2;106;90;205m [m[38;2;234;234;234m[48;2;0;0;0m[41;3Htests/ref/tmux_git_[1m[4ml[m[38;2;234;234;234m[48;2;0;0;0mog/gr[1m[4mi[m[38;2;234;234;234m[48;2;0;0;0md.json
+ tests/ref/tmux_git_[1m[4ml[m[38;2;234;234;234m[48;2;0;0;0mog/s[1m[4mi[m[38;2;234;234;234m[48;2;0;0;0mze.json
+ A[1m[4ml[m[38;2;234;234;234m[48;2;0;0;0macr[1m[4mi[m[38;2;234;234;234m[48;2;0;0;0mtty.app/Contents/Info.plist
+ macos/Info.p[1m[4mli[m[38;2;234;234;234m[48;2;0;0;0mst[44;19H[K[45;3Htmux-c[1m[4mli[m[38;2;234;234;234m[48;2;0;0;0ment-23038.log
+ a[1m[4ml[m[38;2;234;234;234m[48;2;0;0;0macr[1m[4mi[m[38;2;234;234;234m[48;2;0;0;0mtty.recording
+ a[1m[4ml[m[38;2;234;234;234m[48;2;0;0;0macr[1m[4mi[m[38;2;234;234;234m[48;2;0;0;0mtty-pre-eloop
+ a[4C[1m[4mi[m[38;2;234;234;234m[48;2;0;0;0mtty.yml[48;16H[K[49;8H[1m[4mi[m[38;2;234;234;234m[48;2;0;0;0m[17CPkgInfo[49;33H[K[50;3Hfont/src/ft/[1m[4mli[m[38;2;234;234;234m[48;2;0;0;0mst_fonts.rs[50;28H[K[51;3Hcopypasta/[1m[4mLI[m[38;2;234;234;234m[48;2;0;0;0mCENSE-APACHE[51;27H[K[52;3Hcopypasta/src/[1m[4mli[m[38;2;234;234;234m[48;2;0;0;0mb.rs
+ font/src/[1m[4mli[m[38;2;234;234;234m[48;2;0;0;0mb.rs[53;18H[K[54;3Hsrc/[1m[4mli[m[38;2;234;234;234m[48;2;0;0;0mb.rs[54;13H[K[55;3H[7m[38;2;234;234;234m[48;2;66;66;66mLICENSE-APACHE[m[38;2;234;234;234m[48;2;0;0;0m[57;4H[K[57;4Hli[4m[38;2;106;90;205m [55;1H[?12l[?25h[?25l[m[38;2;234;234;234m[48;2;0;0;0m[57;4H[K[57;4Hlib[4m[38;2;106;90;205m [m[38;2;234;234;234m[48;2;0;0;0m[40;1H[38;2;66;66;66m~ [41;1H~ [42;1H~ [43;1H~ [44;1H~ [45;1H~ [46;1H~ [47;1H~ [48;1H~ [49;1H~ [50;1H~ [51;1H~ [m[38;2;234;234;234m[48;2;0;0;0m[52;1H[7m[38;2;77;80;87m[48;2;234;234;234m[No Name] [Git(mouse-reporting)] 0,0-1 All[m[38;2;234;234;234m[48;2;0;0;0m[53;3Hcopypasta/src/[1m[4mlib[m[38;2;234;234;234m[48;2;0;0;0m.rs
+ font/src/[1m[4mlib[m[38;2;234;234;234m[48;2;0;0;0m.rs[55;3H[7m[38;2;234;234;234m[48;2;66;66;66msrc/lib.rs[m[38;2;234;234;234m[48;2;0;0;0m[7m[38;2;234;234;234m[48;2;223;101;102m [m[38;2;234;234;234m[48;2;0;0;0m[56;165H[7m[38;2;77;80;87m[48;2;236;206;88m3,1 [m[38;2;234;234;234m[48;2;0;0;0m[57;4H[K[57;4Hlib[4m[38;2;106;90;205m [55;1H[?12l[?25h[?25l[m[38;2;234;234;234m[48;2;0;0;0m[52;1H[38;2;66;66;66m~ [53;1H~ [m[38;2;234;234;234m[48;2;0;0;0m[54;1H[7m[38;2;77;80;87m[48;2;234;234;234m[No Name] [Git(mouse-reporting)] |