Improved performance of terminal rendering further
This commit is contained in:
parent
a8b8003980
commit
faad24542f
6 changed files with 193 additions and 131 deletions
|
@ -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()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue