summaryrefslogtreecommitdiffstats
path: root/alacritty_terminal
diff options
context:
space:
mode:
authorDettorer <Dettorer@users.noreply.github.com>2020-11-24 00:11:03 +0100
committerGitHub <noreply@github.com>2020-11-23 23:11:03 +0000
commit2fd2db4afa232ebd15dbfff88160224badeaa669 (patch)
treeef0cdf3311df017da5fff4d29ce898d690980a3e /alacritty_terminal
parent07cfe8bbba0851ff4989f6aaf082d72130cd0f5b (diff)
Add blinking cursor support
This adds support for blinking the terminal cursor. This can be controlled either using the configuration file, or using escape sequences. The supported control sequences for changing the blinking state are `CSI Ps SP q` and private mode 12.
Diffstat (limited to 'alacritty_terminal')
-rw-r--r--alacritty_terminal/src/ansi.rs45
-rw-r--r--alacritty_terminal/src/config/mod.rs106
-rw-r--r--alacritty_terminal/src/event.rs2
-rw-r--r--alacritty_terminal/src/term/mod.rs73
4 files changed, 189 insertions, 37 deletions
diff --git a/alacritty_terminal/src/ansi.rs b/alacritty_terminal/src/ansi.rs
index 877fd65f..7567eba2 100644
--- a/alacritty_terminal/src/ansi.rs
+++ b/alacritty_terminal/src/ansi.rs
@@ -141,6 +141,9 @@ pub trait Handler {
/// Set the cursor style.
fn set_cursor_style(&mut self, _: Option<CursorStyle>) {}
+ /// Set the cursor shape.
+ fn set_cursor_shape(&mut self, _shape: CursorShape) {}
+
/// A character to be displayed.
fn input(&mut self, _c: char) {}
@@ -324,9 +327,16 @@ pub trait Handler {
fn text_area_size_chars<W: io::Write>(&mut self, _: &mut W) {}
}
-/// Describes shape of cursor.
-#[derive(Debug, Eq, PartialEq, Copy, Clone, Hash, Deserialize)]
-pub enum CursorStyle {
+/// Terminal cursor configuration.
+#[derive(Deserialize, Default, Debug, Eq, PartialEq, Copy, Clone, Hash)]
+pub struct CursorStyle {
+ pub shape: CursorShape,
+ pub blinking: bool,
+}
+
+/// Terminal cursor shape.
+#[derive(Deserialize, Debug, Eq, PartialEq, Copy, Clone, Hash)]
+pub enum CursorShape {
/// Cursor is a block like `▒`.
Block,
@@ -345,9 +355,9 @@ pub enum CursorStyle {
Hidden,
}
-impl Default for CursorStyle {
- fn default() -> CursorStyle {
- CursorStyle::Block
+impl Default for CursorShape {
+ fn default() -> CursorShape {
+ CursorShape::Block
}
}
@@ -874,13 +884,13 @@ where
&& params[1].len() >= 13
&& params[1][0..12] == *b"CursorShape="
{
- let style = match params[1][12] as char {
- '0' => CursorStyle::Block,
- '1' => CursorStyle::Beam,
- '2' => CursorStyle::Underline,
+ let shape = match params[1][12] as char {
+ '0' => CursorShape::Block,
+ '1' => CursorShape::Beam,
+ '2' => CursorShape::Underline,
_ => return unhandled(params),
};
- self.handler.set_cursor_style(Some(style));
+ self.handler.set_cursor_shape(shape);
return;
}
unhandled(params);
@@ -1065,18 +1075,21 @@ where
('P', None) => handler.delete_chars(Column(next_param_or(1) as usize)),
('q', Some(b' ')) => {
// DECSCUSR (CSI Ps SP q) -- Set Cursor Style.
- let style = match next_param_or(0) {
+ let cursor_style_id = next_param_or(0);
+ let shape = match cursor_style_id {
0 => None,
- 1 | 2 => Some(CursorStyle::Block),
- 3 | 4 => Some(CursorStyle::Underline),
- 5 | 6 => Some(CursorStyle::Beam),
+ 1 | 2 => Some(CursorShape::Block),
+ 3 | 4 => Some(CursorShape::Underline),
+ 5 | 6 => Some(CursorShape::Beam),
_ => {
unhandled!();
return;
},
};
+ let cursor_style =
+ shape.map(|shape| CursorStyle { shape, blinking: cursor_style_id % 2 == 1 });
- handler.set_cursor_style(style);
+ handler.set_cursor_style(cursor_style);
},
('r', None) => {
let top = next_param_or(1) as usize;
diff --git a/alacritty_terminal/src/config/mod.rs b/alacritty_terminal/src/config/mod.rs
index 98849d90..f3221920 100644
--- a/alacritty_terminal/src/config/mod.rs
+++ b/alacritty_terminal/src/config/mod.rs
@@ -1,3 +1,4 @@
+use std::cmp::max;
use std::collections::HashMap;
use std::fmt::Display;
use std::path::PathBuf;
@@ -10,15 +11,16 @@ mod bell;
mod colors;
mod scrolling;
-use crate::ansi::CursorStyle;
+use crate::ansi::{CursorShape, CursorStyle};
pub use crate::config::bell::{BellAnimation, BellConfig};
pub use crate::config::colors::Colors;
pub use crate::config::scrolling::Scrolling;
pub const LOG_TARGET_CONFIG: &str = "alacritty_config";
-const MAX_SCROLLBACK_LINES: u32 = 100_000;
const DEFAULT_CURSOR_THICKNESS: f32 = 0.15;
+const MAX_SCROLLBACK_LINES: u32 = 100_000;
+const MIN_BLINK_INTERVAL: u64 = 10;
pub type MockConfig = Config<HashMap<String, serde_yaml::Value>>;
@@ -121,9 +123,11 @@ impl Default for EscapeChars {
#[derive(Deserialize, Copy, Clone, Debug, PartialEq)]
pub struct Cursor {
#[serde(deserialize_with = "failure_default")]
- pub style: CursorStyle,
+ pub style: ConfigCursorStyle,
#[serde(deserialize_with = "option_explicit_none")]
- pub vi_mode_style: Option<CursorStyle>,
+ pub vi_mode_style: Option<ConfigCursorStyle>,
+ #[serde(deserialize_with = "failure_default")]
+ blink_interval: BlinkInterval,
#[serde(deserialize_with = "deserialize_cursor_thickness")]
thickness: Percentage,
#[serde(deserialize_with = "failure_default")]
@@ -140,6 +144,21 @@ impl Cursor {
pub fn thickness(self) -> f64 {
self.thickness.0 as f64
}
+
+ #[inline]
+ pub fn style(self) -> CursorStyle {
+ self.style.into()
+ }
+
+ #[inline]
+ pub fn vi_mode_style(self) -> Option<CursorStyle> {
+ self.vi_mode_style.map(From::from)
+ }
+
+ #[inline]
+ pub fn blink_interval(self) -> u64 {
+ max(self.blink_interval.0, MIN_BLINK_INTERVAL)
+ }
}
impl Default for Cursor {
@@ -149,10 +168,20 @@ impl Default for Cursor {
vi_mode_style: Default::default(),
thickness: Percentage::new(DEFAULT_CURSOR_THICKNESS),
unfocused_hollow: Default::default(),
+ blink_interval: Default::default(),
}
}
}
+#[derive(Deserialize, Copy, Clone, Debug, PartialEq)]
+struct BlinkInterval(u64);
+
+impl Default for BlinkInterval {
+ fn default() -> Self {
+ BlinkInterval(750)
+ }
+}
+
fn deserialize_cursor_thickness<'a, D>(deserializer: D) -> Result<Percentage, D::Error>
where
D: Deserializer<'a>,
@@ -174,6 +203,75 @@ where
}
#[serde(untagged)]
+#[derive(Deserialize, Debug, Copy, Clone, PartialEq, Eq)]
+pub enum ConfigCursorStyle {
+ Shape(CursorShape),
+ WithBlinking {
+ #[serde(default, deserialize_with = "failure_default")]
+ shape: CursorShape,
+ #[serde(default, deserialize_with = "failure_default")]
+ blinking: CursorBlinking,
+ },
+}
+
+impl Default for ConfigCursorStyle {
+ fn default() -> Self {
+ Self::WithBlinking { shape: CursorShape::default(), blinking: CursorBlinking::default() }
+ }
+}
+
+impl ConfigCursorStyle {
+ /// Check if blinking is force enabled/disabled.
+ pub fn blinking_override(&self) -> Option<bool> {
+ match self {
+ Self::Shape(_) => None,
+ Self::WithBlinking { blinking, .. } => blinking.blinking_override(),
+ }
+ }
+}
+
+impl From<ConfigCursorStyle> for CursorStyle {
+ fn from(config_style: ConfigCursorStyle) -> Self {
+ match config_style {
+ ConfigCursorStyle::Shape(shape) => Self { shape, blinking: false },
+ ConfigCursorStyle::WithBlinking { shape, blinking } => {
+ Self { shape, blinking: blinking.into() }
+ },
+ }
+ }
+}
+
+#[derive(Deserialize, Debug, Copy, Clone, PartialEq, Eq)]
+pub enum CursorBlinking {
+ Never,
+ Off,
+ On,
+ Always,
+}
+
+impl Default for CursorBlinking {
+ fn default() -> Self {
+ CursorBlinking::Off
+ }
+}
+
+impl CursorBlinking {
+ fn blinking_override(&self) -> Option<bool> {
+ match self {
+ Self::Never => Some(false),
+ Self::Off | Self::On => None,
+ Self::Always => Some(true),
+ }
+ }
+}
+
+impl Into<bool> for CursorBlinking {
+ fn into(self) -> bool {
+ self == Self::On || self == Self::Always
+ }
+}
+
+#[serde(untagged)]
#[derive(Deserialize, Debug, Clone, PartialEq, Eq)]
pub enum Program {
Just(String),
diff --git a/alacritty_terminal/src/event.rs b/alacritty_terminal/src/event.rs
index c7129b24..351b7bc2 100644
--- a/alacritty_terminal/src/event.rs
+++ b/alacritty_terminal/src/event.rs
@@ -11,6 +11,7 @@ pub enum Event {
ResetTitle,
ClipboardStore(ClipboardType, String),
ClipboardLoad(ClipboardType, Arc<dyn Fn(&str) -> String + Sync + Send + 'static>),
+ CursorBlinkingChange(bool),
Wakeup,
Bell,
Exit,
@@ -27,6 +28,7 @@ impl Debug for Event {
Event::Wakeup => write!(f, "Wakeup"),
Event::Bell => write!(f, "Bell"),
Event::Exit => write!(f, "Exit"),
+ Event::CursorBlinkingChange(blinking) => write!(f, "CursorBlinking({})", blinking),
}
}
}
diff --git a/alacritty_terminal/src/term/mod.rs b/alacritty_terminal/src/term/mod.rs
index 926b89d7..accb4dc1 100644
--- a/alacritty_terminal/src/term/mod.rs
+++ b/alacritty_terminal/src/term/mod.rs
@@ -12,7 +12,7 @@ use serde::{Deserialize, Serialize};
use unicode_width::UnicodeWidthChar;
use crate::ansi::{
- self, Attr, CharsetIndex, Color, CursorStyle, Handler, NamedColor, StandardCharset,
+ self, Attr, CharsetIndex, Color, CursorShape, CursorStyle, Handler, NamedColor, StandardCharset,
};
use crate::config::{BellAnimation, BellConfig, Config};
use crate::event::{Event, EventListener};
@@ -61,7 +61,7 @@ struct RenderableCursor {
/// A key for caching cursor glyphs.
#[derive(Debug, Eq, PartialEq, Copy, Clone, Hash, Deserialize)]
pub struct CursorKey {
- pub style: CursorStyle,
+ pub shape: CursorShape,
pub is_wide: bool,
}
@@ -202,7 +202,7 @@ impl<'a, C> RenderableCellsIter<'a, C> {
let cell = self.inner.next()?;
let mut cell = RenderableCell::new(self, cell);
- if self.cursor.key.style == CursorStyle::Block {
+ if self.cursor.key.shape == CursorShape::Block {
cell.fg = match self.cursor.cursor_color {
// Apply cursor color, or invert the cursor if it has a fixed background
// close to the cell's background.
@@ -249,7 +249,7 @@ impl<'a, C> RenderableCellsIter<'a, C> {
};
// Do not invert block cursor at selection boundaries.
- if self.cursor.key.style == CursorStyle::Block
+ if self.cursor.key.shape == CursorShape::Block
&& self.cursor.point == point
&& (selection.start == point
|| selection.end == point
@@ -855,8 +855,8 @@ impl<T> Term<T> {
original_colors: colors,
semantic_escape_chars: config.selection.semantic_escape_chars().to_owned(),
cursor_style: None,
- default_cursor_style: config.cursor.style,
- vi_mode_cursor_style: config.cursor.vi_mode_style,
+ default_cursor_style: config.cursor.style(),
+ vi_mode_cursor_style: config.cursor.vi_mode_style(),
event_proxy,
is_focused: true,
title: None,
@@ -885,8 +885,8 @@ impl<T> Term<T> {
if let Some(0) = config.scrolling.faux_multiplier() {
self.mode.remove(TermMode::ALTERNATE_SCROLL);
}
- self.default_cursor_style = config.cursor.style;
- self.vi_mode_cursor_style = config.cursor.vi_mode_style;
+ self.default_cursor_style = config.cursor.style();
+ self.vi_mode_cursor_style = config.cursor.vi_mode_style();
let title_event = match &self.title {
Some(title) => Event::Title(title.clone()),
@@ -1207,7 +1207,10 @@ impl<T> Term<T> {
/// Toggle the vi mode.
#[inline]
- pub fn toggle_vi_mode(&mut self) {
+ pub fn toggle_vi_mode(&mut self)
+ where
+ T: EventListener,
+ {
self.mode ^= TermMode::VI;
let vi_mode = self.mode.contains(TermMode::VI);
@@ -1226,6 +1229,9 @@ impl<T> Term<T> {
self.cancel_search();
}
+ // Update UI about cursor blinking state changes.
+ self.event_proxy.send_event(Event::CursorBlinkingChange(self.cursor_style().blinking));
+
self.dirty = true;
}
@@ -1332,6 +1338,20 @@ impl<T> Term<T> {
&self.semantic_escape_chars
}
+ /// Active terminal cursor style.
+ ///
+ /// While vi mode is active, this will automatically return the vi mode cursor style.
+ #[inline]
+ pub fn cursor_style(&self) -> CursorStyle {
+ let cursor_style = self.cursor_style.unwrap_or(self.default_cursor_style);
+
+ if self.mode.contains(TermMode::VI) {
+ self.vi_mode_cursor_style.unwrap_or(cursor_style)
+ } else {
+ cursor_style
+ }
+ }
+
/// Insert a linebreak at the current cursor position.
#[inline]
fn wrapline(&mut self)
@@ -1395,18 +1415,18 @@ impl<T> Term<T> {
// Cursor shape.
let hidden =
!self.mode.contains(TermMode::SHOW_CURSOR) || point.line >= self.screen_lines();
- let cursor_style = if hidden && !vi_mode {
+ let cursor_shape = if hidden && !vi_mode {
point.line = Line(0);
- CursorStyle::Hidden
+ CursorShape::Hidden
} else if !self.is_focused && config.cursor.unfocused_hollow() {
- CursorStyle::HollowBlock
+ CursorShape::HollowBlock
} else {
let cursor_style = self.cursor_style.unwrap_or(self.default_cursor_style);
if vi_mode {
- self.vi_mode_cursor_style.unwrap_or(cursor_style)
+ self.vi_mode_cursor_style.unwrap_or(cursor_style).shape
} else {
- cursor_style
+ cursor_style.shape
}
};
@@ -1432,7 +1452,7 @@ impl<T> Term<T> {
RenderableCursor {
text_color,
cursor_color,
- key: CursorKey { style: cursor_style, is_wide },
+ key: CursorKey { shape: cursor_shape, is_wide },
point,
rendered: false,
}
@@ -2098,6 +2118,9 @@ impl<T: EventListener> Handler for Term<T> {
// Preserve vi mode across resets.
self.mode &= TermMode::VI;
self.mode.insert(TermMode::default());
+
+ let blinking = self.cursor_style().blinking;
+ self.event_proxy.send_event(Event::CursorBlinkingChange(blinking));
}
#[inline]
@@ -2199,7 +2222,9 @@ impl<T: EventListener> Handler for Term<T> {
ansi::Mode::DECCOLM => self.deccolm(),
ansi::Mode::Insert => self.mode.insert(TermMode::INSERT),
ansi::Mode::BlinkingCursor => {
- trace!("... unimplemented mode");
+ let style = self.cursor_style.get_or_insert(self.default_cursor_style);
+ style.blinking = true;
+ self.event_proxy.send_event(Event::CursorBlinkingChange(true));
},
}
}
@@ -2239,7 +2264,9 @@ impl<T: EventListener> Handler for Term<T> {
ansi::Mode::DECCOLM => self.deccolm(),
ansi::Mode::Insert => self.mode.remove(TermMode::INSERT),
ansi::Mode::BlinkingCursor => {
- trace!("... unimplemented mode");
+ let style = self.cursor_style.get_or_insert(self.default_cursor_style);
+ style.blinking = false;
+ self.event_proxy.send_event(Event::CursorBlinkingChange(false));
},
}
}
@@ -2296,6 +2323,18 @@ impl<T: EventListener> Handler for Term<T> {
fn set_cursor_style(&mut self, style: Option<CursorStyle>) {
trace!("Setting cursor style {:?}", style);
self.cursor_style = style;
+
+ // Notify UI about blinking changes.
+ let blinking = style.unwrap_or(self.default_cursor_style).blinking;
+ self.event_proxy.send_event(Event::CursorBlinkingChange(blinking));
+ }
+
+ #[inline]
+ fn set_cursor_shape(&mut self, shape: CursorShape) {
+ trace!("Setting cursor shape {:?}", shape);
+
+ let style = self.cursor_style.get_or_insert(self.default_cursor_style);
+ style.shape = shape;
}
#[inline]