aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorAram Drevekenin <aram@poor.dev>2020-11-01 11:42:18 +0100
committerAram Drevekenin <aram@poor.dev>2020-11-01 11:42:18 +0100
commitba2fd9f782d3d4f00588ea923ebce2dff40f9aaf (patch)
treef858a5cb95c20dd8c8e0f7cf217d267b10766dc9 /src
parentrefactor(terminal-pane): simplify pending styles (diff)
downloadsl0ppij-ba2fd9f782d3d4f00588ea923ebce2dff40f9aaf.tar.gz
sl0ppij-ba2fd9f782d3d4f00588ea923ebce2dff40f9aaf.tar.xz
sl0ppij-ba2fd9f782d3d4f00588ea923ebce2dff40f9aaf.zip
refactor(structure): separate all the things
Diffstat (limited to 'src')
-rw-r--r--src/boundaries.rs2
-rw-r--r--src/main.rs48
-rw-r--r--src/os_input_output.rs2
-rw-r--r--src/pty_bus.rs2
-rw-r--r--src/screen.rs32
-rw-r--r--src/terminal_pane.rs1506
-rw-r--r--src/terminal_pane/mod.rs7
-rw-r--r--src/terminal_pane/scroll.rs457
-rw-r--r--src/terminal_pane/terminal_character.rs552
-rw-r--r--src/terminal_pane/terminal_pane.rs525
-rw-r--r--src/tests/utils.rs4
11 files changed, 1575 insertions, 1562 deletions
diff --git a/src/boundaries.rs b/src/boundaries.rs
index 9399006e..880c9061 100644
--- a/src/boundaries.rs
+++ b/src/boundaries.rs
@@ -1,6 +1,6 @@
use std::collections::HashMap;
-fn debug_log_to_file (message: String) {
+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();
diff --git a/src/main.rs b/src/main.rs
index a9b06c84..56d45503 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -8,27 +8,17 @@ mod screen;
mod boundaries;
use std::io::{Read, Write};
-use ::std::thread;
+use std::thread;
+use std::os::unix::net::UnixStream;
+use std::path::PathBuf;
+use std::sync::mpsc::{channel, Sender, Receiver};
-use std::os::unix::net::{UnixStream, UnixListener};
-use std::io::prelude::*;
use serde::{Serialize, Deserialize};
+use structopt::StructOpt;
use crate::os_input_output::{get_os_input, OsApi};
-use crate::terminal_pane::TerminalOutput;
use crate::pty_bus::{VteEvent, PtyBus, PtyInstruction};
use crate::screen::{Screen, ScreenInstruction};
-use std::path::{Path, PathBuf};
-use std::fs::remove_file;
-use std::sync::mpsc::{channel, Sender, Receiver};
-
-use structopt::StructOpt;
-
-// sigwinch stuff
-use ::signal_hook::iterator::Signals;
-
-pub type OnSigWinch = dyn Fn(Box<dyn Fn()>) + Send;
-pub type SigCleanup = dyn Fn() + Send;
#[derive(Serialize, Deserialize, Debug)]
enum ApiCommand {
@@ -52,7 +42,7 @@ pub struct Opt {
open_file: Option<PathBuf>
}
-fn debug_log_to_file (message: String) {
+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();
@@ -60,25 +50,6 @@ fn debug_log_to_file (message: String) {
file.write_all("\n".as_bytes()).unwrap();
}
-pub fn sigwinch() -> (Box<OnSigWinch>, Box<SigCleanup>) {
- let signals = Signals::new(&[signal_hook::SIGWINCH]).unwrap();
- let on_winch = {
- let signals = signals.clone();
- move |cb: Box<dyn Fn()>| {
- for signal in signals.forever() {
- match signal {
- signal_hook::SIGWINCH => cb(),
- _ => unreachable!(),
- }
- }
- }
- };
- let cleanup = move || {
- signals.close();
- };
- (Box::new(on_winch), Box::new(cleanup))
-}
-
pub fn main() {
let os_input = get_os_input();
let opts = Opt::from_args();
@@ -217,14 +188,14 @@ pub fn start(mut os_input: Box<dyn OsApi>) {
// because otherwise the app will hang. Need to fix this so it both
// listens to the ipc-bus and is able to quit cleanly
#[cfg(not(test))]
- let ipc_thread = thread::Builder::new()
+ let _ipc_thread = thread::Builder::new()
.name("ipc_server".to_string())
.spawn({
let send_pty_instructions = send_pty_instructions.clone();
let send_screen_instructions = send_screen_instructions.clone();
move || {
- remove_file("/tmp/mosaic").ok();
- let listener = UnixListener::bind("/tmp/mosaic").expect("could not listen on ipc socket");
+ ::std::fs::remove_file("/tmp/mosaic").ok();
+ let listener = ::std::os::unix::net::UnixListener::bind("/tmp/mosaic").expect("could not listen on ipc socket");
for stream in listener.incoming() {
match stream {
@@ -250,7 +221,6 @@ pub fn start(mut os_input: Box<dyn OsApi>) {
}
Err(err) => {
panic!("err {:?}", err);
- break;
}
}
}
diff --git a/src/os_input_output.rs b/src/os_input_output.rs
index 1d2d9547..04a5263c 100644
--- a/src/os_input_output.rs
+++ b/src/os_input_output.rs
@@ -57,7 +57,7 @@ pub fn set_terminal_size_using_fd(fd: RawFd, columns: u16, rows: u16) {
unsafe { ioctl(fd, TIOCSWINSZ.into(), &winsize) };
}
-fn debug_log_to_file (message: String) {
+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();
diff --git a/src/pty_bus.rs b/src/pty_bus.rs
index b83969f6..bd04e45a 100644
--- a/src/pty_bus.rs
+++ b/src/pty_bus.rs
@@ -26,7 +26,7 @@ impl ReadFromPid {
}
}
-fn debug_log_to_file (message: String) {
+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();
diff --git a/src/screen.rs b/src/screen.rs
index 19fef9b2..6557e0bc 100644
--- a/src/screen.rs
+++ b/src/screen.rs
@@ -5,11 +5,19 @@ use std::os::unix::io::RawFd;
use std::sync::mpsc::{Sender, Receiver};
use crate::os_input_output::OsApi;
-use crate::terminal_pane::TerminalOutput;
+use crate::terminal_pane::TerminalPane;
use crate::pty_bus::{VteEvent, PtyInstruction};
use crate::boundaries::Boundaries;
-fn debug_log_to_file (message: String) {
+/*
+ * Screen
+ *
+ * this holds multiple panes (currently terminal panes) which are currently displayed on screen
+ * it tracks their coordinates (x/y) and size, as well as how they should be resized
+ *
+ */
+
+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();
@@ -69,7 +77,7 @@ pub struct Screen {
pub receiver: Receiver<ScreenInstruction>,
send_pty_instructions: Sender<PtyInstruction>,
full_screen_ws: Winsize,
- terminals: BTreeMap<RawFd, TerminalOutput>, // BTreeMap because we need a predictable order when changing focus
+ terminals: BTreeMap<RawFd, TerminalPane>, // BTreeMap because we need a predictable order when changing focus
active_terminal: Option<RawFd>,
os_api: Box<dyn OsApi>,
}
@@ -89,7 +97,7 @@ impl Screen {
if self.terminals.is_empty() {
let x = 0;
let y = 0;
- let new_terminal = TerminalOutput::new(pid, self.full_screen_ws.clone(), x, y);
+ let new_terminal = TerminalPane::new(pid, self.full_screen_ws.clone(), x, y);
self.os_api.set_terminal_size_using_fd(new_terminal.pid, new_terminal.display_cols, new_terminal.display_rows);
self.terminals.insert(pid, new_terminal);
self.active_terminal = Some(pid);
@@ -110,7 +118,7 @@ impl Screen {
};
let (top_winsize, bottom_winsize) = split_horizontally_with_gap(&active_terminal_ws);
let bottom_half_y = active_terminal_y_coords + top_winsize.ws_row + 1;
- let new_terminal = TerminalOutput::new(pid, bottom_winsize, active_terminal_x_coords, bottom_half_y);
+ let new_terminal = TerminalPane::new(pid, bottom_winsize, active_terminal_x_coords, bottom_half_y);
self.os_api.set_terminal_size_using_fd(new_terminal.pid, bottom_winsize.ws_col, bottom_winsize.ws_row);
{
@@ -130,7 +138,7 @@ impl Screen {
if self.terminals.is_empty() {
let x = 0;
let y = 0;
- let new_terminal = TerminalOutput::new(pid, self.full_screen_ws.clone(), x, y);
+ let new_terminal = TerminalPane::new(pid, self.full_screen_ws.clone(), x, y);
self.os_api.set_terminal_size_using_fd(new_terminal.pid, new_terminal.display_cols, new_terminal.display_rows);
self.terminals.insert(pid, new_terminal);
self.active_terminal = Some(pid);
@@ -151,7 +159,7 @@ impl Screen {
};
let (left_winszie, right_winsize) = split_vertically_with_gap(&active_terminal_ws);
let right_side_x = active_terminal_x_coords + left_winszie.ws_col + 1;
- let new_terminal = TerminalOutput::new(pid, right_winsize, right_side_x, active_terminal_y_coords);
+ let new_terminal = TerminalPane::new(pid, right_winsize, right_side_x, active_terminal_y_coords);
self.os_api.set_terminal_size_using_fd(new_terminal.pid, right_winsize.ws_col, right_winsize.ws_row);
{
@@ -167,7 +175,7 @@ impl Screen {
self.render();
}
}
- fn get_active_terminal (&self) -> Option<&TerminalOutput> {
+ fn get_active_terminal (&self) -> Option<&TerminalPane> {
match self.active_terminal {
Some(active_terminal) => self.terminals.get(&active_terminal),
None => None
@@ -276,28 +284,28 @@ impl Screen {
Some(ids)
}
}
- fn panes_top_aligned_with_pane(&self, pane: &TerminalOutput) -> Vec<&TerminalOutput> {
+ fn panes_top_aligned_with_pane(&self, pane: &TerminalPane) -> Vec<&TerminalPane> {
self.terminals
.keys()
.map(|t_id| self.terminals.get(&t_id).unwrap())
.filter(|terminal| terminal.pid != pane.pid && terminal.y_coords == pane.y_coords)
.collect()
}
- fn panes_bottom_aligned_with_pane(&self, pane: &TerminalOutput) -> Vec<&TerminalOutput> {
+ fn panes_bottom_aligned_with_pane(&self, pane: &TerminalPane) -> Vec<&TerminalPane> {
self.terminals
.keys()
.map(|t_id| self.terminals.get(&t_id).unwrap())
.filter(|terminal| terminal.pid != pane.pid && terminal.y_coords + terminal.display_rows == pane.y_coords + pane.display_rows)
.collect()
}
- fn panes_right_aligned_with_pane(&self, pane: &TerminalOutput) -> Vec<&TerminalOutput> {
+ fn panes_right_aligned_with_pane(&self, pane: &TerminalPane) -> Vec<&TerminalPane> {
self.terminals
.keys()
.map(|t_id| self.terminals.get(&t_id).unwrap())
.filter(|terminal| terminal.pid != pane.pid && terminal.x_coords + terminal.display_cols == pane.x_coords + pane.display_cols)
.collect()
}
- fn panes_left_aligned_with_pane(&self, pane: &TerminalOutput) -> Vec<&TerminalOutput> {
+ fn panes_left_aligned_with_pane(&self, pane: &TerminalPane) -> Vec<&TerminalPane> {
self.terminals
.keys()
.map(|t_id| self.terminals.get(&t_id).unwrap())
diff --git a/src/terminal_pane.rs b/src/terminal_pane.rs
deleted file mode 100644
index 7a3be5aa..00000000
--- a/src/terminal_pane.rs
+++ /dev/null
@@ -1,1506 +0,0 @@
-use ::std::fmt::{self, Display, Debug, Formatter};
-use ::std::collections::VecDeque;
-use ::std::os::unix::io::RawFd;
-use ::nix::pty::Winsize;
-use ::vte::Perform;
-
-use crate::VteEvent;
-use crate::boundaries::Rect;
-
-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 {
- Reset,
- NamedColor(NamedColor),
- Code((Option<u16>, Option<u16>))
-}
-
-#[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 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);
- }
-}
-
-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::Code((param1, param2)) => {
- match (param1, param2) {
- (Some(param1), Some(param2)) => {
- write!(f, "\u{1b}[38;{};{}m", param1, param2)?;
- },
- (Some(param1), None) => {
- write!(f, "\u{1b}[38;{}m", param1)?;
- },
- (_, _) => {
- // TODO: can this happen?
- }
- }
- },
- 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::Code((param1, param2)) => {
- match (param1, param2) {
- (Some(param1), Some(param2)) => {
- write!(f, "\u{1b}[48;{};{}m", param1, param2)?;
- },
- (Some(param1), None) => {
- write!(f, "\u{1b}[48;{}m", param1)?;
- },
- (_, _) => {
- // TODO: can this happen?
- }
- }
- },
- 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::Code((param1, param2)) => {
- match (param1, param2) {
- (Some(param1), Some(param2)) => {
- write!(f, "\u{1b}[9;{};{}m", param1, param2)?;
- },
- (Some(param1), None) => {
- write!(f, "\u{1b}[9;{}m", param1)?;
- },
- (_, _) => {
- write!(f, "\u{1b}[9m")?;
- }
- }
- },
- AnsiCode::Reset => {
- write!(f, "\u{1b}[29m")?;
- },
- _ => {}
- }
- }
- if let Some(ansi_code) = self.hidden {
- match ansi_code {
- AnsiCode::Code((param1, param2)) => {
- match (param1, param2) {
- (Some(param1), Some(param2)) => {
- write!(f, "\u{1b}[8;{};{}m", param1, param2)?;
- },
- (Some(param1), None) => {
- write!(f, "\u{1b}[8;{}m", param1)?;
- },
- (_, _) => {
- write!(f, "\u{1b}[8m")?;
- }
- }
- },
- AnsiCode::Reset => {
- write!(f, "\u{1b}[28m")?;
- },
- _ => {}
- }
- }
- if let Some(ansi_code) = self.reverse {
- match ansi_code {
- AnsiCode::Code((param1, param2)) => {
- match (param1, param2) {
- (Some(param1), Some(param2)) => {
- write!(f, "\u{1b}[7;{};{}m", param1, param2)?;
- },
- (Some(param1), None) => {
- write!(f, "\u{1b}[7;{}m", param1)?;
- },
- (_, _) => {
- write!(f, "\u{1b}[7m")?;
- }
- }
- },
- AnsiCode::Reset => {
- write!(f, "\u{1b}[27m")?;
- },
- _ => {}
- }
- }
- if let Some(ansi_code) = self.fast_blink {
- match ansi_code {
- AnsiCode::Code((param1, param2)) => {
- match (param1, param2) {
- (Some(param1), Some(param2)) => {
- write!(f, "\u{1b}[6;{};{}m", param1, param2)?;
- },
- (Some(param1), None) => {
- write!(f, "\u{1b}[6;{}m", param1)?;
- },
- (_, _) => {
- write!(f, "\u{1b}[6m")?;
- }
- }
- },
- AnsiCode::Reset => {
- write!(f, "\u{1b}[25m")?;
- },
- _ => {}
- }
- }
- if let Some(ansi_code) = self.slow_blink {
- match ansi_code {
- AnsiCode::Code((param1, param2)) => {
- match (param1, param2) {
- (Some(param1), Some(param2)) => {
- write!(f, "\u{1b}[5;{};{}m", param1, param2)?;
- },
- (Some(param1), None) => {
- write!(f, "\u{1b}[5;{}m", param1)?;
- },
- (_, _) => {
- write!(f, "\u{1b}[5m")?;
- }
- }
- },
- AnsiCode::Reset => {
- write!(f, "\u{1b}[25m")?;
- },
- _ => {}
- }
- }
- if let Some(ansi_code) = self.bold {
- match ansi_code {
- AnsiCode::Code((param1, param2)) => {
- match (param1, param2) {
- (Some(param1), Some(param2)) => {
- write!(f, "\u{1b}[1;{};{}m", param1, param2)?;
- },
- (Some(param1), None) => {
- write!(f, "\u{1b}[1;{}m", param1)?;
- },
- (_, _) => {
- write!(f, "\u{1b}[1m")?;
- }
- }
- },
- AnsiCode::Reset => {
- write!(f, "\u{1b}[22m\u{1b}[24m")?;
- // character_ansi_codes.push_str(&format!("\u{1b}[22m"));
- // 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::Code((param1, param2)) => {
- match (param1, param2) {
- (Some(param1), Some(param2)) => {
- write!(f, "\u{1b}[4;{};{}m", param1, param2)?;
- },
- (Some(param1), None) => {
- write!(f, "\u{1b}[4;{}m", param1)?;
- },
- (_, _) => {
- write!(f, "\u{1b}[4m")?;
- }
- }
- },
- AnsiCode::Reset => {
- write!(f, "\u{1b}[24m")?;
- },
- _ => {}
- }
- }
- if let Some(ansi_code) = self.dim {
- match ansi_code {
- AnsiCode::Code((param1, param2)) => {
- match (param1, param2) {
- (Some(param1), Some(param2)) => {
- write!(f, "\u{1b}[2;{};{}m", param1, param2)?;
- },
- (Some(param1), None) => {
- write!(f, "\u{1b}[2;{}m", param1)?;
- },
- (_, _) => {
- 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::Code((param1, param2)) => {
- match (param1, param2) {
- (Some(param1), Some(param2)) => {
- write!(f, "\u{1b}[3;{};{}m", param1, param2)?;
- },
- (Some(param1), None) => {
- write!(f, "\u{1b}[3;{}m", param1)?;
- },
- (_, _) => {
- 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)
- }
-}
-
-#[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 mut characters: 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();
- let mut wrapped_fragments = Vec::with_capacity(characters.len() / new_width);
-
- while characters.len() > 0 {
- if characters.len() > new_width {
- wrapped_fragments.push(WrappedFragment::from_vec(characters.drain(..new_width).collect()));
- } else {
- wrapped_fragments.push(WrappedFragment::from_vec(characters.drain(..).collect()));
- }
- }
- 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);
- }
-}
-
-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)]
-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_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_up_by_canonical_lines(&mut self, count: usize) {
- let current_canonical_line_position = self.line_index.0;
- if count > current_canonical_line_position {
- self.line_index = (0, 0);
- } else {
- 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)
-}
-
-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,
- }
- }
- pub fn as_character_lines(&self) -> Vec<Vec<TerminalCharacter>> {
- let mut lines: VecDeque<Vec<TerminalCharacter>> = VecDeque::new(); // TODO: with capacity lines_from_end?
- let mut canonical_lines = self.canonical_lines.iter().rev();
- let mut lines_to_skip = self.viewport_bottom_offset.unwrap_or(0);
- 'gather_lines: loop {
- match canonical_lines.next() {
- Some(current_canonical_line) => {
- 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;
- }
- }
- },
- None => break, // no more lines
- }
- }
- if lines.len() < self.lines_in_view {
- // pad lines in case we don't have enough scrollback to fill the view
- let mut empty_line = vec![];
- for _ in 0..self.total_columns {
- empty_line.push(EMPTY_TERMINAL_CHARACTER);
- }
- 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();
- }
- }
- 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 {
- // the scroll region indices start at 1, so we need to adjust them
- let scroll_region_top_index = scroll_region_top - 1;
- let scroll_region_bottom_index = scroll_region_bottom - 1;
- if current_canonical_line_index == scroll_region_bottom_index { // 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_index);
- self.canonical_lines.insert(scroll_region_bottom_index, CanonicalLine::new());
- return;
- }
- }
- if current_canonical_line_index == self.canonical_lines.len() - 1 {
- let new_canonical_line = CanonicalLine::new();
- self.canonical_lines.push(new_canonical_line);
- self.cursor_position.move_to_next_canonical_line();
- self.cursor_position.move_to_beginning_of_canonical_line();
- } else if current_canonical_line_index < self.canonical_lines.len() - 1 {
- self.cursor_position.move_to_next_canonical_line();
- self.cursor_position.move_to_beginning_of_canonical_line();
- } else {
- panic!("cursor out of bounds, cannot add_canonical_line");
- }
- }
- pub fn cursor_coordinates_on_screen(&self) -> (usize, usize) { // (x, y)
- 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 mut indices_and_canonical_lines = self.canonical_lines.iter().enumerate().rev();
- loop {
- match indices_and_canonical_lines.next() {
- Some((current_index, current_line)) => {
- 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();
- }
-
- },
- None => break,
- }
- }
- 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 {
- self.lines_in_view - y
- };
- (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_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) {
- self.cursor_position.move_up_by_canonical_lines(count);
- }
- pub fn change_size(&mut self, columns: usize, lines: usize) {
- if self.scroll_region.is_none() {
- 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();
- }
- }
- self.lines_in_view = lines;
- self.total_columns = columns;
- }
- pub fn clear_canonical_line_right_of_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);
- }
- 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 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) {
- if self.canonical_lines.len() > line {
- self.cursor_position.move_to_canonical_line(line);
- } else {
- for _ in self.canonical_lines.len()..=line {
- self.canonical_lines.push(CanonicalLine::new());
- }
- self.cursor_position.move_to_canonical_line(line);
- }
- 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 set_scroll_region(&mut self, top_line: usize, bottom_line: usize) {
- self.scroll_region = Some((top_line, bottom_line));
- // TODO: clear linewraps in scroll region?
- }
- pub fn clear_scroll_region(&mut self) {
- self.scroll_region = None;
- }
- pub fn delete_lines_in_scroll_region(&mut self, count: usize) {
- if let Some((scroll_region_top, scroll_region_bottom)) = self.scroll_region {
- // the scroll region indices start at 1, so we need to adjust them
- let scroll_region_top_index = scroll_region_top - 1;
- let scroll_region_bottom_index = scroll_region_bottom - 1;
- let current_canonical_line_index = self.cursor_position.line_index.0;
- if current_canonical_line_index >= scroll_region_top_index &&
- current_canonical_line_index <= scroll_region_bottom_index {
- // 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_index, 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 {
- // the scroll region indices start at 1, so we need to adjust them
- let scroll_region_top_index = scroll_region_top - 1;
- let scroll_region_bottom_index = scroll_region_bottom - 1;
- let current_canonical_line_index = self.cursor_position.line_index.0;
- if current_canonical_line_index >= scroll_region_top_index &&
- current_canonical_line_index <= scroll_region_bottom_index {
- // 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_index);
- self.canonical_lines.insert(current_canonical_line_index, CanonicalLine::new());
- }
- }
- }
- }
- 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(())
- }
-}
-
-pub struct TerminalOutput {
- pub pid: RawFd,
- pub scroll: Scroll,
- pub display_rows: u16,
- pub display_cols: u16,
- pub should_render: bool,
- pub x_coords: u16,
- pub y_coords: u16,
- pending_styles: CharacterStyles,
-}
-
-impl Rect for &mut TerminalOutput {
- fn x(&self) -> usize {
- self.x_coords as usize
- }
- fn y(&self) -> usize {
- self.y_coords as usize
- }
- fn rows(&self) -> usize {
- self.display_rows as usize
- }
- fn columns(&self) -> usize {
- self.display_cols as usize
- }
-}
-
-impl TerminalOutput {
- pub fn new (pid: RawFd, ws: Winsize, x_coords: u16, y_coords: u16) -> TerminalOutput {
- let scroll = Scroll::new(ws.ws_col as usize, ws.ws_row as usize);
- let pending_styles = CharacterStyles::new();
- TerminalOutput {
- pid,
- scroll,
- display_rows: ws.ws_row,
- display_cols: ws.ws_col,
- should_render: true,
- pending_styles,
- x_coords,
- y_coords,
- }
- }
- pub fn handle_event(&mut self, event: VteEvent) {
- match event {
- VteEvent::Print(c) => {
- self.print(c);
- self.should_render = true;
- },
- 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);
- }
- }
- }
- pub fn reduce_width_right(&mut self, count: u16) {
- self.x_coords += count;
- self.display_cols -= count;
- self.reflow_lines();
- self.should_render = true;
- }
- pub fn reduce_width_left(&mut self, count: u16) {
- self.display_cols -= count;
- self.reflow_lines();
- self.should_render = true;
- }
- pub fn increase_width_left(&mut self, count: u16) {
- self.x_coords -= count;
- self.display_cols += count;
- self.reflow_lines();
- self.should_render = true;
- }
- pub fn increase_width_right(&mut self, count: u16) {
- self.display_cols += count;
- self.reflow_lines();
- self.should_render = true;
- }
- pub fn reduce_height_down(&mut self, count: u16) {
- self.y_coords += count;
- self.display_rows -= count;
- self.reflow_lines();
- self.should_render = true;
- }
- pub fn increase_height_down(&mut self, count: u16) {
- self.display_rows += count;
- self.reflow_lines();
- self.should_render = true;
- }
- pub fn increase_height_up(&mut self, count: u16) {
- self.y_coords -= count;
- self.display_rows += count;
- self.reflow_lines();
- self.should_render = true;
- }
- pub fn reduce_height_up(&mut self, count: u16) {
- self.display_rows -= count;
- self.reflow_lines();
- self.should_render = true;
- }
- pub fn change_size(&mut self, ws: &Winsize) {
- self.display_cols = ws.ws_col;
- self.display_rows = ws.ws_row;
- self.reflow_lines();
- self.should_render = true;
- }
- fn reflow_lines (&mut self) {
- self.scroll.change_size(self.display_cols as usize, self.display_rows as usize);
- }
- pub fn buffer_as_vte_output(&mut self) -> Option<String> {
- if self.should_render {
- let mut vte_output = String::new();
- let buffer_lines = &self.read_buffer_as_lines();
- let display_cols = &self.display_cols;
- let mut character_styles = CharacterStyles::new();
- for (row, line) in buffer_lines.iter().enumerate() {
- vte_output = format!("{}\u{1b}[{};{}H\u{1b}[m", vte_output, self.y_coords as usize + row + 1, self.x_coords + 1); // goto row/col and reset styles
- for (col, t_character) in line.iter().enumerate() {
- if (col as u16) < *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.should_render = false;
- Some(vte_output)
- } else {
- None
- }
- }
- pub fn read_buffer_as_lines (&self) -> Vec<Vec<TerminalCharacter>> {
- self.scroll.as_character_lines()
- }
- pub fn cursor_coordinates (&self) -> (usize, usize) { // (x, y)
- self.scroll.cursor_coordinates_on_screen()
- }
- pub fn scroll_up(&mut self, count: usize) {
- self.scroll.move_viewport_up(count);
- self.should_render = true;
- }
- pub fn scroll_down(&mut self, count: usize) {
- self.scroll.move_viewport_down(count);
- self.should_render = true;
- }
- pub fn clear_scroll(&mut self) {
- self.scroll.reset_viewport();
- self.should_render = true;
- }
- fn add_newline (&mut self) {
- self.scroll.add_canonical_line(); // TODO: handle scroll region
- self.reset_all_ansi_codes();
- self.should_render = true;
- }
- fn move_to_beginning_of_line (&mut self) {
- self.scroll.move_cursor_to_beginning_of_canonical_line();
- }
- 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();
- }
-}
-
-fn debug_log_to_file (message: String, pid: RawFd) {
- if pid == 0 {
- 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) {
- // 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) {
- if byte == 13 { // 0d, carriage return
- self.move_to_beginning_of_line();
- } else if byte == 08 { // backspace
- self.move_cursor_backwards(1);
- } else if byte == 10 { // 0a, newline
- self.add_newline();
- }
- }
-
- 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' {
- if params.is_empty() || params[0] == 0 {
- // reset all
- self.pending_styles.reset_all();
- } else if params[0] == 39 {
- self.pending_styles = self.pending_styles.foreground(Some(AnsiCode::Reset));
- } else if params[0] == 49 {
- self.pending_styles = self.pending_styles.background(Some(AnsiCode::Reset));
- } else if params[0] == 21 {
- // reset bold
- self.pending_styles = self.pending_styles.bold(Some(AnsiCode::Reset));
- } else if params[0] == 22 {
- // reset bold and dim
- self.pending_styles = self.pending_styles.bold(Some(AnsiCode::Reset));
- self.pending_styles = self.pending_styles.dim(Some(AnsiCode::Reset));
- } else if params[0] == 23 {
- // reset italic
- self.pending_styles = self.pending_styles.italic(Some(AnsiCode::Reset));
- } else if params[0] == 24 {
- // reset underline
- self.pending_styles = self.pending_styles.underline(Some(AnsiCode::Reset));
- } else if params[0] == 25 {
- // reset blink
- self.pending_styles = self.pending_styles.blink_slow(Some(AnsiCode::Reset));
- self.pending_styles = self.pending_styles.blink_fast(Some(AnsiCode::Reset));
- } else if params[0] == 27 {
- // reset reverse
- self.pending_styles = self.pending_styles.reverse(Some(AnsiCode::Reset));
- } else if params[0] == 28 {
- // reset hidden
- self.pending_styles = self.pending_styles.hidden(Some(AnsiCode::Reset));
- } else if params[0] == 29 {
- // reset strike
- self.pending_styles = self.pending_styles.strike(Some(AnsiCode::Reset));
- } else if params[0] == 38 {
- match (params.get(1), params.get(2)) {
- (Some(param1), Some(param2)) => {
- self.pending_styles = self.pending_styles.foreground(Some(AnsiCode::Code((Some(*param1 as u16), Some(*param2 as u16)))));
- },
- (Some(param1), None) => {
- self.pending_styles = self.pending_styles.foreground(Some(AnsiCode::Code((Some(*param1 as u16), None))));
- }
- (_, _) => {
- self.pending_styles = self.pending_styles.foreground(Some(AnsiCode::Code((None, None))));
- }
- };
- } else if params[0] == 48 {
- match (params.get(1), params.get(2)) {
- (Some(param1), Some(param2)) => {
- self.pending_styles = self.pending_styles.background(Some(AnsiCode::Code((Some(*param1 as u16), Some(*param2 as u16)))));
- },
- (Some(param1), None) => {
- self.pending_styles = self.pending_styles.background(Some(AnsiCode::Code((Some(*param1 as u16), None))));
- }
- (_, _) => {
- self.pending_styles = self.pending_styles.background(Some(AnsiCode::Code((None, None))));
- }
- };
- } else if params[0] == 1 {
- // bold
- match (params.get(1), params.get(2)) {
- (Some(param1), Some(param2)) => {
- self.pending_styles = self.pending_styles.bold(Some(AnsiCode::Code((Some(*param1 as u16), Some(*param2 as u16)))));
- },
- (Some(param1), None) => {
- self.pending_styles = self.pending_styles.bold(Some(AnsiCode::Code((Some(*param1 as u16), None))));
- }
- (_, _) => {
- self.pending_styles = self.pending_styles.bold(Some(AnsiCode::Code((None, None))));
- }
- };
- } else if params[0] == 2 {
- // dim
- match (params.get(1), params.get(2)) {
- (Some(param1), Some(param2)) => {
- self.pending_styles = self.pending_styles.dim(Some(AnsiCode::Code((Some(*param1 as u16), Some(*param2 as u16)))));
- },
- (Some(param1), None) => {
- self.pending_styles = self.pending_styles.dim(Some(AnsiCode::Code((Some(*param1 as u16), None))));
- }
- (_, _) => {
- self.pending_styles = self.pending_styles.dim(Some(AnsiCode::Code((None, None))));
- }
- };
- } else if params[0] == 3 {
- // italic
- match (params.get(1), params.get(2)) {
- (Some(param1), Some(param2)) => {
- self.pending_styles = self.pending_styles.italic(Some(AnsiCode::Code((Some(*param1 as u16), Some(*param2 as u16)))));
- },
- (Some(param1), None) => {
- self.pending_styles = self.pending_styles.italic(Some(AnsiCode::Code((Some(*param1 as u16), None))));
- }
- (_, _) => {
- self.pending_styles = self.pending_styles.italic(Some(AnsiCode::Code((None, None))));
- }
- };
- } else if params[0] == 4 {
- // underline
- match (params.get(1), params.get(2)) {
- (Some(param1), Some(param2)) => {
- self.pending_styles = self.pending_styles.underline(Some(AnsiCode::Code((Some(*param1 as u16), Some(*param2 as u16)))));
- },
- (Some(param1), None) => {
- self.pending_styles = self.pending_styles.underline(Some(AnsiCode::Code((Some(*param1 as u16), None))));
- }
- (_, _) => {
- self.pending_styles = self.pending_styles.underline(Some(AnsiCode::Code((None, None))));
- }
- };
- } else if params[0] == 5 {
- // blink slow
- match (params.get(1), params.get(2)) {
- (Some(param1), Some(param2)) => {
- self.pending_styles = self.pending_styles.blink_slow(Some(AnsiCode::Code((Some(*param1 as u16), Some(*param2 as u16)))));
- },
- (Some(param1), None) => {
- self.pending_styles = self.pending_styles.blink_slow(Some(AnsiCode::Code((Some(*param1 as u16), None))));
- }
- (_, _) => {
- self.pending_styles = self.pending_styles.blink_slow(Some(AnsiCode::Code((None, None))));
- }
- };
- } else if params[0] == 6 {
- // blink fast
- match (params.get(1), params.get(2)) {
- (Some(param1), Some(param2)) => {
- self.pending_styles = self.pending_styles.blink_fast(Some(AnsiCode::Code((Some(*param1 as u16), Some(*param2 as u16)))));
- },
- (Some(param1), None) => {
- self.pending_styles = self.pending_styles.blink_fast(Some(AnsiCode::Code((Some(*param1 as u16), None))));
- }
- (_, _) => {
- self.pending_styles = self.pending_styles.blink_fast(Some(AnsiCode::Code((None, None))));
- }
- };
- } else if params[0] == 7 {
- // reverse
- match (params.get(1), params.get(2)) {
- (Some(param1), Some(param2)) => {
- self.pending_styles = self.pending_styles.reverse(Some(AnsiCode::Code((Some(*param1 as u16), Some(*param2 as u16)))));
- },
- (Some(param1), None) => {
- self.pending_styles = self.pending_styles.reverse(Some(AnsiCode::Code((Some(*param1 as u16), None))));
- }
- (_, _) => {
- self.pending_styles = self.pending_styles.reverse(Some(AnsiCode::Code((None, None))));
- }
- };
- } else if params[0] == 8 {
- // hidden
- match (params.get(1), params.get(2)) {
- (Some(param1), Some(param2)) => {
- self.pending_styles = self.pending_styles.hidden(Some(AnsiCode::Code((Some(*param1 as u16), Some(*param2 as u16)))));
- },
- (Some(param1), None) => {
- self.pending_styles = self.pending_styles.hidden(Some(AnsiCode::Code((Some(*param1 as u16), None))));
- }
- (_, _) => {
- self.pending_styles = self.pending_styles.hidden(Some(AnsiCode::Code((None, None))));
- }
- };
- } else if params[0] == 9 {
- // strike
- match (params.get(1), params.get(2)) {
- (Some(param1), Some(param2)) => {
- self.pending_styles = self.pending_styles.strike(Some(AnsiCode::Code((Some(*param1 as u16), Some(*param2 as u16)))));
- },
- (Some(param1), None) => {
- self.pending_styles = self.pending_styles.strike(Some(AnsiCode::Code((Some(*param1 as u16), None))));
- }
- (_, _) => {
- self.pending_styles = self.pending_styles.strike(Some(AnsiCode::Code((None, None))));
- }
- };
- } else if params[0] == 30 {
- self.pending_styles = self.pending_styles.foreground(Some(AnsiCode::NamedColor(NamedColor::Black)));
- } else if params[0] == 31 {
- self.pending_styles = self.pending_styles.foreground(Some(AnsiCode::NamedColor(NamedColor::Red)));
- } else if params[0] == 32 {
- self.pending_styles = self.pending_styles.foreground(Some(AnsiCode::NamedColor(NamedColor::Green)));
- } else if params[0] == 33 {
- self.pending_styles = self.pending_styles.foreground(Some(AnsiCode::NamedColor(NamedColor::Yellow)));
- } else if params[0] == 34 {
- self.pending_styles = self.pending_styles.foreground(Some(AnsiCode::NamedColor(NamedColor::Blue)));
- } else if params[0] == 35 {
- self.pending_styles = self.pending_styles.foreground(Some(AnsiCode::NamedColor(NamedColor::Magenta)));
- } else if params[0] == 36 {
- self.pending_styles = self.pending_styles.foreground(Some(AnsiCode::NamedColor(NamedColor::Cyan)));
- } else if params[0] == 37 {
- self.pending_styles = self.pending_styles.foreground(Some(AnsiCode::NamedColor(NamedColor::White)));
- } else if params[0] == 40 {
- self.pending_styles = self.pending_styles.background(Some(AnsiCode::NamedColor(NamedColor::Black)));
- } else if params[0] == 41 {
- self.pending_styles = self.pending_styles.background(Some(AnsiCode::NamedColor(NamedColor::Red)));
- } else if params[0] == 42 {
- self.pending_styles = self.pending_styles.background(Some(AnsiCode::NamedColor(NamedColor::Green)));
- } else if params[0] == 43 {
- self.pending_styles = self.pending_styles.background(Some(AnsiCode::NamedColor(NamedColor::Yellow)));
- } else if params[0] == 44 {
- self.pending_styles = self.pending_styles.background(Some(AnsiCode::NamedColor(NamedColor::Blue)));
- } else if params[0] == 45 {
- self.pending_styles = self.pending_styles.background(Some(AnsiCode::NamedColor(NamedColor::Magenta)));
- } else if params[0] == 46 {
- self.pending_styles = self.pending_styles.background(Some(AnsiCode::NamedColor(NamedColor::Cyan)));
- } else if params[0] == 47 {
- self.pending_styles = self.pending_styles.background(Some(AnsiCode::NamedColor(NamedColor::White)));
- } else {
- debug_log_to_file(format!("unhandled csi m code {:?}", params), self.pid);
- }
- } else if c == 'C' { // move cursor forward
- let move_by = 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();
- }
- // TODO: implement 1 and 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
- let (row, col) = if params.len() == 1 {
- (params[0] as usize, 0) // TODO: is this always correct ?
- } else {
- (params[0] as usize - 1, params[1] as usize - 1) // we subtract 1 here because this csi is 1 indexed and we index from 0
- };
- 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 == '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' {
- // TBD
- } else if c == 'h' {
- // TBD
- } else if c == 'r' {
- if params.len() > 1 {
- let top_line_index = params[0] as usize;
- let bottom_line_index = params[1] as usize;
- self.scroll.set_scroll_region(top_line_index, bottom_line_index);
- } 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' || c == 'd' || c == 'X' || c == 'G' {
- // ignore for now to run on mac
- } else {
- panic!("unhandled csi: {:?}->{:?}", c, params);
- }
- }
-
- fn esc_dispatch(&mut self, intermediates: &[u8], ignore: bool, byte: u8) {
- // TBD
- }
-}
diff --git a/src/terminal_pane/mod.rs b/src/terminal_pane/mod.rs
new file mode 100644
index 00000000..d242947d
--- /dev/null
+++ b/src/terminal_pane/mod.rs
@@ -0,0 +1,7 @@
+mod terminal_pane;
+mod terminal_character;
+mod scroll;
+
+pub use terminal_pane::*;
+pub use terminal_character::*;
+pub use scroll::*;
diff --git a/src/terminal_pane/scroll.rs b/src/terminal_pane/scroll.rs
new file mode 100644
index 00000000..13bdaf37
--- /dev/null
+++ b/src/terminal_pane/scroll.rs
@@ -0,0 +1,457 @@
+use ::std::fmt::{self, Debug, Formatter};
+use ::std::collections::VecDeque;
+
+use crate::terminal_pane::terminal_character::{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 mut characters: 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();
+ let mut wrapped_fragments = Vec::with_capacity(characters.len() / new_width);
+
+ while characters.len() > 0 {
+ if characters.len() > new_width {
+ wrapped_fragments.push(WrappedFragment::from_vec(characters.drain(..new_width).collect()));
+ } else {
+ wrapped_fragments.push(WrappedFragment::from_vec(characters.drain(..).collect()));
+ }
+ }
+ 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);
+ }
+}
+
+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)]
+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_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_up_by_canonical_lines(&mut self, count: usize) {
+ let current_canonical_line_position = self.line_index.0;
+ if count > current_canonical_line_position {
+ self.line_index = (0, 0);
+ } else {
+ 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)
+}
+
+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,
+ }
+ }
+ pub fn as_character_lines(&self) -> Vec<Vec<TerminalCharacter>> {
+ let mut lines: VecDeque<Vec<TerminalCharacter>> = VecDeque::new(); // TODO: with capacity lines_from_end?
+ let mut canonical_lines = self.canonical_lines.iter().rev();
+ let mut lines_to_skip = self.viewport_bottom_offset.unwrap_or(0);
+ 'gather_lines: loop {
+ match canonical_lines.next() {
+ Some(current_canonical_line) => {
+ 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;
+ }
+ }
+ },
+ None => break, // no more lines
+ }
+ }
+ if lines.len() < self.lines_in_view {
+ // pad lines in case we don't have enough scrollback to fill the view
+ let mut empty_line = vec![];
+ for _ in 0..self.total_columns {
+ empty_line.push(EMPTY_TERMINAL_CHARACTER);
+ }
+ 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();
+ }
+ }
+ 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 {
+ // the scroll region indices start at 1, so we need to adjust them
+ let scroll_region_top_index = scroll_region_top - 1;
+ let scroll_region_bottom_index = scroll_region_bottom - 1;
+ if current_canonical_line_index == scroll_region_bottom_index { // 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_index);
+ self.canonical_lines.insert(scroll_region_bottom_index, CanonicalLine::new());
+ return;
+ }
+ }
+ if current_canonical_line_index == self.canonical_lines.len() - 1 {
+ let new_canonical_line = CanonicalLine::new();
+ self.canonical_lines.push(new_canonical_line);
+ self.cursor_position.move_to_next_canonical_line();
+ self.cursor_position.move_to_beginning_of_canonical_line();
+ } else if current_canonical_line_index < self.canonical_lines.len() - 1 {
+ self.cursor_position.move_to_next_canonical_line();
+ self.cursor_position.move_to_beginning_of_canonical_line();
+ } else {
+ panic!("cursor out of bounds, cannot add_canonical_line");
+ }
+ }
+ pub fn cursor_coordinates_on_screen(&self) -> (usize, usize) { // (x, y)
+ 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 mut indices_and_canonical_lines = self.canonical_lines.iter().enumerate().rev();
+ loop {
+ match indices_and_canonical_lines.next() {
+ Some((current_index, current_line)) => {
+ 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();
+ }
+
+ },
+ None => break,
+ }
+ }
+ 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 {
+ self.lines_in_view - y
+ };
+ (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_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) {
+ self.cursor_position.move_up_by_canonical_lines(count);
+ }
+ pub fn change_size(&mut self, columns: usize, lines: usize) {
+ if self.scroll_region.is_none() {
+ 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();
+ }
+ }
+ self.lines_in_view = lines;
+ self.total_columns = columns;
+ }
+ pub fn clear_canonical_line_right_of_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);
+ }
+ 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 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) {
+ if self.canonical_lines.len() > line {
+ self.cursor_position.move_to_canonical_line(line);
+ } else {
+ for _ in self.canonical_lines.len()..=line {
+ self.canonical_lines.push(CanonicalLine::new());
+ }
+ self.cursor_position.move_to_canonical_line(line);
+ }
+ 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 set_scroll_region(&mut self, top_line: usize, bottom_line: usize) {
+ self.scroll_region = Some((top_line, bottom_line));
+ // TODO: clear linewraps in scroll region?
+ }
+ pub fn clear_scroll_region(&mut self) {
+ self.scroll_region = None;
+ }
+ pub fn delete_lines_in_scroll_region(&mut self, count: usize) {
+ if let Some((scroll_region_top, scroll_region_bottom)) = self.scroll_region {
+ // the scroll region indices start at 1, so we need to adjust them
+ let scroll_region_top_index = scroll_region_top - 1;
+ let scroll_region_bottom_index = scroll_region_bottom - 1;
+ let current_canonical_line_index = self.cursor_position.line_index.0;
+ if current_canonical_line_index >= scroll_region_top_index &&
+ current_canonical_line_index <= scroll_region_bottom_index {
+ // 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_index, 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 {
+ // the scroll region indices start at 1, so we need to adjust them
+ let scroll_region_top_index = scroll_region_top - 1;
+ let scroll_region_bottom_index = scroll_region_bottom - 1;
+ let current_canonical_line_index = self.cursor_position.line_index.0;
+ if current_canonical_line_index >= scroll_region_top_index &&
+ current_canonical_line_index <= scroll_region_bottom_index {
+ // 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_index);
+ self.canonical_lines.insert(current_canonical_line_index, CanonicalLine::new());
+ }
+ }
+ }
+ }
+ 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/terminal_pane/terminal_character.rs b/src/terminal_pane/terminal_character.rs
new file mode 100644
index 00000000..dd45a7af
--- /dev/null
+++ b/src/terminal_pane/terminal_character.rs
@@ -0,0 +1,552 @@
+use ::std::fmt::{self, Display, Debug, 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 {
+ Reset,
+ NamedColor(NamedColor),
+ Code((Option<u16>, Option<u16>))
+}
+
+#[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 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);
+ }
+}
+
+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::Code((param1, param2)) => {
+ match (param1, param2) {
+ (Some(param1), Some(param2)) => {
+ write!(f, "\u{1b}[38;{};{}m", param1, param2)?;
+ },
+ (Some(param1), None) => {
+ write!(f, "\u{1b}[38;{}m", param1)?;
+ },
+ (_, _) => {
+ // TODO: can this happen?
+ }
+ }
+ },
+ 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::Code((param1, param2)) => {
+ match (param1, param2) {
+ (Some(param1), Some(param2)) => {
+ write!(f, "\u{1b}[48;{};{}m", param1, param2)?;
+ },
+ (Some(param1), None) => {
+ write!(f, "\u{1b}[48;{}m", param1)?;
+ },
+ (_, _) => {
+ // TODO: can this happen?
+ }
+ }
+ },
+ 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::Code((param1, param2)) => {
+ match (param1, param2) {
+ (Some(param1), Some(param2)) => {
+ write!(f, "\u{1b}[9;{};{}m", param1, param2)?;
+ },
+ (Some(param1), None) => {
+ write!(f, "\u{1b}[9;{}m", param1)?;
+ },
+ (_, _) => {
+ write!(f, "\u{1b}[9m")?;
+ }
+ }
+ },
+ AnsiCode::Reset => {
+ write!(f, "\u{1b}[29m")?;
+ },
+ _ => {}
+ }
+ }
+ if let Some(ansi_code) = self.hidden {
+ match ansi_code {
+ AnsiCode::Code((param1, param2)) => {
+ match (param1, param2) {
+ (Some(param1), Some(param2)) => {
+ write!(f, "\u{1b}[8;{};{}m", param1, param2)?;
+ },
+ (Some(param1), None) => {
+ write!(f, "\u{1b}[8;{}m", param1)?;
+ },
+ (_, _) => {
+ write!(f, "\u{1b}[8m")?;
+ }
+ }
+ },
+ AnsiCode::Reset => {
+ write!(f, "\u{1b}[28m")?;
+ },
+ _ => {}
+ }
+ }
+ if let Some(ansi_code) = self.reverse {
+ match ansi_code {
+ AnsiCode::Code((param1, param2)) => {
+ match (param1, param2) {
+ (Some(param1), Some(param2)) => {
+ write!(f, "\u{1b}[7;{};{}m", param1, param2)?;
+ },
+ (Some(param1), None) => {
+ write!(f, "\u{1b}[7;{}m", param1)?;
+ },
+ (_, _) => {
+ write!(f, "\u{1b}[7m")?;
+ }
+ }
+ },
+ AnsiCode::Reset => {
+ write!(f, "\u{1b}[27m")?;
+ },
+ _ => {}
+ }
+ }
+ if let Some(ansi_code) = self.fast_blink {
+ match ansi_code {
+ AnsiCode::Code((param1, param2)) => {
+ match (param1, param2) {
+ (Some(param1), Some(param2)) => {
+ write!(f, "\u{1b}[6;{};{}m", param1, param2)?;
+ },
+ (Some(param1), None) => {
+ write!(f, "\u{1b}[6;{}m", param1)?;
+ },
+ (_, _) => {
+ write!(f, "\u{1b}[6m")?;
+ }
+ }
+ },
+ AnsiCode::Reset => {
+ write!(f, "\u{1b}[25m")?;
+ },
+ _ => {}
+ }
+ }
+ if let Some(ansi_code) = self.slow_blink {
+ match ansi_code {
+ AnsiCode::Code((param1, param2)) => {
+ match (param1, param2) {
+ (Some(param1), Some(param2)) => {
+ write!(f, "\u{1b}[5;{};{}m", param1, param2)?;
+ },
+ (Some(param1), None) => {
+ write!(f, "\u{1b}[5;{}m", param1)?;
+ },
+ (_, _) => {
+ write!(f, "\u{1b}[5m")?;
+ }
+ }
+ },
+ AnsiCode::Reset => {
+ write!(f, "\u{1b}[25m")?;
+ },
+ _ => {}
+ }
+ }
+ if let Some(ansi_code) = self.bold {
+ match ansi_code {
+ AnsiCode::Code((param1, param2)) => {
+ match (param1, param2) {
+ (Some(param1), Some(param2)) => {
+ write!(f, "\u{1b}[1;{};{}m", param1, param2)?;
+ },
+ (Some(param1), None) => {
+ write!(f, "\u{1b}[1;{}m", param1)?;
+ },
+ (_, _) => {
+ write!(f, "\u{1b}[1m")?;
+ }
+ }
+ },
+ AnsiCode::Reset => {
+ write!(f, "\u{1b}[22m\u{1b}[24m")?;
+ // character_ansi_codes.push_str(&format!("\u{1b}[22m"));
+ // 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::Code((param1, param2)) => {
+ match (param1, param2) {
+ (Some(param1), Some(param2)) => {
+ write!(f, "\u{1b}[4;{};{}m", param1, param2)?;
+ },
+ (Some(param1), None) => {
+ write!(f, "\u{1b}[4;{}m", param1)?;
+ },
+ (_, _) => {
+ write!(f, "\u{1b}[4m")?;
+ }
+ }
+ },
+ AnsiCode::Reset => {
+ write!(f, "\u{1b}[24m")?;
+ },
+ _ => {}
+ }
+ }
+ if let Some(ansi_code) = self.dim {
+ match ansi_code {
+ AnsiCode::Code((param1, param2)) => {
+ match (param1, param2) {
+ (Some(param1), Some(param2)) => {
+ write!(f, "\u{1b}[2;{};{}m", param1, param2)?;
+ },
+ (Some(param1), None) => {
+ write!(f, "\u{1b}[2;{}m", param1)?;
+ },
+ (_, _) => {
+ 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::Code((param1, param2)) => {
+ match (param1, param2) {
+ (Some(param1), Some(param2)) => {
+ write!(f, "\u{1b}[3;{};{}m", param1, param2)?;
+ },
+ (Some(param1), None) => {
+ write!(f, "\u{1b}[3;{}m", param1)?;
+ },
+ (_, _) => {
+ 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/terminal_pane/terminal_pane.rs b/src/terminal_pane/terminal_pane.rs
new file mode 100644
index 00000000..22b28373
--- /dev/null
+++ b/src/terminal_pane/terminal_pane.rs
@@ -0,0 +1,525 @@
+use ::std::os::unix::io::RawFd;
+use ::nix::pty::Winsize;
+use ::vte::Perform;
+
+use crate::VteEvent;
+use crate::boundaries::Rect;
+use crate::terminal_pane::Scroll;
+use crate::terminal_pane::terminal_character::{
+ TerminalCharacter,
+ CharacterStyles,
+ AnsiCode,
+ NamedColor
+};
+
+pub struct TerminalPane {
+ pub pid: RawFd,
+ pub scroll: Scroll,
+ pub display_rows: u16,
+ pub display_cols: u16,
+ pub should_render: bool,
+ pub x_coords: u16,
+ pub y_coords: u16,
+ pending_styles: CharacterStyles,
+}
+
+impl Rect for &mut TerminalPane {
+ fn x(&self) -> usize {
+ self.x_coords as usize
+ }
+ fn y(&self) -> usize {
+ self.y_coords as usize
+ }
+ fn rows(&self) -> usize {
+ self.display_rows as usize
+ }
+ fn columns(&self) -> usize {
+ self.display_cols as usize
+ }
+}
+
+impl TerminalPane {
+ pub fn new (pid: RawFd, ws: Winsize, x_coords: u16, y_coords: u16) -> TerminalPane {
+ let scroll = Scroll::new(ws.ws_col as usize, ws.ws_row as usize);
+ let pending_styles = CharacterStyles::new();
+ TerminalPane {
+ pid,
+ scroll,
+ display_rows: ws.ws_row,
+ display_cols: ws.ws_col,
+ should_render: true,
+ pending_styles,
+ x_coords,
+ y_coords,
+ }
+ }
+ pub fn handle_event(&mut self, event: VteEvent) {
+ match event {
+ VteEvent::Print(c) => {
+ self.print(c);
+ self.should_render = true;
+ },
+ 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);
+ }
+ }
+ }
+ pub fn reduce_width_right(&mut self, count: u16) {
+ self.x_coords += count;
+ self.display_cols -= count;
+ self.reflow_lines();
+ self.should_render = true;
+ }
+ pub fn reduce_width_left(&mut self, count: u16) {
+ self.display_cols -= count;
+ self.reflow_lines();
+ self.should_render = true;
+ }
+ pub fn increase_width_left(&mut self, count: u16) {
+ self.x_coords -= count;
+ self.display_cols += count;
+ self.reflow_lines();
+ self.should_render = true;
+ }
+ pub fn increase_width_right(&mut self, count: u16) {
+ self.display_cols += count;
+ self.reflow_lines();
+ self.should_render = true;
+ }
+ pub fn reduce_height_down(&mut self, count: u16) {
+ self.y_coords += count;
+ self.display_rows -= count;
+ self.reflow_lines();
+ self.should_render = true;
+ }
+ pub fn increase_height_down(&mut self, count: u16) {
+ self.display_rows += count;
+ self.reflow_lines();
+ self.should_render = true;
+ }
+ pub fn increase_height_up(&mut self, count: u16) {
+ self.y_coords -= count;
+ self.display_rows += count;
+ self.reflow_lines();
+ self.should_render = true;
+ }
+ pub fn reduce_height_up(&mut self, count: u16) {
+ self.display_rows -= count;
+ self.reflow_lines();
+ self.should_render = true;
+ }
+ pub fn change_size(&mut self, ws: &Winsize) {
+ self.display_cols = ws.ws_col;
+ self.display_rows = ws.ws_row;
+ self.reflow_lines();
+ self.should_render = true;
+ }
+ fn reflow_lines (&mut self) {
+ self.scroll.change_size(self.display_cols as usize, self.display_rows as usize);
+ }
+ pub fn buffer_as_vte_output(&mut self) -> Option<String> {
+ if self.should_render {
+ let mut vte_output = String::new();
+ let buffer_lines = &self.read_buffer_as_lines();
+ let display_cols = &self.display_cols;
+ let mut character_styles = CharacterStyles::new();
+ for (row, line) in buffer_lines.iter().enumerate() {
+ vte_output = format!("{}\u{1b}[{};{}H\u{1b}[m", vte_output, self.y_coords as usize + row + 1, self.x_coords + 1); // goto row/col and reset styles
+ for (col, t_character) in line.iter().enumerate() {
+ if (col as u16) < *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.should_render = false;
+ Some(vte_output)
+ } else {
+ None
+ }
+ }
+ pub fn read_buffer_as_lines (&self) -> Vec<Vec<TerminalCharacter>> {
+ self.scroll.as_character_lines()
+ }
+ pub fn cursor_coordinates (&self) -> (usize, usize) { // (x, y)
+ self.scroll.cursor_coordinates_on_screen()
+ }
+ pub fn scroll_up(&mut self, count: usize) {
+ self.scroll.move_viewport_up(count);
+ self.should_render = true;
+ }
+ pub fn scroll_down(&mut self, count: usize) {
+ self.scroll.move_viewport_down(count);
+ self.should_render = true;
+ }
+ pub fn clear_scroll(&mut self) {
+ self.scroll.reset_viewport();
+ self.should_render = true;
+ }
+ fn add_newline (&mut self) {
+ self.scroll.add_canonical_line(); // TODO: handle scroll region
+ self.reset_all_ansi_codes();
+ self.should_render = true;
+ }
+ fn move_to_beginning_of_line (&mut self) {
+ self.scroll.move_cursor_to_beginning_of_canonical_line();
+ }
+ 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();
+ }
+}
+
+fn debug_log_to_file (message: String, pid: RawFd) {
+ if pid == 0 {
+ 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 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) {
+ if byte == 13 { // 0d, carriage return
+ self.move_to_beginning_of_line();
+ } else if byte == 08 { // backspace
+ self.move_cursor_backwards(1);
+ } else if byte == 10 { // 0a, newline
+ self.add_newline();
+ }
+ }
+
+ 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' {
+ if params.is_empty() || params[0] == 0 {
+ // reset all
+ self.pending_styles.reset_all();
+ } else if params[0] == 39 {
+ self.pending_styles = self.pending_styles.foreground(Some(AnsiCode::Reset));
+ } else if params[0] == 49 {
+ self.pending_styles = self.pending_styles.background(Some(AnsiCode::Reset));
+ } else if params[0] == 21 {
+ // reset bold
+ self.pending_styles = self.pending_styles.bold(Some(AnsiCode::Reset));
+ } else if params[0] == 22 {
+ // reset bold and dim
+ self.pending_styles = self.pending_styles.bold(Some(AnsiCode::Reset));
+ self.pending_styles = self.pending_styles.dim(Some(AnsiCode::Reset));
+ } else if params[0] == 23 {
+ // reset italic
+ self.pending_styles = self.pending_styles.italic(Some(AnsiCode::Reset));
+ } else if params[0] == 24 {
+ // reset underline
+ self.pending_styles = self.pending_styles.underline(Some(AnsiCode::Reset));
+ } else if params[0] == 25 {
+ // reset blink
+ self.pending_styles = self.pending_styles.blink_slow(Some(AnsiCode::Reset));
+ self.pending_styles = self.pending_styles.blink_fast(Some(AnsiCode::Reset));
+ } else if params[0] == 27 {
+ // reset reverse
+ self.pending_styles = self.pending_styles.reverse(Some(AnsiCode::Reset));
+ } else if params[0] == 28 {
+ // reset hidden
+ self.pending_styles = self.pending_styles.hidden(Some(AnsiCode::Reset));
+ } else if params[0] == 29 {
+ // reset strike
+ self.pending_styles = self.pending_styles.strike(Some(AnsiCode::Reset));
+ } else if params[0] == 38 {
+ match (params.get(1), params.get(2)) {
+ (Some(param1), Some(param2)) => {
+ self.pending_styles = self.pending_styles.foreground(Some(AnsiCode::Code((Some(*param1 as u16), Some(*param2 as u16)))));
+ },
+ (Some(param1), None) => {
+ self.pending_styles = self.pending_styles.foreground(Some(AnsiCode::Code((Some(*param1 as u16), None))));
+ }
+ (_, _) => {
+ self.pending_styles = self.pending_styles.foreground(Some(AnsiCode::Code((None, None))));
+ }
+ };
+ } else if params[0] == 48 {
+ match (params.get(1), params.get(2)) {
+ (Some(param1), Some(param2)) => {
+ self.pending_styles = self.pending_styles.background(Some(AnsiCode::Code((Some(*param1 as u16), Some(*param2 as u16)))));
+ },
+ (Some(param1), None) => {
+ self.pending_styles = self.pending_styles.background(Some(AnsiCode::Code((Some(*param1 as u16), None))));
+ }
+ (_, _) => {
+ self.pending_styles = self.pending_styles.background(Some(AnsiCode::Code((None, None))));
+ }
+ };
+ } else if params[0] == 1 {
+ // bold
+ match (params.get(1), params.get(2)) {
+ (Some(param1), Some(param2)) => {
+ self.pending_styles = self.pending_styles.bold(Some(AnsiCode::Code((Some(*param1 as u16), Some(*param2 as u16)))));
+ },
+ (Some(param1), None) => {
+ self.pending_styles = self.pending_styles.bold(Some(AnsiCode::Code((Some(*param1 as u16), None))));
+ }
+ (_, _) => {
+ self.pending_styles = self.pending_styles.bold(Some(AnsiCode::Code((None, None))));
+ }
+ };
+ } else if params[0] == 2 {
+ // dim
+ match (params.get(1), params.get(2)) {
+ (Some(param1), Some(param2)) => {
+ self.pending_styles = self.pending_styles.dim(Some(AnsiCode::Code((Some(*param1 as u16), Some(*param2 as u16)))));
+ },
+ (Some(param1), None) => {
+ self.pending_styles = self.pending_styles.dim(Some(AnsiCode::Code((Some(*param1 as u16), None))));
+ }
+ (_, _) => {
+ self.pending_styles = self.pending_styles.dim(Some(AnsiCode::Code((None, None))));
+ }
+ };
+ } else if params[0] == 3 {
+ // italic
+ match (params.get(1), params.get(2)) {
+ (Some(param1), Some(param2)) => {
+ self.pending_styles = self.pending_styles.italic(Some(AnsiCode::Code((Some(*param1 as u16), Some(*param2 as u16)))));
+ },
+ (Some(param1), None) => {
+ self.pending_styles = self.pending_styles.italic(Some(AnsiCode::Code((Some(*param1 as u16), None))));
+ }
+ (_, _) => {
+ self.pending_styles = self.pending_styles.italic(Some(AnsiCode::Code((None, None))));
+ }
+ };
+ } else if params[0] == 4 {
+ // underline
+ match (params.get(1), params.get(2)) {
+ (Some(param1), Some(param2)) => {
+ self.pending_styles = self.pending_styles.underline(Some(AnsiCode::Code((Some(*param1 as u16), Some(*param2 as u16)))));
+ },
+ (Some(param1), None) => {
+ self.pending_styles = self.pending_styles.underline(Some(AnsiCode::Code((Some(*param1 as u16), None))));
+ }
+ (_, _) => {
+ self.pending_styles = self.pending_styles.underline(Some(AnsiCode::Code((None, None))));
+ }
+ };
+ } else if params[0] == 5 {
+ // blink slow
+ match (params.get(1), params.get(2)) {
+ (Some(param1), Some(param2)) => {
+ self.pending_styles = self.pending_styles.blink_slow(Some(AnsiCode::Code((Some(*param1 as u16), Some(*param2 as u16)))));
+ },
+ (Some(param1), None) => {
+ self.pending_styles = self.pending_styles.blink_slow(Some(AnsiCode::Code((Some(*param1 as u16), None))));
+ }
+ (_, _) => {
+ self.pending_styles = self.pending_styles.blink_slow(Some(AnsiCode::Code((None, None))));
+ }
+ };
+ } else if params[0] == 6 {
+ // blink fast
+ match (params.get(1), params.get(2)) {
+ (Some(param1), Some(param2)) => {
+ self.pending_styles = self.pending_styles.blink_fast(Some(AnsiCode::Code((Some(*param1 as u16), Some(*param2 as u16)))));
+ },
+ (Some(param1), None) => {
+ self.pending_styles = self.pending_styles.blink_fast(Some(AnsiCode::Code((Some(*param1 as u16), None))));
+ }
+ (_, _) => {
+ self.pending_styles = self.pending_styles.blink_fast(Some(AnsiCode::Code((None, None))));
+ }
+ };
+ } else if params[0] == 7 {
+ // reverse
+ match (params.get(1), params.get(2)) {
+ (Some(param1), Some(param2)) => {
+ self.pending_styles = self.pending_styles.reverse(Some(AnsiCode::Code((Some(*param1 as u16), Some(*param2 as u16)))));
+ },
+ (Some(param1), None) => {
+ self.pending_styles = self.pending_styles.reverse(Some(AnsiCode::Code((Some(*param1 as u16), None))));
+ }
+ (_, _) => {
+ self.pending_styles = self.pending_styles.reverse(Some(AnsiCode::Code((None, None))));
+ }
+ };
+ } else if params[0] == 8 {
+ // hidden
+ match (params.get(1), params.get(2)) {
+ (Some(param1), Some(param2)) => {
+ self.pending_styles = self.pending_styles.hidden(Some(AnsiCode::Code((Some(*param1 as u16), Some(*param2 as u16)))));
+ },
+ (Some(param1), None) => {
+ self.pending_styles = self.pending_styles.hidden(Some(AnsiCode::Code((Some(*param1 as u16), None))));
+ }
+ (_, _) => {
+ self.pending_styles = self.pending_styles.hidden(Some(AnsiCode::Code((None, None))));
+ }
+ };
+ } else if params[0] == 9 {
+ // strike
+ match (params.get(1), params.get(2)) {
+ (Some(param1), Some(param2)) => {
+ self.pending_styles = self.pending_styles.strike(Some(AnsiCode::Code((Some(*param1 as u16), Some(*param2 as u16)))));
+ },
+ (Some(param1), None) => {
+ self.pending_styles = self.pending_styles.strike(Some(AnsiCode::Code((Some(*param1 as u16), None))));
+ }
+ (_, _) => {
+ self.pending_styles = self.pending_styles.strike(Some(AnsiCode::Code((None, None))));
+ }
+ };
+ } else if params[0] == 30 {
+ self.pending_styles = self.pending_styles.foreground(Some(AnsiCode::NamedColor(NamedColor::Black)));
+ } else if params[0] == 31 {
+ self.pending_styles = self.pending_styles.foreground(Some(AnsiCode::NamedColor(NamedColor::Red)));
+ } else if params[0] == 32 {
+ self.pending_styles = self.pending_styles.foreground(Some(AnsiCode::NamedColor(NamedColor::Green)));
+ } else if params[0] == 33 {
+ self.pending_styles = self.pending_styles.foreground(Some(AnsiCode::NamedColor(NamedColor::Yellow)));
+ } else if params[0] == 34 {
+ self.pending_styles = self.pending_styles.foreground(Some(AnsiCode::NamedColor(NamedColor::Blue)));
+ } else if params[0] == 35 {
+ self.pending_styles = self.pending_styles.foreground(Some(AnsiCode::NamedColor(NamedColor::Magenta)));
+ } else if params[0] == 36 {
+ self.pending_styles = self.pending_styles.foreground(Some(AnsiCode::NamedColor(NamedColor::Cyan)));
+ } else if params[0] == 37 {
+ self.pending_styles = self.pending_styles.foreground(Some(AnsiCode::NamedColor(NamedColor::White)));
+ } else if params[0] == 40 {
+ self.pending_styles = self.pending_styles.background(Some(AnsiCode::NamedColor(NamedColor::Black)));
+ } else if params[0] == 41 {
+ self.pending_styles = self.pending_styles.background(Some(AnsiCode::NamedColor(NamedColor::Red)));
+ } else if params[0] == 42 {
+ self.pending_styles = self.pending_styles.background(Some(AnsiCode::NamedColor(NamedColor::Green)));
+ } else if params[0] == 43 {
+ self.pending_styles = self.pending_styles.background(Some(AnsiCode::NamedColor(NamedColor::Yellow)));
+ } else if params[0] == 44 {
+ self.pending_styles = self.pending_styles.background(Some(AnsiCode::NamedColor(NamedColor::Blue)));
+ } else if params[0] == 45 {
+ self.pending_styles = self.pending_styles.background(Some(AnsiCode::NamedColor(NamedColor::Magenta)));
+ } else if params[0] == 46 {
+ self.pending_styles = self.pending_styles.background(Some(AnsiCode::NamedColor(NamedColor::Cyan)));
+ } else if params[0] == 47 {
+ self.pending_styles = self.pending_styles.background(Some(AnsiCode::NamedColor(NamedColor::White)));
+ } else {
+ debug_log_to_file(format!("unhandled csi m code {:?}", params), self.pid);
+ }
+ } else if c == 'C' { // move cursor forward
+ let move_by = 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();
+ }
+ // TODO: implement 1 and 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
+ let (row, col) = if params.len() == 1 {
+ (params[0] as usize, 0) // TODO: is this always correct ?
+ } else {
+ (params[0] as usize - 1, params[1] as usize - 1) // we subtract 1 here because this csi is 1 indexed and we index from 0
+ };
+ 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 == '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' {
+ // TBD
+ } else if c == 'h' {
+ // TBD
+ } else if c == 'r' {
+ if params.len() > 1 {
+ let top_line_index = params[0] as usize;
+ let bottom_line_index = params[1] as usize;
+ self.scroll.set_scroll_region(top_line_index, bottom_line_index);
+ } 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' || c == 'd' || c == 'X' || c == 'G' {
+ // ignore for now to run on mac
+ } else {
+ panic!("unhandled csi: {:?}->{:?}", c, params);
+ }
+ }
+
+ fn esc_dispatch(&mut self, _intermediates: &[u8], _ignore: bool, _byte: u8) {
+ // TBD
+ }
+}
diff --git a/src/tests/utils.rs b/src/tests/utils.rs
index dd0c2409..1e76b31f 100644
--- a/src/tests/utils.rs
+++ b/src/tests/utils.rs
@@ -1,12 +1,12 @@
use ::nix::pty::Winsize;
-use crate::TerminalOutput;
+use crate::terminal_pane::TerminalPane;
pub fn get_output_frame_snapshots(output_frames: &[Vec<u8>], win_size: &Winsize) -> Vec<String> {
let mut vte_parser = vte::Parser::new();
let main_pid = 0;
let x = 0;
let y = 0;
- let mut terminal_output = TerminalOutput::new(main_pid, *win_size, x, y);
+ let mut terminal_output = TerminalPane::new(main_pid, *win_size, x, y);
let mut snapshots = vec![];
for frame in output_frames.iter() {