Skip to content

ansi: Add support blinking text #5217

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions src/config/Config.zig
Original file line number Diff line number Diff line change
Expand Up @@ -478,6 +478,41 @@ foreground: Color = .{ .r = 0xFF, .g = 0xFF, .b = 0xFF },
/// This value does not apply to Emoji or images.
@"minimum-contrast": f64 = 1,

/// The amount of time it takes for blinking text and cursors to toggle between
/// being invisible and visible.
/// Any cell on the screen could be set to blink by setting SGR attribute 5,
/// while cursor blinking is controlled by the `cursor-style-blink` setting.
///
/// The interval is specified as a series of numbers followed by time units.
/// Whitespace is allowed between numbers and units. Each number and unit will
/// be added together to form the total interval.
///
/// Blinking is disabled when the interval is set to zero.
///
/// The allowed time units are as follows:
///
/// * `y` - 365 SI days, or 8760 hours, or 31536000 seconds. No adjustments
/// are made for leap years or leap seconds.
/// * `d` - one SI day, or 86400 seconds.
/// * `h` - one hour, or 3600 seconds.
/// * `m` - one minute, or 60 seconds.
/// * `s` - one second.
/// * `ms` - one millisecond, or 0.001 second.
/// * `us` or `µs` - one microsecond, or 0.000001 second.
/// * `ns` - one nanosecond, or 0.000000001 second.
///
/// Examples:
/// * `1h30m`
/// * `45s`
///
/// Units can be repeated and will be added together. This means that
/// `1h1h` is equivalent to `2h`. This is confusing and should be avoided.
/// A future update may disallow this.
///
/// The maximum value is `584y 49w 23h 34m 33s 709ms 551µs 615ns`. Any
/// value larger than this will be clamped to the maximum value.
@"blink-interval": Duration = .{ .duration = 600 * std.time.ns_per_ms },

