mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-15 00:06:09 +03:00
move stream handling into the Window
This commit is contained in:
@ -15,11 +15,11 @@ const gl = @import("opengl.zig");
|
|||||||
const libuv = @import("libuv/main.zig");
|
const libuv = @import("libuv/main.zig");
|
||||||
const Pty = @import("Pty.zig");
|
const Pty = @import("Pty.zig");
|
||||||
const Command = @import("Command.zig");
|
const Command = @import("Command.zig");
|
||||||
const Terminal = @import("terminal/Terminal.zig");
|
|
||||||
const SegmentedPool = @import("segmented_pool.zig").SegmentedPool;
|
const SegmentedPool = @import("segmented_pool.zig").SegmentedPool;
|
||||||
const frame = @import("tracy/tracy.zig").frame;
|
const frame = @import("tracy/tracy.zig").frame;
|
||||||
const trace = @import("tracy/tracy.zig").trace;
|
const trace = @import("tracy/tracy.zig").trace;
|
||||||
const max_timer = @import("max_timer.zig");
|
const max_timer = @import("max_timer.zig");
|
||||||
|
const terminal = @import("terminal/main.zig");
|
||||||
|
|
||||||
const RenderTimer = max_timer.MaxTimer(renderTimerCallback);
|
const RenderTimer = max_timer.MaxTimer(renderTimerCallback);
|
||||||
|
|
||||||
@ -48,7 +48,10 @@ command: Command,
|
|||||||
/// that manages input, grid updating, etc. and is renderer-agnostic. It
|
/// that manages input, grid updating, etc. and is renderer-agnostic. It
|
||||||
/// just stores internal state about a grid. This is connected back to
|
/// just stores internal state about a grid. This is connected back to
|
||||||
/// a renderer.
|
/// a renderer.
|
||||||
terminal: Terminal,
|
terminal: terminal.Terminal,
|
||||||
|
|
||||||
|
/// The stream parser.
|
||||||
|
terminal_stream: terminal.Stream(*Window),
|
||||||
|
|
||||||
/// Timer that blinks the cursor.
|
/// Timer that blinks the cursor.
|
||||||
cursor_timer: libuv.Timer,
|
cursor_timer: libuv.Timer,
|
||||||
@ -169,7 +172,7 @@ pub fn create(alloc: Allocator, loop: libuv.Loop) !*Window {
|
|||||||
try stream.readStart(ttyReadAlloc, ttyRead);
|
try stream.readStart(ttyReadAlloc, ttyRead);
|
||||||
|
|
||||||
// Create our terminal
|
// Create our terminal
|
||||||
var term = try Terminal.init(alloc, grid.size.columns, grid.size.rows);
|
var term = try terminal.Terminal.init(alloc, grid.size.columns, grid.size.rows);
|
||||||
errdefer term.deinit(alloc);
|
errdefer term.deinit(alloc);
|
||||||
|
|
||||||
// Setup a timer for blinking the cursor
|
// Setup a timer for blinking the cursor
|
||||||
@ -186,6 +189,7 @@ pub fn create(alloc: Allocator, loop: libuv.Loop) !*Window {
|
|||||||
.pty = pty,
|
.pty = pty,
|
||||||
.command = cmd,
|
.command = cmd,
|
||||||
.terminal = term,
|
.terminal = term,
|
||||||
|
.terminal_stream = .{ .handler = self },
|
||||||
.cursor_timer = timer,
|
.cursor_timer = timer,
|
||||||
.render_timer = try RenderTimer.init(loop, self, 16, 96),
|
.render_timer = try RenderTimer.init(loop, self, 16, 96),
|
||||||
.pty_stream = stream,
|
.pty_stream = stream,
|
||||||
@ -429,10 +433,6 @@ fn ttyRead(t: *libuv.Tty, n: isize, buf: []const u8) void {
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Add this character to the terminal buffer.
|
|
||||||
win.terminal.append(win.alloc, buf[0..@intCast(usize, n)]) catch |err|
|
|
||||||
log.err("error writing terminal data: {}", .{err});
|
|
||||||
|
|
||||||
// Whenever a character is typed, we ensure the cursor is visible
|
// Whenever a character is typed, we ensure the cursor is visible
|
||||||
// and we restart the cursor timer.
|
// and we restart the cursor timer.
|
||||||
win.grid.cursor_visible = true;
|
win.grid.cursor_visible = true;
|
||||||
@ -442,6 +442,10 @@ fn ttyRead(t: *libuv.Tty, n: isize, buf: []const u8) void {
|
|||||||
|
|
||||||
// Schedule a render
|
// Schedule a render
|
||||||
win.render_timer.schedule() catch unreachable;
|
win.render_timer.schedule() catch unreachable;
|
||||||
|
|
||||||
|
// Process the terminal data
|
||||||
|
win.terminal_stream.nextSlice(buf[0..@intCast(usize, n)]) catch |err|
|
||||||
|
log.err("error processing terminal data: {}", .{err});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ttyWrite(req: *libuv.WriteReq, status: i32) void {
|
fn ttyWrite(req: *libuv.WriteReq, status: i32) void {
|
||||||
@ -488,3 +492,67 @@ fn renderTimerCallback(t: *libuv.Timer) void {
|
|||||||
// Record our run
|
// Record our run
|
||||||
win.render_timer.tick();
|
win.render_timer.tick();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//-------------------------------------------------------------------
|
||||||
|
// Stream Callbacks
|
||||||
|
|
||||||
|
pub fn print(self: *Window, c: u8) !void {
|
||||||
|
try self.terminal.print(self.alloc, c);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn bell(self: Window) !void {
|
||||||
|
_ = self;
|
||||||
|
log.info("BELL", .{});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn backspace(self: *Window) !void {
|
||||||
|
self.terminal.backspace();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn horizontalTab(self: *Window) !void {
|
||||||
|
try self.terminal.horizontalTab(self.alloc);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn linefeed(self: *Window) !void {
|
||||||
|
self.terminal.linefeed(self.alloc);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn carriageReturn(self: *Window) !void {
|
||||||
|
self.terminal.carriageReturn();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setCursorRight(self: *Window, amount: u16) !void {
|
||||||
|
self.terminal.cursorRight(amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setCursorCol(self: *Window, col: u16) !void {
|
||||||
|
try self.terminal.setCursorPos(self.terminal.cursor.y + 1, col);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setCursorRow(self: *Window, row: u16) !void {
|
||||||
|
try self.terminal.setCursorPos(row, self.terminal.cursor.x + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setCursorPos(self: *Window, row: u16, col: u16) !void {
|
||||||
|
try self.terminal.setCursorPos(row, col);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn eraseDisplay(self: *Window, mode: terminal.EraseDisplay) !void {
|
||||||
|
try self.terminal.eraseDisplay(self.alloc, mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn eraseLine(self: *Window, mode: terminal.EraseLine) !void {
|
||||||
|
try self.terminal.eraseLine(mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deleteChars(self: *Window, count: usize) !void {
|
||||||
|
try self.terminal.deleteChars(count);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn eraseChars(self: *Window, count: usize) !void {
|
||||||
|
try self.terminal.eraseChars(count);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reverseIndex(self: *Window) !void {
|
||||||
|
try self.terminal.reverseIndex(self.alloc);
|
||||||
|
}
|
||||||
|
@ -111,192 +111,7 @@ pub fn plainString(self: Terminal, alloc: Allocator) ![]const u8 {
|
|||||||
return buffer[0..i];
|
return buffer[0..i];
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Append a string of characters. See appendChar.
|
pub fn print(self: *Terminal, alloc: Allocator, c: u8) !void {
|
||||||
pub fn append(self: *Terminal, alloc: Allocator, str: []const u8) !void {
|
|
||||||
const tracy = trace(@src());
|
|
||||||
defer tracy.end();
|
|
||||||
|
|
||||||
for (str) |c| {
|
|
||||||
try self.appendChar(alloc, c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Append a single character to the terminal.
|
|
||||||
///
|
|
||||||
/// This may allocate if necessary to store the character in the grid.
|
|
||||||
pub fn appendChar(self: *Terminal, alloc: Allocator, c: u8) !void {
|
|
||||||
const tracy = trace(@src());
|
|
||||||
defer tracy.end();
|
|
||||||
|
|
||||||
//log.debug("char: {}", .{c});
|
|
||||||
const actions = self.parser.next(c);
|
|
||||||
for (actions) |action_opt| {
|
|
||||||
//if (action_opt) |action| log.info("action: {}", .{action});
|
|
||||||
switch (action_opt orelse continue) {
|
|
||||||
.print => |p| try self.print(alloc, p),
|
|
||||||
.execute => |code| try self.execute(alloc, code),
|
|
||||||
.csi_dispatch => |csi| try self.csiDispatch(alloc, csi),
|
|
||||||
.esc_dispatch => |esc| try self.escDispatch(alloc, esc),
|
|
||||||
.osc_dispatch => |cmd| log.warn("unhandled OSC: {}", .{cmd}),
|
|
||||||
.dcs_hook => |dcs| log.warn("unhandled DCS hook: {}", .{dcs}),
|
|
||||||
.dcs_put => |code| log.warn("unhandled DCS put: {}", .{code}),
|
|
||||||
.dcs_unhook => log.warn("unhandled DCS unhook", .{}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn csiDispatch(
|
|
||||||
self: *Terminal,
|
|
||||||
alloc: Allocator,
|
|
||||||
action: Parser.Action.CSI,
|
|
||||||
) !void {
|
|
||||||
switch (action.final) {
|
|
||||||
// CUF - Cursor Right
|
|
||||||
'C' => self.cursorRight(switch (action.params.len) {
|
|
||||||
0 => 1,
|
|
||||||
1 => action.params[0],
|
|
||||||
else => {
|
|
||||||
log.warn("invalid cursor right command: {}", .{action});
|
|
||||||
return;
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
|
|
||||||
// HPA - Cursor Horizontal Position Absolute (Alias, see '`')
|
|
||||||
'G' => if (action.params.len == 0) {
|
|
||||||
try self.setCursorPos(self.cursor.y + 1, 1);
|
|
||||||
} else {
|
|
||||||
try self.setCursorPos(self.cursor.y + 1, action.params[0]);
|
|
||||||
},
|
|
||||||
|
|
||||||
// CUP - Set Cursor Position.
|
|
||||||
'H' => {
|
|
||||||
switch (action.params.len) {
|
|
||||||
0 => try self.setCursorPos(1, 1),
|
|
||||||
1 => try self.setCursorPos(action.params[0], 1),
|
|
||||||
2 => try self.setCursorPos(action.params[0], action.params[1]),
|
|
||||||
else => log.warn("unimplemented CSI: {}", .{action}),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// Erase Display
|
|
||||||
'J' => try self.eraseDisplay(alloc, switch (action.params.len) {
|
|
||||||
0 => .below,
|
|
||||||
1 => mode: {
|
|
||||||
// TODO: use meta to get enum max
|
|
||||||
if (action.params[0] > 3) {
|
|
||||||
log.warn("invalid erase display command: {}", .{action});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
break :mode @intToEnum(
|
|
||||||
csi.EraseDisplay,
|
|
||||||
action.params[0],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
else => {
|
|
||||||
log.warn("invalid erase display command: {}", .{action});
|
|
||||||
return;
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
|
|
||||||
// Erase Line
|
|
||||||
'K' => try self.eraseLine(switch (action.params.len) {
|
|
||||||
0 => .right,
|
|
||||||
1 => mode: {
|
|
||||||
// TODO: use meta to get enum max
|
|
||||||
if (action.params[0] > 3) {
|
|
||||||
log.warn("invalid erase line command: {}", .{action});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
break :mode @intToEnum(
|
|
||||||
csi.EraseLine,
|
|
||||||
action.params[0],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
else => {
|
|
||||||
log.warn("invalid erase line command: {}", .{action});
|
|
||||||
return;
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
|
|
||||||
// Delete Character (DCH)
|
|
||||||
'P' => try self.deleteChars(switch (action.params.len) {
|
|
||||||
0 => 1,
|
|
||||||
1 => action.params[0],
|
|
||||||
else => {
|
|
||||||
log.warn("invalid delete characters command: {}", .{action});
|
|
||||||
return;
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
|
|
||||||
// Erase Characters (ECH)
|
|
||||||
'X' => try self.eraseChars(switch (action.params.len) {
|
|
||||||
0 => 1,
|
|
||||||
1 => action.params[0],
|
|
||||||
else => {
|
|
||||||
log.warn("invalid erase characters command: {}", .{action});
|
|
||||||
return;
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
|
|
||||||
// HPA - Cursor Horizontal Position Absolute
|
|
||||||
'`' => if (action.params.len == 0) {
|
|
||||||
try self.setCursorPos(self.cursor.y + 1, 1);
|
|
||||||
} else {
|
|
||||||
try self.setCursorPos(self.cursor.y + 1, action.params[0]);
|
|
||||||
},
|
|
||||||
|
|
||||||
// VPA - Cursor Vertical Position Absolute
|
|
||||||
'd' => if (action.params.len == 0) {
|
|
||||||
try self.setCursorPos(1, self.cursor.x + 1);
|
|
||||||
} else {
|
|
||||||
try self.setCursorPos(action.params[0], self.cursor.x + 1);
|
|
||||||
},
|
|
||||||
|
|
||||||
// SGR - Select Graphic Rendition
|
|
||||||
'm' => if (action.params.len == 0) {
|
|
||||||
// No values defaults to code 0
|
|
||||||
try self.selectGraphicRendition(.default);
|
|
||||||
} else {
|
|
||||||
// Each parameter sets a separate aspect
|
|
||||||
for (action.params) |param| {
|
|
||||||
try self.selectGraphicRendition(@intToEnum(
|
|
||||||
ansi.RenditionAspect,
|
|
||||||
param,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
else => log.warn("unimplemented CSI: {}", .{action}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn escDispatch(
|
|
||||||
self: *Terminal,
|
|
||||||
alloc: Allocator,
|
|
||||||
action: Parser.Action.ESC,
|
|
||||||
) !void {
|
|
||||||
_ = alloc;
|
|
||||||
|
|
||||||
switch (action.final) {
|
|
||||||
// RI - Reverse Index
|
|
||||||
'M' => switch (action.intermediates.len) {
|
|
||||||
0 => try self.reverseIndex(alloc),
|
|
||||||
else => {
|
|
||||||
log.warn("invalid reverse index command: {}", .{action});
|
|
||||||
return;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
else => {
|
|
||||||
log.warn("unimplemented esc dispatch: {}", .{action});
|
|
||||||
return;
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn print(self: *Terminal, alloc: Allocator, c: u8) !void {
|
|
||||||
const tracy = trace(@src());
|
const tracy = trace(@src());
|
||||||
defer tracy.end();
|
defer tracy.end();
|
||||||
|
|
||||||
@ -315,26 +130,6 @@ fn print(self: *Terminal, alloc: Allocator, c: u8) !void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn execute(self: *Terminal, alloc: Allocator, c: u8) !void {
|
|
||||||
const tracy = trace(@src());
|
|
||||||
defer tracy.end();
|
|
||||||
|
|
||||||
switch (@intToEnum(ansi.C0, c)) {
|
|
||||||
.NUL => {},
|
|
||||||
.BEL => self.bell(),
|
|
||||||
.BS => self.backspace(),
|
|
||||||
.HT => try self.horizontalTab(alloc),
|
|
||||||
.LF => self.linefeed(alloc),
|
|
||||||
.CR => self.carriageReturn(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn bell(self: *Terminal) void {
|
|
||||||
// TODO: bell
|
|
||||||
_ = self;
|
|
||||||
log.info("bell", .{});
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn selectGraphicRendition(self: *Terminal, aspect: ansi.RenditionAspect) !void {
|
pub fn selectGraphicRendition(self: *Terminal, aspect: ansi.RenditionAspect) !void {
|
||||||
switch (aspect) {
|
switch (aspect) {
|
||||||
.default => self.cursor.bold = false,
|
.default => self.cursor.bold = false,
|
||||||
|
@ -1,12 +1,16 @@
|
|||||||
const stream = @import("stream.zig");
|
const stream = @import("stream.zig");
|
||||||
|
const csi = @import("csi.zig");
|
||||||
|
|
||||||
pub const Terminal = @import("Terminal.zig");
|
pub const Terminal = @import("Terminal.zig");
|
||||||
pub const Parser = @import("Parser.zig");
|
pub const Parser = @import("Parser.zig");
|
||||||
pub const Stream = stream.Stream;
|
pub const Stream = stream.Stream;
|
||||||
|
pub const EraseDisplay = csi.EraseDisplay;
|
||||||
|
pub const EraseLine = csi.EraseLine;
|
||||||
|
|
||||||
// Not exported because they're just used for tests.
|
// Not exported because they're just used for tests.
|
||||||
|
|
||||||
test {
|
test {
|
||||||
|
_ = csi;
|
||||||
_ = stream;
|
_ = stream;
|
||||||
_ = Parser;
|
_ = Parser;
|
||||||
_ = Terminal;
|
_ = Terminal;
|
||||||
|
@ -18,11 +18,18 @@ const log = std.log.scoped(.stream);
|
|||||||
/// This is implemented this way because we purposely do NOT want dynamic
|
/// This is implemented this way because we purposely do NOT want dynamic
|
||||||
/// dispatch for performance reasons. The way this is implemented forces
|
/// dispatch for performance reasons. The way this is implemented forces
|
||||||
/// comptime resolution for all function calls.
|
/// comptime resolution for all function calls.
|
||||||
pub fn Stream(comptime T: type) type {
|
pub fn Stream(comptime Handler: type) type {
|
||||||
return struct {
|
return struct {
|
||||||
const Self = @This();
|
const Self = @This();
|
||||||
|
|
||||||
handler: T,
|
// We use T with @hasDecl so it needs to be a struct. Unwrap the
|
||||||
|
// pointer if we were given one.
|
||||||
|
const T = switch (@typeInfo(Handler)) {
|
||||||
|
.Pointer => |p| p.child,
|
||||||
|
else => Handler,
|
||||||
|
};
|
||||||
|
|
||||||
|
handler: Handler,
|
||||||
parser: Parser = .{},
|
parser: Parser = .{},
|
||||||
|
|
||||||
/// Process a string of characters.
|
/// Process a string of characters.
|
||||||
@ -43,7 +50,7 @@ pub fn Stream(comptime T: type) type {
|
|||||||
//if (action_opt) |action| log.info("action: {}", .{action});
|
//if (action_opt) |action| log.info("action: {}", .{action});
|
||||||
switch (action_opt orelse continue) {
|
switch (action_opt orelse continue) {
|
||||||
.print => |p| if (@hasDecl(T, "print")) try self.handler.print(p),
|
.print => |p| if (@hasDecl(T, "print")) try self.handler.print(p),
|
||||||
.execute => |code| if (@hasDecl(T, "execute")) try self.handler.execute(code),
|
.execute => |code| try self.execute(code),
|
||||||
.csi_dispatch => |csi| try self.csiDispatch(csi),
|
.csi_dispatch => |csi| try self.csiDispatch(csi),
|
||||||
.esc_dispatch => |esc| try self.escDispatch(esc),
|
.esc_dispatch => |esc| try self.escDispatch(esc),
|
||||||
.osc_dispatch => |cmd| log.warn("unhandled OSC: {}", .{cmd}),
|
.osc_dispatch => |cmd| log.warn("unhandled OSC: {}", .{cmd}),
|
||||||
@ -54,10 +61,41 @@ pub fn Stream(comptime T: type) type {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn execute(self: *Self, c: u8) !void {
|
||||||
|
switch (@intToEnum(ansi.C0, c)) {
|
||||||
|
.NUL => {},
|
||||||
|
|
||||||
|
.BEL => if (@hasDecl(T, "bell"))
|
||||||
|
try self.handler.bell()
|
||||||
|
else
|
||||||
|
log.warn("unimplemented execute: {x}", .{c}),
|
||||||
|
|
||||||
|
.BS => if (@hasDecl(T, "backspace"))
|
||||||
|
try self.handler.backspace()
|
||||||
|
else
|
||||||
|
log.warn("unimplemented execute: {x}", .{c}),
|
||||||
|
|
||||||
|
.HT => if (@hasDecl(T, "horizontalTab"))
|
||||||
|
try self.handler.horizontalTab()
|
||||||
|
else
|
||||||
|
log.warn("unimplemented execute: {x}", .{c}),
|
||||||
|
|
||||||
|
.LF => if (@hasDecl(T, "linefeed"))
|
||||||
|
try self.handler.linefeed()
|
||||||
|
else
|
||||||
|
log.warn("unimplemented execute: {x}", .{c}),
|
||||||
|
|
||||||
|
.CR => if (@hasDecl(T, "carriageReturn"))
|
||||||
|
try self.handler.carriageReturn()
|
||||||
|
else
|
||||||
|
log.warn("unimplemented execute: {x}", .{c}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn csiDispatch(self: *Self, action: Parser.Action.CSI) !void {
|
fn csiDispatch(self: *Self, action: Parser.Action.CSI) !void {
|
||||||
switch (action.final) {
|
switch (action.final) {
|
||||||
// CUF - Cursor Right
|
// CUF - Cursor Right
|
||||||
'C' => if (@hasDecl(T, "cursorRight")) try self.handler.cursorRight(
|
'C' => if (@hasDecl(T, "setCursorRight")) try self.handler.setCursorRight(
|
||||||
switch (action.params.len) {
|
switch (action.params.len) {
|
||||||
0 => 1,
|
0 => 1,
|
||||||
1 => action.params[0],
|
1 => action.params[0],
|
||||||
@ -70,13 +108,11 @@ pub fn Stream(comptime T: type) type {
|
|||||||
|
|
||||||
// HPA - Cursor Horizontal Position Absolute
|
// HPA - Cursor Horizontal Position Absolute
|
||||||
// TODO: test
|
// TODO: test
|
||||||
'G', '`' => if (@hasDecl(T, "setCursorCol")) try self.handler.setCursorCol(
|
'G', '`' => if (@hasDecl(T, "setCursorCol")) switch (action.params.len) {
|
||||||
switch (action.params.len) {
|
0 => try self.handler.setCursorCol(1),
|
||||||
0 => 1,
|
1 => try self.handler.setCursorCol(action.params[0]),
|
||||||
1 => action.params[0],
|
|
||||||
else => log.warn("invalid HPA command: {}", .{action}),
|
else => log.warn("invalid HPA command: {}", .{action}),
|
||||||
},
|
} else log.warn("unimplemented CSI callback: {}", .{action}),
|
||||||
) else log.warn("unimplemented CSI callback: {}", .{action}),
|
|
||||||
|
|
||||||
// CUP - Set Cursor Position.
|
// CUP - Set Cursor Position.
|
||||||
// TODO: test
|
// TODO: test
|
||||||
@ -89,7 +125,7 @@ pub fn Stream(comptime T: type) type {
|
|||||||
|
|
||||||
// Erase Display
|
// Erase Display
|
||||||
// TODO: test
|
// TODO: test
|
||||||
'J' => if (@hasDecl(T, "eraseDisplay")) try self.eraseDisplay(
|
'J' => if (@hasDecl(T, "eraseDisplay")) try self.handler.eraseDisplay(
|
||||||
switch (action.params.len) {
|
switch (action.params.len) {
|
||||||
0 => .below,
|
0 => .below,
|
||||||
1 => mode: {
|
1 => mode: {
|
||||||
@ -163,7 +199,10 @@ pub fn Stream(comptime T: type) type {
|
|||||||
switch (action.params.len) {
|
switch (action.params.len) {
|
||||||
0 => 1,
|
0 => 1,
|
||||||
1 => action.params[0],
|
1 => action.params[0],
|
||||||
else => log.warn("invalid VPA command: {}", .{action}),
|
else => {
|
||||||
|
log.warn("invalid VPA command: {}", .{action});
|
||||||
|
return;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
) else log.warn("unimplemented CSI callback: {}", .{action}),
|
) else log.warn("unimplemented CSI callback: {}", .{action}),
|
||||||
|
|
||||||
@ -197,7 +236,7 @@ pub fn Stream(comptime T: type) type {
|
|||||||
switch (action.final) {
|
switch (action.final) {
|
||||||
// RI - Reverse Index
|
// RI - Reverse Index
|
||||||
'M' => if (@hasDecl(T, "reverseIndex")) switch (action.intermediates.len) {
|
'M' => if (@hasDecl(T, "reverseIndex")) switch (action.intermediates.len) {
|
||||||
0 => try self.reverseIndex(),
|
0 => try self.handler.reverseIndex(),
|
||||||
else => {
|
else => {
|
||||||
log.warn("invalid reverse index command: {}", .{action});
|
log.warn("invalid reverse index command: {}", .{action});
|
||||||
return;
|
return;
|
||||||
@ -227,32 +266,11 @@ test "stream: print" {
|
|||||||
try testing.expectEqual(@as(u8, 'x'), s.handler.c.?);
|
try testing.expectEqual(@as(u8, 'x'), s.handler.c.?);
|
||||||
}
|
}
|
||||||
|
|
||||||
test "stream: execute" {
|
|
||||||
const H = struct {
|
|
||||||
c: ?u8 = 0,
|
|
||||||
p: bool = false,
|
|
||||||
|
|
||||||
pub fn print(self: *@This(), c: u8) !void {
|
|
||||||
_ = c;
|
|
||||||
self.p = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn execute(self: *@This(), c: u8) !void {
|
|
||||||
self.c = c;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var s: Stream(H) = .{ .handler = .{} };
|
|
||||||
try s.next('\n');
|
|
||||||
try testing.expect(!s.handler.p);
|
|
||||||
try testing.expectEqual(@as(u8, '\n'), s.handler.c.?);
|
|
||||||
}
|
|
||||||
|
|
||||||
test "stream: cursor right (CUF)" {
|
test "stream: cursor right (CUF)" {
|
||||||
const H = struct {
|
const H = struct {
|
||||||
amount: u16 = 0,
|
amount: u16 = 0,
|
||||||
|
|
||||||
pub fn cursorRight(self: *@This(), v: u16) !void {
|
pub fn setCursorRight(self: *@This(), v: u16) !void {
|
||||||
self.amount = v;
|
self.amount = v;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
Reference in New Issue
Block a user