Abandoning this attempt, nto good enough at async
This commit is contained in:
parent
8471af5a7d
commit
05cc78d929
7 changed files with 263 additions and 158 deletions
4
Cargo.lock
generated
4
Cargo.lock
generated
|
@ -62,7 +62,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "alacritty_config_derive"
|
name = "alacritty_config_derive"
|
||||||
version = "0.1.0"
|
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 = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
@ -72,7 +72,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "alacritty_terminal"
|
name = "alacritty_terminal"
|
||||||
version = "0.17.0-dev"
|
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 = [
|
dependencies = [
|
||||||
"alacritty_config_derive",
|
"alacritty_config_derive",
|
||||||
"base64 0.13.0",
|
"base64 0.13.0",
|
||||||
|
|
|
@ -8,7 +8,7 @@ path = "src/terminal.rs"
|
||||||
doctest = false
|
doctest = false
|
||||||
|
|
||||||
[dependencies]
|
[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" }
|
editor = { path = "../editor" }
|
||||||
util = { path = "../util" }
|
util = { path = "../util" }
|
||||||
gpui = { path = "../gpui" }
|
gpui = { path = "../gpui" }
|
||||||
|
|
|
@ -32,7 +32,7 @@ use std::{
|
||||||
use std::{fmt::Debug, ops::Sub};
|
use std::{fmt::Debug, ops::Sub};
|
||||||
|
|
||||||
use crate::{
|
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
|
///Scrolling is unbearably sluggish by default. Alacritty supports a configurable
|
||||||
|
@ -48,7 +48,7 @@ pub struct LayoutState {
|
||||||
cursor: Option<Cursor>,
|
cursor: Option<Cursor>,
|
||||||
background_color: Color,
|
background_color: Color,
|
||||||
selection_color: Color,
|
selection_color: Color,
|
||||||
size: TermDimensions,
|
size: TerminalSize,
|
||||||
display_offset: usize,
|
display_offset: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -319,7 +319,7 @@ impl TerminalEl {
|
||||||
// the same position for sequential indexes. Use em_width instead
|
// the same position for sequential indexes. Use em_width instead
|
||||||
fn shape_cursor(
|
fn shape_cursor(
|
||||||
cursor_point: DisplayCursor,
|
cursor_point: DisplayCursor,
|
||||||
size: TermDimensions,
|
size: TerminalSize,
|
||||||
text_fragment: &Line,
|
text_fragment: &Line,
|
||||||
) -> Option<(Vector2F, f32)> {
|
) -> Option<(Vector2F, f32)> {
|
||||||
if cursor_point.line() < size.total_lines() as i32 {
|
if cursor_point.line() < size.total_lines() as i32 {
|
||||||
|
@ -372,7 +372,7 @@ impl TerminalEl {
|
||||||
origin: Vector2F,
|
origin: Vector2F,
|
||||||
view_id: usize,
|
view_id: usize,
|
||||||
visible_bounds: RectF,
|
visible_bounds: RectF,
|
||||||
cur_size: TermDimensions,
|
cur_size: TerminalSize,
|
||||||
display_offset: usize,
|
display_offset: usize,
|
||||||
cx: &mut PaintContext,
|
cx: &mut PaintContext,
|
||||||
) {
|
) {
|
||||||
|
@ -482,7 +482,7 @@ impl TerminalEl {
|
||||||
pub fn mouse_to_cell_data(
|
pub fn mouse_to_cell_data(
|
||||||
pos: Vector2F,
|
pos: Vector2F,
|
||||||
origin: Vector2F,
|
origin: Vector2F,
|
||||||
cur_size: TermDimensions,
|
cur_size: TerminalSize,
|
||||||
display_offset: usize,
|
display_offset: usize,
|
||||||
) -> (Point, alacritty_terminal::index::Direction) {
|
) -> (Point, alacritty_terminal::index::Direction) {
|
||||||
let pos = pos.sub(origin);
|
let pos = pos.sub(origin);
|
||||||
|
@ -540,7 +540,7 @@ impl Element for TerminalEl {
|
||||||
let dimensions = {
|
let dimensions = {
|
||||||
let line_height = font_cache.line_height(text_style.font_size);
|
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);
|
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 {
|
let background_color = if self.modal {
|
||||||
|
@ -807,7 +807,7 @@ mod test {
|
||||||
let origin_x = 10.;
|
let origin_x = 10.;
|
||||||
let origin_y = 20.;
|
let origin_y = 20.;
|
||||||
|
|
||||||
let cur_size = crate::connected_el::TermDimensions::new(
|
let cur_size = crate::connected_el::TerminalSize::new(
|
||||||
line_height,
|
line_height,
|
||||||
cell_width,
|
cell_width,
|
||||||
gpui::geometry::vector::vec2f(term_width, term_height),
|
gpui::geometry::vector::vec2f(term_width, term_height),
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
use alacritty_terminal::term::TermMode;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions, keymap::Keystroke, AppContext, Element, ElementBox, ModelHandle, MutableAppContext,
|
actions, keymap::Keystroke, AppContext, Element, ElementBox, ModelHandle, MutableAppContext,
|
||||||
View, ViewContext,
|
View, ViewContext,
|
||||||
|
@ -98,43 +99,43 @@ impl ConnectedView {
|
||||||
///Attempt to paste the clipboard into the terminal
|
///Attempt to paste the clipboard into the terminal
|
||||||
fn paste(&mut self, _: &Paste, cx: &mut ViewContext<Self>) {
|
fn paste(&mut self, _: &Paste, cx: &mut ViewContext<Self>) {
|
||||||
cx.read_from_clipboard().map(|item| {
|
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'
|
///Synthesize the keyboard event corresponding to 'up'
|
||||||
fn up(&mut self, _: &Up, cx: &mut ViewContext<Self>) {
|
fn up(&mut self, _: &Up, cx: &mut ViewContext<Self>) {
|
||||||
self.terminal.update(cx, |term, _| {
|
self.terminal
|
||||||
term.try_keystroke(&Keystroke::parse("up").unwrap());
|
.read(cx)
|
||||||
});
|
.try_keystroke(&Keystroke::parse("up").unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
///Synthesize the keyboard event corresponding to 'down'
|
///Synthesize the keyboard event corresponding to 'down'
|
||||||
fn down(&mut self, _: &Down, cx: &mut ViewContext<Self>) {
|
fn down(&mut self, _: &Down, cx: &mut ViewContext<Self>) {
|
||||||
self.terminal.update(cx, |term, _| {
|
self.terminal
|
||||||
term.try_keystroke(&Keystroke::parse("down").unwrap());
|
.read(cx)
|
||||||
});
|
.try_keystroke(&Keystroke::parse("down").unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
///Synthesize the keyboard event corresponding to 'ctrl-c'
|
///Synthesize the keyboard event corresponding to 'ctrl-c'
|
||||||
fn ctrl_c(&mut self, _: &CtrlC, cx: &mut ViewContext<Self>) {
|
fn ctrl_c(&mut self, _: &CtrlC, cx: &mut ViewContext<Self>) {
|
||||||
self.terminal.update(cx, |term, _| {
|
self.terminal
|
||||||
term.try_keystroke(&Keystroke::parse("ctrl-c").unwrap());
|
.read(cx)
|
||||||
});
|
.try_keystroke(&Keystroke::parse("ctrl-c").unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
///Synthesize the keyboard event corresponding to 'escape'
|
///Synthesize the keyboard event corresponding to 'escape'
|
||||||
fn escape(&mut self, _: &Escape, cx: &mut ViewContext<Self>) {
|
fn escape(&mut self, _: &Escape, cx: &mut ViewContext<Self>) {
|
||||||
self.terminal.update(cx, |term, _| {
|
self.terminal
|
||||||
term.try_keystroke(&Keystroke::parse("escape").unwrap());
|
.read(cx)
|
||||||
});
|
.try_keystroke(&Keystroke::parse("escape").unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
///Synthesize the keyboard event corresponding to 'enter'
|
///Synthesize the keyboard event corresponding to 'enter'
|
||||||
fn enter(&mut self, _: &Enter, cx: &mut ViewContext<Self>) {
|
fn enter(&mut self, _: &Enter, cx: &mut ViewContext<Self>) {
|
||||||
self.terminal.update(cx, |term, _| {
|
self.terminal
|
||||||
term.try_keystroke(&Keystroke::parse("enter").unwrap());
|
.read(cx)
|
||||||
});
|
.try_keystroke(&Keystroke::parse("enter").unwrap());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -154,8 +155,17 @@ impl View for ConnectedView {
|
||||||
self.has_new_content = false;
|
self.has_new_content = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn selected_text_range(&self, _: &AppContext) -> Option<std::ops::Range<usize>> {
|
fn selected_text_range(&self, cx: &AppContext) -> Option<std::ops::Range<usize>> {
|
||||||
Some(0..0)
|
if self
|
||||||
|
.terminal
|
||||||
|
.read(cx)
|
||||||
|
.last_mode
|
||||||
|
.contains(TermMode::ALT_SCREEN)
|
||||||
|
{
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(0..0)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn replace_text_in_range(
|
fn replace_text_in_range(
|
||||||
|
|
|
@ -24,18 +24,27 @@ use alacritty_terminal::{
|
||||||
};
|
};
|
||||||
use anyhow::{bail, Result};
|
use anyhow::{bail, Result};
|
||||||
|
|
||||||
use futures::channel::mpsc::{unbounded, UnboundedReceiver, UnboundedSender};
|
use futures::{
|
||||||
use mappings::keys::might_convert;
|
channel::mpsc::{unbounded, UnboundedReceiver, UnboundedSender},
|
||||||
|
future,
|
||||||
|
};
|
||||||
|
|
||||||
use modal::deploy_modal;
|
use modal::deploy_modal;
|
||||||
use settings::{Settings, Shell};
|
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 terminal_view::TerminalView;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use gpui::{
|
use gpui::{
|
||||||
geometry::vector::{vec2f, Vector2F},
|
geometry::vector::{vec2f, Vector2F},
|
||||||
keymap::Keystroke,
|
keymap::Keystroke,
|
||||||
ClipboardItem, CursorStyle, Entity, ModelContext, MutableAppContext,
|
AsyncAppContext, ClipboardItem, Entity, ModelContext, MutableAppContext, WeakModelHandle,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::mappings::{
|
use crate::mappings::{
|
||||||
|
@ -71,10 +80,8 @@ pub enum Event {
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
enum InternalEvent {
|
enum InternalEvent {
|
||||||
TermEvent(AlacTermEvent),
|
TermEvent(AlacTermEvent),
|
||||||
Resize(TermDimensions),
|
Resize(TerminalSize),
|
||||||
Clear,
|
Clear,
|
||||||
Keystroke(Keystroke),
|
|
||||||
Paste(String),
|
|
||||||
Scroll(Scroll),
|
Scroll(Scroll),
|
||||||
SetSelection(Option<Selection>),
|
SetSelection(Option<Selection>),
|
||||||
UpdateSelection((Point, Direction)),
|
UpdateSelection((Point, Direction)),
|
||||||
|
@ -92,16 +99,16 @@ impl EventListener for ZedListener {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub struct TermDimensions {
|
pub struct TerminalSize {
|
||||||
cell_width: f32,
|
cell_width: f32,
|
||||||
line_height: f32,
|
line_height: f32,
|
||||||
height: f32,
|
height: f32,
|
||||||
width: f32,
|
width: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TermDimensions {
|
impl TerminalSize {
|
||||||
pub fn new(line_height: f32, cell_width: f32, size: Vector2F) -> Self {
|
pub fn new(line_height: f32, cell_width: f32, size: Vector2F) -> Self {
|
||||||
TermDimensions {
|
TerminalSize {
|
||||||
cell_width,
|
cell_width,
|
||||||
line_height,
|
line_height,
|
||||||
width: size.x(),
|
width: size.x(),
|
||||||
|
@ -133,9 +140,9 @@ impl TermDimensions {
|
||||||
self.line_height
|
self.line_height
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl Default for TermDimensions {
|
impl Default for TerminalSize {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
TermDimensions::new(
|
TerminalSize::new(
|
||||||
DEBUG_LINE_HEIGHT,
|
DEBUG_LINE_HEIGHT,
|
||||||
DEBUG_CELL_WIDTH,
|
DEBUG_CELL_WIDTH,
|
||||||
vec2f(DEBUG_TERMINAL_WIDTH, DEBUG_TERMINAL_HEIGHT),
|
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 {
|
fn into(self) -> WindowSize {
|
||||||
WindowSize {
|
WindowSize {
|
||||||
num_lines: self.num_lines() as u16,
|
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 {
|
fn total_lines(&self) -> usize {
|
||||||
self.screen_lines() //TODO: Check that this is fine. This is supposed to be for the back buffer...
|
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 {
|
pub struct TerminalBuilder {
|
||||||
terminal: Terminal,
|
terminal: Terminal,
|
||||||
events_rx: UnboundedReceiver<AlacTermEvent>,
|
events_rx: UnboundedReceiver<AlacTermEvent>,
|
||||||
|
@ -259,7 +306,7 @@ impl TerminalBuilder {
|
||||||
working_directory: Option<PathBuf>,
|
working_directory: Option<PathBuf>,
|
||||||
shell: Option<Shell>,
|
shell: Option<Shell>,
|
||||||
env: Option<HashMap<String, String>>,
|
env: Option<HashMap<String, String>>,
|
||||||
initial_size: TermDimensions,
|
initial_size: TerminalSize,
|
||||||
) -> Result<TerminalBuilder> {
|
) -> Result<TerminalBuilder> {
|
||||||
let pty_config = {
|
let pty_config = {
|
||||||
let alac_shell = shell.clone().and_then(|shell| match shell {
|
let alac_shell = shell.clone().and_then(|shell| match shell {
|
||||||
|
@ -299,7 +346,7 @@ impl TerminalBuilder {
|
||||||
let term = Arc::new(FairMutex::new(term));
|
let term = Arc::new(FairMutex::new(term));
|
||||||
|
|
||||||
//Setup the pty...
|
//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,
|
Ok(pty) => pty,
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
bail!(TerminalError {
|
bail!(TerminalError {
|
||||||
|
@ -340,11 +387,12 @@ impl TerminalBuilder {
|
||||||
let terminal = Terminal {
|
let terminal = Terminal {
|
||||||
pty_tx: Notifier(pty_tx),
|
pty_tx: Notifier(pty_tx),
|
||||||
term,
|
term,
|
||||||
|
|
||||||
events: vec![],
|
events: vec![],
|
||||||
title: shell_txt.clone(),
|
title: shell_txt.clone(),
|
||||||
default_title: shell_txt,
|
default_title: shell_txt,
|
||||||
frames_to_skip: 0,
|
last_mode: TermMode::NONE,
|
||||||
|
cur_size: initial_size,
|
||||||
|
utilization: 0.,
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(TerminalBuilder {
|
Ok(TerminalBuilder {
|
||||||
|
@ -353,45 +401,93 @@ impl TerminalBuilder {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn subscribe(mut self, cx: &mut ModelContext<Terminal>) -> Terminal {
|
pub fn subscribe(self, cx: &mut ModelContext<Terminal>) -> Terminal {
|
||||||
let mut frames_to_skip = 0;
|
//Event loop
|
||||||
cx.spawn_weak(|this, mut cx| async move {
|
cx.spawn_weak(|this, mut cx| async move {
|
||||||
'outer: loop {
|
use futures::StreamExt;
|
||||||
let delay = cx
|
|
||||||
.background()
|
|
||||||
.timer(Duration::from_secs_f32(1.0 / Terminal::default_fps()));
|
|
||||||
if frames_to_skip == 0 {
|
|
||||||
let mut events = vec![];
|
|
||||||
|
|
||||||
loop {
|
//Throttle guard
|
||||||
match self.events_rx.try_next() {
|
let mut guard: Option<Arc<ThrottleGuard>> = None;
|
||||||
//Have a buffered event
|
|
||||||
Ok(Some(e)) => events.push(e),
|
self.events_rx
|
||||||
//Channel closed, exit
|
.for_each(|event| {
|
||||||
Ok(None) => break 'outer,
|
|
||||||
//Ran out of buffered events
|
|
||||||
Err(_) => break,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
match this.upgrade(&cx) {
|
match this.upgrade(&cx) {
|
||||||
Some(this) => {
|
Some(this) => {
|
||||||
this.update(&mut cx, |this, cx| {
|
this.update(&mut cx, |this, cx| {
|
||||||
this.push_events(events);
|
//Process the event
|
||||||
frames_to_skip = this.frames_to_skip();
|
this.process_event(&event, cx);
|
||||||
cx.notify();
|
|
||||||
|
//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 {
|
future::ready(())
|
||||||
frames_to_skip = frames_to_skip - 1;
|
})
|
||||||
}
|
.await;
|
||||||
|
|
||||||
delay.await;
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.detach();
|
.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
|
self.terminal
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -402,7 +498,10 @@ pub struct Terminal {
|
||||||
events: Vec<InternalEvent>,
|
events: Vec<InternalEvent>,
|
||||||
default_title: String,
|
default_title: String,
|
||||||
title: String,
|
title: String,
|
||||||
frames_to_skip: usize,
|
cur_size: TerminalSize,
|
||||||
|
last_mode: TermMode,
|
||||||
|
//Percentage, between 0 and 1
|
||||||
|
utilization: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Terminal {
|
impl Terminal {
|
||||||
|
@ -410,16 +509,57 @@ impl Terminal {
|
||||||
MAX_FRAME_RATE
|
MAX_FRAME_RATE
|
||||||
}
|
}
|
||||||
|
|
||||||
///Tells the render loop how many frames to skip before reading from the terminal.
|
fn utilization(&self) -> f32 {
|
||||||
fn frames_to_skip(&self) -> usize {
|
self.utilization
|
||||||
0 //self.frames_to_skip
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn push_events(&mut self, events: Vec<AlacTermEvent>) {
|
fn process_event(&mut self, event: &AlacTermEvent, cx: &mut ModelContext<Self>) {
|
||||||
self.events
|
match event {
|
||||||
.extend(events.into_iter().map(|e| InternalEvent::TermEvent(e)))
|
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
|
///Takes events from Alacritty and translates them to behavior on this view
|
||||||
fn process_terminal_event(
|
fn process_terminal_event(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
@ -430,35 +570,7 @@ impl Terminal {
|
||||||
// TODO: Handle is_self_focused in subscription on terminal view
|
// TODO: Handle is_self_focused in subscription on terminal view
|
||||||
match event {
|
match event {
|
||||||
InternalEvent::TermEvent(term_event) => match term_event {
|
InternalEvent::TermEvent(term_event) => match term_event {
|
||||||
AlacTermEvent::Wakeup => {
|
//Needs to lock
|
||||||
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()),
|
|
||||||
)),
|
|
||||||
AlacTermEvent::ColorRequest(index, format) => {
|
AlacTermEvent::ColorRequest(index, format) => {
|
||||||
let color = term.colors()[*index].unwrap_or_else(|| {
|
let color = term.colors()[*index].unwrap_or_else(|| {
|
||||||
let term_style = &cx.global::<Settings>().theme.terminal;
|
let term_style = &cx.global::<Settings>().theme.terminal;
|
||||||
|
@ -466,18 +578,11 @@ impl Terminal {
|
||||||
});
|
});
|
||||||
self.notify_pty(format(color))
|
self.notify_pty(format(color))
|
||||||
}
|
}
|
||||||
AlacTermEvent::CursorBlinkingChange => {
|
_ => {} //Other events are handled in the event loop
|
||||||
//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")
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
InternalEvent::Resize(new_size) => {
|
InternalEvent::Resize(new_size) => {
|
||||||
|
self.cur_size = new_size.clone();
|
||||||
|
|
||||||
self.pty_tx
|
self.pty_tx
|
||||||
.0
|
.0
|
||||||
.send(Msg::Resize(new_size.clone().into()))
|
.send(Msg::Resize(new_size.clone().into()))
|
||||||
|
@ -489,21 +594,6 @@ impl Terminal {
|
||||||
self.notify_pty("\x0c".to_string());
|
self.notify_pty("\x0c".to_string());
|
||||||
term.clear_screen(ClearMode::Saved);
|
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::Scroll(scroll) => term.scroll_display(*scroll),
|
||||||
InternalEvent::SetSelection(sel) => term.selection = sel.clone(),
|
InternalEvent::SetSelection(sel) => term.selection = sel.clone(),
|
||||||
InternalEvent::UpdateSelection((point, side)) => {
|
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());
|
self.pty_tx.notify(txt.into_bytes());
|
||||||
}
|
}
|
||||||
|
|
||||||
///Write the Input payload to the tty.
|
///Write the Input payload to the tty.
|
||||||
pub fn write_to_pty(&mut self, input: String) {
|
pub fn write_to_pty(&mut self, input: String) {
|
||||||
self.events
|
self.pty_tx.notify(input.into_bytes());
|
||||||
.push(InternalEvent::TermEvent(AlacTermEvent::PtyWrite(input)))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
///Resize the terminal and the PTY.
|
///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()))
|
self.events.push(InternalEvent::Resize(new_size.into()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -540,10 +629,10 @@ impl Terminal {
|
||||||
self.events.push(InternalEvent::Clear)
|
self.events.push(InternalEvent::Clear)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn try_keystroke(&mut self, keystroke: &Keystroke) -> bool {
|
pub fn try_keystroke(&self, keystroke: &Keystroke) -> bool {
|
||||||
if might_convert(keystroke) {
|
let esc = to_esc_str(keystroke, &self.last_mode);
|
||||||
self.events
|
if let Some(esc) = esc {
|
||||||
.push(InternalEvent::Keystroke(keystroke.clone()));
|
self.notify_pty(esc);
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
|
@ -551,8 +640,14 @@ impl Terminal {
|
||||||
}
|
}
|
||||||
|
|
||||||
///Paste text into the terminal
|
///Paste text into the terminal
|
||||||
pub fn paste(&mut self, text: &str) {
|
pub fn paste(&self, text: &str) {
|
||||||
self.events.push(InternalEvent::Paste(text.to_string()));
|
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) {
|
pub fn copy(&mut self) {
|
||||||
|
@ -570,16 +665,9 @@ impl Terminal {
|
||||||
self.process_terminal_event(&e, &mut term, cx)
|
self.process_terminal_event(&e, &mut term, cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO: determine a better metric for this
|
self.utilization = Self::estimate_utilization(term.take_last_processed_bytes());
|
||||||
let buffer_velocity =
|
|
||||||
(term.last_processed_bytes() as f32 / (READ_BUFFER_SIZE as f32 / 4.)).clamp(0., 1.);
|
|
||||||
|
|
||||||
//2nd power
|
self.last_mode = term.mode().clone();
|
||||||
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.
|
|
||||||
|
|
||||||
let content = term.renderable_content();
|
let content = term.renderable_content();
|
||||||
|
|
||||||
|
@ -588,6 +676,13 @@ impl Terminal {
|
||||||
f(content, cursor_text)
|
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
|
///Scroll the terminal
|
||||||
pub fn scroll(&mut self, scroll: Scroll) {
|
pub fn scroll(&mut self, scroll: Scroll) {
|
||||||
self.events.push(InternalEvent::Scroll(scroll));
|
self.events.push(InternalEvent::Scroll(scroll));
|
||||||
|
|
|
@ -6,7 +6,7 @@ use gpui::{
|
||||||
ViewHandle,
|
ViewHandle,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::TermDimensions;
|
use crate::TerminalSize;
|
||||||
use project::{LocalWorktree, Project, ProjectPath};
|
use project::{LocalWorktree, Project, ProjectPath};
|
||||||
use settings::{Settings, WorkingDirectory};
|
use settings::{Settings, WorkingDirectory};
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
|
@ -72,7 +72,7 @@ impl TerminalView {
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
//The exact size here doesn't matter, the terminal will be resized on the first layout
|
//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 settings = cx.global::<Settings>();
|
||||||
let shell = settings.terminal_overrides.shell.clone();
|
let shell = settings.terminal_overrides.shell.clone();
|
||||||
|
|
|
@ -6,7 +6,7 @@ use itertools::Itertools;
|
||||||
use project::{Entry, Project, ProjectPath, Worktree};
|
use project::{Entry, Project, ProjectPath, Worktree};
|
||||||
use workspace::{AppState, Workspace};
|
use workspace::{AppState, Workspace};
|
||||||
|
|
||||||
use crate::{TermDimensions, Terminal, TerminalBuilder};
|
use crate::{Terminal, TerminalBuilder, TerminalSize};
|
||||||
|
|
||||||
pub struct TerminalTestContext<'a> {
|
pub struct TerminalTestContext<'a> {
|
||||||
pub cx: &'a mut TestAppContext,
|
pub cx: &'a mut TestAppContext,
|
||||||
|
@ -17,7 +17,7 @@ impl<'a> TerminalTestContext<'a> {
|
||||||
pub fn new(cx: &'a mut TestAppContext, term: bool) -> Self {
|
pub fn new(cx: &'a mut TestAppContext, term: bool) -> Self {
|
||||||
cx.set_condition_duration(Some(Duration::from_secs(5)));
|
cx.set_condition_duration(Some(Duration::from_secs(5)));
|
||||||
|
|
||||||
let size_info = TermDimensions::default();
|
let size_info = TerminalSize::default();
|
||||||
|
|
||||||
let connection = term.then(|| {
|
let connection = term.then(|| {
|
||||||
cx.add_model(|cx| {
|
cx.add_model(|cx| {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue