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 panic after quitting Alacritty on macOS
- Tabs are no longer replaced by spaces when copying them to the clipboard
## 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 = [
"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)",
"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)",
]
@ -1450,7 +1450,7 @@ dependencies = [
"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)",
"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]]
@ -2057,7 +2057,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"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)",
"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]]
@ -2203,7 +2203,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "syn"
version = "0.15.22"
version = "0.15.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"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 = [
"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)",
"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)",
]
@ -3119,7 +3119,7 @@ dependencies = [
"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 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 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"

View File

@ -884,12 +884,17 @@ impl<'a> RenderApi<'a> {
};
// 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]
} else {
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 {
font_key,
size: glyph_cache.font_size,

View File

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

View File

@ -763,7 +763,7 @@ pub struct Term {
active_charset: CharsetIndex,
/// Tabstops
tabs: Vec<bool>,
tabs: TabStops,
/// Mode flags
mode: TermMode,
@ -915,9 +915,7 @@ impl Term {
let alt = Grid::new(num_lines, num_cols, 0 /* scroll history */, Cell::default());
let tabspaces = config.tabspaces();
let tabs = IndexRange::from(Column(0)..grid.num_cols())
.map(|i| (*i as usize) % tabspaces == 0)
.collect::<Vec<bool>>();
let tabs = TabStops::new(grid.num_cols(), tabspaces);
let scroll_region = Line(0)..grid.num_lines();
@ -1014,11 +1012,23 @@ impl Term {
use std::ops::Range;
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 {
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
line = min(line, grid.len() - 1);
@ -1029,13 +1039,30 @@ impl Term {
if line_end.0 == 0 && cols.end >= grid.num_cols() - 1 {
self.push('\n');
} 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) {
self.push(cell.c);
for c in (&cell.chars()[1..]).iter().filter(|c| **c != ' ') {
self.push(*c);
}
}
if cell.c == '\t' {
tab_mode = true;
}
}
if cols.end >= grid.num_cols() - 1 {
@ -1063,32 +1090,31 @@ impl Term {
match line_count {
// Selection within single line
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
1 => {
// 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
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
_ => {
// 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);
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
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);
// Recreate tabs list
self.tabs = IndexRange::from(Column(0)..self.grid.num_cols())
.map(|i| (*i as usize) % self.tabspaces == 0)
.collect::<Vec<bool>>();
self.tabs = TabStops::new(self.grid.num_cols(), self.tabspaces);
}
#[inline]
@ -1553,23 +1577,26 @@ impl ansi::Handler for Term {
fn put_tab(&mut self, mut count: i64) {
trace!("put_tab: {}", count);
let mut col = self.cursor.point.col;
while col < self.grid.num_cols() && count != 0 {
while self.cursor.point.col < self.grid.num_cols() && count != 0 {
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 {
if (col + 1) == self.grid.num_cols() {
if (self.cursor.point.col + 1) == self.grid.num_cols() {
break;
}
col += 1;
self.cursor.point.col += 1;
if self.tabs[*col as usize] {
if self.tabs[self.cursor.point.col] {
break;
}
}
}
self.cursor.point.col = col;
self.input_needs_wrap = false;
}
@ -1651,7 +1678,7 @@ impl ansi::Handler for Term {
fn set_horizontal_tabstop(&mut self) {
trace!("set_horizontal_tabstop");
let column = self.cursor.point.col;
self.tabs[column.0] = true;
self.tabs[column] = true;
}
#[inline]
@ -1731,7 +1758,7 @@ impl ansi::Handler for Term {
for _ in 0..count {
let mut col = self.cursor.point.col;
for i in (0..(col.0)).rev() {
if self.tabs[i as usize] {
if self.tabs[index::Column(i)] {
col = index::Column(i);
break;
}
@ -1874,15 +1901,10 @@ impl ansi::Handler for Term {
match mode {
ansi::TabulationClearMode::Current => {
let column = self.cursor.point.col;
self.tabs[column.0] = false;
self.tabs[column] = false;
},
ansi::TabulationClearMode::All => {
let len = self.tabs.len();
// 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);
}
self.tabs.clear_all();
}
}
}
@ -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)]
mod tests {
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
bck-i-search: _target/debug/alacritty --ref-test --title alacritty-reftest
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
%  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
b
a b
aa b
aaa b
@ -11,4 +10,4 @@ aaaaaaa b
aaaaaaaa b
aaaaaaaaa 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
[?1l[?3l[?4l[?5l[?6l[?7h[?8l[?40h[?45lVT100 test program, version 2.7 (20140305)Choose test type:
%  UL  ~/…/tests/ref/vttest_tab_cle…  copy-tabs  [?2004hvttestvttest[?2004l
[?1l[?3l[?4l[?5l[?6l[?7h[?8l[?40h[?45lVT100 test program, version 2.7 (20180911)Line speed 38400bd Choose test type:

0. Exit
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}