summaryrefslogtreecommitdiff
path: root/src/main.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/main.zig')
-rw-r--r--src/main.zig129
1 files changed, 24 insertions, 105 deletions
diff --git a/src/main.zig b/src/main.zig
index e998accc..6d895307 100644
--- a/src/main.zig
+++ b/src/main.zig
@@ -50,9 +50,10 @@ const help_text =
\\
\\With no arguments the shell pane starts in tty mode. A FILE argument
\\(optionally file:LINE) opens it alone, filling the window; a DIRECTORY
- \\argument becomes the cwd shells spawn in. `-` reads stdin, its terminal
- \\escapes stripped, into a +Pager pane: inside a pardes pane the outer
- \\session's, returning at once (a terminal's PAGER), else a new editor's.
+ \\argument becomes the cwd shells spawn in. `-` reads stdin into a +Pager
+ \\pane, its colours kept (PagerColor) and every other escape dropped:
+ \\inside a pardes pane the outer session's, returning at once (a
+ \\terminal's PAGER), else a new editor's.
\\
\\Options:
\\ --tty start as one shell pane already in tty mode
@@ -100,11 +101,12 @@ const help_text =
\\
;
-/// `pardes -` inside a session: `text` into the directory's one +Pager
-/// (/pager, refilled when it is there), written into its
-/// body through one open, as one undo step, and left clean. A text
-/// the session would not take is never lost: it goes to stderr, with why,
-/// and the exit is 1.
+/// `pardes -` inside a session: `text`, stdin as it came, escapes and all,
+/// into the directory's one +Pager (/pager, refilled when it is there):
+/// written after the directory on that one open, the session parses it
+/// (pager.zig) and puts it in as one undo step, left clean. A text the
+/// session would not take is never lost: it goes to stderr, a terminal,
+/// with why, and the exit is 1.
fn pageInto(io: std.Io, gpa: std.mem.Allocator, dial: []const u8, text: []const u8) void {
const Fail = struct {
fn with(o: std.Io, t: []const u8, what: []const u8, err: anyerror) noreturn {
@@ -128,22 +130,16 @@ fn pageInto(io: std.Io, gpa: std.mem.Allocator, dial: []const u8, text: []const
} else !pardes.filesystem.deniedAbove(cwd);
var line_buf: [4200]u8 = undefined;
const dir_line = std.fmt.bufPrint(&line_buf, "{s}\n", .{if (plain) cwd else ""}) catch "\n";
- // Its serial read back on the open that asked: another client's exec
- // meanwhile can never send this text into that client's pane.
- const answer = ninep_io.Client.ask(gpa, dial, "/pager", dir_line) catch |err| Fail.with(io, text, "no +Pager pane", err);
- const serial = std.fmt.parseInt(u32, std.mem.trim(u8, answer, " \n"), 10) catch |err| Fail.with(io, text, "no +Pager pane", err);
- // The whole text through one open of the body (truncated once, then
- // written at rising offsets): one undo step, and every byte of it.
- var body_buf: [64]u8 = undefined;
- const body = std.fmt.bufPrint(&body_buf, "/pane/{d}/body", .{serial}) catch unreachable;
- ninep_io.Client.write(gpa, dial, body, text) catch |err| Fail.with(io, text, "the +Pager pane would not take it", err);
- var ctl_buf: [64]u8 = undefined;
- ninep_io.Client.write(gpa, dial, std.fmt.bufPrint(&ctl_buf, "/pane/{d}/ctl", .{serial}) catch unreachable, "clean\n") catch {};
+ // Its serial read back on the open that asked, once the text is in:
+ // another client's exec meanwhile can never send it into that
+ // client's pane.
+ const answer = ninep_io.Client.ask(gpa, dial, "/pager", dir_line, text) catch |err| Fail.with(io, text, "no +Pager pane", err);
+ _ = std.fmt.parseInt(u32, std.mem.trim(u8, answer, " \n"), 10) catch |err| Fail.with(io, text, "no +Pager pane", err);
}
-/// Stdin to its end, its terminal escapes stripped (a pager's input is
-/// coloured for a terminal; a text pane shows text), at most a file's
-/// limit, a longer one cut there and said so at its end.
+/// Stdin to its end, as it came (its escapes are the session's to parse,
+/// pager.zig), at most a file's limit, a longer one cut there and said so
+/// at its end.
fn readStdinText(gpa: std.mem.Allocator) ![]u8 {
const limit = pardes.memory.limits.max_file_bytes;
var raw: std.ArrayList(u8) = .empty;
@@ -164,90 +160,13 @@ fn readStdinText(gpa: std.mem.Allocator) ![]u8 {
}
try raw.appendSlice(gpa, chunk[0..got]);
}
- var text = try stripEscapes(gpa, raw.items);
if (cut) {
- const said = "\n[pardes: the rest of stdin is cut: a pane holds at most a file's limit]\n";
- text = try std.mem.concat(gpa, u8, &.{ text, said });
- }
- return text;
-}
-
-/// `bytes` without terminal escapes: CSI (`ESC [` ... a final byte, SGR
-/// colour among them), OSC (`ESC ]` ... BEL or `ESC \`), any other
-/// two-byte `ESC x`, and a man page's overstrike (`x BS x`, `_ BS x`).
-pub fn stripEscapes(gpa: std.mem.Allocator, bytes: []const u8) ![]u8 {
- var out = try std.ArrayList(u8).initCapacity(gpa, bytes.len);
- var i: usize = 0;
- while (i < bytes.len) {
- const c = bytes[i];
- if (c == 0x1b and i + 1 < bytes.len) {
- const kind = bytes[i + 1];
- i += 2;
- if (kind == '[') {
- while (i < bytes.len and !(bytes[i] >= 0x40 and bytes[i] <= 0x7e)) i += 1;
- i += 1;
- } else if (std.mem.indexOfScalar(u8, "()*+", kind) != null) {
- i += 1; // a charset's name after it
- } else if (kind == ']') {
- while (i < bytes.len) : (i += 1) {
- if (bytes[i] == 0x07) {
- i += 1;
- break;
- }
- if (bytes[i] == 0x1b and i + 1 < bytes.len and bytes[i + 1] == '\\') {
- i += 2;
- break;
- }
- }
- }
- continue;
- }
- // Overstrike: the byte before a backspace is struck over.
- if (c == 0x08) {
- if (out.items.len > 0) _ = out.pop();
- i += 1;
- continue;
- }
- // A carriage return: CRLF is a newline; alone, the line starts
- // over, as on a terminal, so a progress line keeps its last state.
- if (c == '\r') {
- if (i + 1 < bytes.len and bytes[i + 1] == '\n') {
- i += 1;
- continue;
- }
- const line_start = if (std.mem.lastIndexOfScalar(u8, out.items, '\n')) |nl| nl + 1 else 0;
- out.shrinkRetainingCapacity(line_start);
- i += 1;
- continue;
- }
- // Bytes a terminal acts on and never shows: NUL, BEL, SO, SI.
- if (c == 0 or c == 0x07 or c == 0x0e or c == 0x0f) {
- i += 1;
- continue;
- }
- out.appendAssumeCapacity(c);
- i += 1;
- }
- return out.toOwnedSlice(gpa);
-}
-
-test "stripEscapes takes out colour, other escapes and overstrike, and keeps the text" {
- const gpa = std.testing.allocator;
- const cases = [_][2][]const u8{
- .{ "\x1b[33mcommit abc\x1b[m\n", "commit abc\n" },
- .{ "a\x1b]8;;http://x\x1b\\link\x1b]8;;\x07b", "alinkb" },
- .{ "N\x08NA\x08AM\x08ME\x08E _\x08u", "NAME u" },
- .{ "plain\ttext\n", "plain\ttext\n" },
- .{ "\x1b(Bdone", "done" },
- .{ "a\r\nb\r\n", "a\nb\n" },
- .{ "10%\r50%\r100%\ndone\n", "100%\ndone\n" },
- .{ "x\x00y\x07z\x0e\x0f\n", "xyz\n" },
- };
- for (cases) |c| {
- const got = try stripEscapes(gpa, c[0]);
- defer gpa.free(got);
- try std.testing.expectEqualStrings(c[1], got);
+ // CAN ends an escape the cut left half sent, and SGR 0 its colour,
+ // so the note is never swallowed or tinted.
+ const said = "\x18\x1b[m\n[pardes: the rest of stdin is cut: a pane holds at most a file's limit]\n";
+ try raw.appendSlice(gpa, said);
}
+ return raw.toOwnedSlice(gpa);
}
/// A forwarding failure in words, never an error's name: the ones this
@@ -693,7 +612,7 @@ fn nativeMain(init: std.process.Init) !void {
const command = std.fmt.bufPrint(&command_buf, "{s}{s}\n", .{ path, word[target.path.len..] }) catch |err| Refuse.with(init.io, word, err);
// The pane it went to, read on the open that asked (another
// client's look meanwhile is not this one's): what --wait waits on.
- const went = ninep_io.Client.ask(arena, parent.dial, look, command) catch |err| Refuse.with(init.io, word, err);
+ const went = ninep_io.Client.ask(arena, parent.dial, look, command, "") catch |err| Refuse.with(init.io, word, err);
var rows = std.mem.tokenizeAny(u8, went, " \n");
if (rows.next()) |row| made_serial = std.fmt.parseInt(u32, row, 10) catch null;
} else {