diff --git a/nix/hm-module.nix b/nix/hm-module.nix index be96042..ae3095c 100644 --- a/nix/hm-module.nix +++ b/nix/hm-module.nix @@ -2,325 +2,14 @@ let cfg = config.programs.shed; - boolToString = b: - if b then "true" else "false"; - - mkAutoCmd = cfg: - lib.concatLines (map (hook: "autocmd ${hook} ${lib.optionalString (cfg.pattern != null) "-p \"${cfg.pattern}\""} '${cfg.command}'") cfg.hooks); - - - mkFunctionDef = name: body: - let - indented = "\t" + lib.concatStringsSep "\n\t" (lib.splitString "\n" body); - in - '' -${name}() { -${indented} -}''; - - mkKeymapCmd = cfg: let - flags = "-${lib.concatStrings cfg.modes}"; - keys = "'${cfg.keys}'"; - action = "'${cfg.command}'"; - in - "keymap ${flags} ${keys} ${action}"; - - - mkCompleteCmd = name: cfg: let - flags = lib.concatStrings [ - (lib.optionalString cfg.files " -f") - (lib.optionalString cfg.dirs " -d") - (lib.optionalString cfg.commands " -c") - (lib.optionalString cfg.variables " -v") - (lib.optionalString cfg.users " -u") - (lib.optionalString cfg.jobs " -j") - (lib.optionalString cfg.aliases " -a") - (lib.optionalString cfg.signals " -S") - (lib.optionalString cfg.noSpace " -n") - (lib.optionalString (cfg.function != null) " -F ${cfg.function}") - (lib.optionalString (cfg.fallback != "no") " -o ${cfg.fallback}") - (lib.optionalString (cfg.wordList != []) " -W '${lib.concatStringsSep " " cfg.wordList}'") - - ]; - in "complete${flags} ${name}"; in { - options.programs.shed = { - enable = lib.mkEnableOption "shed shell"; - - package = lib.mkOption { - type = lib.types.package; - default = pkgs.shed; - description = "The shed package to use"; - }; - - aliases = lib.mkOption { - type = lib.types.attrsOf lib.types.str; - default = {}; - description = "Aliases to set when shed starts"; - }; - - functions = lib.mkOption { - type = lib.types.attrsOf lib.types.str; - default = {}; - description = "Shell functions to set when shed starts"; - }; - - autocmds = lib.mkOption { - type = lib.types.listOf (lib.types.submodule { - options = { - hooks = lib.mkOption { - type = lib.types.addCheck (lib.types.listOf (lib.types.enum [ - "pre-cmd" - "post-cmd" - "pre-change-dir" - "post-change-dir" - "on-job-finish" - "pre-prompt" - "post-prompt" - "pre-mode-change" - "post-mode-change" - "on-exit" - "on-history-open" - "on-history-close" - "on-history-select" - "on-completion-start" - "on-completion-cancel" - "on-completion-select" - ])) (list: list != []); - description = "The events that trigger this autocmd"; - }; - pattern = lib.mkOption { - type = lib.types.nullOr lib.types.str; - default = null; - description = "A regex pattern to use in the hook to determine whether it runs or not. What it's compared to differs by hook, for instance 'pre-change-dir' compares it to the new directory, pre-cmd compares it to the command, etc"; - }; - command = lib.mkOption { - type = lib.types.addCheck lib.types.str (cmd: cmd != ""); - description = "The shell command to execute when the hook is triggered and the pattern (if provided) matches"; - }; - }; - - }); - default = []; - description = "Custom autocmds to set when shed starts"; - }; - - keymaps = lib.mkOption { - type = lib.types.listOf (lib.types.submodule { - options = { - modes = lib.mkOption { - type = lib.types.listOf (lib.types.enum [ "n" "i" "x" "v" "o" "r" ]); - default = []; - description = "The editing modes this keymap can be used in"; - }; - keys = lib.mkOption { - type = lib.types.str; - default = ""; - description = "The sequence of keys that trigger this keymap"; - }; - command = lib.mkOption { - type = lib.types.str; - default = ""; - description = "The sequence of characters to send to the line editor when the keymap is triggered."; - }; - }; - }); - default = []; - description = "Custom keymaps to set when shed starts"; - }; - - extraCompletion = lib.mkOption { - type = lib.types.attrsOf (lib.types.submodule { - options = { - files = lib.mkOption { - type = lib.types.bool; - default = false; - description = "Complete file names in the current directory"; - }; - dirs = lib.mkOption { - type = lib.types.bool; - default = false; - description = "Complete directory names in the current directory"; - }; - commands = lib.mkOption { - type = lib.types.bool; - default = false; - description = "Complete executable commands in the PATH"; - }; - variables = lib.mkOption { - type = lib.types.bool; - default = false; - description = "Complete variable names"; - }; - users = lib.mkOption { - type = lib.types.bool; - default = false; - description = "Complete user names from /etc/passwd"; - }; - jobs = lib.mkOption { - type = lib.types.bool; - default = false; - description = "Complete job names or pids from the current shell session"; - }; - aliases = lib.mkOption { - type = lib.types.bool; - default = false; - description = "Complete alias names defined in the current shell session"; - }; - signals = lib.mkOption { - type = lib.types.bool; - default = false; - description = "Complete signal names for commands like kill"; - }; - wordList = lib.mkOption { - type = lib.types.listOf lib.types.str; - default = []; - description = "Complete from a custom list of words"; - }; - function = lib.mkOption { - type = lib.types.nullOr lib.types.str; - default = null; - description = "Complete using a custom shell function (should be defined in extraCompletionPreConfig)"; - }; - noSpace = lib.mkOption { - type = lib.types.bool; - default = false; - description = "Don't append a space after completion"; - }; - fallback = lib.mkOption { - type = lib.types.enum [ "no" "default" "dirnames" ]; - default = "no"; - description = "Fallback behavior when no matches are found: 'no' means no fallback, 'default' means fall back to the default shell completion behavior, and 'directories' means fall back to completing directory names"; - }; - - }; - }); - default = {}; - description = "Additional completion scripts to source when shed starts (e.g. for custom tools or functions)"; - }; - - environmentVars = lib.mkOption { - type = lib.types.attrsOf lib.types.str; - default = {}; - description = "Environment variables to set when shed starts"; - }; - - settings = { - dotGlob = lib.mkOption { - type = lib.types.bool; - default = false; - description = "Whether to include hidden files in glob patterns"; - }; - autocd = lib.mkOption { - type = lib.types.bool; - default = false; - description = "Whether to automatically change into directories when they are entered as commands"; - }; - historyIgnoresDupes = lib.mkOption { - type = lib.types.bool; - default = false; - description = "Whether to ignore duplicate entries in the command history"; - }; - maxHistoryEntries = lib.mkOption { - type = lib.types.int; - default = 10000; - description = "The maximum number of entries to keep in the command history"; - }; - interactiveComments = lib.mkOption { - type = lib.types.bool; - default = true; - description = "Whether to allow comments in interactive mode"; - }; - autoHistory = lib.mkOption { - type = lib.types.bool; - default = true; - description = "Whether to automatically add commands to the history as they are executed"; - }; - bellEnabled = lib.mkOption { - type = lib.types.bool; - default = true; - description = "Whether to allow shed to ring the terminal bell on certain events (e.g. command completion, errors, etc.)"; - }; - maxRecurseDepth = lib.mkOption { - type = lib.types.int; - default = 1000; - description = "The maximum depth to allow when recursively executing shell functions"; - }; - - leaderKey = lib.mkOption { - type = lib.types.str; - default = "\\\\"; - description = "The leader key to use for custom keymaps (e.g. if set to '\\\\', then a keymap with keys='x' would be triggered by '\\x')"; - }; - promptPathSegments = lib.mkOption { - type = lib.types.int; - default = 4; - description = "The maximum number of path segments to show in the prompt"; - }; - completionLimit = lib.mkOption { - type = lib.types.int; - default = 1000; - description = "The maximum number of completion candidates to show before truncating the list"; - }; - syntaxHighlighting = lib.mkOption { - type = lib.types.bool; - default = true; - description = "Whether to enable syntax highlighting in the shell"; - }; - linebreakOnIncomplete = lib.mkOption { - type = lib.types.bool; - default = true; - description = "Whether to automatically insert a newline when the input is incomplete"; - }; - extraPostConfig = lib.mkOption { - type = lib.types.str; - default = ""; - description = "Additional configuration to append to the shed configuration file"; - }; - extraPreConfig = lib.mkOption { - type = lib.types.str; - default = ""; - description = "Additional configuration to prepend to the shed configuration file"; - }; - }; - }; + options.programs.shed = import ./shed_opts.nix { inherit pkgs lib; }; config = - let - completeLines = lib.concatLines (lib.mapAttrsToList mkCompleteCmd cfg.extraCompletion); - keymapLines = lib.concatLines (map mkKeymapCmd cfg.keymaps); - functionLines = lib.concatLines (lib.mapAttrsToList mkFunctionDef cfg.functions); - autocmdLines = lib.concatLines (map mkAutoCmd cfg.autocmds); - in lib.mkIf cfg.enable { home.packages = [ cfg.package ]; - home.file.".shedrc".text = lib.concatLines [ - cfg.settings.extraPreConfig - (lib.concatLines (lib.mapAttrsToList (name: value: "export ${name}=\"${value}\"") cfg.environmentVars)) - (lib.concatLines (lib.mapAttrsToList (name: value: "alias ${name}=\"${value}\"") cfg.aliases)) - (lib.concatLines [ - "shopt core.dotglob=${boolToString cfg.settings.dotGlob}" - "shopt core.autocd=${boolToString cfg.settings.autocd}" - "shopt core.hist_ignore_dupes=${boolToString cfg.settings.historyIgnoresDupes}" - "shopt core.max_hist=${toString cfg.settings.maxHistoryEntries}" - "shopt core.interactive_comments=${boolToString cfg.settings.interactiveComments}" - "shopt core.auto_hist=${boolToString cfg.settings.autoHistory}" - "shopt core.bell_enabled=${boolToString cfg.settings.bellEnabled}" - "shopt core.max_recurse_depth=${toString cfg.settings.maxRecurseDepth}" - - "shopt prompt.leader='${cfg.settings.leaderKey}'" - "shopt prompt.trunc_prompt_path=${toString cfg.settings.promptPathSegments}" - "shopt prompt.comp_limit=${toString cfg.settings.completionLimit}" - "shopt prompt.highlight=${boolToString cfg.settings.syntaxHighlighting}" - "shopt prompt.linebreak_on_incomplete=${boolToString cfg.settings.linebreakOnIncomplete}" - functionLines - completeLines - keymapLines - autocmdLines - ]) - cfg.settings.extraPostConfig - ]; + home.file.".shedrc".text = import ./render_rc.nix lib cfg; }; } diff --git a/nix/module.nix b/nix/module.nix index 75d04bc..99e68ed 100644 --- a/nix/module.nix +++ b/nix/module.nix @@ -4,18 +4,11 @@ let cfg = config.programs.shed; in { - options.programs.shed = { - enable = lib.mkEnableOption "shed shell"; - - package = lib.mkOption { - type = lib.types.package; - default = pkgs.shed; - description = "The shed package to use"; - }; - }; + options.programs.shed = import ./shed_opts.nix { inherit pkgs lib; }; config = lib.mkIf cfg.enable { environment.systemPackages = [ cfg.package ]; environment.shells = [ cfg.package ]; + environment.etc."shed/shedrc".text = import ./render_rc.nix lib cfg; }; } diff --git a/nix/render_rc.nix b/nix/render_rc.nix new file mode 100644 index 0000000..21cfaeb --- /dev/null +++ b/nix/render_rc.nix @@ -0,0 +1,83 @@ +lib: cfg: + +let + boolToString = b: + if b then "true" else "false"; + + mkAutoCmd = cfg: + lib.concatLines (map (hook: "autocmd ${hook} ${lib.optionalString (cfg.pattern != null) "-p \"${cfg.pattern}\""} '${cfg.command}'") cfg.hooks); + + + mkFunctionDef = name: body: + let + indented = "\t" + lib.concatStringsSep "\n\t" (lib.splitString "\n" body); + in + '' +${name}() { +${indented} +}''; + + mkKeymapCmd = cfg: let + flags = "-${lib.concatStrings cfg.modes}"; + keys = "'${cfg.keys}'"; + action = "'${cfg.command}'"; + in + "keymap ${flags} ${keys} ${action}"; + + + mkCompleteCmd = name: cfg: let + flags = lib.concatStrings [ + (lib.optionalString cfg.files " -f") + (lib.optionalString cfg.dirs " -d") + (lib.optionalString cfg.commands " -c") + (lib.optionalString cfg.variables " -v") + (lib.optionalString cfg.users " -u") + (lib.optionalString cfg.jobs " -j") + (lib.optionalString cfg.aliases " -a") + (lib.optionalString cfg.signals " -S") + (lib.optionalString cfg.noSpace " -n") + (lib.optionalString (cfg.function != null) " -F ${cfg.function}") + (lib.optionalString (cfg.fallback != "no") " -o ${cfg.fallback}") + (lib.optionalString (cfg.wordList != []) " -W '${lib.concatStringsSep " " cfg.wordList}'") + + ]; + in "complete${flags} ${name}"; + + completeLines = lib.concatLines (lib.mapAttrsToList mkCompleteCmd cfg.extraCompletion); + keymapLines = lib.concatLines (map mkKeymapCmd cfg.keymaps); + functionLines = lib.concatLines (lib.mapAttrsToList mkFunctionDef cfg.functions); + autocmdLines = lib.concatLines (map mkAutoCmd cfg.autocmds); +in +lib.concatLines [ + cfg.settings.extraPreConfig + (lib.concatLines (lib.mapAttrsToList (name: value: "export ${name}=\"${value}\"") cfg.environmentVars)) + (lib.concatLines (lib.mapAttrsToList (name: value: "alias ${name}=\"${value}\"") cfg.aliases)) + (lib.concatLines [ + "shopt core.dotglob=${boolToString cfg.settings.dotGlob}" + "shopt core.autocd=${boolToString cfg.settings.autocd}" + "shopt core.hist_ignore_dupes=${boolToString cfg.settings.historyIgnoresDupes}" + "shopt core.max_hist=${toString cfg.settings.maxHistoryEntries}" + "shopt core.interactive_comments=${boolToString cfg.settings.interactiveComments}" + "shopt core.auto_hist=${boolToString cfg.settings.autoHistory}" + "shopt core.bell_enabled=${boolToString cfg.settings.bellEnabled}" + "shopt core.max_recurse_depth=${toString cfg.settings.maxRecurseDepth}" + "shopt core.xpg_echo=${boolToString cfg.settings.echoExpandsEscapes}" + "shopt core.noclobber=${boolToString cfg.settings.noClobber}" + + "shopt prompt.leader='${cfg.settings.leaderKey}'" + "shopt prompt.trunc_prompt_path=${toString cfg.settings.promptPathSegments}" + "shopt prompt.comp_limit=${toString cfg.settings.completionLimit}" + "shopt prompt.highlight=${boolToString cfg.settings.syntaxHighlighting}" + "shopt prompt.linebreak_on_incomplete=${boolToString cfg.settings.linebreakOnIncomplete}" + "shopt prompt.line_numbers=${boolToString cfg.settings.lineNumbers}" + "shopt prompt.screensaver_idle_time=${toString cfg.settings.screensaverIdleTime}" + "shopt prompt.screensaver_cmd='${cfg.settings.screensaverCmd}'" + "shopt prompt.completion_ignore_case=${boolToString cfg.settings.completionIgnoreCase}" + "shopt prompt.auto_indent=${boolToString cfg.settings.autoIndent}" + functionLines + completeLines + keymapLines + autocmdLines + ]) + cfg.settings.extraPostConfig + ] diff --git a/nix/shed_opts.nix b/nix/shed_opts.nix new file mode 100644 index 0000000..61f6ae4 --- /dev/null +++ b/nix/shed_opts.nix @@ -0,0 +1,279 @@ +{ pkgs, lib }: + +{ + enable = lib.mkEnableOption "shed shell"; + + package = lib.mkOption { + type = lib.types.package; + default = pkgs.shed; + description = "The shed package to use"; + }; + + aliases = lib.mkOption { + type = lib.types.attrsOf lib.types.str; + default = {}; + description = "Aliases to set when shed starts"; + }; + + functions = lib.mkOption { + type = lib.types.attrsOf lib.types.str; + default = {}; + description = "Shell functions to set when shed starts"; + }; + + autocmds = lib.mkOption { + type = lib.types.listOf (lib.types.submodule { + options = { + hooks = lib.mkOption { + type = lib.types.addCheck (lib.types.listOf (lib.types.enum [ + "pre-cmd" + "post-cmd" + "pre-change-dir" + "post-change-dir" + "on-job-finish" + "pre-prompt" + "post-prompt" + "pre-mode-change" + "post-mode-change" + "on-exit" + "on-history-open" + "on-history-close" + "on-history-select" + "on-completion-start" + "on-completion-cancel" + "on-completion-select" + ])) (list: list != []); + description = "The events that trigger this autocmd"; + }; + pattern = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + description = "A regex pattern to use in the hook to determine whether it runs or not. What it's compared to differs by hook, for instance 'pre-change-dir' compares it to the new directory, pre-cmd compares it to the command, etc"; + }; + command = lib.mkOption { + type = lib.types.addCheck lib.types.str (cmd: cmd != ""); + description = "The shell command to execute when the hook is triggered and the pattern (if provided) matches"; + }; + }; + + }); + default = []; + description = "Custom autocmds to set when shed starts"; + }; + + keymaps = lib.mkOption { + type = lib.types.listOf (lib.types.submodule { + options = { + modes = lib.mkOption { + type = lib.types.listOf (lib.types.enum [ "n" "i" "x" "v" "o" "r" ]); + default = []; + description = "The editing modes this keymap can be used in"; + }; + keys = lib.mkOption { + type = lib.types.str; + default = ""; + description = "The sequence of keys that trigger this keymap"; + }; + command = lib.mkOption { + type = lib.types.str; + default = ""; + description = "The sequence of characters to send to the line editor when the keymap is triggered."; + }; + }; + }); + default = []; + description = "Custom keymaps to set when shed starts"; + }; + + extraCompletion = lib.mkOption { + type = lib.types.attrsOf (lib.types.submodule { + options = { + files = lib.mkOption { + type = lib.types.bool; + default = false; + description = "Complete file names in the current directory"; + }; + dirs = lib.mkOption { + type = lib.types.bool; + default = false; + description = "Complete directory names in the current directory"; + }; + commands = lib.mkOption { + type = lib.types.bool; + default = false; + description = "Complete executable commands in the PATH"; + }; + variables = lib.mkOption { + type = lib.types.bool; + default = false; + description = "Complete variable names"; + }; + users = lib.mkOption { + type = lib.types.bool; + default = false; + description = "Complete user names from /etc/passwd"; + }; + jobs = lib.mkOption { + type = lib.types.bool; + default = false; + description = "Complete job names or pids from the current shell session"; + }; + aliases = lib.mkOption { + type = lib.types.bool; + default = false; + description = "Complete alias names defined in the current shell session"; + }; + signals = lib.mkOption { + type = lib.types.bool; + default = false; + description = "Complete signal names for commands like kill"; + }; + wordList = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = []; + description = "Complete from a custom list of words"; + }; + function = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + description = "Complete using a custom shell function (should be defined in extraCompletionPreConfig)"; + }; + noSpace = lib.mkOption { + type = lib.types.bool; + default = false; + description = "Don't append a space after completion"; + }; + fallback = lib.mkOption { + type = lib.types.enum [ "no" "default" "dirnames" ]; + default = "no"; + description = "Fallback behavior when no matches are found: 'no' means no fallback, 'default' means fall back to the default shell completion behavior, and 'directories' means fall back to completing directory names"; + }; + + }; + }); + default = {}; + description = "Additional completion scripts to source when shed starts (e.g. for custom tools or functions)"; + }; + + environmentVars = lib.mkOption { + type = lib.types.attrsOf lib.types.str; + default = {}; + description = "Environment variables to set when shed starts"; + }; + + settings = { + dotGlob = lib.mkOption { + type = lib.types.bool; + default = false; + description = "Whether to include hidden files in glob patterns"; + }; + autocd = lib.mkOption { + type = lib.types.bool; + default = false; + description = "Whether to automatically change into directories when they are entered as commands"; + }; + historyIgnoresDupes = lib.mkOption { + type = lib.types.bool; + default = false; + description = "Whether to ignore duplicate entries in the command history"; + }; + maxHistoryEntries = lib.mkOption { + type = lib.types.int; + default = 10000; + description = "The maximum number of entries to keep in the command history"; + }; + interactiveComments = lib.mkOption { + type = lib.types.bool; + default = true; + description = "Whether to allow comments in interactive mode"; + }; + autoHistory = lib.mkOption { + type = lib.types.bool; + default = true; + description = "Whether to automatically add commands to the history as they are executed"; + }; + bellEnabled = lib.mkOption { + type = lib.types.bool; + default = true; + description = "Whether to allow shed to ring the terminal bell on certain events (e.g. command completion, errors, etc.)"; + }; + maxRecurseDepth = lib.mkOption { + type = lib.types.int; + default = 1000; + description = "The maximum depth to allow when recursively executing shell functions"; + }; + echoExpandsEscapes = lib.mkOption { + type = lib.types.bool; + default = false; + description = "Whether to have the 'echo' builtin expand escape sequences like \\n and \\t (if false, it will print them verbatim)"; + }; + noClobber = lib.mkOption { + type = lib.types.bool; + default = false; + description = "Whether to prevent redirection from overwriting existing files by default (i.e. behave as if 'set -o noclobber' is always in effect)"; + }; + + leaderKey = lib.mkOption { + type = lib.types.str; + default = "\\\\"; + description = "The leader key to use for custom keymaps (e.g. if set to '\\\\', then a keymap with keys='x' would be triggered by '\\x')"; + }; + promptPathSegments = lib.mkOption { + type = lib.types.int; + default = 4; + description = "The maximum number of path segments to show in the prompt"; + }; + completionLimit = lib.mkOption { + type = lib.types.int; + default = 1000; + description = "The maximum number of completion candidates to show before truncating the list"; + }; + syntaxHighlighting = lib.mkOption { + type = lib.types.bool; + default = true; + description = "Whether to enable syntax highlighting in the shell"; + }; + linebreakOnIncomplete = lib.mkOption { + type = lib.types.bool; + default = true; + description = "Whether to automatically insert a newline when the input is incomplete"; + }; + lineNumbers = lib.mkOption { + type = lib.types.bool; + default = false; + description = "Whether to show line numbers in the prompt"; + }; + screensaverCmd = lib.mkOption { + type = lib.types.str; + default = ""; + description = "A shell command to execute after a period of inactivity (i.e. a custom screensaver)"; + }; + screensaverIdleTime = lib.mkOption { + type = lib.types.int; + default = 0; + description = "The amount of inactivity time in seconds before the screensaver command is executed"; + }; + completionIgnoreCase = lib.mkOption { + type = lib.types.bool; + default = false; + description = "Whether to ignore case when completing commands and file names"; + }; + autoIndent = lib.mkOption { + type = lib.types.bool; + default = true; + description = "Whether to automatically indent new lines based on the previous line"; + }; + + + extraPostConfig = lib.mkOption { + type = lib.types.str; + default = ""; + description = "Additional configuration to append to the shed configuration file"; + }; + extraPreConfig = lib.mkOption { + type = lib.types.str; + default = ""; + description = "Additional configuration to prepend to the shed configuration file"; + }; + }; +}