feat(performance): better tty read buffering and less allocations when rendering
This commit is contained in:
parent
049ff1a6dc
commit
e50e9770fd
3 changed files with 128 additions and 429 deletions
|
|
@ -21,6 +21,14 @@ use ::signal_hook::iterator::Signals;
|
||||||
pub type OnSigWinch = dyn Fn(Box<dyn Fn()>) + Send;
|
pub type OnSigWinch = dyn Fn(Box<dyn Fn()>) + Send;
|
||||||
pub type SigCleanup = dyn Fn() + Send;
|
pub type SigCleanup = dyn Fn() + Send;
|
||||||
|
|
||||||
|
fn debug_log_to_file (message: String) {
|
||||||
|
use std::fs::OpenOptions;
|
||||||
|
use std::io::prelude::*;
|
||||||
|
let mut file = OpenOptions::new().append(true).create(true).open("/tmp/mosaic-log.txt").unwrap();
|
||||||
|
file.write_all(message.as_bytes()).unwrap();
|
||||||
|
file.write_all("\n".as_bytes()).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
pub fn sigwinch() -> (Box<OnSigWinch>, Box<SigCleanup>) {
|
pub fn sigwinch() -> (Box<OnSigWinch>, Box<SigCleanup>) {
|
||||||
let signals = Signals::new(&[signal_hook::SIGWINCH]).unwrap();
|
let signals = Signals::new(&[signal_hook::SIGWINCH]).unwrap();
|
||||||
let on_winch = {
|
let on_winch = {
|
||||||
|
|
|
||||||
107
src/pty_bus.rs
107
src/pty_bus.rs
|
|
@ -4,6 +4,7 @@ use ::async_std::task;
|
||||||
use ::async_std::task::*;
|
use ::async_std::task::*;
|
||||||
use ::std::pin::*;
|
use ::std::pin::*;
|
||||||
use ::std::sync::mpsc::{channel, Sender, Receiver};
|
use ::std::sync::mpsc::{channel, Sender, Receiver};
|
||||||
|
use ::std::time::{Instant, Duration};
|
||||||
use ::vte;
|
use ::vte;
|
||||||
|
|
||||||
use crate::os_input_output::OsApi;
|
use crate::os_input_output::OsApi;
|
||||||
|
|
@ -23,10 +24,18 @@ impl ReadFromPid {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn debug_log_to_file (message: String) {
|
||||||
|
use std::fs::OpenOptions;
|
||||||
|
use std::io::prelude::*;
|
||||||
|
let mut file = OpenOptions::new().append(true).create(true).open("/tmp/mosaic-log.txt").unwrap();
|
||||||
|
file.write_all(message.as_bytes()).unwrap();
|
||||||
|
file.write_all("\n".as_bytes()).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
impl Stream for ReadFromPid {
|
impl Stream for ReadFromPid {
|
||||||
type Item = Vec<u8>;
|
type Item = Vec<u8>;
|
||||||
fn poll_next(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
fn poll_next(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||||
let mut read_buffer = [0; 115200];
|
let mut read_buffer = [0; 65535];
|
||||||
let pid = self.pid;
|
let pid = self.pid;
|
||||||
let read_result = &self.os_input.read_from_tty_stdout(pid, &mut read_buffer);
|
let read_result = &self.os_input.read_from_tty_stdout(pid, &mut read_buffer);
|
||||||
match read_result {
|
match read_result {
|
||||||
|
|
@ -136,6 +145,60 @@ pub struct PtyBus {
|
||||||
os_input: Box<dyn OsApi>,
|
os_input: Box<dyn OsApi>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn stream_terminal_bytes(pid: RawFd, send_screen_instructions: Sender<ScreenInstruction>, os_input: Box<dyn OsApi>) {
|
||||||
|
task::spawn({
|
||||||
|
async move {
|
||||||
|
let mut vte_parser = vte::Parser::new();
|
||||||
|
let mut vte_event_sender = VteEventSender::new(pid, send_screen_instructions.clone());
|
||||||
|
let mut terminal_bytes = ReadFromPid::new(&pid, os_input);
|
||||||
|
|
||||||
|
let mut last_byte_receive_time: Option<Instant> = None;
|
||||||
|
let mut pending_render = false;
|
||||||
|
let max_render_pause = Duration::from_millis(30);
|
||||||
|
|
||||||
|
while let Some(bytes) = terminal_bytes.next().await {
|
||||||
|
let bytes_is_empty = bytes.is_empty();
|
||||||
|
for byte in bytes {
|
||||||
|
vte_parser.advance(&mut vte_event_sender, byte);
|
||||||
|
}
|
||||||
|
if !bytes_is_empty {
|
||||||
|
// for UX reasons, if we got something on the wire, we only send the render notice if:
|
||||||
|
// 1. there aren't any more bytes on the wire afterwards
|
||||||
|
// 2. a certain period (currently 30ms) has elapsed since the last render
|
||||||
|
// (otherwise if we get a large amount of data, the display would hang
|
||||||
|
// until it's done)
|
||||||
|
// 3. the stream has ended, and so we render 1 last time
|
||||||
|
match last_byte_receive_time.as_mut() {
|
||||||
|
Some(receive_time) => {
|
||||||
|
// if receive_time.elapsed() > Duration::from_millis(30) {
|
||||||
|
if receive_time.elapsed() > max_render_pause {
|
||||||
|
pending_render = false;
|
||||||
|
send_screen_instructions.send(ScreenInstruction::Render).unwrap();
|
||||||
|
last_byte_receive_time = Some(Instant::now());
|
||||||
|
} else {
|
||||||
|
pending_render = true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
last_byte_receive_time = Some(Instant::now());
|
||||||
|
pending_render = true;
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
if pending_render {
|
||||||
|
pending_render = false;
|
||||||
|
send_screen_instructions.send(ScreenInstruction::Render).unwrap();
|
||||||
|
}
|
||||||
|
last_byte_receive_time = None;
|
||||||
|
task::sleep(::std::time::Duration::from_millis(10)).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
send_screen_instructions.send(ScreenInstruction::Render).unwrap();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
impl PtyBus {
|
impl PtyBus {
|
||||||
pub fn new (send_screen_instructions: Sender<ScreenInstruction>, os_input: Box<dyn OsApi>) -> Self {
|
pub fn new (send_screen_instructions: Sender<ScreenInstruction>, os_input: Box<dyn OsApi>) -> Self {
|
||||||
let (send_pty_instructions, receive_pty_instructions): (Sender<PtyInstruction>, Receiver<PtyInstruction>) = channel();
|
let (send_pty_instructions, receive_pty_instructions): (Sender<PtyInstruction>, Receiver<PtyInstruction>) = channel();
|
||||||
|
|
@ -148,50 +211,12 @@ impl PtyBus {
|
||||||
}
|
}
|
||||||
pub fn spawn_terminal_vertically(&mut self) {
|
pub fn spawn_terminal_vertically(&mut self) {
|
||||||
let (pid_primary, _pid_secondary): (RawFd, RawFd) = self.os_input.spawn_terminal();
|
let (pid_primary, _pid_secondary): (RawFd, RawFd) = self.os_input.spawn_terminal();
|
||||||
task::spawn({
|
stream_terminal_bytes(pid_primary, self.send_screen_instructions.clone(), self.os_input.clone());
|
||||||
let send_screen_instructions = self.send_screen_instructions.clone();
|
|
||||||
let os_input = self.os_input.clone();
|
|
||||||
async move {
|
|
||||||
let mut vte_parser = vte::Parser::new();
|
|
||||||
let mut vte_event_sender = VteEventSender::new(pid_primary, send_screen_instructions.clone());
|
|
||||||
let mut first_terminal_bytes = ReadFromPid::new(&pid_primary, os_input);
|
|
||||||
while let Some(bytes) = first_terminal_bytes.next().await {
|
|
||||||
let bytes_is_empty = bytes.is_empty();
|
|
||||||
for byte in bytes {
|
|
||||||
vte_parser.advance(&mut vte_event_sender, byte);
|
|
||||||
}
|
|
||||||
if !bytes_is_empty {
|
|
||||||
send_screen_instructions.send(ScreenInstruction::Render).unwrap();
|
|
||||||
} else {
|
|
||||||
task::sleep(::std::time::Duration::from_millis(10)).await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
self.send_screen_instructions.send(ScreenInstruction::VerticalSplit(pid_primary)).unwrap();
|
self.send_screen_instructions.send(ScreenInstruction::VerticalSplit(pid_primary)).unwrap();
|
||||||
}
|
}
|
||||||
pub fn spawn_terminal_horizontally(&mut self) {
|
pub fn spawn_terminal_horizontally(&mut self) {
|
||||||
let (pid_primary, _pid_secondary): (RawFd, RawFd) = self.os_input.spawn_terminal();
|
let (pid_primary, _pid_secondary): (RawFd, RawFd) = self.os_input.spawn_terminal();
|
||||||
task::spawn({
|
stream_terminal_bytes(pid_primary, self.send_screen_instructions.clone(), self.os_input.clone());
|
||||||
let send_screen_instructions = self.send_screen_instructions.clone();
|
|
||||||
let os_input = self.os_input.clone();
|
|
||||||
async move {
|
|
||||||
let mut vte_parser = vte::Parser::new();
|
|
||||||
let mut vte_event_sender = VteEventSender::new(pid_primary, send_screen_instructions.clone());
|
|
||||||
let mut first_terminal_bytes = ReadFromPid::new(&pid_primary, os_input);
|
|
||||||
while let Some(bytes) = first_terminal_bytes.next().await {
|
|
||||||
let bytes_is_empty = bytes.is_empty();
|
|
||||||
for byte in bytes {
|
|
||||||
vte_parser.advance(&mut vte_event_sender, byte);
|
|
||||||
}
|
|
||||||
if !bytes_is_empty {
|
|
||||||
send_screen_instructions.send(ScreenInstruction::Render).unwrap();
|
|
||||||
} else {
|
|
||||||
task::sleep(::std::time::Duration::from_millis(10)).await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
self.send_screen_instructions.send(ScreenInstruction::HorizontalSplit(pid_primary)).unwrap();
|
self.send_screen_instructions.send(ScreenInstruction::HorizontalSplit(pid_primary)).unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,17 +10,19 @@ use crate::boundaries::Rect;
|
||||||
|
|
||||||
const EMPTY_TERMINAL_CHARACTER: TerminalCharacter = TerminalCharacter {
|
const EMPTY_TERMINAL_CHARACTER: TerminalCharacter = TerminalCharacter {
|
||||||
character: ' ',
|
character: ' ',
|
||||||
foreground: Some(AnsiCode::Reset),
|
styles: CharacterStyles {
|
||||||
background: Some(AnsiCode::Reset),
|
foreground: Some(AnsiCode::Reset),
|
||||||
strike: Some(AnsiCode::Reset),
|
background: Some(AnsiCode::Reset),
|
||||||
hidden: Some(AnsiCode::Reset),
|
strike: Some(AnsiCode::Reset),
|
||||||
reverse: Some(AnsiCode::Reset),
|
hidden: Some(AnsiCode::Reset),
|
||||||
slow_blink: Some(AnsiCode::Reset),
|
reverse: Some(AnsiCode::Reset),
|
||||||
fast_blink: Some(AnsiCode::Reset),
|
slow_blink: Some(AnsiCode::Reset),
|
||||||
underline: Some(AnsiCode::Reset),
|
fast_blink: Some(AnsiCode::Reset),
|
||||||
bold: Some(AnsiCode::Reset),
|
underline: Some(AnsiCode::Reset),
|
||||||
dim: Some(AnsiCode::Reset),
|
bold: Some(AnsiCode::Reset),
|
||||||
italic: Some(AnsiCode::Reset),
|
dim: Some(AnsiCode::Reset),
|
||||||
|
italic: Some(AnsiCode::Reset),
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
|
|
@ -157,7 +159,7 @@ impl CharacterStyles {
|
||||||
self.dim = None;
|
self.dim = None;
|
||||||
self.italic = None;
|
self.italic = None;
|
||||||
}
|
}
|
||||||
pub fn update_and_return_diff(&mut self, new_styles: &TerminalCharacter) -> Option<CharacterStyles> {
|
pub fn update_and_return_diff(&mut self, new_styles: &CharacterStyles) -> Option<CharacterStyles> {
|
||||||
let mut diff: Option<CharacterStyles> = None;
|
let mut diff: Option<CharacterStyles> = None;
|
||||||
if self.foreground != new_styles.foreground {
|
if self.foreground != new_styles.foreground {
|
||||||
if let Some(new_diff) = diff.as_mut() {
|
if let Some(new_diff) = diff.as_mut() {
|
||||||
|
|
@ -535,356 +537,7 @@ impl Display for CharacterStyles {
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
pub struct TerminalCharacter {
|
pub struct TerminalCharacter {
|
||||||
pub character: char,
|
pub character: char,
|
||||||
pub foreground: Option<AnsiCode>,
|
pub styles: CharacterStyles,
|
||||||
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 TerminalCharacter {
|
|
||||||
pub fn new (character: char) -> Self {
|
|
||||||
TerminalCharacter {
|
|
||||||
character,
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for TerminalCharacter {
|
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
|
||||||
let mut character_ansi_codes = String::new(); // TODO: better
|
|
||||||
|
|
||||||
if let Some(ansi_code) = self.foreground {
|
|
||||||
match ansi_code {
|
|
||||||
AnsiCode::Code((param1, param2)) => {
|
|
||||||
match (param1, param2) {
|
|
||||||
(Some(param1), Some(param2)) => {
|
|
||||||
character_ansi_codes.push_str(&format!("\u{1b}[38;{};{}m", param1, param2));
|
|
||||||
},
|
|
||||||
(Some(param1), None) => {
|
|
||||||
character_ansi_codes.push_str(&format!("\u{1b}[38;{}m", param1));
|
|
||||||
},
|
|
||||||
(_, _) => {
|
|
||||||
// TODO: can this happen?
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
AnsiCode::Reset => {
|
|
||||||
character_ansi_codes.push_str(&format!("\u{1b}[39m"));
|
|
||||||
},
|
|
||||||
AnsiCode::NamedColor(named_color) => {
|
|
||||||
character_ansi_codes.push_str(&format!("\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)) => {
|
|
||||||
character_ansi_codes.push_str(&format!("\u{1b}[48;{};{}m", param1, param2));
|
|
||||||
},
|
|
||||||
(Some(param1), None) => {
|
|
||||||
character_ansi_codes.push_str(&format!("\u{1b}[48;{}m", param1));
|
|
||||||
},
|
|
||||||
(_, _) => {
|
|
||||||
// TODO: can this happen?
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
AnsiCode::Reset => {
|
|
||||||
character_ansi_codes.push_str(&format!("\u{1b}[49m"));
|
|
||||||
}
|
|
||||||
AnsiCode::NamedColor(named_color) => {
|
|
||||||
character_ansi_codes.push_str(&format!("\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)) => {
|
|
||||||
character_ansi_codes.push_str(&format!("\u{1b}[9;{};{}m", param1, param2));
|
|
||||||
},
|
|
||||||
(Some(param1), None) => {
|
|
||||||
character_ansi_codes.push_str(&format!("\u{1b}[9;{}m", param1));
|
|
||||||
},
|
|
||||||
(_, _) => {
|
|
||||||
character_ansi_codes.push_str("\u{1b}[9m");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
AnsiCode::Reset => {
|
|
||||||
character_ansi_codes.push_str(&format!("\u{1b}[29m"));
|
|
||||||
},
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if let Some(ansi_code) = self.hidden {
|
|
||||||
match ansi_code {
|
|
||||||
AnsiCode::Code((param1, param2)) => {
|
|
||||||
match (param1, param2) {
|
|
||||||
(Some(param1), Some(param2)) => {
|
|
||||||
character_ansi_codes.push_str(&format!("\u{1b}[8;{};{}m", param1, param2));
|
|
||||||
},
|
|
||||||
(Some(param1), None) => {
|
|
||||||
character_ansi_codes.push_str(&format!("\u{1b}[8;{}m", param1));
|
|
||||||
},
|
|
||||||
(_, _) => {
|
|
||||||
character_ansi_codes.push_str("\u{1b}[8m");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
AnsiCode::Reset => {
|
|
||||||
character_ansi_codes.push_str(&format!("\u{1b}[28m"));
|
|
||||||
},
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if let Some(ansi_code) = self.reverse {
|
|
||||||
match ansi_code {
|
|
||||||
AnsiCode::Code((param1, param2)) => {
|
|
||||||
match (param1, param2) {
|
|
||||||
(Some(param1), Some(param2)) => {
|
|
||||||
character_ansi_codes.push_str(&format!("\u{1b}[7;{};{}m", param1, param2));
|
|
||||||
},
|
|
||||||
(Some(param1), None) => {
|
|
||||||
character_ansi_codes.push_str(&format!("\u{1b}[7;{}m", param1));
|
|
||||||
},
|
|
||||||
(_, _) => {
|
|
||||||
character_ansi_codes.push_str("\u{1b}[7m");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
AnsiCode::Reset => {
|
|
||||||
character_ansi_codes.push_str(&format!("\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)) => {
|
|
||||||
character_ansi_codes.push_str(&format!("\u{1b}[6;{};{}m", param1, param2));
|
|
||||||
},
|
|
||||||
(Some(param1), None) => {
|
|
||||||
character_ansi_codes.push_str(&format!("\u{1b}[6;{}m", param1));
|
|
||||||
},
|
|
||||||
(_, _) => {
|
|
||||||
character_ansi_codes.push_str("\u{1b}[6m");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
AnsiCode::Reset => {
|
|
||||||
character_ansi_codes.push_str(&format!("\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)) => {
|
|
||||||
character_ansi_codes.push_str(&format!("\u{1b}[5;{};{}m", param1, param2));
|
|
||||||
},
|
|
||||||
(Some(param1), None) => {
|
|
||||||
character_ansi_codes.push_str(&format!("\u{1b}[5;{}m", param1));
|
|
||||||
},
|
|
||||||
(_, _) => {
|
|
||||||
character_ansi_codes.push_str("\u{1b}[5m");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
AnsiCode::Reset => {
|
|
||||||
character_ansi_codes.push_str(&format!("\u{1b}[25m"));
|
|
||||||
},
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if let Some(ansi_code) = self.underline {
|
|
||||||
match ansi_code {
|
|
||||||
AnsiCode::Code((param1, param2)) => {
|
|
||||||
match (param1, param2) {
|
|
||||||
(Some(param1), Some(param2)) => {
|
|
||||||
character_ansi_codes.push_str(&format!("\u{1b}[4;{};{}m", param1, param2));
|
|
||||||
},
|
|
||||||
(Some(param1), None) => {
|
|
||||||
character_ansi_codes.push_str(&format!("\u{1b}[4;{}m", param1));
|
|
||||||
},
|
|
||||||
(_, _) => {
|
|
||||||
character_ansi_codes.push_str("\u{1b}[4m");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
AnsiCode::Reset => {
|
|
||||||
character_ansi_codes.push_str(&format!("\u{1b}[24m"));
|
|
||||||
},
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if let Some(ansi_code) = self.bold {
|
|
||||||
match ansi_code {
|
|
||||||
AnsiCode::Code((param1, param2)) => {
|
|
||||||
match (param1, param2) {
|
|
||||||
(Some(param1), Some(param2)) => {
|
|
||||||
character_ansi_codes.push_str(&format!("\u{1b}[1;{};{}m", param1, param2));
|
|
||||||
},
|
|
||||||
(Some(param1), None) => {
|
|
||||||
character_ansi_codes.push_str(&format!("\u{1b}[1;{}m", param1));
|
|
||||||
},
|
|
||||||
(_, _) => {
|
|
||||||
character_ansi_codes.push_str("\u{1b}[1m");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
AnsiCode::Reset => {
|
|
||||||
character_ansi_codes.push_str(&format!("\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)) => {
|
|
||||||
character_ansi_codes.push_str(&format!("\u{1b}[4;{};{}m", param1, param2));
|
|
||||||
},
|
|
||||||
(Some(param1), None) => {
|
|
||||||
character_ansi_codes.push_str(&format!("\u{1b}[4;{}m", param1));
|
|
||||||
},
|
|
||||||
(_, _) => {
|
|
||||||
character_ansi_codes.push_str("\u{1b}[4m");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
AnsiCode::Reset => {
|
|
||||||
character_ansi_codes.push_str(&format!("\u{1b}[24m"));
|
|
||||||
},
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if let Some(ansi_code) = self.dim {
|
|
||||||
match ansi_code {
|
|
||||||
AnsiCode::Code((param1, param2)) => {
|
|
||||||
match (param1, param2) {
|
|
||||||
(Some(param1), Some(param2)) => {
|
|
||||||
character_ansi_codes.push_str(&format!("\u{1b}[2;{};{}m", param1, param2));
|
|
||||||
},
|
|
||||||
(Some(param1), None) => {
|
|
||||||
character_ansi_codes.push_str(&format!("\u{1b}[2;{}m", param1));
|
|
||||||
},
|
|
||||||
(_, _) => {
|
|
||||||
character_ansi_codes.push_str("\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 => character_ansi_codes.push_str(&format!("\u{1b}[22m")), // TODO: better
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if let Some(ansi_code) = self.italic {
|
|
||||||
match ansi_code {
|
|
||||||
AnsiCode::Code((param1, param2)) => {
|
|
||||||
match (param1, param2) {
|
|
||||||
(Some(param1), Some(param2)) => {
|
|
||||||
character_ansi_codes.push_str(&format!("\u{1b}[3;{};{}m", param1, param2));
|
|
||||||
},
|
|
||||||
(Some(param1), None) => {
|
|
||||||
character_ansi_codes.push_str(&format!("\u{1b}[3;{}m", param1));
|
|
||||||
},
|
|
||||||
(_, _) => {
|
|
||||||
character_ansi_codes.push_str("\u{1b}[3m");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
AnsiCode::Reset => {
|
|
||||||
character_ansi_codes.push_str(&format!("\u{1b}[23m"));
|
|
||||||
},
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
write!(f, "{}", character_ansi_codes)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ::std::fmt::Debug for TerminalCharacter {
|
impl ::std::fmt::Debug for TerminalCharacter {
|
||||||
|
|
@ -978,7 +631,7 @@ impl<'a> Grid <'a>{
|
||||||
let mut newline_indices: Vec<usize> = vec![];
|
let mut newline_indices: Vec<usize> = vec![];
|
||||||
for line in &self.cells {
|
for line in &self.cells {
|
||||||
for character in line.iter().copied() {
|
for character in line.iter().copied() {
|
||||||
characters.push(character.clone());
|
characters.push(*character);
|
||||||
}
|
}
|
||||||
let last_newline_index = newline_indices.last().copied().unwrap_or(0);
|
let last_newline_index = newline_indices.last().copied().unwrap_or(0);
|
||||||
newline_indices.push(last_newline_index + line.len());
|
newline_indices.push(last_newline_index + line.len());
|
||||||
|
|
@ -1046,9 +699,10 @@ impl Rect for &mut TerminalOutput {
|
||||||
|
|
||||||
impl TerminalOutput {
|
impl TerminalOutput {
|
||||||
pub fn new (pid: RawFd, ws: Winsize, x_coords: u16, y_coords: u16) -> TerminalOutput {
|
pub fn new (pid: RawFd, ws: Winsize, x_coords: u16, y_coords: u16) -> TerminalOutput {
|
||||||
|
let characters = Vec::with_capacity(100000);
|
||||||
TerminalOutput {
|
TerminalOutput {
|
||||||
pid,
|
pid,
|
||||||
characters: vec![],
|
characters,
|
||||||
cursor_position: 0,
|
cursor_position: 0,
|
||||||
newline_indices: Vec::new(),
|
newline_indices: Vec::new(),
|
||||||
linebreak_indices: Vec::new(),
|
linebreak_indices: Vec::new(),
|
||||||
|
|
@ -1073,10 +727,11 @@ impl TerminalOutput {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn handle_event(&mut self, event: VteEvent) {
|
pub fn handle_event(&mut self, event: VteEvent) {
|
||||||
self.should_render = true; // TODO: more accurately
|
// self.should_render = true; // TODO: more accurately
|
||||||
match event {
|
match event {
|
||||||
VteEvent::Print(c) => {
|
VteEvent::Print(c) => {
|
||||||
self.print(c);
|
self.print(c);
|
||||||
|
self.should_render = true; // TODO: more accurately
|
||||||
},
|
},
|
||||||
VteEvent::Execute(byte) => {
|
VteEvent::Execute(byte) => {
|
||||||
self.execute(byte);
|
self.execute(byte);
|
||||||
|
|
@ -1188,7 +843,7 @@ impl TerminalOutput {
|
||||||
// in some cases (eg. while resizing) some characters will spill over
|
// in some cases (eg. while resizing) some characters will spill over
|
||||||
// before they are corrected by the shell (for the prompt) or by reflowing
|
// before they are corrected by the shell (for the prompt) or by reflowing
|
||||||
// lines
|
// lines
|
||||||
if let Some(new_styles) = character_styles.update_and_return_diff(&t_character) {
|
if let Some(new_styles) = character_styles.update_and_return_diff(&t_character.styles) {
|
||||||
vte_output = format!("{}{}", vte_output, new_styles);
|
vte_output = format!("{}{}", vte_output, new_styles);
|
||||||
}
|
}
|
||||||
vte_output.push(t_character.character);
|
vte_output.push(t_character.character);
|
||||||
|
|
@ -1495,36 +1150,47 @@ fn debug_log_to_file (message: String, pid: RawFd) {
|
||||||
|
|
||||||
impl vte::Perform for TerminalOutput {
|
impl vte::Perform for TerminalOutput {
|
||||||
fn print(&mut self, c: char) {
|
fn print(&mut self, c: char) {
|
||||||
// while not ideal that we separate the reset and actual code logic here,
|
// apparently, building TerminalCharacter like this without a "new" method
|
||||||
// combining them is a question of rendering performance and not refactoring,
|
// is a little faster
|
||||||
// so will be addressed separately
|
let terminal_character = TerminalCharacter {
|
||||||
let terminal_character = TerminalCharacter::new(c)
|
character: c,
|
||||||
.foreground(self.pending_foreground_code)
|
styles: CharacterStyles {
|
||||||
.background(self.pending_background_code)
|
foreground: self.pending_foreground_code,
|
||||||
.bold(self.pending_bold_code)
|
background: self.pending_background_code,
|
||||||
.dim(self.pending_dim_code)
|
bold: self.pending_bold_code,
|
||||||
.italic(self.pending_italic_code)
|
dim: self.pending_dim_code,
|
||||||
.underline(self.pending_underline_code)
|
italic: self.pending_italic_code,
|
||||||
.blink_slow(self.pending_slow_blink_code)
|
underline: self.pending_underline_code,
|
||||||
.blink_fast(self.pending_fast_blink_code)
|
slow_blink: self.pending_slow_blink_code,
|
||||||
.reverse(self.pending_reverse_code)
|
fast_blink: self.pending_fast_blink_code,
|
||||||
.hidden(self.pending_hidden_code)
|
reverse: self.pending_reverse_code,
|
||||||
.strike(self.pending_strike_code);
|
hidden: self.pending_hidden_code,
|
||||||
|
strike: self.pending_strike_code,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
if self.characters.len() > self.cursor_position {
|
let length_of_characters = self.characters.len();
|
||||||
self.characters.remove(self.cursor_position);
|
let current_character_capacity = self.characters.capacity();
|
||||||
self.characters.insert(self.cursor_position, terminal_character);
|
|
||||||
|
if current_character_capacity <= self.characters.len() {
|
||||||
|
self.characters.reserve(current_character_capacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
if length_of_characters > self.cursor_position {
|
||||||
|
// this is a little hacky but significantly more performant
|
||||||
|
// than removing self.cursor_position and then inserting terminal_character
|
||||||
|
self.characters.push(terminal_character);
|
||||||
|
self.characters.swap_remove(self.cursor_position);
|
||||||
if !self.newline_indices.contains(&(self.cursor_position + 1)) {
|
if !self.newline_indices.contains(&(self.cursor_position + 1)) {
|
||||||
// advancing the cursor beyond the borders of the line has to be done explicitly
|
// advancing the cursor beyond the borders of the line has to be done explicitly
|
||||||
self.cursor_position += 1;
|
self.cursor_position += 1;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for _ in self.characters.len()..self.cursor_position {
|
for _ in length_of_characters..self.cursor_position {
|
||||||
self.characters.push(EMPTY_TERMINAL_CHARACTER.clone());
|
self.characters.push(EMPTY_TERMINAL_CHARACTER);
|
||||||
};
|
};
|
||||||
self.characters.push(terminal_character);
|
self.characters.push(terminal_character);
|
||||||
|
let start_of_last_line = max(self.newline_indices.last(), self.linebreak_indices.last()).unwrap_or(&0);
|
||||||
let start_of_last_line = self.index_of_beginning_of_line(self.cursor_position);
|
|
||||||
let difference_from_last_newline = self.cursor_position - start_of_last_line;
|
let difference_from_last_newline = self.cursor_position - start_of_last_line;
|
||||||
if difference_from_last_newline == self.display_cols as usize && self.scroll_region.is_none() {
|
if difference_from_last_newline == self.display_cols as usize && self.scroll_region.is_none() {
|
||||||
self.linebreak_indices.push(self.cursor_position);
|
self.linebreak_indices.push(self.cursor_position);
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue