diff --git a/assets/keymaps/default.json b/assets/keymaps/default.json index 6f34facc421e203b5b276fd8704ed1265b34c2dd..b40a076c499db29a99d80db17c39d3c3295afc6b 100644 --- a/assets/keymaps/default.json +++ b/assets/keymaps/default.json @@ -420,6 +420,7 @@ "enter": "terminal::Enter", "ctrl-c": "terminal::CtrlC", // Useful terminal actions: + "ctrl-cmd-space": "terminal::ShowCharacterPalette", "cmd-c": "terminal::Copy", "cmd-v": "terminal::Paste", "cmd-k": "terminal::Clear" @@ -428,6 +429,7 @@ { "context": "ModalTerminal", "bindings": { + "ctrl-cmd-space": "terminal::ShowCharacterPalette", "shift-escape": "terminal::DeployModal" } } diff --git a/assets/settings/default.json b/assets/settings/default.json index c6f08f4e5696ee22e7a795c3f10176ac4d98fdf2..739b34d743fbac5596ac0c4b2acd7ddf89f951d1 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -105,16 +105,13 @@ //Set the cursor blinking behavior in the terminal. //May take 4 values: // 1. Never blink the cursor, ignoring the terminal mode - // "blinking": "never", - // 2. Default the cursor blink to off, but allow the terminal to - // turn blinking on // "blinking": "off", - // 3. Default the cursor blink to on, but allow the terminal to - // turn 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", - // 4. Always blink the cursor, ignoring the terminal mode - // "blinking": "always", - "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. "env": { diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 6140731579455a9a98887bf524a5372edb5bad24..c935df7d07a47479b032cc16aad0d585c04a3c2a 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -1801,7 +1801,7 @@ impl Cursor { pub fn paint(&self, origin: Vector2F, cx: &mut PaintContext) { let bounds = match self.shape { CursorShape::Bar => RectF::new(self.origin + origin, vec2f(2.0, self.line_height)), - CursorShape::Block => RectF::new( + CursorShape::Block | CursorShape::Hollow => RectF::new( self.origin + origin, vec2f(self.block_width, self.line_height), ), @@ -1809,59 +1809,32 @@ impl Cursor { self.origin + origin + Vector2F::new(0.0, self.line_height - 2.0), vec2f(self.block_width, 2.0), ), - CursorShape::Hollow => RectF::new( - self.origin + origin + Vector2F::new(0.0, self.line_height - 1.0), - vec2f(self.block_width, 1.0), - ), }; - //Draw text under the hollow block if need be - if matches!(self.shape, CursorShape::Hollow) { - if let Some(block_text) = &self.block_text { - block_text.paint(self.origin + origin, bounds, self.line_height, cx); - } - } - - cx.scene.push_quad(Quad { - bounds, - background: Some(self.color), - border: Border::new(0., Color::black()), - corner_radius: 0., - }); - + //Draw background or border quad if matches!(self.shape, CursorShape::Hollow) { - //Top - cx.scene.push_quad(Quad { - bounds: RectF::new( - self.origin + origin + Vector2F::new(0.0, -1.0), - vec2f(self.block_width + 1., 1.0), - ), - background: Some(self.color), - border: Border::new(0., Color::black()), - corner_radius: 0., - }); - //Left cx.scene.push_quad(Quad { - bounds: RectF::new(self.origin + origin, vec2f(1.0, self.line_height)), - background: Some(self.color), - border: Border::new(0., Color::black()), + bounds, + background: None, + border: Border::all(1., self.color), corner_radius: 0., }); - //Right + } else { cx.scene.push_quad(Quad { - bounds: RectF::new( - self.origin + origin + vec2f(self.block_width, 0.), - vec2f(1.0, self.line_height), - ), + bounds, background: Some(self.color), - border: Border::new(0., Color::black()), + border: Default::default(), corner_radius: 0., }); - } else { - if let Some(block_text) = &self.block_text { - block_text.paint(self.origin + origin, bounds, self.line_height, cx); - } } + + if let Some(block_text) = &self.block_text { + block_text.paint(self.origin + origin, bounds, self.line_height, cx); + } + } + + pub fn shape(&self) -> CursorShape { + self.shape } } diff --git a/crates/gpui/src/platform/mac/window.rs b/crates/gpui/src/platform/mac/window.rs index aed8bd92b62f1f381f372a52996ec0dac81d1ca6..9b52041d806bdbd60ec42139a2cca6619e9b53c2 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/crates/gpui/src/platform/mac/window.rs @@ -278,6 +278,18 @@ unsafe fn build_classes() { pub struct Window(Rc>); +///Used to track what the IME does when we send it a keystroke. +///This is only used to handle the case where the IME mysteriously +///swallows certain keys. +/// +///Basically a direct copy of the approach that WezTerm uses in: +///github.com/wez/wezterm : d5755f3e : window/src/os/macos/window.rs +enum ImeState { + Continue, + Acted, + None, +} + struct WindowState { id: usize, native_window: id, @@ -299,6 +311,10 @@ struct WindowState { layer: id, traffic_light_position: Option, previous_modifiers_changed_event: Option, + //State tracking what the IME did after the last request + ime_state: ImeState, + //Retains the last IME Text + ime_text: Option, } struct InsertText { @@ -395,6 +411,8 @@ impl Window { layer, traffic_light_position: options.traffic_light_position, previous_modifiers_changed_event: None, + ime_state: ImeState::None, + ime_text: None, }))); (*native_window).set_ivar( @@ -764,6 +782,7 @@ extern "C" fn handle_key_event(this: &Object, native_event: id, key_equivalent: let mut window_state_borrow = window_state.as_ref().borrow_mut(); let event = unsafe { Event::from_native(native_event, Some(window_state_borrow.size().y())) }; + if let Some(event) = event { if key_equivalent { window_state_borrow.performed_key_equivalent = true; @@ -801,7 +820,9 @@ extern "C" fn handle_key_event(this: &Object, native_event: id, key_equivalent: let mut handled = false; let mut window_state_borrow = window_state.borrow_mut(); + let ime_text = window_state_borrow.ime_text.clone(); if let Some((event, insert_text)) = window_state_borrow.pending_key_down.take() { + let is_held = event.is_held; if let Some(mut callback) = window_state_borrow.event_callback.take() { drop(window_state_borrow); @@ -820,6 +841,18 @@ extern "C" fn handle_key_event(this: &Object, native_event: id, key_equivalent: input_handler .replace_text_in_range(insert.replacement_range, &insert.text) }); + } else if !is_composing && is_held { + if let Some(last_insert_text) = ime_text { + //MacOS IME is a bit funky, and even when you've told it there's nothing to + //inter it will still swallow certain keys (e.g. 'f', 'j') and not others + //(e.g. 'n'). This is a problem for certain kinds of views, like the terminal + with_input_handler(this, |input_handler| { + if input_handler.selected_text_range().is_none() { + handled = true; + input_handler.replace_text_in_range(None, &last_insert_text) + } + }); + } } } @@ -1132,7 +1165,6 @@ extern "C" fn first_rect_for_character_range( let window = get_window_state(this).borrow().native_window; NSView::frame(window) }; - with_input_handler(this, |input_handler| { input_handler.rect_for_range(range.to_range()?) }) @@ -1170,6 +1202,9 @@ extern "C" fn insert_text(this: &Object, _: Sel, text: id, replacement_range: NS .unwrap(); let replacement_range = replacement_range.to_range(); + window_state.borrow_mut().ime_text = Some(text.to_string()); + window_state.borrow_mut().ime_state = ImeState::Acted; + let is_composing = with_input_handler(this, |input_handler| input_handler.marked_text_range()) .flatten() @@ -1198,7 +1233,8 @@ extern "C" fn set_marked_text( replacement_range: NSRange, ) { unsafe { - get_window_state(this).borrow_mut().pending_key_down.take(); + let window_state = get_window_state(this); + window_state.borrow_mut().pending_key_down.take(); let is_attributed_string: BOOL = msg_send![text, isKindOfClass: [class!(NSAttributedString)]]; @@ -1213,6 +1249,9 @@ extern "C" fn set_marked_text( .to_str() .unwrap(); + window_state.borrow_mut().ime_state = ImeState::Acted; + window_state.borrow_mut().ime_text = Some(text.to_string()); + with_input_handler(this, |input_handler| { input_handler.replace_and_mark_text_in_range(replacement_range, text, selected_range); }); @@ -1220,6 +1259,13 @@ extern "C" fn set_marked_text( } extern "C" fn unmark_text(this: &Object, _: Sel) { + unsafe { + let state = get_window_state(this); + let mut borrow = state.borrow_mut(); + borrow.ime_state = ImeState::Acted; + borrow.ime_text.take(); + } + with_input_handler(this, |input_handler| input_handler.unmark_text()); } @@ -1246,7 +1292,14 @@ extern "C" fn attributed_substring_for_proposed_range( .unwrap_or(nil) } -extern "C" fn do_command_by_selector(_: &Object, _: Sel, _: Sel) {} +extern "C" fn do_command_by_selector(this: &Object, _: Sel, _: Sel) { + unsafe { + let state = get_window_state(this); + let mut borrow = state.borrow_mut(); + borrow.ime_state = ImeState::Continue; + borrow.ime_text.take(); + } +} async fn synthetic_drag( window_state: Weak>, diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index 9c1a17a46215a0617c0473d460e6dd6fcff4a71a..9defb6f41094d95ba3e8f31126664a1c5194bc40 100644 --- a/crates/settings/src/settings.rs +++ b/crates/settings/src/settings.rs @@ -89,15 +89,14 @@ pub struct TerminalSettings { #[derive(Clone, Debug, Deserialize, PartialEq, Eq, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum TerminalBlink { - Never, - On, Off, - Always, + TerminalControlled, + On, } impl Default for TerminalBlink { fn default() -> Self { - TerminalBlink::On + TerminalBlink::TerminalControlled } } diff --git a/crates/terminal/README.md b/crates/terminal/README.md index 99ab583ebbf8590b0137c0acc066ba2f7f261313..cdfdaffe854ef51533bd63f2bfb24199fd7b830c 100644 --- a/crates/terminal/README.md +++ b/crates/terminal/README.md @@ -4,6 +4,17 @@ This crate is split into two conceptual halves: - The terminal.rs file and the src/mappings/ folder, these contain the code for interacting with Alacritty and maintaining the pty event loop. Some behavior in this file is constrained by terminal protocols and standards. The Zed init function is also placed here. - Everything else. These other files integrate the `Terminal` struct created in terminal.rs into the rest of GPUI. The main entry point for GPUI is the terminal_view.rs file and the modal.rs file. -Terminals are created externally, and so can fail in unexpected ways However, GPUI currently does not have an API for models than can fail to instantiate. `TerminalBuilder` solves this by using Rust's type system to split `Terminal` instantiation into a 2 step process: first attempt to create the file handles with `TerminalBuilder::new()`, check the result, then call `TerminalBuilder::subscribe(cx)` from within a model context. -The TerminalView struct abstracts over failed and successful terminals, and provides a standardized way of instantiating an always-successful view of a terminal. +ttys are created externally, and so can fail in unexpected ways. However, GPUI currently does not have an API for models than can fail to instantiate. `TerminalBuilder` solves this by using Rust's type system to split tty instantiation into a 2 step process: first attempt to create the file handles with `TerminalBuilder::new()`, check the result, then call `TerminalBuilder::subscribe(cx)` from within a model context. + +The TerminalView struct abstracts over failed and successful terminals, passing focus through to the associated view and allowing clients to build a terminal without worrying about errors. + +#Input + +There are currently 3 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 + +2. GPU Action handlers. GPUI clobbers a few vital keys by adding bindings to them in the global context. These keys are synthesized and then dispatched through the same `try_keystroke()` API as the above mappings + +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()`. diff --git a/crates/terminal/src/connected_el.rs b/crates/terminal/src/connected_el.rs index d017aad65b419f7b67c8ceed7e53bdd5c9106e86..f21727af66eb69dcc2ed67c12054787f7660bdbb 100644 --- a/crates/terminal/src/connected_el.rs +++ b/crates/terminal/src/connected_el.rs @@ -1,5 +1,5 @@ use alacritty_terminal::{ - ansi::{Color as AnsiColor, Color::Named, NamedColor}, + ansi::{Color as AnsiColor, Color::Named, CursorShape as AlacCursorShape, NamedColor}, grid::{Dimensions, Scroll}, index::{Column as GridCol, Line as GridLine, Point, Side}, selection::SelectionRange, @@ -21,7 +21,7 @@ use gpui::{ }; use itertools::Itertools; use ordered_float::OrderedFloat; -use settings::{Settings, TerminalBlink}; +use settings::Settings; use theme::TerminalStyle; use util::ResultExt; @@ -201,7 +201,7 @@ pub struct TerminalEl { view: WeakViewHandle, modal: bool, focused: bool, - blink_state: bool, + cursor_visible: bool, } impl TerminalEl { @@ -210,14 +210,14 @@ impl TerminalEl { terminal: WeakModelHandle, modal: bool, focused: bool, - blink_state: bool, + cursor_visible: bool, ) -> TerminalEl { TerminalEl { view, terminal, modal, focused, - blink_state, + cursor_visible, } } @@ -571,33 +571,6 @@ impl TerminalEl { (point, side) } - - pub fn should_show_cursor( - settings: Option, - blinking_on: bool, - focused: bool, - blink_show: bool, - ) -> bool { - if !focused { - true - } else { - match settings { - Some(setting) => match setting { - TerminalBlink::Never => true, - TerminalBlink::On | TerminalBlink::Off if blinking_on => blink_show, - TerminalBlink::On | TerminalBlink::Off /*if !blinking_on */ => true, - TerminalBlink::Always => focused && blink_show, - }, - None => { - if blinking_on { - blink_show - } else { - false - } - } - } - } - } } impl Element for TerminalEl { @@ -610,7 +583,6 @@ impl Element for TerminalEl { cx: &mut gpui::LayoutContext, ) -> (gpui::geometry::vector::Vector2F, Self::LayoutState) { let settings = cx.global::(); - let blink_settings = settings.terminal_overrides.blinking.clone(); let font_cache = cx.font_cache(); //Setup layout information @@ -629,13 +601,13 @@ impl Element for TerminalEl { terminal_theme.colors.background }; - let (cells, selection, cursor, display_offset, cursor_text, blink_mode) = self + let (cells, selection, cursor, display_offset, cursor_text) = self .terminal .upgrade(cx) .unwrap() .update(cx.app, |terminal, mcx| { terminal.set_size(dimensions); - terminal.render_lock(mcx, |content, cursor_text, blink_mode| { + terminal.render_lock(mcx, |content, cursor_text| { let mut cells = vec![]; cells.extend( content @@ -659,7 +631,6 @@ impl Element for TerminalEl { content.cursor, content.display_offset, cursor_text, - blink_mode, ) }) }); @@ -674,59 +645,57 @@ impl Element for TerminalEl { selection, ); - //Layout cursor - let cursor = { - if !TerminalEl::should_show_cursor( - blink_settings, - blink_mode, - self.focused, - self.blink_state, - ) { - None - } else { - let cursor_point = DisplayCursor::from(cursor.point, display_offset); - let cursor_text = { - let str_trxt = cursor_text.to_string(); - - let color = if self.focused { - terminal_theme.colors.background - } else { - terminal_theme.colors.foreground - }; - - cx.text_layout_cache.layout_str( - &str_trxt, - text_style.font_size, - &[( - str_trxt.len(), - RunStyle { - font_id: text_style.font_id, - color, - underline: Default::default(), - }, - )], - ) + //Layout cursor. Rectangle is used for IME, so we should lay it out even + //if we don't end up showing it. + let cursor = if let AlacCursorShape::Hidden = cursor.shape { + None + } else { + let cursor_point = DisplayCursor::from(cursor.point, display_offset); + let cursor_text = { + let str_trxt = cursor_text.to_string(); + + let color = if self.focused { + terminal_theme.colors.background + } else { + terminal_theme.colors.foreground }; - TerminalEl::shape_cursor(cursor_point, dimensions, &cursor_text).map( - move |(cursor_position, block_width)| { - let (shape, color) = if self.focused { - (CursorShape::Block, terminal_theme.colors.cursor) - } else { - (CursorShape::Hollow, terminal_theme.colors.foreground) - }; - - Cursor::new( - cursor_position, - block_width, - dimensions.line_height, + cx.text_layout_cache.layout_str( + &str_trxt, + text_style.font_size, + &[( + str_trxt.len(), + RunStyle { + font_id: text_style.font_id, color, - shape, - Some(cursor_text), - ) - }, + underline: Default::default(), + }, + )], ) - } + }; + + TerminalEl::shape_cursor(cursor_point, dimensions, &cursor_text).map( + move |(cursor_position, block_width)| { + let shape = match cursor.shape { + AlacCursorShape::Block if !self.focused => CursorShape::Hollow, + AlacCursorShape::Block => CursorShape::Block, + AlacCursorShape::Underline => CursorShape::Underscore, + AlacCursorShape::Beam => CursorShape::Bar, + AlacCursorShape::HollowBlock => CursorShape::Hollow, + //This case is handled in the if wrapping the whole cursor layout + AlacCursorShape::Hidden => unreachable!(), + }; + + Cursor::new( + cursor_position, + block_width, + dimensions.line_height, + terminal_theme.colors.cursor, + shape, + Some(cursor_text), + ) + }, + ) }; //Done! @@ -817,10 +786,12 @@ impl Element for TerminalEl { }); //Draw cursor - if let Some(cursor) = &layout.cursor { - cx.paint_layer(clip_bounds, |cx| { - cursor.paint(origin, cx); - }) + if self.cursor_visible { + if let Some(cursor) = &layout.cursor { + cx.paint_layer(clip_bounds, |cx| { + cursor.paint(origin, cx); + }) + } } }); } diff --git a/crates/terminal/src/connected_view.rs b/crates/terminal/src/connected_view.rs index 6f16ac9bcdd2a25fc70fbd391468cf974768d4ad..5b521748bb02b869d6d6688e76c8a6d6d8d0068b 100644 --- a/crates/terminal/src/connected_view.rs +++ b/crates/terminal/src/connected_view.rs @@ -11,6 +11,7 @@ use gpui::{ AnyViewHandle, AppContext, Element, ElementBox, ModelHandle, MutableAppContext, View, ViewContext, ViewHandle, }; +use settings::{Settings, TerminalBlink}; use smol::Timer; use workspace::pane; @@ -29,7 +30,17 @@ pub struct DeployContextMenu { actions!( terminal, - [Up, Down, CtrlC, Escape, Enter, Clear, Copy, Paste,] + [ + Up, + Down, + CtrlC, + Escape, + Enter, + Clear, + Copy, + Paste, + ShowCharacterPalette + ] ); impl_internal_actions!(project_panel, [DeployContextMenu]); @@ -45,6 +56,7 @@ pub fn init(cx: &mut MutableAppContext) { cx.add_action(ConnectedView::copy); cx.add_action(ConnectedView::paste); cx.add_action(ConnectedView::clear); + cx.add_action(ConnectedView::show_character_palette); } ///A terminal view, maintains the PTY's file handles and communicates with the terminal @@ -56,7 +68,8 @@ pub struct ConnectedView { // Only for styling purposes. Doesn't effect behavior modal: bool, context_menu: ViewHandle, - show_cursor: bool, + blink_state: bool, + blinking_on: bool, blinking_paused: bool, blink_epoch: usize, } @@ -80,7 +93,7 @@ impl ConnectedView { this.has_bell = true; cx.emit(Event::Wakeup); } - + Event::BlinkChanged => this.blinking_on = !this.blinking_on, _ => cx.emit(*event), }) .detach(); @@ -91,7 +104,8 @@ impl ConnectedView { has_bell: false, modal, context_menu: cx.add_view(ContextMenu::new), - show_cursor: true, + blink_state: true, + blinking_on: false, blinking_paused: false, blink_epoch: 0, } @@ -126,19 +140,64 @@ impl ConnectedView { cx.notify(); } + fn show_character_palette(&mut self, _: &ShowCharacterPalette, cx: &mut ViewContext) { + if !self + .terminal + .read(cx) + .last_mode + .contains(TermMode::ALT_SCREEN) + { + cx.show_character_palette(); + } else { + self.terminal.update(cx, |term, _| { + term.try_keystroke(&Keystroke::parse("ctrl-cmd-space").unwrap()) + }); + } + } + fn clear(&mut self, _: &Clear, cx: &mut ViewContext) { self.terminal.update(cx, |term, _| term.clear()); cx.notify(); } - //Following code copied from editor cursor - pub fn blink_show(&self) -> bool { - self.blinking_paused || self.show_cursor + pub fn should_show_cursor( + &self, + focused: bool, + cx: &mut gpui::RenderContext<'_, Self>, + ) -> bool { + //Don't blink the cursor when not focused, blinking is disabled, or paused + if !focused + || !self.blinking_on + || self.blinking_paused + || self + .terminal + .read(cx) + .last_mode + .contains(TermMode::ALT_SCREEN) + { + return true; + } + + let setting = { + let settings = cx.global::(); + settings + .terminal_overrides + .blinking + .clone() + .unwrap_or(TerminalBlink::TerminalControlled) + }; + + match setting { + //If the user requested to never blink, don't blink it. + TerminalBlink::Off => true, + //If the terminal is controlling it, check terminal mode + TerminalBlink::TerminalControlled | TerminalBlink::On => self.blink_state, + } } fn blink_cursors(&mut self, epoch: usize, cx: &mut ViewContext) { if epoch == self.blink_epoch && !self.blinking_paused { - self.show_cursor = !self.show_cursor; + self.blink_state = !self.blink_state; cx.notify(); let epoch = self.next_blink_epoch(); @@ -156,7 +215,7 @@ impl ConnectedView { } pub fn pause_cursor_blinking(&mut self, cx: &mut ViewContext) { - self.show_cursor = true; + self.blink_state = true; cx.notify(); let epoch = self.next_blink_epoch(); @@ -199,41 +258,41 @@ impl ConnectedView { ///Synthesize the keyboard event corresponding to 'up' fn up(&mut self, _: &Up, cx: &mut ViewContext) { self.clear_bel(cx); - self.terminal - .read(cx) - .try_keystroke(&Keystroke::parse("up").unwrap()); + self.terminal.update(cx, |term, _| { + term.try_keystroke(&Keystroke::parse("up").unwrap()) + }); } ///Synthesize the keyboard event corresponding to 'down' fn down(&mut self, _: &Down, cx: &mut ViewContext) { self.clear_bel(cx); - self.terminal - .read(cx) - .try_keystroke(&Keystroke::parse("down").unwrap()); + self.terminal.update(cx, |term, _| { + term.try_keystroke(&Keystroke::parse("down").unwrap()) + }); } ///Synthesize the keyboard event corresponding to 'ctrl-c' fn ctrl_c(&mut self, _: &CtrlC, cx: &mut ViewContext) { self.clear_bel(cx); - self.terminal - .read(cx) - .try_keystroke(&Keystroke::parse("ctrl-c").unwrap()); + self.terminal.update(cx, |term, _| { + term.try_keystroke(&Keystroke::parse("ctrl-c").unwrap()) + }); } ///Synthesize the keyboard event corresponding to 'escape' fn escape(&mut self, _: &Escape, cx: &mut ViewContext) { self.clear_bel(cx); - self.terminal - .read(cx) - .try_keystroke(&Keystroke::parse("escape").unwrap()); + self.terminal.update(cx, |term, _| { + term.try_keystroke(&Keystroke::parse("escape").unwrap()) + }); } ///Synthesize the keyboard event corresponding to 'enter' fn enter(&mut self, _: &Enter, cx: &mut ViewContext) { self.clear_bel(cx); - self.terminal - .read(cx) - .try_keystroke(&Keystroke::parse("enter").unwrap()); + self.terminal.update(cx, |term, _| { + term.try_keystroke(&Keystroke::parse("enter").unwrap()) + }); } } @@ -258,7 +317,7 @@ impl View for ConnectedView { terminal_handle, self.modal, focused, - self.blink_show(), + self.should_show_cursor(focused, cx), ) .contained() .boxed(), @@ -299,8 +358,10 @@ impl View for ConnectedView { text: &str, cx: &mut ViewContext, ) { - self.terminal - .update(cx, |terminal, _| terminal.write_to_pty(text.into())); + self.terminal.update(cx, |terminal, _| { + terminal.write_to_pty(text.into()); + terminal.scroll(alacritty_terminal::grid::Scroll::Bottom); + }); } fn keymap_context(&self, _: &gpui::AppContext) -> gpui::keymap::Context { diff --git a/crates/terminal/src/mappings/keys.rs b/crates/terminal/src/mappings/keys.rs index e07a3249984d07502e59ad473ba7f6e3f6d4b522..002759d78ddff932ea928706b7741a45ff4cb0f7 100644 --- a/crates/terminal/src/mappings/keys.rs +++ b/crates/terminal/src/mappings/keys.rs @@ -53,7 +53,6 @@ pub fn to_esc_str(keystroke: &Keystroke, mode: &TermMode) -> Option { // Manual Bindings including modifiers let manual_esc_str = match (keystroke.key.as_ref(), &modifiers) { //Basic special keys - ("space", Modifiers::None) => Some(" ".to_string()), ("tab", Modifiers::None) => Some("\x09".to_string()), ("escape", Modifiers::None) => Some("\x1b".to_string()), ("enter", Modifiers::None) => Some("\x0d".to_string()), diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index 0debf4fa918e3a0129d2ad16ab4ca42c65f1a826..5254ffbe7fbcdf474249fa80fe46ebd1e3a30cf8 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -62,6 +62,7 @@ pub enum Event { CloseTerminal, Bell, Wakeup, + BlinkChanged, } #[derive(Clone, Debug)] @@ -294,15 +295,10 @@ impl TerminalBuilder { let mut term = Term::new(&config, &initial_size, ZedListener(events_tx.clone())); //Start off blinking if we need to - match blink_settings { - Some(setting) => match setting { - TerminalBlink::On | TerminalBlink::Always => { - term.set_mode(alacritty_terminal::ansi::Mode::BlinkingCursor) - } - _ => {} - }, - None => term.set_mode(alacritty_terminal::ansi::Mode::BlinkingCursor), + if let Some(TerminalBlink::On) = blink_settings { + term.set_mode(alacritty_terminal::ansi::Mode::BlinkingCursor) } + let term = Arc::new(FairMutex::new(term)); //Setup the pty... @@ -469,17 +465,17 @@ impl Terminal { AlacTermEvent::ClipboardStore(_, data) => { cx.write_to_clipboard(ClipboardItem::new(data.to_string())) } - AlacTermEvent::ClipboardLoad(_, format) => self.notify_pty(format( + AlacTermEvent::ClipboardLoad(_, format) => self.write_to_pty(format( &cx.read_from_clipboard() .map(|ci| ci.text().to_string()) .unwrap_or_else(|| "".to_string()), )), - AlacTermEvent::PtyWrite(out) => self.notify_pty(out.clone()), + AlacTermEvent::PtyWrite(out) => self.write_to_pty(out.clone()), AlacTermEvent::TextAreaSizeRequest(format) => { - self.notify_pty(format(self.cur_size.into())) + self.write_to_pty(format(self.cur_size.into())) } AlacTermEvent::CursorBlinkingChange => { - //TODO whatever state we need to set to get the cursor blinking + cx.emit(Event::BlinkChanged); } AlacTermEvent::Bell => { cx.emit(Event::Bell); @@ -519,7 +515,7 @@ impl Terminal { let term_style = &cx.global::().theme.terminal; to_alac_rgb(get_color_at_index(index, &term_style.colors)) }); - self.notify_pty(format(color)) + self.write_to_pty(format(color)) } } InternalEvent::Resize(new_size) => { @@ -530,7 +526,7 @@ impl Terminal { term.resize(*new_size); } InternalEvent::Clear => { - self.notify_pty("\x0c".to_string()); + self.write_to_pty("\x0c".to_string()); term.clear_screen(ClearMode::Saved); } InternalEvent::Scroll(scroll) => term.scroll_display(*scroll), @@ -550,12 +546,8 @@ impl Terminal { } } - pub fn notify_pty(&self, txt: String) { - self.pty_tx.notify(txt.into_bytes()); - } - ///Write the Input payload to the tty. - pub fn write_to_pty(&mut self, input: String) { + pub fn write_to_pty(&self, input: String) { self.pty_tx.notify(input.into_bytes()); } @@ -568,10 +560,11 @@ impl Terminal { self.events.push(InternalEvent::Clear) } - pub fn try_keystroke(&self, keystroke: &Keystroke) -> bool { + pub fn try_keystroke(&mut self, keystroke: &Keystroke) -> bool { let esc = to_esc_str(keystroke, &self.last_mode); if let Some(esc) = esc { - self.notify_pty(esc); + self.write_to_pty(esc); + self.scroll(Scroll::Bottom); true } else { false @@ -581,11 +574,11 @@ impl Terminal { ///Paste text into the terminal pub fn paste(&self, text: &str) { if self.last_mode.contains(TermMode::BRACKETED_PASTE) { - self.notify_pty("\x1b[200~".to_string()); - self.notify_pty(text.replace('\x1b', "")); - self.notify_pty("\x1b[201~".to_string()); + self.write_to_pty("\x1b[200~".to_string()); + self.write_to_pty(text.replace('\x1b', "")); + self.write_to_pty("\x1b[201~".to_string()); } else { - self.notify_pty(text.replace("\r\n", "\r").replace('\n', "\r")); + self.write_to_pty(text.replace("\r\n", "\r").replace('\n', "\r")); } } @@ -595,7 +588,7 @@ impl Terminal { pub fn render_lock(&mut self, cx: &mut ModelContext, f: F) -> T where - F: FnOnce(RenderableContent, char, bool) -> T, + F: FnOnce(RenderableContent, char) -> T, { let m = self.term.clone(); //Arc clone let mut term = m.lock(); @@ -611,7 +604,7 @@ impl Terminal { let cursor_text = term.grid()[content.cursor.point].c; - f(content, cursor_text, term.cursor_style().blinking) + f(content, cursor_text) } ///Scroll the terminal @@ -621,13 +614,13 @@ impl Terminal { pub fn focus_in(&self) { if self.last_mode.contains(TermMode::FOCUS_IN_OUT) { - self.notify_pty("\x1b[I".to_string()); + self.write_to_pty("\x1b[I".to_string()); } } pub fn focus_out(&self) { if self.last_mode.contains(TermMode::FOCUS_IN_OUT) { - self.notify_pty("\x1b[O".to_string()); + self.write_to_pty("\x1b[O".to_string()); } }