diff options
Diffstat (limited to 'src/exec.zig')
| -rw-r--r-- | src/exec.zig | 89 |
1 files changed, 78 insertions, 11 deletions
diff --git a/src/exec.zig b/src/exec.zig index eed9297a..5a421f19 100644 --- a/src/exec.zig +++ b/src/exec.zig @@ -169,10 +169,54 @@ pub fn pointerOperand(p: *Pardes, pane: *Pane, clicked: Pane.Sel) PointerOperand result.text = edit.currentSelText(p, pane); return result; } + // In a diff, a file or hunk header, and a hunk line pointed at on its + // `+`/`-`/` ` prefix, are looked at whole: the place the line names + // (look.diffLook). A hunk line's code is words, looked at as ever: + // the code's, the prefix never one of them. + var word_from: usize = 0; + if (clicked.r0 >= pane.tag_rows and clicked.r0 == clicked.r1 and clicked.c0 == clicked.c1 and row >= 0) diff: { + const lines = look.diffLines(p, pane) orelse break :diff; + const at: usize = @intCast(row); + if (at >= lines.len) break :diff; + const line = lines[at]; + const target = @import("diff.zig").targetAt(lines, at, pane.file == null) orelse break :diff; + const on_prefix = col == 0 and line.len > 0 and std.mem.indexOfScalar(u8, "+- ", line[0]) != null; + switch (target.kind) { + .context, .added, .removed => if (!on_prefix) { + word_from = @intFromBool(line.len > 0 and std.mem.indexOfScalar(u8, "+- ", line[0]) != null); + break :diff; + }, + else => {}, + } + result.text = line; + p.diff_look = .{ .serial = pane.serial, .row = at }; + const hi = std.math.cast(i32, line.len) orelse return result; + if (hi == 0) return result; + var expanded = clicked; + if (pane.file != null) { + result.file_word = .{ .row = row, .lo = 0, .hi = hi }; + const first = pane.wrapRow(row, 0); + const last = pane.wrapRow(row, hi - 1); + if (first.row < 0 or last.row < 0) return result; + expanded.r0 = first.row + @as(i32, pane.tag_rows); + expanded.c0 = @as(i32, panes.File.gutterWidth(pane)) + panes.File.displayOffset(pane, row, first.at, 0); + expanded.r1 = last.row + @as(i32, pane.tag_rows); + expanded.c1 = @as(i32, panes.File.gutterWidth(pane)) + panes.File.displayEndOffset(pane, row, last.at, hi - 1); + } else { + const shown = if (raw) raw_line else line; + expanded.c0 = 0; + expanded.c1 = @as(i32, @intCast(panes.File.rawDisplayCol(shown, shown.len))) - 1; + } + result.preview = expanded; + result.expanded = expanded; + return result; + } if (pane.file != null and clicked.r0 >= pane.tag_rows and clicked.r0 == clicked.r1 and clicked.c0 == clicked.c1) { const line = panes.File.sourceLine(pane, row); const source_col: usize = @intCast(@max(0, col)); - const b = look.expandedWord(line, source_col) orelse return result; + if (source_col < word_from or word_from > line.len) return result; + const word = look.expandedWord(line[word_from..], source_col - word_from) orelse return result; + const b: look.ExpandedWord = .{ .lo = word.lo + word_from, .hi = word.hi + word_from }; const lo = std.math.cast(i32, b.lo) orelse return result; const hi = std.math.cast(i32, b.hi) orelse return result; result.text = line[b.lo..b.hi]; @@ -295,9 +339,10 @@ pub fn saveTo(p: *Pardes, id: usize, path: []const u8) void { if (path.len == 0 or path[path.len - 1] == '/' or std.mem.indexOfScalar(u8, path, '\n') != null) return p.reportError(id, "save", error.NoFilename); + var home_buf: [4096]u8 = undefined; const full = std.fs.path.resolvePosix( p.scratch.allocator(), - &.{ Pardes.paneDir(pane), path }, + &.{ Pardes.paneDir(pane), @import("fs.zig").expandHome(path, &home_buf) }, ) catch return; if (!std.fs.path.isAbsolute(full)) return p.reportError(id, "save", error.NoDirectory); if (full.len > Pardes.SavePath.cap) return p.reportError(id, "save", error.PathTooLong); @@ -643,12 +688,22 @@ pub fn execute(p: *Pardes, id: usize, txt: []const u8) ?usize { /// `execute` for text a gesture took from `id`'s body when `from_body`: /// such text may be a bound REPL's. Run by name (`Exec ...`, whose line /// comes back through `execute`) or written as a command word, never. +/// A line that starts with `#` is a comment, as in a shell: it runs as +/// nothing, silently, wherever a line runs (exec, a ctl, the init file), +/// so DumpConfig's report reads back as configuration whole. +pub fn isComment(line: []const u8) bool { + const t = std.mem.trimStart(u8, line, " \t"); + return t.len > 0 and t[0] == '#'; +} + pub fn executeFrom(p: *Pardes, id: usize, txt: []const u8, from_body: bool) ?usize { - // An empty window runs its session words, and nothing else: no pane to - // run a command for. + if (isComment(commandText(txt))) return null; + // An empty window runs its session words, and a command line in the + // session's directory (execDir), as its workspace tag would. const pane = p.panes[id] orelse { - _ = executeBuiltinLine(p, id, commandText(txt)); - return null; + const cmd = commandText(txt); + if (cmd.len == 0 or executeBuiltinLine(p, id, cmd)) return null; + return runCommand(p, id, cmd); }; const gesture = from_body and config.commandWord(std.mem.trim(u8, txt, " \t\r\n")) == null; const cmd = commandText(txt); @@ -699,7 +754,9 @@ fn runCommand(p: *Pardes, from: usize, line: []const u8) ?usize { p.reportFailure(from, "exec: a command line is at most 1024 bytes"); return null; } - const src = p.panes[from] orelse return null; + // None asking (an empty window): the session's directory, no pane of + // its own to reuse. + const src = p.panes[from]; const dir = p.execDir(from); // A directory that is not there runs nothing, as Tty makes nothing // there: no pane, and the write fails ENOENT. @@ -712,7 +769,7 @@ fn runCommand(p: *Pardes, from: usize, line: []const u8) ?usize { // would hang that job up. // From a column's tag, only a finished pane in that column is reused, // as the new one goes in that column (placeNew). - const reuse: ?usize = if (p.exec_column == null and src.command != null and src.command_done and !src.command_pty) from else for (p.panes, 0..) |slot, i| { + const reuse: ?usize = if (p.exec_column == null and src != null and src.?.command != null and src.?.command_done and !src.?.command_pty) from else for (p.panes, 0..) |slot, i| { const other = slot orelse continue; if (p.exec_column) |c| if (if (layout.findPane(p, i)) |f| f.col != c else true) continue; if (other.command != null and other.command_done and !other.command_pty and std.mem.eql(u8, other.cwdSlice(), dir)) break i; @@ -725,6 +782,7 @@ fn runCommand(p: *Pardes, from: usize, line: []const u8) ?usize { }; p.gpa.free(pane.command.?); pane.command = owned; + p.diff_output.drop(p.gpa, pane.serial); pane.command_done = false; pane.command_status = null; pane.command_pty = true; @@ -838,6 +896,7 @@ pub const acme_words = [_]struct { acme: []const u8, pardes: ?[]const u8, hint: pub fn executeBuiltinLine(p: *Pardes, id: usize, txt: []const u8) bool { const raw = commandText(txt); if (raw.len == 0 or p.exec_depth >= max_exec_depth) return false; + if (isComment(raw)) return true; const word_end = std.mem.indexOfAny(u8, raw, " \t") orelse raw.len; var renamed: [1024 + 16]u8 = undefined; const cmd = for (acme_words) |w| { @@ -1032,9 +1091,12 @@ pub fn placeNew(p: *Pardes, from: usize, origin: ?usize, id: usize, kind: NewPan // No pane to halve, but the column holds every pane's minimum // with the new one's: the rows are shared out again, so what is // refused is only what the documented arithmetic cannot hold. - // +Errors goes wherever there is room, last column first; with - // none, its maker says the text some other way (appendErrors). - if (layout.shareColumn(p, f.col)) {} else if (kind == .errors) { + // +Errors and 9P's pane/new go wherever there is room, last + // column first, as acme takes rows from a window that has them + // before saying there are none; with none, +Errors's maker says + // the text some other way (appendErrors), and pane/new is + // refused. + if (layout.shareColumn(p, f.col)) {} else if (kind == .errors or kind == .fs) { layout.removePane(p, id); var c = p.ncol; while (c > 0) { @@ -1051,6 +1113,11 @@ pub fn placeNew(p: *Pardes, from: usize, origin: ?usize, id: usize, kind: NewPan } else { layout.insert(p, f.col, p.col_n[f.col], id); p.unplaced.set(id); + // No pane anywhere can give it rows: pane/new is refused. + if (kind == .fs) { + p.panes[id].?.vweight = 0; + p.reportError(from, "placement", error.NoPaneRoom); + } return; } } else { |
