diff --git a/.chezmoiscripts/run_once_after_10_install_python_toolchains.sh b/.chezmoiscripts/run_once_after_10_install_python_toolchains.sh index 0e6ac77..ae1dc21 100644 --- a/.chezmoiscripts/run_once_after_10_install_python_toolchains.sh +++ b/.chezmoiscripts/run_once_after_10_install_python_toolchains.sh @@ -1,7 +1,9 @@ #!/bin/bash echo "🐍 Installing Python" -curl -LsSf https://astral.sh/uv/install.sh | sh +if ! command -v uv &>/dev/null; then + UV_NO_MODIFY_PATH=1 curl -LsSf https://astral.sh/uv/install.sh | sh +fi # install the latest Python version uv python install @@ -9,5 +11,4 @@ uv python install uv generate-shell-completion zsh >"${XDG_DATA_HOME}/zsh/completions/_uv" uvx --generate-shell-completion zsh >"$XDG_DATA_HOME/zsh/completions/_uvx" -uv pip install argcomplete -activate-global-python-argcomplete +uv tool install argcomplete diff --git a/CLAUDE.md b/CLAUDE.md index 6d9d1c3..802110e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -25,6 +25,7 @@ dot_kube/ → Kubernetes config dot_cargo/ → Cargo (Rust) config dot_cache/ → cached files dot_local/ → local binaries/share +dot_claude/ → ~/.claude/ (Claude Code settings, skills, permissions) Library/ → macOS Library preferences gui/ → GUI app configs (not managed by chezmoi) ``` diff --git a/dot_claude/settings.json b/dot_claude/settings.json index 0f6b37c..d6a1cc5 100644 --- a/dot_claude/settings.json +++ b/dot_claude/settings.json @@ -14,11 +14,11 @@ "Bash(find:*)", "Bash(grep:*)", "Bash(head:*)", - "Bash(sort:*)", "Bash(ls:*)", "Bash(mkdir:*)", "Bash(pwd:*)", "Bash(rg:*)", + "Bash(sort:*)", "Bash(tail:*)", "Bash(tree:*)", "Bash(wc:*)", @@ -104,15 +104,23 @@ "WebFetch(domain:*.githubusercontent.com)", "WebFetch(domain:*.readthedocs.io)", "WebFetch(domain:about.gitlab.com)", + "WebFetch(domain:alacritty.org)", + "WebFetch(domain:bazel.build)", + "WebFetch(domain:www.gnu.org)", "WebFetch(domain:developer.hashicorp.com)", + "WebFetch(domain:developers.raycast.com)", + "WebFetch(domain:doc.rust-lang.org)", "WebFetch(domain:docs.ansible.com)", "WebFetch(domain:docs.anthropic.com)", + "WebFetch(domain:docs.astral.sh)", "WebFetch(domain:docs.aws.amazon.com)", "WebFetch(domain:docs.claude.com)", "WebFetch(domain:docs.datadoghq.com)", "WebFetch(domain:docs.docker.com)", "WebFetch(domain:docs.github.com)", "WebFetch(domain:docs.gitlab.com)", + "WebFetch(domain:docs.python.org)", + "WebFetch(domain:docs.rs)", "WebFetch(domain:github.com)", "WebFetch(domain:gitlab.com)", "WebFetch(domain:go.dev)", @@ -120,9 +128,14 @@ "WebFetch(domain:graphite.dev)", "WebFetch(domain:handbook.gitlab.com)", "WebFetch(domain:helm.sh)", + "WebFetch(domain:katacontainers.io)", + "WebFetch(domain:kernel.org)", "WebFetch(domain:kubernetes.io)", "WebFetch(domain:man7.org)", + "WebFetch(domain:mypy.readthedocs.io)", + "WebFetch(domain:neovim.io)", "WebFetch(domain:nginx.org)", + "WebFetch(domain:opentelemetry.io)", "WebFetch(domain:pkg.go.dev)", "WebFetch(domain:platform.openai.com)", "WebFetch(domain:prometheus.io)", @@ -130,6 +143,9 @@ "WebFetch(domain:registry.terraform.io)", "WebFetch(domain:stackoverflow.com)", "WebFetch(domain:support.anthropic.com)", + "WebFetch(domain:www.jetbrains.com)", + "WebFetch(domain:www.lua.org)", + "WebFetch(domain:zed.dev)", "mcp__atlassian__atlassianUserInfo", "mcp__atlassian__fetch", diff --git a/dot_config/nvim/after/plugin/blink-cmp.lua b/dot_config/nvim/after/plugin/blink-cmp.lua index 3cc01d3..d993d26 100644 --- a/dot_config/nvim/after/plugin/blink-cmp.lua +++ b/dot_config/nvim/after/plugin/blink-cmp.lua @@ -16,3 +16,5 @@ capabilities = vim.tbl_deep_extend("force", capabilities, { }, }, }) + +vim.lsp.config("*", { capabilities = capabilities }) diff --git a/dot_config/nvim/after/plugin/treesitter.lua b/dot_config/nvim/after/plugin/treesitter.lua index e6771a2..378e93d 100644 --- a/dot_config/nvim/after/plugin/treesitter.lua +++ b/dot_config/nvim/after/plugin/treesitter.lua @@ -5,16 +5,21 @@ end configs.setup({ ensure_installed = { + "bash", "c", "cmake", "dockerfile", "dot", "go", "hcl", + "json", "lua", "make", "python", "rust", + "terraform", + "toml", + "yaml", }, sync_install = false, autopairs = { diff --git a/dot_config/nvim/filetype.lua b/dot_config/nvim/filetype.lua index 6e90a02..68227e2 100644 --- a/dot_config/nvim/filetype.lua +++ b/dot_config/nvim/filetype.lua @@ -28,27 +28,27 @@ vim.filetype.add({ local augroups = require("user.augroups") local autocmd = vim.api.nvim_create_autocmd -local set = vim.opt +-- Use vim.bo (buffer-local) not vim.opt (global) to avoid leaking settings across buffers -- https://neovim.io/doc/user/api.html#nvim_create_autocmd() autocmd({ "FileType" }, { group = augroups.filetype, pattern = { "terraform", "hcl", "json", "yaml" }, - callback = function() - set.tabstop = 2 - set.shiftwidth = 2 - set.softtabstop = 2 - set.expandtab = true + callback = function(event) + vim.bo[event.buf].tabstop = 2 + vim.bo[event.buf].shiftwidth = 2 + vim.bo[event.buf].softtabstop = 2 + vim.bo[event.buf].expandtab = true end, }) autocmd({ "FileType" }, { group = augroups.filetype, pattern = { "go", "make" }, - callback = function() - set.expandtab = false - set.tabstop = 4 - set.shiftwidth = 4 - set.softtabstop = 4 + callback = function(event) + vim.bo[event.buf].expandtab = false + vim.bo[event.buf].tabstop = 4 + vim.bo[event.buf].shiftwidth = 4 + vim.bo[event.buf].softtabstop = 4 end, }) diff --git a/dot_config/nvim/init.lua b/dot_config/nvim/init.lua index abe1602..95fe8f7 100644 --- a/dot_config/nvim/init.lua +++ b/dot_config/nvim/init.lua @@ -20,14 +20,33 @@ else -- automatically hide and show the command line vim.o.ch = 0 - -- Run `chezmoi apply` whenever its configuration is modified. + -- Run `chezmoi apply` asynchronously whenever its source files are saved. + -- Uses jobstart to avoid blocking the UI; errors are reported via vim.notify. local augroups = require("user.augroups") vim.api.nvim_create_autocmd("BufWritePost", { group = augroups.chezmoi, pattern = vim.fn.expand("~") .. "/.local/share/chezmoi/*", - command = "silent! !chezmoi apply --no-tty --force --source-path '%'", + callback = function(event) + local source_path = vim.api.nvim_buf_get_name(event.buf) + vim.fn.jobstart({ "chezmoi", "apply", "--no-tty", "--force", "--source-path", source_path }, { + on_exit = function(_, code) + if code ~= 0 then + vim.schedule(function() + vim.notify("chezmoi apply failed (exit " .. code .. ")", vim.log.levels.ERROR) + end) + end + end, + }) + end, }) + -- Python + local xdg_data = os.getenv("XDG_DATA_HOME") or (os.getenv("HOME") .. "/.local/share") + local python3 = xdg_data .. "/pyenv/versions/neovim/bin/python3" + if vim.uv.fs_stat(python3) then + vim.g.python3_host_prog = python3 + end + -- Files to ignore -- Python vim.opt.wildignore:append("*.pyc,*.pyo,*/__pycache__/*") @@ -39,7 +58,8 @@ else -- Use ripgrep when available if vim.fn.executable("rg") == 1 then vim.o.grepprg = "rg --no-heading --vimgrep" - vim.o.grepformat = "f:%l:%c:%m" + -- https://neovim.io/doc/user/quickfix.html#errorformat + vim.o.grepformat = "%f:%l:%c:%m" end end diff --git a/dot_config/nvim/lsp/gopls.lua b/dot_config/nvim/lsp/gopls.lua index 91aac52..feafdfe 100644 --- a/dot_config/nvim/lsp/gopls.lua +++ b/dot_config/nvim/lsp/gopls.lua @@ -3,21 +3,17 @@ return { cmd = { "gopls" }, filetypes = { "go", "gomod", "gowork", "gotmpl", "gosum" }, root_markers = { "go.mod", "go.work", ".git" }, + init_options = { + usePlaceholders = true, + }, settings = { gopls = { - settings = { - gopls = { - experimentalPostfixCompletions = true, - analyses = { - unusedparams = true, - shadow = true, - }, - staticcheck = true, - }, - }, - init_options = { - usePlaceholders = true, + experimentalPostfixCompletions = true, + analyses = { + unusedparams = true, + shadow = true, }, + staticcheck = true, }, }, } diff --git a/dot_config/nvim/lua/plugins/code.lua b/dot_config/nvim/lua/plugins/code.lua index 51536a6..c7b051d 100644 --- a/dot_config/nvim/lua/plugins/code.lua +++ b/dot_config/nvim/lua/plugins/code.lua @@ -8,11 +8,10 @@ return { cond = profile.active("default"), ft = "lua", -- only load on lua files opts = { - -- It can also be a table with trigger words / mods - -- Only load luvit types when the `vim.uv` word is found - { path = "${3rd}/luv/library", words = { "vim%.uv" } }, - -- Only load the lazyvim library when the `LazyVim` global is found - { path = "LazyVim", words = { "LazyVim" } }, + library = { + -- Only load luvit types when the `vim.uv` word is found + { path = "${3rd}/luv/library", words = { "vim%.uv" } }, + }, }, }, @@ -55,10 +54,7 @@ return { { "mm", "Mason", desc = "Packages" }, }, }, - { - "neovim/nvim-lspconfig", - dependencies = { "saghen/blink.cmp" }, - }, + { "neovim/nvim-lspconfig" }, }, }, diff --git a/dot_config/nvim/lua/plugins/editor.lua b/dot_config/nvim/lua/plugins/editor.lua index 2645829..fffc99e 100644 --- a/dot_config/nvim/lua/plugins/editor.lua +++ b/dot_config/nvim/lua/plugins/editor.lua @@ -1,8 +1,10 @@ +local profile = require("user.profile") + ---@type LazySpec return { - "psliwka/vim-smoothie", { "lewis6991/gitsigns.nvim", + cond = profile.active("default"), dependencies = { "nvim-lua/plenary.nvim" }, }, -- Flash enhances the built-in search functionality by showing labels diff --git a/dot_config/nvim/lua/plugins/linux/telescope-media-files.lua b/dot_config/nvim/lua/plugins/linux/telescope-media-files.lua index 19159da..6e6f7df 100644 --- a/dot_config/nvim/lua/plugins/linux/telescope-media-files.lua +++ b/dot_config/nvim/lua/plugins/linux/telescope-media-files.lua @@ -1,7 +1,10 @@ +local profile = require("user.profile") + ---@type LazySpec return { { "nvim-telescope/telescope-media-files.nvim", + cond = profile.active("default"), dependencies = { "nvim-telescope/telescope.nvim", "nvim-lua/popup.nvim", diff --git a/dot_config/nvim/lua/plugins/ui.lua b/dot_config/nvim/lua/plugins/ui.lua index 4dd1ffc..8e0c1af 100644 --- a/dot_config/nvim/lua/plugins/ui.lua +++ b/dot_config/nvim/lua/plugins/ui.lua @@ -37,9 +37,6 @@ return { end, dependencies = { "kkharji/sqlite.lua" }, }, - { - "nvim-lualine/lualine.nvim", - dependencies = { "kyazdani42/nvim-web-devicons" }, - }, + { "nvim-lualine/lualine.nvim" }, { "akinsho/toggleterm.nvim", cond = profile.active("default") }, } diff --git a/dot_config/nvim/lua/user/keymaps.lua b/dot_config/nvim/lua/user/keymaps.lua index dfc8eba..badfde4 100644 --- a/dot_config/nvim/lua/user/keymaps.lua +++ b/dot_config/nvim/lua/user/keymaps.lua @@ -30,7 +30,6 @@ utils.nmap("s", "w") -- Navigation hotkeys utils.nmap("", "q") utils.nmap("", "q!") -utils.inoremap("", "") utils.inoremap("jk", "") utils.inoremap("kj", "") diff --git a/dot_config/nvim/lua/user/lsp.lua b/dot_config/nvim/lua/user/lsp.lua index 7caf0c2..e279b8b 100644 --- a/dot_config/nvim/lua/user/lsp.lua +++ b/dot_config/nvim/lua/user/lsp.lua @@ -31,8 +31,7 @@ local augroups = require("user.augroups") -- Format on save vim.api.nvim_create_autocmd("BufWritePre", { group = augroups.lsp, - -- todo: use file types? pattern = { "terraform", "go" }, - pattern = { "*.tf", "*.tfvars" }, + pattern = { "*.tf", "*.tfvars", "*.go" }, callback = vim.lsp.buf.format, }) @@ -41,7 +40,22 @@ local utils = require("user.utils") vim.api.nvim_create_autocmd("LspAttach", { group = augroups.lsp, callback = function(attach) - utils.nmap("fb", vim.lsp.buf.format, { buffer = true, desc = "[F]ormat [B]uffer" }) + -- Buffer-scoped keymaps: only active while an LSP client is attached + local buf_opts = { buffer = attach.buf } + utils.nmap("fb", vim.lsp.buf.format, vim.tbl_extend("force", buf_opts, { desc = "[F]ormat [B]uffer" })) + utils.nmap("gd", vim.lsp.buf.definition, vim.tbl_extend("force", buf_opts, { desc = "[G]oto [D]efinition" })) + utils.nmap("gD", vim.lsp.buf.declaration, vim.tbl_extend("force", buf_opts, { desc = "[G]oto [D]eclaration" })) + utils.nmap( + "ca", + vim.lsp.buf.code_action, + vim.tbl_extend("force", buf_opts, { desc = "[C]ode [A]ction" }) + ) + utils.nmap("rn", vim.lsp.buf.rename, vim.tbl_extend("force", buf_opts, { desc = "[R]e[n]ame" })) + utils.nmap( + "D", + vim.lsp.buf.type_definition, + vim.tbl_extend("force", buf_opts, { desc = "Type [D]efinition" }) + ) local client = assert(vim.lsp.get_client_by_id(attach.data.client_id)) if client:supports_method(vim.lsp.protocol.Methods.textDocument_documentHighlight) then @@ -57,15 +71,19 @@ vim.api.nvim_create_autocmd("LspAttach", { group = augroups.lsp, callback = vim.lsp.buf.clear_references, }) + end + end, +}) - -- When LSP detaches: Clears the highlighting - vim.api.nvim_create_autocmd("LspDetach", { - group = augroups.lsp, - callback = function(detach) - vim.lsp.buf.clear_references() - vim.api.nvim_clear_autocmds({ group = augroups.lsp, buffer = detach.buf }) - end, - }) +-- Top-level (not inside LspAttach) to avoid registering a new handler on every attach. +-- Only clears buffer autocmds when no LSP clients remain. +vim.api.nvim_create_autocmd("LspDetach", { + group = augroups.lsp, + callback = function(detach) + vim.lsp.buf.clear_references() + local remaining = vim.lsp.get_clients({ bufnr = detach.buf }) + if #remaining == 0 then + vim.api.nvim_clear_autocmds({ group = augroups.lsp, buffer = detach.buf }) end end, }) diff --git a/dot_config/nvim/lua/user/options.lua b/dot_config/nvim/lua/user/options.lua index bb10c84..3a57284 100644 --- a/dot_config/nvim/lua/user/options.lua +++ b/dot_config/nvim/lua/user/options.lua @@ -14,6 +14,7 @@ local options = { autoindent = true, -- Navigation + smoothscroll = true, title = true, number = true, relativenumber = true, diff --git a/dot_config/nvim/lua/user/plugin_manager.lua b/dot_config/nvim/lua/user/plugin_manager.lua index ffb0891..bec44b1 100644 --- a/dot_config/nvim/lua/user/plugin_manager.lua +++ b/dot_config/nvim/lua/user/plugin_manager.lua @@ -5,7 +5,7 @@ if not vim.uv.fs_stat(lazypath) then "git", "clone", "--filter=blob:none", - "gh:folke/lazy.nvim.git", + "https://github.com/folke/lazy.nvim.git", "--branch=stable", -- latest stable release lazypath, }) diff --git a/dot_config/zsh/dot_zshrc.tmpl b/dot_config/zsh/dot_zshrc.tmpl index 412e7d2..05cab00 100644 --- a/dot_config/zsh/dot_zshrc.tmpl +++ b/dot_config/zsh/dot_zshrc.tmpl @@ -86,9 +86,10 @@ autoload -Uz tldr-fzf {{- if eq .chezmoi.os "darwin" }} fpath+="$(brew --prefix)/share/zsh/site-functions" {{ end }} -fpath+="$HOME/.rustup/toolchains/stable-x86_64-apple-darwin/share/zsh/site-functions" -fpath+="$HOME/.docker/completions" +fpath+=($HOME/.rustup/toolchains/stable-x86_64-apple-darwin/share/zsh/site-functions(N/)) +fpath+=($HOME/.docker/completions(N/)) fpath+="$XDG_DATA_HOME/zsh/completions" +fpath+=($XDG_DATA_HOME/uv/tools/argcomplete/lib/python*/site-packages/argcomplete/bash_completion.d(N)) autoload -Uz compinit && compinit autoload -Uz select-word-style && select-word-style bash