Merge branch 'main' into drag-and-drop
This commit is contained in:
commit
13e9336049
65 changed files with 2371 additions and 797 deletions
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue