From 76582c1ee95c5e5e97168799ed20fc1f6141bb12 Mon Sep 17 00:00:00 2001 From: Bernardo Magri Date: Sat, 13 Jun 2026 14:32:11 +0100 Subject: [PATCH] =?UTF-8?q?feat:=20file=20manager=20=E2=80=94=20yazi=20(fl?= =?UTF-8?q?agship=20TUI)=20+=20Thunar=20(GUI)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Legacy shipped only Thunar (and a stray nautilus extension, mid-migration). This pairs a keyboard-driven TUI flagship that fits the distro's identity with the GUI half for point-and-click + the "open folder" handler. yazi (modules/home/yazi.nix, nomarchy.yazi.enable, SUPER+E, `y` shell wrapper cd's on exit): themed from theme-state.json (mgr/mode/status mapped from the palette; merges over yazi's defaults so it tracks the theme) with a curated, correctly-wired plugin set — full-border, git (setup + prepend_fetchers), smart-enter (l/Enter), chmod (cm), mount (M), compress (caa), mediainfo (audio/subtitle only — images/videos keep native thumbnails), glow (markdown previews). Preview/tooling deps: ffmpegthumbnailer, poppler-utils, p7zip, jq, fd, ripgrep, fzf, chafa, mediainfo, glow. Thunar (modules/nixos/file-manager.nix, nomarchy.system.fileManager.enable): GTK file manager (Stylix-themed) + archive/volman/media-tags plugins + gvfs/tumbler/udisks2 backends + thumbnailers + exo. udisks2 also backs yazi's mount plugin. Verified: flake check green, HM generation builds (yazi TOML/init.lua render, all 8 plugins resolve), every package present in the live ISO closure (offline install draws from it — no source-build leak). Not verified: yazi's runtime preview/plugin behaviour needs a real session (TUI; can't drive in CI here). Co-Authored-By: Claude Fable 5 --- README.md | 12 +++- modules/home/default.nix | 1 + modules/home/hyprland.nix | 1 + modules/home/options.nix | 1 + modules/home/yazi.nix | 126 +++++++++++++++++++++++++++++++++ modules/nixos/default.nix | 2 +- modules/nixos/file-manager.nix | 35 +++++++++ modules/nixos/options.nix | 6 ++ 8 files changed, 180 insertions(+), 4 deletions(-) create mode 100644 modules/home/yazi.nix create mode 100644 modules/nixos/file-manager.nix diff --git a/README.md b/README.md index 450ff6a..33dd0a1 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,9 @@ Flat on purpose. Two module trees, one options file each, no hidden layers. ├── modules/ │ ├── nixos/ # the distro, system side │ │ ├── default.nix # Hyprland session, Pipewire, greetd, fonts -│ │ └── options.nix # nomarchy.system.* toggles +│ │ ├── options.nix # nomarchy.system.* toggles +│ │ ├── plymouth.nix # boot splash, tinted from the JSON +│ │ └── file-manager.nix # Thunar GUI + gvfs/tumbler/udisks2 │ └── home/ # the distro, user side │ ├── default.nix # entry point │ ├── options.nix # nomarchy.* option surface @@ -57,7 +59,8 @@ Flat on purpose. Two module trees, one options file each, no hidden layers. │ ├── btop.nix │ ├── fuzzel.nix # launcher + nomarchy-menu (power, clip) │ ├── swaync.nix # notifications, same JSON -│ └── idle.nix # hyprlock + hypridle, same JSON +│ ├── idle.nix # hyprlock + hypridle, same JSON +│ └── yazi.nix # flagship TUI file manager + plugins ├── hosts/ │ ├── default/ # reference machine (thin: boot, user, hostname) │ └── live.nix # bootable live ISO (try the distro, no install) @@ -172,11 +175,13 @@ examples: **[docs/OVERRIDES.md](docs/OVERRIDES.md)**. | `nomarchy.fuzzel.enable` | `true` | Themed fuzzel + `nomarchy-menu` (power menu) | | `nomarchy.swaync.enable` | `true` | swaync notifications, themed | | `nomarchy.idle.enable` | `true` | hyprlock + hypridle (idle lock 5 min, display off 10, suspend 30) | +| `nomarchy.yazi.enable` | `true` | yazi TUI file manager, themed + curated plugins | | `nomarchy.ghostty.enable` | `true` | Nomarchy's Ghostty | | `nomarchy.btop.enable` | `true` | btop with per-theme colors | | `nomarchy.stylix.enable` | `true` | GTK/Qt/cursor theming | | `nomarchy.themesDir` | Nomarchy's `themes/` | Where per-theme app overrides are probed | | `nomarchy.system.plymouth.enable` | `true` | Branded boot splash, background from the theme JSON (recolors on system rebuilds) | +| `nomarchy.system.fileManager.enable` | `true` | Thunar GUI + gvfs/tumbler/udisks2 (the "open folder" handler) | | `nomarchy.system.greeter.enable` | `true` | greetd/tuigreet | | `nomarchy.system.audio.enable` | `true` | Pipewire stack | | `nomarchy.system.bluetooth.enable` | `true` | Bluetooth + blueman | @@ -241,7 +246,8 @@ home-update # rebuild just the desktop layer Keybinds: `SUPER+Return` terminal · `SUPER+D` launcher · `SUPER+T` theme picker · `SUPER+SHIFT+T` next wallpaper · `SUPER+X` power menu · -`SUPER+N` notifications · `SUPER+CTRL+V` clipboard history · `SUPER+Q` +`SUPER+E` file manager (yazi) · `SUPER+N` notifications · `SUPER+CTRL+V` +clipboard history · `SUPER+Q` close · `SUPER+1..9` workspaces · `Print` region screenshot. ## 6. Extending diff --git a/modules/home/default.nix b/modules/home/default.nix index e33dd6c..93419af 100644 --- a/modules/home/default.nix +++ b/modules/home/default.nix @@ -15,6 +15,7 @@ ./fuzzel.nix # launcher theming + the nomarchy-menu dispatcher ./swaync.nix # notification daemon, themed from the same JSON ./idle.nix # hyprlock + hypridle, themed from the same JSON + ./yazi.nix # flagship TUI file manager, themed + plugins ]; # Clipboard history (wl-paste watcher); browsed via the SUPER+CTRL+V diff --git a/modules/home/hyprland.nix b/modules/home/hyprland.nix index 542e7fd..5b30454 100644 --- a/modules/home/hyprland.nix +++ b/modules/home/hyprland.nix @@ -128,6 +128,7 @@ in bind = [ "$mod, Return, exec, $terminal" "$mod, D, exec, fuzzel" + "$mod, E, exec, $terminal -e yazi" "$mod, Q, killactive" "$mod, F, fullscreen" "$mod, V, togglefloating" diff --git a/modules/home/options.nix b/modules/home/options.nix index 5f3aab0..ea7118d 100644 --- a/modules/home/options.nix +++ b/modules/home/options.nix @@ -66,6 +66,7 @@ fuzzel.enable = lib.mkEnableOption "Nomarchy's themed fuzzel + the nomarchy-menu dispatcher (power menu)" // { default = true; }; swaync.enable = lib.mkEnableOption "swaync notifications, themed from the state file" // { default = true; }; idle.enable = lib.mkEnableOption "hyprlock + hypridle (idle lock, display off, suspend)" // { default = true; }; + yazi.enable = lib.mkEnableOption "the yazi TUI file manager, themed with a curated plugin set" // { default = true; }; ghostty.enable = lib.mkEnableOption "Nomarchy's Ghostty configuration" // { default = true; }; btop.enable = lib.mkEnableOption "btop with the per-theme nomarchy theme" // { default = true; }; stylix.enable = lib.mkEnableOption "Stylix theming for the long tail of apps (GTK, Qt, cursors)" // { default = true; }; diff --git a/modules/home/yazi.nix b/modules/home/yazi.nix new file mode 100644 index 0000000..03749e1 --- /dev/null +++ b/modules/home/yazi.nix @@ -0,0 +1,126 @@ +# yazi — the flagship file manager: a fast, keyboard-driven TUI that +# fits the distro's identity (Ghostty's Kitty-graphics previews, fuzzel +# menus, everything from one JSON). Themed from theme-state.json and +# shipped with a curated plugin set. The GUI half (Thunar, "open folder" +# handler) is nomarchy.system.fileManager on the system side. +# +# `y` (the shell wrapper) cd's the shell to yazi's last directory on +# exit; SUPER+E opens it in a fresh terminal. +{ config, lib, pkgs, ... }: + +let + cfg = config.nomarchy; + t = cfg.theme; + c = t.colors; +in +{ + config = lib.mkIf cfg.yazi.enable { + # Preview + tooling deps yazi shells out to (thumbnails, search, + # archives, the plugins below). All small CLIs. + home.packages = with pkgs; [ + ffmpegthumbnailer # video thumbnails + poppler-utils # PDF previews (pdftoppm) + p7zip # archive preview + the compress plugin + jq # JSON preview + fd ripgrep fzf # navigation / search integrations + chafa # image fallback when the terminal lacks graphics + mediainfo # mediainfo plugin (audio/subtitle metadata) + glow # glow plugin (markdown rendering) + ]; + + programs.yazi = { + enable = true; + shellWrapperName = "y"; # `y` cd's on exit; `yazi` stays a no-cd launcher + enableBashIntegration = true; + + settings = { + mgr = { + show_hidden = false; + sort_by = "natural"; + sort_dir_first = true; + linemode = "size"; + }; + preview = { + max_width = 1000; + max_height = 1000; + }; + plugin = { + # git status in the linemode (fetcher registered before setup). + prepend_fetchers = [ + { id = "git"; url = "*"; run = "git"; } + { id = "git"; url = "*/"; run = "git"; } + ]; + # Route only audio + subtitles to mediainfo's text metadata; + # images/videos keep yazi's native visual thumbnails. Markdown + # renders through glow. + prepend_previewers = [ + { name = "*.md"; run = "glow"; } + { mime = "audio/*"; run = "mediainfo"; } + { mime = "application/subrip"; run = "mediainfo"; } + ]; + }; + }; + + keymap.mgr.prepend_keymap = [ + { on = "l"; run = "plugin smart-enter"; desc = "Enter dir or open file"; } + { on = ""; run = "plugin smart-enter"; desc = "Enter dir or open file"; } + { on = [ "c" "m" ]; run = "plugin chmod"; desc = "Chmod on selected files"; } + { on = "M"; run = "plugin mount"; desc = "Mount/unmount manager"; } + { on = [ "c" "a" "a" ]; run = "plugin compress"; desc = "Archive selected files"; } + ]; + + # Curated plugins (nixpkgs yaziPlugins). setup = true emits the + # require(...):setup() call in init.lua; the rest are invoked via + # the keymap/previewer/fetcher wiring above. + plugins = { + full-border = { package = pkgs.yaziPlugins.full-border; setup = true; }; + git = { package = pkgs.yaziPlugins.git; setup = true; }; + smart-enter = { package = pkgs.yaziPlugins.smart-enter; }; + chmod = { package = pkgs.yaziPlugins.chmod; }; + mount = { package = pkgs.yaziPlugins.mount; }; + compress = { package = pkgs.yaziPlugins.compress; }; + mediainfo = { package = pkgs.yaziPlugins.mediainfo; }; + glow = { package = pkgs.yaziPlugins.glow; }; + }; + + # Theme from the palette. yazi merges this over its built-in theme, + # so only the accent-bearing UI is specified; everything else keeps + # yazi's defaults (which already follow Ghostty's ANSI colors). + theme = { + mgr = { + cwd = { fg = c.accent; }; + hovered = { fg = c.base; bg = c.accent; }; + preview_hovered = { underline = true; }; + find_keyword = { fg = c.warn; italic = true; }; + find_position = { fg = c.accentAlt; bg = "reset"; italic = true; }; + marker_copied = { fg = c.good; bg = c.good; }; + marker_cut = { fg = c.bad; bg = c.bad; }; + marker_marked = { fg = c.accent; bg = c.accent; }; + marker_selected = { fg = c.warn; bg = c.warn; }; + count_copied = { fg = c.base; bg = c.good; }; + count_cut = { fg = c.base; bg = c.bad; }; + count_selected = { fg = c.base; bg = c.warn; }; + border_symbol = "│"; + border_style = { fg = c.muted; }; + }; + mode = { + normal_main = { fg = c.base; bg = c.accent; bold = true; }; + normal_alt = { fg = c.accent; bg = c.surface; }; + select_main = { fg = c.base; bg = c.good; bold = true; }; + select_alt = { fg = c.good; bg = c.surface; }; + unset_main = { fg = c.base; bg = c.accentAlt; bold = true; }; + unset_alt = { fg = c.accentAlt; bg = c.surface; }; + }; + status = { + perm_type = { fg = c.accent; }; + perm_read = { fg = c.good; }; + perm_write = { fg = c.bad; }; + perm_exec = { fg = c.warn; }; + progress_label = { fg = c.text; bold = true; }; + progress_normal = { fg = c.accent; bg = c.surface; }; + progress_error = { fg = c.bad; bg = c.surface; }; + }; + }; + }; + }; +} diff --git a/modules/nixos/default.nix b/modules/nixos/default.nix index 4beaa52..286b1bb 100644 --- a/modules/nixos/default.nix +++ b/modules/nixos/default.nix @@ -11,7 +11,7 @@ let cfg = config.nomarchy.system; in { - imports = [ ./options.nix ./plymouth.nix ]; + imports = [ ./options.nix ./plymouth.nix ./file-manager.nix ]; config = { # The safe half of distro branding: distroName flows into diff --git a/modules/nixos/file-manager.nix b/modules/nixos/file-manager.nix new file mode 100644 index 0000000..33a46e2 --- /dev/null +++ b/modules/nixos/file-manager.nix @@ -0,0 +1,35 @@ +# GUI file manager — Thunar (lightweight GTK, Stylix-themed) plus the +# backend services a standalone compositor needs for it to work fully: +# gvfs (trash/mount/network), tumbler (thumbnails), udisks2 (removable +# media — also what yazi's mount plugin drives). The keyboard-driven TUI +# flagship is yazi (modules/home/yazi.nix); this is the point-and-click +# half and the XDG handler for "open folder" / "show in files". +{ config, lib, pkgs, ... }: + +let + cfg = config.nomarchy.system; +in +{ + config = lib.mkIf cfg.fileManager.enable { + programs.thunar = { + enable = lib.mkDefault true; + plugins = with pkgs.xfce; [ + thunar-archive-plugin # create/extract archives from the context menu + thunar-volman # auto-manage removable drives/media + thunar-media-tags-plugin # rename/inspect audio by tags + ]; + }; + + services.gvfs.enable = lib.mkDefault true; # trash, mounts, network shares + services.tumbler.enable = lib.mkDefault true; # thumbnail generation + services.udisks2.enable = lib.mkDefault true; # mount/unmount removable media + + environment.systemPackages = with pkgs; [ + ffmpegthumbnailer # video thumbnails (Thunar + yazi) + libgsf # ODF thumbnails + poppler-utils # PDF thumbnails / pdftoppm (yazi PDF preview too) + xfce.exo # "Open Terminal Here" and default-app associations + shared-mime-info # MIME database + ]; + }; +} diff --git a/modules/nixos/options.nix b/modules/nixos/options.nix index 9ed548e..10634e8 100644 --- a/modules/nixos/options.nix +++ b/modules/nixos/options.nix @@ -37,6 +37,12 @@ ''; }; + fileManager.enable = lib.mkEnableOption '' + the Thunar GUI file manager + backend services (gvfs/tumbler/udisks2) + for point-and-click file management and the "open folder" handler. + The keyboard-driven TUI flagship (yazi) is the nomarchy.yazi.* home + option'' // { default = true; }; + audio.enable = lib.mkEnableOption "the Pipewire audio stack" // { default = true; }; bluetooth.enable = lib.mkEnableOption "Bluetooth support with blueman" // { default = true; };