Abandoning this attempt, nto good enough at async

This commit is contained in:
Mikayla Maki 2022-08-01 16:47:16 -07:00
parent 8471af5a7d
commit 05cc78d929
7 changed files with 263 additions and 158 deletions

4
Cargo.lock generated
View file

@ -62,7 +62,7 @@ dependencies = [
[[package]]
name = "alacritty_config_derive"
version = "0.1.0"
source = "git+https://github.com/zed-industries/alacritty?rev=ba56f545e3e3606af0112c6bdfe998baf7faab50#ba56f545e3e3606af0112c6bdfe998baf7faab50"
source = "git+https://github.com/zed-industries/alacritty?rev=a274ff43c38c76f9766a908ff86a4e10a8998a6f#a274ff43c38c76f9766a908ff86a4e10a8998a6f"
dependencies = [
"proc-macro2",
"quote",
@ -72,7 +72,7 @@ dependencies = [
[[package]]
name = "alacritty_terminal"
version = "0.17.0-dev"
source = "git+https://github.com/zed-industries/alacritty?rev=ba56f545e3e3606af0112c6bdfe998baf7faab50#ba56f545e3e3606af0112c6bdfe998baf7faab50"
source = "git+https://github.com/zed-industries/alacritty?rev=a274ff43c38c76f9766a908ff86a4e10a8998a6f#a274ff43c38c76f9766a908ff86a4e10a8998a6f"
dependencies = [
"alacritty_config_derive",
"base64 0.13.0",

View file

@ -8,7 +8,7 @@ path = "src/terminal.rs"
doctest = false
[dependencies]
alacritty_terminal = { git = "https://github.com/zed-industries/alacritty", rev = "ba56f545e3e3606af0112c6bdfe998baf7faab50"}
alacritty_terminal = { git = "https://github.com/zed-industries/alacritty", rev = "a274ff43c38c76f9766a908ff86a4e10a8998a6f"}
editor = { path = "../editor" }
util = { path = "../util" }
gpui = { path = "../gpui" }

View file

@ -32,7 +32,7 @@ use std::{
use std::{fmt::Debug, ops::Sub};
use crate::{
connected_view::ConnectedView, mappings::colors::convert_color, TermDimensions, Terminal,
connected_view::ConnectedView, mappings::colors::convert_color, Terminal, TerminalSize,
};
///Scrolling is unbearably sluggish by default. Alacritty supports a configurable
@ -48,7 +48,7 @@ pub struct LayoutState {
cursor: Option<Cursor>,
background_color: Color,
selection_color: Color,
size: TermDimensions,
size: TerminalSize,
display_offset: usize,
}
@ -319,7 +319,7 @@ impl TerminalEl {
// the same position for sequential indexes. Use em_width instead
fn shape_cursor(
cursor_point: DisplayCursor,
size: TermDimensions,
size: TerminalSize,
text_fragment: &Line,
) -> Option<(Vector2F, f32)> {
if cursor_point.line() < size.total_lines() as i32 {
@ -372,7 +372,7 @@ impl TerminalEl {
origin: Vector2F,
view_id: usize,
visible_bounds: RectF,
cur_size: TermDimensions,
cur_size: TerminalSize,
display_offset: usize,
cx: &mut PaintContext,
) {
@ -482,7 +482,7 @@ impl TerminalEl {
pub fn mouse_to_cell_data(
pos: Vector2F,
origin: Vector2F,
cur_size: TermDimensions,
cur_size: TerminalSize,
display_offset: usize,
) -> (Point, alacritty_terminal::index::Direction) {
let pos = pos.sub(origin);
@ -540,7 +540,7 @@ impl Element for TerminalEl {
let dimensions = {
let line_height = font_cache.line_height(text_style.font_size);
let cell_width = font_cache.em_advance(text_style.font_id, text_style.font_size);
TermDimensions::new(line_height, cell_width, constraint.max)
TerminalSize::new(line_height, cell_width, constraint.max)
};
let background_color = if self.modal {
@ -807,7 +807,7 @@ mod test {
let origin_x = 10.;
let origin_y = 20.;
let cur_size = crate::connected_el::TermDimensions::new(
let cur_size = crate::connected_el::TerminalSize::new(
line_height,
cell_width,
gpui::geometry::vector::vec2f(term_width, term_height),

View file

@ -1,3 +1,4 @@
use alacritty_terminal::term::TermMode;
use gpui::{
actions, keymap::Keystroke, AppContext, Element, ElementBox, ModelHandle, MutableAppContext,
View, ViewContext,
@ -98,43 +99,43 @@ impl ConnectedView {
///Attempt to paste the clipboard into the terminal
fn paste(&mut self, _: &Paste, cx: &mut ViewContext<Self>) {
cx.read_from_clipboard().map(|item| {
self.terminal.update(cx, |term, _| term.paste(item.text()));
self.terminal.read(cx).paste(item.text());
});
}
///Synthesize the keyboard event corresponding to 'up'
fn up(&mut self, _: &Up, cx: &mut ViewContext<Self>) {
self.terminal.update(cx, |term, _| {
term.try_keystroke(&Keystroke::parse("up").unwrap());
});
self.terminal
.read(cx)
.try_keystroke(&Keystroke::parse("up").unwrap());
}
///Synthesize the keyboard event corresponding to 'down'
fn down(&mut self, _: &Down, cx: &mut ViewContext<Self>) {
self.terminal.update(cx, |term, _| {
term.try_keystroke(&Keystroke::parse("down").unwrap());
});
self.terminal
.read(cx)
.try_keystroke(&Keystroke::parse("down").unwrap());
}
///Synthesize the keyboard event corresponding to 'ctrl-c'
fn ctrl_c(&mut self, _: &CtrlC, cx: &mut ViewContext<Self>) {
self.terminal.update(cx, |term, _| {
term.try_keystroke(&Keystroke::parse("ctrl-c").unwrap());
});
self.terminal
.read(cx)
.try_keystroke(&Keystroke::parse("ctrl-c").unwrap());
}
///Synthesize the keyboard event corresponding to 'escape'
fn escape(&mut self, _: &Escape, cx: &mut ViewContext<Self>) {
self.terminal.update(cx, |term, _| {
term.try_keystroke(&Keystroke::parse("escape").unwrap());
});
self.terminal
.read(cx)
.try_keystroke(&Keystroke::parse("escape").unwrap());
}
///Synthesize the keyboard event corresponding to 'enter'
fn enter(&mut self, _: &Enter, cx: &mut ViewContext<Self>) {
self.terminal.update(cx, |term, _| {
term.try_keystroke(&Keystroke::parse("enter").unwrap());
});
self.terminal
.read(cx)
.try_keystroke(&Keystroke::parse("enter").unwrap());
}
}
@ -154,8 +155,17 @@ impl View for ConnectedView {
self.has_new_content = false;
}
fn selected_text_range(&self, _: &AppContext) -> Option<std::ops::Range<usize>> {
Some(0..0)
fn selected_text_range(&self, cx: &AppContext) -> Option<std::ops::Range<usize>> {
if self
.terminal
.read(cx)
.last_mode
.contains(TermMode::ALT_SCREEN)
{
None
} else {
Some(0..0)
}
}
fn replace_text_in_range(

View file

@ -24,18 +24,27 @@ use alacritty_terminal::{
};
use anyhow::{bail, Result};
use futures::channel::mpsc::{unbounded, UnboundedReceiver, UnboundedSender};
use mappings::keys::might_convert;
use futures::{
channel::mpsc::{unbounded, UnboundedReceiver, UnboundedSender},
future,
};
use modal::deploy_modal;
use settings::{Settings, Shell};
use std::{collections::HashMap, fmt::Display, path::PathBuf, sync::Arc, time::Duration};
use std::{
collections::HashMap,
fmt::Display,
path::PathBuf,
sync::Arc,
time::{Duration, Instant},
};
use terminal_view::TerminalView;
use thiserror::Error;
use gpui::{
geometry::vector::{vec2f, Vector2F},
keymap::Keystroke,
ClipboardItem, CursorStyle, Entity, ModelContext, MutableAppContext,
AsyncAppContext, ClipboardItem, Entity, ModelContext, MutableAppContext, WeakModelHandle,
};
use crate::mappings::{
@ -71,10 +80,8 @@ pub enum Event {
#[derive(Clone, Debug)]
enum InternalEvent {
TermEvent(AlacTermEvent),
Resize(TermDimensions),
Resize(TerminalSize),
Clear,
Keystroke(Keystroke),
Paste(String),
Scroll(Scroll),
SetSelection(Option<Selection>),
UpdateSelection((Point, Direction)),
@ -92,16 +99,16 @@ impl EventListener for ZedListener {
}
#[derive(Clone, Copy, Debug)]
pub struct TermDimensions {
pub struct TerminalSize {
cell_width: f32,
line_height: f32,
height: f32,
width: f32,
}
impl TermDimensions {
impl TerminalSize {
pub fn new(line_height: f32, cell_width: f32, size: Vector2F) -> Self {
TermDimensions {
TerminalSize {
cell_width,
line_height,
width: size.x(),
@ -133,9 +140,9 @@ impl TermDimensions {
self.line_height
}
}
impl Default for TermDimensions {
impl Default for TerminalSize {
fn default() -> Self {
TermDimensions::new(
TerminalSize::new(
DEBUG_LINE_HEIGHT,
DEBUG_CELL_WIDTH,
vec2f(DEBUG_TERMINAL_WIDTH, DEBUG_TERMINAL_HEIGHT),
@ -143,7 +150,7 @@ impl Default for TermDimensions {
}
}
impl Into<WindowSize> for TermDimensions {
impl Into<WindowSize> for TerminalSize {
fn into(self) -> WindowSize {
WindowSize {
num_lines: self.num_lines() as u16,
@ -154,7 +161,7 @@ impl Into<WindowSize> for TermDimensions {
}
}
impl Dimensions for TermDimensions {
impl Dimensions for TerminalSize {
fn total_lines(&self) -> usize {
self.screen_lines() //TODO: Check that this is fine. This is supposed to be for the back buffer...
}
@ -249,6 +256,46 @@ impl Display for TerminalError {
}
}
///This is a helper struct that represents a block on a
struct ThrottleGuard {
should_complete: bool,
length: Duration,
start: Instant,
}
impl ThrottleGuard {
fn new<T, F>(duration: Duration, cx: &mut ModelContext<T>, on_completion: F) -> Arc<Self>
where
T: Entity,
F: FnOnce(WeakModelHandle<T>, AsyncAppContext) -> () + 'static,
{
let selff = Arc::new(Self {
should_complete: false,
start: Instant::now(),
length: duration,
});
let moved_self = selff.clone();
cx.spawn_weak(|w, cx| async move {
cx.background().timer(duration).await;
if moved_self.should_complete {
on_completion(w, cx);
}
});
selff
}
fn activate(&mut self) {
self.should_complete = true;
}
fn is_done(&self) -> bool {
self.start.elapsed() > self.length
}
}
pub struct TerminalBuilder {
terminal: Terminal,
events_rx: UnboundedReceiver<AlacTermEvent>,
@ -259,7 +306,7 @@ impl TerminalBuilder {
working_directory: Option<PathBuf>,
shell: Option<Shell>,
env: Option<HashMap<String, String>>,
initial_size: TermDimensions,
initial_size: TerminalSize,
) -> Result<TerminalBuilder> {
let pty_config = {
let alac_shell = shell.clone().and_then(|shell| match shell {
@ -299,7 +346,7 @@ impl TerminalBuilder {
let term = Arc::new(FairMutex::new(term));
//Setup the pty...
let pty = match tty::new(&pty_config, initial_size.into(), None) {
let pty = match tty::new(&pty_config, initial_size.clone().into(), None) {
Ok(pty) => pty,
Err(error) => {
bail!(TerminalError {
@ -340,11 +387,12 @@ impl TerminalBuilder {
let terminal = Terminal {
pty_tx: Notifier(pty_tx),
term,
events: vec![],
title: shell_txt.clone(),
default_title: shell_txt,
frames_to_skip: 0,
last_mode: TermMode::NONE,
cur_size: initial_size,
utilization: 0.,
};
Ok(TerminalBuilder {
@ -353,45 +401,93 @@ impl TerminalBuilder {
})
}
pub fn subscribe(mut self, cx: &mut ModelContext<Terminal>) -> Terminal {
let mut frames_to_skip = 0;
pub fn subscribe(self, cx: &mut ModelContext<Terminal>) -> Terminal {
//Event loop
cx.spawn_weak(|this, mut cx| async move {
'outer: loop {
let delay = cx
.background()
.timer(Duration::from_secs_f32(1.0 / Terminal::default_fps()));
if frames_to_skip == 0 {
let mut events = vec![];
use futures::StreamExt;
loop {
match self.events_rx.try_next() {
//Have a buffered event
Ok(Some(e)) => events.push(e),
//Channel closed, exit
Ok(None) => break 'outer,
//Ran out of buffered events
Err(_) => break,
}
}
//Throttle guard
let mut guard: Option<Arc<ThrottleGuard>> = None;
self.events_rx
.for_each(|event| {
match this.upgrade(&cx) {
Some(this) => {
this.update(&mut cx, |this, cx| {
this.push_events(events);
frames_to_skip = this.frames_to_skip();
cx.notify();
//Process the event
this.process_event(&event, cx);
//Clean up the guard if it's expired
guard = match guard.take() {
Some(guard) => {
if guard.is_done() {
None
} else {
Some(guard)
}
}
None => None,
};
//Figure out whether to render or not.
if matches!(event, AlacTermEvent::Wakeup) {
if guard.is_none() {
cx.emit(Event::Wakeup);
cx.notify();
let dur = Duration::from_secs_f32(
1.0 / (Terminal::default_fps()
* (1. - this.utilization()).clamp(0.1, 1.)),
);
guard = Some(ThrottleGuard::new(dur, cx, |this, mut cx| {
match this.upgrade(&cx) {
Some(handle) => handle.update(&mut cx, |_, cx| {
cx.emit(Event::Wakeup);
cx.notify();
}),
None => {}
}
}))
} else {
let taken = guard.take().unwrap();
taken.activate();
guard = Some(taken);
}
}
});
}
None => break 'outer,
None => {}
}
} else {
frames_to_skip = frames_to_skip - 1;
}
delay.await;
}
future::ready(())
})
.await;
})
.detach();
// //Render loop
// cx.spawn_weak(|this, mut cx| async move {
// loop {
// let utilization = match this.upgrade(&cx) {
// Some(this) => this.update(&mut cx, |this, cx| {
// cx.emit(Event::Wakeup);
// cx.notify();
// this.utilization()
// }),
// None => break,
// };
// let utilization = (1. - this.utilization()).clamp(0.1, 1.);
// let delay = cx.background().timer(Duration::from_secs_f32(
// 1.0 / (Terminal::default_fps() * utilization),
// ));
// delay.await;
// }
// })
// .detach();
self.terminal
}
}
@ -402,7 +498,10 @@ pub struct Terminal {
events: Vec<InternalEvent>,
default_title: String,
title: String,
frames_to_skip: usize,
cur_size: TerminalSize,
last_mode: TermMode,
//Percentage, between 0 and 1
utilization: f32,
}
impl Terminal {
@ -410,16 +509,57 @@ impl Terminal {
MAX_FRAME_RATE
}
///Tells the render loop how many frames to skip before reading from the terminal.
fn frames_to_skip(&self) -> usize {
0 //self.frames_to_skip
fn utilization(&self) -> f32 {
self.utilization
}
fn push_events(&mut self, events: Vec<AlacTermEvent>) {
self.events
.extend(events.into_iter().map(|e| InternalEvent::TermEvent(e)))
fn process_event(&mut self, event: &AlacTermEvent, cx: &mut ModelContext<Self>) {
match event {
AlacTermEvent::Title(title) => {
self.title = title.to_string();
cx.emit(Event::TitleChanged);
}
AlacTermEvent::ResetTitle => {
self.title = self.default_title.clone();
cx.emit(Event::TitleChanged);
}
AlacTermEvent::ClipboardStore(_, data) => {
cx.write_to_clipboard(ClipboardItem::new(data.to_string()))
}
AlacTermEvent::ClipboardLoad(_, format) => self.notify_pty(format(
&cx.read_from_clipboard()
.map(|ci| ci.text().to_string())
.unwrap_or("".to_string()),
)),
AlacTermEvent::PtyWrite(out) => self.notify_pty(out.clone()),
AlacTermEvent::TextAreaSizeRequest(format) => {
self.notify_pty(format(self.cur_size.clone().into()))
}
AlacTermEvent::CursorBlinkingChange => {
//TODO whatever state we need to set to get the cursor blinking
}
AlacTermEvent::Bell => {
cx.emit(Event::Bell);
}
AlacTermEvent::Exit => cx.emit(Event::CloseTerminal),
AlacTermEvent::MouseCursorDirty => {
//NOOP, Handled in render
}
AlacTermEvent::Wakeup => {
//NOOP, Handled elsewhere
}
AlacTermEvent::ColorRequest(_, _) => {
self.events.push(InternalEvent::TermEvent(event.clone()))
}
}
}
// fn process_events(&mut self, events: Vec<AlacTermEvent>, cx: &mut ModelContext<Self>) {
// for event in events.into_iter() {
// self.process_event(&event, cx);
// }
// }
///Takes events from Alacritty and translates them to behavior on this view
fn process_terminal_event(
&mut self,
@ -430,35 +570,7 @@ impl Terminal {
// TODO: Handle is_self_focused in subscription on terminal view
match event {
InternalEvent::TermEvent(term_event) => match term_event {
AlacTermEvent::Wakeup => {
cx.emit(Event::Wakeup);
}
//TODO: Does not need to be in lock context
AlacTermEvent::PtyWrite(out) => self.notify_pty(out.clone()),
AlacTermEvent::MouseCursorDirty => {
//Calculate new cursor style.
//TODO: alacritty/src/input.rs:L922-L939
//Check on correctly handling mouse events for terminals
cx.platform().set_cursor_style(CursorStyle::Arrow); //???
}
AlacTermEvent::Title(title) => {
self.title = title.to_string();
cx.emit(Event::TitleChanged);
}
AlacTermEvent::ResetTitle => {
self.title = self.default_title.clone();
cx.emit(Event::TitleChanged);
}
//TODO: Does not need to be in lock context
AlacTermEvent::ClipboardStore(_, data) => {
cx.write_to_clipboard(ClipboardItem::new(data.to_string()))
}
//TODO: Does not need to be in lock context
AlacTermEvent::ClipboardLoad(_, format) => self.notify_pty(format(
&cx.read_from_clipboard()
.map(|ci| ci.text().to_string())
.unwrap_or("".to_string()),
)),
//Needs to lock
AlacTermEvent::ColorRequest(index, format) => {
let color = term.colors()[*index].unwrap_or_else(|| {
let term_style = &cx.global::<Settings>().theme.terminal;
@ -466,18 +578,11 @@ impl Terminal {
});
self.notify_pty(format(color))
}
AlacTermEvent::CursorBlinkingChange => {
//TODO: Set a timer to blink the cursor on and off
}
AlacTermEvent::Bell => {
cx.emit(Event::Bell);
}
AlacTermEvent::Exit => cx.emit(Event::CloseTerminal),
AlacTermEvent::TextAreaSizeRequest(_) => {
println!("Received text area resize request")
}
_ => {} //Other events are handled in the event loop
},
InternalEvent::Resize(new_size) => {
self.cur_size = new_size.clone();
self.pty_tx
.0
.send(Msg::Resize(new_size.clone().into()))
@ -489,21 +594,6 @@ impl Terminal {
self.notify_pty("\x0c".to_string());
term.clear_screen(ClearMode::Saved);
}
InternalEvent::Keystroke(keystroke) => {
let esc = to_esc_str(keystroke, term.mode());
if let Some(esc) = esc {
self.notify_pty(esc);
}
}
InternalEvent::Paste(text) => {
if term.mode().contains(TermMode::BRACKETED_PASTE) {
self.notify_pty("\x1b[200~".to_string());
self.notify_pty(text.replace('\x1b', "").to_string());
self.notify_pty("\x1b[201~".to_string());
} else {
self.notify_pty(text.replace("\r\n", "\r").replace('\n', "\r"));
}
}
InternalEvent::Scroll(scroll) => term.scroll_display(*scroll),
InternalEvent::SetSelection(sel) => term.selection = sel.clone(),
InternalEvent::UpdateSelection((point, side)) => {
@ -521,18 +611,17 @@ impl Terminal {
}
}
fn notify_pty(&self, txt: String) {
pub fn notify_pty(&self, txt: String) {
self.pty_tx.notify(txt.into_bytes());
}
///Write the Input payload to the tty.
pub fn write_to_pty(&mut self, input: String) {
self.events
.push(InternalEvent::TermEvent(AlacTermEvent::PtyWrite(input)))
self.pty_tx.notify(input.into_bytes());
}
///Resize the terminal and the PTY.
pub fn set_size(&mut self, new_size: TermDimensions) {
pub fn set_size(&mut self, new_size: TerminalSize) {
self.events.push(InternalEvent::Resize(new_size.into()))
}
@ -540,10 +629,10 @@ impl Terminal {
self.events.push(InternalEvent::Clear)
}
pub fn try_keystroke(&mut self, keystroke: &Keystroke) -> bool {
if might_convert(keystroke) {
self.events
.push(InternalEvent::Keystroke(keystroke.clone()));
pub fn try_keystroke(&self, keystroke: &Keystroke) -> bool {
let esc = to_esc_str(keystroke, &self.last_mode);
if let Some(esc) = esc {
self.notify_pty(esc);
true
} else {
false
@ -551,8 +640,14 @@ impl Terminal {
}
///Paste text into the terminal
pub fn paste(&mut self, text: &str) {
self.events.push(InternalEvent::Paste(text.to_string()));
pub fn paste(&self, text: &str) {
if self.last_mode.contains(TermMode::BRACKETED_PASTE) {
self.notify_pty("\x1b[200~".to_string());
self.notify_pty(text.replace('\x1b', "").to_string());
self.notify_pty("\x1b[201~".to_string());
} else {
self.notify_pty(text.replace("\r\n", "\r").replace('\n', "\r"));
}
}
pub fn copy(&mut self) {
@ -570,16 +665,9 @@ impl Terminal {
self.process_terminal_event(&e, &mut term, cx)
}
//TODO: determine a better metric for this
let buffer_velocity =
(term.last_processed_bytes() as f32 / (READ_BUFFER_SIZE as f32 / 4.)).clamp(0., 1.);
self.utilization = Self::estimate_utilization(term.take_last_processed_bytes());
//2nd power
let scaled_velocity = buffer_velocity * buffer_velocity;
self.frames_to_skip = (scaled_velocity * (Self::default_fps() / 10.)).round() as usize;
term.set_last_processed_bytes(0); //Clear it in case no reads between this lock and the next.
self.last_mode = term.mode().clone();
let content = term.renderable_content();
@ -588,6 +676,13 @@ impl Terminal {
f(content, cursor_text)
}
fn estimate_utilization(last_processed: usize) -> f32 {
let buffer_utilization = (last_processed as f32 / (READ_BUFFER_SIZE as f32)).clamp(0., 1.);
//Scale result to bias low, then high
buffer_utilization * buffer_utilization
}
///Scroll the terminal
pub fn scroll(&mut self, scroll: Scroll) {
self.events.push(InternalEvent::Scroll(scroll));

View file

@ -6,7 +6,7 @@ use gpui::{
ViewHandle,
};
use crate::TermDimensions;
use crate::TerminalSize;
use project::{LocalWorktree, Project, ProjectPath};
use settings::{Settings, WorkingDirectory};
use smallvec::SmallVec;
@ -72,7 +72,7 @@ impl TerminalView {
cx: &mut ViewContext<Self>,
) -> Self {
//The exact size here doesn't matter, the terminal will be resized on the first layout
let size_info = TermDimensions::default();
let size_info = TerminalSize::default();
let settings = cx.global::<Settings>();
let shell = settings.terminal_overrides.shell.clone();

View file

@ -6,7 +6,7 @@ use itertools::Itertools;
use project::{Entry, Project, ProjectPath, Worktree};
use workspace::{AppState, Workspace};
use crate::{TermDimensions, Terminal, TerminalBuilder};
use crate::{Terminal, TerminalBuilder, TerminalSize};
pub struct TerminalTestContext<'a> {
pub cx: &'a mut TestAppContext,
@ -17,7 +17,7 @@ impl<'a> TerminalTestContext<'a> {
pub fn new(cx: &'a mut TestAppContext, term: bool) -> Self {
cx.set_condition_duration(Some(Duration::from_secs(5)));
let size_info = TermDimensions::default();
let size_info = TerminalSize::default();
let connection = term.then(|| {
cx.add_model(|cx| {