Something went wrong. Try again.
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155const std = @import("std");const terminal = @import("../terminal/main.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, }; }};
pub const StyleOptions = struct { preedit: bool = false, focused: bool = false, blink_visible: bool = false,};
/// 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: *const terminal.RenderState, opts: StyleOptions,) ?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 must be visible in the viewport to be rendered. if (state.cursor.viewport == null) 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 (opts.preedit) return .block;
// If we're at a password input its always a lock. if (state.cursor.password_input) return .lock;
// If the cursor is explicitly not visible by terminal mode, we don't render. if (!state.cursor.visible) return null;
// If we're not focused, our cursor is always visible so that // we can show the hollow box. if (!opts.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.cursor.blinking and !opts.blink_visible) return null;
// Otherwise, we use whatever style the terminal wants. return .fromTerminal(state.cursor.visual_style);}
test "cursor: default uses configured style" { const testing = std.testing; const alloc = testing.allocator; var term: terminal.Terminal = try .init(alloc, .{ .cols = 10, .rows = 10 }); defer term.deinit(alloc);
term.screens.active.cursor.cursor_style = .bar; term.modes.set(.cursor_blinking, true);
var state: terminal.RenderState = .empty; defer state.deinit(alloc); try state.update(alloc, &term);
try testing.expect(style(&state, .{ .preedit = false, .focused = true, .blink_visible = true }) == .bar); try testing.expect(style(&state, .{ .preedit = false, .focused = false, .blink_visible = true }) == .block_hollow); try testing.expect(style(&state, .{ .preedit = false, .focused = false, .blink_visible = false }) == .block_hollow); try testing.expect(style(&state, .{ .preedit = false, .focused = true, .blink_visible = 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.screens.active.cursor.cursor_style = .bar; term.modes.set(.cursor_blinking, false);
var state: terminal.RenderState = .empty; defer state.deinit(alloc); try state.update(alloc, &term);
try testing.expect(style(&state, .{ .focused = true, .blink_visible = true }) == .bar); try testing.expect(style(&state, .{ .focused = true, .blink_visible = false }) == .bar); try testing.expect(style(&state, .{ .focused = false, .blink_visible = true }) == .block_hollow); try testing.expect(style(&state, .{ .focused = false, .blink_visible = 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.screens.active.cursor.cursor_style = .bar; term.modes.set(.cursor_visible, false); term.modes.set(.cursor_blinking, false);
var state: terminal.RenderState = .empty; defer state.deinit(alloc); try state.update(alloc, &term);
try testing.expect(style(&state, .{ .focused = true, .blink_visible = true }) == null); try testing.expect(style(&state, .{ .focused = true, .blink_visible = false }) == null); try testing.expect(style(&state, .{ .focused = false, .blink_visible = true }) == null); try testing.expect(style(&state, .{ .focused = false, .blink_visible = 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: terminal.RenderState = .empty; defer state.deinit(alloc); try state.update(alloc, &term);
// In any bool state try testing.expect(style(&state, .{ .preedit = true, .focused = false, .blink_visible = false }) == .block); try testing.expect(style(&state, .{ .preedit = true, .focused = true, .blink_visible = false }) == .block); try testing.expect(style(&state, .{ .preedit = true, .focused = true, .blink_visible = true }) == .block); try testing.expect(style(&state, .{ .preedit = true, .focused = false, .blink_visible = true }) == .block);
// If we're scrolled though, then we don't show the cursor. for (0..100) |_| try term.index(); term.scrollViewport(.{ .top = {} }); try state.update(alloc, &term);
// In any bool state try testing.expect(style(&state, .{ .preedit = true, .focused = false, .blink_visible = false }) == null); try testing.expect(style(&state, .{ .preedit = true, .focused = true, .blink_visible = false }) == null); try testing.expect(style(&state, .{ .preedit = true, .focused = true, .blink_visible = true }) == null); try testing.expect(style(&state, .{ .preedit = true, .focused = false, .blink_visible = true }) == null);}