/// Color palette for the 256 color form that many terminal applications use.
/// The syntax of this configuration is `N=COLOR` where `N` is 0 to 255 (for
/// the 256 colors in the terminal color table) and `COLOR` is a typical RGB
Expand Down
35 changes: 35 additions & 0 deletions src/font/shaper/harfbuzz.zig
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ pub const Shaper = struct {
grid: *SharedGrid,
screen: *const terminal.Screen,
row: terminal.Pin,
text_blink_visible: bool,
selection: ?terminal.Selection,
cursor_x: ?usize,
) font.shape.RunIterator {
Expand All @@ -100,6 +101,7 @@ pub const Shaper = struct {
.grid = grid,
.screen = screen,
.row = row,
.text_blink_visible = text_blink_visible,
.selection = selection,
.cursor_x = cursor_x,
};
Expand Down Expand Up @@ -229,6 +231,7 @@ test "run iterator" {
testdata.grid,
&screen,
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
true,
null,
null,
);
Expand All @@ -248,6 +251,7 @@ test "run iterator" {
testdata.grid,
&screen,
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
true,
null,
null,
);
Expand All @@ -268,6 +272,7 @@ test "run iterator" {
testdata.grid,
&screen,
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
true,
null,
null,
);
Expand Down Expand Up @@ -320,6 +325,7 @@ test "run iterator: empty cells with background set" {
testdata.grid,
&screen,
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
true,
null,
null,
);
Expand Down Expand Up @@ -357,6 +363,7 @@ test "shape" {
testdata.grid,
&screen,
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
true,
null,
null,
);
Expand Down Expand Up @@ -386,6 +393,7 @@ test "shape inconsolata ligs" {
testdata.grid,
&screen,
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
true,
null,
null,
);
Expand All @@ -411,6 +419,7 @@ test "shape inconsolata ligs" {
testdata.grid,
&screen,
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
true,
null,
null,
);
Expand Down Expand Up @@ -444,6 +453,7 @@ test "shape monaspace ligs" {
testdata.grid,
&screen,
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
true,
null,
null,
);
Expand Down Expand Up @@ -480,6 +490,7 @@ test "shape arabic forced LTR" {
testdata.grid,
&screen,
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
true,
null,
null,
);
Expand Down Expand Up @@ -517,6 +528,7 @@ test "shape emoji width" {
testdata.grid,
&screen,
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
true,
null,
null,
);
Expand Down Expand Up @@ -559,6 +571,7 @@ test "shape emoji width long" {
testdata.grid,
&screen,
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
true,
null,
null,
);
Expand Down Expand Up @@ -597,6 +610,7 @@ test "shape variation selector VS15" {
testdata.grid,
&screen,
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
true,
null,
null,
);
Expand Down Expand Up @@ -634,6 +648,7 @@ test "shape variation selector VS16" {
testdata.grid,
&screen,
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
true,
null,
null,
);
Expand Down Expand Up @@ -668,6 +683,7 @@ test "shape with empty cells in between" {
testdata.grid,
&screen,
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
true,
null,
null,
);
Expand Down Expand Up @@ -706,6 +722,7 @@ test "shape Chinese characters" {
testdata.grid,
&screen,
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
true,
null,
null,
);
Expand Down Expand Up @@ -746,6 +763,7 @@ test "shape box glyphs" {
testdata.grid,
&screen,
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
true,
null,
null,
);
Expand Down Expand Up @@ -783,6 +801,7 @@ test "shape selection boundary" {
testdata.grid,
&screen,
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
true,
terminal.Selection.init(
screen.pages.pin(.{ .active = .{ .x = 0, .y = 0 } }).?,
screen.pages.pin(.{ .active = .{ .x = screen.pages.cols - 1, .y = 0 } }).?,
Expand All @@ -806,6 +825,7 @@ test "shape selection boundary" {
testdata.grid,
&screen,
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
true,
terminal.Selection.init(
screen.pages.pin(.{ .active = .{ .x = 2, .y = 0 } }).?,
screen.pages.pin(.{ .active = .{ .x = screen.pages.cols - 1, .y = 0 } }).?,
Expand All @@ -829,6 +849,7 @@ test "shape selection boundary" {
testdata.grid,
&screen,
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
true,
terminal.Selection.init(
screen.pages.pin(.{ .active = .{ .x = 0, .y = 0 } }).?,
screen.pages.pin(.{ .active = .{ .x = 3, .y = 0 } }).?,
Expand All @@ -852,6 +873,7 @@ test "shape selection boundary" {
testdata.grid,
&screen,
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
true,
terminal.Selection.init(
screen.pages.pin(.{ .active = .{ .x = 1, .y = 0 } }).?,
screen.pages.pin(.{ .active = .{ .x = 3, .y = 0 } }).?,
Expand All @@ -875,6 +897,7 @@ test "shape selection boundary" {
testdata.grid,
&screen,
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
true,
terminal.Selection.init(
screen.pages.pin(.{ .active = .{ .x = 1, .y = 0 } }).?,
screen.pages.pin(.{ .active = .{ .x = 1, .y = 0 } }).?,
Expand Down Expand Up @@ -911,6 +934,7 @@ test "shape cursor boundary" {
testdata.grid,
&screen,
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
true,
null,
null,
);
Expand All @@ -930,6 +954,7 @@ test "shape cursor boundary" {
testdata.grid,
&screen,
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
true,
null,
0,
);
Expand All @@ -949,6 +974,7 @@ test "shape cursor boundary" {
testdata.grid,
&screen,
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
true,
null,
1,
);
Expand All @@ -968,6 +994,7 @@ test "shape cursor boundary" {
testdata.grid,
&screen,
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
true,
null,
9,
);
Expand Down Expand Up @@ -1000,6 +1027,7 @@ test "shape cursor boundary and colored emoji" {
testdata.grid,
&screen,
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
true,
null,
null,
);
Expand All @@ -1019,6 +1047,7 @@ test "shape cursor boundary and colored emoji" {
testdata.grid,
&screen,
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
true,
null,
0,
);
Expand All @@ -1036,6 +1065,7 @@ test "shape cursor boundary and colored emoji" {
testdata.grid,
&screen,
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
true,
null,
1,
);
Expand Down Expand Up @@ -1066,6 +1096,7 @@ test "shape cell attribute change" {
testdata.grid,
&screen,
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
true,
null,
null,
);
Expand All @@ -1090,6 +1121,7 @@ test "shape cell attribute change" {
testdata.grid,
&screen,
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
true,
null,
null,
);
Expand All @@ -1115,6 +1147,7 @@ test "shape cell attribute change" {
testdata.grid,
&screen,
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
true,
null,
null,
);
Expand All @@ -1140,6 +1173,7 @@ test "shape cell attribute change" {
testdata.grid,
&screen,
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
true,
null,
null,
);
Expand All @@ -1164,6 +1198,7 @@ test "shape cell attribute change" {
testdata.grid,
&screen,
screen.pages.pin(.{ .screen = .{ .y = 0 } }).?,
true,
null,
null,
);
Expand Down
2 changes: 2 additions & 0 deletions src/font/shaper/noop.zig
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ pub const Shaper = struct {
grid: *SharedGrid,
screen: *const terminal.Screen,
row: terminal.Pin,
text_blink_visible: bool,
selection: ?terminal.Selection,
cursor_x: ?usize,
) font.shape.RunIterator {
Expand All @@ -81,6 +82,7 @@ pub const Shaper = struct {
.grid = grid,
.screen = screen,
.row = row,
.text_blink_visible = text_blink_visible,
.selection = selection,
.cursor_x = cursor_x,
};
Expand Down
4 changes: 3 additions & 1 deletion src/font/shaper/run.zig
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ pub const RunIterator = struct {
grid: *font.SharedGrid,
screen: *const terminal.Screen,
row: terminal.Pin,
text_blink_visible: bool,
selection: ?terminal.Selection = null,
cursor_x: ?usize = null,
i: usize = 0,
Expand All @@ -58,7 +59,8 @@ pub const RunIterator = struct {
// Invisible cells don't have any glyphs rendered,
// so we explicitly skip them in the shaping process.
while (self.i < max and
self.row.style(&cells[self.i]).flags.invisible)
(self.row.style(&cells[self.i]).flags.invisible or
(self.row.style(&cells[self.i]).flags.blink and !self.text_blink_visible)))
{
self.i += 1;
}
Expand Down
4 changes: 3 additions & 1 deletion src/font/shaper/web_canvas.zig
Original file line number Diff line number Diff line change
Expand Up @@ -63,13 +63,15 @@ pub const Shaper = struct {
self: *Shaper,
group: *font.GroupCache,
row: terminal.Screen.Row,
text_blink_visible: bool,
selection: ?terminal.Selection,
cursor_x: ?usize,
) font.shape.RunIterator {
return .{
.hooks = .{ .shaper = self },
.group = group,
.row = row,
.text_blink_visible = text_blink_visible,
.selection = selection,
.cursor_x = cursor_x,
};
Expand Down Expand Up @@ -295,7 +297,7 @@ pub const Wasm = struct {
while (rowIter.next()) |row| {
defer y += 1;

var iter = self.runIterator(group, row, null, null);
var iter = self.runIterator(group, row, true, null, null);
while (try iter.next(alloc)) |run| {
const cells = try self.shape(run);
log.info("y={} run={d} shape={any} idx={}", .{
Expand Down
Loading