diff --git a/assets/settings/default.json b/assets/settings/default.json index 739b34d743..8ea515e7a9 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -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": { diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 97d8aa0293..ee46544df3 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -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), diff --git a/crates/gpui/src/elements/flex.rs b/crates/gpui/src/elements/flex.rs index 1d577344c6..798fb3e8c0 100644 --- a/crates/gpui/src/elements/flex.rs +++ b/crates/gpui/src/elements/flex.rs @@ -293,6 +293,7 @@ impl Element for Flex { position, delta, precise, + .. }) = event { if *remaining_space < 0. && bounds.contains_point(position) { diff --git a/crates/gpui/src/elements/list.rs b/crates/gpui/src/elements/list.rs index e0ca47b598..d3c15c4e2b 100644 --- a/crates/gpui/src/elements/list.rs +++ b/crates/gpui/src/elements/list.rs @@ -316,6 +316,7 @@ impl Element for List { position, delta, precise, + .. }) = event { if bounds.contains_point(*position) diff --git a/crates/gpui/src/elements/uniform_list.rs b/crates/gpui/src/elements/uniform_list.rs index bf9a0a4e30..103cb00d8c 100644 --- a/crates/gpui/src/elements/uniform_list.rs +++ b/crates/gpui/src/elements/uniform_list.rs @@ -315,6 +315,7 @@ impl Element for UniformList { position, delta, precise, + .. }) = event { if bounds.contains_point(*position) diff --git a/crates/gpui/src/platform/event.rs b/crates/gpui/src/platform/event.rs index 9a84b8ef2f..b7631aac4b 100644 --- a/crates/gpui/src/platform/event.rs +++ b/crates/gpui/src/platform/event.rs @@ -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)] diff --git a/crates/gpui/src/platform/mac/event.rs b/crates/gpui/src/platform/mac/event.rs index a53e009589..c6f838b431 100644 --- a/crates/gpui/src/platform/mac/event.rs +++ b/crates/gpui/src/platform/mac/event.rs @@ -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 diff --git a/crates/gpui/src/presenter.rs b/crates/gpui/src/presenter.rs index 3e6c97a208..6836d157fe 100644 --- a/crates/gpui/src/presenter.rs +++ b/crates/gpui/src/presenter.rs @@ -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) = diff --git a/crates/gpui/src/scene/mouse_region.rs b/crates/gpui/src/scene/mouse_region.rs index 704567450a..ba7173b4a1 100644 --- a/crates/gpui/src/scene/mouse_region.rs +++ b/crates/gpui/src/scene/mouse_region.rs @@ -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)] diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index 9defb6f410..2b97cb0b10 100644 --- a/crates/settings/src/settings.rs +++ b/crates/settings/src/settings.rs @@ -84,6 +84,7 @@ pub struct TerminalSettings { pub font_family: Option, pub env: Option>, pub blinking: Option, + pub alternate_scroll: Option, } #[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 { diff --git a/crates/terminal/README.md b/crates/terminal/README.md index cdfdaffe85..272212a538 100644 --- a/crates/terminal/README.md +++ b/crates/terminal/README.md @@ -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 \ No newline at end of file diff --git a/crates/terminal/src/connected_el.rs b/crates/terminal/src/connected_el.rs index f21727af66..bc80796aae 100644 --- a/crates/terminal/src/connected_el.rs +++ b/crates/terminal/src/connected_el.rs @@ -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, @@ -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, + origin: Vector2F, + f: impl Fn(&mut Terminal, Vector2F, MouseButtonEvent, &mut ModelContext), + ) -> 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), - ) - ); - } -} diff --git a/crates/terminal/src/connected_view.rs b/crates/terminal/src/connected_view.rs index 5b521748bb..703a54ba91 100644 --- a/crates/terminal/src/connected_view.rs +++ b/crates/terminal/src/connected_view.rs @@ -251,7 +251,8 @@ impl ConnectedView { ///Attempt to paste the clipboard into the terminal fn paste(&mut self, _: &Paste, cx: &mut ViewContext) { 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.terminal.update(cx, |terminal, _| { - terminal.write_to_pty(text.into()); - terminal.scroll(alacritty_terminal::grid::Scroll::Bottom); + terminal.input(text.into()); }); } diff --git a/crates/terminal/src/mappings/keys.rs b/crates/terminal/src/mappings/keys.rs index 002759d78d..da730e6296 100644 --- a/crates/terminal/src/mappings/keys.rs +++ b/crates/terminal/src/mappings/keys.rs @@ -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; diff --git a/crates/terminal/src/mappings/mod.rs b/crates/terminal/src/mappings/mod.rs index cde6c337ea..d58dd27f96 100644 --- a/crates/terminal/src/mappings/mod.rs +++ b/crates/terminal/src/mappings/mod.rs @@ -1,2 +1,3 @@ pub mod colors; pub mod keys; +pub mod mouse; diff --git a/crates/terminal/src/mappings/mouse.rs b/crates/terminal/src/mappings/mouse.rs new file mode 100644 index 0000000000..236f954c74 --- /dev/null +++ b/crates/terminal/src/mappings/mouse.rs @@ -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>> { + 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 { + 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> { + 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> { + 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> { + 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> { + 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 { + 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), + ) + ); + } +} diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index 5254ffbe7f..ea3774a586 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -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>, initial_size: TerminalSize, blink_settings: Option, + alternate_scroll: &AlternateScroll, ) -> Result { 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) { match event { AlacTermEvent::Title(title) => { @@ -494,12 +482,6 @@ impl Terminal { } } - // fn process_events(&mut self, events: Vec, cx: &mut ModelContext) { - // 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, cx: &mut ModelContext, ) { - // 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)); + } + } } } diff --git a/crates/terminal/src/terminal_view.rs b/crates/terminal/src/terminal_view.rs index a997ebf631..8041eafb9c 100644 --- a/crates/terminal/src/terminal_view.rs +++ b/crates/terminal/src/terminal_view.rs @@ -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)); diff --git a/styles/package-lock.json b/styles/package-lock.json index 582f1c8496..5499f1852c 100644 --- a/styles/package-lock.json +++ b/styles/package-lock.json @@ -5,7 +5,6 @@ "requires": true, "packages": { "": { - "name": "styles", "version": "1.0.0", "license": "ISC", "dependencies": {