1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
|
const std = @import("std");
const terminal = @import("../terminal/main.zig");
const State = @import("State.zig");
/// Available cursor styles for drawing that renderers must support.
/// This is a superset of terminal cursor styles since the renderer supports
/// some additional cursor states such as the hollow block.
pub const Style = enum {
// Typical cursor input styles
block,
block_hollow,
bar,
underline,
// Special cursor styles
lock,
/// Create a cursor style from the terminal style request.
pub fn fromTerminal(term: terminal.CursorStyle) ?Style {
return switch (term) {
.bar => .bar,
.block => .block,
.block_hollow => .block_hollow,
.underline => .underline,
};
}
};
/// Returns the cursor style to use for the current render state or null
/// if a cursor should not be rendered at all.
pub fn style(
state: *State,
focused: bool,
blink_visible: bool,
) ?Style {
// Note the order of conditionals below is important. It represents
// a priority system of how we determine what state overrides cursor
// visibility and style.
// The cursor is only at the bottom of the viewport. If we aren't
// at the bottom, we never render the cursor. The cursor x/y is by
// viewport so if we are above the viewport, we'll end up rendering
// the cursor in some random part of the screen.
if (!state.terminal.screen.viewportIsBottom()) return null;
// If we are in preedit, then we always show the block cursor. We do
// this even if the cursor is explicitly not visible because it shows
// an important editing state to the user.
if (state.preedit != null) return .block;
// If the cursor is explicitly not visible by terminal mode, we don't render.
if (!state.terminal.modes.get(.cursor_visible)) return null;
// If we're not focused, our cursor is always visible so that
// we can show the hollow box.
if (!focused) return .block_hollow;
// If the cursor is blinking and our blink state is not visible,
// then we don't show the cursor.
if (state.terminal.modes.get(.cursor_blinking) and !blink_visible) {
return null;
}
// Otherwise, we use whatever style the terminal wants.
return .fromTerminal(state.terminal.screen.cursor.cursor_style);
}
test "cursor: default uses configured style" {
const testing = std.testing;
const alloc = testing.allocator;
var term = try terminal.Terminal.init(alloc, .{ .cols = 10, .rows = 10 });
defer term.deinit(alloc);
term.screen.cursor.cursor_style = .bar;
term.modes.set(.cursor_blinking, true);
var state: State = .{
.mutex = undefined,
.terminal = &term,
.preedit = null,
};
try testing.expect(style(&state, true, true) == .bar);
try testing.expect(style(&state, false, true) == .block_hollow);
try testing.expect(style(&state, false, false) == .block_hollow);
try testing.expect(style(&state, true, false) == null);
}
test "cursor: blinking disabled" {
const testing = std.testing;
const alloc = testing.allocator;
var term = try terminal.Terminal.init(alloc, .{ .cols = 10, .rows = 10 });
defer term.deinit(alloc);
term.screen.cursor.cursor_style = .bar;
term.modes.set(.cursor_blinking, false);
var state: State = .{
.mutex = undefined,
.terminal = &term,
.preedit = null,
};
try testing.expect(style(&state, true, true) == .bar);
try testing.expect(style(&state, true, false) == .bar);
try testing.expect(style(&state, false, true) == .block_hollow);
try testing.expect(style(&state, false, false) == .block_hollow);
}
test "cursor: explicitly not visible" {
const testing = std.testing;
const alloc = testing.allocator;
var term = try terminal.Terminal.init(alloc, .{ .cols = 10, .rows = 10 });
defer term.deinit(alloc);
term.screen.cursor.cursor_style = .bar;
term.modes.set(.cursor_visible, false);
term.modes.set(.cursor_blinking, false);
var state: State = .{
.mutex = undefined,
.terminal = &term,
.preedit = null,
};
try testing.expect(style(&state, true, true) == null);
try testing.expect(style(&state, true, false) == null);
try testing.expect(style(&state, false, true) == null);
try testing.expect(style(&state, false, false) == null);
}
test "cursor: always block with preedit" {
const testing = std.testing;
const alloc = testing.allocator;
var term = try terminal.Terminal.init(alloc, .{ .cols = 10, .rows = 10 });
defer term.deinit(alloc);
var state: State = .{
.mutex = undefined,
.terminal = &term,
.preedit = .{},
};
// In any bool state
try testing.expect(style(&state, false, false) == .block);
try testing.expect(style(&state, true, false) == .block);
try testing.expect(style(&state, true, true) == .block);
try testing.expect(style(&state, false, true) == .block);
// If we're scrolled though, then we don't show the cursor.
for (0..100) |_| try term.index();
try term.scrollViewport(.{ .top = {} });
// In any bool state
try testing.expect(style(&state, false, false) == null);
try testing.expect(style(&state, true, false) == null);
try testing.expect(style(&state, true, true) == null);
try testing.expect(style(&state, false, true) == null);
}
|