diff --git a/modules/modules.nix b/modules/modules.nix
index 7d3b54043..cf485d952 100644
--- a/modules/modules.nix
+++ b/modules/modules.nix
@@ -238,6 +238,7 @@ let
./programs/sqls.nix
./programs/ssh.nix
./programs/starship.nix
+ ./programs/superfile.nix
./programs/swayimg.nix
./programs/swaylock.nix
./programs/swayr.nix
diff --git a/modules/programs/superfile.nix b/modules/programs/superfile.nix
new file mode 100644
index 000000000..06937ae6a
--- /dev/null
+++ b/modules/programs/superfile.nix
@@ -0,0 +1,125 @@
+{ config, lib, pkgs, ... }:
+
+let
+ cfg = config.programs.superfile;
+ tomlFormat = pkgs.formats.toml { };
+ inherit (pkgs.stdenv.hostPlatform) isDarwin;
+ inherit (lib)
+ literalExpression mapAttrs' mkEnableOption mkIf mkMerge mkOption
+ mkPackageOption nameValuePair recursiveUpdate types hm;
+in {
+ meta.maintainers = [ hm.maintainers.LucasWagler ];
+
+ options.programs.superfile = {
+ enable = mkEnableOption
+ "superfile - Pretty fancy and modern terminal file manager";
+
+ package = mkPackageOption pkgs "superfile" { nullable = true; };
+
+ settings = mkOption {
+ type = tomlFormat.type;
+ default = { };
+ description = ''
+ Configuration written to {file}`$XDG_CONFIG_HOME/superfile/config.toml`
+ (linux) or {file}`Library/Application Support/superfile/config.toml` (darwin), See
+ for supported values.
+ '';
+ example = literalExpression ''
+ theme = "catppuccin-frappe";
+ default_sort_type = 0;
+ transparent_background = false;
+ '';
+ };
+
+ hotkeys = mkOption {
+ type = tomlFormat.type;
+ default = { };
+ description = ''
+ Hotkey configuration written to {file}`$XDG_CONFIG_HOME/superfile/hotkeys.toml`
+ (linux) or {file}`Library/Application Support/superfile/hotkeys.toml` (darwin), See
+ for supported values.
+ '';
+ example = literalExpression ''
+ confirm = [
+ "enter"
+ "right"
+ "l"
+ ];
+ '';
+ };
+
+ themes = mkOption {
+ type = with types; attrsOf (either tomlFormat.type path);
+ default = { };
+ description = ''
+ Theme files written to {file}`$XDG_CONFIG_HOME/superfile/theme/`
+ (linux) or {file}`Library/Application Support/superfile/theme/` (darwin), See
+ for supported values.
+ '';
+ example = literalExpression ''
+ myTheme = {
+ code_syntax_highlight = "catppuccin-latte";
+
+ file_panel_border = "#101010";
+ sidebar_border = "#101011";
+ footer_border = "#101012";
+
+ gradient_color = [
+ "#101013"
+ "#101014"
+ ];
+
+ # ...
+ };
+ myOtherFavoriteTheme = {
+ code_syntax_highlight = "catppuccin-mocha";
+
+ file_panel_border = "#505050";
+ sidebar_border = "#505051";
+ footer_border = "#505052";
+
+ gradient_color = [
+ "#505053"
+ "#505054"
+ ];
+
+ # ...
+ };
+ '';
+ };
+ };
+
+ config = let
+ enableXdgConfig = !isDarwin || config.xdg.enable;
+ themeSetting = if (!(cfg.settings ? theme) && cfg.themes != { }) then {
+ theme = "${builtins.elemAt (builtins.attrNames cfg.themes) 0}";
+ } else
+ { };
+ baseConfigPath = if enableXdgConfig then
+ "superfile"
+ else
+ "Library/Application Support/superfile";
+ configFile = mkIf (cfg.settings != { }) {
+ "${baseConfigPath}/config.toml".source =
+ tomlFormat.generate "superfile-config.toml"
+ (recursiveUpdate themeSetting cfg.settings);
+ };
+ hotkeysFile = mkIf (cfg.hotkeys != { }) {
+ "${baseConfigPath}/hotkeys.toml".source =
+ tomlFormat.generate "superfile-hotkeys.toml" (cfg.hotkeys);
+ };
+ themeFiles = mapAttrs' (name: value:
+ nameValuePair "${baseConfigPath}/theme/${name}.toml" {
+ source = if types.path.check value then
+ value
+ else
+ (tomlFormat.generate "superfile-theme-${name}.toml" value);
+ }) cfg.themes;
+ configFiles = mkMerge [ configFile hotkeysFile themeFiles ];
+ in mkIf cfg.enable {
+ home.packages = mkIf (cfg.package != null) [ cfg.package ];
+
+ xdg.configFile = mkIf enableXdgConfig configFiles;
+ home.file = mkIf (!enableXdgConfig) configFiles;
+ };
+}
diff --git a/tests/default.nix b/tests/default.nix
index 94a993da4..8c6244df5 100644
--- a/tests/default.nix
+++ b/tests/default.nix
@@ -294,6 +294,7 @@ in import nmtSrc {
./modules/programs/spotify-player
./modules/programs/ssh
./modules/programs/starship
+ ./modules/programs/superfile
./modules/programs/taskwarrior
./modules/programs/tealdeer
./modules/programs/texlive
diff --git a/tests/modules/programs/superfile/default.nix b/tests/modules/programs/superfile/default.nix
new file mode 100644
index 000000000..373700b4d
--- /dev/null
+++ b/tests/modules/programs/superfile/default.nix
@@ -0,0 +1,5 @@
+{
+ superfile-example-settings = ./example-settings.nix;
+ superfile-empty-settings = ./empty-settings.nix;
+ superfile-partial-theme-settings = ./partial-theme-settings.nix;
+}
diff --git a/tests/modules/programs/superfile/empty-settings.nix b/tests/modules/programs/superfile/empty-settings.nix
new file mode 100644
index 000000000..6a6729763
--- /dev/null
+++ b/tests/modules/programs/superfile/empty-settings.nix
@@ -0,0 +1,16 @@
+{ pkgs, lib, ... }:
+
+{
+ programs.superfile.enable = true;
+
+ xdg.enable = lib.mkIf pkgs.stdenv.isDarwin false;
+
+ nmt.script = let
+ configDir = if !pkgs.stdenv.isDarwin then
+ ".config/superfile"
+ else
+ "Library/Application Support/superfile";
+ in ''
+ assertPathNotExists home-files/${configDir}
+ '';
+}
diff --git a/tests/modules/programs/superfile/example-config-expected.toml b/tests/modules/programs/superfile/example-config-expected.toml
new file mode 100644
index 000000000..2b6fdca16
--- /dev/null
+++ b/tests/modules/programs/superfile/example-config-expected.toml
@@ -0,0 +1,3 @@
+default_sort_type = 0
+theme = "catppuccin-frappe"
+transparent_background = false
diff --git a/tests/modules/programs/superfile/example-hotkeys-expected.toml b/tests/modules/programs/superfile/example-hotkeys-expected.toml
new file mode 100644
index 000000000..75a7db972
--- /dev/null
+++ b/tests/modules/programs/superfile/example-hotkeys-expected.toml
@@ -0,0 +1 @@
+confirm = ["enter", "right", "l"]
diff --git a/tests/modules/programs/superfile/example-settings.nix b/tests/modules/programs/superfile/example-settings.nix
new file mode 100644
index 000000000..75fdd5a78
--- /dev/null
+++ b/tests/modules/programs/superfile/example-settings.nix
@@ -0,0 +1,69 @@
+{ config, pkgs, lib, ... }:
+
+{
+ xdg.enable = lib.mkIf pkgs.stdenv.isDarwin false;
+
+ programs.superfile = {
+ enable = true;
+ package = config.lib.test.mkStubPackage { };
+
+ settings = {
+ theme = "catppuccin-frappe";
+ default_sort_type = 0;
+ transparent_background = false;
+ };
+ hotkeys = { confirm = [ "enter" "right" "l" ]; };
+ themes = {
+ test0 = {
+ code_syntax_highlight = "catppuccin-latte";
+
+ file_panel_border = "#101010";
+ sidebar_border = "#101011";
+ footer_border = "#101012";
+
+ gradient_color = [ "#101013" "#101014" ];
+ };
+
+ test1 = ./example-theme-expected.toml;
+
+ test2 = {
+ code_syntax_highlight = "catppuccin-frappe";
+
+ file_panel_border = "#202020";
+ sidebar_border = "#202021";
+ footer_border = "#202022";
+
+ gradient_color = [ "#202023" "#202024" ];
+ };
+ };
+ };
+
+ nmt.script = let
+ configSubPath = if !pkgs.stdenv.isDarwin then
+ ".config/superfile"
+ else
+ "Library/Application Support/superfile";
+ configBasePath = "home-files/" + configSubPath;
+ in ''
+ assertFileExists "${configBasePath}/config.toml"
+ assertFileContent \
+ "${configBasePath}/config.toml" \
+ ${./example-config-expected.toml}
+ assertFileExists "${configBasePath}/hotkeys.toml"
+ assertFileContent \
+ "${configBasePath}/hotkeys.toml" \
+ ${./example-hotkeys-expected.toml}
+ assertFileExists "${configBasePath}/theme/test0.toml"
+ assertFileContent \
+ "${configBasePath}/theme/test0.toml" \
+ ${./example-theme-expected.toml}
+ assertFileExists "${configBasePath}/theme/test1.toml"
+ assertFileContent \
+ "${configBasePath}/theme/test1.toml" \
+ ${./example-theme-expected.toml}
+ assertFileExists "${configBasePath}/theme/test2.toml"
+ assertFileContent \
+ "${configBasePath}/theme/test2.toml" \
+ ${./example-theme2-expected.toml}
+ '';
+}
diff --git a/tests/modules/programs/superfile/example-theme-expected.toml b/tests/modules/programs/superfile/example-theme-expected.toml
new file mode 100644
index 000000000..4a117c459
--- /dev/null
+++ b/tests/modules/programs/superfile/example-theme-expected.toml
@@ -0,0 +1,5 @@
+code_syntax_highlight = "catppuccin-latte"
+file_panel_border = "#101010"
+footer_border = "#101012"
+gradient_color = ["#101013", "#101014"]
+sidebar_border = "#101011"
diff --git a/tests/modules/programs/superfile/example-theme2-expected.toml b/tests/modules/programs/superfile/example-theme2-expected.toml
new file mode 100644
index 000000000..81280fd31
--- /dev/null
+++ b/tests/modules/programs/superfile/example-theme2-expected.toml
@@ -0,0 +1,5 @@
+code_syntax_highlight = "catppuccin-frappe"
+file_panel_border = "#202020"
+footer_border = "#202022"
+gradient_color = ["#202023", "#202024"]
+sidebar_border = "#202021"
diff --git a/tests/modules/programs/superfile/partial-theme-settings-expected.toml b/tests/modules/programs/superfile/partial-theme-settings-expected.toml
new file mode 100644
index 000000000..c13337a9d
--- /dev/null
+++ b/tests/modules/programs/superfile/partial-theme-settings-expected.toml
@@ -0,0 +1,2 @@
+theme = "test0"
+transparent_background = false
diff --git a/tests/modules/programs/superfile/partial-theme-settings.nix b/tests/modules/programs/superfile/partial-theme-settings.nix
new file mode 100644
index 000000000..81a6066cf
--- /dev/null
+++ b/tests/modules/programs/superfile/partial-theme-settings.nix
@@ -0,0 +1,44 @@
+# When not specified in `programs.superfile.settings.theme`,
+# test that the first skin name (alphabetically) is used in the config file
+{ pkgs, lib, ... }: {
+ xdg.enable = lib.mkIf pkgs.stdenv.isDarwin false;
+
+ programs.superfile = {
+ enable = true;
+ settings = { transparent_background = false; };
+ themes = {
+ test2 = {
+ code_syntax_highlight = "catppuccin-frappe";
+
+ file_panel_border = "#202020";
+ sidebar_border = "#202021";
+ footer_border = "#202022";
+
+ gradient_color = [ "#202023" "#202024" ];
+ };
+
+ test0 = {
+ code_syntax_highlight = "catppuccin-latte";
+
+ file_panel_border = "#101010";
+ sidebar_border = "#101011";
+ footer_border = "#101012";
+
+ gradient_color = [ "#101013" "#101014" ];
+ };
+ };
+ };
+
+ nmt.script = let
+ configSubPath = if !pkgs.stdenv.isDarwin then
+ ".config/superfile"
+ else
+ "Library/Application Support/superfile";
+ configBasePath = "home-files/" + configSubPath;
+ in ''
+ assertFileExists "${configBasePath}/config.toml"
+ assertFileContent \
+ "${configBasePath}/config.toml" \
+ ${./partial-theme-settings-expected.toml}
+ '';
+}