aboutsummaryrefslogtreecommitdiffstats
path: root/src/panes
diff options
context:
space:
mode:
authorBrooks J Rady <b.j.rady@gmail.com>2021-01-06 17:46:50 +0000
committerBrooks J Rady <b.j.rady@gmail.com>2021-01-06 17:46:50 +0000
commit4a176669fa93f6ea37530acb667acfaf151fb7f1 (patch)
tree050f89fba88986bd7db2f8fd193091c854342a89 /src/panes
parentWarnings purged and some FIXME's addressed (diff)
downloadsl0ppij-4a176669fa93f6ea37530acb667acfaf151fb7f1.tar.gz
sl0ppij-4a176669fa93f6ea37530acb667acfaf151fb7f1.tar.xz
sl0ppij-4a176669fa93f6ea37530acb667acfaf151fb7f1.zip
A module rename to please clippy
Diffstat (limited to 'src/panes')
-rw-r--r--src/panes/mod.rs9
-rw-r--r--src/panes/plugin_pane.rs167
-rw-r--r--src/panes/scroll.rs833
-rw-r--r--src/panes/terminal_character.rs577
-rw-r--r--src/panes/terminal_pane.rs617
5 files changed, 2203 insertions, 0 deletions
diff --git a/src/panes/mod.rs b/src/panes/mod.rs
new file mode 100644
index 00000000..7d478886
--- /dev/null
+++ b/src/panes/mod.rs
@@ -0,0 +1,9 @@
+mod plugin_pane;
+mod scroll;
+mod terminal_character;
+mod terminal_pane;
+
+pub use plugin_pane::*;
+pub use scroll::*;
+pub use terminal_character::*;
+pub use terminal_pane::*;
diff --git a/src/panes/plugin_pane.rs b/src/panes/plugin_pane.rs
new file mode 100644
index 00000000..1573d577
--- /dev/null
+++ b/src/panes/plugin_pane.rs
@@ -0,0 +1,167 @@
+#![allow(clippy::clippy::if_same_then_else)]
+
+use crate::{pty_bus::VteEvent, tab::Pane, wasm_vm::PluginInstruction, SenderWithContext};
+
+use std::{sync::mpsc::channel, unimplemented};
+
+use crate::panes::{PaneId, PositionAndSize};
+
+pub struct PluginPane {
+ pub pid: u32,
+ pub should_render: bool,
+ pub position_and_size: PositionAndSize,
+ pub position_and_size_override: Option<PositionAndSize>,
+ pub send_plugin_instructions: SenderWithContext<PluginInstruction>,
+}
+
+impl PluginPane {
+ pub fn new(
+ pid: u32,
+ position_and_size: PositionAndSize,
+ send_plugin_instructions: SenderWithContext<PluginInstruction>,
+ ) -> Self {
+ Self {
+ pid,
+ should_render: true,
+ position_and_size,
+ position_and_size_override: None,
+ send_plugin_instructions,
+ }
+ }
+}
+
+impl Pane for PluginPane {
+ // FIXME: These position and size things should all be moved to default trait implementations,
+ // with something like a get_pos_and_sz() method underpinning all of them. Alternatively and
+ // preferably, just use an enum and not a trait object
+ fn x(&self) -> usize {
+ self.position_and_size_override
+ .unwrap_or(self.position_and_size)
+ .x
+ }
+ fn y(&self) -> usize {
+ self.position_and_size_override
+ .unwrap_or(self.position_and_size)
+ .y
+ }
+ fn rows(&self) -> usize {
+ self.position_and_size_override
+ .unwrap_or(self.position_and_size)
+ .rows
+ }
+ fn columns(&self) -> usize {
+ self.position_and_size_override
+ .unwrap_or(self.position_and_size)
+ .columns
+ }
+ fn reset_size_and_position_override(&mut self) {
+ self.position_and_size_override = None;
+ self.should_render = true;
+ }
+ fn change_pos_and_size(&mut self, position_and_size: &PositionAndSize) {
+ self.position_and_size = *position_and_size;
+ self.should_render = true;
+ }
+ // FIXME: This is obviously a bit outdated and needs the x and y moved into `size`
+ fn override_size_and_position(&mut self, x: usize, y: usize, size: &PositionAndSize) {
+ let position_and_size_override = PositionAndSize {
+ x,
+ y,
+ rows: size.rows,
+ columns: size.columns,
+ };
+ self.position_and_size_override = Some(position_and_size_override);
+ self.should_render = true;
+ }
+ fn handle_event(&mut self, _event: VteEvent) {
+ unimplemented!()
+ }
+ fn cursor_coordinates(&self) -> Option<(usize, usize)> {
+ None
+ }
+ fn adjust_input_to_terminal(&self, _input_bytes: Vec<u8>) -> Vec<u8> {
+ unimplemented!() // FIXME: Shouldn't need this implmented?
+ }
+
+ fn position_and_size_override(&self) -> Option<PositionAndSize> {
+ self.position_and_size_override
+ }
+ fn should_render(&self) -> bool {
+ self.should_render
+ }
+ fn set_should_render(&mut self, should_render: bool) {
+ self.should_render = should_render;
+ }
+ fn render(&mut self) -> Option<String> {
+ // if self.should_render {
+ if true {
+ // while checking should_render rather than rendering each pane every time
+ // is more performant, it causes some problems when the pane to the left should be
+ // rendered and has wide characters (eg. Chinese characters or emoji)
+ // as a (hopefully) temporary hack, we render all panes until we find a better solution
+ let (buf_tx, buf_rx) = channel();
+
+ self.send_plugin_instructions
+ .send(PluginInstruction::Draw(
+ buf_tx,
+ self.pid,
+ self.rows(),
+ self.columns(),
+ ))
+ .unwrap();
+
+ self.should_render = false;
+ Some(buf_rx.recv().unwrap())
+ } else {
+ None
+ }
+ }
+ fn pid(&self) -> PaneId {
+ PaneId::Plugin(self.pid)
+ }
+ fn reduce_height_down(&mut self, count: usize) {
+ self.position_and_size.y += count;
+ self.position_and_size.rows -= count;
+ self.should_render = true;
+ }
+ fn increase_height_down(&mut self, count: usize) {
+ self.position_and_size.rows += count;
+ self.should_render = true;
+ }
+ fn increase_height_up(&mut self, count: usize) {
+ self.position_and_size.y -= count;
+ self.position_and_size.rows += count;
+ self.should_render = true;
+ }
+ fn reduce_height_up(&mut self, count: usize) {
+ self.position_and_size.rows -= count;
+ self.should_render = true;
+ }
+ fn reduce_width_right(&mut self, count: usize) {
+ self.position_and_size.x += count;
+ self.position_and_size.columns -= count;
+ self.should_render = true;
+ }
+ fn reduce_width_left(&mut self, count: usize) {
+ self.position_and_size.columns -= count;
+ self.should_render = true;
+ }
+ fn increase_width_left(&mut self, count: usize) {
+ self.position_and_size.x -= count;
+ self.position_and_size.columns += count;
+ self.should_render = true;
+ }
+ fn increase_width_right(&mut self, count: usize) {
+ self.position_and_size.columns += count;
+ self.should_render = true;
+ }
+ fn scroll_up(&mut self, _count: usize) {
+ unimplemented!()
+ }
+ fn scroll_down(&mut self, _count: usize) {
+ unimplemented!()
+ }
+ fn clear_scroll(&mut self) {
+ unimplemented!()
+ }
+}
diff --git a/src/panes/scroll.rs b/src/panes/scroll.rs
new file mode 100644
index 00000000..05252b03
--- /dev/null
+++ b/src/panes/scroll.rs
@@ -0,0 +1,833 @@
+use std::collections::VecDeque;
+use std::fmt::{self, Debug, Formatter};
+
+use crate::panes::terminal_character::{
+ CharacterStyles, TerminalCharacter, EMPTY_TERMINAL_CHARACTER,
+};
+
+/*
+ * Scroll
+ *
+ * holds the terminal buffer and controls the viewport (which part of it we see)
+ * its functions include line-wrapping and tracking the location of the cursor
+ *
+ */
+
+/*
+ * CanonicalLine vs. WrappedFragment
+ *
+ * If the terminal had infinite width and we did not need to line wrap, the CanonicalLine would
+ * be our only unit of line separation.
+ * Each CanonicalLine has one or more WrappedFragments, which are re-calculated when the terminal is
+ * resized, or when characters are added to the line
+ *
+ */
+
+#[derive(Clone)]
+pub struct CanonicalLine {
+ pub wrapped_fragments: Vec<WrappedFragment>,
+}
+
+impl CanonicalLine {
+ pub fn new() -> Self {
+ CanonicalLine {
+ wrapped_fragments: vec![WrappedFragment::new()],
+ }
+ }
+ pub fn add_new_wrap(&mut self, terminal_character: TerminalCharacter) {
+ let mut new_fragment = WrappedFragment::new();
+ new_fragment.add_character(terminal_character, 0);
+ self.wrapped_fragments.push(new_fragment);
+ }
+ pub fn change_width(&mut self, new_width: usize) {
+ let characters = self.flattened_characters();
+ let wrapped_fragments =
+ CanonicalLine::fill_fragments_up_to_width(characters, new_width, None);
+ self.wrapped_fragments = wrapped_fragments;
+ }
+ pub fn clear_after(&mut self, fragment_index: usize, column_index: usize) {
+ let fragment_to_clear = self
+ .wrapped_fragments
+ .get_mut(fragment_index)
+ .expect("fragment out of bounds");
+ fragment_to_clear.clear_after_and_including(column_index);
+ self.wrapped_fragments.truncate(fragment_index + 1);
+ }
+ pub fn replace_with_empty_chars(
+ &mut self,
+ fragment_index: usize,
+ from_col: usize,
+ count: usize,
+ style_of_empty_space: CharacterStyles,
+ ) {
+ let mut characters_replaced = 0;
+ let mut column_position_in_fragment = from_col;
+ let mut current_fragment = fragment_index;
+ let mut empty_space_character = EMPTY_TERMINAL_CHARACTER;
+ empty_space_character.styles = style_of_empty_space;
+ loop {
+ if characters_replaced == count {
+ break;
+ }
+ match self.wrapped_fragments.get_mut(current_fragment) {
+ Some(fragment_to_clear) => {
+ let fragment_characters_count = fragment_to_clear.characters.len();
+ if fragment_characters_count >= column_position_in_fragment {
+ fragment_to_clear
+ .add_character(empty_space_character, column_position_in_fragment);
+ column_position_in_fragment += 1;
+ characters_replaced += 1;
+ } else {
+ current_fragment += 1;
+ column_position_in_fragment = 0;
+ }
+ }
+ None => {
+ // end of line, nothing more to clear
+ break;
+ }
+ }
+ }
+ }
+ pub fn erase_chars(
+ &mut self,
+ fragment_index: usize,
+ from_col: usize,
+ count: usize,
+ style_of_empty_space: CharacterStyles,
+ ) {
+ let mut empty_character = EMPTY_TERMINAL_CHARACTER;
+ empty_character.styles = style_of_empty_space;
+ let current_width = self.wrapped_fragments.get(0).unwrap().characters.len();
+ let mut characters = self.flattened_characters();
+ let absolute_position_of_character = fragment_index * current_width + from_col;
+ for _ in 0..count {
+ characters.remove(absolute_position_of_character);
+ }
+ let wrapped_fragments = CanonicalLine::fill_fragments_up_to_width(
+ characters,
+ current_width,
+ Some(empty_character),
+ );
+ self.wrapped_fragments = wrapped_fragments;
+ }
+ pub fn replace_with_empty_chars_after_cursor(
+ &mut self,
+ fragment_index: usize,
+ from_col: usize,
+ total_columns: usize,
+ style_of_empty_space: CharacterStyles,
+ ) {
+ let mut empty_char_character = EMPTY_TERMINAL_CHARACTER;
+ empty_char_character.styles = style_of_empty_space;
+ let current_fragment = self.wrapped_fragments.get_mut(fragment_index).unwrap();
+ let fragment_characters_count = current_fragment.characters.len();
+
+ for i in from_col..fragment_characters_count {
+ current_fragment.add_character(empty_char_character, i);
+ }
+
+ for i in fragment_characters_count..total_columns {
+ current_fragment.add_character(empty_char_character, i);
+ }
+
+ self.wrapped_fragments.truncate(fragment_index + 1);
+ }
+ pub fn replace_with_empty_chars_before_cursor(
+ &mut self,
+ fragment_index: usize,
+ until_col: usize,
+ style_of_empty_space: CharacterStyles,
+ ) {
+ let mut empty_char_character = EMPTY_TERMINAL_CHARACTER;
+ empty_char_character.styles = style_of_empty_space;
+
+ if fragment_index > 0 {
+ for i in 0..(fragment_index - 1) {
+ let fragment = self.wrapped_fragments.get_mut(i).unwrap();
+ for i in 0..fragment.characters.len() {
+ fragment.add_character(empty_char_character, i);
+ }
+ }
+ }
+ let current_fragment = self.wrapped_fragments.get_mut(fragment_index).unwrap();
+ for i in 0..until_col {
+ current_fragment.add_character(empty_char_character, i);
+ }
+ }
+ fn flattened_characters(&self) -> Vec<TerminalCharacter> {
+ self.wrapped_fragments
+ .iter()
+ .fold(
+ Vec::with_capacity(self.wrapped_fragments.len()),
+ |mut characters, wrapped_fragment| {
+ characters.push(wrapped_fragment.characters.iter().copied());
+ characters
+ },
+ )
+ .into_iter()
+ .flatten()
+ .collect()
+ }
+ fn fill_fragments_up_to_width(
+ mut characters: Vec<TerminalCharacter>,
+ width: usize,
+ padding: Option<TerminalCharacter>,
+ ) -> Vec<WrappedFragment> {
+ let mut wrapped_fragments = vec![];
+ while !characters.is_empty() {
+ if characters.len() > width {
+ wrapped_fragments.push(WrappedFragment::from_vec(
+ characters.drain(..width).collect(),
+ ));
+ } else {
+ let mut last_fragment = WrappedFragment::from_vec(characters.drain(..).collect());
+ let last_fragment_len = last_fragment.characters.len();
+ if let Some(empty_char_character) = padding {
+ if last_fragment_len < width {
+ for _ in last_fragment_len..width {
+ last_fragment.characters.push(empty_char_character);
+ }
+ }
+ }
+ wrapped_fragments.push(last_fragment);
+ }
+ }
+ if wrapped_fragments.is_empty() {
+ wrapped_fragments.push(WrappedFragment::new());
+ }
+ wrapped_fragments
+ }
+}
+
+impl Debug for CanonicalLine {
+ fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
+ for wrapped_fragment in &self.wrapped_fragments {
+ writeln!(f, "{:?}", wrapped_fragment)?;
+ }
+ Ok(())
+ }
+}
+
+#[derive(Clone)]
+pub struct WrappedFragment {
+ pub characters: Vec<TerminalCharacter>,
+}
+
+impl WrappedFragment {
+ pub fn new() -> Self {
+ WrappedFragment { characters: vec![] }
+ }
+ pub fn add_character(
+ &mut self,
+ terminal_character: TerminalCharacter,
+ position_in_line: usize,
+ ) {
+ if position_in_line == self.characters.len() {
+ self.characters.push(terminal_character);
+ } else {
+ // this is much more performant than remove/insert
+ self.characters.push(terminal_character);
+ self.characters.swap_remove(position_in_line);
+ }
+ }
+ pub fn from_vec(characters: Vec<TerminalCharacter>) -> Self {
+ WrappedFragment { characters }
+ }
+ pub fn clear_after_and_including(&mut self, character_index: usize) {
+ self.characters.truncate(character_index);
+ }
+}
+
+impl Debug for WrappedFragment {
+ fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
+ for character in &self.characters {
+ write!(f, "{:?}", character)?;
+ }
+ Ok(())
+ }
+}
+
+#[derive(Debug, Clone, Copy)]
+pub struct CursorPosition {
+ line_index: (usize, usize), // (canonical line index, fragment index in line)
+ column_index: usize, // 0 is the first character from the pane edge
+}
+
+impl CursorPosition {
+ pub fn new() -> Self {
+ CursorPosition {
+ line_index: (0, 0),
+ column_index: 0,
+ }
+ }
+ pub fn move_forward(&mut self, count: usize) {
+ // TODO: panic if out of bounds?
+ self.column_index += count;
+ }
+ pub fn move_backwards(&mut self, count: usize) {
+ self.column_index -= count;
+ }
+ pub fn move_to_next_linewrap(&mut self) {
+ self.line_index.1 += 1;
+ }
+ pub fn move_to_next_canonical_line(&mut self) {
+ self.line_index.0 += 1;
+ }
+ pub fn _move_to_prev_canonical_line(&mut self) {
+ self.line_index.0 -= 1;
+ }
+ pub fn move_to_beginning_of_linewrap(&mut self) {
+ self.column_index = 0;
+ }
+ pub fn move_to_beginning_of_canonical_line(&mut self) {
+ self.column_index = 0;
+ self.line_index.1 = 0;
+ }
+ pub fn move_down_by_canonical_lines(&mut self, count: usize) {
+ // this method does not verify that we will not overflow
+ let current_canonical_line_position = self.line_index.0;
+ self.line_index = (current_canonical_line_position + count, 0);
+ }
+ pub fn move_to_canonical_line(&mut self, index: usize) {
+ self.line_index = (index, 0);
+ }
+ pub fn move_to_column(&mut self, col: usize) {
+ self.column_index = col;
+ }
+ pub fn reset(&mut self) {
+ self.column_index = 0;
+ self.line_index = (0, 0);
+ }
+}
+
+pub struct Scroll {
+ canonical_lines: Vec<CanonicalLine>,
+ cursor_position: CursorPosition,
+ total_columns: usize,
+ lines_in_view: usize,
+ viewport_bottom_offset: Option<usize>,
+ scroll_region: Option<(usize, usize)>, // start line, end line (if set, this is the area the will scroll)
+ show_cursor: bool,
+ alternative_buffer: Option<Vec<CanonicalLine>>,
+ alternative_cursor_position: Option<CursorPosition>,
+}
+
+impl Scroll {
+ pub fn new(total_columns: usize, lines_in_view: usize) -> Self {
+ let mut canonical_lines = vec![];
+ canonical_lines.push(CanonicalLine::new());
+ let cursor_position = CursorPosition::new();
+ Scroll {
+ canonical_lines: vec![CanonicalLine::new()], // The rest will be created by newlines explicitly
+ total_columns,
+ lines_in_view,
+ cursor_position,
+ viewport_bottom_offset: None,
+ scroll_region: None,
+ show_cursor: true,
+ alternative_buffer: None,
+ alternative_cursor_position: None,
+ }
+ }
+ pub fn as_character_lines(&self) -> Vec<Vec<TerminalCharacter>> {
+ let mut lines: VecDeque<Vec<TerminalCharacter>> = VecDeque::new(); // TODO: with capacity lines_from_end?
+ let canonical_lines = self.canonical_lines.iter().rev();
+ let mut lines_to_skip = self.viewport_bottom_offset.unwrap_or(0);
+ 'gather_lines: for current_canonical_line in canonical_lines {
+ for wrapped_fragment in current_canonical_line.wrapped_fragments.iter().rev() {
+ let mut line: Vec<TerminalCharacter> =
+ wrapped_fragment.characters.iter().copied().collect();
+ if lines_to_skip > 0 {
+ lines_to_skip -= 1;
+ } else {
+ for _ in line.len()..self.total_columns {
+ // pad line if needed
+ line.push(EMPTY_TERMINAL_CHARACTER);
+ }
+ lines.push_front(line);
+ }
+ if lines.len() == self.lines_in_view {
+ break 'gather_lines;
+ }
+ }
+ }
+ if lines.len() < self.lines_in_view {
+ // pad lines in case we don't have enough scrollback to fill the view
+ let empty_line = vec![EMPTY_TERMINAL_CHARACTER; self.total_columns];
+
+ for _ in lines.len()..self.lines_in_view {
+ // pad lines in case we didn't have enough
+ lines.push_back(empty_line.clone());
+ }
+ }
+ Vec::from(lines)
+ }
+ pub fn add_character(&mut self, terminal_character: TerminalCharacter) {
+ let (canonical_line_position, wrapped_fragment_index_in_line) =
+ self.cursor_position.line_index;
+ let cursor_position_in_line = self.cursor_position.column_index;
+ let current_line = self
+ .canonical_lines
+ .get_mut(canonical_line_position)
+ .expect("cursor out of bounds");
+ let current_wrapped_fragment = current_line
+ .wrapped_fragments
+ .get_mut(wrapped_fragment_index_in_line)
+ .expect("cursor out of bounds");
+
+ if cursor_position_in_line < self.total_columns {
+ current_wrapped_fragment.add_character(terminal_character, cursor_position_in_line);
+ self.cursor_position.move_forward(1);
+ } else {
+ current_line.add_new_wrap(terminal_character);
+ self.cursor_position.move_to_next_linewrap();
+ self.cursor_position.move_to_beginning_of_linewrap();
+ self.cursor_position.move_forward(1);
+ }
+ }
+ pub fn show_cursor(&mut self) {
+ self.show_cursor = true;
+ }
+ pub fn hide_cursor(&mut self) {
+ self.show_cursor = false;
+ }
+ pub fn add_canonical_line(&mut self) {
+ let current_canonical_line_index = self.cursor_position.line_index.0;
+ if let Some((scroll_region_top, scroll_region_bottom)) =
+ self.scroll_region_absolute_indices()
+ {
+ if current_canonical_line_index == scroll_region_bottom {
+ // end of scroll region
+ // when we have a scroll region set and we're at its bottom
+ // we need to delete its first line, thus shifting all lines in it upwards
+ // then we add an empty line at its end which will be filled by the application
+ // controlling the scroll region (presumably filled by whatever comes next in the
+ // scroll buffer, but that's not something we control)
+ self.canonical_lines.remove(scroll_region_top);
+ self.canonical_lines
+ .insert(scroll_region_bottom, CanonicalLine::new());
+ return;
+ }
+ }
+
+ use std::cmp::Ordering;
+ match current_canonical_line_index.cmp(&(self.canonical_lines.len() - 1)) {
+ Ordering::Equal => {
+ self.canonical_lines.push(CanonicalLine::new());
+ self.cursor_position.move_to_next_canonical_line();
+ self.cursor_position.move_to_beginning_of_canonical_line();
+ }
+ Ordering::Less => {
+ self.cursor_position.move_to_next_canonical_line();
+ self.cursor_position.move_to_beginning_of_canonical_line();
+ }
+ _ => panic!("cursor out of bounds, cannot add_canonical_line"),
+ }
+ }
+ pub fn cursor_coordinates_on_screen(&self) -> Option<(usize, usize)> {
+ // (x, y)
+ if !self.show_cursor {
+ return None;
+ }
+ let (canonical_line_cursor_position, line_wrap_cursor_position) =
+ self.cursor_position.line_index;
+ let x = self.cursor_position.column_index;
+ let mut y = 0;
+ let indices_and_canonical_lines = self.canonical_lines.iter().enumerate().rev();
+ for (current_index, current_line) in indices_and_canonical_lines {
+ if current_index == canonical_line_cursor_position {
+ y += current_line.wrapped_fragments.len() - line_wrap_cursor_position;
+ break;
+ } else {
+ y += current_line.wrapped_fragments.len();
+ }
+ }
+ let total_lines = self
+ .canonical_lines
+ .iter()
+ .fold(0, |total_lines, current_line| {
+ total_lines + current_line.wrapped_fragments.len()
+ }); // TODO: is this performant enough? should it be cached or kept track of?
+ let y = if total_lines < self.lines_in_view {
+ total_lines - y
+ } else if y > self.lines_in_view {
+ self.lines_in_view
+ } else {
+ self.lines_in_view - y
+ };
+ Some((x, y))
+ }
+ pub fn move_cursor_forward(&mut self, count: usize) {
+ let (current_canonical_line_index, current_line_wrap_position) =
+ self.cursor_position.line_index;
+ let current_cursor_column_position = self.cursor_position.column_index;
+ let current_canonical_line = self
+ .canonical_lines
+ .get_mut(current_canonical_line_index)
+ .expect("cursor out of bounds");
+ let current_fragment = current_canonical_line
+ .wrapped_fragments
+ .get_mut(current_line_wrap_position)
+ .expect("cursor out of bounds");
+ let move_count = if current_cursor_column_position + count > self.total_columns {
+ // move to last column in the current line wrap
+ self.total_columns - current_cursor_column_position
+ } else {
+ count
+ };
+
+ for _ in current_fragment.characters.len()..current_cursor_column_position + move_count {
+ current_fragment.characters.push(EMPTY_TERMINAL_CHARACTER);
+ }
+ self.cursor_position.move_forward(move_count);
+ }
+ pub fn move_cursor_back(&mut self, count: usize) {
+ let current_cursor_column_position = self.cursor_position.column_index;
+ if current_cursor_column_position < count {
+ self.cursor_position.move_to_beginning_of_linewrap();
+ } else {
+ self.cursor_position.move_backwards(count);
+ }
+ }
+ pub fn move_cursor_to_beginning_of_linewrap(&mut self) {
+ self.cursor_position.move_to_beginning_of_linewrap();
+ }
+ pub fn _move_cursor_to_beginning_of_canonical_line(&mut self) {
+ self.cursor_position.move_to_beginning_of_canonical_line();
+ }
+ pub fn move_cursor_backwards(&mut self, count: usize) {
+ self.cursor_position.move_backwards(count);
+ }
+ pub fn move_cursor_up(&mut self, count: usize) {
+ for _ in 0..count {
+ let (current_canonical_line_index, current_line_wrap_index) =
+ self.cursor_position.line_index;
+ if current_line_wrap_index > 0 {
+ // go up one line-wrap
+ self.cursor_position.line_index.1 -= 1;
+ } else if current_canonical_line_index > 0 {
+ // go up to previous canonical line
+ self.cursor_position.line_index.0 -= 1;
+ let current_canonical_line = self
+ .canonical_lines
+ .get(self.cursor_position.line_index.0)
+ .unwrap();
+ let wraps_in_current_line = current_canonical_line.wrapped_fragments.len();
+ // make sure to only go up to the last wrap of the previous line
+ self.cursor_position.line_index.1 = wraps_in_current_line - 1;
+ }
+ }
+ }
+ pub fn move_cursor_down(&mut self, count: usize) {
+ let current_canonical_line = self.cursor_position.line_index.0;
+ let max_count = (self.canonical_lines.len() - 1) - current_canonical_line;
+ let count = std::cmp::min(count, max_count);
+ self.cursor_position.move_down_by_canonical_lines(count);
+ }
+ pub fn change_size(&mut self, columns: usize, lines: usize) {
+ for canonical_line in self.canonical_lines.iter_mut() {
+ canonical_line.change_width(columns);
+ }
+ let cursor_line = self
+ .canonical_lines
+ .get(self.cursor_position.line_index.0)
+ .expect("cursor out of bounds");
+ if cursor_line.wrapped_fragments.len() <= self.cursor_position.line_index.1 {
+ self.cursor_position.line_index.1 = cursor_line.wrapped_fragments.len() - 1;
+ }
+ self.lines_in_view = lines;
+ self.total_columns = columns;
+ }
+ pub fn clear_canonical_line_right_of_cursor(&mut self, style_of_empty_space: CharacterStyles) {
+ let (current_canonical_line_index, current_line_wrap_position) =
+ self.cursor_position.line_index;
+ let current_cursor_column_position = self.cursor_position.column_index;
+ let current_canonical_line = self
+ .canonical_lines
+ .get_mut(current_canonical_line_index)
+ .expect("cursor out of bounds");
+ current_canonical_line.replace_with_empty_chars_after_cursor(
+ current_line_wrap_position,
+ current_cursor_column_position,
+ self.total_columns,
+ style_of_empty_space,
+ );
+ }
+ pub fn clear_canonical_line_left_of_cursor(&mut self, style_of_empty_space: CharacterStyles) {
+ let (current_canonical_line_index, current_line_wrap_position) =
+ self.cursor_position.line_index;
+ let current_cursor_column_position = self.cursor_position.column_index;
+ let current_canonical_line = self
+ .canonical_lines
+ .get_mut(current_canonical_line_index)
+ .expect("cursor out of bounds");
+ current_canonical_line.replace_with_empty_chars_before_cursor(
+ current_line_wrap_position,
+ current_cursor_column_position,
+ style_of_empty_space,
+ );
+ }
+ pub fn clear_all_after_cursor(&mut self) {
+ let (current_canonical_line_index, current_line_wrap_position) =
+ self.cursor_position.line_index;
+ let current_cursor_column_position = self.cursor_position.column_index;
+ let current_canonical_line = self
+ .canonical_lines
+ .get_mut(current_canonical_line_index)
+ .expect("cursor out of bounds");
+ current_canonical_line
+ .clear_after(current_line_wrap_position, current_cursor_column_position);
+ self.canonical_lines
+ .truncate(current_canonical_line_index + 1);
+ }
+ pub fn replace_with_empty_chars(
+ &mut self,
+ count: usize,
+ style_of_empty_space: CharacterStyles,
+ ) {
+ let (current_canonical_line_index, current_line_wrap_position) =
+ self.cursor_position.line_index;
+ let current_cursor_column_position = self.cursor_position.column_index;
+ let current_canonical_line = self
+ .canonical_lines
+ .get_mut(current_canonical_line_index)
+ .expect("cursor out of bounds");
+ current_canonical_line.replace_with_empty_chars(
+ current_line_wrap_position,
+ current_cursor_column_position,
+ count,
+ style_of_empty_space,
+ );
+ }
+ pub fn erase_characters(&mut self, count: usize, style_of_empty_space: CharacterStyles) {
+ let (current_canonical_line_index, current_line_wrap_position) =
+ self.cursor_position.line_index;
+ let current_cursor_column_position = self.cursor_position.column_index;
+ let current_canonical_line = self
+ .canonical_lines
+ .get_mut(current_canonical_line_index)
+ .expect("cursor out of bounds");
+ current_canonical_line.erase_chars(
+ current_line_wrap_position,
+ current_cursor_column_position,
+ count,
+ style_of_empty_space,
+ );
+ }
+ pub fn clear_all(&mut self) {
+ self.canonical_lines.clear();
+ self.canonical_lines.push(CanonicalLine::new());
+ self.cursor_position.reset();
+ }
+ pub fn move_cursor_to(&mut self, line: usize, col: usize) {
+ let line_on_screen = if self.canonical_lines.len() > self.lines_in_view {
+ line + (self.canonical_lines.len() - self.lines_in_view)
+ } else {
+ line
+ };
+ if self.canonical_lines.len() > line_on_screen {
+ self.cursor_position.move_to_canonical_line(line_on_screen);
+ } else {
+ for _ in self.canonical_lines.len()..=line_on_screen {
+ self.canonical_lines.push(CanonicalLine::new());
+ }
+ self.cursor_position.move_to_canonical_line(line_on_screen);
+ }
+ let (current_canonical_line_index, current_line_wrap_position) =
+ self.cursor_position.line_index;
+ let current_canonical_line = self
+ .canonical_lines
+ .get_mut(current_canonical_line_index)
+ .expect("cursor out of bounds");
+ let current_fragment = current_canonical_line
+ .wrapped_fragments
+ .get_mut(current_line_wrap_position)
+ .expect("cursor out of bounds");
+
+ for _ in current_fragment.characters.len()..col {
+ current_fragment.characters.push(EMPTY_TERMINAL_CHARACTER);
+ }
+ self.cursor_position.move_to_column(col);
+ }
+ pub fn move_cursor_to_column(&mut self, col: usize) {
+ let current_canonical_line = self.cursor_position.line_index.0;
+ self.move_cursor_to(current_canonical_line, col);
+ }
+ pub fn move_cursor_to_line(&mut self, line: usize) {
+ let current_column = self.cursor_position.column_index;
+ self.move_cursor_to(line, current_column);
+ }
+ pub fn move_current_buffer_to_alternative_buffer(&mut self) {
+ self.alternative_buffer = Some(self.canonical_lines.drain(..).collect());
+ self.alternative_cursor_position = Some(self.cursor_position);
+ self.clear_all();
+ }
+ pub fn override_current_buffer_with_alternative_buffer(&mut self) {
+ if let Some(alternative_buffer) = self.alternative_buffer.as_mut() {
+ self.canonical_lines = alternative_buffer.drain(..).collect();
+ }
+ if let Some(alternative_cursor_position) = self.alternative_cursor_position {
+ self.cursor_position = alternative_cursor_position;
+ }
+ self.alternative_buffer = None;
+ self.alternative_cursor_position = None;
+ }
+ pub fn set_scroll_region(&mut self, top_line: usize, bottom_line: usize) {
+ self.scroll_region = Some((top_line, bottom_line));
+ }
+ pub fn clear_scroll_region(&mut self) {
+ self.scroll_region = None;
+ }
+ fn scroll_region_absolute_indices(&mut self) -> Option<(usize, usize)> {
+ if self.scroll_region.is_none() {
+ return None;
+ };
+ if self.canonical_lines.len() > self.lines_in_view {
+ let absolute_top = self.canonical_lines.len() - 1 - self.lines_in_view;
+ let absolute_bottom = self.canonical_lines.len() - 1;
+ Some((absolute_top, absolute_bottom))
+ } else {
+ Some((self.scroll_region.unwrap().0, self.scroll_region.unwrap().1))
+ }
+ }
+ fn scroll_region_absolute_indices_or_screen_edges(&mut self) -> (usize, usize) {
+ match self.scroll_region {
+ Some(_scroll_region) => self.scroll_region_absolute_indices().unwrap(),
+ None => {
+ // indices of screen top and bottom edge
+ // TODO: what if we don't have enough lines?
+ // let absolute_top = self.canonical_lines.len() - 1 - self.lines_in_view;
+ let absolute_top = self.canonical_lines.len() - self.lines_in_view;
+ let absolute_bottom = self.canonical_lines.len() - 1;
+ (absolute_top, absolute_bottom)
+ }
+ }
+ }
+ pub fn delete_lines_in_scroll_region(&mut self, count: usize) {
+ if let Some((scroll_region_top, scroll_region_bottom)) =
+ self.scroll_region_absolute_indices()
+ {
+ let current_canonical_line_index = self.cursor_position.line_index.0;
+ if current_canonical_line_index >= scroll_region_top
+ && current_canonical_line_index <= scroll_region_bottom
+ {
+ // when deleting lines inside the scroll region, we must make sure it stays the
+ // same size (and that other lines below it aren't shifted inside it)
+ // so we delete the current line(s) and add an empty line at the end of the scroll
+ // region
+ for _ in 0..count {
+ self.canonical_lines.remove(current_canonical_line_index);
+ self.canonical_lines
+ .insert(scroll_region_bottom, CanonicalLine::new());
+ }
+ }
+ }
+ }
+ pub fn add_empty_lines_in_scroll_region(&mut self, count: usize) {
+ if let Some((scroll_region_top, scroll_region_bottom)) =
+ self.scroll_region_absolute_indices()
+ {
+ let current_canonical_line_index = self.cursor_position.line_index.0;
+ if current_canonical_line_index >= scroll_region_top
+ && current_canonical_line_index <= scroll_region_bottom
+ {
+ // when adding empty lines inside the scroll region, we must make sure it stays the
+ // same size and that lines don't "leak" outside of it
+ // so we add an empty line where the cursor currently is, and delete the last line
+ // of the scroll region
+ for _ in 0..count {
+ self.canonical_lines.remove(scroll_region_bottom);
+ self.canonical_lines
+ .insert(current_canonical_line_index, CanonicalLine::new());
+ }
+ }
+ }
+ }
+ pub fn move_cursor_up_in_scroll_region(&mut self, count: usize) {
+ let (scroll_region_top, scroll_region_bottom) =
+ self.scroll_region_absolute_indices_or_screen_edges();
+ for _ in 0..count {
+ let current_canonical_line_index = self.cursor_position.line_index.0;
+ if current_canonical_line_index == scroll_region_top {
+ // if we're at the top line, we create a new line and remove the last line that
+ // would otherwise overflow
+ self.canonical_lines.remove(scroll_region_bottom);
+ self.canonical_lines
+ .insert(current_canonical_line_index, CanonicalLine::new());
+ } else if current_canonical_line_index > scroll_region_top
+ && current_canonical_line_index <= scroll_region_bottom
+ {
+ self.move_cursor_up(count);
+ }
+ }
+ }
+ /// [scroll_up](https://github.com/alacritty/alacritty/blob/ec42b42ce601808070462111c0c28edb0e89babb/alacritty_terminal/src/grid/mod.rs#L261)
+ /// This function takes the first line of the scroll region and moves it to the bottom (count times)
+ pub fn rotate_scroll_region_up(&mut self, count: usize) {
+ if let Some((_, scroll_region_bottom)) = self.scroll_region_absolute_indices() {
+ if self.show_cursor {
+ let scroll_region_bottom_index = scroll_region_bottom - 1;
+ self.cursor_position
+ .move_to_canonical_line(scroll_region_bottom_index);
+
+ let new_empty_lines = vec![CanonicalLine::new(); count];
+ self.canonical_lines.splice(
+ scroll_region_bottom_index..scroll_region_bottom_index + 1,
+ new_empty_lines,
+ );
+
+ self.cursor_position
+ .move_to_canonical_line(scroll_region_bottom_index + count);
+ }
+ }
+ }
+ /// [scroll_down](https://github.com/alacritty/alacritty/blob/ec42b42ce601808070462111c0c28edb0e89babb/alacritty_terminal/src/grid/mod.rs#L221)
+ /// This function takes the last line of the scroll region and moves it to the top (count times)
+ pub fn rotate_scroll_region_down(&mut self, count: usize) {
+ if let Some((scroll_region_top, _)) = self.scroll_region_absolute_indices() {
+ if self.show_cursor {
+ let scroll_region_top_index = scroll_region_top - 1;
+ self.cursor_position
+ .move_to_canonical_line(scroll_region_top_index);
+
+ let new_empty_lines = vec![CanonicalLine::new(); count];
+ self.canonical_lines.splice(
+ scroll_region_top_index..scroll_region_top_index,
+ new_empty_lines,
+ );
+
+ self.cursor_position
+ .move_to_canonical_line(scroll_region_top_index + count);
+ }
+ }
+ }
+ pub fn move_viewport_up(&mut self, count: usize) {
+ if let Some(current_offset) = self.viewport_bottom_offset.as_mut() {
+ *current_offset += count;
+ } else {
+ self.viewport_bottom_offset = Some(count);
+ }
+ }
+ pub fn move_viewport_down(&mut self, count: usize) {
+ if let Some(current_offset) = self.viewport_bottom_offset.as_mut() {
+ if *current_offset > count {
+ *current_offset -= count;
+ } else {
+ self.viewport_bottom_offset = None;
+ }
+ }
+ }
+ pub fn reset_viewport(&mut self) {
+ self.viewport_bottom_offset = None;
+ }
+}
+
+impl Debug for Scroll {
+ fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
+ for line in &self.canonical_lines {
+ writeln!(f, "{:?}", line)?;
+ }
+ Ok(())
+ }
+}
diff --git a/src/panes/terminal_character.rs b/src/panes/terminal_character.rs
new file mode 100644
index 00000000..28d638f9
--- /dev/null
+++ b/src/panes/terminal_character.rs
@@ -0,0 +1,577 @@
+use crate::utils::logging::debug_log_to_file;
+use ::std::fmt::{self, Debug, Display, Formatter};
+
+pub const EMPTY_TERMINAL_CHARACTER: TerminalCharacter = TerminalCharacter {
+ character: ' ',
+ styles: CharacterStyles {
+ foreground: Some(AnsiCode::Reset),
+ background: Some(AnsiCode::Reset),
+ strike: Some(AnsiCode::Reset),
+ hidden: Some(AnsiCode::Reset),
+ reverse: Some(AnsiCode::Reset),
+ slow_blink: Some(AnsiCode::Reset),
+ fast_blink: Some(AnsiCode::Reset),
+ underline: Some(AnsiCode::Reset),
+ bold: Some(AnsiCode::Reset),
+ dim: Some(AnsiCode::Reset),
+ italic: Some(AnsiCode::Reset),
+ },
+};
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub enum AnsiCode {
+ On,
+ Reset,
+ NamedColor(NamedColor),
+ RGBCode((u8, u8, u8)),
+ ColorIndex(u8),
+}
+
+#[derive(Clone, Copy, Eq, PartialEq, Debug)]
+pub enum NamedColor {
+ Black,
+ Red,
+ Green,
+ Yellow,
+ Blue,
+ Magenta,
+ Cyan,
+ White,
+}
+
+impl NamedColor {
+ fn to_foreground_ansi_code(&self) -> String {
+ match self {
+ NamedColor::Black => format!("{}", 30),
+ NamedColor::Red => format!("{}", 31),
+ NamedColor::Green => format!("{}", 32),
+ NamedColor::Yellow => format!("{}", 33),
+ NamedColor::Blue => format!("{}", 34),
+ NamedColor::Magenta => format!("{}", 35),
+ NamedColor::Cyan => format!("{}", 36),
+ NamedColor::White => format!("{}", 37),
+ }
+ }
+ fn to_background_ansi_code(&self) -> String {
+ match self {
+ NamedColor::Black => format!("{}", 40),
+ NamedColor::Red => format!("{}", 41),
+ NamedColor::Green => format!("{}", 42),
+ NamedColor::Yellow => format!("{}", 43),
+ NamedColor::Blue => format!("{}", 44),
+ NamedColor::Magenta => format!("{}", 45),
+ NamedColor::Cyan => format!("{}", 46),
+ NamedColor::White => format!("{}", 47),
+ }
+ }
+}
+
+#[derive(Clone, Copy, Debug)]
+pub struct CharacterStyles {
+ pub foreground: Option<AnsiCode>,
+ pub background: Option<AnsiCode>,
+ pub strike: Option<AnsiCode>,
+ pub hidden: Option<AnsiCode>,
+ pub reverse: Option<AnsiCode>,
+ pub slow_blink: Option<AnsiCode>,
+ pub fast_blink: Option<AnsiCode>,
+ pub underline: Option<AnsiCode>,
+ pub bold: Option<AnsiCode>,
+ pub dim: Option<AnsiCode>,
+ pub italic: Option<AnsiCode>,
+}
+
+impl CharacterStyles {
+ pub fn new() -> Self {
+ CharacterStyles {
+ foreground: None,
+ background: None,
+ strike: None,
+ hidden: None,
+ reverse: None,
+ slow_blink: None,
+ fast_blink: None,
+ underline: None,
+ bold: None,
+ dim: None,
+ italic: None,
+ }
+ }
+ pub fn foreground(mut self, foreground_code: Option<AnsiCode>) -> Self {
+ self.foreground = foreground_code;
+ self
+ }
+ pub fn background(mut self, background_code: Option<AnsiCode>) -> Self {
+ self.background = background_code;
+ self
+ }
+ pub fn bold(mut self, bold_code: Option<AnsiCode>) -> Self {
+ self.bold = bold_code;
+ self
+ }
+ pub fn dim(mut self, dim_code: Option<AnsiCode>) -> Self {
+ self.dim = dim_code;
+ self
+ }
+ pub fn italic(mut self, italic_code: Option<AnsiCode>) -> Self {
+ self.italic = italic_code;
+ self
+ }
+ pub fn underline(mut self, underline_code: Option<AnsiCode>) -> Self {
+ self.underline = underline_code;
+ self
+ }
+ pub fn blink_slow(mut self, slow_blink_code: Option<AnsiCode>) -> Self {
+ self.slow_blink = slow_blink_code;
+ self
+ }
+ pub fn blink_fast(mut self, fast_blink_code: Option<AnsiCode>) -> Self {
+ self.fast_blink = fast_blink_code;
+ self
+ }
+ pub fn reverse(mut self, reverse_code: Option<AnsiCode>) -> Self {
+ self.reverse = reverse_code;
+ self
+ }
+ pub fn hidden(mut self, hidden_code: Option<AnsiCode>) -> Self {
+ self.hidden = hidden_code;
+ self
+ }
+ pub fn strike(mut self, strike_code: Option<AnsiCode>) -> Self {
+ self.strike = strike_code;
+ self
+ }
+ pub fn clear(&mut self) {
+ self.foreground = None;
+ self.background = None;
+ self.strike = None;
+ self.hidden = None;
+ self.reverse = None;
+ self.slow_blink = None;
+ self.fast_blink = None;
+ self.underline = None;
+ self.bold = None;
+ self.dim = None;
+ self.italic = None;
+ }
+ pub fn update_and_return_diff(
+ &mut self,
+ new_styles: &CharacterStyles,
+ ) -> Option<CharacterStyles> {
+ let mut diff: Option<CharacterStyles> = None;
+
+ if new_styles.foreground == Some(AnsiCode::Reset)
+ && new_styles.background == Some(AnsiCode::Reset)
+ && new_styles.strike == Some(AnsiCode::Reset)
+ && new_styles.hidden == Some(AnsiCode::Reset)
+ && new_styles.reverse == Some(AnsiCode::Reset)
+ && new_styles.fast_blink == Some(AnsiCode::Reset)
+ && new_styles.slow_blink == Some(AnsiCode::Reset)
+ && new_styles.underline == Some(AnsiCode::Reset)
+ && new_styles.bold == Some(AnsiCode::Reset)
+ && new_styles.dim == Some(AnsiCode::Reset)
+ && new_styles.italic == Some(AnsiCode::Reset)
+ {
+ self.foreground = Some(AnsiCode::Reset);
+ self.background = Some(AnsiCode::Reset);
+ self.strike = Some(AnsiCode::Reset);
+ self.hidden = Some(AnsiCode::Reset);
+ self.reverse = Some(AnsiCode::Reset);
+ self.fast_blink = Some(AnsiCode::Reset);
+ self.slow_blink = Some(AnsiCode::Reset);
+ self.underline = Some(AnsiCode::Reset);
+ self.bold = Some(AnsiCode::Reset);
+ self.dim = Some(AnsiCode::Reset);
+ self.italic = Some(AnsiCode::Reset);
+ return Some(*new_styles);
+ };
+
+ if self.foreground != new_styles.foreground {
+ if let Some(new_diff) = diff.as_mut() {
+ diff = Some(new_diff.foreground(new_styles.foreground));
+ self.foreground = new_styles.foreground;
+ } else {
+ diff = Some(CharacterStyles::new().foreground(new_styles.foreground));
+ self.foreground = new_styles.foreground;
+ }
+ }
+ if self.background != new_styles.background {
+ if let Some(new_diff) = diff.as_mut() {
+ diff = Some(new_diff.background(new_styles.background));
+ self.background = new_styles.background;
+ } else {
+ diff = Some(CharacterStyles::new().background(new_styles.background));
+ self.background = new_styles.background;
+ }
+ }
+ if self.strike != new_styles.strike {
+ if let Some(new_diff) = diff.as_mut() {
+ diff = Some(new_diff.strike(new_styles.strike));
+ self.strike = new_styles.strike;
+ } else {
+ diff = Some(CharacterStyles::new().strike(new_styles.strike));
+ self.strike = new_styles.strike;
+ }
+ }
+ if self.hidden != new_styles.hidden {
+ if let Some(new_diff) = diff.as_mut() {
+ diff = Some(new_diff.hidden(new_styles.hidden));
+ self.hidden = new_styles.hidden;
+ } else {
+ diff = Some(CharacterStyles::new().hidden(new_styles.hidden));
+ self.hidden = new_styles.hidden;
+ }
+ }
+ if self.reverse != new_styles.reverse {
+ if let Some(new_diff) = diff.as_mut() {
+ diff = Some(new_diff.reverse(new_styles.reverse));
+ self.reverse = new_styles.reverse;
+ } else {
+ diff = Some(CharacterStyles::new().reverse(new_styles.reverse));
+ self.reverse = new_styles.reverse;
+ }
+ }
+ if self.slow_blink != new_styles.slow_blink {
+ if let Some(new_diff) = diff.as_mut() {
+ diff = Some(new_diff.blink_slow(new_styles.slow_blink));
+ self.slow_blink = new_styles.slow_blink;
+ } else {
+ diff = Some(CharacterStyles::new().blink_slow(new_styles.slow_blink));
+ self.slow_blink = new_styles.slow_blink;
+ }
+ }
+ if self.fast_blink != new_styles.fast_blink {
+ if let Some(new_diff) = diff.as_mut() {
+ diff = Some(new_diff.blink_fast(new_styles.fast_blink));
+ self.fast_blink = new_styles.fast_blink;
+ } else {
+ diff = Some(CharacterStyles::new().blink_fast(new_styles.fast_blink));
+ self.fast_blink = new_styles.fast_blink;
+ }
+ }
+ if self.underline != new_styles.underline {
+ if let Some(new_diff) = diff.as_mut() {
+ diff = Some(new_diff.underline(new_styles.underline));
+ self.underline = new_styles.underline;
+ } else {
+ diff = Some(CharacterStyles::new().underline(new_styles.underline));
+ self.underline = new_styles.underline;
+ }
+ }
+ if self.bold != new_styles.bold {
+ if let Some(new_diff) = diff.as_mut() {
+ diff = Some(new_diff.bold(new_styles.bold));
+ self.bold = new_styles.bold;
+ } else {
+ diff = Some(CharacterStyles::new().bold(new_styles.bold));
+ self.bold = new_styles.bold;
+ }
+ }
+ if self.dim != new_styles.dim {
+ if let Some(new_diff) = diff.as_mut() {
+ diff = Some(new_diff.dim(new_styles.dim));
+ self.dim = new_styles.dim;
+ } else {
+ diff = Some(CharacterStyles::new().dim(new_styles.dim));
+ self.dim = new_styles.dim;
+ }
+ }
+ if self.italic != new_styles.italic {
+ if let Some(new_diff) = diff.as_mut() {
+ diff = Some(new_diff.italic(new_styles.italic));
+ self.italic = new_styles.italic;
+ } else {
+ diff = Some(CharacterStyles::new().italic(new_styles.italic));
+ self.italic = new_styles.italic;
+ }
+ }
+ diff
+ }
+ pub fn reset_all(&mut self) {
+ self.foreground = Some(AnsiCode::Reset);
+ self.background = Some(AnsiCode::Reset);
+ self.bold = Some(AnsiCode::Reset);
+ self.dim = Some(AnsiCode::Reset);
+ self.italic = Some(AnsiCode::Reset);
+ self.underline = Some(AnsiCode::Reset);
+ self.slow_blink = Some(AnsiCode::Reset);
+ self.fast_blink = Some(AnsiCode::Reset);
+ self.reverse = Some(AnsiCode::Reset);
+ self.hidden = Some(AnsiCode::Reset);
+ self.strike = Some(AnsiCode::Reset);
+ }
+ pub fn add_style_from_ansi_params(&mut self, ansi_params: &[i64]) {
+ let mut params_used = 1; // if there's a parameter, it is always used
+ match ansi_params {
+ [] | [0, ..] => self.reset_all(),
+ [1, ..] => *self = self.bold(Some(AnsiCode::On)),
+ [2, ..] => *self = self.dim(Some(AnsiCode::On)),
+ [3, ..] => *self = self.italic(Some(AnsiCode::On)),
+ [4, ..] => *self = self.underline(Some(AnsiCode::On)),
+ [5, ..] => *self = self.blink_slow(Some(AnsiCode::On)),
+ [6, ..] => *self = self.blink_fast(Some(AnsiCode::On)),
+ [7, ..] => *self = self.reverse(Some(AnsiCode::On)),
+ [8, ..] => *self = self.hidden(Some(AnsiCode::On)),
+ [9, ..] => *self = self.strike(Some(AnsiCode::On)),
+ [21, ..] => *self = self.bold(Some(AnsiCode::Reset)),
+ [22, ..] => {
+ *self = self.bold(Some(AnsiCode::Reset));
+ *self = self.dim(Some(AnsiCode::Reset));
+ }
+ [23, ..] => *self = self.italic(Some(AnsiCode::Reset)),
+ [24, ..] => *self = self.underline(Some(AnsiCode::Reset)),
+ [25, ..] => {
+ *self = self.blink_slow(Some(AnsiCode::Reset));
+ *self = self.blink_fast(Some(AnsiCode::Reset));
+ }
+ [27, ..] => *self = self.reverse(Some(AnsiCode::Reset)),
+ [28, ..] => *self = self.hidden(Some(AnsiCode::Reset)),
+ [29, ..] => *self = self.strike(Some(AnsiCode::Reset)),
+ [30, ..] => *self = self.foreground(Some(AnsiCode::NamedColor(NamedColor::Black))),
+ [31, ..] => *self = self.foreground(Some(AnsiCode::NamedColor(NamedColor::Red))),
+ [32, ..] => *self = self.foreground(Some(AnsiCode::NamedColor(NamedColor::Green))),
+ [33, ..] => *self = self.foreground(Some(AnsiCode::NamedColor(NamedColor::Yellow))),
+ [34, ..] => *self = self.foreground(Some(AnsiCode::NamedColor(NamedColor::Blue))),
+ [35, ..] => *self = self.foreground(Some(AnsiCode::NamedColor(NamedColor::Magenta))),
+ [36, ..] => *self = self.foreground(Some(AnsiCode::NamedColor(NamedColor::Cyan))),
+ [37, ..] => *self = self.foreground(Some(AnsiCode::NamedColor(NamedColor::White))),
+ [38, 2, ..] => {
+ let ansi_code = AnsiCode::RGBCode((
+ *ansi_params.get(2).unwrap() as u8,
+ *ansi_params.get(3).unwrap() as u8,
+ *ansi_params.get(4).unwrap() as u8,
+ ));
+ *self = self.foreground(Some(ansi_code));
+ params_used += 4 // one for the indicator (2 in this case) and three for the rgb code
+ }
+ [38, 5, ..] => {
+ let ansi_code = AnsiCode::ColorIndex(*ansi_params.get(2).unwrap() as u8);
+ *self = self.foreground(Some(ansi_code));
+ params_used += 2 // one for the indicator (5 in this case) and one for the color index
+ }
+ [38, ..] => {
+ // this is a bug
+ // it means we got a color encoding we don't know how to handle (or is invalid)
+ params_used += 1; // even if it's a bug, let's not create an endless loop, eh?
+ }
+ [39, ..] => *self = self.foreground(Some(AnsiCode::Reset)),
+ [40, ..] => *self = self.background(Some(AnsiCode::NamedColor(NamedColor::Black))),
+ [41, ..] => *self = self.background(Some(AnsiCode::NamedColor(NamedColor::Red))),
+ [42, ..] => *self = self.background(Some(AnsiCode::NamedColor(NamedColor::Green))),
+ [43, ..] => *self = self.background(Some(AnsiCode::NamedColor(NamedColor::Yellow))),
+ [44, ..] => *self = self.background(Some(AnsiCode::NamedColor(NamedColor::Blue))),
+ [45, ..] => *self = self.background(Some(AnsiCode::NamedColor(NamedColor::Magenta))),
+ [46, ..] => *self = self.background(Some(AnsiCode::NamedColor(NamedColor::Cyan))),
+ [47, ..] => *self = self.background(Some(AnsiCode::NamedColor(NamedColor::White))),
+ [48, 2, ..] => {
+ let ansi_code = AnsiCode::RGBCode((
+ *ansi_params.get(2).unwrap() as u8,
+ *ansi_params.get(3).unwrap() as u8,
+ *ansi_params.get(4).unwrap() as u8,
+ ));
+ *self = self.background(Some(ansi_code));
+ params_used += 4 // one for the indicator (2 in this case) and three for the rgb code
+ }
+ [48, 5, ..] => {
+ let ansi_code = AnsiCode::ColorIndex(*ansi_params.get(2).unwrap() as u8);
+ *self = self.background(Some(ansi_code));
+ params_used += 2 // one for the indicator (5 in this case) and one for the color index
+ }
+ [48, ..] => {
+ // this is a bug
+ // it means we got a color encoding we don't know how to handle (or is invalid)
+ params_used += 1; // even if it's a bug, let's not create an endless loop, eh?
+ }
+ [49, ..] => *self = self.background(Some(AnsiCode::Reset)),
+ _ => {
+ // if this happens, it's a bug
+ let _ = debug_log_to_file(format!("unhandled csi m code {:?}", ansi_params));
+ return;
+ }
+ }
+ if let Some(next_params) = ansi_params.get(params_used..) {
+ if next_params.len() > 0 {
+ self.add_style_from_ansi_params(next_params);
+ }
+ }
+ }
+}
+
+impl Display for CharacterStyles {
+ fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
+ if self.foreground == Some(AnsiCode::Reset)
+ && self.background == Some(AnsiCode::Reset)
+ && self.strike == Some(AnsiCode::Reset)
+ && self.hidden == Some(AnsiCode::Reset)
+ && self.reverse == Some(AnsiCode::Reset)
+ && self.fast_blink == Some(AnsiCode::Reset)
+ && self.slow_blink == Some(AnsiCode::Reset)
+ && self.underline == Some(AnsiCode::Reset)
+ && self.bold == Some(AnsiCode::Reset)
+ && self.dim == Some(AnsiCode::Reset)
+ && self.italic == Some(AnsiCode::Reset)
+ {
+ write!(f, "\u{1b}[m")?; // reset all
+ return Ok(());
+ }
+ if let Some(ansi_code) = self.foreground {
+ match ansi_code {
+ AnsiCode::RGBCode((r, g, b)) => {
+ write!(f, "\u{1b}[38;2;{};{};{}m", r, g, b)?;
+ }
+ AnsiCode::ColorIndex(color_index) => {
+ write!(f, "\u{1b}[38;5;{}m", color_index)?;
+ }
+ AnsiCode::Reset => {
+ write!(f, "\u{1b}[39m")?;
+ }
+ AnsiCode::NamedColor(named_color) => {
+ write!(f, "\u{1b}[{}m", named_color.to_foreground_ansi_code())?;
+ }
+ _ => {}
+ }
+ };
+ if let Some(ansi_code) = self.background {
+ match ansi_code {
+ AnsiCode::RGBCode((r, g, b)) => {
+ write!(f, "\u{1b}[48;2;{};{};{}m", r, g, b)?;
+ }
+ AnsiCode::ColorIndex(color_index) => {
+ write!(f, "\u{1b}[48;5;{}m", color_index)?;
+ }
+ AnsiCode::Reset => {
+ write!(f, "\u{1b}[49m")?;
+ }
+ AnsiCode::NamedColor(named_color) => {
+ write!(f, "\u{1b}[{}m", named_color.to_background_ansi_code())?;
+ }
+ _ => {}
+ }
+ }
+ if let Some(ansi_code) = self.strike {
+ match ansi_code {
+ AnsiCode::On => {
+ write!(f, "\u{1b}[9m")?;
+ }
+ AnsiCode::Reset => {
+ write!(f, "\u{1b}[29m")?;
+ }
+ _ => {}
+ }
+ }
+ if let Some(ansi_code) = self.hidden {
+ match ansi_code {
+ AnsiCode::On => {
+ write!(f, "\u{1b}[8m")?;
+ }
+ AnsiCode::Reset => {
+ write!(f, "\u{1b}[28m")?;
+ }
+ _ => {}
+ }
+ }
+ if let Some(ansi_code) = self.reverse {
+ match ansi_code {
+ AnsiCode::On => {
+ write!(f, "\u{1b}[7m")?;
+ }
+ AnsiCode::Reset => {
+ write!(f, "\u{1b}[27m")?;
+ }
+ _ => {}
+ }
+ }
+ if let Some(ansi_code) = self.fast_blink {
+ match ansi_code {
+ AnsiCode::On => {
+ write!(f, "\u{1b}[6m")?;
+ }
+ AnsiCode::Reset => {
+ write!(f, "\u{1b}[25m")?;
+ }
+ _ => {}
+ }
+ }
+ if let Some(ansi_code) = self.slow_blink {
+ match ansi_code {
+ AnsiCode::On => {
+ write!(f, "\u{1b}[5m")?;
+ }
+ AnsiCode::Reset => {
+ write!(f, "\u{1b}[25m")?;
+ }
+ _ => {}
+ }
+ }
+ if let Some(ansi_code) = self.bold {
+ match ansi_code {
+ AnsiCode::On => {
+ write!(f, "\u{1b}[1m")?;
+ }
+ AnsiCode::Reset => {
+ write!(f, "\u{1b}[22m\u{1b}[24m")?;
+ // TODO: this cancels bold + underline, if this behaviour is indeed correct, we
+ // need to properly handle it in the struct methods etc like dim
+ }
+ _ => {}
+ }
+ }
+ // notice the order is important here, bold must be before underline
+ // because the bold reset also resets underline, and would override it
+ // otherwise
+ if let Some(ansi_code) = self.underline {
+ match ansi_code {
+ AnsiCode::On => {
+ write!(f, "\u{1b}[4m")?;
+ }
+ AnsiCode::Reset => {
+ write!(f, "\u{1b}[24m")?;
+ }
+ _ => {}
+ }
+ }
+ if let Some(ansi_code) = self.dim {
+ match ansi_code {
+ AnsiCode::On => {
+ write!(f, "\u{1b}[2m")?;
+ }
+ AnsiCode::Reset => {
+ if let Some(bold) = self.bold {
+ // we only reset dim if both dim and bold should be reset
+ match bold {
+ AnsiCode::Reset => {
+ write!(f, "\u{1b}[22m")?;
+ }
+ _ => {}
+ }
+ }
+ }
+ _ => {}
+ }
+ }
+ if let Some(ansi_code) = self.italic {
+ match ansi_code {
+ AnsiCode::On => {
+ write!(f, "\u{1b}[3m")?;
+ }
+ AnsiCode::Reset => {
+ write!(f, "\u{1b}[23m")?;
+ }
+ _ => {}
+ }
+ };
+ Ok(())
+ }
+}
+
+#[derive(Clone, Copy)]
+pub struct TerminalCharacter {
+ pub character: char,
+ pub styles: CharacterStyles,
+}
+
+impl ::std::fmt::Debug for TerminalCharacter {
+ fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
+ write!(f, "{}", self.character)
+ }
+}
diff --git a/src/panes/terminal_pane.rs b/src/panes/terminal_pane.rs
new file mode 100644
index 00000000..70eb216a
--- /dev/null
+++ b/src/panes/terminal_pane.rs
@@ -0,0 +1,617 @@
+#![allow(clippy::clippy::if_same_then_else)]
+
+use crate::tab::Pane;
+use ::nix::pty::Winsize;
+use ::std::os::unix::io::RawFd;
+use ::vte::Perform;
+
+use crate::panes::terminal_character::{CharacterStyles, TerminalCharacter};
+use crate::panes::Scroll;
+use crate::utils::logging::debug_log_to_file;
+use crate::VteEvent;
+
+#[derive(PartialEq, Eq, Ord, PartialOrd, Hash, Clone, Copy, Debug)]
+pub enum PaneId {
+ Terminal(RawFd),
+ Plugin(u32), // FIXME: Drop the trait object, make this a wrapper for the struct?
+ BuiltIn(u32),
+}
+
+#[derive(Clone, Copy, Debug)]
+pub struct PositionAndSize {
+ pub x: usize,
+ pub y: usize,
+ pub rows: usize,
+ pub columns: usize,
+}
+
+impl PositionAndSize {
+ pub fn from(winsize: Winsize) -> PositionAndSize {
+ PositionAndSize {
+ columns: winsize.ws_col as usize,
+ rows: winsize.ws_row as usize,
+ x: winsize.ws_xpixel as usize,
+ y: winsize.ws_ypixel as usize,
+ }
+ }
+}
+
+#[derive(Debug)]
+pub struct TerminalPane {
+ pub pid: RawFd,
+ pub scroll: Scroll,
+ pub should_render: bool,
+ pub position_and_size: PositionAndSize,
+ pub position_and_size_override: Option<PositionAndSize>,
+ pub cursor_key_mode: bool, // DECCKM - when set, cursor keys should send ANSI direction codes (eg. "OD") instead of the arrow keys (eg. "")
+ pending_styles: CharacterStyles,
+}
+
+impl Pane for TerminalPane {
+ fn x(&self) -> usize {
+ self.get_x()
+ }
+ fn y(&self) -> usize {
+ self.get_y()
+ }
+ fn rows(&self) -> usize {
+ self.get_rows()
+ }
+ fn columns(&self) -> usize {
+ self.get_columns()
+ }
+ fn reset_size_and_position_override(&mut self) {
+ self.position_and_size_override = None;
+ self.reflow_lines();
+ self.mark_for_rerender();
+ }
+ fn change_pos_and_size(&mut self, position_and_size: &PositionAndSize) {
+ self.position_and_size.columns = position_and_size.columns;
+ self.position_and_size.rows = position_and_size.rows;
+ self.reflow_lines();
+ self.mark_for_rerender();
+ }
+ fn override_size_and_position(&mut self, x: usize, y: usize, size: &PositionAndSize) {
+ let position_and_size_override = PositionAndSize {
+ x,
+ y,
+ rows: size.rows,
+ columns: size.columns,
+ };
+ self.position_and_size_override = Some(position_and_size_override);
+ self.reflow_lines();
+ self.mark_for_rerender();
+ }
+ fn handle_event(&mut self, event: VteEvent) {
+ match event {
+ VteEvent::Print(c) => {
+ self.print(c);
+ self.mark_for_rerender();
+ }
+ VteEvent::Execute(byte) => {
+ self.execute(byte);
+ }
+ VteEvent::Hook(params, intermediates, ignore, c) => {
+ self.hook(&params, &intermediates, ignore, c);
+ }
+ VteEvent::Put(byte) => {
+ self.put(byte);
+ }
+ VteEvent::Unhook => {
+ self.unhook();
+ }
+ VteEvent::OscDispatch(params, bell_terminated) => {
+ let params: Vec<&[u8]> = params.iter().map(|p| &p[..]).collect();
+ self.osc_dispatch(&params[..], bell_terminated);
+ }
+ VteEvent::CsiDispatch(params, intermediates, ignore, c) => {
+ self.csi_dispatch(&params, &intermediates, ignore, c);
+ }
+ VteEvent::EscDispatch(intermediates, ignore, byte) => {
+ self.esc_dispatch(&intermediates, ignore, byte);
+ }
+ }
+ }
+ fn cursor_coordinates(&self) -> Option<(usize, usize)> {
+ // (x, y)
+ self.scroll.cursor_coordinates_on_screen()
+ }
+ fn adjust_input_to_terminal(&self, input_bytes: Vec<u8>) -> Vec<u8> {
+ // there are some cases in which the terminal state means that input sent to it
+ // needs to be adjusted.
+ // here we match against those cases - if need be, we adjust the input and if not
+ // we send back the original input
+ match input_bytes.as_slice() {
+ [27, 91, 68] => {
+ // left arrow
+ if self.cursor_key_mode {
+ // please note that in the line below, there is an ANSI escape code (27) at the beginning of the string,
+ // some editors will not show this
+ return "OD".as_bytes().to_vec();
+ }
+ }
+ [27, 91, 67] => {
+ // right arrow
+ if self.cursor_key_mode {
+ // please note that in the line below, there is an ANSI escape code (27) at the beginning of the string,
+ // some editors will not show this
+ return "OC".as_bytes().to_vec();
+ }
+ }
+ [27, 91, 65] => {
+ // up arrow
+ if self.cursor_key_mode {
+ // please note that in the line below, there is an ANSI escape code (27) at the beginning of the string,
+ // some editors will not show this
+ return "OA".as_bytes().to_vec();
+ }
+ }
+ [27, 91, 66] => {
+ // down arrow
+ if self.cursor_key_mode {
+ // please note that in the line below, there is an ANSI escape code (27) at the beginning of the string,
+ // some editors will not show this
+ return "OB".as_bytes().to_vec();
+ }
+ }
+ _ => {}
+ };
+ input_bytes
+ }
+
+ fn position_and_size_override(&self) -> Option<PositionAndSize> {
+ self.position_and_size_override
+ }
+ fn should_render(&self) -> bool {
+ self.should_render
+ }
+ fn set_should_render(&mut self, should_render: bool) {
+ self.should_render = should_render;
+ }
+ fn render(&mut self) -> Option<String> {
+ // if self.should_render {
+ if true {
+ // while checking should_render rather than rendering each pane every time
+ // is more performant, it causes some problems when the pane to the left should be
+ // rendered and has wide characters (eg. Chinese characters or emoji)
+ // as a (hopefully) temporary hack, we render all panes until we find a better solution
+ let mut vte_output = String::new();
+ let buffer_lines = &self.read_buffer_as_lines();
+ let display_cols = self.get_columns();
+ let mut character_styles = CharacterStyles::new();
+ for (row, line) in buffer_lines.iter().enumerate() {
+ let x = self.get_x();
+ let y = self.get_y();
+ vte_output = format!("{}\u{1b}[{};{}H\u{1b}[m", vte_output, y + row + 1, x + 1); // goto row/col and reset styles
+ for (col, t_character) in line.iter().enumerate() {
+ if col < display_cols {
+ // in some cases (eg. while resizing) some characters will spill over
+ // before they are corrected by the shell (for the prompt) or by reflowing
+ // lines
+ if let Some(new_styles) =
+ character_styles.update_and_return_diff(&t_character.styles)
+ {
+ // the terminal keeps the previous styles as long as we're in the same
+ // line, so we only want to update the new styles here (this also
+ // includes resetting previous styles as needed)
+ vte_output = format!("{}{}", vte_output, new_styles);
+ }
+ vte_output.push(t_character.character);
+ }
+ }
+ character_styles.clear();
+ }
+ self.mark_for_rerender();
+ Some(vte_output)
+ } else {
+ None
+ }
+ }
+ fn pid(&self) -> PaneId {
+ PaneId::Terminal(self.pid)
+ }
+ fn reduce_height_down(&mut self, count: usize) {
+ self.position_and_size.y += count;
+ self.position_and_size.rows -= count;
+ self.reflow_lines();
+ self.mark_for_rerender();
+ }
+ fn increase_height_down(&mut self, count: usize) {
+ self.position_and_size.rows += count;
+ self.reflow_lines();
+ self.mark_for_rerender();
+ }
+ fn increase_height_up(&mut self, count: usize) {
+ self.position_and_size.y -= count;
+ self.position_and_size.rows += count;
+ self.reflow_lines();
+ self.mark_for_rerender();
+ }
+ fn reduce_height_up(&mut self, count: usize) {
+ self.position_and_size.rows -= count;
+ self.reflow_lines();
+ self.mark_for_rerender();
+ }
+ fn reduce_width_right(&mut self, count: usize) {
+ self.position_and_size.x += count;
+ self.position_and_size.columns -= count;
+ self.reflow_lines();
+ self.mark_for_rerender();
+ }
+ fn reduce_width_left(&mut self, count: usize) {
+ self.position_and_size.columns -= count;
+ self.reflow_lines();
+ self.mark_for_rerender();
+ }
+ fn increase_width_left(&mut self, count: usize) {
+ self.position_and_size.x -= count;
+ self.position_and_size.columns += count;
+ self.reflow_lines();
+ self.mark_for_rerender();
+ }
+ fn increase_width_right(&mut self, count: usize) {
+ self.position_and_size.columns += count;
+ self.reflow_lines();
+ self.mark_for_rerender();
+ }
+ fn scroll_up(&mut self, count: usize) {
+ self.scroll.move_viewport_up(count);
+ self.mark_for_rerender();
+ }
+ fn scroll_down(&mut self, count: usize) {
+ self.scroll.move_viewport_down(count);
+ self.mark_for_rerender();
+ }
+ fn clear_scroll(&mut self) {
+ self.scroll.reset_viewport();
+ self.mark_for_rerender();
+ }
+}
+
+impl TerminalPane {
+ pub fn new(pid: RawFd, position_and_size: PositionAndSize) -> TerminalPane {
+ let scroll = Scroll::new(position_and_size.columns, position_and_size.rows);
+ let pending_styles = CharacterStyles::new();
+
+ TerminalPane {
+ pid,
+ scroll,
+ should_render: true,
+ pending_styles,
+ position_and_size,
+ position_and_size_override: None,
+ cursor_key_mode: false,
+ }
+ }
+ pub fn mark_for_rerender(&mut self) {
+ self.should_render = true;
+ }
+ pub fn get_x(&self) -> usize {
+ match self.position_and_size_override {
+ Some(position_and_size_override) => position_and_size_override.x,
+ None => self.position_and_size.x as usize,
+ }
+ }
+ pub fn get_y(&self) -> usize {
+ match self.position_and_size_override {
+ Some(position_and_size_override) => position_and_size_override.y,
+ None => self.position_and_size.y as usize,
+ }
+ }
+ pub fn get_columns(&self) -> usize {
+ match &self.position_and_size_override.as_ref() {
+ Some(position_and_size_override) => position_and_size_override.columns,
+ None => self.position_and_size.columns as usize,
+ }
+ }
+ pub fn get_rows(&self) -> usize {
+ match &self.position_and_size_override.as_ref() {
+ Some(position_and_size_override) => position_and_size_override.rows,
+ None => self.position_and_size.rows as usize,
+ }
+ }
+ fn reflow_lines(&mut self) {
+ let rows = self.get_rows();
+ let columns = self.get_columns();
+ self.scroll.change_size(columns, rows);
+ }
+
+ pub fn read_buffer_as_lines(&self) -> Vec<Vec<TerminalCharacter>> {
+ self.scroll.as_character_lines()
+ }
+ #[cfg(test)]
+ pub fn cursor_coordinates(&self) -> Option<(usize, usize)> {
+ // (x, y)
+ self.scroll.cursor_coordinates_on_screen()
+ }
+ pub fn rotate_scroll_region_up(&mut self, count: usize) {
+ self.scroll.rotate_scroll_region_up(count);
+ self.mark_for_rerender();
+ }
+ pub fn rotate_scroll_region_down(&mut self, count: usize) {
+ self.scroll.rotate_scroll_region_down(count);
+ self.mark_for_rerender();
+ }
+ fn add_newline(&mut self) {
+ self.scroll.add_canonical_line();
+ // self.reset_all_ansi_codes(); // TODO: find out if we should be resetting here or not
+ self.mark_for_rerender();
+ }
+ fn move_to_beginning_of_line(&mut self) {
+ self.scroll.move_cursor_to_beginning_of_linewrap();
+ }
+ fn move_cursor_backwards(&mut self, count: usize) {
+ self.scroll.move_cursor_backwards(count);
+ }
+ fn _reset_all_ansi_codes(&mut self) {
+ self.pending_styles.clear();
+ }
+}
+
+impl vte::Perform for TerminalPane {
+ fn print(&mut self, c: char) {
+ // apparently, building TerminalCharacter like this without a "new" method
+ // is a little faster
+ let terminal_character = TerminalCharacter {
+ character: c,
+ styles: self.pending_styles,
+ };
+ self.scroll.add_character(terminal_character);
+ }
+
+ fn execute(&mut self, byte: u8) {
+ match byte {
+ 8 => {
+ // backspace
+ self.move_cursor_backwards(1);
+ }
+ 9 => {
+ // tab
+ let terminal_tab_character = TerminalCharacter {
+ character: '\t',
+ styles: self.pending_styles,
+ };
+ // TODO: handle better with line wrapping
+ self.scroll.add_character(terminal_tab_character);
+ }
+ 10 => {
+ // 0a, newline
+ self.add_newline();
+ }
+ 13 => {
+ // 0d, carriage return
+ self.move_to_beginning_of_line();
+ }
+ _ => {}
+ }
+ }
+
+ fn hook(&mut self, _params: &[i64], _intermediates: &[u8], _ignore: bool, _c: char) {
+ // TBD
+ }
+
+ fn put(&mut self, _byte: u8) {
+ // TBD
+ }
+
+ fn unhook(&mut self) {
+ // TBD
+ }
+
+ fn osc_dispatch(&mut self, _params: &[&[u8]], _bell_terminated: bool) {
+ // TBD
+ }
+
+ fn csi_dispatch(&mut self, params: &[i64], _intermediates: &[u8], _ignore: bool, c: char) {
+ if c == 'm' {
+ self.pending_styles.add_style_from_ansi_params(params);
+ } else if c == 'C' {
+ // move cursor forward
+ let move_by = if params[0] == 0 {
+ 1
+ } else {
+ params[0] as usize
+ };
+ self.scroll.move_cursor_forward(move_by);
+ } else if c == 'K' {
+ // clear line (0 => right, 1 => left, 2 => all)
+ if params[0] == 0 {
+ self.scroll
+ .clear_canonical_line_right_of_cursor(self.pending_styles);
+ } else if params[0] == 1 {
+ self.scroll
+ .clear_canonical_line_left_of_cursor(self.pending_styles);
+ }
+ // TODO: implement 2
+ } else if c == 'J' {
+ // clear all (0 => below, 1 => above, 2 => all, 3 => saved)
+ if params[0] == 0 {
+ self.scroll.clear_all_after_cursor();
+ } else if params[0] == 2 {
+ self.scroll.clear_all();
+ }
+ // TODO: implement 1
+ } else if c == 'H' {
+ // goto row/col
+ // we subtract 1 from the row/column because these are 1 indexed
+ // (except when they are 0, in which case they should be 1
+ // don't look at me, I don't make the rules)
+ let (row, col) = if params.len() == 1 {
+ if params[0] == 0 {
+ (0, params[0] as usize)
+ } else {
+ (params[0] as usize - 1, params[0] as usize)
+ }
+ } else {
+ if params[0] == 0 {
+ (0, params[1] as usize - 1)
+ } else {
+ (params[0] as usize - 1, params[1] as usize - 1)
+ }
+ };
+ self.scroll.move_cursor_to(row, col);
+ } else if c == 'A' {
+ // move cursor up until edge of screen
+ let move_up_count = if params[0] == 0 { 1 } else { params[0] };
+ self.scroll.move_cursor_up(move_up_count as usize);
+ } else if c == 'B' {
+ // move cursor down until edge of screen
+ let move_down_count = if params[0] == 0 { 1 } else { params[0] };
+ self.scroll.move_cursor_down(move_down_count as usize);
+ } else if c == 'D' {
+ let move_back_count = if params[0] == 0 {
+ 1
+ } else {
+ params[0] as usize
+ };
+ self.scroll.move_cursor_back(move_back_count);
+ } else if c == 'l' {
+ let first_intermediate_is_questionmark = match _intermediates.get(0) {
+ Some(b'?') => true,
+ None => false,
+ _ => false,
+ };
+ if first_intermediate_is_questionmark {
+ match params.get(0) {
+ Some(&1049) => {
+ self.scroll
+ .override_current_buffer_with_alternative_buffer();
+ }
+ Some(&25) => {
+ self.scroll.hide_cursor();
+ self.mark_for_rerender();
+ }
+ Some(&1) => {
+ self.cursor_key_mode = false;
+ }
+ _ => {}
+ };
+ }
+ } else if c == 'h' {
+ let first_intermediate_is_questionmark = match _intermediates.get(0) {
+ Some(b'?') => true,
+ None => false,
+ _ => false,
+ };
+ if first_intermediate_is_questionmark {
+ match params.get(0) {
+ Some(&25) => {
+ self.scroll.show_cursor();
+ self.mark_for_rerender();
+ }
+ Some(&1049) => {
+ self.scroll.move_current_buffer_to_alternative_buffer();
+ }
+ Some(&1) => {
+ self.cursor_key_mode = true;
+ }
+ _ => {}
+ };
+ }
+ } else if c == 'r' {
+ if params.len() > 1 {
+ // minus 1 because these are 1 indexed
+ let top_line_index = params[0] as usize - 1;
+ let bottom_line_index = params[1] as usize - 1;
+ self.scroll
+ .set_scroll_region(top_line_index, bottom_line_index);
+ self.scroll.show_cursor();
+ } else {
+ self.scroll.clear_scroll_region();
+ }
+ } 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 = if params[0] == 0 {
+ 1
+ } else {
+ params[0] as usize
+ };
+ self.scroll
+ .delete_lines_in_scroll_region(line_count_to_delete);
+ } else if c == 'L' {
+ // insert blank lines if inside scroll region
+ let line_count_to_add = if params[0] == 0 {
+ 1
+ } else {
+ params[0] as usize
+ };
+ self.scroll
+ .add_empty_lines_in_scroll_region(line_count_to_add);
+ } else if c == 'q' {
+ // ignore for now to run on mac
+ } else if c == 'G' {
+ let column = if params[0] == 0 {
+ 0
+ } else {
+ // params[0] as usize
+ params[0] as usize - 1
+ };
+ self.scroll.move_cursor_to_column(column);
+ } else if c == 'd' {
+ // goto line
+ let line = if params[0] == 0 {
+ 1
+ } else {
+ // minus 1 because this is 1 indexed
+ params[0] as usize - 1
+ };
+ self.scroll.move_cursor_to_line(line);
+ } else if c == 'P' {
+ // erase characters
+ let count = if params[0] == 0 {
+ 1
+ } else {
+ params[0] as usize
+ };
+ self.scroll.erase_characters(count, self.pending_styles);
+ } else if c == 'X' {
+ // erase characters and replace with empty characters of current style
+ let count = if params[0] == 0 {
+ 1
+ } else {
+ params[0] as usize
+ };
+ self.scroll
+ .replace_with_empty_chars(count, self.pending_styles);
+ } else if c == 'T' {
+ /*
+ * 124 54 T SD
+ * Scroll down, new lines inserted at top of screen
+ * [4T = Scroll down 4, bring previous lines back into view
+ */
+ let line_count: i64 = *params.get(0).expect("A number of lines was expected.");
+
+ if line_count >= 0 {
+ self.rotate_scroll_region_up(line_count as usize);
+ } else {
+ self.rotate_scroll_region_down(line_count.abs() as usize);
+ }
+ } else if c == 'S' {
+ // move scroll up
+ let count = if params[0] == 0 {
+ 1
+ } else {
+ params[0] as usize
+ };
+ self.scroll.delete_lines_in_scroll_region(count);
+ self.scroll.add_empty_lines_in_scroll_region(count);
+ } else {
+ let _ = debug_log_to_file(format!("Unhandled csi: {}->{:?}", c, params));
+ }
+ }
+
+ fn esc_dispatch(&mut self, intermediates: &[u8], _ignore: bool, byte: u8) {
+ match (byte, intermediates.get(0)) {
+ (b'M', None) => {
+ self.scroll.move_cursor_up_in_scroll_region(1);
+ }
+ _ => {}
+ }
+ }
+}