diff options
| -rw-r--r-- | flake.lock | 42 | ||||
| -rw-r--r-- | flake.nix | 18 | ||||
| -rw-r--r-- | modules/common/bar.nix | 69 | ||||
| -rw-r--r-- | modules/common/defaultGlobalKeys.nix | 66 | ||||
| -rw-r--r-- | modules/common/defaultLeaderMenu.nix | 51 | ||||
| -rw-r--r-- | modules/common/findFile.nix | 66 | ||||
| -rw-r--r-- | modules/common/fzfExec.nix | 55 | ||||
| -rw-r--r-- | modules/common/keys.nix | 66 | ||||
| -rw-r--r-- | modules/common/main.nix | 23 | ||||
| -rw-r--r-- | modules/common/sessionizer.nix | 92 | ||||
| -rw-r--r-- | modules/common/sessionizer2.nix | 50 | ||||
| -rw-r--r-- | modules/hjem.nix | 58 | ||||
| -rw-r--r-- | modules/installer.nix | 38 | ||||
| -rw-r--r-- | planning.md | 56 |
14 files changed, 750 insertions, 0 deletions
diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..63066a7 --- /dev/null +++ b/flake.lock @@ -0,0 +1,42 @@ +{ + "nodes": { + "nix-std": { + "locked": { + "lastModified": 1710870712, + "narHash": "sha256-e+7MJF2gsgTBuOWv4mCimSP0D9+naeFSw9a7N3yEmv4=", + "owner": "chessai", + "repo": "nix-std", + "rev": "31bbc925750cc9d8f828fe55cee1a2bd985e0c00", + "type": "github" + }, + "original": { + "owner": "chessai", + "repo": "nix-std", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1759578544, + "narHash": "sha256-GFvucgx8PDytuftANATwu7IQMydblpMDWNZwjg2Oyb4=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "b0bb1a55163139b0b24228143567f0c1c8c178f4", + "type": "github" + }, + "original": { + "owner": "NixOS", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "nix-std": "nix-std", + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..e74feb7 --- /dev/null +++ b/flake.nix @@ -0,0 +1,18 @@ +{ + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs"; + nix-std.url = "github:chessai/nix-std"; + }; + outputs = inputs: { + hjemModules.tsunami = import ./modules/hjem.nix { + std = inputs.nix-std.lib; + }; + + packages."x86_64-linux".tsunamiInstaller = let + installer = import ./modules/installer.nix { + pkgs = import inputs.nixpkgs {system = "x86_64-linux";}; + }; + in + installer.mkInstaller; + }; +} diff --git a/modules/common/bar.nix b/modules/common/bar.nix new file mode 100644 index 0000000..cbb162a --- /dev/null +++ b/modules/common/bar.nix @@ -0,0 +1,69 @@ +{ + config, + lib, + tsunamiLib, + ... +}: let + inherit (tsunamiLib) scriptPath; +in { + options = let + hexColor = lib.types.strMatching "^#?([0-9a-fA-F]{6}|[0-9a-fA-F]{3})$"; + in { + tsunami.theme = { + bg = lib.mkOption { + type = hexColor; + default = "#fff"; + }; + bg_dark = lib.mkOption { + type = hexColor; + default = "#000"; + }; + }; + }; + config = { + tsunami = { + scripts = { + "battery" = '' + energy_now=$(cat /sys/class/power_supply/BAT0/energy_now) + energy_full=$(cat /sys/class/power_supply/BAT0/energy_full) + percentage=$((energy_now * 100 / energy_full)) + printf "%.0f%%" "$percentage" + ''; + + "bar" = '' + tmux set -g status on + + tmux set -g status-position bottom + tmux set -g status-justify absolute-centre + tmux set -g status-bg "${config.tsunami.theme.bg}" + + tmux set -g status-left-style fg=green,bold + tmux set -g status-left " #{client_session}" + + tmux set -g window-status-style fg=color243 + tmux set -g window-status-format " #I " + tmux set -g window-status-current-style fg=color12,bold + tmux set -g window-status-current-format " #I " + tmux set -g window-status-separator "" + + tmux set -g status-right "#[fg=color243]%l:%M #[fg=red]#(${scriptPath "battery"}) " + + ## pane borders + tmux set -g pane-border-style fg=${config.tsunami.theme.bg_dark},bg=${config.tsunami.theme.bg_dark} + tmux set -g pane-active-border-style fg=${config.tsunami.theme.bg_dark},bg=${config.tsunami.theme.bg_dark} + + ## pane backgrounds + # Set the foreground/background color for the active window + tmux set -g window-active-style bg=${config.tsunami.theme.bg} + + # Set the foreground/background color for all other windows + tmux set -g window-style bg=${config.tsunami.theme.bg_dark} + ''; + }; + + confs."bar" = '' + run-shell ${scriptPath "bar"} + ''; + }; + }; +} diff --git a/modules/common/defaultGlobalKeys.nix b/modules/common/defaultGlobalKeys.nix new file mode 100644 index 0000000..fc3c669 --- /dev/null +++ b/modules/common/defaultGlobalKeys.nix @@ -0,0 +1,66 @@ +{ + pkgs, + tsunamiLib, + ... +}: let + inherit (tsunamiLib) scriptPath; +in { + tsunami.scripts."split" = '' + width=$(tmux display -p "#{pane_width}") + height=$(tmux display -p "#{pane_height}") + + if (( $(echo "$width / $height > 2.5" | ${pkgs.bc}/bin/bc -l) )); then + tmux split-window -h "$@" + else + tmux split-window -v "$@" + fi + ''; + + tsunami.scripts."clean-sessions" = '' + current="$(tmux display -p '#{session_name}')" + + tmux list-sessions -F '#{session_name}' | while read -r line; do + if [[ "$line" != "$current" && "$line" =~ ^[[:digit:]]+$ ]]; then + tmux kill-session -t "$line" + fi + done + ''; + + tsunami.keys.global = [ + { + key = "C-w"; + exec = "kill-pane"; + } + { + key = "M-z"; + exec = "resize-pane -Z"; + } + { + key = "C-Enter"; + exec = ''run-shell "${scriptPath "split"} -c '#{?@default-path,#{@default-path},#{pane_current_path}}'"''; + } + { + key = "C-t"; + exec = "new-window -c '#{?@default-path,#{@default-path},#{pane_current_path}}'"; + } + { + key = "C-Tab"; + exec = "next-window"; + } + { + key = "C-S-Tab"; + exec = "previous-window"; + } + ]; + + tsunami.confs."windows" = '' + unbind -n MouseDown3Pane + set -g allow-rename on + set -g automatic-rename off + set -g renumber-windows on + set -g base-index 1 + + set-hook -ag client-detached 'run-shell ${scriptPath "clean-sessions"}' + set-hook -ag client-session-changed 'run-shell ${scriptPath "clean-sessions"}' + ''; +} diff --git a/modules/common/defaultLeaderMenu.nix b/modules/common/defaultLeaderMenu.nix new file mode 100644 index 0000000..71489e7 --- /dev/null +++ b/modules/common/defaultLeaderMenu.nix @@ -0,0 +1,51 @@ +{tsunamiLib, ...}: let + inherit (tsunamiLib) scriptPath; +in { + tsunami.scripts = { + "find-pane" = '' + display_format="#{window_name} #{pane_title} #{pane_current_path} #{pane_current_command}" + hidden_format="#{session_name}:#{window_id}:#{pane_id}" + + # select pane + selected=$(tmux list-panes -a -F "$hidden_format:$display_format" | fzf --delimiter=: --with-nth 4 --color=hl:2) + [ -z "$selected" ] || exit + + # switch to selected + args=(''\${selected//:/ }) + tmux select-pane -t ''\${args[2]} && tmux select-window -t ''\${args[1]} && tmux switch-client -t ''\${args[0]} + ''; + + "search-pane" = '' + trap 'rm -f -- "''\${scrollback:-}"' EXIT + scrollback="$(mktemp)" + + tmux capture-pane -e -p -S - > "$scrollback" + cat "$scrollback" | fzf --ansi + + exit 0 + ''; + }; + + tsunami.keys.leader = [ + { + name = "Find Pane"; + key = "C-w"; + exec = ''run-shell "${scriptPath "minibuffer"} '${scriptPath "find-pane"}'"''; + } + { + name = "Search Pane"; + key = "C-/"; + exec = ''run-shell "${scriptPath "minibuffer"} '${scriptPath "search-pane"}'"''; + } + { + name = "Detach"; + key = "C-d"; + exec = "detach"; + } + { + name = "Reload"; + key = "C-r"; + exec = "source-file ~/.config/tmux/tmux.conf"; + } + ]; +} diff --git a/modules/common/findFile.nix b/modules/common/findFile.nix new file mode 100644 index 0000000..3d08603 --- /dev/null +++ b/modules/common/findFile.nix @@ -0,0 +1,66 @@ +{ + pkgs, + config, + lib, + tsunamiLib, + std, + ... +}: let + inherit (tsunamiLib) scriptPath filePath; + cfg = config.tsunami.findFile; +in { + options = { + tsunami.findFile = { + enable = lib.mkOption { + type = lib.types.bool; + default = false; + }; + }; + }; + + config = lib.mkIf cfg.enable { + tsunami.keys.leader = [ + { + name = "Find File"; + key = "C-f"; + exec = ''run-shell "${scriptPath "minibuffer"} -d '#{?@default-path,#{@default-path},#{pane_current_path}}' '${scriptPath "find-file"}'"''; + } + ]; + tsunami.scripts = { + "minibuffer" = '' + window_height="$(tmux display -p '#{window_height}')" + tmux display-popup -EB \ + -w 100% -h 16 \ + -x 0 -y "$(($window_height + 1))" \ + "$@" + ''; + + "find-file" = ''${pkgs.broot}/bin/broot --conf ${filePath "broot_find_file.toml"}''; + }; + + tsunami.files."broot_find_file.toml" = std.serde.toTOML { + imports = ["~/.config/broot/conf.hjson"]; + quit_on_last_cancel = true; + verbs = [ + { + invocation = "tmux-split"; + external = ["bash" "-c" ''${scriptPath "split"} -c "#{?@default-path,#{@default-path},#{pane_current_path}}" "$EDITOR '{file}'"'']; + key = "ctrl-s"; + apply_to = "file"; + leave_broot = true; + } + { + invocation = "tmux-window"; + external = ["tmux" "new-window" "-c" "#{?@default-path,#{@default-path},#{pane_current_path}}" "$EDITOR '{file}'"]; + key = "ctrl-w"; + apply_to = "file"; + leave_broot = true; + } + { + key = "enter"; + cmd = ":tmux-window"; + } + ]; + }; + }; +} diff --git a/modules/common/fzfExec.nix b/modules/common/fzfExec.nix new file mode 100644 index 0000000..2de3741 --- /dev/null +++ b/modules/common/fzfExec.nix @@ -0,0 +1,55 @@ +{ + tsunamiLib, + lib, + pkgs, + config, + ... +}: let + inherit (tsunamiLib) scriptPath; + cfg = config.tsunami.fzfExec; +in { + options = { + tsunami.fzfExec = { + enable = lib.mkOption { + type = lib.types.bool; + default = true; + }; + }; + }; + + config = lib.mkIf cfg.enable { + tsunami.scripts = { + # this is buggy af; it only works half the time. + # But it's the only way I know of to get docs on a specific tmux command + "tmux-doc" = '' + usage=$(tmux list-commands -F "#{command_list_name} #{command_list_usage}" "$1") + + man tmux | ${pkgs.gawk}/bin/awk -v usage="$usage" ' + index($0, usage) > 0 { found=1 } + found { + print + if ($0 == "") exit + } + ' + ''; + + "fzf-exec" = '' + all_cmds() { + tmux list-commands -F $'#{command_list_name}#{?command_list_alias,\n#{command_list_alias},}' + } + + selected_cmd=$(all_cmds | ${pkgs.fzf}/bin/fzf --bind 'enter:accept-or-print-query,tab:replace-query,alt-backspace:clear-query' --prompt : --preview "~/.config/tmux/scripts/tmux-doc.sh $(printf '{}' | cut -d' ' -f1)") + test -z "$selected_cmd" && exit + + tmux $(echo "$selected_cmd" | sed "s@~@$HOME@g") + ''; + }; + + tsunami.keys.global = [ + { + key = "M-x"; + exec = "run-shell '${scriptPath "minibuffer"} -h 10 ${scriptPath "fzf-exec"}'"; + } + ]; + }; +} diff --git a/modules/common/keys.nix b/modules/common/keys.nix new file mode 100644 index 0000000..9e3c1a2 --- /dev/null +++ b/modules/common/keys.nix @@ -0,0 +1,66 @@ +{ + lib, + config, + tsunamiLib, + ... +}: let + inherit (tsunamiLib) scriptPath; + inherit (lib.strings) escapeShellArg; + cfg = config.tsunami.keys; +in { + options = let + tmuxKey = lib.types.strMatching "^((C|M|S)-)*(.|Enter|Escape|Tab|Up|Down|Left|Right)$"; + inherit (lib) mkOption; + in { + tsunami.keys = { + leader = mkOption { + type = with lib.types; + listOf (submodule { + options = { + name = mkOption {type = str;}; + key = mkOption {type = tmuxKey;}; + exec = mkOption {type = str;}; + }; + }); + }; + + global = mkOption { + type = with lib.types; + listOf (submodule { + options = { + key = mkOption {type = tmuxKey;}; + exec = mkOption {type = str;}; + }; + }); + }; + }; + }; + + config = { + tsunami.scripts = { + "menu" = '' + tmux display-menu \ + -x "#{window_width}" -y S \ + -b none \ + -s "bg=#313244,fg=#9399b2" \ + -S "bg=#313244" \ + -H "bg=#45475a fg=#b4befe" \ + "$@" + ''; + + "leader-menu" = let + args = lib.concatStringsSep " " (builtins.map (f: "${escapeShellArg f.name} ${f.key} ${escapeShellArg f.exec}") cfg.leader); + in '' + ${scriptPath "menu"} ${args} + ''; + }; + tsunami.keys.global = [ + { + key = "C-x"; + exec = "run-shell ${scriptPath "leader-menu"}"; + } + ]; + + tsunami.confs."global-keys" = cfg.global |> builtins.map (f: "bind-key -n ${f.key} ${escapeShellArg f.exec}") |> lib.concatStringsSep "\n"; + }; +} diff --git a/modules/common/main.nix b/modules/common/main.nix new file mode 100644 index 0000000..25366d9 --- /dev/null +++ b/modules/common/main.nix @@ -0,0 +1,23 @@ +{lib, ...}: let + inherit (lib) mkOption mkEnableOption; +in { + options = { + tsunami = { + enable = mkEnableOption "tsunami tmux distro"; + scripts = mkOption { + type = lib.types.attrsOf (lib.types.str); + default = []; + }; + + files = mkOption { + type = lib.types.attrsOf (lib.types.str); + default = []; + }; + + confs = mkOption { + type = lib.types.attrsOf (lib.types.str); + default = []; + }; + }; + }; +} diff --git a/modules/common/sessionizer.nix b/modules/common/sessionizer.nix new file mode 100644 index 0000000..09aa65a --- /dev/null +++ b/modules/common/sessionizer.nix @@ -0,0 +1,92 @@ +{ + pkgs, + config, + lib, + tsunamiLib, + std, + ... +}: let + inherit (tsunamiLib) scriptPath filePath; + cfg = config.tsunami.sessionizer; +in { + options = { + tsunami.sessionizer = { + enable = lib.mkOption { + type = lib.types.bool; + default = false; + }; + }; + }; + + config = lib.mkIf cfg.enable { + tsunami.keys.leader = [ + { + name = "Sessions+"; + key = "C-s"; + exec = "run-shell ${scriptPath "sessions-menu"}"; + } + ]; + + tsunami.scripts = { + "sessionizer" = + # bash + '' + [ -z "$1" ] && exit + + selected="$1" + session_name="$(basename "$selected" | tr '.' '_')" + + # if the session doesn't exist, create it + if ! tmux has-session -t "$session_name"; then + tmux new-session -ds "$session_name" -c "$selected" + tmux set-option -t "$session_name" @default-path "$selected" + fi + + # switch to it + tmux switch -t "$session_name" + ''; + "launch-sessionizer" = ''${pkgs.broot}/bin/broot --only-folders --conf ${filePath "broot_sessionizer.toml"}''; + "new-session" = ''tmux switch-client -t "$(tmux new-session -dP)"''; + "sessions-menu" = + # bash + '' + menu_items=( + "Switch Session" s "run-shell '${scriptPath "minibuffer"} -d $HOME ${scriptPath "launch-sessionizer"}'" + "New Unnamed Session" n "run-shell ${scriptPath "new-session"}" + ) + + # put tmux session names into a list + mapfile -t sessions < <(tmux list-sessions -F "#{session_name}") + + # ...but limit at 9 sessions + for i in "''\${!sessions[@]}"; do + (( i >= 9 )) && break # Stop after 9 sessions + + session="''\${sessions[$i]}" + key=$((i + 1)) # 1-based key + + # and put those 9 in the list of menu items + menu_items+=("$session" "$key" "switch-client -t $session") + done + + # Display the menu + ${scriptPath "menu"} -T "#[align=centre]Sessions" "''\${menu_items[@]}" + ''; + }; + tsunami.files = { + "broot_sessionizer.toml" = std.serde.toTOML { + imports = ["~/.config/broot/conf.hjson"]; # inherit from user's config + quit_on_cancel = true; + verbs = [ + { + invocation = "session"; + external = ''bash -c -- "${scriptPath "sessionizer"} '{file}'"''; + key = "enter"; + apply_to = "directory"; + leave_broot = true; + } + ]; + }; + }; + }; +} diff --git a/modules/common/sessionizer2.nix b/modules/common/sessionizer2.nix new file mode 100644 index 0000000..5f159df --- /dev/null +++ b/modules/common/sessionizer2.nix @@ -0,0 +1,50 @@ +{ + pkgs, + config, + lib, + tsunamiLib, + std, + ... +}: let + inherit (tsunamiLib) scriptPath filePath; + cfg = config.tsunami.sessionizer2; +in { + options.tsunami.sessionizer2 = { + enable = lib.mkOption { + type = lib.types.bool; + default = true; + }; + }; + + config = lib.mkIf cfg.enable { + tsunami.keys.leader = [ + { + name = "New Session"; + key = "C-n"; + exec = "run-shell ${scriptPath "sessions-menu"}"; + } + ]; + + tsunami.files = { + "broot_sessionizer2_launcher" = std.serde.toTOML { + imports = ["~/.config/broot/conf.hjson"]; # inherit from user's config + verbs = [ + { + invocation = "tmux-window"; + external = ["tmux" "new-window" "-c" "#{?@default-path,#{@default-path},#{pane_current_path}}" "$EDITOR '{file}'"]; + key = "enter"; + apply_to = "file"; + leave_broot = false; + } + { + invocation = "tmux-window"; + external = ["tmux" "new-window" "-c" "#{?@default-path,#{@default-path},#{pane_current_path}}" "$EDITOR '{file}'"]; + key = "enter"; + apply_to = "file"; + leave_broot = false; + } + ]; + }; + }; + }; +} diff --git a/modules/hjem.nix b/modules/hjem.nix new file mode 100644 index 0000000..019eada --- /dev/null +++ b/modules/hjem.nix @@ -0,0 +1,58 @@ +{std, ...}: { + pkgs, + lib, + config, + ... +}: let + cfg = config.tsunami; +in { + imports = lib.filesystem.listFilesRecursive ./common; + + _module.args = { + inherit std; + tsunamiLib = { + scriptPath = f: "~/.config/tmux/scripts/${f}.sh"; + filePath = f: "~/.config/tmux/files/${f}"; + }; + }; + + files = lib.mkIf cfg.enable (lib.mkMerge [ + ( + cfg.scripts + |> builtins.mapAttrs (name: value: { + name = ".config/tmux/scripts/${name}.sh"; + value = { + text = value; + executable = true; + }; + }) + |> lib.attrValues + |> builtins.listToAttrs + ) + ( + cfg.files + |> builtins.mapAttrs (name: value: { + name = ".config/tmux/files/${name}"; + value.text = value; + }) + |> lib.attrValues + |> builtins.listToAttrs + ) + ( + cfg.confs + |> builtins.mapAttrs (name: value: { + name = ".config/tmux/conf.d/${name}.conf"; + value.text = value; + }) + |> lib.attrValues + |> builtins.listToAttrs + ) + { + ".config/tmux/tmux.conf".text = '' + run-shell "find ~/.config/tmux/conf.d -print0 | xargs -0 -n1 tmux source-file" + ''; + } + ]); + + packages = lib.optional cfg.enable pkgs.tmux; +} diff --git a/modules/installer.nix b/modules/installer.nix new file mode 100644 index 0000000..9cff811 --- /dev/null +++ b/modules/installer.nix @@ -0,0 +1,38 @@ +{pkgs, ...}: let + tsunamiLib = { + scriptPath = f: "$HOME/.config/tmux/scripts/${f}.sh"; + filePath = f: "$HOME/.config/tmux/files/${f}"; + }; + config = pkgs.lib.evalModules { + modules = + [ + {_module.args = {inherit pkgs tsunamiLib;};} + ] + ++ (pkgs.lib.filesystem.listFilesRecursive ./common); + }; + + moduleConfig = pkgs.writeText "tsunami-installer-config.json" (builtins.toJSON { + inherit (config.config.tsunami) scripts files confs; + }); +in { + mkInstaller = + pkgs.runCommand "tsunami-installer" { + buildInputs = [pkgs.jq]; + } '' + mkdir -p $out + { + cat ${moduleConfig} | jq -r ' + (.scripts | to_entries[] | "cat <<\"EOF\" > $HOME/.config/tmux/scripts/\(.key).sh\n\(.value)\nEOF\n"), + (.files | to_entries[] | "cat <<\"EOF\" > $HOME/.config/tmux/files/\(.key)\n\(.value)\nEOF\n"), + (.confs | to_entries[] | "cat <<\"EOF\" > $HOME/.config/tmux/conf.d/\(.key).conf\n\(.value)\nEOF\n") + ' + printf ' + cat <<EOF > $HOME/.config/tmux/tmux.conf + run-shell "find $out/conf.d -print0 | xargs -0 -n1 tmux source-file" + EOF + ' + } > $out/tsunami-installer + + chmod +x $out/tsunami-installer + ''; +} diff --git a/planning.md b/planning.md new file mode 100644 index 0000000..872bbb5 --- /dev/null +++ b/planning.md @@ -0,0 +1,56 @@ +## Pieces: +sessionizer +- leaderkey keybinding C-p+ +- script launch-sessionizer +- script clean-sessions +- script new-session +- script projects-menu +- extrafile broot_sessionizer + +```nix +{pkgs, tsunamiLib, ...}: let + inherit (tsunamiLib) scriptPath filePath; +in { + tsunami.keys.leader = [{ + name = "Projects+"; + key = "C-p"; + exec = "run-shell ${scriptPath "projects-menu"}"; + }]; + + tsunami.scripts = { + "launch-sessionizer" = ''${pkgs.broot}/bin/broot --conf ${filePath "broot_sessionizer.toml"}''; + "clean-sessions" = ''...''; + "new-session" = ''...''; + }; + tsunami.files = { + "broot_sessionizer.toml" = (pkgs.formats.toml {}).generate "file.toml" { + ... + }; + }; +} +``` + +find-file +- leaderkey keybinding C-f +- script find-file +- extrafile broot_find_file +fzf-exec +- keybinding M-x +- script tmux-doc +- script fzf-exec +search-buffer +- leaderkey keybinding C-/ +- script search-buffer +find-window +- leaderkey keybinding C-w +- script find-buffer +leader-key +- keybinding C-x +- script leader-menu +bar +- script bar +- script battery +main config +- script minibuffer +- script split +- |
