aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorAram Drevekenin <aram@poor.dev>2020-10-05 16:46:40 +0200
committerAram Drevekenin <aram@poor.dev>2020-10-05 16:46:40 +0200
commit5cd365a73ce83991b98a92b41a5e1994afaa1688 (patch)
tree86ece9fab529737b70696c926fd3edfb768c5629 /src
parentrefactor(terminal): minor cleanups (diff)
downloadsl0ppij-5cd365a73ce83991b98a92b41a5e1994afaa1688.tar.gz
sl0ppij-5cd365a73ce83991b98a92b41a5e1994afaa1688.tar.xz
sl0ppij-5cd365a73ce83991b98a92b41a5e1994afaa1688.zip
feat(compatibility): vim working!
Diffstat (limited to 'src')
-rw-r--r--src/terminal_pane.rs184
-rw-r--r--src/tests/fixtures/vim_ctrl_dbin0 -> 3698 bytes
-rw-r--r--src/tests/fixtures/vim_ctrl_ubin0 -> 4994 bytes
-rw-r--r--src/tests/fixtures/vim_scroll_region_downbin0 -> 3829 bytes
-rw-r--r--src/tests/integration/compatibility.rs77
-rw-r--r--src/tests/integration/snapshots/mosaic__tests__integration__compatibility__vim_ctrl_d-2.snap32
-rw-r--r--src/tests/integration/snapshots/mosaic__tests__integration__compatibility__vim_ctrl_d.snap32
-rw-r--r--src/tests/integration/snapshots/mosaic__tests__integration__compatibility__vim_ctrl_u-2.snap32
-rw-r--r--src/tests/integration/snapshots/mosaic__tests__integration__compatibility__vim_ctrl_u.snap32
-rw-r--r--src/tests/integration/snapshots/mosaic__tests__integration__compatibility__vim_scroll_region_down-2.snap32
-rw-r--r--src/tests/integration/snapshots/mosaic__tests__integration__compatibility__vim_scroll_region_down.snap32
11 files changed, 452 insertions, 1 deletions
diff --git a/src/terminal_pane.rs b/src/terminal_pane.rs
index 555d92b4..2fbde328 100644
--- a/src/terminal_pane.rs
+++ b/src/terminal_pane.rs
@@ -159,6 +159,7 @@ pub struct TerminalOutput {
cursor_position: usize,
newline_indices: Vec<usize>, // canonical line breaks we get from the vt interpreter
linebreak_indices: Vec<usize>, // linebreaks from line wrapping
+ scroll_region: (usize, usize), // top line index / bottom line index
reset_foreground_ansi_code: bool, // this is a performance optimization, rather than placing and looking for the ansi reset code in pending_ansi_codes
reset_background_ansi_code: bool, // this is a performance optimization, rather than placing and looking for the ansi reset code in pending_ansi_codes
reset_misc_ansi_code: bool, // this is a performance optimization, rather than placing and looking for the ansi reset code in pending_ansi_codes
@@ -175,6 +176,7 @@ impl TerminalOutput {
cursor_position: 0,
newline_indices: Vec::new(),
linebreak_indices: Vec::new(),
+ scroll_region: (1, ws.ws_row as usize),
display_rows: ws.ws_row,
display_cols: ws.ws_col,
should_render: true,
@@ -440,21 +442,168 @@ impl TerminalOutput {
let last_linebreak_index = self.linebreak_indices.iter().rev().find(|&&l_i| l_i <= index_in_line).unwrap_or(&0);
max(*last_newline_index, *last_linebreak_index)
}
+ fn scroll_region_line_indices (&self) -> (usize, usize) {
+ let mut newline_indices = self.newline_indices.iter().rev();
+ let mut linebreak_indices = self.linebreak_indices.iter().rev();
+
+ let mut next_newline = newline_indices.next();
+ let mut next_linebreak = linebreak_indices.next();
+
+ let mut lines_from_end = 0;
+
+ let scroll_end_index_from_screen_bottom = self.display_rows as usize - self.scroll_region.1;
+ let scroll_start_index_from_screen_bottom = scroll_end_index_from_screen_bottom + (self.scroll_region.1 - self.scroll_region.0);
+
+ let mut scroll_region_start_index = None;
+ let mut scroll_region_end_index = None;
+
+ loop {
+ match max(next_newline, next_linebreak) {
+ Some(next_line_index) => {
+ if lines_from_end == scroll_start_index_from_screen_bottom {
+ scroll_region_start_index = Some(next_line_index);
+ }
+ if lines_from_end == scroll_end_index_from_screen_bottom {
+ scroll_region_end_index = Some(next_line_index);
+ }
+ if scroll_region_start_index.is_some() && scroll_region_end_index.is_some() {
+ break;
+ }
+ if Some(next_line_index) == next_newline {
+ next_newline = newline_indices.next();
+ } else if Some(next_line_index) == next_linebreak {
+ next_linebreak = linebreak_indices.next();
+ }
+ lines_from_end += 1;
+ },
+ None => break,
+ }
+ }
+ (*scroll_region_start_index.unwrap_or(&0), *scroll_region_end_index.unwrap_or(&0))
+ }
+ fn index_of_next_line_after(&self, index_in_line: usize) -> usize {
+ let last_newline_index = self.newline_indices.iter().find(|&&n_i| n_i >= index_in_line).unwrap_or(&0);
+ let last_linebreak_index = self.linebreak_indices.iter().find(|&&l_i| l_i >= index_in_line).unwrap_or(&0);
+ max(*last_newline_index, *last_linebreak_index)
+ }
+ fn insert_empty_lines_at_cursor(&mut self, count: usize) {
+ for _ in 0..count {
+ self.delete_last_line_in_scroll_region();
+ let start_of_current_line = self.index_of_beginning_of_line(self.cursor_position);
+ let end_of_current_line = self.index_of_next_line_after(self.cursor_position);
+ for i in 0..end_of_current_line - start_of_current_line {
+ self.characters.insert(start_of_current_line + i, EMPTY_TERMINAL_CHARACTER.clone())
+ }
+ }
+
+ }
+ fn delete_last_line_in_scroll_region(&mut self) {
+ if let Some(newline_index_of_scroll_region_end) = self.get_line_position_on_screen(self.scroll_region.1) {
+ let end_of_last_scroll_region_line = self.get_line_position_on_screen(self.scroll_region.1 + 1).unwrap();
+ &self.characters.drain(newline_index_of_scroll_region_end..end_of_last_scroll_region_line);
+ }
+ }
+ fn delete_first_line_in_scroll_region(&mut self) {
+ if let Some(newline_index_of_scroll_region_start) = self.get_line_position_on_screen(self.scroll_region.0) {
+ let end_of_first_scroll_region_line = self.get_line_position_on_screen(self.scroll_region.0 + 1).unwrap();
+ let removed_count = {
+ let removed_line = &self.characters.drain(newline_index_of_scroll_region_start..end_of_first_scroll_region_line);
+ removed_line.len()
+ };
+ let newline_index_of_scroll_region_end = self.get_line_position_on_screen(self.scroll_region.1).unwrap();
+ for i in 0..removed_count {
+ self.characters.insert(newline_index_of_scroll_region_end + i, EMPTY_TERMINAL_CHARACTER.clone())
+ }
+ // TODO: if removed_count is larger than the line it was inserted it, recalculate all
+ // newline_indices after it
+ }
+ }
+ fn get_line_position_on_screen(&self, index_on_screen: usize) -> Option<usize> {
+ let mut newline_indices = self.newline_indices.iter().rev();
+ let mut linebreak_indices = self.linebreak_indices.iter().rev();
+
+ let mut next_newline = newline_indices.next();
+ let mut next_linebreak = linebreak_indices.next();
+
+ let mut lines_from_end = 0; // 1 because we're counting and not indexing TODO: fix this
+ loop {
+ match max(next_newline, next_linebreak) {
+ Some(next_line_index) => {
+ if index_on_screen == self.display_rows as usize - lines_from_end {
+ return Some(*next_line_index);
+ } else {
+ lines_from_end += 1;
+ }
+ if lines_from_end > self.display_rows as usize {
+ return None;
+ }
+ if Some(next_line_index) == next_newline {
+ next_newline = newline_indices.next();
+ } else if Some(next_line_index) == next_linebreak {
+ next_linebreak = linebreak_indices.next();
+ }
+ },
+ None => {
+ if index_on_screen == self.display_rows as usize - lines_from_end {
+ return Some(0);
+ } else {
+ return None;
+ }
+ }
+ }
+ }
+ }
+ // TODO: better naming of these two functions
+ fn get_line_index_on_screen (&self, position_in_characters: usize) -> Option<usize> {
+ let mut newline_indices = self.newline_indices.iter().rev();
+ let mut linebreak_indices = self.linebreak_indices.iter().rev();
+
+ let mut next_newline = newline_indices.next();
+ let mut next_linebreak = linebreak_indices.next();
+
+ let mut lines_from_end = 0;
+ loop {
+ match max(next_newline, next_linebreak) {
+ Some(next_line_index) => {
+ if *next_line_index <= position_in_characters {
+ break;
+ } else {
+ lines_from_end += 1;
+ if Some(next_line_index) == next_newline {
+ next_newline = newline_indices.next();
+ } else if Some(next_line_index) == next_linebreak {
+ next_linebreak = linebreak_indices.next();
+ }
+ }
+ },
+ None => break,
+ }
+ }
+ if lines_from_end > self.display_rows as usize {
+ None
+ } else {
+ Some(self.display_rows as usize - lines_from_end)
+ }
+ }
fn add_newline (&mut self) {
let nearest_line_end = self.index_of_end_of_canonical_line(self.cursor_position);
+ let current_line_index_on_screen = self.get_line_index_on_screen(self.cursor_position);
if nearest_line_end == self.characters.len() {
self.newline_indices.push(nearest_line_end);
self.cursor_position = nearest_line_end;
+ } else if current_line_index_on_screen == Some(self.scroll_region.1) { // end of scroll region
+ // shift all lines in scroll region up
+ self.delete_first_line_in_scroll_region();
} else {
// we shouldn't add a new line in the middle of the text
// in this case, we'll move to the next existing line and it
// will be overriden as we print on it
self.cursor_position = nearest_line_end;
}
- self.should_render = true;
self.pending_foreground_ansi_codes.clear();
self.pending_background_ansi_codes.clear();
self.pending_misc_ansi_codes.clear();
+ self.should_render = true;
}
fn move_to_beginning_of_line (&mut self) {
let last_newline_index = self.index_of_beginning_of_line(self.cursor_position);
@@ -463,8 +612,18 @@ impl TerminalOutput {
}
}
+fn debug_log_to_file (message: String) {
+ use std::fs::OpenOptions;
+ use std::io::prelude::*;
+ let mut file = OpenOptions::new().append(true).create(true).open("/tmp/mosaic-log.txt").unwrap();
+ file.write_all(message.as_bytes()).unwrap();
+ file.write_all("\n".as_bytes()).unwrap();
+}
+
impl vte::Perform for TerminalOutput {
fn print(&mut self, c: char) {
+
+
// while not ideal that we separate the reset and actual code logic here,
// combining them is a question of rendering performance and not refactoring,
// so will be addressed separately
@@ -673,6 +832,29 @@ impl vte::Perform for TerminalOutput {
// TBD
} else if c == 'h' {
// TBD
+ } else if c == 'r' {
+ debug_log_to_file(format!("\rparams {:?}", params));
+ if params.len() > 1 {
+ // TODO: why do we need this if? what does a 1 parameter 'r' mean?
+ self.scroll_region = (params[0] as usize, params[1] as usize);
+ }
+ } else if c == 't' {
+ // TBD - title?
+ } else if c == 'n' {
+ // TBD - device status report
+ } else if c == 'c' {
+ // TBD - identify terminal
+ } else if c == 'M' {
+ // delete lines if currently inside scroll region
+ let line_count_to_delete = params[0];
+ for _ in 0..line_count_to_delete {
+ // TODO: better, do this in bulk
+ self.delete_first_line_in_scroll_region()
+ }
+ } else if c == 'L' {
+ // insert blank lines if inside scroll region
+ let line_count_to_add = params[0];
+ self.insert_empty_lines_at_cursor(line_count_to_add as usize);
} else {
println!("unhandled csi: {:?}->{:?}", c, params);
panic!("aaa!!!");
diff --git a/src/tests/fixtures/vim_ctrl_d b/src/tests/fixtures/vim_ctrl_d
new file mode 100644
index 00000000..1f4929b8
--- /dev/null
+++ b/src/tests/fixtures/vim_ctrl_d
Binary files differ
diff --git a/src/tests/fixtures/vim_ctrl_u b/src/tests/fixtures/vim_ctrl_u
new file mode 100644
index 00000000..063eb231
--- /dev/null
+++ b/src/tests/fixtures/vim_ctrl_u
Binary files differ
diff --git a/src/tests/fixtures/vim_scroll_region_down b/src/tests/fixtures/vim_scroll_region_down
new file mode 100644
index 00000000..cd2061d2
--- /dev/null
+++ b/src/tests/fixtures/vim_scroll_region_down
Binary files differ
diff --git a/src/tests/integration/compatibility.rs b/src/tests/integration/compatibility.rs
index e5e10061..cd04d354 100644
--- a/src/tests/integration/compatibility.rs
+++ b/src/tests/integration/compatibility.rs
@@ -88,3 +88,80 @@ pub fn fish_select_tab_completion_options() {
assert_snapshot!(snapshot);
}
}
+
+#[test]
+pub fn vim_scroll_region_down () {
+ // here we test a case where vim defines the scroll region as lesser than the screen row count
+ // and then scrolls down
+ // the region is defined here by vim as 1-26 (there are 28 rows)
+ // then the cursor is moved to line 26 and a new line is added
+ // what should happen is that the first line in the scroll region (1) is deleted
+ // and an empty line is inserted in the last scroll region line (26)
+ // this tests also has other steps afterwards that fills the line with the next line in the
+ // file
+ // experience appear to the user
+ let fake_win_size = Winsize {
+ ws_col: 116,
+ ws_row: 28,
+ ws_xpixel: 0,
+ ws_ypixel: 0,
+ };
+ let fixture_name = "vim_scroll_region_down";
+ let mut fake_input_output = get_fake_os_input(&fake_win_size, fixture_name);
+ fake_input_output.add_terminal_input(&[17]); // quit (ctrl-q)
+ start(Box::new(fake_input_output.clone()));
+ let output_frames = fake_input_output.stdout_writer.output_frames.lock().unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ for snapshot in snapshots {
+ assert_snapshot!(snapshot);
+ }
+}
+
+#[test]
+pub fn vim_ctrl_d() {
+ // in vim ctrl-d moves down half a page
+ // in this case, it sends the terminal the csi 'M' directive, which tells it to delete X (13 in
+ // this case) lines inside the scroll region and push the other lines up
+ // what happens here is that 13 lines are deleted and instead 13 empty lines are added at the
+ // end of the scroll region
+ // vim makes sure to fill these empty lines with the rest of the file
+ let fake_win_size = Winsize {
+ ws_col: 116,
+ ws_row: 28,
+ ws_xpixel: 0,
+ ws_ypixel: 0,
+ };
+ let fixture_name = "vim_ctrl_d";
+ let mut fake_input_output = get_fake_os_input(&fake_win_size, fixture_name);
+ fake_input_output.add_terminal_input(&[17]); // quit (ctrl-q)
+ start(Box::new(fake_input_output.clone()));
+ let output_frames = fake_input_output.stdout_writer.output_frames.lock().unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ for snapshot in snapshots {
+ assert_snapshot!(snapshot);
+ }
+}
+
+#[test]
+pub fn vim_ctrl_u() {
+ // in vim ctrl-u moves up half a page
+ // in this case, it sends the terminal the csi 'L' directive, which tells it to insert X (13 in
+ // this case) lines at the cursor, pushing away (deleting) the last line in the scroll region
+ // this causes the effect of scrolling up X lines (vim replaces the lines with the ones in the
+ // file above the current content)
+ let fake_win_size = Winsize {
+ ws_col: 116,
+ ws_row: 28,
+ ws_xpixel: 0,
+ ws_ypixel: 0,
+ };
+ let fixture_name = "vim_ctrl_u";
+ let mut fake_input_output = get_fake_os_input(&fake_win_size, fixture_name);
+ fake_input_output.add_terminal_input(&[17]); // quit (ctrl-q)
+ start(Box::new(fake_input_output.clone()));
+ let output_frames = fake_input_output.stdout_writer.output_frames.lock().unwrap();
+ let snapshots = get_output_frame_snapshots(&output_frames, &fake_win_size);
+ for snapshot in snapshots {
+ assert_snapshot!(snapshot);
+ }
+}
diff --git a/src/tests/integration/snapshots/mosaic__tests__integration__compatibility__vim_ctrl_d-2.snap b/src/tests/integration/snapshots/mosaic__tests__integration__compatibility__vim_ctrl_d-2.snap
new file mode 100644
index 00000000..5a3f7ce5
--- /dev/null
+++ b/src/tests/integration/snapshots/mosaic__tests__integration__compatibility__vim_ctrl_d-2.snap
@@ -0,0 +1,32 @@
+---
+source: src/tests/integration/compatibility.rs
+expression: snapshot
+---
+ 2 line15
+ 1 line16
+17 line17
+ 1 line18
+ 2 line19
+ 3 line20
+ 4 line21
+ 5 line22
+ 6 line23
+ 7 line24
+ 8 line25
+ 9 line26
+ 10 line27
+ 11 line28
+ 12 line29
+ 13 line30
+ 14 line31
+ 15 line32
+ 16 line33
+ 17 line34
+ 18 line35
+ 19 line36
+ 20 line37
+ 21 line38
+ 22 line39
+ NORMAL testfile.rs unix | utf-8 | rust 40% 17:1
+"/tmp/testfile.rs" 42L, 285C
+Bye from Mosaic!█
diff --git a/src/tests/integration/snapshots/mosaic__tests__integration__compatibility__vim_ctrl_d.snap b/src/tests/integration/snapshots/mosaic__tests__integration__compatibility__vim_ctrl_d.snap
new file mode 100644
index 00000000..b4eee2ea
--- /dev/null
+++ b/src/tests/integration/snapshots/mosaic__tests__integration__compatibility__vim_ctrl_d.snap
@@ -0,0 +1,32 @@
+---
+source: src/tests/integration/compatibility.rs
+expression: snapshot
+---
+ 3 line14
+ 2 line15
+ 1 line16
+17 █ine17
+ 1 line18
+ 2 line19
+ 3 line20
+ 4 line21
+ 5 line22
+ 6 line23
+ 7 line24
+ 8 line25
+ 9 line26
+ 10 line27
+ 11 line28
+ 12 line29
+ 13 line30
+ 14 line31
+ 15 line32
+ 16 line33
+ 17 line34
+ 18 line35
+ 19 line36
+ 20 line37
+ 21 line38
+ 22 line39
+ NORMAL testfile.rs unix | utf-8 | rust 40% 17:1
+"/tmp/testfile.rs" 42L, 285C
diff --git a/src/tests/integration/snapshots/mosaic__tests__integration__compatibility__vim_ctrl_u-2.snap b/src/tests/integration/snapshots/mosaic__tests__integration__compatibility__vim_ctrl_u-2.snap
new file mode 100644
index 00000000..82d924a0
--- /dev/null
+++ b/src/tests/integration/snapshots/mosaic__tests__integration__compatibility__vim_ctrl_u-2.snap
@@ -0,0 +1,32 @@
+---
+source: src/tests/integration/compatibility.rs
+expression: snapshot
+---
+ 21 line5
+ 20 line6
+ 19 line7
+ 18 line8
+ 17 line9
+ 16 line10
+ 15 line11
+ 14 line12
+ 13 line13
+ 12 line14
+ 11 line15
+ 10 line16
+ 9 line17
+ 8 line18
+ 7 line19
+ 6 line20
+ 5 line21
+ 4 line22
+ 3 line23
+ 2 line24
+ 1 line25
+26 line26
+ 1 line27
+ 2 line28
+ 3 line29
+ NORMAL testfile.rs unix | utf-8 | rust 61% 26:1
+"/tmp/testfile.rs" 42L, 285C
+Bye from Mosaic!█
diff --git a/src/tests/integration/snapshots/mosaic__tests__integration__compatibility__vim_ctrl_u.snap b/src/tests/integration/snapshots/mosaic__tests__integration__compatibility__vim_ctrl_u.snap
new file mode 100644
index 00000000..cdb821f9
--- /dev/null
+++ b/src/tests/integration/snapshots/mosaic__tests__integration__compatibility__vim_ctrl_u.snap
@@ -0,0 +1,32 @@
+---
+source: src/tests/integration/compatibility.rs
+expression: snapshot
+---
+ 22 line4
+ 21 line5
+ 20 line6
+ 19 line7
+ 18 line8
+ 17 line9
+ 16 line10
+ 15 line11
+ 14 line12
+ 13 line13
+ 12 line14
+ 11 line15
+ 10 line16
+ 9 line17
+ 8 line18
+ 7 line19
+ 6 line20
+ 5 line21
+ 4 line22
+ 3 line23
+ 2 line24
+ 1 line25
+26 █ine26
+ 1 line27
+ 2 line28
+ 3 line29
+ NORMAL testfile.rs unix | utf-8 | rust 61% 26:1
+"/tmp/testfile.rs" 42L, 285C
diff --git a/src/tests/integration/snapshots/mosaic__tests__integration__compatibility__vim_scroll_region_down-2.snap b/src/tests/integration/snapshots/mosaic__tests__integration__compatibility__vim_scroll_region_down-2.snap
new file mode 100644
index 00000000..a897b376
--- /dev/null
+++ b/src/tests/integration/snapshots/mosaic__tests__integration__compatibility__vim_scroll_region_down-2.snap
@@ -0,0 +1,32 @@
+---
+source: src/tests/integration/compatibility.rs
+expression: snapshot
+---
+ 21 line3
+ 20 line4
+ 19 line5
+ 18 line6
+ 17 line7
+ 16 line8
+ 15 line9
+ 14 line10
+ 13 line11
+ 12 line12
+ 11 line13
+ 10 line14
+ 9 line15
+ 8 line16
+ 7 line17
+ 6 line18
+ 5 line19
+ 4 line20
+ 3 line21
+ 2 line22
+ 1 line23
+24 line24
+ 1 line25
+ 2 line26
+ 3 line27
+ NORMAL testfile.rs unix | utf-8 | rust 57% 24:1
+"/tmp/testfile.rs" 42L, 285C
+Bye from Mosaic!█
diff --git a/src/tests/integration/snapshots/mosaic__tests__integration__compatibility__vim_scroll_region_down.snap b/src/tests/integration/snapshots/mosaic__tests__integration__compatibility__vim_scroll_region_down.snap
new file mode 100644
index 00000000..3ca2370e
--- /dev/null
+++ b/src/tests/integration/snapshots/mosaic__tests__integration__compatibility__vim_scroll_region_down.snap
@@ -0,0 +1,32 @@
+---
+source: src/tests/integration/compatibility.rs
+expression: snapshot
+---
+ 22 line2
+ 21 line3
+ 20 line4
+ 19 line5
+ 18 line6
+ 17 line7
+ 16 line8
+ 15 line9
+ 14 line10
+ 13 line11
+ 12 line12
+ 11 line13
+ 10 line14
+ 9 line15
+ 8 line16
+ 7 line17
+ 6 line18
+ 5 line19
+ 4 line20
+ 3 line21
+ 2 line22
+ 1 line23
+24 █ine24
+ 1 line25
+ 2 line26
+ 3 line27
+ NORMAL testfile.rs unix | utf-8 | rust 57% 24:1
+"/tmp/testfile.rs" 42L, 285C