feat(performance): better tty read buffering and less allocations when rendering

This commit is contained in:
Aram Drevekenin 2020-10-19 11:55:22 +02:00
parent 049ff1a6dc
commit e50e9770fd
3 changed files with 128 additions and 429 deletions

View file

@ -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 = {

View file

@ -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();
} }
} }

View file

@ -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);