mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-14 07:46:12 +03:00
terminal: writing strings and multiline strings starting to work
This commit is contained in:
@ -4,6 +4,10 @@
|
||||
const Terminal = @This();
|
||||
|
||||
const std = @import("std");
|
||||
const testing = std.testing;
|
||||
const Allocator = std.mem.Allocator;
|
||||
const ansi = @import("ansi.zig");
|
||||
const Parser = @import("Parser.zig");
|
||||
|
||||
/// Screen is the current screen state.
|
||||
screen: Screen,
|
||||
@ -15,13 +19,16 @@ cursor: Cursor,
|
||||
rows: usize,
|
||||
cols: usize,
|
||||
|
||||
/// VT stream parser
|
||||
parser: Parser,
|
||||
|
||||
/// Screen represents a presentable terminal screen made up of lines and cells.
|
||||
const Screen = std.ArrayListUnmanaged(Line);
|
||||
const Line = std.ArrayListUnmanaged(Cell);
|
||||
|
||||
/// Cell is a single cell within the terminal.
|
||||
const Cell = struct {
|
||||
/// Each cell contains exactly one character.
|
||||
/// Each cell contains exactly one character. The character is UTF-8 encoded.
|
||||
char: u32,
|
||||
|
||||
// TODO(mitchellh): this is where we'll track fg/bg and other attrs.
|
||||
@ -40,9 +47,136 @@ pub fn init(cols: usize, rows: usize) Terminal {
|
||||
.rows = rows,
|
||||
.screen = .{},
|
||||
.cursor = .{ .x = 0, .y = 0 },
|
||||
.parser = Parser.init(),
|
||||
};
|
||||
}
|
||||
|
||||
test {
|
||||
_ = @import("Parser.zig");
|
||||
pub fn deinit(self: *Terminal, alloc: Allocator) void {
|
||||
for (self.screen.items) |*line| line.deinit(alloc);
|
||||
self.screen.deinit(alloc);
|
||||
self.* = undefined;
|
||||
}
|
||||
|
||||
/// Return the current string value of the terminal. Newlines are
|
||||
/// encoded as "\n". This omits any formatting such as fg/bg.
|
||||
///
|
||||
/// The caller must free the string.
|
||||
pub fn plainString(self: Terminal, alloc: Allocator) ![]const u8 {
|
||||
// Create a buffer that has the number of lines we have times the maximum
|
||||
// width it could possibly be. In all likelihood we aren't using the full
|
||||
// width (of at least the last line) but the error margine here won't be
|
||||
// much.
|
||||
const buffer = try alloc.alloc(u8, self.screen.items.len * self.cols * 4);
|
||||
var i: usize = 0;
|
||||
for (self.screen.items) |line, y| {
|
||||
if (y > 0) {
|
||||
buffer[i] = '\n';
|
||||
i += 1;
|
||||
}
|
||||
|
||||
for (line.items) |cell| {
|
||||
i += try std.unicode.utf8Encode(@intCast(u21, cell.char), buffer[i..]);
|
||||
}
|
||||
}
|
||||
|
||||
return buffer[0..i];
|
||||
}
|
||||
|
||||
/// Append a string of characters. See appendChar.
|
||||
pub fn append(self: *Terminal, alloc: Allocator, str: []const u8) !void {
|
||||
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 actions = self.parser.next(c);
|
||||
for (actions) |action_opt| {
|
||||
switch (action_opt orelse continue) {
|
||||
.print => |p| try self.print(alloc, p),
|
||||
.execute => |code| try self.execute(code),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn print(self: *Terminal, alloc: Allocator, c: u8) !void {
|
||||
// Build our cell
|
||||
const cell = try self.getOrPutCell(alloc, self.cursor.x, self.cursor.y);
|
||||
cell.* = .{
|
||||
.char = @intCast(u32, c),
|
||||
};
|
||||
|
||||
// Move the cursor
|
||||
self.cursor.x += 1;
|
||||
}
|
||||
|
||||
fn execute(self: *Terminal, c: u8) !void {
|
||||
switch (@intToEnum(ansi.C0, c)) {
|
||||
.LF => self.linefeed(),
|
||||
.CR => self.carriage_return(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Carriage return moves the cursor to the first column.
|
||||
pub fn carriage_return(self: *Terminal) void {
|
||||
self.cursor.x = 0;
|
||||
}
|
||||
|
||||
/// Linefeed moves the cursor to the next line.
|
||||
pub fn linefeed(self: *Terminal) void {
|
||||
// TODO: end of screen
|
||||
self.cursor.y += 1;
|
||||
}
|
||||
|
||||
fn getOrPutCell(self: *Terminal, alloc: Allocator, x: usize, y: usize) !*Cell {
|
||||
// If we don't have enough lines to get to y, then add it.
|
||||
if (self.screen.items.len < y + 1) {
|
||||
try self.screen.ensureTotalCapacity(alloc, y + 1);
|
||||
self.screen.appendNTimesAssumeCapacity(.{}, y + 1 - self.screen.items.len);
|
||||
}
|
||||
|
||||
const line = &self.screen.items[y];
|
||||
if (line.items.len < x + 1) {
|
||||
try line.ensureTotalCapacity(alloc, x + 1);
|
||||
line.appendNTimesAssumeCapacity(undefined, x + 1 - line.items.len);
|
||||
}
|
||||
|
||||
return &line.items[x];
|
||||
}
|
||||
|
||||
test {
|
||||
_ = Parser;
|
||||
}
|
||||
|
||||
test "Terminal: simple input" {
|
||||
var t = init(80, 80);
|
||||
defer t.deinit(testing.allocator);
|
||||
|
||||
// Basic grid writing
|
||||
try t.append(testing.allocator, "hello");
|
||||
try testing.expectEqual(@as(usize, 0), t.cursor.y);
|
||||
try testing.expectEqual(@as(usize, 5), t.cursor.x);
|
||||
{
|
||||
var str = try t.plainString(testing.allocator);
|
||||
defer testing.allocator.free(str);
|
||||
try testing.expectEqualStrings("hello", str);
|
||||
}
|
||||
}
|
||||
|
||||
test "Terminal: multiline input" {
|
||||
var t = init(80, 80);
|
||||
defer t.deinit(testing.allocator);
|
||||
|
||||
// Basic grid writing
|
||||
try t.append(testing.allocator, "hello\r\nworld");
|
||||
try testing.expectEqual(@as(usize, 1), t.cursor.y);
|
||||
try testing.expectEqual(@as(usize, 5), t.cursor.x);
|
||||
{
|
||||
var str = try t.plainString(testing.allocator);
|
||||
defer testing.allocator.free(str);
|
||||
try testing.expectEqualStrings("hello\nworld", str);
|
||||
}
|
||||
}
|
||||
|
10
src/terminal/ansi.zig
Normal file
10
src/terminal/ansi.zig
Normal file
@ -0,0 +1,10 @@
|
||||
/// C0 (7-bit) control characters from ANSI.
|
||||
///
|
||||
/// This is not complete, control characters are only added to this
|
||||
/// as the terminal emulator handles them.
|
||||
pub const C0 = enum(u7) {
|
||||
/// Line feed
|
||||
LF = 0x0A,
|
||||
/// Carriage return
|
||||
CR = 0x0D,
|
||||
};
|
Reference in New Issue
Block a user