mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-08-01 05:09:01 +00:00
This commit represents the majority of the work necessary to upgrade Ghostty to use Zig 0.16.0. Key parts: * In addition to its previous responsibilities, the global state now houses state for global I/O implementations and the process environment. It is now also utilized in the main application along with the C library. Where necessary, global state is isolated from key parts of the implementation (e.g., in libghostty subsystems), and it's expected that this list will grow. * We currently manage our own C translation layer where necessary. In these cases, cImport has been removed in favor of the new external translate-c package. Due to fixes that have needed be made to properly translate the dependencies that were swapped out, as mentioned, we have had to backport fixes from the current translate-c package (and the upstream Arocc dependency). We will host this ourselves until Zig 0.17.0 is released with these fixes. * Where necessary (only a small number of cases), some stdlib code from 0.15.2 (and even from 0.17.0) has been taken, adopted, and vendored in lib/compat. Co-authored-by: Leah Amelia Chen <hi@pluie.me>
159 lines
6.3 KiB
Zig
159 lines
6.3 KiB
Zig
const 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;
|
|
const io = testing.io;
|
|
var term: terminal.Terminal = try .init(io, 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;
|
|
const io = testing.io;
|
|
var term = try terminal.Terminal.init(io, 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;
|
|
const io = testing.io;
|
|
var term = try terminal.Terminal.init(io, 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;
|
|
const io = testing.io;
|
|
var term = try terminal.Terminal.init(io, 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);
|
|
}
|