Handlers attached, things are looking good 2 go

This commit is contained in:
Mikayla Maki 2022-08-19 11:41:17 -07:00
parent 04600d73fc
commit a806634b82
3 changed files with 270 additions and 250 deletions

View file

@ -1,26 +1,22 @@
use alacritty_terminal::{ use alacritty_terminal::{
ansi::{Color as AnsiColor, Color::Named, CursorShape as AlacCursorShape, NamedColor}, ansi::{Color as AnsiColor, Color::Named, CursorShape as AlacCursorShape, NamedColor},
grid::{Dimensions, Scroll}, grid::Dimensions,
index::{Column as GridCol, Direction, Line as GridLine, Point, Side}, index::Point,
selection::SelectionRange, selection::SelectionRange,
term::{ term::cell::{Cell, Flags},
cell::{Cell, Flags},
TermMode,
},
}; };
use editor::{Cursor, CursorShape, HighlightedRange, HighlightedRangeLine}; use editor::{Cursor, CursorShape, HighlightedRange, HighlightedRangeLine};
use gpui::{ use gpui::{
color::Color, color::Color,
elements::*,
fonts::{Properties, Style::Italic, TextStyle, Underline, Weight}, fonts::{Properties, Style::Italic, TextStyle, Underline, Weight},
geometry::{ geometry::{
rect::RectF, rect::RectF,
vector::{vec2f, Vector2F}, vector::{vec2f, Vector2F},
}, },
json::json, serde_json::json,
text_layout::{Line, RunStyle}, text_layout::{Line, RunStyle},
Event, EventContext, FontCache, KeyDownEvent, ModelContext, MouseButton, MouseButtonEvent, Element, Event, EventContext, FontCache, KeyDownEvent, ModelContext, MouseButton,
MouseRegion, PaintContext, Quad, ScrollWheelEvent, TextLayoutCache, WeakModelHandle, MouseButtonEvent, MouseRegion, PaintContext, Quad, TextLayoutCache, WeakModelHandle,
WeakViewHandle, WeakViewHandle,
}; };
use itertools::Itertools; use itertools::Itertools;
@ -29,12 +25,11 @@ use settings::Settings;
use theme::TerminalStyle; use theme::TerminalStyle;
use util::ResultExt; use util::ResultExt;
use std::fmt::Debug;
use std::{ use std::{
cmp::min,
mem, mem,
ops::{Deref, Range}, ops::{Deref, Range},
}; };
use std::{fmt::Debug, ops::Sub};
use crate::{ use crate::{
connected_view::{ConnectedView, DeployContextMenu}, connected_view::{ConnectedView, DeployContextMenu},
@ -42,11 +37,6 @@ use crate::{
Terminal, TerminalSize, Terminal, TerminalSize,
}; };
///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.;
///The information generated during layout that is nescessary for painting ///The information generated during layout that is nescessary for painting
pub struct LayoutState { pub struct LayoutState {
cells: Vec<LayoutCell>, cells: Vec<LayoutCell>,
@ -56,7 +46,6 @@ pub struct LayoutState {
background_color: Color, background_color: Color,
selection_color: Color, selection_color: Color,
size: TerminalSize, size: TerminalSize,
display_offset: usize,
} }
#[derive(Debug)] #[derive(Debug)]
@ -420,22 +409,13 @@ impl TerminalEl {
fn generic_button_handler( fn generic_button_handler(
connection: WeakModelHandle<Terminal>, connection: WeakModelHandle<Terminal>,
origin: Vector2F, origin: Vector2F,
cur_size: TerminalSize, f: impl Fn(&mut Terminal, Vector2F, MouseButtonEvent, &mut ModelContext<Terminal>),
display_offset: usize,
f: impl Fn(&mut Terminal, Point, Direction, MouseButtonEvent, &mut ModelContext<Terminal>),
) -> impl Fn(MouseButtonEvent, &mut EventContext) { ) -> impl Fn(MouseButtonEvent, &mut EventContext) {
move |event, cx| { move |event, cx| {
cx.focus_parent_view(); cx.focus_parent_view();
if let Some(conn_handle) = connection.upgrade(cx.app) { if let Some(conn_handle) = connection.upgrade(cx.app) {
conn_handle.update(cx.app, |terminal, cx| { conn_handle.update(cx.app, |terminal, cx| {
let (point, side) = TerminalEl::mouse_to_cell_data( f(terminal, origin, event, cx);
event.position,
origin,
cur_size,
display_offset,
);
f(terminal, point, side, event, cx);
cx.notify(); cx.notify();
}) })
@ -448,8 +428,6 @@ impl TerminalEl {
origin: Vector2F, origin: Vector2F,
view_id: usize, view_id: usize,
visible_bounds: RectF, visible_bounds: RectF,
cur_size: TerminalSize,
display_offset: usize,
cx: &mut PaintContext, cx: &mut PaintContext,
) { ) {
let connection = self.terminal; let connection = self.terminal;
@ -459,34 +437,20 @@ impl TerminalEl {
if cx.is_parent_view_focused() { if cx.is_parent_view_focused() {
if let Some(conn_handle) = connection.upgrade(cx.app) { if let Some(conn_handle) = connection.upgrade(cx.app) {
conn_handle.update(cx.app, |terminal, cx| { conn_handle.update(cx.app, |terminal, cx| {
let (point, side) = TerminalEl::mouse_to_cell_data( terminal.mouse_move(&event, origin);
event.position,
origin,
cur_size,
display_offset,
);
terminal.mouse_move(point, side, &event);
cx.notify(); cx.notify();
}) })
} }
} }
}) })
.on_drag(MouseButton::Left, move |_prev, event, cx| { .on_drag(MouseButton::Left, move |_prev, event, cx| {
if let Some(conn_handle) = connection.upgrade(cx.app) { if cx.is_parent_view_focused() {
conn_handle.update(cx.app, |terminal, cx| { if let Some(conn_handle) = connection.upgrade(cx.app) {
let (point, side) = TerminalEl::mouse_to_cell_data( conn_handle.update(cx.app, |terminal, cx| {
event.position, terminal.mouse_drag(event, origin);
origin, cx.notify();
cur_size, })
display_offset, }
);
terminal.mouse_drag(point, side);
cx.notify();
})
} }
}) })
.on_down( .on_down(
@ -494,10 +458,8 @@ impl TerminalEl {
TerminalEl::generic_button_handler( TerminalEl::generic_button_handler(
connection, connection,
origin, origin,
cur_size, move |terminal, origin, e, _cx| {
display_offset, terminal.mouse_down(&e, origin);
move |terminal, point, side, _e, _cx| {
terminal.mouse_down(point, side);
}, },
), ),
) )
@ -506,10 +468,8 @@ impl TerminalEl {
TerminalEl::generic_button_handler( TerminalEl::generic_button_handler(
connection, connection,
origin, origin,
cur_size, move |terminal, origin, e, _cx| {
display_offset, terminal.mouse_down(&e, origin);
move |terminal, point, side, _e, _cx| {
terminal.mouse_down(point, side);
}, },
), ),
) )
@ -518,65 +478,61 @@ impl TerminalEl {
TerminalEl::generic_button_handler( TerminalEl::generic_button_handler(
connection, connection,
origin, origin,
cur_size, move |terminal, origin, e, _cx| {
display_offset, terminal.mouse_down(&e, origin);
move |terminal, point, side, _e, _cx| { },
terminal.mouse_down(point, side); ),
)
.on_up(
MouseButton::Left,
TerminalEl::generic_button_handler(
connection,
origin,
move |terminal, origin, e, _cx| {
terminal.mouse_up(&e, origin);
},
),
)
.on_up(
MouseButton::Right,
TerminalEl::generic_button_handler(
connection,
origin,
move |terminal, origin, e, _cx| {
terminal.mouse_up(&e, origin);
},
),
)
.on_up(
MouseButton::Middle,
TerminalEl::generic_button_handler(
connection,
origin,
move |terminal, origin, e, _cx| {
terminal.mouse_up(&e, origin);
}, },
), ),
) )
//TODO
.on_click( .on_click(
MouseButton::Left, MouseButton::Left,
TerminalEl::generic_button_handler( TerminalEl::generic_button_handler(
connection, connection,
origin, origin,
cur_size, move |terminal, origin, e, _cx| {
display_offset, terminal.left_click(&e, origin);
move |terminal, point, side, e, _cx| {
terminal.click(point, side, e.click_count);
},
),
)
.on_click(
MouseButton::Middle,
TerminalEl::generic_button_handler(
connection,
origin,
cur_size,
display_offset,
move |terminal, point, side, e, _cx| {
terminal.click(point, side, e.click_count);
}, },
), ),
) )
.on_click( .on_click(
MouseButton::Right, MouseButton::Right,
move |e @ MouseButtonEvent { position, .. }, cx| { move |e @ MouseButtonEvent { position, .. }, cx| {
//Attempt to check the mode let mouse_mode = if let Some(conn_handle) = connection.upgrade(cx.app) {
if let Some(conn_handle) = connection.upgrade(cx.app) { conn_handle.update(cx.app, |terminal, _cx| terminal.mouse_mode(e.shift))
let handled = conn_handle.update(cx.app, |terminal, _cx| {
//Finally, we can check the mode!
if terminal.last_mode.intersects(TermMode::MOUSE_MODE) {
let (point, side) = TerminalEl::mouse_to_cell_data(
position,
origin,
cur_size,
display_offset,
);
terminal.click(point, side, e.click_count);
true
} else {
false
}
});
//If I put this up by the true, then we're in the wrong 'cx'
if !handled {
cx.dispatch_action(DeployContextMenu { position });
}
} else { } else {
//If we can't get the model handle, probably can't deploy the context menu
true
};
if !mouse_mode {
cx.dispatch_action(DeployContextMenu { position }); cx.dispatch_action(DeployContextMenu { position });
} }
}, },
@ -615,47 +571,6 @@ impl TerminalEl {
underline: Default::default(), underline: Default::default(),
} }
} }
pub fn mouse_to_cell_data(
pos: Vector2F,
origin: Vector2F,
cur_size: TerminalSize,
display_offset: usize,
) -> (Point, alacritty_terminal::index::Direction) {
let pos = pos.sub(origin);
let point = {
let col = pos.x() / cur_size.cell_width; //TODO: underflow...
let col = min(GridCol(col as usize), cur_size.last_column());
let line = pos.y() / cur_size.line_height;
let line = min(line as i32, cur_size.bottommost_line().0);
Point::new(GridLine(line - display_offset as i32), col)
};
//Copied (with modifications) from alacritty/src/input.rs > Processor::cell_side()
let side = {
let x = pos.0.x() as usize;
let cell_x =
x.saturating_sub(cur_size.cell_width as usize) % cur_size.cell_width as usize;
let half_cell_width = (cur_size.cell_width / 2.0) as usize;
let additional_padding =
(cur_size.width() - cur_size.cell_width * 2.) % cur_size.cell_width;
let end_of_grid = cur_size.width() - cur_size.cell_width - additional_padding;
//Width: Pixels or columns?
if cell_x > half_cell_width
// Edge case when mouse leaves the window.
|| x as f32 >= end_of_grid
{
Side::Right
} else {
Side::Left
}
};
(point, side)
}
} }
impl Element for TerminalEl { impl Element for TerminalEl {
@ -712,7 +627,7 @@ impl Element for TerminalEl {
( (
cells, cells,
dbg!(content.selection), content.selection,
content.cursor, content.cursor,
content.display_offset, content.display_offset,
cursor_text, cursor_text,
@ -794,7 +709,6 @@ impl Element for TerminalEl {
size: dimensions, size: dimensions,
rects, rects,
highlights, highlights,
display_offset,
}, },
) )
} }
@ -813,14 +727,7 @@ impl Element for TerminalEl {
let origin = bounds.origin() + vec2f(layout.size.cell_width, 0.); let origin = bounds.origin() + vec2f(layout.size.cell_width, 0.);
//Elements are ephemeral, only at paint time do we know what could be clicked by a mouse //Elements are ephemeral, only at paint time do we know what could be clicked by a mouse
self.attach_mouse_handlers( self.attach_mouse_handlers(origin, self.view.id(), visible_bounds, cx);
origin,
self.view.id(),
visible_bounds,
layout.size,
layout.display_offset,
cx,
);
cx.paint_layer(clip_bounds, |cx| { cx.paint_layer(clip_bounds, |cx| {
//Start with a background color //Start with a background color
@ -884,28 +791,22 @@ impl Element for TerminalEl {
fn dispatch_event( fn dispatch_event(
&mut self, &mut self,
event: &gpui::Event, event: &gpui::Event,
_bounds: gpui::geometry::rect::RectF, bounds: gpui::geometry::rect::RectF,
visible_bounds: gpui::geometry::rect::RectF, visible_bounds: gpui::geometry::rect::RectF,
layout: &mut Self::LayoutState, layout: &mut Self::LayoutState,
_paint: &mut Self::PaintState, _paint: &mut Self::PaintState,
cx: &mut gpui::EventContext, cx: &mut gpui::EventContext,
) -> bool { ) -> bool {
match event { match event {
Event::ScrollWheel(ScrollWheelEvent { Event::ScrollWheel(e) => visible_bounds
delta, position, .. .contains_point(e.position)
}) => visible_bounds
.contains_point(*position)
.then(|| { .then(|| {
let scroll_lines = let origin = bounds.origin() + vec2f(layout.size.cell_width, 0.);
(delta.y() / layout.size.line_height) * ALACRITTY_SCROLL_MULTIPLIER;
if let Some(terminal) = self.terminal.upgrade(cx.app) { if let Some(terminal) = self.terminal.upgrade(cx.app) {
terminal.update(cx.app, |term, _| { terminal.update(cx.app, |term, _| term.scroll(e, origin));
term.scroll(Scroll::Delta(scroll_lines.round() as i32)) cx.notify();
});
} }
cx.notify();
}) })
.is_some(), .is_some(),
Event::KeyDown(KeyDownEvent { keystroke, .. }) => { Event::KeyDown(KeyDownEvent { keystroke, .. }) => {
@ -913,7 +814,6 @@ impl Element for TerminalEl {
return false; return false;
} }
//TODO Talk to keith about how to catch events emitted from an element.
if let Some(view) = self.view.upgrade(cx.app) { if let Some(view) = self.view.upgrade(cx.app) {
view.update(cx.app, |view, cx| { view.update(cx.app, |view, cx| {
view.clear_bel(cx); view.clear_bel(cx);
@ -969,36 +869,3 @@ impl Element for TerminalEl {
Some(layout.cursor.as_ref()?.bounding_rect(origin)) Some(layout.cursor.as_ref()?.bounding_rect(origin))
} }
} }
mod test {
#[test]
fn test_mouse_to_selection() {
let term_width = 100.;
let term_height = 200.;
let cell_width = 10.;
let line_height = 20.;
let mouse_pos_x = 100.; //Window relative
let mouse_pos_y = 100.; //Window relative
let origin_x = 10.;
let origin_y = 20.;
let cur_size = crate::connected_el::TerminalSize::new(
line_height,
cell_width,
gpui::geometry::vector::vec2f(term_width, term_height),
);
let mouse_pos = gpui::geometry::vector::vec2f(mouse_pos_x, mouse_pos_y);
let origin = gpui::geometry::vector::vec2f(origin_x, origin_y); //Position of terminal window, 1 'cell' in
let (point, _) =
crate::connected_el::TerminalEl::mouse_to_cell_data(mouse_pos, origin, cur_size, 0);
assert_eq!(
point,
alacritty_terminal::index::Point::new(
alacritty_terminal::index::Line(((mouse_pos_y - origin_y) / line_height) as i32),
alacritty_terminal::index::Column(((mouse_pos_x - origin_x) / cell_width) as usize),
)
);
}
}

View file

@ -1,16 +1,23 @@
use std::cmp::min;
use std::iter::repeat;
use alacritty_terminal::grid::Dimensions;
/// Most of the code, and specifically the constants, in this are copied from Alacritty, /// Most of the code, and specifically the constants, in this are copied from Alacritty,
/// with modifications for our circumstances /// with modifications for our circumstances
use alacritty_terminal::{index::Point, term::TermMode}; use alacritty_terminal::index::{Column as GridCol, Line as GridLine, Point, Side};
use gpui::{MouseButtonEvent, MouseMovedEvent, ScrollWheelEvent}; use alacritty_terminal::term::TermMode;
use gpui::{geometry::vector::Vector2F, MouseButtonEvent, MouseMovedEvent, ScrollWheelEvent};
pub struct Modifiers { use crate::TerminalSize;
struct Modifiers {
ctrl: bool, ctrl: bool,
shift: bool, shift: bool,
alt: bool, alt: bool,
} }
impl Modifiers { impl Modifiers {
pub fn from_moved(e: &MouseMovedEvent) -> Self { fn from_moved(e: &MouseMovedEvent) -> Self {
Modifiers { Modifiers {
ctrl: e.ctrl, ctrl: e.ctrl,
shift: e.shift, shift: e.shift,
@ -18,7 +25,7 @@ impl Modifiers {
} }
} }
pub fn from_button(e: &MouseButtonEvent) -> Self { fn from_button(e: &MouseButtonEvent) -> Self {
Modifiers { Modifiers {
ctrl: e.ctrl, ctrl: e.ctrl,
shift: e.shift, shift: e.shift,
@ -27,7 +34,7 @@ impl Modifiers {
} }
//TODO: Determine if I should add modifiers into the ScrollWheelEvent type //TODO: Determine if I should add modifiers into the ScrollWheelEvent type
pub fn from_scroll() -> Self { fn from_scroll() -> Self {
Modifiers { Modifiers {
ctrl: false, ctrl: false,
shift: false, shift: false,
@ -36,13 +43,13 @@ impl Modifiers {
} }
} }
pub enum MouseFormat { enum MouseFormat {
SGR, SGR,
Normal(bool), Normal(bool),
} }
impl MouseFormat { impl MouseFormat {
pub fn from_mode(mode: TermMode) -> Self { fn from_mode(mode: TermMode) -> Self {
if mode.contains(TermMode::SGR_MOUSE) { if mode.contains(TermMode::SGR_MOUSE) {
MouseFormat::SGR MouseFormat::SGR
} else if mode.contains(TermMode::UTF8_MOUSE) { } else if mode.contains(TermMode::UTF8_MOUSE) {
@ -53,7 +60,7 @@ impl MouseFormat {
} }
} }
pub enum MouseButton { enum MouseButton {
LeftButton = 0, LeftButton = 0,
MiddleButton = 1, MiddleButton = 1,
RightButton = 2, RightButton = 2,
@ -67,7 +74,7 @@ pub enum MouseButton {
} }
impl MouseButton { impl MouseButton {
pub fn from_move(e: &MouseMovedEvent) -> Self { fn from_move(e: &MouseMovedEvent) -> Self {
match e.pressed_button { match e.pressed_button {
Some(b) => match b { Some(b) => match b {
gpui::MouseButton::Left => MouseButton::LeftMove, gpui::MouseButton::Left => MouseButton::LeftMove,
@ -79,7 +86,7 @@ impl MouseButton {
} }
} }
pub fn from_button(e: &MouseButtonEvent) -> Self { fn from_button(e: &MouseButtonEvent) -> Self {
match e.button { match e.button {
gpui::MouseButton::Left => MouseButton::LeftButton, gpui::MouseButton::Left => MouseButton::LeftButton,
gpui::MouseButton::Right => MouseButton::MiddleButton, gpui::MouseButton::Right => MouseButton::MiddleButton,
@ -88,7 +95,7 @@ impl MouseButton {
} }
} }
pub fn from_scroll(e: &ScrollWheelEvent) -> Self { fn from_scroll(e: &ScrollWheelEvent) -> Self {
if e.delta.y() > 0. { if e.delta.y() > 0. {
MouseButton::ScrollUp MouseButton::ScrollUp
} else { } else {
@ -96,7 +103,7 @@ impl MouseButton {
} }
} }
pub fn is_other(&self) -> bool { fn is_other(&self) -> bool {
match self { match self {
MouseButton::Other => true, MouseButton::Other => true,
_ => false, _ => false,
@ -109,24 +116,31 @@ pub fn scroll_report(
scroll_lines: i32, scroll_lines: i32,
e: &ScrollWheelEvent, e: &ScrollWheelEvent,
mode: TermMode, mode: TermMode,
) -> Option<Vec<Vec<u8>>> { ) -> Option<impl Iterator<Item = Vec<u8>>> {
if mode.intersects(TermMode::MOUSE_MODE) && scroll_lines >= 1 { if mode.intersects(TermMode::MOUSE_MODE) && scroll_lines >= 1 {
if let Some(report) = mouse_report( mouse_report(
point, point,
MouseButton::from_scroll(e), MouseButton::from_scroll(e),
true, true,
Modifiers::from_scroll(), Modifiers::from_scroll(),
MouseFormat::from_mode(mode), MouseFormat::from_mode(mode),
) { )
let mut res = vec![]; .map(|report| repeat(report).take(scroll_lines as usize))
for _ in 0..scroll_lines.abs() { } else {
res.push(report.clone()); None
}
return Some(res);
}
} }
}
None pub fn alt_scroll(scroll_lines: i32) -> Vec<u8> {
let cmd = if scroll_lines > 0 { b'A' } else { b'B' };
let mut content = Vec::with_capacity(scroll_lines as usize * 3);
for _ in 0..scroll_lines {
content.push(0x1b);
content.push(b'O');
content.push(cmd);
}
content
} }
pub fn mouse_button_report( pub fn mouse_button_report(
@ -164,6 +178,31 @@ pub fn mouse_moved_report(point: Point, e: &MouseMovedEvent, mode: TermMode) ->
} }
} }
pub fn mouse_side(pos: Vector2F, cur_size: TerminalSize) -> alacritty_terminal::index::Direction {
let x = pos.0.x() as usize;
let cell_x = x.saturating_sub(cur_size.cell_width as usize) % cur_size.cell_width as usize;
let half_cell_width = (cur_size.cell_width / 2.0) as usize;
let additional_padding = (cur_size.width() - cur_size.cell_width * 2.) % cur_size.cell_width;
let end_of_grid = cur_size.width() - cur_size.cell_width - additional_padding;
//Width: Pixels or columns?
if cell_x > half_cell_width
// Edge case when mouse leaves the window.
|| x as f32 >= end_of_grid
{
Side::Right
} else {
Side::Left
}
}
pub fn mouse_point(pos: Vector2F, cur_size: TerminalSize, display_offset: usize) -> Point {
let col = pos.x() / cur_size.cell_width;
let col = min(GridCol(col as usize), cur_size.last_column());
let line = pos.y() / cur_size.line_height;
let line = min(line as i32, cur_size.bottommost_line().0);
Point::new(GridLine(line - display_offset as i32), col)
}
///Generate the bytes to send to the terminal, from the cell location, a mouse event, and the terminal mode ///Generate the bytes to send to the terminal, from the cell location, a mouse event, and the terminal mode
fn mouse_report( fn mouse_report(
point: Point, point: Point,
@ -246,3 +285,38 @@ fn sgr_mouse_report(point: Point, button: u8, pressed: bool) -> String {
msg msg
} }
#[cfg(test)]
mod test {
use crate::mappings::mouse::mouse_point;
#[test]
fn test_mouse_to_selection() {
let term_width = 100.;
let term_height = 200.;
let cell_width = 10.;
let line_height = 20.;
let mouse_pos_x = 100.; //Window relative
let mouse_pos_y = 100.; //Window relative
let origin_x = 10.;
let origin_y = 20.;
let cur_size = crate::TerminalSize::new(
line_height,
cell_width,
gpui::geometry::vector::vec2f(term_width, term_height),
);
let mouse_pos = gpui::geometry::vector::vec2f(mouse_pos_x, mouse_pos_y);
let origin = gpui::geometry::vector::vec2f(origin_x, origin_y); //Position of terminal window, 1 'cell' in
let mouse_pos = mouse_pos - origin;
let point = mouse_point(mouse_pos, cur_size, 0);
assert_eq!(
point,
alacritty_terminal::index::Point::new(
alacritty_terminal::index::Line(((mouse_pos_y - origin_y) / line_height) as i32),
alacritty_terminal::index::Column(((mouse_pos_x - origin_x) / cell_width) as usize),
)
);
}
}

View file

@ -24,16 +24,19 @@ use futures::{
FutureExt, FutureExt,
}; };
use mappings::mouse::mouse_moved_report; use mappings::mouse::{
alt_scroll, mouse_button_report, mouse_moved_report, mouse_point, mouse_side, scroll_report,
};
use modal::deploy_modal; use modal::deploy_modal;
use settings::{Settings, Shell, TerminalBlink}; use settings::{Settings, Shell, TerminalBlink};
use std::{collections::HashMap, fmt::Display, path::PathBuf, sync::Arc, time::Duration}; use std::{collections::HashMap, fmt::Display, ops::Sub, path::PathBuf, sync::Arc, time::Duration};
use thiserror::Error; use thiserror::Error;
use gpui::{ use gpui::{
geometry::vector::{vec2f, Vector2F}, geometry::vector::{vec2f, Vector2F},
keymap::Keystroke, keymap::Keystroke,
ClipboardItem, Entity, ModelContext, MouseMovedEvent, MutableAppContext, ClipboardItem, Entity, ModelContext, MouseButtonEvent, MouseMovedEvent, MutableAppContext,
ScrollWheelEvent,
}; };
use crate::mappings::{ use crate::mappings::{
@ -49,6 +52,11 @@ pub fn init(cx: &mut MutableAppContext) {
connected_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_WIDTH: f32 = 500.;
const DEBUG_TERMINAL_HEIGHT: f32 = 30.; const DEBUG_TERMINAL_HEIGHT: f32 = 30.;
const DEBUG_CELL_WIDTH: f32 = 5.; const DEBUG_CELL_WIDTH: f32 = 5.;
@ -348,6 +356,7 @@ impl TerminalBuilder {
last_mode: TermMode::NONE, last_mode: TermMode::NONE,
cur_size: initial_size, cur_size: initial_size,
last_mouse: None, last_mouse: None,
last_offset: 0,
}; };
Ok(TerminalBuilder { Ok(TerminalBuilder {
@ -417,6 +426,7 @@ pub struct Terminal {
title: String, title: String,
cur_size: TerminalSize, cur_size: TerminalSize,
last_mode: TermMode, last_mode: TermMode,
last_offset: usize,
last_mouse: Option<(Point, Direction)>, last_mouse: Option<(Point, Direction)>,
} }
@ -509,7 +519,7 @@ impl Terminal {
} }
pub fn input(&mut self, input: String) { pub fn input(&mut self, input: String) {
self.scroll(Scroll::Bottom); self.events.push(InternalEvent::Scroll(Scroll::Bottom));
self.events.push(InternalEvent::SetSelection(None)); self.events.push(InternalEvent::SetSelection(None));
self.write_to_pty(input); self.write_to_pty(input);
} }
@ -563,11 +573,12 @@ impl Terminal {
self.process_terminal_event(&e, &mut term, cx) self.process_terminal_event(&e, &mut term, cx)
} }
// self.utilization = Self::estimate_utilization(term.take_last_processed_bytes());
self.last_mode = *term.mode(); self.last_mode = *term.mode();
let content = term.renderable_content(); let content = term.renderable_content();
self.last_offset = content.display_offset;
let cursor_text = term.grid()[content.cursor.point].c; let cursor_text = term.grid()[content.cursor.point].c;
f(content, cursor_text) f(content, cursor_text)
@ -602,23 +613,45 @@ impl Terminal {
} }
} }
/// Handle a mouse move pub fn mouse_mode(&self, shift: bool) -> bool {
pub fn mouse_move(&mut self, point: Point, side: Direction, e: &MouseMovedEvent) { self.last_mode.intersects(TermMode::MOUSE_MODE) && !shift
if self.mouse_changed(point, side) { }
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) { if let Some(bytes) = mouse_moved_report(point, e, self.last_mode) {
self.pty_tx.notify(bytes); self.pty_tx.notify(bytes);
} }
} }
} }
pub fn mouse_drag(&mut self, point: Point, side: Direction) { pub fn mouse_drag(&mut self, e: MouseMovedEvent, origin: Vector2F) {
self.events let position = e.position.sub(origin);
.push(InternalEvent::UpdateSelection((point, side)));
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, point: Point, side: Direction) { pub fn mouse_down(&mut self, e: &MouseButtonEvent, origin: Vector2F) {
if self.last_mode.intersects(TermMode::MOUSE_REPORT_CLICK) { let position = e.position.sub(origin);
//TODE: MOUSE MODE
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 { } else {
self.events self.events
.push(InternalEvent::SetSelection(Some(Selection::new( .push(InternalEvent::SetSelection(Some(Selection::new(
@ -629,11 +662,15 @@ impl Terminal {
} }
} }
pub fn click(&mut self, point: Point, side: Direction, clicks: usize) { pub fn left_click(&mut self, e: &MouseButtonEvent, origin: Vector2F) {
if self.last_mode.intersects(TermMode::MOUSE_MODE) { let position = e.position.sub(origin);
//TODE: MOUSE MODE //TODO: Alt-click cursor position
} else {
let selection_type = match clicks { 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 0 => return, //This is a release
1 => Some(SelectionType::Simple), 1 => Some(SelectionType::Simple),
2 => Some(SelectionType::Semantic), 2 => Some(SelectionType::Semantic),
@ -648,13 +685,55 @@ impl Terminal {
} }
} }
///Scroll the terminal pub fn mouse_up(&mut self, e: &MouseButtonEvent, origin: Vector2F) {
pub fn scroll(&mut self, scroll: Scroll) { let position = e.position.sub(origin);
if self.last_mode.intersects(TermMode::MOUSE_MODE) {
//TODE: MOUSE MODE
}
self.events.push(InternalEvent::Scroll(scroll)); 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 {
// 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: &ScrollWheelEvent, origin: Vector2F) {
if self.mouse_mode(false) {
//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);
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)
{
//TODO: See above TODO, also applies here.
let scroll_lines = ((scroll.delta.y() * ALACRITTY_SCROLL_MULTIPLIER)
/ self.cur_size.line_height) as i32;
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));
}
}
} }
} }