From 8f32fa7396e1b3e6460d7b24df6d5162c961f261 Mon Sep 17 00:00:00 2001 From: Stefanie Jane Date: Sun, 13 Sep 2026 14:18:41 -0700 Subject: [PATCH 1/4] fix(moniker): read agent status from herdr's event envelope Herdr delivers HERDR_PLUGIN_EVENT_JSON as {"event": ..., "data": {...}}, with agent_status and pane_id inside data. The handler read them off the top level, so every pane.agent_status_changed hook exited before naming anything and tabs stayed numbered. Unwrap data when present, keeping the flat shape as a fallback. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01E6ZMPccKUq6uyexvfxSWaG --- herdr/plugins/moniker/moniker.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/herdr/plugins/moniker/moniker.py b/herdr/plugins/moniker/moniker.py index 3ca8c72..8a51a9e 100644 --- a/herdr/plugins/moniker/moniker.py +++ b/herdr/plugins/moniker/moniker.py @@ -221,9 +221,11 @@ def name_pane(pane_id: str, force: bool) -> None: def on_event() -> None: try: - event = json.loads(os.environ.get("HERDR_PLUGIN_EVENT_JSON") or "{}") + envelope = json.loads(os.environ.get("HERDR_PLUGIN_EVENT_JSON") or "{}") except ValueError: return + # Herdr wraps the payload: {"event": "...", "data": {"agent_status": ...}}. + event = envelope.get("data") if isinstance(envelope.get("data"), dict) else envelope if event.get("agent_status") != "working": return pane_id = event.get("pane_id") or os.environ.get("HERDR_PANE_ID") From 829ed199cf90de18b13e60b45b58ace2fc081ab8 Mon Sep 17 00:00:00 2001 From: Stefanie Jane Date: Tue, 15 Sep 2026 16:29:20 -0700 Subject: [PATCH 2/4] feat(helix): add a Helix setup shaped like the AstroNvim config Helix 25.07 config and language wiring, linked at ~/.config/helix by the base layer. Motions and minor modes stay stock Helix; the Space leader mirrors the Neovim groups (f find, b buffers, l language tools, g git, u toggles, x diagnostics, a agents) plus Ctrl-s, ]b/[b and Esc collapsing to one cursor. Space a y copies a file:line reference for pasting at an agent, Space g g opens lazygit in a tmux popup, Space g b blames the line. languages.toml swaps in the servers Mason already installs for Neovim (vtsls, docker-language-server, ty and ruff) and wires the same formatters conform uses (prettier, stylua, shfmt). sh/helix.sh appends Mason's bin directory to PATH so Helix resolves them with no second install. editor.auto-format stays off as the master switch, matching g.autoformat in the Neovim config, with every formatted language opted in so Space u F flips them together. Two Helix quirks shaped the bindings: languages.toml merges only three levels deep, so the rust-analyzer config block is copied whole before adding clippy, and a :sh command expands its first word only when the whole word is an expansion, so Space a y parks its variables behind set --. Theme is silkcircuit-glow; the SilkCircuit installer owns the themes directory, which is why the two files are linked individually. Co-Authored-By: Claude Fable 5.1 --- dotbot.d/base.yaml | 5 ++ helix/config.toml | 188 +++++++++++++++++++++++++++++++++++++++++++ helix/languages.toml | 143 ++++++++++++++++++++++++++++++++ macos/Brewfile | 1 + packages.conf | 2 + sh/helix.sh | 21 +++++ 6 files changed, 360 insertions(+) create mode 100644 helix/config.toml create mode 100644 helix/languages.toml create mode 100644 sh/helix.sh diff --git a/dotbot.d/base.yaml b/dotbot.d/base.yaml index 39220e7..64753c1 100644 --- a/dotbot.d/base.yaml +++ b/dotbot.d/base.yaml @@ -80,6 +80,11 @@ ~/.config/nvim: path: nvim force: true + # Helix reads these two files from ~/.config/helix, and the SilkCircuit + # installer drops its themes into ~/.config/helix/themes, so the files + # are linked rather than the directory. + ~/.config/helix/config.toml: helix/config.toml + ~/.config/helix/languages.toml: helix/languages.toml ~/bin: bin ~/.tmux/plugins/tpm: tpm diff --git a/helix/config.toml b/helix/config.toml new file mode 100644 index 0000000..a8577e2 --- /dev/null +++ b/helix/config.toml @@ -0,0 +1,188 @@ +# Helix, tuned for hands that learned AstroNvim. +# +# Motions, selections and the g/m/z minor modes stay stock Helix: the +# select-then-act model is the reason to be here, so nothing below tries +# to turn it back into Vim. What does carry over is the Space leader +# layout from the Neovim config in this repo (f find, b buffers, l language +# tools, g git, u toggles, x diagnostics, a agents) plus a handful of +# reflexes (Ctrl-s, ]b/[b, Esc clearing the selection). Space e is already +# the file explorer and Space k is hover, so those needed no remap. +# +# The SilkCircuit installer owns ~/.config/helix/themes. `:config-reload` +# picks up edits here; `hx --health ` shows what each language +# resolved to. + +theme = "silkcircuit-glow" + +[editor] +line-number = "relative" +cursorline = true +scrolloff = 6 +bufferline = "multiple" +color-modes = true +true-color = true +undercurl = true +popup-border = "all" +text-width = 120 +completion-timeout = 50 +end-of-line-diagnostics = "hint" +# Master switch, like g.autoformat in the Neovim config. Every language in +# languages.toml with a formatter sets auto-format = true, so flipping this +# with Space u F turns format-on-save on for all of them at once. +auto-format = false + +[editor.inline-diagnostics] +cursor-line = "warning" + +[editor.lsp] +display-inlay-hints = true + +[editor.cursor-shape] +normal = "block" +insert = "bar" +select = "underline" + +[editor.file-picker] +# Neo-tree shows dotfiles; so does this picker. .gitignore still applies. +hidden = false + +[editor.indent-guides] +render = true +character = "│" +skip-levels = 1 + +[editor.statusline] +left = ["mode", "spinner", "version-control", "file-name", "read-only-indicator", "file-modification-indicator"] +center = [] +right = ["diagnostics", "selections", "register", "position", "position-percentage", "total-line-numbers", "file-type"] +separator = "│" +mode.normal = "NORMAL" +mode.insert = "INSERT" +mode.select = "SELECT" + +[editor.soft-wrap] +enable = false + +# ───────────────────────────────────────────────────────────────────── +# Reflexes +# ───────────────────────────────────────────────────────────────────── + +[keys.normal] +C-s = ":write" +C-q = ":quit-all!" +C-r = "redo" +C-h = "jump_view_left" +C-j = "jump_view_down" +C-k = "jump_view_up" +C-l = "jump_view_right" +# Vim's Esc clears the highlight; here it collapses to one cursor. +esc = ["collapse_selection", "keep_primary_selection"] + +[keys.normal."]"] +b = "goto_next_buffer" + +[keys.normal."["] +b = "goto_previous_buffer" + +[keys.insert] +C-s = ["normal_mode", ":write"] + +# ───────────────────────────────────────────────────────────────────── +# Space leader, AstroNvim layout +# +# Helix defaults kept as-is: Space e/E explorer, Space k hover, Space r +# rename, Space s/S symbols, Space d/D diagnostics, Space j jumplist, +# Space h references, Space ' last picker, Space ? palette, Space y/p/P/R +# clipboard, Space G debug. Space a becomes the agents group below, so code +# actions live at Space l a, as in AstroNvim. +# ───────────────────────────────────────────────────────────────────── + +[keys.normal.space] +w = ":write" +q = ":quit" +Q = ":quit-all" +n = ":new" +c = ":buffer-close" +C = ":buffer-close!" +"/" = "toggle_comments" +"|" = "vsplit" +"\\" = "hsplit" + +# Find +[keys.normal.space.f] +f = "file_picker" +F = "file_picker_in_current_directory" +w = "global_search" +b = "buffer_picker" +s = "symbol_picker" +S = "workspace_symbol_picker" +j = "jumplist_picker" +g = "changed_file_picker" +# Word under the cursor, searched across the project: select it, push it +# into the search register, open global search and paste it in. The picker +# searches as you type, so no Enter: that would jump to the first hit. +c = "@miw*fw/" + +# Buffers +[keys.normal.space.b] +b = "buffer_picker" +n = "goto_next_buffer" +p = "goto_previous_buffer" +d = ":buffer-close" +c = ":buffer-close-others" +C = ":buffer-close-all" + +# Language tools +[keys.normal.space.l] +a = "code_action" +r = "rename_symbol" +h = "hover" +f = ":format" +s = "symbol_picker" +G = "workspace_symbol_picker" +d = "diagnostics_picker" +D = "workspace_diagnostics_picker" +R = "goto_reference" +i = "goto_implementation" +y = "goto_type_definition" +l = ":lsp-restart" +L = ":log-open" + +# Git. lazygit rides a tmux popup because Helix has no terminal of its +# own; outside tmux the shell one-liner says so instead of failing quietly. +[keys.normal.space.g] +g = ':sh if [ -n "$TMUX" ]; then tmux display-popup -E -w 90%% -h 90%% -d "$PWD" lazygit; else echo "lazygit popup needs tmux: C-z, lazygit, fg"; fi' +f = ':sh if [ -n "$TMUX" ]; then tmux display-popup -E -w 90%% -h 90%% -d "$PWD" lazygit -f "%{buffer_name}"; else echo "lazygit popup needs tmux: C-z, lazygit, fg"; fi' +s = "changed_file_picker" +b = ':sh git log -1 --format="%%h %%an, %%ar%%n%%s" $(git blame --porcelain -L %{cursor_line},+1 -- %{buffer_name} | head -1 | cut -d" " -f1)' + +# Agents: copy a file:line reference the way Claude Code likes it, spanning +# the selection when it covers more than one line. The variables sit behind +# `set --` because Helix expands the first word of a :sh command only when +# the whole word is an expansion: `s=%{selection_line_start}` stays literal. +[keys.normal.space.a] +y = ':sh set -- %{selection_line_start} %{selection_line_end} %{buffer_name}; if [ "$1" = "$2" ]; then r="$1"; else r="$1-$2"; fi; if command -v pbcopy >/dev/null 2>&1; then printf "%%s" "$3:$r" | pbcopy; elif command -v wl-copy >/dev/null 2>&1; then printf "%%s" "$3:$r" | wl-copy; else printf "%%s" "$3:$r" | xclip -selection clipboard; fi; echo "copied $3:$r"' + +# UI toggles +[keys.normal.space.u] +n = ":toggle line-number relative absolute" +w = ":toggle soft-wrap.enable" +i = ":toggle lsp.display-inlay-hints" +g = ":toggle indent-guides.render" +h = ":toggle whitespace.render all none" +c = ":toggle cursorline" +b = ":toggle bufferline multiple never" +m = ":toggle mouse" +F = ":toggle auto-format" +d = [":toggle end-of-line-diagnostics hint disable", ":toggle inline-diagnostics.cursor-line warning disable"] + +# Diagnostics lists (Trouble's corner) +[keys.normal.space.x] +x = "diagnostics_picker" +X = "workspace_diagnostics_picker" + +[keys.select.space] +"/" = "toggle_comments" + +[keys.select.space.a] +y = ':sh set -- %{selection_line_start} %{selection_line_end} %{buffer_name}; if [ "$1" = "$2" ]; then r="$1"; else r="$1-$2"; fi; if command -v pbcopy >/dev/null 2>&1; then printf "%%s" "$3:$r" | pbcopy; elif command -v wl-copy >/dev/null 2>&1; then printf "%%s" "$3:$r" | wl-copy; else printf "%%s" "$3:$r" | xclip -selection clipboard; fi; echo "copied $3:$r"' diff --git a/helix/languages.toml b/helix/languages.toml new file mode 100644 index 0000000..231ba71 --- /dev/null +++ b/helix/languages.toml @@ -0,0 +1,143 @@ +# Language servers and formatters, mirroring the AstroNvim language packs. +# +# Helix ships sensible defaults for every language here; this file only +# swaps servers for the ones Mason already installs (see sh/helix.sh for +# how those reach PATH), trims servers that are not installed so the +# health report stays honest, and wires the same formatters conform uses +# in Neovim. Each formatted language sets auto-format = true so the global +# editor.auto-format switch in config.toml governs all of them at once. + +# ───────────────────────────────────────────────────────────────────── +# Servers Helix does not define out of the box +# ───────────────────────────────────────────────────────────────────── + +[language-server.vtsls] +command = "vtsls" +args = ["--stdio"] + +[language-server.vtsls.config] +# Match the Neovim setup: rename and imports from vtsls, formatting from +# prettier below. +typescript.format.enable = false +javascript.format.enable = false + +# Docker's own server, the one Mason installs; it covers Dockerfiles and +# compose files in a single process. +[language-server.docker-language-server] +command = "docker-language-server" +args = ["start", "--stdio"] + +# Helix merges languages.toml three levels deep, so a user [config] table +# replaces the default one wholesale. This is the 25.07.1 default block plus +# clippy as the check command. +[language-server.rust-analyzer.config] +check.command = "clippy" +files.watcher = "server" +inlayHints.bindingModeHints.enable = false +inlayHints.closingBraceHints.minLines = 10 +inlayHints.closureReturnTypeHints.enable = "with_block" +inlayHints.discriminantHints.enable = "fieldless" +inlayHints.lifetimeElisionHints.enable = "skip_trivial" +inlayHints.typeHints.hideClosureInitialization = false + +# ───────────────────────────────────────────────────────────────────── +# Languages +# ───────────────────────────────────────────────────────────────────── + +[[language]] +name = "python" +language-servers = ["ty", "ruff"] +auto-format = true + +[[language]] +name = "rust" +auto-format = true + +[[language]] +name = "typescript" +language-servers = ["vtsls"] +formatter = { command = "prettier", args = ["--stdin-filepath", "%{buffer_name}"] } +auto-format = true + +[[language]] +name = "tsx" +language-servers = ["vtsls", "tailwindcss-ls"] +formatter = { command = "prettier", args = ["--stdin-filepath", "%{buffer_name}"] } +auto-format = true + +[[language]] +name = "javascript" +language-servers = ["vtsls"] +formatter = { command = "prettier", args = ["--stdin-filepath", "%{buffer_name}"] } +auto-format = true + +[[language]] +name = "jsx" +language-servers = ["vtsls", "tailwindcss-ls"] +formatter = { command = "prettier", args = ["--stdin-filepath", "%{buffer_name}"] } +auto-format = true + +[[language]] +name = "json" +formatter = { command = "prettier", args = ["--stdin-filepath", "%{buffer_name}"] } +auto-format = true + +[[language]] +name = "jsonc" +formatter = { command = "prettier", args = ["--stdin-filepath", "%{buffer_name}"] } +auto-format = true + +[[language]] +name = "css" +language-servers = ["vscode-css-language-server", "tailwindcss-ls"] +formatter = { command = "prettier", args = ["--stdin-filepath", "%{buffer_name}"] } +auto-format = true + +[[language]] +name = "scss" +formatter = { command = "prettier", args = ["--stdin-filepath", "%{buffer_name}"] } +auto-format = true + +[[language]] +name = "html" +language-servers = ["vscode-html-language-server", "tailwindcss-ls"] +formatter = { command = "prettier", args = ["--stdin-filepath", "%{buffer_name}"] } +auto-format = true + +[[language]] +name = "yaml" +language-servers = ["yaml-language-server"] +formatter = { command = "prettier", args = ["--stdin-filepath", "%{buffer_name}"] } +auto-format = true + +[[language]] +name = "toml" +language-servers = ["taplo"] +auto-format = true + +[[language]] +name = "markdown" +language-servers = ["marksman"] +formatter = { command = "prettier", args = ["--stdin-filepath", "%{buffer_name}"] } +auto-format = true +text-width = 120 +soft-wrap = { enable = true, wrap-at-text-width = true } + +[[language]] +name = "lua" +formatter = { command = "stylua", args = ["--search-parent-directories", "--stdin-filepath", "%{buffer_name}", "-"] } +auto-format = true + +[[language]] +name = "bash" +# shfmt reads the repo .editorconfig once it knows the file name. +formatter = { command = "shfmt", args = ["--filename", "%{buffer_name}"] } +auto-format = true + +[[language]] +name = "dockerfile" +language-servers = ["docker-language-server"] + +[[language]] +name = "docker-compose" +language-servers = ["docker-language-server", "yaml-language-server"] diff --git a/macos/Brewfile b/macos/Brewfile index c89f365..be32cac 100644 --- a/macos/Brewfile +++ b/macos/Brewfile @@ -18,6 +18,7 @@ brew "git-delta" # Syntax-highlighting pager for git brew "gnu-sed" # GNU implementation of the famous stream editor brew "go" # Open source programming language brew "grep" # GNU grep, egrep and fgrep +brew "helix" # Post-modern modal text editor brew "btop" # Resource monitor (C++ htop/top alternative) brew "htop" # Improved top (interactive process viewer) brew "jq" # Lightweight and flexible command-line JSON processor diff --git a/packages.conf b/packages.conf index ba349b1..4f0d969 100644 --- a/packages.conf +++ b/packages.conf @@ -46,6 +46,7 @@ golang-backports desktop ppa=ppa:longsleep/golang-backports git-core desktop,server ppa=ppa:git-core/ppa fastfetch desktop ppa=ppa:zhangsongcui3371/fastfetch +helix-editor desktop,server ppa=ppa:maintainers/helix-editor [Basic development tools] build-essential desktop,server apt pacman=base-devel @@ -56,6 +57,7 @@ git desktop,server apt=git-core pacman winget=Git.Git go desktop apt=golang-go pacman winget=GoLang.Go inetutils desktop pacman neovim desktop,server apt pacman winget=Neovim.Neovim +helix desktop,server apt pacman winget=Helix.Helix ninja desktop apt=ninja-build pacman winget=Ninja-build.Ninja pkg-config desktop,server apt pacman python desktop,server apt=python-is-python3 pacman winget=Python.Python.3.12 diff --git a/sh/helix.sh b/sh/helix.sh new file mode 100644 index 0000000..28f733b --- /dev/null +++ b/sh/helix.sh @@ -0,0 +1,21 @@ +# helix.sh +# Helix borrows Neovim's toolbox. +# https://helix-editor.com +# +# Mason installs every language server and formatter the Neovim config +# uses under ~/.local/share/nvim/mason/bin, and Helix resolves the same +# binaries by name on PATH. Appending that directory means ruff, ty, vtsls, +# marksman, taplo, prettier and friends light up in Helix with no second +# install, while brew, rustup and proto keep winning for anything they also +# ship. `hx --health ` shows what each language resolved to. +# +# No role guard: a box without Mason simply has nothing to append. + +_mason_bin="${HOME}/.local/share/nvim/mason/bin" +if [[ -d "${_mason_bin}" ]]; then + case ":${PATH}:" in + *":${_mason_bin}:"*) ;; + *) export PATH="${PATH}:${_mason_bin}" ;; + esac +fi +unset _mason_bin From 59e678dbe1af0d6e2b3b31d63fae12ad7071d58c Mon Sep 17 00:00:00 2001 From: Stefanie Jane Date: Tue, 15 Sep 2026 16:29:20 -0700 Subject: [PATCH 3/4] docs(helix): document the Helix setup and its Neovim translation Adds docs/helix/ to the VitePress site with the structure, quick start, a coming-from-Neovim table, every Space group, the language server map and the gotchas the config works around. The README gains a Helix row in the core tools table and a line in the repository tree, and helix/ gets the short README the other tool directories carry. Co-Authored-By: Claude Fable 5.1 --- README.md | 16 +-- docs/.vitepress/config.ts | 7 ++ docs/helix/index.md | 214 ++++++++++++++++++++++++++++++++++++++ helix/README.md | 11 ++ 4 files changed, 241 insertions(+), 7 deletions(-) create mode 100644 docs/helix/index.md create mode 100644 helix/README.md diff --git a/README.md b/README.md index 2d3fec9..dd618d8 100755 --- a/README.md +++ b/README.md @@ -43,13 +43,14 @@ This README is the tour. The field manual lives at ### 📊 Core Development -| Tool | Description | Features | -| -------------------------------------------------------------------- | -------------------- | --------------------------------------------------------------------------- | -| 📝 **[AstroNvim v5](https://astronvim.com/)** | Neovim configuration | • IDE-like features
• Avante.nvim AI assistant
• SilkCircuit theme | -| 👻 **[Ghostty](https://ghostty.org/)** | Terminal emulator | • GPU-accelerated
• SilkCircuit theme
• Native macOS/Linux | -| 🌌 **[Starship](https://starship.rs/)** | Cross-shell prompt | • SilkCircuit gradient theme
• Git status integration
• Context-aware | -| 🖥️ **[Tmux](https://github.com/tmux/tmux)** | Terminal multiplexer | • Custom key bindings
• SilkCircuit color scheme
• Session management | -| 🤖 **[Claude Code](https://docs.anthropic.com/en/docs/claude-code)** | AI pair programmer | • Custom status line
• Security hooks
• Project-aware assistance | +| Tool | Description | Features | +| -------------------------------------------------------------------- | ------------------------ | ------------------------------------------------------------------------------------------- | +| 📝 **[AstroNvim v5](https://astronvim.com/)** | Neovim configuration | • IDE-like features
• Avante.nvim AI assistant
• SilkCircuit theme | +| 🌙 **[Helix](https://helix-editor.com/)** | Modal editor, no plugins | • AstroNvim-shaped Space leader
• Shares Mason's language servers
• SilkCircuit theme | +| 👻 **[Ghostty](https://ghostty.org/)** | Terminal emulator | • GPU-accelerated
• SilkCircuit theme
• Native macOS/Linux | +| 🌌 **[Starship](https://starship.rs/)** | Cross-shell prompt | • SilkCircuit gradient theme
• Git status integration
• Context-aware | +| 🖥️ **[Tmux](https://github.com/tmux/tmux)** | Terminal multiplexer | • Custom key bindings
• SilkCircuit color scheme
• Session management | +| 🤖 **[Claude Code](https://docs.anthropic.com/en/docs/claude-code)** | AI pair programmer | • Custom status line
• Security hooks
• Project-aware assistance | ### 🎯 Modern CLI Tools @@ -78,6 +79,7 @@ This README is the tour. The field manual lives at dotfiles/ ├── nvim/ # AstroNvim v5 configuration (→ ~/.config/nvim) │ └── lua/plugins/ # Plugin configs (silkcircuit, avante, treesitter, …) +├── helix/ # Helix config + languages (→ ~/.config/helix/*.toml) ├── zsh/ # Zsh configuration (zshrc + completion) ├── bash/ # Bash configuration (profile + bashrc.local) ├── sh/ # 30 modular shell scripts (git, docker, k8s, macos, …) diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts index 2656a9a..d6d107b 100644 --- a/docs/.vitepress/config.ts +++ b/docs/.vitepress/config.ts @@ -21,6 +21,7 @@ export default defineConfig({ { text: 'Guide', link: '/getting-started/' }, { text: 'Shell', link: '/shell/' }, { text: 'Neovim', link: '/neovim/' }, + { text: 'Helix', link: '/helix/' }, { text: 'Tools', link: '/tools/' }, { text: 'Reference', @@ -88,6 +89,12 @@ export default defineConfig({ ], }, ], + '/helix/': [ + { + text: 'Helix', + items: [{ text: 'Overview', link: '/helix/' }], + }, + ], '/tools/': [ { text: 'CLI Tools', diff --git a/docs/helix/index.md b/docs/helix/index.md new file mode 100644 index 0000000..2163b35 --- /dev/null +++ b/docs/helix/index.md @@ -0,0 +1,214 @@ +# Helix + +Helix 25.07, painted in SilkCircuit glow, with the Space leader laid out the way AstroNvim taught your hands + +## Overview + +[Helix](https://helix-editor.com) is a modal editor with no plugin system: tree-sitter, LSP, pickers, multiple cursors +and a file explorer are built in, and the whole setup is two TOML files. It is Kakoune-shaped rather than Vim-shaped, so +you select first and act second. That model is the reason to try it, and the config here leaves it alone. What it does +carry over from the [Neovim setup](../neovim/) is the muscle memory that has nothing to do with motions: the Space +groups, `Ctrl-s`, `]b` and `[b`, and `Esc` clearing your selection. + +What you get: + +- The same language servers and formatters as Neovim: Rust, Python (ruff and ty), TypeScript (vtsls), HTML, CSS, + Tailwind, JSON, YAML, TOML, Markdown, Bash, Lua and Docker, with prettier, stylua and shfmt for formatting +- One master switch for format-on-save, off by default like the Neovim config, toggled with `Space u F` +- Inlay hints, inline diagnostics on the cursor line, a bufferline when more than one file is open, a statusline that + shows the git branch +- lazygit in a tmux popup, one-line git blame, and a `Space a y` that copies a `file:line` reference for pasting at an + agent + +## Structure + +``` +helix/ +├── config.toml # Editor options and keymap (→ ~/.config/helix/config.toml) +├── languages.toml # Language servers and formatters (→ ~/.config/helix/languages.toml) +└── README.md +sh/helix.sh # Puts Mason's binaries on PATH so Helix finds the servers +``` + +The SilkCircuit installer drops its five Helix themes into `~/.config/helix/themes/`, which is why the two files are +linked individually rather than the directory. Pick another variant any time with `:theme silkcircuit-neon` (also +`vibrant`, `soft` and `dawn`); the tracked default is `glow`. + +## Quick start + +``` +hx . # File explorer at the project root +hx file.py # Open a file + +Space f f # Find files +Space f w # Grep the project +Space f c # Grep the word under the cursor +Space e # File explorer +Space x x # Diagnostics for this buffer +Space l f # Format buffer +Space g g # lazygit (tmux popup) +Space ? # Command palette, searchable by description +:config-reload # After editing config.toml +hx --health python # What a language resolved to +``` + +## Coming from Neovim + +The unlearning is smaller than it looks. Everything that is not in this table works the way you expect. + +| Habit | Helix | +| ---------------------- | -------------------------------------------------------------------------------------------------- | +| `dw`, `ciw`, `yap` | Select first, then act: `wd`, `miwc`, `mapy`. `w`, `e`, `b` and friends leave a selection behind | +| `v` then move | You are always selecting. `v` toggles select mode, where motions extend instead of replace | +| `x` | Selects the whole line (repeat to grow). `d` deletes what is selected | +| `%` | Selects the whole file. `mm` jumps to the matching bracket | +| `Ctrl-r` | `U` redoes, and `Ctrl-r` is mapped to it too | +| `K` for hover | `Space k` (or `Space l h`). `K` keeps selections matching a regex, which you will want | +| `.` repeat | Repeats the last insert. Multiple cursors cover most of what `.` did: `s` splits a selection | +| | into one cursor per regex match, `C` copies the cursor down, `,` collapses back to one | +| `:%s/a/b/g` | `%` to select all, `s` to pick the matches, then `c` and type. Live, with every match visible | +| `f`, `t` | Not confined to the line | +| `:terminal`, lazygit | No terminal. `Space g g` opens lazygit in a tmux popup; anywhere else `Ctrl-z` drops you to the | +| | shell and `fg` brings Helix back | +| `:Mason`, `:Lazy` | Nothing to manage. `hx --health` reports what each language found on PATH | +| which-key | Press `Space` and wait; the infobox lists the group. Same for `g`, `m`, `z`, `Ctrl-w` | +| `q:`, `:help` | `Space ?` searches every command by description | +| `gd`, `gr`, `gi`, `gy` | Same keys. `gd` definition, `gr` references, `gi` implementation, `gy` type definition | +| `]d`, `[d`, `]g`, `[g` | Same keys: diagnostics and git hunks. Also `]f` function, `]t` type, `]a` argument, `]p` paragraph | + +## Keybindings + +Notation: `Space` is the leader, `C-x` is Ctrl. Mode is normal unless a table says otherwise. Mappings marked (ours) +come from `helix/config.toml`; the rest are Helix defaults kept because they already matched. + +### Files and windows + +| Key | Action | +| ----------------------- | --------------------------------------------------------------- | +| `Space w` | Save (ours) | +| `Space q` / `Space Q` | Close view / close all views (ours) | +| `Space n` | New scratch buffer (ours) | +| `Space c` / `Space C` | Close buffer / force close (ours) | +| `Space e` / `Space E` | File explorer at workspace root / at this file's directory | +| `Space \|` / `Space \\` | Vertical / horizontal split (ours) | +| `C-h` `C-j` `C-k` `C-l` | Move between splits (ours) | +| `C-w` | Window mode: `v` `s` split, `q` close, `o` only, `H J K L` swap | +| `C-s` | Save, in normal and insert mode (ours) | +| `C-q` | Quit everything, discarding changes (ours, same as AstroNvim) | +| `]b` / `[b` | Next / previous buffer (ours) | +| `Esc` | Collapse to one cursor, drop extra selections (ours) | + +### Find (`Space f`) + +| Key | Action | +| ----------- | --------------------------------- | +| `Space f f` | Files in the workspace | +| `Space f F` | Files under the current directory | +| `Space f w` | Grep the workspace, live | +| `Space f c` | Grep the word under the cursor | +| `Space f b` | Buffers | +| `Space f s` | Symbols in this file | +| `Space f S` | Symbols in the workspace | +| `Space f j` | Jumplist | +| `Space f g` | Files changed in git | +| `Space '` | Reopen the last picker | + +### Buffers (`Space b`) + +| Key | Action | +| ------------------------- | ------------------------ | +| `Space b b` | Buffer picker | +| `Space b n` / `Space b p` | Next / previous buffer | +| `Space b d` | Close this buffer | +| `Space b c` | Close every other buffer | +| `Space b C` | Close all buffers | + +### Language tools (`Space l`) + +| Key | Action | +| ------------------------- | -------------------------------------- | +| `Space l a` | Code action | +| `Space l r` / `Space r` | Rename symbol | +| `Space l h` / `Space k` | Hover documentation | +| `Space l f` | Format buffer | +| `Space l s` / `Space l G` | Document / workspace symbols | +| `Space l d` / `Space l D` | Document / workspace diagnostics | +| `Space l R` | References | +| `Space l i` / `Space l y` | Implementation / type definition | +| `Space l l` | Restart the language servers | +| `Space l L` | Open the Helix log | +| `Space x x` / `Space x X` | Diagnostics picker, buffer / workspace | + +### Git (`Space g`) + +| Key | Action | +| ----------- | ------------------------------------------------------- | +| `Space g g` | lazygit in a tmux popup | +| `Space g f` | lazygit filtered to this file's history | +| `Space g s` | Picker of files changed in the working tree | +| `Space g b` | Blame the current line: commit, author, age and subject | +| `]g` / `[g` | Next / previous hunk | + +Outside tmux, `Space g g` prints a reminder instead of failing quietly: `Ctrl-z`, run lazygit, `fg`. + +### Agents (`Space a`) + +| Key | Action | +| ----------- | -------------------------------------------------------------------------------------------------------- | +| `Space a y` | Copy `path:line` for the cursor, or `path:start-end` for a multi-line selection, to the system clipboard | + +Works in normal and select mode. It uses `pbcopy`, `wl-copy` or `xclip`, whichever the box has. + +### Toggles (`Space u`) + +| Key | Toggles | +| ----------- | ------------------------------------------ | +| `Space u n` | Relative / absolute line numbers | +| `Space u w` | Soft wrap | +| `Space u i` | Inlay hints | +| `Space u g` | Indent guides | +| `Space u h` | Visible whitespace | +| `Space u c` | Cursor line highlight | +| `Space u b` | Bufferline | +| `Space u m` | Mouse | +| `Space u d` | Inline and end-of-line diagnostics | +| `Space u F` | Format on save, for every language at once | + +### Kept from Helix + +`Space y`, `Space p`, `Space P` and `Space R` move text through the system clipboard. `Space j` is the jumplist, +`Space h` selects every reference to the symbol under the cursor, `Space G` is the debugger, `Space /` comments the +selection (matching AstroNvim, and replacing Helix's `Space c`). Two defaults moved: `Space a` is the agents group, so +code actions are `Space l a`, and `Space w` saves, so window mode is `Ctrl-w`. + +## Language servers + +Everything below resolves to the binaries Mason installed for Neovim under `~/.local/share/nvim/mason/bin`, which +`sh/helix.sh` appends to PATH. Nothing gets installed twice, and a box that has never run Neovim shows the gaps in +`hx --health`. + +| Language | Server(s) | Formatter | +| ---------------------- | ----------------------------------------------------------- | ------------------------------- | +| Rust | rust-analyzer, with clippy as the check command | LSP | +| Python | ty, ruff | LSP | +| TypeScript, JavaScript | vtsls (tsx and jsx also get tailwindcss-ls) | prettier | +| HTML, CSS, SCSS | vscode html and css servers, tailwindcss-ls | prettier | +| JSON, YAML, Markdown | vscode-json-language-server, yaml-language-server, marksman | prettier | +| TOML | taplo | LSP | +| Lua | lua-language-server | stylua | +| Bash | bash-language-server | shfmt, honoring `.editorconfig` | +| Dockerfile, Compose | docker-language-server (compose adds yaml-language-server) | none | + +Formatting is off on save until you flip `Space u F`, matching the Neovim config. `Space l f` formats on demand. Helix +has no separate linter hook, so the markdownlint and yamllint passes that nvim-lint runs in Neovim stay in `make lint`. + +## Gotchas + +- `hx --health` warns that `~/.config/helix/runtime` does not exist. That is where a source build keeps grammars; the + Homebrew install ships them elsewhere and the warning is noise. +- Helix expands the first word of a `:sh` command only when the whole word is an expansion, so `s=%{cursor_line}` stays + literal there. `Space a y` goes through `set --` for exactly that reason. +- `git blame -l` prefixes boundary commits with `^`; `Space g b` uses `--porcelain` to get a clean hash. +- `Space f c` deliberately does not press Enter: global search runs as you type, and Enter would jump to the first match + instead of leaving the picker open. +- Run `make install` after pulling: it links the two files and lets the SilkCircuit installer lay down the themes. diff --git a/helix/README.md b/helix/README.md new file mode 100644 index 0000000..a6d25c4 --- /dev/null +++ b/helix/README.md @@ -0,0 +1,11 @@ +# Helix + +Helix 25.07 in the SilkCircuit glow theme, with the Space leader laid out like the AstroNvim config next door. +`~/.config/helix/config.toml` and `languages.toml` link here. + +- `config.toml` holds editor options and the keymap. Motions stay stock Helix; the Space groups mirror Neovim. +- `languages.toml` wires the language servers and formatters, the same binaries the Neovim config installs through + Mason. `sh/helix.sh` puts them on PATH. +- `~/.config/helix/themes/` is filled by the SilkCircuit installer and is not tracked here. + +The full tour lives in `docs/helix/`. From c9c04895c2705f6fa4e9b578440487ab1b1cbfea Mon Sep 17 00:00:00 2001 From: Stefanie Jane Date: Tue, 15 Sep 2026 16:56:56 -0700 Subject: [PATCH 4/4] feat(helix): lay the statusline out like heirline Mode block with Nerd Font glyphs (terminal, pencil, select-all), the LSP spinner and git branch on the left with the file name and its state; buffer and workspace diagnostics, selection count and length, register, position and file type on the right, cyan bars between the sections. Helix takes the bar colours from the theme, so the matching surface and mode hues land in the SilkCircuit helix theme alongside this. Co-Authored-By: Claude Fable 5.1 --- docs/helix/index.md | 16 ++++++++++++++++ helix/config.toml | 36 +++++++++++++++++++++++++++++++----- 2 files changed, 47 insertions(+), 5 deletions(-) diff --git a/docs/helix/index.md b/docs/helix/index.md index 2163b35..e54e31d 100644 --- a/docs/helix/index.md +++ b/docs/helix/index.md @@ -181,6 +181,22 @@ Works in normal and select mode. It uses `pbcopy`, `wl-copy` or `xclip`, whichev selection (matching AstroNvim, and replacing Helix's `Space c`). Two defaults moved: `Space a` is the agents group, so code actions are `Space l a`, and `Space w` saves, so window mode is `Ctrl-w`. +## Statusline and bufferline + +The bar follows the heirline layout from Neovim: a colored mode block on the left (NORMAL, INSERT and SELECT, each with +a Nerd Font glyph, the LSP spinner beside it), then the git branch, the file name and its modified or read-only state. +The right side carries diagnostics for the buffer and the workspace as colored dots, the selection count and length, the +active register, position and percentage, and the file type. Sections sit on the SilkCircuit highlight surface with cyan +separators; the mode block takes purple, pink or cyan for normal, insert or select, the same hues heirline uses. Those +colors live in the SilkCircuit Helix theme, generated from the helix extra in that repo, because Helix draws the bar +from `ui.statusline` scopes rather than from config. + +With more than one buffer open the bufferline appears on top, active buffer in bold purple on the editor background, the +rest muted on the section surface, matching the heirline tabline. + +Helix has no custom statusline components, so a few heirline pieces have no equivalent: git added, changed and removed +counts, attached server names and the scrollbar. The spinner covers server activity and `Space l L` opens the log. + ## Language servers Everything below resolves to the binaries Mason installed for Neovim under `~/.local/share/nvim/mason/bin`, which diff --git a/helix/config.toml b/helix/config.toml index a8577e2..3aac85a 100644 --- a/helix/config.toml +++ b/helix/config.toml @@ -51,14 +51,40 @@ render = true character = "│" skip-levels = 1 +# Statusline, laid out like heirline: mode block, branch, file and its +# state on the left; diagnostics, selection, position and filetype on the +# right. Helix paints the bar from the theme (ui.statusline.* in the +# SilkCircuit helix theme), so this side only picks elements, the +# separator glyph and the mode labels. Nerd Font glyphs: terminal for +# normal, pencil for insert, select-all for select. [editor.statusline] -left = ["mode", "spinner", "version-control", "file-name", "read-only-indicator", "file-modification-indicator"] +left = [ + "mode", + "spinner", + "version-control", + "separator", + "file-name", + "file-modification-indicator", + "read-only-indicator", +] center = [] -right = ["diagnostics", "selections", "register", "position", "position-percentage", "total-line-numbers", "file-type"] +right = [ + "diagnostics", + "workspace-diagnostics", + "separator", + "selections", + "primary-selection-length", + "register", + "separator", + "position", + "position-percentage", + "separator", + "file-type", +] separator = "│" -mode.normal = "NORMAL" -mode.insert = "INSERT" -mode.select = "SELECT" +mode.normal = " NORMAL" +mode.insert = " INSERT" +mode.select = "󰒅 SELECT" [editor.soft-wrap] enable = false