From 7b3a7727c64797fa449ad4bd3667953812d9761e Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Mon, 15 Aug 2022 17:00:44 -0700 Subject: [PATCH 1/3] Basic cursor blinking :) --- crates/terminal/src/connected_el.rs | 94 ++++++++++++++++----------- crates/terminal/src/connected_view.rs | 66 +++++++++++++++++++ crates/terminal/src/terminal.rs | 1 + 3 files changed, 123 insertions(+), 38 deletions(-) diff --git a/crates/terminal/src/connected_el.rs b/crates/terminal/src/connected_el.rs index 69fc5f581f..392783f02d 100644 --- a/crates/terminal/src/connected_el.rs +++ b/crates/terminal/src/connected_el.rs @@ -643,49 +643,64 @@ impl Element for TerminalEl { ); //Layout cursor + //TODO: This logic can be a lot better + let show_cursor = if let Some(view_handle) = self.view.upgrade(cx) { + if view_handle.read(cx).show_cursor() { + false + } else { + true + } + } else { + true + }; + let cursor = { - let cursor_point = DisplayCursor::from(cursor.point, display_offset); - let cursor_text = { - let str_trxt = cursor_text.to_string(); + if show_cursor { + 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(), - }, - )], - ) - }; - - 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) + let color = if self.focused { + terminal_theme.colors.background } else { - (CursorShape::Underscore, terminal_theme.colors.foreground) + terminal_theme.colors.foreground }; - Cursor::new( - cursor_position, - block_width, - dimensions.line_height, - color, - shape, - Some(cursor_text), + 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(), + }, + )], ) - }, - ) + }; + + 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::Underscore, terminal_theme.colors.foreground) + }; + + Cursor::new( + cursor_position, + block_width, + dimensions.line_height, + color, + shape, + Some(cursor_text), + ) + }, + ) + } }; //Done! @@ -818,7 +833,10 @@ impl Element for TerminalEl { //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)) + view.update(cx.app, |view, cx| { + view.clear_bel(cx); + view.pause_cursor_blinking(cx); + }) } self.terminal diff --git a/crates/terminal/src/connected_view.rs b/crates/terminal/src/connected_view.rs index 9e909d5bcc..924dece2c2 100644 --- a/crates/terminal/src/connected_view.rs +++ b/crates/terminal/src/connected_view.rs @@ -1,3 +1,5 @@ +use std::time::Duration; + use alacritty_terminal::term::TermMode; use context_menu::{ContextMenu, ContextMenuItem}; use gpui::{ @@ -9,10 +11,13 @@ use gpui::{ AnyViewHandle, AppContext, Element, ElementBox, ModelHandle, MutableAppContext, View, ViewContext, ViewHandle, }; +use smol::Timer; use workspace::pane; use crate::{connected_el::TerminalEl, Event, Terminal}; +const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500); + ///Event to transmit the scroll from the element to the view #[derive(Clone, Debug, PartialEq)] pub struct ScrollTerminal(pub i32); @@ -51,6 +56,9 @@ pub struct ConnectedView { // Only for styling purposes. Doesn't effect behavior modal: bool, context_menu: ViewHandle, + show_cursor: bool, + blinking_paused: bool, + blink_epoch: usize, } impl ConnectedView { @@ -83,6 +91,9 @@ impl ConnectedView { has_bell: false, modal, context_menu: cx.add_view(ContextMenu::new), + show_cursor: true, + blinking_paused: false, + blink_epoch: 0, } } @@ -120,6 +131,59 @@ impl ConnectedView { cx.notify(); } + //Following code copied from editor cursor + pub fn show_cursor(&self) -> bool { + self.blinking_paused || self.show_cursor + } + + fn blink_cursors(&mut self, epoch: usize, cx: &mut ViewContext) { + if epoch == self.blink_epoch && !self.blinking_paused { + self.show_cursor = !self.show_cursor; + cx.notify(); + + let epoch = self.next_blink_epoch(); + cx.spawn(|this, mut cx| { + let this = this.downgrade(); + async move { + Timer::after(CURSOR_BLINK_INTERVAL).await; + if let Some(this) = this.upgrade(&cx) { + this.update(&mut cx, |this, cx| this.blink_cursors(epoch, cx)); + } + } + }) + .detach(); + } + } + + pub fn pause_cursor_blinking(&mut self, cx: &mut ViewContext) { + self.show_cursor = true; + cx.notify(); + + let epoch = self.next_blink_epoch(); + cx.spawn(|this, mut cx| { + let this = this.downgrade(); + async move { + Timer::after(CURSOR_BLINK_INTERVAL).await; + if let Some(this) = this.upgrade(&cx) { + this.update(&mut cx, |this, cx| this.resume_cursor_blinking(epoch, cx)) + } + } + }) + .detach(); + } + + fn next_blink_epoch(&mut self) -> usize { + self.blink_epoch += 1; + self.blink_epoch + } + + fn resume_cursor_blinking(&mut self, epoch: usize, cx: &mut ViewContext) { + if epoch == self.blink_epoch { + self.blinking_paused = false; + self.blink_cursors(epoch, cx); + } + } + ///Attempt to paste the clipboard into the terminal fn copy(&mut self, _: &Copy, cx: &mut ViewContext) { self.terminal.update(cx, |term, _| term.copy()) @@ -200,6 +264,7 @@ impl View for ConnectedView { fn on_focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext) { self.has_new_content = false; self.terminal.read(cx).focus_in(); + self.blink_cursors(self.blink_epoch, cx); cx.notify(); } @@ -208,6 +273,7 @@ impl View for ConnectedView { cx.notify(); } + //IME stuff fn selected_text_range(&self, cx: &AppContext) -> Option> { if self .terminal diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index af1c763f52..0c1d29a248 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -287,6 +287,7 @@ impl TerminalBuilder { setup_env(&config); //Spawn a task so the Alacritty EventLoop can communicate with us in a view context + //TODO: Remove with a bounded sender which can be dispatched on &self let (events_tx, events_rx) = unbounded(); //Set up the terminal... let term = Term::new(&config, &initial_size, ZedListener(events_tx.clone())); From bba51c3ae6df6fc03b41fa95f85a38bf059cc19f Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Mon, 15 Aug 2022 18:05:07 -0700 Subject: [PATCH 2/3] Added cursor blink and settings --- assets/settings/default.json | 13 +++++++ crates/settings/src/settings.rs | 16 ++++++++ crates/terminal/src/connected_el.rs | 56 ++++++++++++++++++++------- crates/terminal/src/connected_view.rs | 14 +++++-- crates/terminal/src/terminal.rs | 22 ++++++++--- crates/terminal/src/terminal_view.rs | 9 ++++- 6 files changed, 104 insertions(+), 26 deletions(-) diff --git a/assets/settings/default.json b/assets/settings/default.json index 43b4be512f..c6f08f4e56 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -102,6 +102,19 @@ // // "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": "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 + // "blinking": "on", + // 4. Always blink the cursor, ignoring the terminal mode + // "blinking": "always", + "blinking": "on", //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/settings/src/settings.rs b/crates/settings/src/settings.rs index ce1fa09d99..9c1a17a462 100644 --- a/crates/settings/src/settings.rs +++ b/crates/settings/src/settings.rs @@ -83,6 +83,22 @@ pub struct TerminalSettings { pub font_size: Option, pub font_family: Option, pub env: Option>, + pub blinking: Option, +} + +#[derive(Clone, Debug, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum TerminalBlink { + Never, + On, + Off, + Always, +} + +impl Default for TerminalBlink { + fn default() -> Self { + TerminalBlink::On + } } #[derive(Clone, Debug, Deserialize, PartialEq, Eq, JsonSchema)] diff --git a/crates/terminal/src/connected_el.rs b/crates/terminal/src/connected_el.rs index 392783f02d..eb9eb32b81 100644 --- a/crates/terminal/src/connected_el.rs +++ b/crates/terminal/src/connected_el.rs @@ -21,7 +21,7 @@ use gpui::{ }; use itertools::Itertools; use ordered_float::OrderedFloat; -use settings::Settings; +use settings::{Settings, TerminalBlink}; use theme::TerminalStyle; use util::ResultExt; @@ -201,6 +201,7 @@ pub struct TerminalEl { view: WeakViewHandle, modal: bool, focused: bool, + blink_state: bool, } impl TerminalEl { @@ -209,12 +210,14 @@ impl TerminalEl { terminal: WeakModelHandle, modal: bool, focused: bool, + blink_state: bool, ) -> TerminalEl { TerminalEl { view, terminal, modal, focused, + blink_state, } } @@ -568,6 +571,33 @@ 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 { @@ -580,6 +610,7 @@ 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 @@ -598,13 +629,13 @@ 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, blink_mode) = self .terminal .upgrade(cx) .unwrap() .update(cx.app, |terminal, mcx| { terminal.set_size(dimensions); - terminal.render_lock(mcx, |content, cursor_text| { + terminal.render_lock(mcx, |content, cursor_text, style| { let mut cells = vec![]; cells.extend( content @@ -628,6 +659,7 @@ impl Element for TerminalEl { content.cursor, content.display_offset, cursor_text, + style, ) }) }); @@ -643,19 +675,13 @@ impl Element for TerminalEl { ); //Layout cursor - //TODO: This logic can be a lot better - let show_cursor = if let Some(view_handle) = self.view.upgrade(cx) { - if view_handle.read(cx).show_cursor() { - false - } else { - true - } - } else { - true - }; - let cursor = { - if show_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); diff --git a/crates/terminal/src/connected_view.rs b/crates/terminal/src/connected_view.rs index 924dece2c2..6f16ac9bcd 100644 --- a/crates/terminal/src/connected_view.rs +++ b/crates/terminal/src/connected_view.rs @@ -132,7 +132,7 @@ impl ConnectedView { } //Following code copied from editor cursor - pub fn show_cursor(&self) -> bool { + pub fn blink_show(&self) -> bool { self.blinking_paused || self.show_cursor } @@ -253,9 +253,15 @@ impl View for ConnectedView { Stack::new() .with_child( - TerminalEl::new(cx.handle(), terminal_handle, self.modal, focused) - .contained() - .boxed(), + TerminalEl::new( + cx.handle(), + terminal_handle, + self.modal, + focused, + self.blink_show(), + ) + .contained() + .boxed(), ) .with_child(ChildView::new(&self.context_menu).boxed()) .boxed() diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index 0c1d29a248..0debf4fa91 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -25,7 +25,7 @@ use futures::{ }; use modal::deploy_modal; -use settings::{Settings, Shell}; +use settings::{Settings, Shell, TerminalBlink}; use std::{collections::HashMap, fmt::Display, path::PathBuf, sync::Arc, time::Duration}; use thiserror::Error; @@ -254,6 +254,7 @@ impl TerminalBuilder { shell: Option, env: Option>, initial_size: TerminalSize, + blink_settings: Option, ) -> Result { let pty_config = { let alac_shell = shell.clone().and_then(|shell| match shell { @@ -290,7 +291,18 @@ impl TerminalBuilder { //TODO: Remove with a bounded sender which can be dispatched on &self let (events_tx, events_rx) = unbounded(); //Set up the terminal... - let term = Term::new(&config, &initial_size, ZedListener(events_tx.clone())); + let mut term = Term::new(&config, &initial_size, ZedListener(events_tx.clone())); + + //Start off blinking if we need to + 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), + } let term = Arc::new(FairMutex::new(term)); //Setup the pty... @@ -322,7 +334,7 @@ impl TerminalBuilder { //And connect them together let event_loop = EventLoop::new( term.clone(), - ZedListener(events_tx), + ZedListener(events_tx.clone()), pty, pty_config.hold, false, @@ -583,7 +595,7 @@ impl Terminal { pub fn render_lock(&mut self, cx: &mut ModelContext, f: F) -> T where - F: FnOnce(RenderableContent, char) -> T, + F: FnOnce(RenderableContent, char, bool) -> T, { let m = self.term.clone(); //Arc clone let mut term = m.lock(); @@ -599,7 +611,7 @@ impl Terminal { let cursor_text = term.grid()[content.cursor.point].c; - f(content, cursor_text) + f(content, cursor_text, term.cursor_style().blinking) } ///Scroll the terminal diff --git a/crates/terminal/src/terminal_view.rs b/crates/terminal/src/terminal_view.rs index ed63217178..a997ebf631 100644 --- a/crates/terminal/src/terminal_view.rs +++ b/crates/terminal/src/terminal_view.rs @@ -94,8 +94,13 @@ impl TerminalView { let shell = settings.terminal_overrides.shell.clone(); let envs = settings.terminal_overrides.env.clone(); //Should be short and cheap. - let content = match TerminalBuilder::new(working_directory.clone(), shell, envs, size_info) - { + let content = match TerminalBuilder::new( + working_directory.clone(), + shell, + envs, + size_info, + settings.terminal_overrides.blinking.clone(), + ) { Ok(terminal) => { let terminal = cx.add_model(|cx| terminal.subscribe(cx)); let view = cx.add_view(|cx| ConnectedView::from_terminal(terminal, modal, cx)); From b9c73127b43e631b267a3789ddc2c67d4b274253 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Mon, 15 Aug 2022 18:27:26 -0700 Subject: [PATCH 3/3] Added a hollow mode to the cursor rendering code, for terminal lost focus --- crates/editor/src/element.rs | 46 +++++++++++++++++++++++++++-- crates/terminal/src/connected_el.rs | 6 ++-- 2 files changed, 47 insertions(+), 5 deletions(-) diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index da1e78179a..6140731579 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -1753,6 +1753,7 @@ pub enum CursorShape { Bar, Block, Underscore, + Hollow, } impl Default for CursorShape { @@ -1808,8 +1809,19 @@ 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), @@ -1817,8 +1829,38 @@ impl Cursor { corner_radius: 0., }); - if let Some(block_text) = &self.block_text { - block_text.paint(self.origin + origin, bounds, self.line_height, cx); + 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()), + corner_radius: 0., + }); + //Right + cx.scene.push_quad(Quad { + bounds: RectF::new( + self.origin + origin + vec2f(self.block_width, 0.), + vec2f(1.0, self.line_height), + ), + background: Some(self.color), + border: Border::new(0., Color::black()), + corner_radius: 0., + }); + } else { + if let Some(block_text) = &self.block_text { + block_text.paint(self.origin + origin, bounds, self.line_height, cx); + } } } } diff --git a/crates/terminal/src/connected_el.rs b/crates/terminal/src/connected_el.rs index eb9eb32b81..d017aad65b 100644 --- a/crates/terminal/src/connected_el.rs +++ b/crates/terminal/src/connected_el.rs @@ -635,7 +635,7 @@ impl Element for TerminalEl { .unwrap() .update(cx.app, |terminal, mcx| { terminal.set_size(dimensions); - terminal.render_lock(mcx, |content, cursor_text, style| { + terminal.render_lock(mcx, |content, cursor_text, blink_mode| { let mut cells = vec![]; cells.extend( content @@ -659,7 +659,7 @@ impl Element for TerminalEl { content.cursor, content.display_offset, cursor_text, - style, + blink_mode, ) }) }); @@ -713,7 +713,7 @@ impl Element for TerminalEl { let (shape, color) = if self.focused { (CursorShape::Block, terminal_theme.colors.cursor) } else { - (CursorShape::Underscore, terminal_theme.colors.foreground) + (CursorShape::Hollow, terminal_theme.colors.foreground) }; Cursor::new(