summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorMitchell Hashimoto <mitchell.hashimoto@gmail.com>2023-10-12 20:46:26 -0700
committerMitchell Hashimoto <mitchell.hashimoto@gmail.com>2023-10-12 20:46:26 -0700
commit5ce50d08a1bd1d2adf338109d78ef8f13b4ee27c (patch)
tree309e16e7ebe067d7e542aa6fa8841dd87302e9f6 /src
parent8c61f8d890866e55ffa145b4c2844d6319b18acf (diff)
terminal: linefeed mode
Diffstat (limited to 'src')
-rw-r--r--src/terminal/Terminal.zig17
-rw-r--r--src/terminal/modes.zig1
-rw-r--r--src/termio/Exec.zig48
-rw-r--r--src/termio/Thread.zig11
-rw-r--r--src/termio/message.zig3
5 files changed, 70 insertions, 10 deletions
diff --git a/src/terminal/Terminal.zig b/src/terminal/Terminal.zig
index 9332c5479..8a0e0c7f2 100644
--- a/src/terminal/Terminal.zig
+++ b/src/terminal/Terminal.zig
@@ -1522,6 +1522,7 @@ pub fn linefeed(self: *Terminal) !void {
defer tracy.end();
try self.index();
+ if (self.modes.get(.linefeed)) self.carriageReturn();
}
/// Inserts spaces at current cursor position moving existing cell contents
@@ -2319,6 +2320,22 @@ test "Terminal: linefeed unsets pending wrap" {
try testing.expect(t.screen.cursor.pending_wrap == false);
}
+test "Terminal: linefeed mode automatic carriage return" {
+ var t = try init(testing.allocator, 10, 10);
+ defer t.deinit(testing.allocator);
+
+ // Basic grid writing
+ t.modes.set(.linefeed, true);
+ try t.printString("123456");
+ try t.linefeed();
+ try t.print('X');
+ {
+ var str = try t.plainString(testing.allocator);
+ defer testing.allocator.free(str);
+ try testing.expectEqualStrings("123456\nX", str);
+ }
+}
+
test "Terminal: carriage return unsets pending wrap" {
var t = try init(testing.allocator, 5, 80);
defer t.deinit(testing.allocator);
diff --git a/src/terminal/modes.zig b/src/terminal/modes.zig
index 0e70dd53c..b7d2e567e 100644
--- a/src/terminal/modes.zig
+++ b/src/terminal/modes.zig
@@ -174,6 +174,7 @@ const entries: []const ModeEntry = &.{
.{ .name = "disable_keyboard", .value = 2, .ansi = true }, // KAM
.{ .name = "insert", .value = 4, .ansi = true },
.{ .name = "send_receive_mode", .value = 12, .ansi = true, .default = true }, // SRM
+ .{ .name = "linefeed", .value = 20, .ansi = true },
// DEC
.{ .name = "cursor_keys", .value = 1 }, // DECCKM
diff --git a/src/termio/Exec.zig b/src/termio/Exec.zig
index bdf9bffad..c25b70031 100644
--- a/src/termio/Exec.zig
+++ b/src/termio/Exec.zig
@@ -393,7 +393,7 @@ pub fn clearScreen(self: *Exec, history: bool) !void {
// If we reached here it means we're at a prompt, so we send a form-feed.
assert(self.terminal.cursorIsAtPrompt());
- try self.queueWrite(&[_]u8{0x0C});
+ try self.queueWrite(&[_]u8{0x0C}, false);
}
/// Scroll the viewport
@@ -418,7 +418,7 @@ pub fn jumpToPrompt(self: *Exec, delta: isize) !void {
}
}
-pub inline fn queueWrite(self: *Exec, data: []const u8) !void {
+pub inline fn queueWrite(self: *Exec, data: []const u8, linefeed: bool) !void {
const ev = self.data.?;
// We go through and chunk the data if necessary to fit into
@@ -427,19 +427,49 @@ pub inline fn queueWrite(self: *Exec, data: []const u8) !void {
while (i < data.len) {
const req = try ev.write_req_pool.getGrow(self.alloc);
const buf = try ev.write_buf_pool.getGrow(self.alloc);
- const end = @min(data.len, i + buf.len);
- fastmem.copy(u8, buf, data[i..end]);
+ const slice = slice: {
+ // The maximum end index is either the end of our data or
+ // the end of our buffer, whichever is smaller.
+ const max = @min(data.len, i + buf.len);
+
+ // Fast
+ if (!linefeed) {
+ fastmem.copy(u8, buf, data[i..max]);
+ const len = max - i;
+ i = max;
+ break :slice buf[0..len];
+ }
+
+ // Slow, have to replace \r with \r\n
+ var buf_i: usize = 0;
+ while (i < data.len and buf_i < buf.len - 1) {
+ const ch = data[i];
+ i += 1;
+
+ if (ch != '\r') {
+ buf[buf_i] = ch;
+ buf_i += 1;
+ continue;
+ }
+
+ // CRLF
+ buf[buf_i] = '\r';
+ buf[buf_i + 1] = '\n';
+ buf_i += 2;
+ }
+
+ break :slice buf[0..buf_i];
+ };
+
ev.data_stream.queueWrite(
ev.loop,
&ev.write_queue,
req,
- .{ .slice = buf[0..(end - i)] },
+ .{ .slice = slice },
EventData,
ev,
ttyWrite,
);
-
- i = end;
}
}
@@ -1507,6 +1537,10 @@ const StreamHandler = struct {
try self.queueRender();
},
+ .linefeed => {
+ self.messageWriter(.{ .linefeed_mode = enabled });
+ },
+
.mouse_event_x10 => self.terminal.flags.mouse_event = if (enabled) .x10 else .none,
.mouse_event_normal => self.terminal.flags.mouse_event = if (enabled) .normal else .none,
.mouse_event_button => self.terminal.flags.mouse_event = if (enabled) .button else .none,
diff --git a/src/termio/Thread.zig b/src/termio/Thread.zig
index 0b345ac1f..459cec97c 100644
--- a/src/termio/Thread.zig
+++ b/src/termio/Thread.zig
@@ -62,6 +62,10 @@ sync_reset_cancel_c: xev.Completion = .{},
/// The underlying IO implementation.
impl: *termio.Impl,
+/// True if linefeed mode is enabled. This is duplicated here so that the
+/// write thread doesn't need to grab a lock to check this on every write.
+linefeed_mode: bool = false,
+
/// The mailbox that can be used to send this thread messages. Note
/// this is a blocking queue so if it is full you will get errors (or block).
mailbox: *Mailbox,
@@ -175,11 +179,12 @@ fn drainMailbox(self: *Thread) !void {
.scroll_viewport => |v| try self.impl.scrollViewport(v),
.jump_to_prompt => |v| try self.impl.jumpToPrompt(v),
.start_synchronized_output => self.startSynchronizedOutput(),
- .write_small => |v| try self.impl.queueWrite(v.data[0..v.len]),
- .write_stable => |v| try self.impl.queueWrite(v),
+ .linefeed_mode => |v| self.linefeed_mode = v,
+ .write_small => |v| try self.impl.queueWrite(v.data[0..v.len], self.linefeed_mode),
+ .write_stable => |v| try self.impl.queueWrite(v, self.linefeed_mode),
.write_alloc => |v| {
defer v.alloc.free(v.data);
- try self.impl.queueWrite(v.data);
+ try self.impl.queueWrite(v.data, self.linefeed_mode);
},
}
}
diff --git a/src/termio/message.zig b/src/termio/message.zig
index 06e8aae85..c7fe19976 100644
--- a/src/termio/message.zig
+++ b/src/termio/message.zig
@@ -56,6 +56,9 @@ pub const Message = union(enum) {
/// period of time so that a bad actor can't hang the terminal.
start_synchronized_output: void,
+ /// Enable or disable linefeed mode (mode 20).
+ linefeed_mode: bool,
+
/// Write where the data fits in the union.
write_small: WriteReq.Small,