mirror of
https://codeberg.org/muon/home.git
synced 2026-03-09 20:03:12 +00:00
448 lines
19 KiB
Nix
448 lines
19 KiB
Nix
{
|
|
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 in the status bar
|
|
# Hints use nerd font glyphs: separators, icons, key labels
|
|
modes = {
|
|
locked = {
|
|
fg = color.base03;
|
|
hint = "⌥: ⌥hl: ⌥1-9: ⌥n: ⌥w: ^ud:";
|
|
};
|
|
normal = {
|
|
fg = color.base0D;
|
|
hint = "t: x: r: c: s: esc:";
|
|
};
|
|
resize = {
|
|
fg = color.base0A;
|
|
hint = "hjkl: HJKL: esc:";
|
|
};
|
|
copy_mode = {
|
|
fg = color.base0E;
|
|
hint = "hjkl: v: V: ^v: y: /: n/p: esc:";
|
|
};
|
|
search_mode = {
|
|
fg = color.base08;
|
|
hint = "type: ^n/^p: ^r: ↵/esc:";
|
|
};
|
|
};
|
|
|
|
# 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 fg_dim = "${color.base03}"
|
|
local bg_main = "${color.base00}"
|
|
|
|
-- Render hints with the key in the mode accent colour and the icon dimmed.
|
|
-- Each hint token is "key:icon"; we colour them individually.
|
|
local function render_hints(hint, accent)
|
|
local cells = {}
|
|
for token in hint:gmatch("%S+") do
|
|
local key, icon = token:match("^(.-):(.)$")
|
|
if key and icon then
|
|
table.insert(cells, { Background = { Color = bg_bar } })
|
|
table.insert(cells, { Foreground = { Color = accent } })
|
|
table.insert(cells, { Text = key })
|
|
table.insert(cells, { Foreground = { Color = fg_text } })
|
|
table.insert(cells, { Text = ":" .. icon .. " " })
|
|
else
|
|
table.insert(cells, { Background = { Color = bg_bar } })
|
|
table.insert(cells, { Foreground = { Color = fg_dim } })
|
|
table.insert(cells, { Text = token .. " " })
|
|
end
|
|
end
|
|
return cells
|
|
end
|
|
|
|
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"]
|
|
|
|
window:set_left_status("")
|
|
|
|
-- Right: coloured hints then mode badge
|
|
local cells = { { Text = " " } }
|
|
for _, c in ipairs(render_hints(info.hint, info.fg)) do
|
|
table.insert(cells, c)
|
|
end
|
|
table.insert(cells, "ResetAttributes")
|
|
table.insert(cells, { Background = { Color = info.fg } })
|
|
table.insert(cells, { Foreground = { Color = bg_bar } })
|
|
table.insert(cells, { Text = "" })
|
|
table.insert(cells, { Foreground = { Color = bg_main } })
|
|
table.insert(cells, { Attribute = { Intensity = "Bold" } })
|
|
table.insert(cells, { Text = " " .. mode:upper() .. " " })
|
|
table.insert(cells, "ResetAttributes")
|
|
window:set_right_status(wezterm.format(cells))
|
|
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.colors = {
|
|
tab_bar = {
|
|
background = "${color.base01}",
|
|
active_tab = { bg_color = "${color.base0D}", fg_color = "${color.base00}", intensity = "Bold" },
|
|
inactive_tab = { bg_color = "${color.base01}", fg_color = "${color.base03}" },
|
|
inactive_tab_hover = { bg_color = "${color.base02}", fg_color = "${color.base04}" },
|
|
new_tab = { bg_color = "${color.base01}", fg_color = "${color.base03}" },
|
|
new_tab_hover = { bg_color = "${color.base02}", fg_color = "${color.base04}" },
|
|
},
|
|
}
|
|
config.tab_bar_at_bottom = false
|
|
|
|
-- ─── 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.
|
|
-- From normal: t→new tab x→close tab p/n→split r→resize mode
|
|
-- 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+w: close current pane
|
|
{ key = "w", mods = "ALT", action = act.CloseCurrentPane({ confirm = true }) },
|
|
-- 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 = "t", action = ${andPop "act.SpawnTab(\"CurrentPaneDomain\")"} },
|
|
{ key = "x", action = ${andPop "act.CloseCurrentTab({ confirm = true })"} },
|
|
{ key = "r", action = act.ActivateKeyTable({ name = "resize", one_shot = false }) },
|
|
{ key = "c", action = ${andPop "act.ActivateCopyMode"} },
|
|
{ key = "s", action = ${andPop "act.Search({ CaseSensitiveString = \"\" })"} },
|
|
${exitKeys {selfKey = "Escape";}} },
|
|
|
|
-- ── 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";}} },
|
|
|
|
-- ── COPY MODE ──────────────────────────────────────────────────────────
|
|
copy_mode = {
|
|
-- movement
|
|
{ key = "h", action = act.CopyMode("MoveLeft") },
|
|
{ key = "j", action = act.CopyMode("MoveDown") },
|
|
{ key = "k", action = act.CopyMode("MoveUp") },
|
|
{ key = "l", action = act.CopyMode("MoveRight") },
|
|
{ key = "LeftArrow", action = act.CopyMode("MoveLeft") },
|
|
{ key = "DownArrow", action = act.CopyMode("MoveDown") },
|
|
{ key = "UpArrow", action = act.CopyMode("MoveUp") },
|
|
{ key = "RightArrow", action = act.CopyMode("MoveRight") },
|
|
{ key = "w", action = act.CopyMode("MoveForwardWord") },
|
|
{ key = "b", action = act.CopyMode("MoveBackwardWord") },
|
|
{ key = "e", action = act.CopyMode("MoveForwardWordEnd") },
|
|
{ key = "0", action = act.CopyMode("MoveToStartOfLine") },
|
|
{ key = "^", action = act.CopyMode("MoveToStartOfLineContent") },
|
|
{ key = "$", action = act.CopyMode("MoveToEndOfLineContent") },
|
|
{ key = "g", action = act.CopyMode("MoveToScrollbackTop") },
|
|
{ key = "G", action = act.CopyMode("MoveToScrollbackBottom") },
|
|
{ key = "Enter", action = act.CopyMode("MoveToStartOfNextLine") },
|
|
{ key = "u", mods = "CTRL", action = act.CopyMode({ MoveByPage = -0.5 }) },
|
|
{ key = "d", mods = "CTRL", action = act.CopyMode({ MoveByPage = 0.5 }) },
|
|
{ key = "b", mods = "CTRL", action = act.CopyMode("PageUp") },
|
|
{ key = "f", mods = "CTRL", action = act.CopyMode("PageDown") },
|
|
{ key = "PageUp", action = act.CopyMode("PageUp") },
|
|
{ key = "PageDown", action = act.CopyMode("PageDown") },
|
|
-- selection
|
|
{ key = "v", action = act.CopyMode({ SetSelectionMode = "Cell" }) },
|
|
{ key = "V", action = act.CopyMode({ SetSelectionMode = "Line" }) },
|
|
{ key = "v", mods = "CTRL", action = act.CopyMode({ SetSelectionMode = "Block" }) },
|
|
{ key = "o", action = act.CopyMode("MoveToSelectionOtherEnd") },
|
|
{ key = "O", action = act.CopyMode("MoveToSelectionOtherEndHoriz") },
|
|
-- yank and exit
|
|
{ key = "y", action = act.Multiple({
|
|
act.CopyTo("ClipboardAndPrimarySelection"),
|
|
act.Multiple({ act.ScrollToBottom, act.CopyMode("Close") }),
|
|
})
|
|
},
|
|
-- search within copy mode
|
|
{ key = "/", action = act.Search({ CaseSensitiveString = "" }) },
|
|
{ key = "n", action = act.CopyMode("NextMatch") },
|
|
{ key = "p", action = act.CopyMode("PriorMatch") },
|
|
-- exit
|
|
{ key = "q", action = act.Multiple({ act.ScrollToBottom, act.CopyMode("Close") }) },
|
|
{ key = "Escape", action = act.Multiple({ act.ScrollToBottom, act.CopyMode("Close") }) },
|
|
{ key = "c", mods = "CTRL", action = act.Multiple({ act.ScrollToBottom, act.CopyMode("Close") }) },
|
|
},
|
|
|
|
-- ── SEARCH MODE ────────────────────────────────────────────────────────
|
|
search_mode = {
|
|
-- navigate matches
|
|
{ key = "Enter", action = act.CopyMode("AcceptPattern") },
|
|
{ key = "n", mods = "CTRL", action = act.CopyMode("NextMatch") },
|
|
{ key = "p", mods = "CTRL", action = act.CopyMode("PriorMatch") },
|
|
{ key = "PageUp", action = act.CopyMode("PriorMatchPage") },
|
|
{ key = "PageDown", action = act.CopyMode("NextMatchPage") },
|
|
{ key = "UpArrow", action = act.CopyMode("PriorMatch") },
|
|
{ key = "DownArrow", action = act.CopyMode("NextMatch") },
|
|
-- cycle match type (case-sensitive / insensitive / regex)
|
|
{ key = "r", mods = "CTRL", action = act.CopyMode("CycleMatchType") },
|
|
-- clear search input
|
|
{ key = "u", mods = "CTRL", action = act.CopyMode("ClearPattern") },
|
|
-- Escape: dismiss search bar, return to copy mode at current match
|
|
{ key = "Escape", action = act.CopyMode("AcceptPattern") },
|
|
},
|
|
}
|
|
|
|
return config
|
|
'';
|
|
};
|
|
};
|
|
}
|