summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-29 22:27:38 -0300
committerGabriel Schneider <[email protected]>2026-10-01 00:12:17 -0300
commitd4960472375ea68f709c56a677d8a37571419ff7 (patch)
tree8b5b2dde93eded887c7b1a17164137c3c09a4bb7 /src
parentdfa5bd4d413504a6afff414962c4de70ab093af0 (diff)
downloadpardes-d4960472375ea68f709c56a677d8a37571419ff7.tar.gz
pardes-d4960472375ea68f709c56a677d8a37571419ff7.zip
Config opens the startup file, DumpConfig the settings report, and the workspace tag reads Dump Themes Config Debug Exit
Config (SPC f c) opens init, found as the settings report finds it (opts.startup_config_path: $XDG_CONFIG_HOME/pardes/init or ~/.config/pardes/init, and the macOS and Windows paths config.md gives). A pane that already holds it is gone to instead; a file on disk is looked at; with none, an empty pane is named for it, and Save makes its directory and any above it first (File.make_dirs, fs.makeDirs) before writing. Without a configuration directory it says so and opens nothing. The report Config used to open, the startup path and every live setting, is DumpConfig, in a +DumpConfig pane. No alias keeps the old meaning. The workspace tag's default words put Themes where NextColor was, NextColor staying a builtin, and Config beside it. A tag the user edited is kept as it was: a dump stores only an edited one (topbar_custom), and a restore puts back only that. theme.snap types NextColor onto the workspace tag to click it, and tagnav's walk back to Dump takes five long-word steps. The rest of the re-recorded goldens differ in the workspace words and their widths only. Co-Authored-By: Claude Opus 5.5 <[email protected]>
Diffstat (limited to 'src')
-rw-r--r--src/CHANGELOG.md10
-rw-r--r--src/File.zig4
-rw-r--r--src/Messages.zig2
-rw-r--r--src/Output.zig2
-rw-r--r--src/builtins.zig32
-rw-r--r--src/config.zig11
-rw-r--r--src/crash.zig2
-rw-r--r--src/fs.zig19
-rw-r--r--src/ninep/ctl.zig6
-rw-r--r--src/pardes.zig10
-rw-r--r--src/tutor.txt4
11 files changed, 83 insertions, 19 deletions
diff --git a/src/CHANGELOG.md b/src/CHANGELOG.md
index 50149551..ca37856e 100644
--- a/src/CHANGELOG.md
+++ b/src/CHANGELOG.md
@@ -1,5 +1,15 @@
# Changelog
+## Unreleased
+
+- `Config` (`SPC f c`) opens the startup file, `init`, in a pane, or goes
+ to its pane when it is open. With no file yet, it opens an empty pane of
+ that name, and Save writes it, making the directory first. The settings
+ report `Config` used to open is `DumpConfig`, in a `+DumpConfig` pane.
+- The workspace tag reads `… Dump Themes Config Debug Exit`: `Themes` in
+ place of `NextColor`, which stays a builtin, and `Config` beside it. A
+ workspace tag you edited, and one a dump kept, stays as it is.
+
## 0.0.5
- Over a slow link the terminal build keeps at most one frame in flight: it
diff --git a/src/File.zig b/src/File.zig
index 8babb27d..e8a9e2f8 100644
--- a/src/File.zig
+++ b/src/File.zig
@@ -58,6 +58,10 @@ pub const State = struct {
/// as an undo to the save point is in acme.
saved_hash: ?u64 = null,
watch_after_save: bool = false,
+ /// Save makes the file's directory, and any above it, first: `Config`'s
+ /// pane for an init file that is not there yet, whose directory may not
+ /// be either.
+ make_dirs: bool = false,
/// The file changed on disk under unsaved edits, which were kept (see
/// `changed`): its hash, so the same change is said once, until a Save
/// or get settles it. A Save warns once before overwriting it, as acme's
diff --git a/src/Messages.zig b/src/Messages.zig
index 7dcb22ed..d1478ac7 100644
--- a/src/Messages.zig
+++ b/src/Messages.zig
@@ -782,7 +782,7 @@ test "a message falls in, lingers past the input that dismisses it, and dissolve
try std.testing.expect(p.executeBuiltinLine(p.active, "MessageDissolve 0"));
try std.testing.expectEqual(@as(u16, 3), messageFrames(p.settings.message_fall_ms));
try std.testing.expectEqual(@as(u16, 1), messageFrames(p.settings.message_dissolve_ms));
- // The Config report reads back as configuration.
+ // The DumpConfig report reads back as configuration.
try std.testing.expect(p.executeBuiltinLine(p.active, "MessageAnimation"));
try std.testing.expect(!p.settings.message_animation);
try std.testing.expect(p.executeBuiltinLine(p.active, "Msg plain"));
diff --git a/src/Output.zig b/src/Output.zig
index fb8fe7a3..69e9644c 100644
--- a/src/Output.zig
+++ b/src/Output.zig
@@ -835,7 +835,7 @@ pub fn openConfig(p: *Pardes, id: usize) !void {
.state = &p.settings,
});
const content = try out.toOwnedSlice();
- return openRead(p, id, .{ .cmd = .Config }, "", content);
+ return openRead(p, id, .{ .cmd = .DumpConfig }, "", content);
}
/// The message-row log, oldest first — the lines that were said in passing and
diff --git a/src/builtins.zig b/src/builtins.zig
index c549b17f..c40b2e00 100644
--- a/src/builtins.zig
+++ b/src/builtins.zig
@@ -1283,10 +1283,38 @@ pub const Help = struct {
}
};
-/// Open the settings in effect, each a word to click.
+/// Open the startup file, `init`, in a pane: its pane when it is open, else
+/// the file, else a pane of that name for Save to make it and its directory.
pub const Config = struct {
pub const scope: Scope = .session;
- pub const output: OutputTraits = .{ .name = config.config_buffer };
+ pub fn run(c: Ctx) void {
+ const path = c.p.opts.startup_config_path orelse
+ return c.p.reportFailure(c.id, "Config: no startup file here: no configuration directory");
+ var real_buf: [4096]u8 = undefined;
+ const real = if (pardes.filesystem.resolveOs(path, &real_buf)) |r| r.path else null;
+ for (c.p.panes, 0..) |slot, id| {
+ const pane = slot orelse continue;
+ const f = pane.file orelse continue;
+ if (f.output != null) continue;
+ if (!std.mem.eql(u8, f.path, path) and !(if (real) |r| std.mem.eql(u8, f.path, r) else false)) continue;
+ return look.focusPaneLine(c.p, id, .{}, .keep);
+ }
+ if (real != null) return look.lookAt(c.p, c.id, path);
+ // Not there yet: a pane named for it, which Save makes.
+ const free = c.p.freeSlot() orelse return c.p.reportError(c.id, "Config", error.NoPaneSlots);
+ c.p.newScratchFor(c.id, .scratch);
+ const pane = c.p.panes[free] orelse return;
+ @import("ninep/pane.zig").nameBuffer(c.p, free, path, false) catch |err| return c.p.reportError(c.id, "Config", err);
+ pane.file.?.make_dirs = true;
+ c.p.active = free;
+ }
+};
+
+/// Open the settings in effect, each a word to click, and the startup
+/// file's path.
+pub const DumpConfig = struct {
+ pub const scope: Scope = .session;
+ pub const output: OutputTraits = .{ .name = config.dump_config_buffer };
pub fn run(c: Ctx) void {
panes.Output.openConfig(c.p, c.id) catch |err| c.p.reportError(c.id, "config", err);
}
diff --git a/src/config.zig b/src/config.zig
index ea7a877a..bb033823 100644
--- a/src/config.zig
+++ b/src/config.zig
@@ -68,6 +68,7 @@ pub const leader_path = paths: {
.Recent = "fr",
.Grep = "fg",
.Config = "fc",
+ .DumpConfig = null,
.LocationsConfig = null,
.Tutor = "ht",
.Changelog = "hc",
@@ -266,7 +267,7 @@ pub const child_term = "xterm-256color";
pub const child_colorterm = "truecolor";
pub const child_term_program = "pardes";
-pub const topbar_str = "Newcol Joincol Find Grep Help Changelog Tutor Dump NextColor Debug Exit";
+pub const topbar_str = "Newcol Joincol Find Grep Help Changelog Tutor Dump Themes Config Debug Exit";
pub const pane_builtins_str = "Tty Collapse Del";
pub const pdf_pane_builtins_str = "Tty PdfSections PdfTint Collapse Del";
@@ -452,7 +453,7 @@ pub const pipe_marker = " |";
pub const search_buffer = "+Search";
pub const help_buffer = "+Help";
-pub const config_buffer = "+Config";
+pub const dump_config_buffer = "+DumpConfig";
pub const effect_code_buffer = "+EffectCode";
pub const jumps_buffer = "+Jumps";
pub const recent_buffer = "+Recent";
@@ -968,7 +969,7 @@ pub const Runtime = struct {
}
};
- // The builtin registry and Config report share this command table.
+ // The builtin registry and DumpConfig report share this command table.
pub const settings = [_]Setting{
.{ .word = "Colors", .action = .{ .toggle = .colors }, .doc = "Syntax colours in text panes, on or off." },
.{ .word = "Wrap", .action = .{ .toggle = .wrap }, .doc = "Long lines wrap onto the next row, on or off." },
@@ -1292,7 +1293,7 @@ pub const Runtime = struct {
/// Each line reads as the command that would set it: the word, then the
/// value you would type after it. No colons, no units — `WindowOpacity 70`
- /// is both what Config prints and what you write back.
+ /// is both what DumpConfig prints and what you write back.
pub fn writeReport(out: *std.Io.Writer, context: ReportContext) !void {
const state = context.state;
var wrote_transition = false;
@@ -1653,7 +1654,7 @@ pub const Runtime = struct {
try std.testing.expect(FontSpec.parse(bad) == null);
}
- test "Config report observes every simple setting and all live context" {
+ test "DumpConfig report observes every simple setting and all live context" {
var state: Runtime = .{};
// Every setting, every context line: the report outgrows 4K.
var storage: [8192]u8 = undefined;
diff --git a/src/crash.zig b/src/crash.zig
index 10d38591..99822aad 100644
--- a/src/crash.zig
+++ b/src/crash.zig
@@ -23,7 +23,7 @@ const std = @import("std");
const builtin = @import("builtin");
const pardes = @import("pardes.zig");
-/// Beside `init`, so `Config` opens the directory that holds both.
+/// The crash log's name, beside `init` in the configuration directory.
pub const name = "crashes";
/// The config directory `config.User.load` resolved, COPIED rather than
diff --git a/src/fs.zig b/src/fs.zig
index fc3eb98f..1d0bbc22 100644
--- a/src/fs.zig
+++ b/src/fs.zig
@@ -637,6 +637,25 @@ test "a path with a part over 255 bytes is NameTooLong, never a panic" {
try std.testing.expectError(error.NameTooLong, statPath(std.testing.io, long, .{}));
}
+/// Make `dir` and each directory above it that is missing, as `mkdir -p`
+/// does: one already there is no failure. A failure shows at the open
+/// that follows.
+pub fn makeDirs(dir: []const u8) void {
+ if (comptime !platform_has_fs) return;
+ var z: [4096:0]u8 = undefined;
+ if (dir.len == 0 or dir.len >= z.len) return;
+ @memcpy(z[0..dir.len], dir);
+ z[dir.len] = 0;
+ pardes.turn.yield();
+ defer pardes.turn.back();
+ for (z[1..dir.len], 1..) |c, i| if (c == '/') {
+ z[i] = 0;
+ _ = libc.mkdir(z[0..i :0], 0o755);
+ z[i] = '/';
+ };
+ _ = libc.mkdir(z[0..dir.len :0], 0o755);
+}
+
pub fn localPath(path: []const u8) ?[]const u8 {
if (std.mem.eql(u8, path, "/n/os")) return "/";
if (std.mem.startsWith(u8, path, "/n/os/")) return path[5..];
diff --git a/src/ninep/ctl.zig b/src/ninep/ctl.zig
index 060e0d6c..5ed03c58 100644
--- a/src/ninep/ctl.zig
+++ b/src/ninep/ctl.zig
@@ -485,7 +485,7 @@ const e_prompt = "control message needs its argument";
/// Every setting, one a line, in the words a write of it takes, so that the
/// file read back is the configuration, as webfs's ctl reads (webfs(4)).
/// What is not a setting -- platform, themes, where the config came from --
-/// is /status's or the Config window's.
+/// is /status's or the DumpConfig window's.
fn writeSettings(p: *Pardes, w: *std.Io.Writer) !void {
try writeSettingsOf(p, &p.settings, w);
try w.writeAll("LocationsConfig ");
@@ -523,7 +523,7 @@ pub fn writeSettingsOf(p: *Pardes, s: *const config.Runtime, w: *std.Io.Writer)
inline else => |field| try w.print("{s} {s}\n", .{ word, @tagName(@field(s, @tagName(field))) }),
},
.message_ms => |which| try w.print("{s} {d}\n", .{ word, @field(s, "message_" ++ @tagName(which) ++ "_ms") }),
- // The directory in effect, as Config reports it; bare only when
+ // The directory in effect, as DumpConfig reports it; bare only when
// there is none (no home to put the default in).
.dump_dir => {
var buf: [900]u8 = undefined;
@@ -1165,7 +1165,7 @@ test "the root ctl reads the settings as a write takes them, and takes the sessi
try testing.expectEqual(@as(u64, settings.len), call(p, .{ .tag = 1, .op = .getattr, .node = root_ctl }).reply.attr.size);
try testing.expect(std.mem.indexOf(u8, settings, "Verbose on\n") != null);
try testing.expect(std.mem.indexOf(u8, settings, "LocationsConfig ") != null);
- // Settings only: nothing /status or the Config window says.
+ // Settings only: nothing /status or the DumpConfig window says.
try testing.expect(std.mem.indexOf(u8, settings, "Platform") == null);
try testing.expect(std.mem.indexOf(u8, settings, "requested") == null);
diff --git a/src/pardes.zig b/src/pardes.zig
index 18794975..e3db97d9 100644
--- a/src/pardes.zig
+++ b/src/pardes.zig
@@ -1329,7 +1329,7 @@ test "acme placement: New and 9P's new take the bottom half of the last pane" {
try std.testing.expect(p.rects[made].y > was.y + BOX_H);
}
-test "Placement pardes brings back the old rules, and says so in Config" {
+test "Placement pardes brings back the old rules, and says so in DumpConfig" {
const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true, .cols = 100, .rows = 40 });
defer p.deinit();
try std.testing.expect(p.executeBuiltinLine(p.active, "Placement pardes"));
@@ -3493,7 +3493,7 @@ test "s in a header selects in the header's own text" {
var ranges: [Text.max_selections]modal.Selection = undefined;
const text = try edit.flatSurface(p, &p.global_tag);
const got = p.global_tag.ranges(text, 0, &ranges);
- try std.testing.expectEqual(@as(usize, 3), got.n); // Newcol, Joincol, NextColor
+ try std.testing.expectEqual(@as(usize, 2), got.n); // Newcol, Joincol
try std.testing.expectEqual(@as(u16, 0), pane.body.nsel);
}
@@ -5261,7 +5261,7 @@ pub const Pardes = struct {
}
/// Resolve the request already handed to the GUI host. Requested and
- /// effective tuples deliberately remain separate so Config can show both.
+ /// effective tuples deliberately remain separate so DumpConfig can show both.
pub fn acknowledgeFont(
p: *Pardes,
effective_name: []const u8,
@@ -5596,6 +5596,8 @@ pub const Pardes = struct {
const was = f.saved_revision;
const failures = p.fs.failures;
f.saved_revision = f.revision;
+ if (f.make_dirs) if (filesystem.localPath(f.path)) |local|
+ filesystem.makeDirs(std.fs.path.dirname(local) orelse "/");
p.hostWriteFile(sf.pane, f.path, f.content);
const saved_pane = p.panes[sf.pane] orelse return;
if (saved_pane.serial != serial) return;
@@ -6721,7 +6723,7 @@ pub const Pardes = struct {
}
/// A native post pass proved unavailable: the chain is emptied, which
- /// Config then reports.
+ /// DumpConfig then reports.
pub fn disableSceneEffects(p: *Pardes) void {
p.settings.post.clear();
}
diff --git a/src/tutor.txt b/src/tutor.txt
index c59deeae..f8aa7aef 100644
--- a/src/tutor.txt
+++ b/src/tutor.txt
@@ -65,8 +65,8 @@
Every pane has Collapse: hide its body, then execute again to expand it.
The top bar is:
- Newcol Joincol Find Grep Help Changelog Tutor Dump NextColor
- Debug Exit
+ Newcol Joincol Find Grep Help Changelog Tutor Dump Themes
+ Config Debug Exit
Reach it from the keyboard with `k` off the topmost tagline; `j` comes
back down.