Merge pull request #1520 from zed-industries/terminal-blink
Terminal cursor blinking
This commit is contained in:
commit
73cd6b51d8
7 changed files with 258 additions and 53 deletions
|
@ -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": {
|
||||
|
|
|
@ -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,10 +1829,40 @@ impl Cursor {
|
|||
corner_radius: 0.,
|
||||
});
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
|
|
@ -83,6 +83,22 @@ pub struct TerminalSettings {
|
|||
pub font_size: Option<f32>,
|
||||
pub font_family: Option<String>,
|
||||
pub env: Option<HashMap<String, String>>,
|
||||
pub blinking: Option<TerminalBlink>,
|
||||
}
|
||||
|
||||
#[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)]
|
||||
|
|
|
@ -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<ConnectedView>,
|
||||
modal: bool,
|
||||
focused: bool,
|
||||
blink_state: bool,
|
||||
}
|
||||
|
||||
impl TerminalEl {
|
||||
|
@ -209,12 +210,14 @@ impl TerminalEl {
|
|||
terminal: WeakModelHandle<Terminal>,
|
||||
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<TerminalBlink>,
|
||||
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::<Settings>();
|
||||
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, blink_mode| {
|
||||
let mut cells = vec![];
|
||||
cells.extend(
|
||||
content
|
||||
|
@ -628,6 +659,7 @@ impl Element for TerminalEl {
|
|||
content.cursor,
|
||||
content.display_offset,
|
||||
cursor_text,
|
||||
blink_mode,
|
||||
)
|
||||
})
|
||||
});
|
||||
|
@ -644,6 +676,14 @@ impl Element for TerminalEl {
|
|||
|
||||
//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();
|
||||
|
@ -673,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(
|
||||
|
@ -686,6 +726,7 @@ impl Element for TerminalEl {
|
|||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
//Done!
|
||||
|
@ -818,7 +859,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
|
||||
|
|
|
@ -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<ContextMenu>,
|
||||
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 blink_show(&self) -> bool {
|
||||
self.blinking_paused || self.show_cursor
|
||||
}
|
||||
|
||||
fn blink_cursors(&mut self, epoch: usize, cx: &mut ViewContext<Self>) {
|
||||
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>) {
|
||||
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<Self>) {
|
||||
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>) {
|
||||
self.terminal.update(cx, |term, _| term.copy())
|
||||
|
@ -189,7 +253,13 @@ impl View for ConnectedView {
|
|||
|
||||
Stack::new()
|
||||
.with_child(
|
||||
TerminalEl::new(cx.handle(), terminal_handle, self.modal, focused)
|
||||
TerminalEl::new(
|
||||
cx.handle(),
|
||||
terminal_handle,
|
||||
self.modal,
|
||||
focused,
|
||||
self.blink_show(),
|
||||
)
|
||||
.contained()
|
||||
.boxed(),
|
||||
)
|
||||
|
@ -200,6 +270,7 @@ impl View for ConnectedView {
|
|||
fn on_focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
|
||||
self.has_new_content = false;
|
||||
self.terminal.read(cx).focus_in();
|
||||
self.blink_cursors(self.blink_epoch, cx);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
|
@ -208,6 +279,7 @@ impl View for ConnectedView {
|
|||
cx.notify();
|
||||
}
|
||||
|
||||
//IME stuff
|
||||
fn selected_text_range(&self, cx: &AppContext) -> Option<std::ops::Range<usize>> {
|
||||
if self
|
||||
.terminal
|
||||
|
|
|
@ -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<Shell>,
|
||||
env: Option<HashMap<String, String>>,
|
||||
initial_size: TerminalSize,
|
||||
blink_settings: Option<TerminalBlink>,
|
||||
) -> Result<TerminalBuilder> {
|
||||
let pty_config = {
|
||||
let alac_shell = shell.clone().and_then(|shell| match shell {
|
||||
|
@ -287,9 +288,21 @@ 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()));
|
||||
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...
|
||||
|
@ -321,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,
|
||||
|
@ -582,7 +595,7 @@ impl Terminal {
|
|||
|
||||
pub fn render_lock<F, T>(&mut self, cx: &mut ModelContext<Self>, 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();
|
||||
|
@ -598,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
|
||||
|
|
|
@ -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));
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue