Merge branch 'main' into drag-and-drop

This commit is contained in:
K Simmons 2022-08-22 17:18:29 -07:00
commit 13e9336049
65 changed files with 2371 additions and 797 deletions

View file

@ -24,15 +24,20 @@ use futures::{
FutureExt,
};
use mappings::mouse::{
alt_scroll, mouse_button_report, mouse_moved_report, mouse_point, mouse_side, scroll_report,
};
use modal::deploy_modal;
use settings::{Settings, Shell};
use std::{collections::HashMap, fmt::Display, path::PathBuf, sync::Arc, time::Duration};
use settings::{AlternateScroll, Settings, Shell, TerminalBlink};
use std::{collections::HashMap, fmt::Display, ops::Sub, path::PathBuf, sync::Arc, time::Duration};
use thiserror::Error;
use gpui::{
geometry::vector::{vec2f, Vector2F},
keymap::Keystroke,
ClipboardItem, Entity, ModelContext, MutableAppContext,
scene::{ClickRegionEvent, DownRegionEvent, DragRegionEvent, UpRegionEvent},
ClipboardItem, Entity, ModelContext, MouseButton, MouseMovedEvent, MutableAppContext,
ScrollWheelEvent,
};
use crate::mappings::{
@ -42,18 +47,24 @@ use crate::mappings::{
///Initialize and register all of our action handlers
pub fn init(cx: &mut MutableAppContext) {
cx.add_action(deploy_modal);
let settings = cx.global::<Settings>();
if settings.experiments.modal_terminal() {
cx.add_action(deploy_modal);
}
terminal_view::init(cx);
connected_view::init(cx);
}
///Scrolling is unbearably sluggish by default. Alacritty supports a configurable
///Scroll multiplier that is set to 3 by default. This will be removed when I
///Implement scroll bars.
pub const ALACRITTY_SCROLL_MULTIPLIER: f32 = 3.;
const DEBUG_TERMINAL_WIDTH: f32 = 500.;
const DEBUG_TERMINAL_HEIGHT: f32 = 30.; //This needs to be wide enough that the CI & a local dev's prompt can fill the whole space.
const DEBUG_TERMINAL_HEIGHT: f32 = 30.;
const DEBUG_CELL_WIDTH: f32 = 5.;
const DEBUG_LINE_HEIGHT: f32 = 5.;
// const MAX_FRAME_RATE: f32 = 60.;
// const BACK_BUFFER_SIZE: usize = 5000;
///Upward flowing events, for changing the title and such
#[derive(Clone, Copy, Debug)]
@ -62,6 +73,7 @@ pub enum Event {
CloseTerminal,
Bell,
Wakeup,
BlinkChanged,
}
#[derive(Clone, Debug)]
@ -254,6 +266,8 @@ impl TerminalBuilder {
shell: Option<Shell>,
env: Option<HashMap<String, String>>,
initial_size: TerminalSize,
blink_settings: Option<TerminalBlink>,
alternate_scroll: &AlternateScroll,
) -> Result<TerminalBuilder> {
let pty_config = {
let alac_shell = shell.clone().and_then(|shell| match shell {
@ -287,9 +301,24 @@ impl TerminalBuilder {
setup_env(&config);
//Spawn a task so the Alacritty EventLoop can communicate with us in a view context
//TODO: Remove with a bounded sender which can be dispatched on &self
let (events_tx, events_rx) = unbounded();
//Set up the terminal...
let term = Term::new(&config, &initial_size, ZedListener(events_tx.clone()));
let mut term = Term::new(&config, &initial_size, ZedListener(events_tx.clone()));
//Start off blinking if we need to
if let Some(TerminalBlink::On) = blink_settings {
term.set_mode(alacritty_terminal::ansi::Mode::BlinkingCursor)
}
//Start alternate_scroll if we need to
if let AlternateScroll::On = alternate_scroll {
term.set_mode(alacritty_terminal::ansi::Mode::AlternateScroll)
} else {
//Alacritty turns it on by default, so we need to turn it off.
term.unset_mode(alacritty_terminal::ansi::Mode::AlternateScroll)
}
let term = Arc::new(FairMutex::new(term));
//Setup the pty...
@ -321,7 +350,7 @@ impl TerminalBuilder {
//And connect them together
let event_loop = EventLoop::new(
term.clone(),
ZedListener(events_tx),
ZedListener(events_tx.clone()),
pty,
pty_config.hold,
false,
@ -339,7 +368,8 @@ impl TerminalBuilder {
default_title: shell_txt,
last_mode: TermMode::NONE,
cur_size: initial_size,
// utilization: 0.,
last_mouse: None,
last_offset: 0,
};
Ok(TerminalBuilder {
@ -397,27 +427,6 @@ impl TerminalBuilder {
})
.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.notify();
// this.utilization()
// }),
// None => break,
// };
// let utilization = (1. - 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
}
}
@ -430,19 +439,11 @@ pub struct Terminal {
title: String,
cur_size: TerminalSize,
last_mode: TermMode,
//Percentage, between 0 and 1
// utilization: f32,
last_offset: usize,
last_mouse: Option<(Point, Direction)>,
}
impl Terminal {
// fn default_fps() -> f32 {
// MAX_FRAME_RATE
// }
// fn utilization(&self) -> f32 {
// self.utilization
// }
fn process_event(&mut self, event: &AlacTermEvent, cx: &mut ModelContext<Self>) {
match event {
AlacTermEvent::Title(title) => {
@ -456,17 +457,17 @@ impl Terminal {
AlacTermEvent::ClipboardStore(_, data) => {
cx.write_to_clipboard(ClipboardItem::new(data.to_string()))
}
AlacTermEvent::ClipboardLoad(_, format) => self.notify_pty(format(
AlacTermEvent::ClipboardLoad(_, format) => self.write_to_pty(format(
&cx.read_from_clipboard()
.map(|ci| ci.text().to_string())
.unwrap_or_else(|| "".to_string()),
)),
AlacTermEvent::PtyWrite(out) => self.notify_pty(out.clone()),
AlacTermEvent::PtyWrite(out) => self.write_to_pty(out.clone()),
AlacTermEvent::TextAreaSizeRequest(format) => {
self.notify_pty(format(self.cur_size.into()))
self.write_to_pty(format(self.cur_size.into()))
}
AlacTermEvent::CursorBlinkingChange => {
//TODO whatever state we need to set to get the cursor blinking
cx.emit(Event::BlinkChanged);
}
AlacTermEvent::Bell => {
cx.emit(Event::Bell);
@ -485,12 +486,6 @@ impl Terminal {
}
}
// 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,
@ -498,7 +493,6 @@ impl Terminal {
term: &mut Term<ZedListener>,
cx: &mut ModelContext<Self>,
) {
// TODO: Handle is_self_focused in subscription on terminal view
match event {
InternalEvent::TermEvent(term_event) => {
if let AlacTermEvent::ColorRequest(index, format) = term_event {
@ -506,7 +500,7 @@ impl Terminal {
let term_style = &cx.global::<Settings>().theme.terminal;
to_alac_rgb(get_color_at_index(index, &term_style.colors))
});
self.notify_pty(format(color))
self.write_to_pty(format(color))
}
}
InternalEvent::Resize(new_size) => {
@ -517,7 +511,7 @@ impl Terminal {
term.resize(*new_size);
}
InternalEvent::Clear => {
self.notify_pty("\x0c".to_string());
self.write_to_pty("\x0c".to_string());
term.clear_screen(ClearMode::Saved);
}
InternalEvent::Scroll(scroll) => term.scroll_display(*scroll),
@ -537,12 +531,14 @@ impl Terminal {
}
}
pub fn notify_pty(&self, txt: String) {
self.pty_tx.notify(txt.into_bytes());
pub fn input(&mut self, input: String) {
self.events.push(InternalEvent::Scroll(Scroll::Bottom));
self.events.push(InternalEvent::SetSelection(None));
self.write_to_pty(input);
}
///Write the Input payload to the tty.
pub fn write_to_pty(&mut self, input: String) {
fn write_to_pty(&self, input: String) {
self.pty_tx.notify(input.into_bytes());
}
@ -555,10 +551,10 @@ impl Terminal {
self.events.push(InternalEvent::Clear)
}
pub fn try_keystroke(&self, keystroke: &Keystroke) -> bool {
pub fn try_keystroke(&mut self, keystroke: &Keystroke) -> bool {
let esc = to_esc_str(keystroke, &self.last_mode);
if let Some(esc) = esc {
self.notify_pty(esc);
self.input(esc);
true
} else {
false
@ -566,14 +562,13 @@ impl Terminal {
}
///Paste text into the terminal
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', ""));
self.notify_pty("\x1b[201~".to_string());
pub fn paste(&mut self, text: &str) {
let paste_text = if self.last_mode.contains(TermMode::BRACKETED_PASTE) {
format!("{}{}{}", "\x1b[200~", text.replace('\x1b', ""), "\x1b[201~")
} else {
self.notify_pty(text.replace("\r\n", "\r").replace('\n', "\r"));
}
text.replace("\r\n", "\r").replace('\n', "\r")
};
self.input(paste_text)
}
pub fn copy(&mut self) {
@ -591,56 +586,165 @@ impl Terminal {
self.process_terminal_event(&e, &mut term, cx)
}
// self.utilization = Self::estimate_utilization(term.take_last_processed_bytes());
self.last_mode = *term.mode();
let content = term.renderable_content();
self.last_offset = content.display_offset;
let cursor_text = term.grid()[content.cursor.point].c;
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.);
pub fn focus_in(&self) {
if self.last_mode.contains(TermMode::FOCUS_IN_OUT) {
self.write_to_pty("\x1b[I".to_string());
}
}
// //Scale result to bias low, then high
// buffer_utilization * buffer_utilization
// }
pub fn focus_out(&self) {
if self.last_mode.contains(TermMode::FOCUS_IN_OUT) {
self.write_to_pty("\x1b[O".to_string());
}
}
pub fn mouse_changed(&mut self, point: Point, side: Direction) -> bool {
match self.last_mouse {
Some((old_point, old_side)) => {
if old_point == point && old_side == side {
false
} else {
self.last_mouse = Some((point, side));
true
}
}
None => {
self.last_mouse = Some((point, side));
true
}
}
}
pub fn mouse_mode(&self, shift: bool) -> bool {
self.last_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 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) {
self.pty_tx.notify(bytes);
}
}
}
pub fn mouse_drag(&mut self, e: DragRegionEvent, 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 side = mouse_side(position, self.cur_size);
self.events
.push(InternalEvent::UpdateSelection((point, side)));
}
}
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 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) {
self.pty_tx.notify(bytes);
}
} else if e.button == MouseButton::Left {
self.events
.push(InternalEvent::SetSelection(Some(Selection::new(
SelectionType::Simple,
point,
side,
))));
}
}
pub fn left_click(&mut self, e: &ClickRegionEvent, 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 side = mouse_side(position, self.cur_size);
let selection_type = match e.click_count {
0 => return, //This is a release
1 => Some(SelectionType::Simple),
2 => Some(SelectionType::Semantic),
3 => Some(SelectionType::Lines),
_ => None,
};
let selection =
selection_type.map(|selection_type| Selection::new(selection_type, point, side));
self.events.push(InternalEvent::SetSelection(selection));
}
}
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);
if let Some(bytes) = mouse_button_report(point, e, false, self.last_mode) {
self.pty_tx.notify(bytes);
}
} else if e.button == MouseButton::Left {
// Seems pretty standard to automatically copy on mouse_up for terminals,
// so let's do that here
self.copy();
}
}
///Scroll the terminal
pub fn scroll(&mut self, scroll: Scroll) {
self.events.push(InternalEvent::Scroll(scroll));
}
pub fn scroll(&mut self, scroll: &ScrollWheelEvent, origin: Vector2F) {
if self.mouse_mode(scroll.shift) {
//TODO: Currently this only sends the current scroll reports as they come in. Alacritty
//Sends the *entire* scroll delta on *every* scroll event, only resetting it when
//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 = (scroll.delta.y() / self.cur_size.line_height) as i32;
let point = mouse_point(scroll.position.sub(origin), self.cur_size, self.last_offset);
pub fn click(&mut self, point: Point, side: Direction, clicks: usize) {
let selection_type = match clicks {
0 => return, //This is a release
1 => Some(SelectionType::Simple),
2 => Some(SelectionType::Semantic),
3 => Some(SelectionType::Lines),
_ => None,
};
if let Some(scrolls) = scroll_report(point, scroll_lines as i32, scroll, self.last_mode)
{
for scroll in scrolls {
self.pty_tx.notify(scroll);
}
};
} else if self
.last_mode
.contains(TermMode::ALT_SCREEN | TermMode::ALTERNATE_SCROLL)
&& !scroll.shift
{
//TODO: See above TODO, also applies here.
let scroll_lines = ((scroll.delta.y() * ALACRITTY_SCROLL_MULTIPLIER)
/ self.cur_size.line_height) as i32;
let selection =
selection_type.map(|selection_type| Selection::new(selection_type, point, side));
self.events.push(InternalEvent::SetSelection(selection));
}
pub fn drag(&mut self, point: Point, side: Direction) {
self.events
.push(InternalEvent::UpdateSelection((point, side)));
}
///TODO: Check if the mouse_down-then-click assumption holds, so this code works as expected
pub fn mouse_down(&mut self, point: Point, side: Direction) {
self.events
.push(InternalEvent::SetSelection(Some(Selection::new(
SelectionType::Simple,
point,
side,
))));
self.pty_tx.notify(alt_scroll(scroll_lines))
} else {
let scroll_lines = ((scroll.delta.y() * ALACRITTY_SCROLL_MULTIPLIER)
/ self.cur_size.line_height) as i32;
if scroll_lines != 0 {
let scroll = Scroll::Delta(scroll_lines);
self.events.push(InternalEvent::Scroll(scroll));
}
}
}
}