mirror of
https://github.com/zed-industries/zed.git
synced 2024-09-20 02:47:34 +03:00
Merge pull request #1522 from zed-industries/terminal-mouse
Terminal mouse mode
This commit is contained in:
commit
6122bc863d
@ -102,27 +102,37 @@
|
||||
//
|
||||
//
|
||||
"working_directory": "current_project_directory",
|
||||
//Set the cursor blinking behavior in the terminal.
|
||||
//May take 4 values:
|
||||
// 1. Never blink the cursor, ignoring the terminal mode
|
||||
// "blinking": "off",
|
||||
// 2. Default the cursor blink to off, but allow the terminal to
|
||||
// set blinking
|
||||
// "blinking": "terminal_controlled",
|
||||
// 3. Always blink the cursor, ignoring the terminal mode
|
||||
// "blinking": "on",
|
||||
// Set the cursor blinking behavior in the terminal.
|
||||
// May take 4 values:
|
||||
// 1. Never blink the cursor, ignoring the terminal mode
|
||||
// "blinking": "off",
|
||||
// 2. Default the cursor blink to off, but allow the terminal to
|
||||
// set blinking
|
||||
// "blinking": "terminal_controlled",
|
||||
// 3. Always blink the cursor, ignoring the terminal mode
|
||||
// "blinking": "on",
|
||||
"blinking": "terminal_controlled",
|
||||
//Any key-value pairs added to this list will be added to the terminal's
|
||||
//enviroment. Use `:` to seperate multiple values.
|
||||
// Set whether Alternate Scroll mode (code: ?1007) is active by default.
|
||||
// Alternate Scroll mode converts mouse scroll events into up / down key
|
||||
// presses when in the alternate screen (e.g. when running applications
|
||||
// like vim or less). The terminal can still set and unset this mode.
|
||||
// May take 2 values:
|
||||
// 1. Default alternate scroll mode to on
|
||||
// "alternate_scroll": "on",
|
||||
// 2. Default alternate scroll mode to off
|
||||
// "alternate_scroll": "off",
|
||||
"alternate_scroll": "off",
|
||||
// Any key-value pairs added to this list will be added to the terminal's
|
||||
// enviroment. Use `:` to seperate multiple values.
|
||||
"env": {
|
||||
//"KEY": "value1:value2"
|
||||
// "KEY": "value1:value2"
|
||||
}
|
||||
//Set the terminal's font size. If this option is not included,
|
||||
//the terminal will default to matching the buffer's font size.
|
||||
//"font_size": "15"
|
||||
//Set the terminal's font family. If this option is not included,
|
||||
//the terminal will default to matching the buffer's font family.
|
||||
//"font_family": "Zed Mono"
|
||||
// Set the terminal's font size. If this option is not included,
|
||||
// the terminal will default to matching the buffer's font size.
|
||||
// "font_size": "15"
|
||||
// Set the terminal's font family. If this option is not included,
|
||||
// the terminal will default to matching the buffer's font family.
|
||||
// "font_family": "Zed Mono"
|
||||
},
|
||||
// Different settings for specific languages.
|
||||
"languages": {
|
||||
@ -155,15 +165,15 @@
|
||||
"tab_size": 2
|
||||
}
|
||||
},
|
||||
//LSP Specific settings.
|
||||
// LSP Specific settings.
|
||||
"lsp": {
|
||||
//Specify the LSP name as a key here.
|
||||
//As of 8/10/22, supported LSPs are:
|
||||
//pyright
|
||||
//gopls
|
||||
//rust-analyzer
|
||||
//typescript-language-server
|
||||
//vscode-json-languageserver
|
||||
// Specify the LSP name as a key here.
|
||||
// As of 8/10/22, supported LSPs are:
|
||||
// pyright
|
||||
// gopls
|
||||
// rust-analyzer
|
||||
// typescript-language-server
|
||||
// vscode-json-languageserver
|
||||
// "rust_analyzer": {
|
||||
// //These initialization options are merged into Zed's defaults
|
||||
// "initialization_options": {
|
||||
|
@ -1610,6 +1610,7 @@ impl Element for EditorElement {
|
||||
position,
|
||||
delta,
|
||||
precise,
|
||||
..
|
||||
}) => self.scroll(*position, *delta, *precise, layout, paint, cx),
|
||||
|
||||
&Event::ModifiersChanged(event) => self.modifiers_changed(event, cx),
|
||||
|
@ -293,6 +293,7 @@ impl Element for Flex {
|
||||
position,
|
||||
delta,
|
||||
precise,
|
||||
..
|
||||
}) = event
|
||||
{
|
||||
if *remaining_space < 0. && bounds.contains_point(position) {
|
||||
|
@ -316,6 +316,7 @@ impl Element for List {
|
||||
position,
|
||||
delta,
|
||||
precise,
|
||||
..
|
||||
}) = event
|
||||
{
|
||||
if bounds.contains_point(*position)
|
||||
|
@ -315,6 +315,7 @@ impl Element for UniformList {
|
||||
position,
|
||||
delta,
|
||||
precise,
|
||||
..
|
||||
}) = event
|
||||
{
|
||||
if bounds.contains_point(*position)
|
||||
|
@ -24,6 +24,10 @@ pub struct ScrollWheelEvent {
|
||||
pub position: Vector2F,
|
||||
pub delta: Vector2F,
|
||||
pub precise: bool,
|
||||
pub ctrl: bool,
|
||||
pub alt: bool,
|
||||
pub shift: bool,
|
||||
pub cmd: bool,
|
||||
}
|
||||
|
||||
#[derive(Hash, PartialEq, Eq, Copy, Clone, Debug)]
|
||||
|
@ -148,6 +148,8 @@ impl Event {
|
||||
})
|
||||
}
|
||||
NSEventType::NSScrollWheel => window_height.map(|window_height| {
|
||||
let modifiers = native_event.modifierFlags();
|
||||
|
||||
Self::ScrollWheel(ScrollWheelEvent {
|
||||
position: vec2f(
|
||||
native_event.locationInWindow().x as f32,
|
||||
@ -158,6 +160,10 @@ impl Event {
|
||||
native_event.scrollingDeltaY() as f32,
|
||||
),
|
||||
precise: native_event.hasPreciseScrollingDeltas() == YES,
|
||||
ctrl: modifiers.contains(NSEventModifierFlags::NSControlKeyMask),
|
||||
alt: modifiers.contains(NSEventModifierFlags::NSAlternateKeyMask),
|
||||
shift: modifiers.contains(NSEventModifierFlags::NSShiftKeyMask),
|
||||
cmd: modifiers.contains(NSEventModifierFlags::NSCommandKeyMask),
|
||||
})
|
||||
}),
|
||||
NSEventType::NSLeftMouseDragged
|
||||
|
@ -235,7 +235,9 @@ impl Presenter {
|
||||
if let Some(root_view_id) = cx.root_view_id(self.window_id) {
|
||||
let mut invalidated_views = Vec::new();
|
||||
let mut mouse_down_out_handlers = Vec::new();
|
||||
let mut mouse_moved_region = None;
|
||||
let mut mouse_down_region = None;
|
||||
let mut mouse_up_region = None;
|
||||
let mut clicked_region = None;
|
||||
let mut dragged_region = None;
|
||||
|
||||
@ -282,6 +284,15 @@ impl Presenter {
|
||||
}
|
||||
}
|
||||
|
||||
for (region, _) in self.mouse_regions.iter().rev() {
|
||||
if region.bounds.contains_point(position) {
|
||||
invalidated_views.push(region.view_id);
|
||||
mouse_up_region =
|
||||
Some((region.clone(), MouseRegionEvent::Up(e.clone())));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(moved) = &mut self.last_mouse_moved_event {
|
||||
if moved.pressed_button == Some(button) {
|
||||
moved.pressed_button = None;
|
||||
@ -302,6 +313,15 @@ impl Presenter {
|
||||
*prev_drag_position = *position;
|
||||
}
|
||||
|
||||
for (region, _) in self.mouse_regions.iter().rev() {
|
||||
if region.bounds.contains_point(*position) {
|
||||
invalidated_views.push(region.view_id);
|
||||
mouse_moved_region =
|
||||
Some((region.clone(), MouseRegionEvent::Move(e.clone())));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
self.last_mouse_moved_event = Some(e.clone());
|
||||
}
|
||||
|
||||
@ -329,6 +349,28 @@ impl Presenter {
|
||||
}
|
||||
}
|
||||
|
||||
if let Some((move_moved_region, region_event)) = mouse_moved_region {
|
||||
handled = true;
|
||||
if let Some(mouse_moved_callback) =
|
||||
move_moved_region.handlers.get(®ion_event.handler_key())
|
||||
{
|
||||
event_cx.with_current_view(move_moved_region.view_id, |event_cx| {
|
||||
mouse_moved_callback(region_event, event_cx);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if let Some((mouse_up_region, region_event)) = mouse_up_region {
|
||||
handled = true;
|
||||
if let Some(mouse_up_callback) =
|
||||
mouse_up_region.handlers.get(®ion_event.handler_key())
|
||||
{
|
||||
event_cx.with_current_view(mouse_up_region.view_id, |event_cx| {
|
||||
mouse_up_callback(region_event, event_cx);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if let Some((clicked_region, region_event)) = clicked_region {
|
||||
handled = true;
|
||||
if let Some(click_callback) =
|
||||
|
@ -1,6 +1,7 @@
|
||||
use std::{any::TypeId, mem::Discriminant, rc::Rc};
|
||||
|
||||
use collections::HashMap;
|
||||
|
||||
use pathfinder_geometry::{rect::RectF, vector::Vector2F};
|
||||
|
||||
use crate::{EventContext, MouseButton, MouseButtonEvent, MouseMovedEvent, ScrollWheelEvent};
|
||||
@ -97,6 +98,14 @@ impl MouseRegion {
|
||||
self.handlers = self.handlers.on_hover(handler);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn on_move(
|
||||
mut self,
|
||||
handler: impl Fn(MouseMovedEvent, &mut EventContext) + 'static,
|
||||
) -> Self {
|
||||
self.handlers = self.handlers.on_move(handler);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
|
||||
@ -267,6 +276,23 @@ impl HandlerSet {
|
||||
}));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn on_move(
|
||||
mut self,
|
||||
handler: impl Fn(MouseMovedEvent, &mut EventContext) + 'static,
|
||||
) -> Self {
|
||||
self.set.insert((MouseRegionEvent::move_disc(), None),
|
||||
Rc::new(move |region_event, cx| {
|
||||
if let MouseRegionEvent::Move(move_event)= region_event {
|
||||
handler(move_event, cx);
|
||||
} else {
|
||||
panic!(
|
||||
"Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::Move, found {:?}",
|
||||
region_event);
|
||||
}
|
||||
}));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -84,6 +84,7 @@ pub struct TerminalSettings {
|
||||
pub font_family: Option<String>,
|
||||
pub env: Option<HashMap<String, String>>,
|
||||
pub blinking: Option<TerminalBlink>,
|
||||
pub alternate_scroll: Option<AlternateScroll>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, JsonSchema)]
|
||||
@ -114,6 +115,19 @@ impl Default for Shell {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, JsonSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum AlternateScroll {
|
||||
On,
|
||||
Off,
|
||||
}
|
||||
|
||||
impl Default for AlternateScroll {
|
||||
fn default() -> Self {
|
||||
AlternateScroll::On
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, JsonSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum WorkingDirectory {
|
||||
|
@ -10,7 +10,7 @@ The TerminalView struct abstracts over failed and successful terminals, passing
|
||||
|
||||
#Input
|
||||
|
||||
There are currently 3 distinct paths for getting keystrokes to the terminal:
|
||||
There are currently many distinct paths for getting keystrokes to the terminal:
|
||||
|
||||
1. Terminal specific characters and bindings. Things like ctrl-a mapping to ASCII control character 1, ANSI escape codes associated with the function keys, etc. These are caught with a raw key-down handler in the element and are processed immediately. This is done with the `try_keystroke()` method on Terminal
|
||||
|
||||
@ -18,3 +18,6 @@ There are currently 3 distinct paths for getting keystrokes to the terminal:
|
||||
|
||||
3. IME text. When the special character mappings fail, we pass the keystroke back to GPUI to hand it to the IME system. This comes back to us in the `View::replace_text_in_range()` method, and we then send that to the terminal directly, bypassing `try_keystroke()`.
|
||||
|
||||
4. Pasted text has a seperate pathway.
|
||||
|
||||
Generally, there's a distinction between 'keystrokes that need to be mapped' and 'strings which need to be written'. I've attempted to unify these under the '.try_keystroke()' API and the `.input()` API (which try_keystroke uses) so we have consistent input handling across the terminal
|
@ -1,23 +1,26 @@
|
||||
use alacritty_terminal::{
|
||||
ansi::{Color as AnsiColor, Color::Named, CursorShape as AlacCursorShape, NamedColor},
|
||||
grid::{Dimensions, Scroll},
|
||||
index::{Column as GridCol, Line as GridLine, Point, Side},
|
||||
grid::Dimensions,
|
||||
index::Point,
|
||||
selection::SelectionRange,
|
||||
term::cell::{Cell, Flags},
|
||||
term::{
|
||||
cell::{Cell, Flags},
|
||||
TermMode,
|
||||
},
|
||||
};
|
||||
use editor::{Cursor, CursorShape, HighlightedRange, HighlightedRangeLine};
|
||||
use gpui::{
|
||||
color::Color,
|
||||
elements::*,
|
||||
fonts::{Properties, Style::Italic, TextStyle, Underline, Weight},
|
||||
geometry::{
|
||||
rect::RectF,
|
||||
vector::{vec2f, Vector2F},
|
||||
},
|
||||
json::json,
|
||||
serde_json::json,
|
||||
text_layout::{Line, RunStyle},
|
||||
Event, FontCache, KeyDownEvent, MouseButton, MouseButtonEvent, MouseMovedEvent, MouseRegion,
|
||||
PaintContext, Quad, ScrollWheelEvent, TextLayoutCache, WeakModelHandle, WeakViewHandle,
|
||||
Element, Event, EventContext, FontCache, KeyDownEvent, ModelContext, MouseButton,
|
||||
MouseButtonEvent, MouseRegion, PaintContext, Quad, TextLayoutCache, WeakModelHandle,
|
||||
WeakViewHandle,
|
||||
};
|
||||
use itertools::Itertools;
|
||||
use ordered_float::OrderedFloat;
|
||||
@ -25,12 +28,11 @@ use settings::Settings;
|
||||
use theme::TerminalStyle;
|
||||
use util::ResultExt;
|
||||
|
||||
use std::fmt::Debug;
|
||||
use std::{
|
||||
cmp::min,
|
||||
mem,
|
||||
ops::{Deref, Range},
|
||||
};
|
||||
use std::{fmt::Debug, ops::Sub};
|
||||
|
||||
use crate::{
|
||||
connected_view::{ConnectedView, DeployContextMenu},
|
||||
@ -38,11 +40,6 @@ use crate::{
|
||||
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
|
||||
pub struct LayoutState {
|
||||
cells: Vec<LayoutCell>,
|
||||
@ -52,7 +49,7 @@ pub struct LayoutState {
|
||||
background_color: Color,
|
||||
selection_color: Color,
|
||||
size: TerminalSize,
|
||||
display_offset: usize,
|
||||
mode: TermMode,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@ -413,90 +410,159 @@ impl TerminalEl {
|
||||
}
|
||||
}
|
||||
|
||||
fn generic_button_handler(
|
||||
connection: WeakModelHandle<Terminal>,
|
||||
origin: Vector2F,
|
||||
f: impl Fn(&mut Terminal, Vector2F, MouseButtonEvent, &mut ModelContext<Terminal>),
|
||||
) -> impl Fn(MouseButtonEvent, &mut EventContext) {
|
||||
move |event, cx| {
|
||||
cx.focus_parent_view();
|
||||
if let Some(conn_handle) = connection.upgrade(cx.app) {
|
||||
conn_handle.update(cx.app, |terminal, cx| {
|
||||
f(terminal, origin, event, cx);
|
||||
|
||||
cx.notify();
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn attach_mouse_handlers(
|
||||
&self,
|
||||
origin: Vector2F,
|
||||
view_id: usize,
|
||||
visible_bounds: RectF,
|
||||
cur_size: TerminalSize,
|
||||
display_offset: usize,
|
||||
mode: TermMode,
|
||||
cx: &mut PaintContext,
|
||||
) {
|
||||
let mouse_down_connection = self.terminal;
|
||||
let click_connection = self.terminal;
|
||||
let drag_connection = self.terminal;
|
||||
cx.scene.push_mouse_region(
|
||||
MouseRegion::new(view_id, None, visible_bounds)
|
||||
.on_down(
|
||||
MouseButton::Left,
|
||||
move |MouseButtonEvent { position, .. }, cx| {
|
||||
if let Some(conn_handle) = mouse_down_connection.upgrade(cx.app) {
|
||||
conn_handle.update(cx.app, |terminal, cx| {
|
||||
let (point, side) = TerminalEl::mouse_to_cell_data(
|
||||
position,
|
||||
origin,
|
||||
cur_size,
|
||||
display_offset,
|
||||
);
|
||||
let connection = self.terminal;
|
||||
|
||||
terminal.mouse_down(point, side);
|
||||
let mut region = MouseRegion::new(view_id, None, visible_bounds);
|
||||
|
||||
cx.notify();
|
||||
})
|
||||
}
|
||||
},
|
||||
)
|
||||
.on_click(
|
||||
MouseButton::Left,
|
||||
move |MouseButtonEvent {
|
||||
position,
|
||||
click_count,
|
||||
..
|
||||
},
|
||||
cx| {
|
||||
cx.focus_parent_view();
|
||||
if let Some(conn_handle) = click_connection.upgrade(cx.app) {
|
||||
conn_handle.update(cx.app, |terminal, cx| {
|
||||
let (point, side) = TerminalEl::mouse_to_cell_data(
|
||||
position,
|
||||
origin,
|
||||
cur_size,
|
||||
display_offset,
|
||||
);
|
||||
|
||||
terminal.click(point, side, click_count);
|
||||
|
||||
cx.notify();
|
||||
});
|
||||
}
|
||||
},
|
||||
)
|
||||
.on_click(
|
||||
MouseButton::Right,
|
||||
move |MouseButtonEvent { position, .. }, cx| {
|
||||
cx.dispatch_action(DeployContextMenu { position });
|
||||
},
|
||||
)
|
||||
.on_drag(
|
||||
MouseButton::Left,
|
||||
move |_, MouseMovedEvent { position, .. }, cx| {
|
||||
if let Some(conn_handle) = drag_connection.upgrade(cx.app) {
|
||||
conn_handle.update(cx.app, |terminal, cx| {
|
||||
let (point, side) = TerminalEl::mouse_to_cell_data(
|
||||
position,
|
||||
origin,
|
||||
cur_size,
|
||||
display_offset,
|
||||
);
|
||||
|
||||
terminal.drag(point, side);
|
||||
|
||||
cx.notify()
|
||||
});
|
||||
}
|
||||
//Terminal Emulator controlled behavior:
|
||||
region = region
|
||||
//Start selections
|
||||
.on_down(
|
||||
MouseButton::Left,
|
||||
TerminalEl::generic_button_handler(
|
||||
connection,
|
||||
origin,
|
||||
move |terminal, origin, e, _cx| {
|
||||
terminal.mouse_down(&e, origin);
|
||||
},
|
||||
),
|
||||
);
|
||||
)
|
||||
//Update drag selections
|
||||
.on_drag(MouseButton::Left, move |_prev, event, cx| {
|
||||
if cx.is_parent_view_focused() {
|
||||
if let Some(conn_handle) = connection.upgrade(cx.app) {
|
||||
conn_handle.update(cx.app, |terminal, cx| {
|
||||
terminal.mouse_drag(event, origin);
|
||||
cx.notify();
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
//Copy on up behavior
|
||||
.on_up(
|
||||
MouseButton::Left,
|
||||
TerminalEl::generic_button_handler(
|
||||
connection,
|
||||
origin,
|
||||
move |terminal, origin, e, _cx| {
|
||||
terminal.mouse_up(&e, origin);
|
||||
},
|
||||
),
|
||||
)
|
||||
//Handle click based selections
|
||||
.on_click(
|
||||
MouseButton::Left,
|
||||
TerminalEl::generic_button_handler(
|
||||
connection,
|
||||
origin,
|
||||
move |terminal, origin, e, _cx| {
|
||||
terminal.left_click(&e, origin);
|
||||
},
|
||||
),
|
||||
)
|
||||
//Context menu
|
||||
.on_click(
|
||||
MouseButton::Right,
|
||||
move |e @ MouseButtonEvent { position, .. }, cx| {
|
||||
let mouse_mode = if let Some(conn_handle) = connection.upgrade(cx.app) {
|
||||
conn_handle.update(cx.app, |terminal, _cx| terminal.mouse_mode(e.shift))
|
||||
} 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 });
|
||||
}
|
||||
},
|
||||
)
|
||||
//This handles both drag mode and mouse motion mode
|
||||
//Mouse Move TODO
|
||||
//This cannot be done conditionally for unknown reasons. Pending drag and drop rework.
|
||||
//This also does not fire on right-mouse-down-move events wild.
|
||||
.on_move(move |event, cx| {
|
||||
dbg!(event);
|
||||
if cx.is_parent_view_focused() {
|
||||
if let Some(conn_handle) = connection.upgrade(cx.app) {
|
||||
conn_handle.update(cx.app, |terminal, cx| {
|
||||
terminal.mouse_move(&event, origin);
|
||||
cx.notify();
|
||||
})
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if mode.contains(TermMode::MOUSE_MODE) {
|
||||
region = region
|
||||
.on_down(
|
||||
MouseButton::Right,
|
||||
TerminalEl::generic_button_handler(
|
||||
connection,
|
||||
origin,
|
||||
move |terminal, origin, e, _cx| {
|
||||
terminal.mouse_down(&e, origin);
|
||||
},
|
||||
),
|
||||
)
|
||||
.on_down(
|
||||
MouseButton::Middle,
|
||||
TerminalEl::generic_button_handler(
|
||||
connection,
|
||||
origin,
|
||||
move |terminal, origin, e, _cx| {
|
||||
terminal.mouse_down(&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: Mouse drag isn't correct
|
||||
//TODO: Nor is mouse motion. Move events aren't happening??
|
||||
cx.scene.push_mouse_region(region);
|
||||
}
|
||||
|
||||
///Configures a text style from the current settings.
|
||||
@ -530,47 +596,6 @@ impl TerminalEl {
|
||||
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 {
|
||||
@ -601,7 +626,7 @@ impl Element for TerminalEl {
|
||||
terminal_theme.colors.background
|
||||
};
|
||||
|
||||
let (cells, selection, cursor, display_offset, cursor_text) = self
|
||||
let (cells, selection, cursor, display_offset, cursor_text, mode) = self
|
||||
.terminal
|
||||
.upgrade(cx)
|
||||
.unwrap()
|
||||
@ -624,13 +649,13 @@ impl Element for TerminalEl {
|
||||
cell: ic.cell.clone(),
|
||||
}),
|
||||
);
|
||||
|
||||
(
|
||||
cells,
|
||||
content.selection,
|
||||
content.cursor,
|
||||
content.display_offset,
|
||||
cursor_text,
|
||||
content.mode,
|
||||
)
|
||||
})
|
||||
});
|
||||
@ -709,7 +734,7 @@ impl Element for TerminalEl {
|
||||
size: dimensions,
|
||||
rects,
|
||||
highlights,
|
||||
display_offset,
|
||||
mode,
|
||||
},
|
||||
)
|
||||
}
|
||||
@ -728,14 +753,7 @@ impl Element for TerminalEl {
|
||||
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
|
||||
self.attach_mouse_handlers(
|
||||
origin,
|
||||
self.view.id(),
|
||||
visible_bounds,
|
||||
layout.size,
|
||||
layout.display_offset,
|
||||
cx,
|
||||
);
|
||||
self.attach_mouse_handlers(origin, self.view.id(), visible_bounds, layout.mode, cx);
|
||||
|
||||
cx.paint_layer(clip_bounds, |cx| {
|
||||
//Start with a background color
|
||||
@ -799,28 +817,22 @@ impl Element for TerminalEl {
|
||||
fn dispatch_event(
|
||||
&mut self,
|
||||
event: &gpui::Event,
|
||||
_bounds: gpui::geometry::rect::RectF,
|
||||
bounds: gpui::geometry::rect::RectF,
|
||||
visible_bounds: gpui::geometry::rect::RectF,
|
||||
layout: &mut Self::LayoutState,
|
||||
_paint: &mut Self::PaintState,
|
||||
cx: &mut gpui::EventContext,
|
||||
) -> bool {
|
||||
match event {
|
||||
Event::ScrollWheel(ScrollWheelEvent {
|
||||
delta, position, ..
|
||||
}) => visible_bounds
|
||||
.contains_point(*position)
|
||||
Event::ScrollWheel(e) => visible_bounds
|
||||
.contains_point(e.position)
|
||||
.then(|| {
|
||||
let vertical_scroll =
|
||||
(delta.y() / layout.size.line_height) * ALACRITTY_SCROLL_MULTIPLIER;
|
||||
let origin = bounds.origin() + vec2f(layout.size.cell_width, 0.);
|
||||
|
||||
if let Some(terminal) = self.terminal.upgrade(cx.app) {
|
||||
terminal.update(cx.app, |term, _| {
|
||||
term.scroll(Scroll::Delta(vertical_scroll.round() as i32))
|
||||
});
|
||||
terminal.update(cx.app, |term, _| term.scroll(e, origin));
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
cx.notify();
|
||||
})
|
||||
.is_some(),
|
||||
Event::KeyDown(KeyDownEvent { keystroke, .. }) => {
|
||||
@ -828,7 +840,6 @@ impl Element for TerminalEl {
|
||||
return false;
|
||||
}
|
||||
|
||||
//TODO Talk to keith about how to catch events emitted from an element.
|
||||
if let Some(view) = self.view.upgrade(cx.app) {
|
||||
view.update(cx.app, |view, cx| {
|
||||
view.clear_bel(cx);
|
||||
@ -884,36 +895,3 @@ impl Element for TerminalEl {
|
||||
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),
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -251,7 +251,8 @@ impl ConnectedView {
|
||||
///Attempt to paste the clipboard into the terminal
|
||||
fn paste(&mut self, _: &Paste, cx: &mut ViewContext<Self>) {
|
||||
if let Some(item) = cx.read_from_clipboard() {
|
||||
self.terminal.read(cx).paste(item.text());
|
||||
self.terminal
|
||||
.update(cx, |terminal, _cx| terminal.paste(item.text()));
|
||||
}
|
||||
}
|
||||
|
||||
@ -359,8 +360,7 @@ impl View for ConnectedView {
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
self.terminal.update(cx, |terminal, _| {
|
||||
terminal.write_to_pty(text.into());
|
||||
terminal.scroll(alacritty_terminal::grid::Scroll::Bottom);
|
||||
terminal.input(text.into());
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1,3 +1,4 @@
|
||||
/// The mappings defined in this file where created from reading the alacritty source
|
||||
use alacritty_terminal::term::TermMode;
|
||||
use gpui::keymap::Keystroke;
|
||||
|
||||
|
@ -1,2 +1,3 @@
|
||||
pub mod colors;
|
||||
pub mod keys;
|
||||
pub mod mouse;
|
||||
|
330
crates/terminal/src/mappings/mouse.rs
Normal file
330
crates/terminal/src/mappings/mouse.rs
Normal file
@ -0,0 +1,330 @@
|
||||
use std::cmp::{max, min};
|
||||
use std::iter::repeat;
|
||||
|
||||
use alacritty_terminal::grid::Dimensions;
|
||||
/// Most of the code, and specifically the constants, in this are copied from Alacritty,
|
||||
/// with modifications for our circumstances
|
||||
use alacritty_terminal::index::{Column as GridCol, Line as GridLine, Point, Side};
|
||||
use alacritty_terminal::term::TermMode;
|
||||
use gpui::{geometry::vector::Vector2F, MouseButtonEvent, MouseMovedEvent, ScrollWheelEvent};
|
||||
|
||||
use crate::TerminalSize;
|
||||
|
||||
struct Modifiers {
|
||||
ctrl: bool,
|
||||
shift: bool,
|
||||
alt: bool,
|
||||
}
|
||||
|
||||
impl Modifiers {
|
||||
fn from_moved(e: &MouseMovedEvent) -> Self {
|
||||
Modifiers {
|
||||
ctrl: e.ctrl,
|
||||
shift: e.shift,
|
||||
alt: e.alt,
|
||||
}
|
||||
}
|
||||
|
||||
fn from_button(e: &MouseButtonEvent) -> Self {
|
||||
Modifiers {
|
||||
ctrl: e.ctrl,
|
||||
shift: e.shift,
|
||||
alt: e.alt,
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: Determine if I should add modifiers into the ScrollWheelEvent type
|
||||
fn from_scroll() -> Self {
|
||||
Modifiers {
|
||||
ctrl: false,
|
||||
shift: false,
|
||||
alt: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum MouseFormat {
|
||||
SGR,
|
||||
Normal(bool),
|
||||
}
|
||||
|
||||
impl MouseFormat {
|
||||
fn from_mode(mode: TermMode) -> Self {
|
||||
if mode.contains(TermMode::SGR_MOUSE) {
|
||||
MouseFormat::SGR
|
||||
} else if mode.contains(TermMode::UTF8_MOUSE) {
|
||||
MouseFormat::Normal(true)
|
||||
} else {
|
||||
MouseFormat::Normal(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum MouseButton {
|
||||
LeftButton = 0,
|
||||
MiddleButton = 1,
|
||||
RightButton = 2,
|
||||
LeftMove = 32,
|
||||
MiddleMove = 33,
|
||||
RightMove = 34,
|
||||
NoneMove = 35,
|
||||
ScrollUp = 64,
|
||||
ScrollDown = 65,
|
||||
Other = 99,
|
||||
}
|
||||
|
||||
impl MouseButton {
|
||||
fn from_move(e: &MouseMovedEvent) -> Self {
|
||||
match e.pressed_button {
|
||||
Some(b) => match b {
|
||||
gpui::MouseButton::Left => MouseButton::LeftMove,
|
||||
gpui::MouseButton::Middle => MouseButton::MiddleMove,
|
||||
gpui::MouseButton::Right => MouseButton::RightMove,
|
||||
gpui::MouseButton::Navigate(_) => MouseButton::Other,
|
||||
},
|
||||
None => MouseButton::NoneMove,
|
||||
}
|
||||
}
|
||||
|
||||
fn from_button(e: &MouseButtonEvent) -> Self {
|
||||
match e.button {
|
||||
gpui::MouseButton::Left => MouseButton::LeftButton,
|
||||
gpui::MouseButton::Right => MouseButton::MiddleButton,
|
||||
gpui::MouseButton::Middle => MouseButton::RightButton,
|
||||
gpui::MouseButton::Navigate(_) => MouseButton::Other,
|
||||
}
|
||||
}
|
||||
|
||||
fn from_scroll(e: &ScrollWheelEvent) -> Self {
|
||||
if e.delta.y() > 0. {
|
||||
MouseButton::ScrollUp
|
||||
} else {
|
||||
MouseButton::ScrollDown
|
||||
}
|
||||
}
|
||||
|
||||
fn is_other(&self) -> bool {
|
||||
match self {
|
||||
MouseButton::Other => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn scroll_report(
|
||||
point: Point,
|
||||
scroll_lines: i32,
|
||||
e: &ScrollWheelEvent,
|
||||
mode: TermMode,
|
||||
) -> Option<impl Iterator<Item = Vec<u8>>> {
|
||||
if mode.intersects(TermMode::MOUSE_MODE) {
|
||||
mouse_report(
|
||||
point,
|
||||
MouseButton::from_scroll(e),
|
||||
true,
|
||||
Modifiers::from_scroll(),
|
||||
MouseFormat::from_mode(mode),
|
||||
)
|
||||
.map(|report| repeat(report).take(max(scroll_lines, 1) as usize))
|
||||
} else {
|
||||
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.abs() as usize * 3);
|
||||
for _ in 0..scroll_lines.abs() {
|
||||
content.push(0x1b);
|
||||
content.push(b'O');
|
||||
content.push(cmd);
|
||||
}
|
||||
content
|
||||
}
|
||||
|
||||
pub fn mouse_button_report(
|
||||
point: Point,
|
||||
e: &MouseButtonEvent,
|
||||
pressed: bool,
|
||||
mode: TermMode,
|
||||
) -> Option<Vec<u8>> {
|
||||
let button = MouseButton::from_button(e);
|
||||
if !button.is_other() && mode.intersects(TermMode::MOUSE_MODE) {
|
||||
mouse_report(
|
||||
point,
|
||||
button,
|
||||
pressed,
|
||||
Modifiers::from_button(e),
|
||||
MouseFormat::from_mode(mode),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mouse_moved_report(point: Point, e: &MouseMovedEvent, mode: TermMode) -> Option<Vec<u8>> {
|
||||
let button = MouseButton::from_move(e);
|
||||
dbg!(&button);
|
||||
|
||||
if !button.is_other() && mode.intersects(TermMode::MOUSE_MOTION | TermMode::MOUSE_DRAG) {
|
||||
//Only drags are reported in drag mode, so block NoneMove.
|
||||
if mode.contains(TermMode::MOUSE_DRAG) && matches!(button, MouseButton::NoneMove) {
|
||||
None
|
||||
} else {
|
||||
mouse_report(
|
||||
point,
|
||||
button,
|
||||
true,
|
||||
Modifiers::from_moved(e),
|
||||
MouseFormat::from_mode(mode),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
fn mouse_report(
|
||||
point: Point,
|
||||
button: MouseButton,
|
||||
pressed: bool,
|
||||
modifiers: Modifiers,
|
||||
format: MouseFormat,
|
||||
) -> Option<Vec<u8>> {
|
||||
if point.line < 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut mods = 0;
|
||||
if modifiers.shift {
|
||||
mods += 4;
|
||||
}
|
||||
if modifiers.alt {
|
||||
mods += 8;
|
||||
}
|
||||
if modifiers.ctrl {
|
||||
mods += 16;
|
||||
}
|
||||
|
||||
match format {
|
||||
MouseFormat::SGR => {
|
||||
Some(sgr_mouse_report(point, button as u8 + mods, pressed).into_bytes())
|
||||
}
|
||||
MouseFormat::Normal(utf8) => {
|
||||
if pressed {
|
||||
normal_mouse_report(point, button as u8 + mods, utf8)
|
||||
} else {
|
||||
normal_mouse_report(point, 3 + mods, utf8)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn normal_mouse_report(point: Point, button: u8, utf8: bool) -> Option<Vec<u8>> {
|
||||
let Point { line, column } = point;
|
||||
let max_point = if utf8 { 2015 } else { 223 };
|
||||
|
||||
if line >= max_point || column >= max_point {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut msg = vec![b'\x1b', b'[', b'M', 32 + button];
|
||||
|
||||
let mouse_pos_encode = |pos: usize| -> Vec<u8> {
|
||||
let pos = 32 + 1 + pos;
|
||||
let first = 0xC0 + pos / 64;
|
||||
let second = 0x80 + (pos & 63);
|
||||
vec![first as u8, second as u8]
|
||||
};
|
||||
|
||||
if utf8 && column >= 95 {
|
||||
msg.append(&mut mouse_pos_encode(column.0));
|
||||
} else {
|
||||
msg.push(32 + 1 + column.0 as u8);
|
||||
}
|
||||
|
||||
if utf8 && line >= 95 {
|
||||
msg.append(&mut mouse_pos_encode(line.0 as usize));
|
||||
} else {
|
||||
msg.push(32 + 1 + line.0 as u8);
|
||||
}
|
||||
|
||||
Some(msg)
|
||||
}
|
||||
|
||||
fn sgr_mouse_report(point: Point, button: u8, pressed: bool) -> String {
|
||||
let c = if pressed { 'M' } else { 'm' };
|
||||
|
||||
let msg = format!(
|
||||
"\x1b[<{};{};{}{}",
|
||||
button,
|
||||
point.column + 1,
|
||||
point.line + 1,
|
||||
c
|
||||
);
|
||||
|
||||
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),
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
@ -24,15 +24,19 @@ 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, TerminalBlink};
|
||||
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,
|
||||
ClipboardItem, Entity, ModelContext, MouseButton, MouseButtonEvent, MouseMovedEvent,
|
||||
MutableAppContext, ScrollWheelEvent,
|
||||
};
|
||||
|
||||
use crate::mappings::{
|
||||
@ -48,12 +52,15 @@ pub fn init(cx: &mut MutableAppContext) {
|
||||
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)]
|
||||
@ -256,6 +263,7 @@ impl TerminalBuilder {
|
||||
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 {
|
||||
@ -299,6 +307,14 @@ impl TerminalBuilder {
|
||||
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...
|
||||
@ -348,7 +364,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 {
|
||||
@ -406,27 +423,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
|
||||
}
|
||||
}
|
||||
@ -439,19 +435,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) => {
|
||||
@ -494,12 +482,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,
|
||||
@ -507,7 +489,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 {
|
||||
@ -546,8 +527,14 @@ impl Terminal {
|
||||
}
|
||||
}
|
||||
|
||||
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(&self, input: String) {
|
||||
fn write_to_pty(&self, input: String) {
|
||||
self.pty_tx.notify(input.into_bytes());
|
||||
}
|
||||
|
||||
@ -563,8 +550,7 @@ impl Terminal {
|
||||
pub fn try_keystroke(&mut self, keystroke: &Keystroke) -> bool {
|
||||
let esc = to_esc_str(keystroke, &self.last_mode);
|
||||
if let Some(esc) = esc {
|
||||
self.write_to_pty(esc);
|
||||
self.scroll(Scroll::Bottom);
|
||||
self.input(esc);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
@ -572,14 +558,13 @@ impl Terminal {
|
||||
}
|
||||
|
||||
///Paste text into the terminal
|
||||
pub fn paste(&self, text: &str) {
|
||||
if self.last_mode.contains(TermMode::BRACKETED_PASTE) {
|
||||
self.write_to_pty("\x1b[200~".to_string());
|
||||
self.write_to_pty(text.replace('\x1b', ""));
|
||||
self.write_to_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.write_to_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) {
|
||||
@ -597,21 +582,17 @@ 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)
|
||||
}
|
||||
|
||||
///Scroll the terminal
|
||||
pub fn scroll(&mut self, scroll: Scroll) {
|
||||
self.events.push(InternalEvent::Scroll(scroll));
|
||||
}
|
||||
|
||||
pub fn focus_in(&self) {
|
||||
if self.last_mode.contains(TermMode::FOCUS_IN_OUT) {
|
||||
self.write_to_pty("\x1b[I".to_string());
|
||||
@ -624,34 +605,143 @@ impl Terminal {
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
};
|
||||
|
||||
let selection =
|
||||
selection_type.map(|selection_type| Selection::new(selection_type, point, side));
|
||||
|
||||
self.events.push(InternalEvent::SetSelection(selection));
|
||||
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 drag(&mut self, point: Point, side: Direction) {
|
||||
self.events
|
||||
.push(InternalEvent::UpdateSelection((point, side)));
|
||||
pub fn mouse_mode(&self, shift: bool) -> bool {
|
||||
self.last_mode.intersects(TermMode::MOUSE_MODE) && !shift
|
||||
}
|
||||
|
||||
///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,
|
||||
))));
|
||||
pub fn mouse_move(&mut self, e: &MouseMovedEvent, origin: Vector2F) {
|
||||
dbg!("term mouse_move");
|
||||
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: MouseMovedEvent, 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: &MouseButtonEvent, 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: &MouseButtonEvent, 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: &MouseButtonEvent, 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: &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);
|
||||
|
||||
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;
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -10,7 +10,7 @@ use workspace::{Item, Workspace};
|
||||
|
||||
use crate::TerminalSize;
|
||||
use project::{LocalWorktree, Project, ProjectPath};
|
||||
use settings::{Settings, WorkingDirectory};
|
||||
use settings::{AlternateScroll, Settings, WorkingDirectory};
|
||||
use smallvec::SmallVec;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
@ -94,12 +94,26 @@ impl TerminalView {
|
||||
let shell = settings.terminal_overrides.shell.clone();
|
||||
let envs = settings.terminal_overrides.env.clone(); //Should be short and cheap.
|
||||
|
||||
//TODO: move this pattern to settings
|
||||
let scroll = settings
|
||||
.terminal_overrides
|
||||
.alternate_scroll
|
||||
.as_ref()
|
||||
.unwrap_or(
|
||||
settings
|
||||
.terminal_defaults
|
||||
.alternate_scroll
|
||||
.as_ref()
|
||||
.unwrap_or_else(|| &AlternateScroll::On),
|
||||
);
|
||||
|
||||
let content = match TerminalBuilder::new(
|
||||
working_directory.clone(),
|
||||
shell,
|
||||
envs,
|
||||
size_info,
|
||||
settings.terminal_overrides.blinking.clone(),
|
||||
scroll,
|
||||
) {
|
||||
Ok(terminal) => {
|
||||
let terminal = cx.add_model(|cx| terminal.subscribe(cx));
|
||||
|
1
styles/package-lock.json
generated
1
styles/package-lock.json
generated
@ -5,7 +5,6 @@
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "styles",
|
||||
"version": "1.0.0",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
|
Loading…
Reference in New Issue
Block a user