Commit Diff


commit - af46877b6bf86045d68dcfb761c05139c2033a36
commit + 69bab0eed9881f0a425140ef8c9677f0fcf7dd4c
blob - b75234e499966821a6a53c29f28a09d61bc7081f
blob + f9a7ffd55622381c638449dab08e9e3ce4edcb6c
--- src/cfg.zig
+++ src/cfg.zig
@@ -4,9 +4,10 @@ pub const Config = struct {
     openers: []Opener = &.{},
     ignored_patterns: []const []const u8 = &.{},
     fzf_opts: []const []const u8 = &.{},
-    fav_dirs: []const []const u8 = &.{},
+    favorites: []const []const u8 = &.{},
     editor: ?[]const u8 = null,
     max_depth: usize = 3,
+    overrides: []FolderOverride = &.{},
 
     pub const Opener = struct {
         extensions: []const []const u8,
@@ -15,6 +16,35 @@ pub const Config = struct {
     };
 };
 
+// Per-directory settings applied when cwd matches `path` exactly.
+// All override fields are optional; only non-null ones replace the base config.
+pub const FolderOverride = struct {
+    path: []const u8,
+    openers: ?[]Config.Opener = null,
+    ignored_patterns: ?[]const []const u8 = null,
+    fzf_opts: ?[]const []const u8 = null,
+    favorites: ?[]const []const u8 = null,
+    editor: ?[]const u8 = null,
+    max_depth: ?usize = null,
+};
+
+// Return a Config with the first override whose path exactly matches cwd applied on top.
+// The returned Config borrows memory from the parsed Config — no extra allocation needed.
+pub fn applyFolderOverride(base: Config, cwd: []const u8) Config {
+    for (base.overrides) |*ov| {
+        if (!std.mem.eql(u8, cwd, ov.path)) continue;
+        var result = base;
+        if (ov.openers) |v| result.openers = v;
+        if (ov.ignored_patterns) |v| result.ignored_patterns = v;
+        if (ov.fzf_opts) |v| result.fzf_opts = v;
+        if (ov.favorites) |v| result.favorites = v;
+        if (ov.editor) |v| result.editor = v;
+        if (ov.max_depth) |v| result.max_depth = v;
+        return result;
+    }
+    return base;
+}
+
 pub fn loadConfig(allocator: std.mem.Allocator) ?std.json.Parsed(Config) {
     const home = std.posix.getenv("HOME") orelse return null;
     const path = std.fs.path.join(allocator, &.{ home, ".config", "magdalena", "config.json" }) catch return null;
blob - 9466b39f6645858d5d72f5278de807e3d9a8efb4
blob + 22f1bfde3c9ca67e9f7abd2d7e41b262bf47fa61
--- src/cli.zig
+++ src/cli.zig
@@ -5,7 +5,7 @@ const io = @import("io.zig");
 pub const Action = enum {
     recent_dirs,
     recent_files,
-    fav_dirs,
+    favorites,
     search,
     goto_dir,
     goto_file,
@@ -79,8 +79,8 @@ pub fn parseArgs(allocator: std.mem.Allocator) !Args {
         res.action = .recent_dirs;
     } else if (std.mem.eql(u8, cmd_str, "recent-files")) {
         res.action = .recent_files;
-    } else if (std.mem.eql(u8, cmd_str, "fav-dirs")) {
-        res.action = .fav_dirs;
+    } else if (std.mem.eql(u8, cmd_str, "favorites")) {
+        res.action = .favorites;
     } else if (std.mem.eql(u8, cmd_str, "search")) {
         res.action = .search;
     } else if (std.mem.eql(u8, cmd_str, "goto-dir")) {
@@ -125,7 +125,7 @@ pub fn printUsage() !void {
         \\Usage:
         \\  magdalena recent-dirs
         \\  magdalena recent-files
-        \\  magdalena fav-dirs
+        \\  magdalena favorites
         \\  magdalena goto-dir
         \\  magdalena goto-file
         \\  magdalena log-dir <path>
blob - 1526c962e7f2772dadd64acd77bcb51390208003
blob + a7032b45bcb669d92a49a75a39f4b1a9ffc68d83
--- src/db.zig
+++ src/db.zig
@@ -75,7 +75,7 @@ pub const Db = struct {
         };
         defer file.close();
 
-        const content = try file.readToEndAlloc(self.allocator, std.math.maxInt(usize));
+        const content = try file.readToEndAlloc(self.allocator, 64 * 1024 * 1024);
         errdefer self.allocator.free(content);
 
         var arena = try self.allocator.create(std.heap.ArenaAllocator);
@@ -123,7 +123,7 @@ pub const Db = struct {
         };
         defer file.close();
 
-        const content = try file.readToEndAlloc(self.allocator, std.math.maxInt(usize));
+        const content = try file.readToEndAlloc(self.allocator, 64 * 1024 * 1024);
         errdefer self.allocator.free(content);
 
         var arena = try self.allocator.create(std.heap.ArenaAllocator);
@@ -222,12 +222,9 @@ pub const Db = struct {
         const abs_path = try self.resolvePath(dir_path);
         defer self.allocator.free(abs_path);
 
-        const file = std.fs.cwd().openFile(self.dirs_path, .{ .mode = .write_only }) catch |err| {
-            if (err == error.FileNotFound) {
-                _ = try std.fs.cwd().createFile(self.dirs_path, .{ .truncate = false });
-                return try self.logDir(dir_path);
-            }
-            return err;
+        const file = std.fs.cwd().openFile(self.dirs_path, .{ .mode = .write_only }) catch |err| switch (err) {
+            error.FileNotFound => try std.fs.cwd().createFile(self.dirs_path, .{ .truncate = false }),
+            else => return err,
         };
         defer file.close();
         try file.seekFromEnd(0);
@@ -238,8 +235,8 @@ pub const Db = struct {
         try now.format(&ts_writer);
         const ts = ts_buf[0..ts_writer.end];
 
-        var line_buf: [4096]u8 = undefined;
-        const line = try std.fmt.bufPrint(&line_buf, "{s}|{s}\n", .{ ts, abs_path });
+        const line = try std.fmt.allocPrint(self.allocator, "{s}|{s}\n", .{ ts, abs_path });
+        defer self.allocator.free(line);
         try file.writeAll(line);
     }
 
@@ -247,12 +244,9 @@ pub const Db = struct {
         const abs_path = try self.resolvePath(file_path);
         defer self.allocator.free(abs_path);
 
-        const file = std.fs.cwd().openFile(self.files_path, .{ .mode = .write_only }) catch |err| {
-            if (err == error.FileNotFound) {
-                _ = try std.fs.cwd().createFile(self.files_path, .{ .truncate = false });
-                return try self.logFile(file_path, file_type, action);
-            }
-            return err;
+        const file = std.fs.cwd().openFile(self.files_path, .{ .mode = .write_only }) catch |err| switch (err) {
+            error.FileNotFound => try std.fs.cwd().createFile(self.files_path, .{ .truncate = false }),
+            else => return err,
         };
         defer file.close();
         try file.seekFromEnd(0);
@@ -263,8 +257,8 @@ pub const Db = struct {
         try now.format(&ts_writer);
         const ts = ts_buf[0..ts_writer.end];
 
-        var line_buf: [4096]u8 = undefined;
-        const line = try std.fmt.bufPrint(&line_buf, "{s}|{s}|{s}|{s}\n", .{ ts, file_type, action, abs_path });
+        const line = try std.fmt.allocPrint(self.allocator, "{s}|{s}|{s}|{s}\n", .{ ts, file_type, action, abs_path });
+        defer self.allocator.free(line);
         try file.writeAll(line);
     }
 
@@ -277,8 +271,8 @@ pub const Db = struct {
         io.warn("Cleaning up {s}...\n", .{path});
 
         const file = try std.fs.cwd().openFile(path, .{});
-        const content = try file.readToEndAlloc(self.allocator, std.math.maxInt(usize));
-        file.close();
+        defer file.close();
+        const content = try file.readToEndAlloc(self.allocator, 64 * 1024 * 1024);
         defer self.allocator.free(content);
 
         var seen = std.StringHashMap(void).init(self.allocator);
blob - 50989ef8c80a2225b9e99a9dff9648184a88c400
blob + ddf61bef646fa749ba11af67d28f06350ed14dae
--- src/fzf.zig
+++ src/fzf.zig
@@ -22,6 +22,32 @@ fn gotoFile(allocator: std.mem.Allocator, db: ?*hist.D
         if (command != null) break;
     }
 
+    const tty = std.fs.openFileAbsolute("/dev/tty", .{ .mode = .read_write }) catch |err| blk: {
+        io.warn("failed to open /dev/tty: {}\n", .{err});
+        break :blk null;
+    };
+    defer if (tty) |t| t.close();
+
+    const saved_stdin = std.posix.dup(std.posix.STDIN_FILENO) catch |err| blk: {
+        io.warn("failed to save stdin: {}\n", .{err});
+        break :blk null;
+    };
+    defer if (saved_stdin) |fd| std.posix.close(fd);
+    const saved_stdout = std.posix.dup(std.posix.STDOUT_FILENO) catch |err| blk: {
+        io.warn("failed to save stdout: {}\n", .{err});
+        break :blk null;
+    };
+    defer if (saved_stdout) |fd| std.posix.close(fd);
+
+    if (tty) |t| {
+        std.posix.dup2(t.handle, std.posix.STDIN_FILENO) catch |err| io.warn("failed to redirect stdin to tty: {}\n", .{err});
+        std.posix.dup2(t.handle, std.posix.STDOUT_FILENO) catch |err| io.warn("failed to redirect stdout to tty: {}\n", .{err});
+    }
+    defer {
+        if (saved_stdin) |fd| std.posix.dup2(fd, std.posix.STDIN_FILENO) catch |err| io.warn("failed to restore stdin: {}\n", .{err});
+        if (saved_stdout) |fd| std.posix.dup2(fd, std.posix.STDOUT_FILENO) catch |err| io.warn("failed to restore stdout: {}\n", .{err});
+    }
+
     const term = if (command) |cmd| blk: {
         var child = std.process.Child.init(&.{ cmd, file_path }, allocator);
         child.stdin_behavior = .Inherit;
@@ -98,6 +124,7 @@ const Fzf = struct {
     }
 };
 
+// ignored_patterns entries are matched by exact path component name, not globs.
 fn shouldSkip(path: []const u8, ignored_patterns: []const []const u8) bool {
     var it = std.mem.splitScalar(u8, path, std.fs.path.sep);
     while (it.next()) |component| {
@@ -190,9 +217,9 @@ pub fn recentDir(allocator: std.mem.Allocator, db: *hi
     try out.end();
 }
 
-pub fn favDirs(allocator: std.mem.Allocator, config: *const cfg.Config) !void {
-    if (config.fav_dirs.len == 0) {
-        io.warn("No favorite directories found in config.\n", .{});
+pub fn favorites(allocator: std.mem.Allocator, db: *hist.Db, config: *const cfg.Config) !void {
+    if (config.favorites.len == 0) {
+        io.warn("No favorites found in config.\n", .{});
         return;
     }
 
@@ -202,18 +229,29 @@ pub fn favDirs(allocator: std.mem.Allocator, config: *
         items.deinit(allocator);
     }
 
-    for (config.fav_dirs) |dir| {
-        const expanded = try expandPath(allocator, dir);
+    for (config.favorites) |entry| {
+        const expanded = try expandPath(allocator, entry);
         try items.append(allocator, expanded);
     }
 
     const selected = try runFzfPicker(allocator, config.fzf_opts, items.items);
     defer allocator.free(selected);
 
-    var out_buf: [4096]u8 = undefined;
-    var out = io.getStdout(&out_buf);
-    try out.print("{s}\n", .{selected});
-    try out.end();
+    const is_dir = blk: {
+        var dir = std.fs.openDirAbsolute(selected, .{}) catch break :blk false;
+        dir.close();
+        break :blk true;
+    };
+
+    if (is_dir) {
+        try db.logDir(selected);
+        var out_buf: [4096]u8 = undefined;
+        var out = io.getStdout(&out_buf);
+        try out.print("{s}\n", .{selected});
+        try out.end();
+    } else {
+        try gotoFile(allocator, db, selected, null, config);
+    }
 }
 
 fn expandPath(allocator: std.mem.Allocator, path: []const u8) ![]const u8 {
blob - 364f5c862db1c24fdcad9eb47d5e04a21b292543
blob + 84361b7c1a1a193e31e1e9d93af1a346d8fb826f
--- src/main.zig
+++ src/main.zig
@@ -25,8 +25,15 @@ pub fn main() void {
     const parsed_config = cfg.loadConfig(allocator);
     defer if (parsed_config) |c| c.deinit();
     const default_config = cfg.Config{};
-    const config = if (parsed_config) |c| c.value else default_config;
+    const base_config = if (parsed_config) |c| c.value else default_config;
 
+    var cwd_buf: [std.fs.max_path_bytes]u8 = undefined;
+    const cwd = std.process.getCwd(&cwd_buf) catch |err| blk: {
+        io.warn("failed to get cwd for folder overrides: {}\n", .{err});
+        break :blk "";
+    };
+    const config = cfg.applyFolderOverride(base_config, cwd);
+
     const db_path = hist.getDefaultDbPath(allocator) catch |err| {
         io.fatal("failed to get database path: {}", .{err});
     };
@@ -64,8 +71,8 @@ fn runAction(allocator: std.mem.Allocator, db: *hist.D
                 try writer.print("{s}|{s}|{s}|{s}\n", .{ f.timestamp orelse "-", f.file_type, f.action, f.path });
             }
         },
-        .fav_dirs => {
-            try fzf.favDirs(allocator, config);
+        .favorites => {
+            try fzf.favorites(allocator, db, config);
         },
         .search => {
             if (args.query) |query| {