aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md6
-rw-r--r--alacritty.yml26
-rw-r--r--alacritty/src/cursor.rs14
-rw-r--r--alacritty/src/display.rs7
-rw-r--r--alacritty/src/event.rs71
-rw-r--r--alacritty/src/input.rs32
-rw-r--r--alacritty/src/renderer/mod.rs2
-rw-r--r--alacritty/src/scheduler.rs1
-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
-rw-r--r--docs/escape_support.md2
13 files changed, 316 insertions, 71 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a870d9c3..7e9e48ec 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -20,6 +20,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Wide characters sometimes being cut off
- Preserve vi mode across terminal `reset`
+### Added
+
+- New `cursor.style.blinking` option to set the default blinking state
+- New `cursor.blink_interval` option to configure the blinking frequency
+- Support for cursor blinking escapes (`CSI ? 12 h`, `CSI ? 12 l` and `CSI Ps SP q`)
+
## 0.6.0
### Packaging
diff --git a/alacritty.yml b/alacritty.yml
index 3c6d18ec..2f2e5dc9 100644
--- a/alacritty.yml
+++ b/alacritty.yml
@@ -341,12 +341,23 @@
#cursor:
# Cursor style
- #
- # Values for `style`:
- # - ▇ Block
- # - _ Underline
- # - | Beam
- #style: Block
+ #style:
+ # Cursor shape
+ #
+ # Values for `shape`:
+ # - ▇ Block
+ # - _ Underline
+ # - | Beam
+ #shape: Block
+
+ # Cursor blinking state
+ #
+ # Values for `blinking`:
+ # - Never: Prevent the cursor from ever blinking
+ # - Off: Disable blinking by default
+ # - On: Enable blinking by default
+ # - Always: Force the cursor to always blink
+ #blinking: Off
# Vi mode cursor style
#
@@ -356,6 +367,9 @@
# See `cursor.style` for available options.
#vi_mode_style: None
+ # Cursor blinking interval in milliseconds.
+ #blink_interval: 750
+
# If this is `true`, the cursor will be rendered as a hollow box when the
# window is not focused.
#unfocused_hollow: true
diff --git a/alacritty/src/cursor.rs b/alacritty/src/cursor.rs
index 8c782185..edf76bf3 100644
--- a/alacritty/src/cursor.rs
+++ b/alacritty/src/cursor.rs
@@ -2,10 +2,10 @@
use crossfont::{BitmapBuffer, Metrics, RasterizedGlyph};
-use alacritty_terminal::ansi::CursorStyle;
+use alacritty_terminal::ansi::CursorShape;
pub fn get_cursor_glyph(
- cursor: CursorStyle,
+ cursor: CursorShape,
metrics: Metrics,
offset_x: i8,
offset_y: i8,
@@ -26,11 +26,11 @@ pub fn get_cursor_glyph(
}
match cursor {
- CursorStyle::HollowBlock => get_box_cursor_glyph(height, width, line_width),
- CursorStyle::Underline => get_underline_cursor_glyph(width, line_width),
- CursorStyle::Beam => get_beam_cursor_glyph(height, line_width),
- CursorStyle::Block => get_block_cursor_glyph(height, width),
- CursorStyle::Hidden => RasterizedGlyph::default(),
+ CursorShape::HollowBlock => get_box_cursor_glyph(height, width, line_width),
+ CursorShape::Underline => get_underline_cursor_glyph(width, line_width),
+ CursorShape::Beam => get_beam_cursor_glyph(height, line_width),
+ CursorShape::Block => get_block_cursor_glyph(height, width),
+ CursorShape::Hidden => RasterizedGlyph::default(),
}
}
diff --git a/alacritty/src/display.rs b/alacritty/src/display.rs
index af21001e..451874c8 100644
--- a/alacritty/src/display.rs
+++ b/alacritty/src/display.rs
@@ -160,6 +160,9 @@ pub struct Display {
#[cfg(not(any(target_os = "macos", windows)))]
pub is_x11: bool,
+ /// UI cursor visibility for blinking.
+ pub cursor_hidden: bool,
+
renderer: QuadRenderer,
glyph_cache: GlyphCache,
meter: Meter,
@@ -300,6 +303,7 @@ impl Display {
is_x11,
#[cfg(all(feature = "wayland", not(any(target_os = "macos", windows))))]
wayland_event_queue,
+ cursor_hidden: false,
})
}
@@ -442,8 +446,9 @@ impl Display {
let viewport_match = search_state
.focused_match()
.and_then(|focused_match| terminal.grid().clamp_buffer_range_to_visible(focused_match));
+ let cursor_hidden = self.cursor_hidden || search_state.regex().is_some();
- let grid_cells = terminal.renderable_cells(config, !search_active).collect::<Vec<_>>();
+ let grid_cells = terminal.renderable_cells(config, !cursor_hidden).collect::<Vec<_>>();
let visual_bell_intensity = terminal.visual_bell.intensity();
let background_color = terminal.background_color();
let cursor_point = terminal.grid().cursor.point;
diff --git a/alacritty/src/event.rs b/alacritty/src/event.rs
index c1f81300..8ecf6f00 100644
--- a/alacritty/src/event.rs
+++ b/alacritty/src/event.rs
@@ -67,6 +67,7 @@ pub enum Event {
Scroll(Scroll),
ConfigReload(PathBuf),
Message(Message),
+ BlinkCursor,
SearchNext,
}
@@ -150,6 +151,7 @@ pub struct ActionContext<'a, N, T> {
pub urls: &'a Urls,
pub scheduler: &'a mut Scheduler,
pub search_state: &'a mut SearchState,
+ cursor_hidden: &'a mut bool,
cli_options: &'a CLIOptions,
font_size: &'a mut Size,
}
@@ -495,6 +497,28 @@ impl<'a, N: Notify + 'a, T: EventListener> input::ActionContext<T> for ActionCon
}
}
+ /// Handle keyboard typing start.
+ ///
+ /// This will temporarily disable some features like terminal cursor blinking or the mouse
+ /// cursor.
+ ///
+ /// All features are re-enabled again automatically.
+ #[inline]
+ fn on_typing_start(&mut self) {
+ // Disable cursor blinking.
+ let blink_interval = self.config.cursor.blink_interval();
+ if let Some(timer) = self.scheduler.get_mut(TimerId::BlinkCursor) {
+ timer.deadline = Instant::now() + Duration::from_millis(blink_interval);
+ *self.cursor_hidden = false;
+ self.terminal.dirty = true;
+ }
+
+ // Hide mouse cursor.
+ if self.config.ui_config.mouse.hide_when_typing {
+ self.window.set_mouse_visible(false);
+ }
+ }
+
#[inline]
fn search_direction(&self) -> Direction {
self.search_state.direction
@@ -667,6 +691,33 @@ impl<'a, N: Notify + 'a, T: EventListener> ActionContext<'a, N, T> {
origin.line = (origin.line as isize + self.search_state.display_offset_delta) as usize;
origin
}
+
+ /// Update the cursor blinking state.
+ fn update_cursor_blinking(&mut self) {
+ // Get config cursor style.
+ let mut cursor_style = self.config.cursor.style;
+ if self.terminal.mode().contains(TermMode::VI) {
+ cursor_style = self.config.cursor.vi_mode_style.unwrap_or(cursor_style);
+ };
+
+ // Check terminal cursor style.
+ let terminal_blinking = self.terminal.cursor_style().blinking;
+ let blinking = cursor_style.blinking_override().unwrap_or(terminal_blinking);
+
+ // Update cursor blinking state.
+ self.scheduler.unschedule(TimerId::BlinkCursor);
+ if blinking && self.terminal.is_focused {
+ self.scheduler.schedule(
+ GlutinEvent::UserEvent(Event::BlinkCursor),
+ Duration::from_millis(self.config.cursor.blink_interval()),
+ true,
+ TimerId::BlinkCursor,
+ )
+ } else {
+ *self.cursor_hidden = false;
+ self.terminal.dirty = true;
+ }
+ }
}
#[derive(Debug, Eq, PartialEq)]
@@ -804,6 +855,12 @@ impl<N: Notify + OnResize> Processor<N> {
{
let mut scheduler = Scheduler::new();
+ // Start the initial cursor blinking timer.
+ if self.config.cursor.style().blinking {
+ let event: Event = TerminalEvent::CursorBlinkingChange(true).into();
+ self.event_queue.push(event.into());
+ }
+
event_loop.run_return(|event, event_loop, control_flow| {
if self.config.ui_config.debug.print_events {
info!("glutin event: {:?}", event);
@@ -873,6 +930,7 @@ impl<N: Notify + OnResize> Processor<N> {
scheduler: &mut scheduler,
search_state: &mut self.search_state,
cli_options: &self.cli_options,
+ cursor_hidden: &mut self.display.cursor_hidden,
event_loop,
};
let mut processor = input::Processor::new(context, &self.display.highlighted_url);
@@ -953,6 +1011,10 @@ impl<N: Notify + OnResize> Processor<N> {
Event::SearchNext => processor.ctx.goto_match(None),
Event::ConfigReload(path) => Self::reload_config(&path, processor),
Event::Scroll(scroll) => processor.ctx.scroll(scroll),
+ Event::BlinkCursor => {
+ *processor.ctx.cursor_hidden ^= true;
+ processor.ctx.terminal.dirty = true;
+ },
Event::TerminalEvent(event) => match event {
TerminalEvent::Title(title) => {
let ui_config = &processor.ctx.config.ui_config;
@@ -983,6 +1045,9 @@ impl<N: Notify + OnResize> Processor<N> {
},
TerminalEvent::MouseCursorDirty => processor.reset_mouse_cursor(),
TerminalEvent::Exit => (),
+ TerminalEvent::CursorBlinkingChange(_) => {
+ processor.ctx.update_cursor_blinking();
+ },
},
},
GlutinEvent::RedrawRequested(_) => processor.ctx.terminal.dirty = true,
@@ -1033,6 +1098,7 @@ impl<N: Notify + OnResize> Processor<N> {
processor.ctx.window.set_mouse_visible(true);
}
+ processor.ctx.update_cursor_blinking();
processor.on_focus_change(is_focused);
}
},
@@ -1111,7 +1177,7 @@ impl<N: Notify + OnResize> Processor<N> {
processor.ctx.terminal.update_config(&config);
- // Reload cursor if we've changed its thickness.
+ // Reload cursor if its thickness has changed.
if (processor.ctx.config.cursor.thickness() - config.cursor.thickness()).abs()
> std::f64::EPSILON
{
@@ -1154,6 +1220,9 @@ impl<N: Notify + OnResize> Processor<N> {
*processor.ctx.config = config;
+ // Update cursor blinking.
+ processor.ctx.update_cursor_blinking();
+
processor.ctx.terminal.dirty = true;
}
diff --git a/alacritty/src/input.rs b/alacritty/src/input.rs
index 348db610..ce89625b 100644
--- a/alacritty/src/input.rs
+++ b/alacritty/src/input.rs
@@ -103,6 +103,7 @@ pub trait ActionContext<T: EventListener> {
fn advance_search_origin(&mut self, direction: Direction);
fn search_direction(&self) -> Direction;
fn search_active(&self) -> bool;
+ fn on_typing_start(&mut self);
}
trait Execute<T: EventListener> {
@@ -138,9 +139,7 @@ impl<T: EventListener> Execute<T> for Action {
fn execute<A: ActionContext<T>>(&self, ctx: &mut A) {
match *self {
Action::Esc(ref s) => {
- if ctx.config().ui_config.mouse.hide_when_typing {
- ctx.window_mut().set_mouse_visible(false);
- }
+ ctx.on_typing_start();
ctx.clear_selection();
ctx.scroll(Scroll::Bottom);
@@ -167,10 +166,7 @@ impl<T: EventListener> Execute<T> for Action {
Action::ClearSelection => ctx.clear_selection(),
Action::ToggleViMode => ctx.terminal_mut().toggle_vi_mode(),
Action::ViMotion(motion) => {
- if ctx.config().ui_config.mouse.hide_when_typing {
- ctx.window_mut().set_mouse_visible(false);
- }
-
+ ctx.on_typing_start();
ctx.terminal_mut().vi_motion(motion)
},
Action::ViAction(ViAction::ToggleNormalSelection) => {
@@ -870,6 +866,13 @@ impl<'a, T: EventListener, A: ActionContext<T>> Processor<'a, T, A> {
self.ctx.window_mut().set_mouse_cursor(mouse_state.into());
}
+ /// Reset mouse cursor based on modifier and terminal state.
+ #[inline]
+ pub fn reset_mouse_cursor(&mut self) {
+ let mouse_state = self.mouse_state();
+ self.ctx.window_mut().set_mouse_cursor(mouse_state.into());
+ }
+
/// Process a received character.
pub fn received_char(&mut self, c: char) {
let suppress_chars = *self.ctx.suppress_chars();
@@ -890,9 +893,7 @@ impl<'a, T: EventListener, A: ActionContext<T>> Processor<'a, T, A> {
return;
}
- if self.ctx.config().ui_config.mouse.hide_when_typing {
- self.ctx.window_mut().set_mouse_visible(false);
- }
+ self.ctx.on_typing_start();
self.ctx.scroll(Scroll::Bottom);
self.ctx.clear_selection();
@@ -917,13 +918,6 @@ impl<'a, T: EventListener, A: ActionContext<T>> Processor<'a, T, A> {
*self.ctx.received_count() += 1;
}
- /// Reset mouse cursor based on modifier and terminal state.
- #[inline]
- pub fn reset_mouse_cursor(&mut self) {
- let mouse_state = self.mouse_state();
- self.ctx.window_mut().set_mouse_cursor(mouse_state.into());
- }
-
/// Attempt to find a binding and execute its action.
///
/// The provided mode, mods, and key must match what is allowed by a binding
@@ -1270,6 +1264,10 @@ mod tests {
fn scheduler_mut(&mut self) -> &mut Scheduler {
unimplemented!();
}
+
+ fn on_typing_start(&mut self) {
+ unimplemented!();
+ }
}
macro_rules! test_clickstate {
diff --git a/alacritty/src/renderer/mod.rs b/alacritty/src/renderer/mod.rs
index f63f92fd..af1f3c3e 100644
--- a/alacritty/src/renderer/mod.rs
+++ b/alacritty/src/renderer/mod.rs
@@ -1010,7 +1010,7 @@ impl<'a> RenderApi<'a> {
let metrics = glyph_cache.metrics;
let glyph = glyph_cache.cursor_cache.entry(cursor_key).or_insert_with(|| {
self.load_glyph(&cursor::get_cursor_glyph(
- cursor_key.style,
+ cursor_key.shape,
metrics,
self.config.font.offset.x,
self.config.font.offset.y,
diff --git a/alacritty/src/scheduler.rs b/alacritty/src/scheduler.rs
index db6180ca..5e454141 100644
--- a/alacritty/src/scheduler.rs
+++ b/alacritty/src/scheduler.rs
@@ -14,6 +14,7 @@ type Event = GlutinEvent<'static, AlacrittyEvent>;
pub enum TimerId {
SelectionScrolling,
DelayedSearch,
+ BlinkCursor,
}
/// Event scheduled to be emitted at a specific time.
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]
diff --git a/docs/escape_support.md b/docs/escape_support.md
index f1a0337c..d7c36d33 100644
--- a/docs/escape_support.md
+++ b/docs/escape_support.md
@@ -68,7 +68,7 @@ brevity.
| `CSI m` | PARTIAL | Only singular straight underlines are supported |
| `CSI n` | IMPLEMENTED | |
| `CSI P` | IMPLEMENTED | |
-| `CSI SP q` | PARTIAL | No blinking support |
+| `CSI SP q` | IMPLEMENTED | |
| `CSI r` | IMPLEMENTED | |
| `CSI S` | IMPLEMENTED | |
| `CSI s` | IMPLEMENTED | |