From 6b01ee918acf2df043c97428dd8fe89ef992b751 Mon Sep 17 00:00:00 2001 From: Sage Date: Fri, 6 Mar 2026 17:49:33 +0000 Subject: [PATCH] Add wezterm --- hosts/musk/home.nix | 2 + modules/home/terminal/default.nix | 2 + modules/home/terminal/development.nix | 5 +- modules/home/terminal/nushell.nix | 71 ++++++ modules/home/terminal/wezterm.nix | 314 ++++++++++++++++++++++++++ 5 files changed, 393 insertions(+), 1 deletion(-) create mode 100644 modules/home/terminal/nushell.nix create mode 100644 modules/home/terminal/wezterm.nix diff --git a/hosts/musk/home.nix b/hosts/musk/home.nix index 54dde08..b72bcdd 100644 --- a/hosts/musk/home.nix +++ b/hosts/musk/home.nix @@ -14,7 +14,9 @@ in { mods.i3.enable = true; # mods.hyprland.enable = true; mods.terminal.zsh.enable = true; + mods.terminal.nushell.enable = true; mods.terminal.emulator.enable = true; + mods.terminal.wezterm.enable = true; mods.terminal.development.enable = true; mods.terminal.tools.enable = true; mods.terminal.gh.enable = true; diff --git a/modules/home/terminal/default.nix b/modules/home/terminal/default.nix index fc255fb..7beb495 100644 --- a/modules/home/terminal/default.nix +++ b/modules/home/terminal/default.nix @@ -6,6 +6,8 @@ imports = [ ./shell.nix ./emulator.nix + ./wezterm.nix + ./nushell.nix ./development.nix ./tools.nix ./yazi.nix diff --git a/modules/home/terminal/development.nix b/modules/home/terminal/development.nix index 89392a4..b0f55d6 100644 --- a/modules/home/terminal/development.nix +++ b/modules/home/terminal/development.nix @@ -23,9 +23,12 @@ in { enable = true; nix-direnv.enable = true; enableZshIntegration = lib.mkIf config.mods.terminal.zsh.enable true; + enableNushellIntegration = lib.mkIf config.mods.terminal.nushell.enable true; }; home.sessionVariables.EDITOR = "nvim"; - programs.zsh.sessionVariables.EDITOR = "nvim"; + programs.zsh.sessionVariables = lib.mkIf config.mods.terminal.zsh.enable { + EDITOR = "nvim"; + }; }; } diff --git a/modules/home/terminal/nushell.nix b/modules/home/terminal/nushell.nix new file mode 100644 index 0000000..b7e38d1 --- /dev/null +++ b/modules/home/terminal/nushell.nix @@ -0,0 +1,71 @@ +{ + pkgs, + lib, + config, + ... +}: let + cfg = config.mods.terminal; + + aliases = { + la = "ls -la"; + ".." = "cd .."; + "..." = "cd ../.."; + "...." = "cd ../../.."; + "....." = "cd ../../../.."; + "......" = "cd ../../../../.."; + }; +in { + options.mods.terminal.nushell.enable = lib.mkEnableOption "enables nushell"; + + config = lib.mkIf cfg.nushell.enable { + programs.nushell = { + enable = true; + + shellAliases = aliases; + + # vi mode + sensible defaults + extraConfig = '' + $env.config = { + show_banner: false + edit_mode: vi + + cursor_shape: { + vi_insert: line + vi_normal: block + } + + history: { + max_size: 2097152 + sync_on_enter: true + file_format: "sqlite" + isolation: false + } + + completions: { + case_sensitive: false + quick: true + partial: true + algorithm: "fuzzy" + } + + table: { + mode: rounded + } + } + ''; + + # Carry over zsh session variables + extraEnv = lib.optionalString config.mods.terminal.development.enable '' + $env.EDITOR = "nvim" + ''; + }; + + # Starship prompt (same as zsh) + programs.starship.enable = true; + + # direnv nushell integration + programs.direnv = lib.mkIf config.mods.terminal.development.enable { + enableNushellIntegration = true; + }; + }; +} diff --git a/modules/home/terminal/wezterm.nix b/modules/home/terminal/wezterm.nix new file mode 100644 index 0000000..0314889 --- /dev/null +++ b/modules/home/terminal/wezterm.nix @@ -0,0 +1,314 @@ +{ + pkgs, + lib, + config, + ... +}: let + cfg = config.mods.terminal; + color = config.lib.stylix.colors.withHashtag; + + # Shell to use inside wezterm + shell = + if cfg.nushell.enable + then lib.getExe pkgs.nushell + else if cfg.zsh.enable + then lib.getExe pkgs.zsh + else null; + + # ── Lua codegen helpers ────────────────────────────────────────────────────── + + luaKey = { + key, + mods ? null, + action, + indent ? " ", + }: let + modsStr = lib.optionalString (mods != null) ", mods = \"${mods}\""; + in "${indent}{ key = \"${key}\"${modsStr}, action = ${action} },\n"; + + # vi directions: key, arrow alias, wezterm direction name + viDirs = [ + {key = "h"; arrow = "LeftArrow"; dir = "Left";} + {key = "j"; arrow = "DownArrow"; dir = "Down";} + {key = "k"; arrow = "UpArrow"; dir = "Up";} + {key = "l"; arrow = "RightArrow"; dir = "Right";} + ]; + + # hjkl + arrow equivalents; mkAction :: dir -> lua-action-string + viDirKeys = {mods ? null, indent ? " ", mkAction}: + lib.concatMapStrings (d: + luaKey {inherit mods indent; key = d.key; action = mkAction d.dir;} + + luaKey {inherit mods indent; key = d.arrow; action = mkAction d.dir;}) + viDirs; + + # 1-9 tab-jump bindings + tabJumpKeys = {mods ? null, indent ? " "}: + lib.concatStrings (builtins.genList (i: + luaKey { + inherit mods indent; + key = toString (i + 1); + action = "act.ActivateTab(${toString i})"; + }) + 9); + + # hjkl + arrows for AdjustPaneSize at a given step + resizeDirKeys = {step, indent ? " "}: + lib.concatMapStrings (d: + luaKey {inherit indent; key = d.key; action = "act.AdjustPaneSize({ \"${d.dir}\", ${toString step} })";} + + luaKey {inherit indent; key = d.arrow; action = "act.AdjustPaneSize({ \"${d.dir}\", ${toString step} })";}) + viDirs; + + # Uppercase HJKL fine-tune resize + resizeShiftKeys = {step, indent ? " "}: + lib.concatMapStrings (d: + luaKey {inherit indent; key = lib.toUpper d.key; action = "act.AdjustPaneSize({ \"${d.dir}\", ${toString step} })";}) + viDirs; + + # Wrap a lua action string so it also pops the key table (return to locked) + andPop = action: "act.Multiple({ ${action}, act.PopKeyTable })"; + + # Standard exit bindings, given the mode's own pop key + exitKeys = {selfKey, indent ? " "}: + lib.concatStrings [ + (luaKey {inherit indent; key = selfKey; action = "act.PopKeyTable";}) + (luaKey {inherit indent; key = "Escape"; action = "act.PopKeyTable";}) + (luaKey {inherit indent; key = "Enter"; action = "act.PopKeyTable";}) + (luaKey {inherit indent; key = "Space"; mods = "ALT"; action = "act.PopKeyTable";}) + ]; + + # ── Status bar ─────────────────────────────────────────────────────────────── + + # Per-mode accent colour (base16) and hint string shown on the left status bar + modes = { + locked = {fg = color.base03; hint = "Alt+Space:enter Alt+hl:focus/tab Alt+1-9:tab Alt+n:split C-ud:scroll";}; + normal = {fg = color.base0D; hint = "p:pane t:tab r:resize s:scroll n:split Esc:exit";}; + pane = {fg = color.base0B; hint = "hjkl:focus n/r:split→ d:split↓ f:zoom x:close Esc:exit";}; + tab = {fg = color.base0C; hint = "hjkl:prev/next 1-9:jump n:new x:close r:rename Esc:exit";}; + resize = {fg = color.base0A; hint = "hjkl:+5 HJKL:+1 Esc:exit";}; + scroll = {fg = color.base0E; hint = "jk:line ud:half hl:page f:search Esc:exit";}; + }; + + # Lua table literal mapping mode name -> { fg, hint } + modeTableEntries = lib.concatStringsSep "\n " (lib.mapAttrsToList (name: m: '' + ["${name}"] = { fg = "${m.fg}", hint = "${m.hint}" }, + '') modes); +in { + options.mods.terminal.wezterm.enable = lib.mkEnableOption "enables wezterm"; + + config = lib.mkIf cfg.wezterm.enable { + programs.wezterm = { + enable = true; + + extraConfig = '' + local wezterm = require("wezterm") + local act = wezterm.action + + -- ─── Helpers ─────────────────────────────────────────────────────────────── + + -- move_focus_or_tab: mirrors Zellij's MoveFocusOrTab. + -- Move focus in direction; if at the edge, switch to the adjacent tab. + local function move_focus_or_tab(direction) + return wezterm.action_callback(function(window, pane) + local neighbour = pane:tab():get_pane_direction(direction) + if neighbour ~= nil then + window:perform_action(act.ActivatePaneDirection(direction), pane) + else + if direction == "Left" then + window:perform_action(act.ActivateTabRelative(-1), pane) + elseif direction == "Right" then + window:perform_action(act.ActivateTabRelative(1), pane) + end + end + end) + end + + -- ─── Status bar ──────────────────────────────────────────────────────────── + + -- Per-mode accent colour and hint text, generated from Nix/stylix palette + local mode_info = { + ${modeTableEntries} + } + + local bg_bar = "${color.base01}" + local fg_text = "${color.base05}" + local bg_main = "${color.base00}" + + wezterm.on("update-status", function(window, _pane) + local kt = window:active_key_table() + local mode = kt or "locked" + local info = mode_info[mode] or mode_info["locked"] + + -- Left: coloured mode badge + hint line + window:set_left_status(wezterm.format({ + { Background = { Color = info.fg } }, + { Foreground = { Color = bg_main } }, + { Attribute = { Intensity = "Bold" } }, + { Text = " " .. mode:upper() .. " " }, + "ResetAttributes", + { Background = { Color = bg_bar } }, + { Foreground = { Color = fg_text } }, + { Text = " " .. info.hint .. " " }, + "ResetAttributes", + })) + + window:set_right_status("") + end) + + -- ─── Appearance ──────────────────────────────────────────────────────────── + + local config = wezterm.config_builder() + + -- Stylix sets the color scheme via its wezterm integration; + -- this is a fallback in case it is not active. + config.color_scheme = "Synthwave (Gogh)" + + config.font = wezterm.font("CommitMono Nerd Font") + config.font_size = 12.0 + + config.window_padding = { left = 6, right = 6, top = 6, bottom = 6 } + + config.default_cursor_style = "BlinkingBar" + config.hide_tab_bar_if_only_one_tab = false + config.use_fancy_tab_bar = false + config.tab_bar_at_bottom = true + + -- ─── Shell ───────────────────────────────────────────────────────────────── + + ${lib.optionalString (shell != null) '' + config.default_prog = { "${shell}" } + ''} + + -- ─── Scrollback ──────────────────────────────────────────────────────────── + + config.scrollback_lines = 10000 + + -- ─── Key bindings ────────────────────────────────────────────────────────── + -- + -- Default mode = "locked" (all keys pass through to the shell). + -- Alt+Space enters "normal" mode (like Zellij). + -- From normal: p→pane t→tab r→resize s→scroll + -- Esc / Enter / Alt+Space → back to locked from any mode. + + config.disable_default_key_bindings = true + + config.keys = { + -- Alt+h/l: move focus or switch tab at edge (Zellij MoveFocusOrTab) + -- Alt+j/k: move focus between panes + ${viDirKeys { + mods = "ALT"; + indent = " "; + mkAction = d: + if d == "Left" || d == "Right" + then "move_focus_or_tab(\"${d}\")" + else "act.ActivatePaneDirection(\"${d}\")"; + }} + -- Alt+1-9: jump to tab by number + ${tabJumpKeys {mods = "ALT"; indent = " ";}} + -- Alt+n: new pane (split right) + { key = "n", mods = "ALT", action = act.SplitHorizontal({ domain = "CurrentPaneDomain" }) }, + -- Alt+f: toggle zoom + { key = "f", mods = "ALT", action = act.TogglePaneZoomState }, + -- Alt+[/]: rotate panes + { key = "[", mods = "ALT", action = act.RotatePanes("CounterClockwise") }, + { key = "]", mods = "ALT", action = act.RotatePanes("Clockwise") }, + -- Alt++/-/=: font size + { key = "+", mods = "ALT", action = act.IncreaseFontSize }, + { key = "-", mods = "ALT", action = act.DecreaseFontSize }, + { key = "=", mods = "ALT", action = act.ResetFontSize }, + -- Alt+i/o: reorder tabs + { key = "i", mods = "ALT", action = act.MoveTabRelative(-1) }, + { key = "o", mods = "ALT", action = act.MoveTabRelative(1) }, + -- Alt+q: quit + { key = "q", mods = "ALT", action = act.QuitApplication }, + -- Ctrl+U/D: scroll half page (matches Alacritty) + { key = "u", mods = "CTRL", action = act.ScrollByPage(-0.5) }, + { key = "d", mods = "CTRL", action = act.ScrollByPage(0.5) }, + -- Copy / paste + { key = "c", mods = "CTRL|SHIFT", action = act.CopyTo("Clipboard") }, + { key = "v", mods = "CTRL|SHIFT", action = act.PasteFrom("Clipboard") }, + -- Enter normal mode + { key = "Space", mods = "ALT", action = act.ActivateKeyTable({ name = "normal", one_shot = false }) }, + } + + config.key_tables = { + + -- ── NORMAL ───────────────────────────────────────────────────────────── + normal = { + { key = "p", action = act.ActivateKeyTable({ name = "pane", one_shot = false }) }, + { key = "t", action = act.ActivateKeyTable({ name = "tab", one_shot = false }) }, + { key = "r", action = act.ActivateKeyTable({ name = "resize", one_shot = false }) }, + { key = "s", action = act.ActivateKeyTable({ name = "scroll", one_shot = false }) }, + { key = "n", action = ${andPop "act.SplitHorizontal({ domain = \"CurrentPaneDomain\" })"} }, + ${exitKeys {selfKey = "Escape";}} }, + + -- ── PANE ─────────────────────────────────────────────────────────────── + pane = { + ${viDirKeys { + mkAction = d: + if d == "Left" || d == "Right" + then "move_focus_or_tab(\"${d}\")" + else "act.ActivatePaneDirection(\"${d}\")"; + }} + { key = "n", action = ${andPop "act.SplitHorizontal({ domain = \"CurrentPaneDomain\" })"} }, + { key = "r", action = ${andPop "act.SplitHorizontal({ domain = \"CurrentPaneDomain\" })"} }, + { key = "d", action = ${andPop "act.SplitVertical({ domain = \"CurrentPaneDomain\" })"} }, + { key = "f", action = ${andPop "act.TogglePaneZoomState"} }, + { key = "z", action = ${andPop "act.TogglePaneZoomState"} }, + { key = "x", action = ${andPop "act.CloseCurrentPane({ confirm = true })"} }, + { key = "Tab", action = act.ActivatePaneDirection("Next") }, + ${exitKeys {selfKey = "p";}} }, + + -- ── TAB ──────────────────────────────────────────────────────────────── + tab = { + { key = "h", action = act.ActivateTabRelative(-1) }, + { key = "k", action = act.ActivateTabRelative(-1) }, + { key = "j", action = act.ActivateTabRelative(1) }, + { key = "l", action = act.ActivateTabRelative(1) }, + { key = "LeftArrow", action = act.ActivateTabRelative(-1) }, + { key = "UpArrow", action = act.ActivateTabRelative(-1) }, + { key = "RightArrow", action = act.ActivateTabRelative(1) }, + { key = "DownArrow", action = act.ActivateTabRelative(1) }, + { key = "Tab", action = act.ActivateTabRelative(1) }, + ${tabJumpKeys {}} + { key = "n", action = ${andPop "act.SpawnTab(\"CurrentPaneDomain\")"} }, + { key = "x", action = ${andPop "act.CloseCurrentTab({ confirm = true })"} }, + { key = "r", action = act.PromptInputLine({ + description = "Rename tab", + action = wezterm.action_callback(function(window, _, line) + if line then window:active_tab():set_title(line) end + window:perform_action(act.PopKeyTable, window:active_pane()) + end), + }) + }, + ${exitKeys {selfKey = "t";}} }, + + -- ── RESIZE ───────────────────────────────────────────────────────────── + resize = { + ${resizeDirKeys {step = 5;}}${resizeShiftKeys {step = 1;}} + { key = "+", action = act.AdjustPaneSize({ "Right", 5 }) }, + { key = "-", action = act.AdjustPaneSize({ "Left", 5 }) }, + { key = "=", action = act.AdjustPaneSize({ "Right", 5 }) }, + ${exitKeys {selfKey = "r";}} }, + + -- ── SCROLL ───────────────────────────────────────────────────────────── + scroll = { + { key = "j", action = act.ScrollByLine(1) }, + { key = "k", action = act.ScrollByLine(-1) }, + { key = "d", action = act.ScrollByPage(0.5) }, + { key = "u", action = act.ScrollByPage(-0.5) }, + { key = "h", action = act.ScrollByPage(-1) }, + { key = "l", action = act.ScrollByPage(1) }, + { key = "DownArrow", action = act.ScrollByLine(1) }, + { key = "UpArrow", action = act.ScrollByLine(-1) }, + { key = "PageDown", action = act.ScrollByPage(1) }, + { key = "PageUp", action = act.ScrollByPage(-1) }, + { key = "f", action = act.Search({ CaseSensitiveString = "" }) }, + { key = "c", mods = "CTRL", action = act.ScrollToBottom }, + ${exitKeys {selfKey = "s";}} }, + } + + return config + ''; + }; + }; +}