1
0
Fork 0
mirror of https://github.com/alacritty/alacritty.git synced 2024-11-25 14:05:41 -05:00

Fixing tabs in copy-paste

This resolves issues with copy-pasting tabs by including them in the
pasted string.

Selection of tabs is still inconsistent with what might be expected
based on other terminal emulators, however the behavior hasn't
regressed.

This fixes https://github.com/jwilm/alacritty/issues/219.
This commit is contained in:
Steve Blundy 2018-12-15 13:33:33 -08:00 committed by Christian Duerr
parent 21f888ec41
commit 0c3e28617a
11 changed files with 111 additions and 50 deletions

View file

@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Fix color issue in ncurses programs by updating terminfo pairs from 0x10000 to 0x7FFF - Fix color issue in ncurses programs by updating terminfo pairs from 0x10000 to 0x7FFF
- Fix panic after quitting Alacritty on macOS - Fix panic after quitting Alacritty on macOS
- Tabs are no longer replaced by spaces when copying them to the clipboard
## Version 0.2.4 ## Version 0.2.4

12
Cargo.lock generated
View file

@ -716,7 +716,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [ dependencies = [
"proc-macro2 0.4.24 (registry+https://github.com/rust-lang/crates.io-index)", "proc-macro2 0.4.24 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", "quote 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 0.15.22 (registry+https://github.com/rust-lang/crates.io-index)", "syn 0.15.23 (registry+https://github.com/rust-lang/crates.io-index)",
"synstructure 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", "synstructure 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
@ -1450,7 +1450,7 @@ dependencies = [
"num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", "num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
"proc-macro2 0.4.24 (registry+https://github.com/rust-lang/crates.io-index)", "proc-macro2 0.4.24 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", "quote 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 0.15.22 (registry+https://github.com/rust-lang/crates.io-index)", "syn 0.15.23 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]] [[package]]
@ -2057,7 +2057,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [ dependencies = [
"proc-macro2 0.4.24 (registry+https://github.com/rust-lang/crates.io-index)", "proc-macro2 0.4.24 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", "quote 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 0.15.22 (registry+https://github.com/rust-lang/crates.io-index)", "syn 0.15.23 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]] [[package]]
@ -2203,7 +2203,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]] [[package]]
name = "syn" name = "syn"
version = "0.15.22" version = "0.15.23"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [ dependencies = [
"proc-macro2 0.4.24 (registry+https://github.com/rust-lang/crates.io-index)", "proc-macro2 0.4.24 (registry+https://github.com/rust-lang/crates.io-index)",
@ -2218,7 +2218,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [ dependencies = [
"proc-macro2 0.4.24 (registry+https://github.com/rust-lang/crates.io-index)", "proc-macro2 0.4.24 (registry+https://github.com/rust-lang/crates.io-index)",
"quote 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", "quote 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)",
"syn 0.15.22 (registry+https://github.com/rust-lang/crates.io-index)", "syn 0.15.23 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
@ -3119,7 +3119,7 @@ dependencies = [
"checksum static_assertions 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "389ce475f424f267dbed6479cbd8f126c5e1afb053b0acdaa019c74305fc65d1" "checksum static_assertions 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "389ce475f424f267dbed6479cbd8f126c5e1afb053b0acdaa019c74305fc65d1"
"checksum stb_truetype 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "71a7d260b43b6129a22dc341be18a231044ca67a48b7e32625f380cc5ec9ad70" "checksum stb_truetype 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "71a7d260b43b6129a22dc341be18a231044ca67a48b7e32625f380cc5ec9ad70"
"checksum strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4f380125926a99e52bc279241539c018323fab05ad6368b56f93d9369ff550" "checksum strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4f380125926a99e52bc279241539c018323fab05ad6368b56f93d9369ff550"
"checksum syn 0.15.22 (registry+https://github.com/rust-lang/crates.io-index)" = "ae8b29eb5210bc5cf63ed6149cbf9adfc82ac0be023d8735c176ee74a2db4da7" "checksum syn 0.15.23 (registry+https://github.com/rust-lang/crates.io-index)" = "9545a6a093a3f0bd59adb472700acc08cad3776f860f16a897dfce8c88721cbc"
"checksum synstructure 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "73687139bf99285483c96ac0add482c3776528beac1d97d444f6e91f203a2015" "checksum synstructure 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "73687139bf99285483c96ac0add482c3776528beac1d97d444f6e91f203a2015"
"checksum tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8" "checksum tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8"
"checksum tempfile 3.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "7e91405c14320e5c79b3d148e1c86f40749a36e490642202a31689cb1a3452b2" "checksum tempfile 3.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "7e91405c14320e5c79b3d148e1c86f40749a36e490642202a31689cb1a3452b2"

View file

@ -884,12 +884,17 @@ impl<'a> RenderApi<'a> {
}; };
// Don't render text of HIDDEN cells // Don't render text of HIDDEN cells
let chars = if cell.flags.contains(cell::Flags::HIDDEN) { let mut chars = if cell.flags.contains(cell::Flags::HIDDEN) {
[' '; cell::MAX_ZEROWIDTH_CHARS + 1] [' '; cell::MAX_ZEROWIDTH_CHARS + 1]
} else { } else {
cell.chars cell.chars
}; };
// Render tabs as spaces in case the font doesn't support it
if chars[0] == '\t' {
chars[0] = ' ';
}
let mut glyph_key = GlyphKey { let mut glyph_key = GlyphKey {
font_key, font_key,
size: glyph_cache.font_size, size: glyph_cache.font_size,

View file

@ -114,7 +114,7 @@ impl Cell {
#[inline] #[inline]
pub fn is_empty(&self) -> bool { pub fn is_empty(&self) -> bool {
self.c == ' ' (self.c == ' ' || self.c == '\t')
&& self.extra[0] == ' ' && self.extra[0] == ' '
&& self.bg == Color::Named(NamedColor::Background) && self.bg == Color::Named(NamedColor::Background)
&& !self.flags.intersects(Flags::INVERSE | Flags::UNDERLINE) && !self.flags.intersects(Flags::INVERSE | Flags::UNDERLINE)

View file

@ -763,7 +763,7 @@ pub struct Term {
active_charset: CharsetIndex, active_charset: CharsetIndex,
/// Tabstops /// Tabstops
tabs: Vec<bool>, tabs: TabStops,
/// Mode flags /// Mode flags
mode: TermMode, mode: TermMode,
@ -915,9 +915,7 @@ impl Term {
let alt = Grid::new(num_lines, num_cols, 0 /* scroll history */, Cell::default()); let alt = Grid::new(num_lines, num_cols, 0 /* scroll history */, Cell::default());
let tabspaces = config.tabspaces(); let tabspaces = config.tabspaces();
let tabs = IndexRange::from(Column(0)..grid.num_cols()) let tabs = TabStops::new(grid.num_cols(), tabspaces);
.map(|i| (*i as usize) % tabspaces == 0)
.collect::<Vec<bool>>();
let scroll_region = Line(0)..grid.num_lines(); let scroll_region = Line(0)..grid.num_lines();
@ -1014,11 +1012,23 @@ impl Term {
use std::ops::Range; use std::ops::Range;
trait Append : PushChar { trait Append : PushChar {
fn append(&mut self, grid: &Grid<Cell>, line: usize, cols: Range<Column>); fn append(
&mut self,
grid: &Grid<Cell>,
tabs: &TabStops,
line: usize,
cols: Range<Column>,
);
} }
impl Append for String { impl Append for String {
fn append(&mut self, grid: &Grid<Cell>, mut line: usize, cols: Range<Column>) { fn append(
&mut self,
grid: &Grid<Cell>,
tabs: &TabStops,
mut line: usize,
cols: Range<Column>
) {
// Select until last line still within the buffer // Select until last line still within the buffer
line = min(line, grid.len() - 1); line = min(line, grid.len() - 1);
@ -1029,13 +1039,30 @@ impl Term {
if line_end.0 == 0 && cols.end >= grid.num_cols() - 1 { if line_end.0 == 0 && cols.end >= grid.num_cols() - 1 {
self.push('\n'); self.push('\n');
} else if cols.start < line_end { } else if cols.start < line_end {
for cell in &grid_line[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) { if !cell.flags.contains(cell::Flags::WIDE_CHAR_SPACER) {
self.push(cell.c); self.push(cell.c);
for c in (&cell.chars()[1..]).iter().filter(|c| **c != ' ') { for c in (&cell.chars()[1..]).iter().filter(|c| **c != ' ') {
self.push(*c); self.push(*c);
} }
} }
if cell.c == '\t' {
tab_mode = true;
}
} }
if cols.end >= grid.num_cols() - 1 { if cols.end >= grid.num_cols() - 1 {
@ -1063,32 +1090,31 @@ impl Term {
match line_count { match line_count {
// Selection within single line // Selection within single line
0 => { 0 => {
res.append(&self.grid, start.line, start.col..end.col); res.append(&self.grid, &self.tabs, start.line, start.col..end.col);
}, },
// Selection ends on line following start // Selection ends on line following start
1 => { 1 => {
// Ending line // Ending line
res.append(&self.grid, end.line, end.col..max_col); res.append(&self.grid, &self.tabs, end.line, end.col..max_col);
// Starting line // Starting line
res.append(&self.grid, start.line, Column(0)..start.col); res.append(&self.grid, &self.tabs, start.line, Column(0)..start.col);
}, },
// Multi line selection // Multi line selection
_ => { _ => {
// Ending line // Ending line
res.append(&self.grid, end.line, end.col..max_col); res.append(&self.grid, &self.tabs, end.line, end.col..max_col);
let middle_range = (start.line + 1)..(end.line); let middle_range = (start.line + 1)..(end.line);
for line in middle_range.rev() { for line in middle_range.rev() {
res.append(&self.grid, line, Column(0)..max_col); res.append(&self.grid, &self.tabs, line, Column(0)..max_col);
} }
// Starting line // Starting line
res.append(&self.grid, start.line, Column(0)..start.col); res.append(&self.grid, &self.tabs, start.line, Column(0)..start.col);
} }
} }
@ -1232,9 +1258,7 @@ impl Term {
self.cursor_save_alt.point.line = min(self.cursor_save_alt.point.line, num_lines - 1); self.cursor_save_alt.point.line = min(self.cursor_save_alt.point.line, num_lines - 1);
// Recreate tabs list // Recreate tabs list
self.tabs = IndexRange::from(Column(0)..self.grid.num_cols()) self.tabs = TabStops::new(self.grid.num_cols(), self.tabspaces);
.map(|i| (*i as usize) % self.tabspaces == 0)
.collect::<Vec<bool>>();
} }
#[inline] #[inline]
@ -1553,23 +1577,26 @@ impl ansi::Handler for Term {
fn put_tab(&mut self, mut count: i64) { fn put_tab(&mut self, mut count: i64) {
trace!("put_tab: {}", count); trace!("put_tab: {}", count);
let mut col = self.cursor.point.col; while self.cursor.point.col < self.grid.num_cols() && count != 0 {
while col < self.grid.num_cols() && count != 0 {
count -= 1; count -= 1;
let cell = &mut self.grid[&self.cursor.point];
*cell = self.cursor.template;
cell.c = self.cursor.charsets[self.active_charset].map('\t');
loop { loop {
if (col + 1) == self.grid.num_cols() { if (self.cursor.point.col + 1) == self.grid.num_cols() {
break; break;
} }
col += 1; self.cursor.point.col += 1;
if self.tabs[*col as usize] { if self.tabs[self.cursor.point.col] {
break; break;
} }
} }
} }
self.cursor.point.col = col;
self.input_needs_wrap = false; self.input_needs_wrap = false;
} }
@ -1651,7 +1678,7 @@ impl ansi::Handler for Term {
fn set_horizontal_tabstop(&mut self) { fn set_horizontal_tabstop(&mut self) {
trace!("set_horizontal_tabstop"); trace!("set_horizontal_tabstop");
let column = self.cursor.point.col; let column = self.cursor.point.col;
self.tabs[column.0] = true; self.tabs[column] = true;
} }
#[inline] #[inline]
@ -1731,7 +1758,7 @@ impl ansi::Handler for Term {
for _ in 0..count { for _ in 0..count {
let mut col = self.cursor.point.col; let mut col = self.cursor.point.col;
for i in (0..(col.0)).rev() { for i in (0..(col.0)).rev() {
if self.tabs[i as usize] { if self.tabs[index::Column(i)] {
col = index::Column(i); col = index::Column(i);
break; break;
} }
@ -1874,15 +1901,10 @@ impl ansi::Handler for Term {
match mode { match mode {
ansi::TabulationClearMode::Current => { ansi::TabulationClearMode::Current => {
let column = self.cursor.point.col; let column = self.cursor.point.col;
self.tabs[column.0] = false; self.tabs[column] = false;
}, },
ansi::TabulationClearMode::All => { ansi::TabulationClearMode::All => {
let len = self.tabs.len(); self.tabs.clear_all();
// Safe since false boolean is null, each item occupies only 1
// byte, and called on the length of the vec.
unsafe {
::std::ptr::write_bytes(self.tabs.as_mut_ptr(), 0, len);
}
} }
} }
} }
@ -2068,6 +2090,40 @@ impl ansi::Handler for Term {
} }
} }
struct TabStops {
tabs: Vec<bool>
}
impl TabStops {
fn new(num_cols: Column, tabspaces: usize) -> TabStops {
TabStops {
tabs: IndexRange::from(Column(0)..num_cols)
.map(|i| (*i as usize) % tabspaces == 0)
.collect::<Vec<bool>>()
}
}
fn clear_all(&mut self) {
unsafe {
ptr::write_bytes(self.tabs.as_mut_ptr(), 0, self.tabs.len());
}
}
}
impl Index<Column> for TabStops {
type Output = bool;
fn index(&self, index: Column) -> &bool {
&self.tabs[index.0]
}
}
impl IndexMut<Column> for TabStops {
fn index_mut(&mut self, index: Column) -> &mut bool {
self.tabs.index_mut(index.0)
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use serde_json; use serde_json;

View file

@ -1,6 +1,5 @@
% ]2;jwilm@jwilm-desk: ~/code/alacritty]1;..ode/alacritty jwilm@jwilm-desk ➜  ~/code/alacritty  [?1h=[?2004h %  UL  ~/…/tests/ref/tab_rendering  copy-tabs  [?2004hecho '\tb\na\tb\naa\tb\naaa\tb\naaaa\tb\naaaaa\tb\naaaaaa\tb\naaaaaaa\tb\naaaaaaaa\tb\naaaaaaaaa\tb\naaaaaaaaaa\tb'[?2004l
bck-i-search: _target/debug/alacritty --ref-test --title alacritty-reftest b
bck-i-search: e_echo '\tb\na\tb\naa\tb\naaa\tb\naaaa\tb\naaaaa\tb\naaaaaa\tb\naaaaaaa\tb\naaaaaaaa\tb\naaaaaaaaa\tb\naaaaaaaaaa\tb'c_echh_hoo_echo '\tb\na\tb\naa\tb\naaa\tb\naaaa\tb\naaaaa\tb\naaaaaa\tb\naaaaaaa\tb\naaaaaaaa\tb\naaaaaaaaa\tb\naaaaaaaaaa\tb' [?1l>[?2004l ]2;echo ]1;echo b
a b a b
aa b aa b
aaa b aaa b
@ -11,4 +10,4 @@ aaaaaaa b
aaaaaaaa b aaaaaaaa b
aaaaaaaaa b aaaaaaaaa b
aaaaaaaaaa b aaaaaaaaaa b
% ]2;jwilm@jwilm-desk: ~/code/alacritty]1;..ode/alacritty jwilm@jwilm-desk ➜  ~/code/alacritty  [?1h=[?2004h %  UL  ~/…/tests/ref/tab_rendering  copy-tabs  [?2004h

File diff suppressed because one or more lines are too long

View file

@ -1 +1 @@
{"width":644.0,"height":412.0,"cell_width":8.0,"cell_height":17.0,"padding_x":0.0,"padding_y":0.0} {"width":939.0,"height":1020.0,"cell_width":8.0,"cell_height":16.0,"padding_x":5.0,"padding_y":6.0,"dpr":1.0}

View file

@ -1,5 +1,5 @@
% jwilm@sanctuary.local ➜  ~/code/alacritty  [?1h=[?2004hvvtvttteesvttest[?1l>[?2004l %  UL  ~/…/tests/ref/vttest_tab_cle…  copy-tabs  [?2004hvttestvttest[?2004l
[?1l[?3l[?4l[?5l[?6l[?7h[?8l[?40h[?45lVT100 test program, version 2.7 (20140305)Choose test type: [?1l[?3l[?4l[?5l[?6l[?7h[?8l[?40h[?45lVT100 test program, version 2.7 (20180911)Line speed 38400bd Choose test type:
 
0. Exit 0. Exit
1. Test of cursor movements 1. Test of cursor movements

File diff suppressed because one or more lines are too long

View file

@ -1 +1 @@
{"width":1124.0,"height":676.0,"cell_width":14.0,"cell_height":28.0,"padding_x":0.0,"padding_y":0.0} {"width":939.0,"height":503.0,"cell_width":8.0,"cell_height":16.0,"padding_x":5.0,"padding_y":3.0,"dpr":1.0}