Improved performance of terminal rendering further

This commit is contained in:
Mikayla Maki 2022-09-01 11:43:27 -07:00
parent a8b8003980
commit faad24542f
6 changed files with 193 additions and 131 deletions

View file

@ -11,17 +11,19 @@ use alacritty_terminal::{
event_loop::{EventLoop, Msg, Notifier},
grid::{Dimensions, Scroll as AlacScroll},
index::{Column, Direction, Line, Point},
selection::{Selection, SelectionType},
selection::{Selection, SelectionRange, SelectionType},
sync::FairMutex,
term::{
cell::Cell,
color::Rgb,
search::{Match, RegexIter, RegexSearch},
RenderableContent, TermMode,
RenderableCursor, TermMode,
},
tty::{self, setup_env},
Term,
};
use anyhow::{bail, Result};
use futures::{
channel::mpsc::{unbounded, UnboundedReceiver, UnboundedSender},
FutureExt,
@ -36,10 +38,10 @@ use settings::{AlternateScroll, Settings, Shell, TerminalBlink};
use std::{
collections::{HashMap, VecDeque},
fmt::Display,
ops::{RangeInclusive, Sub},
ops::{Deref, RangeInclusive, Sub},
path::PathBuf,
sync::Arc,
time::Duration,
time::{Duration, Instant},
};
use thiserror::Error;
@ -376,12 +378,12 @@ impl TerminalBuilder {
events: VecDeque::with_capacity(10), //Should never get this high.
title: shell_txt.clone(),
default_title: shell_txt,
last_mode: TermMode::NONE,
last_content: Default::default(),
cur_size: initial_size,
last_mouse: None,
last_offset: 0,
matches: Vec::new(),
selection_text: None,
last_synced: Instant::now(),
sync_task: None,
};
Ok(TerminalBuilder {
@ -443,18 +445,61 @@ impl TerminalBuilder {
}
}
#[derive(Debug, Clone)]
struct IndexedCell {
point: Point,
cell: Cell,
}
impl Deref for IndexedCell {
type Target = Cell;
#[inline]
fn deref(&self) -> &Cell {
&self.cell
}
}
#[derive(Clone)]
pub struct TerminalContent {
cells: Vec<IndexedCell>,
mode: TermMode,
display_offset: usize,
selection_text: Option<String>,
selection: Option<SelectionRange>,
cursor: RenderableCursor,
cursor_char: char,
}
impl Default for TerminalContent {
fn default() -> Self {
TerminalContent {
cells: Default::default(),
mode: Default::default(),
display_offset: Default::default(),
selection_text: Default::default(),
selection: Default::default(),
cursor: RenderableCursor {
shape: alacritty_terminal::ansi::CursorShape::Block,
point: Point::new(Line(0), Column(0)),
},
cursor_char: Default::default(),
}
}
}
pub struct Terminal {
pty_tx: Notifier,
term: Arc<FairMutex<Term<ZedListener>>>,
events: VecDeque<InternalEvent>,
default_title: String,
title: String,
cur_size: TerminalSize,
last_mode: TermMode,
last_offset: usize,
last_mouse: Option<(Point, Direction)>,
pub matches: Vec<RangeInclusive<Point>>,
pub selection_text: Option<String>,
cur_size: TerminalSize,
last_content: TerminalContent,
last_synced: Instant,
sync_task: Option<Task<()>>,
}
impl Terminal {
@ -576,6 +621,10 @@ impl Terminal {
}
}
pub fn last_content(&self) -> &TerminalContent {
&self.last_content
}
fn begin_select(&mut self, sel: Selection) {
self.events
.push_back(InternalEvent::SetSelection(Some(sel)));
@ -648,7 +697,7 @@ impl Terminal {
}
pub fn try_keystroke(&mut self, keystroke: &Keystroke) -> bool {
let esc = to_esc_str(keystroke, &self.last_mode);
let esc = to_esc_str(keystroke, &self.last_content.mode);
if let Some(esc) = esc {
self.input(esc);
true
@ -659,7 +708,7 @@ impl Terminal {
///Paste text into the terminal
pub fn paste(&mut self, text: &str) {
let paste_text = if self.last_mode.contains(TermMode::BRACKETED_PASTE) {
let paste_text = if self.last_content.mode.contains(TermMode::BRACKETED_PASTE) {
format!("{}{}{}", "\x1b[200~", text.replace('\x1b', ""), "\x1b[201~")
} else {
text.replace("\r\n", "\r").replace('\n', "\r")
@ -667,38 +716,76 @@ impl Terminal {
self.input(paste_text)
}
pub fn render_lock<F, T>(&mut self, cx: &mut ModelContext<Self>, f: F) -> T
where
F: FnOnce(RenderableContent, char) -> T,
{
pub fn try_sync(&mut self, cx: &mut ModelContext<Self>) {
let term = self.term.clone();
let mut term = term.lock();
let mut terminal = if let Some(term) = term.try_lock_unfair() {
term
} else if self.last_synced.elapsed().as_secs_f32() > 0.25 {
term.lock_unfair()
} else if let None = self.sync_task {
//Skip this frame
let delay = cx.background().timer(Duration::from_millis(16));
self.sync_task = Some(cx.spawn_weak(|weak_handle, mut cx| async move {
delay.await;
cx.update(|cx| {
if let Some(handle) = weak_handle.upgrade(cx) {
handle.update(cx, |terminal, cx| {
terminal.sync_task.take();
cx.notify();
});
}
});
}));
return;
} else {
//No lock and delayed rendering already scheduled, nothing to do
return;
};
//Note that this ordering matters for event processing
while let Some(e) = self.events.pop_front() {
self.process_terminal_event(&e, &mut term, cx)
self.process_terminal_event(&e, &mut terminal, cx)
}
self.last_mode = *term.mode();
self.last_content = Self::make_content(&terminal);
self.last_synced = Instant::now();
}
fn make_content(term: &Term<ZedListener>) -> TerminalContent {
let content = term.renderable_content();
self.selection_text = term.selection_to_string();
self.last_offset = content.display_offset;
let cursor_text = term.grid()[content.cursor.point].c;
f(content, cursor_text)
TerminalContent {
cells: content
.display_iter
//TODO: Add this once there's a way to retain empty lines
// .filter(|ic| {
// !ic.flags.contains(Flags::HIDDEN)
// && !(ic.bg == Named(NamedColor::Background)
// && ic.c == ' '
// && !ic.flags.contains(Flags::INVERSE))
// })
.map(|ic| IndexedCell {
point: ic.point,
cell: ic.cell.clone(),
})
.collect::<Vec<IndexedCell>>(),
mode: content.mode,
display_offset: content.display_offset,
selection_text: term.selection_to_string(),
selection: content.selection,
cursor: content.cursor,
cursor_char: term.grid()[content.cursor.point].c,
}
}
pub fn focus_in(&self) {
if self.last_mode.contains(TermMode::FOCUS_IN_OUT) {
if self.last_content.mode.contains(TermMode::FOCUS_IN_OUT) {
self.write_to_pty("\x1b[I".to_string());
}
}
pub fn focus_out(&self) {
if self.last_mode.contains(TermMode::FOCUS_IN_OUT) {
if self.last_content.mode.contains(TermMode::FOCUS_IN_OUT) {
self.write_to_pty("\x1b[O".to_string());
}
}
@ -721,17 +808,17 @@ impl Terminal {
}
pub fn mouse_mode(&self, shift: bool) -> bool {
self.last_mode.intersects(TermMode::MOUSE_MODE) && !shift
self.last_content.mode.intersects(TermMode::MOUSE_MODE) && !shift
}
pub fn mouse_move(&mut self, e: &MouseMovedEvent, origin: Vector2F) {
let position = e.position.sub(origin);
let point = mouse_point(position, self.cur_size, self.last_offset);
let point = mouse_point(position, self.cur_size, self.last_content.display_offset);
let side = mouse_side(position, self.cur_size);
if self.mouse_changed(point, side) && self.mouse_mode(e.shift) {
if let Some(bytes) = mouse_moved_report(point, e, self.last_mode) {
if let Some(bytes) = mouse_moved_report(point, e, self.last_content.mode) {
self.pty_tx.notify(bytes);
}
}
@ -746,7 +833,7 @@ impl Terminal {
self.continue_selection(position);
// Doesn't make sense to scroll the alt screen
if !self.last_mode.contains(TermMode::ALT_SCREEN) {
if !self.last_content.mode.contains(TermMode::ALT_SCREEN) {
let scroll_delta = match self.drag_line_delta(e) {
Some(value) => value,
None => return,
@ -775,11 +862,11 @@ impl Terminal {
pub fn mouse_down(&mut self, e: &DownRegionEvent, origin: Vector2F) {
let position = e.position.sub(origin);
let point = mouse_point(position, self.cur_size, self.last_offset);
let point = mouse_point(position, self.cur_size, self.last_content.display_offset);
let side = mouse_side(position, self.cur_size);
if self.mouse_mode(e.shift) {
if let Some(bytes) = mouse_button_report(point, e, true, self.last_mode) {
if let Some(bytes) = mouse_button_report(point, e, true, self.last_content.mode) {
self.pty_tx.notify(bytes);
}
} else if e.button == MouseButton::Left {
@ -791,7 +878,7 @@ impl Terminal {
let position = e.position.sub(origin);
if !self.mouse_mode(e.shift) {
let point = mouse_point(position, self.cur_size, self.last_offset);
let point = mouse_point(position, self.cur_size, self.last_content.display_offset);
let side = mouse_side(position, self.cur_size);
let selection_type = match e.click_count {
@ -814,9 +901,9 @@ impl Terminal {
pub fn mouse_up(&mut self, e: &UpRegionEvent, origin: Vector2F) {
let position = e.position.sub(origin);
if self.mouse_mode(e.shift) {
let point = mouse_point(position, self.cur_size, self.last_offset);
let point = mouse_point(position, self.cur_size, self.last_content.display_offset);
if let Some(bytes) = mouse_button_report(point, e, false, self.last_mode) {
if let Some(bytes) = mouse_button_report(point, e, false, self.last_content.mode) {
self.pty_tx.notify(bytes);
}
} else if e.button == MouseButton::Left {
@ -835,15 +922,22 @@ impl Terminal {
//The scroll enters 'TouchPhase::Started'. Do I need to replicate this?
//This would be consistent with a scroll model based on 'distance from origin'...
let scroll_lines = (e.delta.y() / self.cur_size.line_height) as i32;
let point = mouse_point(e.position.sub(origin), self.cur_size, self.last_offset);
let point = mouse_point(
e.position.sub(origin),
self.cur_size,
self.last_content.display_offset,
);
if let Some(scrolls) = scroll_report(point, scroll_lines as i32, e, self.last_mode) {
if let Some(scrolls) =
scroll_report(point, scroll_lines as i32, e, self.last_content.mode)
{
for scroll in scrolls {
self.pty_tx.notify(scroll);
}
};
} else if self
.last_mode
.last_content
.mode
.contains(TermMode::ALT_SCREEN | TermMode::ALTERNATE_SCROLL)
&& !e.shift
{
@ -868,7 +962,6 @@ impl Terminal {
cx: &mut ModelContext<Self>,
) -> Task<Vec<RangeInclusive<Point>>> {
let term = self.term.clone();
dbg!("Spawning find_matches");
cx.background().spawn(async move {
let searcher = match query {
project::search::SearchQuery::Text { query, .. } => {
@ -885,7 +978,8 @@ impl Terminal {
let searcher = searcher.unwrap();
let term = term.lock();
dbg!(make_search_matches(&term, &searcher).collect())
make_search_matches(&term, &searcher).collect()
})
}
}