Abandoning this attempt, nto good enough at async

Mikayla Maki created

Change summary

Cargo.lock                                         |   4 
crates/terminal/Cargo.toml                         |   2 
crates/terminal/src/connected_el.rs                |  14 
crates/terminal/src/connected_view.rs              |  46 +
crates/terminal/src/terminal.rs                    | 349 ++++++++++-----
crates/terminal/src/terminal_view.rs               |   4 
crates/terminal/src/tests/terminal_test_context.rs |   4 
7 files changed, 264 insertions(+), 159 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -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",

crates/terminal/Cargo.toml 🔗

@@ -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" }

crates/terminal/src/connected_el.rs 🔗

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

crates/terminal/src/connected_view.rs 🔗

@@ -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(

crates/terminal/src/terminal.rs 🔗

@@ -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![];
-
-                    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,
-                        }
-                    }
+            use futures::StreamExt;
+
+            //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.);
-
-        //2nd power
-        let scaled_velocity = buffer_velocity * buffer_velocity;
-
-        self.frames_to_skip = (scaled_velocity * (Self::default_fps() / 10.)).round() as usize;
+        self.utilization = Self::estimate_utilization(term.take_last_processed_bytes());
 
-        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));

crates/terminal/src/terminal_view.rs 🔗

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

crates/terminal/src/tests/terminal_test_context.rs 🔗

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