summaryrefslogtreecommitdiff
path: root/src/main.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/main.zig')
-rw-r--r--src/main.zig88
1 files changed, 77 insertions, 11 deletions
diff --git a/src/main.zig b/src/main.zig
index 8818645e..6627bece 100644
--- a/src/main.zig
+++ b/src/main.zig
@@ -100,6 +100,58 @@ const help_text =
\\
;
+/// `pardes -` inside a session: `text` into the directory's one +Pager
+/// (root ctl `pager <dir>`, refilled when it is there), streamed into its
+/// body a bounded chunk at a time 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.
+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 {
+ var words_buf: [128]u8 = undefined;
+ const why = if (err == error.Remote) ninep_io.Client.lastSaid() else forwardWords(err, &words_buf);
+ var buf: [512]u8 = undefined;
+ const head = std.fmt.bufPrint(&buf, "pardes: -: {s}: {s}; the text follows\n", .{ what, why }) catch "pardes: -: refused; the text follows\n";
+ std.Io.File.stderr().writeStreamingAll(o, head) catch {};
+ std.Io.File.stderr().writeStreamingAll(o, t) catch {};
+ std.process.exit(1);
+ }
+ };
+ // The pane's directory: this one, unless its name could not be one line
+ // of a ctl (a newline, a control byte): then the session's.
+ var cwd_buf: [4096]u8 = undefined;
+ const cwd: []const u8 = if (std.c.getcwd(&cwd_buf, cwd_buf.len)) |c| std.mem.span(@as([*:0]u8, @ptrCast(c))) else "";
+ const plain = for (cwd) |c| {
+ if (c < ' ' or c == 0x7f) break false;
+ } else true;
+ var line_buf: [4200]u8 = undefined;
+ const ctl_line = std.fmt.bufPrint(&line_buf, "pager {s}\n", .{if (plain) cwd else ""}) catch "pager\n";
+ ninep_io.Client.write(gpa, dial, "/ctl", ctl_line) catch |err| Fail.with(io, text, "no +Pager pane", err);
+ const answer = ninep_io.Client.read(gpa, dial, "/exec", "/exec") 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);
+ var path_buf: [64]u8 = undefined;
+ const mark = std.fmt.bufPrint(&path_buf, "/pane/{d}/mark", .{serial}) catch unreachable;
+ // One undo step for the whole text, however many writes carry it.
+ ninep_io.Client.write(gpa, dial, mark, "0\n") catch |err| Fail.with(io, text, "the +Pager pane went", err);
+ var body_buf: [64]u8 = undefined;
+ const body = std.fmt.bufPrint(&body_buf, "/pane/{d}/body", .{serial}) catch unreachable;
+ // A chunk ends on a line's end (never inside a character), each its own
+ // write with its own deadline, so a large text arrives whole.
+ const chunk_max = 8 * 1024 * 1024;
+ var at: usize = 0;
+ while (at < text.len) {
+ var end = @min(text.len, at + chunk_max);
+ if (end < text.len) if (std.mem.lastIndexOfScalar(u8, text[at..end], '\n')) |nl| {
+ end = at + nl + 1;
+ };
+ ninep_io.Client.write(gpa, dial, body, text[at..end]) catch |err| Fail.with(io, text[at..], "the +Pager pane would not take it", err);
+ at = end;
+ }
+ ninep_io.Client.write(gpa, dial, mark, "1\n") catch {};
+ 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 {};
+}
+
/// 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.
@@ -167,6 +219,23 @@ pub fn stripEscapes(gpa: std.mem.Allocator, bytes: []const u8) ![]u8 {
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;
}
@@ -181,6 +250,9 @@ test "stripEscapes takes out colour, other escapes and overstrike, and keeps the
.{ "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]);
@@ -537,17 +609,9 @@ fn nativeMain(init: std.process.Init) !void {
// and the body), and back at once: the PAGER a pane's shell gets.
if (positional) |word| if (std.mem.eql(u8, word, "-")) {
const text = readStdinText(arena) catch |err| Refuse.with(init.io, "stdin", err);
- const made = ninep_io.Client.read(arena, parent.dial, "/pane/new", "/pane/new") catch |err| Refuse.with(init.io, "-", err);
- const serial = std.fmt.parseInt(u32, std.mem.trim(u8, made, " \n"), 10) catch |err| Refuse.with(init.io, "-", err);
- var cwd_buf: [4096]u8 = undefined;
- const cwd = if (std.c.getcwd(&cwd_buf, cwd_buf.len)) |c| std.mem.span(@as([*:0]u8, @ptrCast(c))) else "/";
- var path_buf: [64]u8 = undefined;
- var line_buf: [4200]u8 = undefined;
- const name_line = std.fmt.bufPrint(&line_buf, "{s}/+Pager\n", .{std.mem.trimEnd(u8, cwd, "/")}) catch "/+Pager\n";
- ninep_io.Client.write(arena, parent.dial, try std.fmt.bufPrint(&path_buf, "/pane/{d}/name", .{serial}), name_line) catch |err| Refuse.with(init.io, "-", err);
- if (text.len > 0) ninep_io.Client.write(arena, parent.dial, try std.fmt.bufPrint(&path_buf, "/pane/{d}/body", .{serial}), text) catch |err| Refuse.with(init.io, "-", err);
- // Shown, not edited: nothing to ask about at Exit.
- ninep_io.Client.write(arena, parent.dial, try std.fmt.bufPrint(&path_buf, "/pane/{d}/ctl", .{serial}), "clean\n") catch {};
+ // Nothing paged: no pane, and done.
+ if (text.len == 0) return;
+ pageInto(init.io, arena, parent.dial, text);
return;
};
const typed_word = positional orelse {
@@ -644,6 +708,8 @@ fn nativeMain(init: std.process.Init) !void {
// as `vim -` does; the terminal is /dev/tty, never stdin.
if (positional) |a| if (std.mem.eql(u8, a, "-")) {
opts.stdin_text = readStdinText(arena) catch |err| badArgs(init.io, "stdin: {s}", .{@errorName(err)});
+ // Nothing paged: nothing to show.
+ if (opts.stdin_text.?.len == 0) return;
positional = null;
};
if (positional) |a| {