diff --git a/.beads/interactions.jsonl b/.beads/interactions.jsonl index ccaca4f..3be02c5 100644 --- a/.beads/interactions.jsonl +++ b/.beads/interactions.jsonl @@ -6,3 +6,5 @@ {"id":"int-b7da815a","kind":"field_change","created_at":"2026-08-09T18:48:13.663074277Z","actor":"Prad Nukala","issue_id":"chezmoi-1a0","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"secrets.fish age-encrypted; plaintext purged from git history and remote force-pushed; noctalia/plugins excluded (live ghp_ token); gh hosts.yml excluded; npmrc deferred; rotation recommended"}} {"id":"int-bbcf4500","kind":"field_change","created_at":"2026-08-09T18:49:28.483381629Z","actor":"Prad Nukala","issue_id":"chezmoi-uw2","extra":{"field":"status","new_value":"in_progress","old_value":"open"}} {"id":"int-aca22d17","kind":"field_change","created_at":"2026-08-09T18:51:25.617630581Z","actor":"Prad Nukala","issue_id":"chezmoi-fde","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"starship.toml added from live; packages updated"}} +{"id":"int-8975e4ce","kind":"field_change","created_at":"2026-08-09T18:51:54.252398427Z","actor":"Prad Nukala","issue_id":"chezmoi-uw2","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Closed"}} +{"id":"int-fcc3a2e9","kind":"field_change","created_at":"2026-08-09T18:51:54.41281937Z","actor":"Prad Nukala","issue_id":"chezmoi-sd9","extra":{"field":"status","new_value":"in_progress","old_value":"open"}} diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl index 2a184c2..6f053fa 100644 --- a/.beads/issues.jsonl +++ b/.beads/issues.jsonl @@ -13,7 +13,7 @@ {"_type":"issue","id":"chezmoi-fde","title":"starship: prompt config, universal","description":"Keep starship (user decision — omp dropped). dot_config/starship.toml from live; starship in packages.yaml both OSes. Noctalia palette block is auto-generated on linux — noctalia rewrites it in place; acceptable drift, noted.","status":"closed","priority":2,"issue_type":"task","owner":"prad@sonr.io","created_at":"2026-08-09T18:42:06Z","created_by":"Prad Nukala","updated_at":"2026-08-09T18:51:26Z","closed_at":"2026-08-09T18:51:26Z","close_reason":"starship.toml added from live; packages updated","dependencies":[{"issue_id":"chezmoi-fde","depends_on_id":"chezmoi-byc","type":"blocks","created_at":"2026-08-09T14:42:52Z","created_by":"Prad Nukala","metadata":"{}"},{"issue_id":"chezmoi-fde","depends_on_id":"chezmoi-rnv","type":"blocks","created_at":"2026-08-09T14:42:51Z","created_by":"Prad Nukala","metadata":"{}"}],"dependency_count":2,"dependent_count":2,"comment_count":0} {"_type":"issue","id":"chezmoi-x7f","title":"lazygit: static config","description":"~/.config/lazygit. Already partially in repo — reconcile.","status":"open","priority":2,"issue_type":"task","owner":"prad@sonr.io","created_at":"2026-08-09T18:42:06Z","created_by":"Prad Nukala","updated_at":"2026-08-09T18:42:06Z","dependencies":[{"issue_id":"chezmoi-x7f","depends_on_id":"chezmoi-byc","type":"blocks","created_at":"2026-08-09T14:42:51Z","created_by":"Prad Nukala","metadata":"{}"},{"issue_id":"chezmoi-x7f","depends_on_id":"chezmoi-rnv","type":"blocks","created_at":"2026-08-09T14:42:50Z","created_by":"Prad Nukala","metadata":"{}"}],"dependency_count":2,"dependent_count":2,"comment_count":0} {"_type":"issue","id":"chezmoi-hou","title":"yazi: config, opener rules OS-branched","description":"~/.config/yazi. open vs xdg-open template branch.","status":"open","priority":2,"issue_type":"task","owner":"prad@sonr.io","created_at":"2026-08-09T18:42:05Z","created_by":"Prad Nukala","updated_at":"2026-08-09T18:42:05Z","dependencies":[{"issue_id":"chezmoi-hou","depends_on_id":"chezmoi-byc","type":"blocks","created_at":"2026-08-09T14:42:50Z","created_by":"Prad Nukala","metadata":"{}"},{"issue_id":"chezmoi-hou","depends_on_id":"chezmoi-rnv","type":"blocks","created_at":"2026-08-09T14:42:50Z","created_by":"Prad Nukala","metadata":"{}"}],"dependency_count":2,"dependent_count":2,"comment_count":0} -{"_type":"issue","id":"chezmoi-sd9","title":"nvim: AstroNvim config, static","description":"chezmoi add ~/.config/nvim as-is, no templating. Exclude lazy-lock churn decision noted.","status":"open","priority":2,"issue_type":"task","owner":"prad@sonr.io","created_at":"2026-08-09T18:42:05Z","created_by":"Prad Nukala","updated_at":"2026-08-09T18:42:05Z","dependencies":[{"issue_id":"chezmoi-sd9","depends_on_id":"chezmoi-byc","type":"blocks","created_at":"2026-08-09T14:42:49Z","created_by":"Prad Nukala","metadata":"{}"},{"issue_id":"chezmoi-sd9","depends_on_id":"chezmoi-rnv","type":"blocks","created_at":"2026-08-09T14:42:49Z","created_by":"Prad Nukala","metadata":"{}"}],"dependency_count":2,"dependent_count":2,"comment_count":0} +{"_type":"issue","id":"chezmoi-sd9","title":"nvim: AstroNvim config, static","description":"chezmoi add ~/.config/nvim as-is, no templating. Exclude lazy-lock churn decision noted.","status":"in_progress","priority":2,"issue_type":"task","owner":"prad@sonr.io","created_at":"2026-08-09T18:42:05Z","created_by":"Prad Nukala","updated_at":"2026-08-09T18:51:54Z","started_at":"2026-08-09T18:51:54Z","dependencies":[{"issue_id":"chezmoi-sd9","depends_on_id":"chezmoi-byc","type":"blocks","created_at":"2026-08-09T14:42:49Z","created_by":"Prad Nukala","metadata":"{}"},{"issue_id":"chezmoi-sd9","depends_on_id":"chezmoi-rnv","type":"blocks","created_at":"2026-08-09T14:42:49Z","created_by":"Prad Nukala","metadata":"{}"}],"dependency_count":2,"dependent_count":2,"comment_count":0} {"_type":"issue","id":"chezmoi-utl","title":"kitty: live config, OS-branch fonts/paths if needed","description":"~/.config/kitty. Keybinds shared.","status":"open","priority":2,"issue_type":"task","owner":"prad@sonr.io","created_at":"2026-08-09T18:42:05Z","created_by":"Prad Nukala","updated_at":"2026-08-09T18:42:05Z","dependencies":[{"issue_id":"chezmoi-utl","depends_on_id":"chezmoi-byc","type":"blocks","created_at":"2026-08-09T14:42:50Z","created_by":"Prad Nukala","metadata":"{}"},{"issue_id":"chezmoi-utl","depends_on_id":"chezmoi-rnv","type":"blocks","created_at":"2026-08-09T14:42:50Z","created_by":"Prad Nukala","metadata":"{}"}],"dependency_count":2,"dependent_count":2,"comment_count":0} -{"_type":"issue","id":"chezmoi-uw2","title":"fish: config, conf.d, functions, completions, bindings","description":"Live ~/.config/fish (Rogz storage superset; Noctalia canonical, deltas to MIGRATION-NOTES). OS-branch clipboard/open/package paths. secrets.fish excluded (own issue). fish -n all files.","status":"in_progress","priority":2,"issue_type":"task","owner":"prad@sonr.io","created_at":"2026-08-09T18:42:05Z","created_by":"Prad Nukala","updated_at":"2026-08-09T18:49:28Z","started_at":"2026-08-09T18:49:28Z","dependencies":[{"issue_id":"chezmoi-uw2","depends_on_id":"chezmoi-1a0","type":"blocks","created_at":"2026-08-09T14:42:24Z","created_by":"Prad Nukala","metadata":"{}"},{"issue_id":"chezmoi-uw2","depends_on_id":"chezmoi-byc","type":"blocks","created_at":"2026-08-09T14:42:49Z","created_by":"Prad Nukala","metadata":"{}"},{"issue_id":"chezmoi-uw2","depends_on_id":"chezmoi-rnv","type":"blocks","created_at":"2026-08-09T14:42:49Z","created_by":"Prad Nukala","metadata":"{}"}],"dependency_count":3,"dependent_count":2,"comment_count":0} +{"_type":"issue","id":"chezmoi-uw2","title":"fish: config, conf.d, functions, completions, bindings","description":"Live ~/.config/fish (Rogz storage superset; Noctalia canonical, deltas to MIGRATION-NOTES). OS-branch clipboard/open/package paths. secrets.fish excluded (own issue). fish -n all files.","status":"closed","priority":2,"issue_type":"task","owner":"prad@sonr.io","created_at":"2026-08-09T18:42:05Z","created_by":"Prad Nukala","updated_at":"2026-08-09T18:51:54Z","started_at":"2026-08-09T18:49:28Z","closed_at":"2026-08-09T18:51:54Z","close_reason":"Closed","dependencies":[{"issue_id":"chezmoi-uw2","depends_on_id":"chezmoi-1a0","type":"blocks","created_at":"2026-08-09T14:42:24Z","created_by":"Prad Nukala","metadata":"{}"},{"issue_id":"chezmoi-uw2","depends_on_id":"chezmoi-byc","type":"blocks","created_at":"2026-08-09T14:42:49Z","created_by":"Prad Nukala","metadata":"{}"},{"issue_id":"chezmoi-uw2","depends_on_id":"chezmoi-rnv","type":"blocks","created_at":"2026-08-09T14:42:49Z","created_by":"Prad Nukala","metadata":"{}"}],"dependency_count":3,"dependent_count":2,"comment_count":0} {"_type":"issue","id":"chezmoi-xi5","title":"tmux: config from live, strip vertical geometry","description":"~/.config/tmux (tmux.conf symlink from Vertical-Rogz_AppleStudio). Verify live, gate vertical-display-specific bits if present. Plugins dir decision.","status":"open","priority":2,"issue_type":"task","owner":"prad@sonr.io","created_at":"2026-08-09T18:42:05Z","created_by":"Prad Nukala","updated_at":"2026-08-09T18:42:05Z","dependencies":[{"issue_id":"chezmoi-xi5","depends_on_id":"chezmoi-byc","type":"blocks","created_at":"2026-08-09T14:42:50Z","created_by":"Prad Nukala","metadata":"{}"},{"issue_id":"chezmoi-xi5","depends_on_id":"chezmoi-rnv","type":"blocks","created_at":"2026-08-09T14:42:50Z","created_by":"Prad Nukala","metadata":"{}"}],"dependency_count":2,"dependent_count":2,"comment_count":0} diff --git a/.chezmoidata/packages.yaml b/.chezmoidata/packages.yaml index dac2066..468becc 100644 --- a/.chezmoidata/packages.yaml +++ b/.chezmoidata/packages.yaml @@ -42,6 +42,8 @@ packages: - atuin - wget - starship + # fonts + - ttf-victor-mono-nerd aur: [] desktop: # linux desktop layer (role desktop|laptop) @@ -83,6 +85,7 @@ packages: - starship cask: - kitty + - font-victor-mono-nerd-font shared: gh_extensions: - davidraviv/gh-clean-branches diff --git a/.chezmoiignore.tmpl b/.chezmoiignore.tmpl index 3430e8e..e9c5110 100644 --- a/.chezmoiignore.tmpl +++ b/.chezmoiignore.tmpl @@ -9,6 +9,11 @@ README.md .config/tmux/plugins/** .config/fish/fish_variables .config/nvim/lazy-lock.json +.config/nvim/.git +.config/nvim/.git/** +.config/nvim/.omc/** +.config/nvim/.crush/** +.config/nvim/.understand-anything/** {{ if ne .chezmoi.os "linux" }} # Arch desktop layer — linux only @@ -24,4 +29,11 @@ README.md .config/systemd .config/systemd/** .local/bin/niri-launch +.config/fish/auto-Niri.fish +.config/fish/functions/cp-appid.fish +.config/tmux/niri-title.sh {{ end }} + +# Runtime plugin state with embedded tokens — never managed +.config/noctalia/plugins +.config/noctalia/plugins/** diff --git a/dot_config/nvim/README.md b/dot_config/nvim/README.md new file mode 100644 index 0000000..4488bde --- /dev/null +++ b/dot_config/nvim/README.md @@ -0,0 +1,34 @@ +# AstroNvim Template + +**NOTE:** This is for AstroNvim v6+ + +A template for getting started with [AstroNvim](https://github.com/AstroNvim/AstroNvim) + +## 🛠️ Installation + +#### Make a backup of your current nvim and shared folder + +```shell +mv ~/.config/nvim ~/.config/nvim.bak +mv ~/.local/share/nvim ~/.local/share/nvim.bak +mv ~/.local/state/nvim ~/.local/state/nvim.bak +mv ~/.cache/nvim ~/.cache/nvim.bak +``` + +#### Create a new user repository from this template + +Press the "Use this template" button above to create a new repository to store your user configuration. + +You can also just clone this repository directly if you do not want to track your user configuration in GitHub. + +#### Clone the repository + +```shell +git clone https://github.com// ~/.config/nvim +``` + +#### Start Neovim + +```shell +nvim +``` diff --git a/dot_config/nvim/dot_luarc.json b/dot_config/nvim/dot_luarc.json new file mode 100644 index 0000000..8415579 --- /dev/null +++ b/dot_config/nvim/dot_luarc.json @@ -0,0 +1,3 @@ +{ + "format.enable": false +} diff --git a/dot_config/nvim/dot_neoconf.json b/dot_config/nvim/dot_neoconf.json new file mode 100644 index 0000000..2d7a81f --- /dev/null +++ b/dot_config/nvim/dot_neoconf.json @@ -0,0 +1,20 @@ +{ + "neodev": { + "library": { + "enabled": true, + "plugins": true + } + }, + "neoconf": { + "plugins": { + "lua_ls": { + "enabled": true + } + } + }, + "lspconfig": { + "lua_ls": { + "Lua.format.enable": false + } + } +} diff --git a/dot_config/nvim/dot_stylua.toml b/dot_config/nvim/dot_stylua.toml new file mode 100644 index 0000000..bfcffff --- /dev/null +++ b/dot_config/nvim/dot_stylua.toml @@ -0,0 +1,7 @@ +column_width = 120 +line_endings = "Unix" +indent_type = "Spaces" +indent_width = 2 +quote_style = "AutoPreferDouble" +call_parentheses = "None" +collapse_simple_statement = "Always" diff --git a/dot_config/nvim/init.lua b/dot_config/nvim/init.lua new file mode 100644 index 0000000..5c1c71b --- /dev/null +++ b/dot_config/nvim/init.lua @@ -0,0 +1,38 @@ +-- This file simply bootstraps the installation of Lazy.nvim and then calls other files for execution +-- This file doesn't necessarily need to be touched, BE CAUTIOUS editing this file and proceed at your own risk. +local lazypath = vim.env.LAZY or vim.fn.stdpath "data" .. "/lazy/lazy.nvim" + +if not (vim.env.LAZY or (vim.uv or vim.loop).fs_stat(lazypath)) then + -- stylua: ignore + local result = vim.fn.system({ "git", "clone", "--filter=blob:none", "https://github.com/folke/lazy.nvim.git", "--branch=stable", lazypath }) + if vim.v.shell_error ~= 0 then + -- stylua: ignore + vim.api.nvim_echo({ { ("Error cloning lazy.nvim:\n%s\n"):format(result), "ErrorMsg" }, { "Press any key to exit...", "MoreMsg" } }, true, {}) + vim.fn.getchar() + vim.cmd.quit() + end +end + +vim.opt.rtp:prepend(lazypath) + +-- validate that lazy is available +if not pcall(require, "lazy") then + -- stylua: ignore + vim.api.nvim_echo({ { ("Unable to load lazy from: %s\n"):format(lazypath), "ErrorMsg" }, { "Press any key to exit...", "MoreMsg" } }, true, {}) + vim.fn.getchar() + vim.cmd.quit() +end + +require "lazy_setup" +require "polish" + +-- Enable terminal title setting in Nvim +vim.opt.title = true +-- Set the format of the title +vim.opt.titlestring = "%t - Nvim" + +-- Ensure Nvim doesn't slow down on exit by resetting shell +vim.cmd('set shell=sh') -- Prevents fish-incompatible lag when exiting nvim in fish/tmux + +vim.opt.guicursor:append("v:hor20") +vim.opt.guicursor:append("a:blinkon0") diff --git a/dot_config/nvim/lazyvim.json b/dot_config/nvim/lazyvim.json new file mode 100644 index 0000000..dc3e179 --- /dev/null +++ b/dot_config/nvim/lazyvim.json @@ -0,0 +1,9 @@ +{ + "extras": [ + + ], + "news": { + "NEWS.md": "11866" + }, + "version": 8 +} \ No newline at end of file diff --git a/dot_config/nvim/lua/codemark/.keep b/dot_config/nvim/lua/codemark/.keep new file mode 100644 index 0000000..e69de29 diff --git a/dot_config/nvim/lua/community.lua b/dot_config/nvim/lua/community.lua new file mode 100644 index 0000000..793bdb2 --- /dev/null +++ b/dot_config/nvim/lua/community.lua @@ -0,0 +1,32 @@ + +-- AstroCommunity: import any community modules here +-- We import this file in `lazy_setup.lua` before the `plugins/` folder. +-- This guarantees that the specs are processed before any user plugins. + +---@type LazySpec +return { + "AstroNvim/astrocommunity", + version = false, -- Use latest astrocommunity + { import = "astrocommunity.bars-and-lines.bufferline-nvim" }, + { import = "astrocommunity.editing-support.refactoring-nvim" }, + { import = "astrocommunity.editing-support.conform-nvim" }, + { import = "astrocommunity.diagnostics.trouble-nvim" }, + { import = "astrocommunity.git.octo-nvim" }, + { import = "astrocommunity.pack.mdx" }, + { import = "astrocommunity.pack.biome" }, + { import = "astrocommunity.pack.go" }, + { import = "astrocommunity.pack.golangci-lint" }, + { import = "astrocommunity.pack.html-css" }, + { import = "astrocommunity.pack.json" }, + { import = "astrocommunity.pack.proto" }, + { import = "astrocommunity.pack.pkl" }, + { import = "astrocommunity.pack.rust" }, + { import = "astrocommunity.pack.yaml" }, + { import = "astrocommunity.pack.toml" }, + { import = "astrocommunity.pack.typescript" }, + { import = "astrocommunity.pack.tailwindcss" }, + { import = "astrocommunity.completion.supermaven-nvim" }, + { import = "astrocommunity.lsp.dev-tools-nvim" }, + { import = "astrocommunity.lsp.nvim-lsp-file-operations" }, + { import = "astrocommunity.colorscheme.cyberdream-nvim" }, +} diff --git a/dot_config/nvim/lua/config/ignore_patterns.lua b/dot_config/nvim/lua/config/ignore_patterns.lua new file mode 100644 index 0000000..5c33c9f --- /dev/null +++ b/dot_config/nvim/lua/config/ignore_patterns.lua @@ -0,0 +1,314 @@ +--- Shared ignore patterns for file explorers and pickers +--- This module provides a centralized list of patterns to ignore across +--- snacks.picker and other file browsing tools. (The never_show lists were +--- consumed by neo-tree, now replaced by yazi.nvim; kept as reference data.) +--- +--- Everything here is fast-event-context safe (pure Lua + libuv only): +--- snacks picker transforms run inside async/uv callbacks where vimL +--- functions (vim.fn.*) raise E5560. +--- @module config.ignore_patterns + +local uv = vim.uv or vim.loop + +local M = {} + +--- Core ignore patterns used across the configuration +--- These patterns use glob syntax (* and ? wildcards), matched against +--- file basenames and individual path segments. +M.patterns = { + -- Build artifacts and generated files + ".beads", + "chain_*.json", + ".npmrc", + ".epiq", + ".omc", + ".claude", + ".changeset", + ".codegraph", + ".gitignore", + "turbo.json", + "vite.config.ts", + "bunfig.toml", + "*.pb.go", + "*.pkl.go", + "*.pulsar.go", + "*.pb.gorm.go", + "*.pb.gw.go", + "*_templ.go", + "*_mock.go", + "*_integration.go", + "*_test.go", + "AGENTS.md", + "test", + "tests", + + -- Configuration files + ".editorconfig", + ".gitpod.*", + + -- Lock files and dependencies + "*.lock", + "*.lockb", + "*.sum", + "*.work.*", + "package-lock.json", + "pnpm-lock.yaml", + ".gitattributes", + ".oxlintrc.json", + "tsconfig*", + + -- Temporary and cache files + "*.tmp", + ".typecopy", + ".python-version", + ".prettierrc", + ".trunk", + ".config", + ".pkl-lsp", + ".tsbuildinfo", + ".wrangler", + ".parcel-cache", + ".conform*", + ".null-ls_*", + + -- Binary and media files + "*.wasm", + "*.png", + "*.jpg", + "*.icns", + "*.ico", + + -- Directories and special files + "contrib", + "interchaintest", + "PULL_REQUEST_TEMPLATE.md", + "Icon?", + "iCloud~", + "com~", +} + +--- Patterns that should never be shown in any file browser +M.never_show = { + -- Version control + ".git", + ".ai", + ".omc", + + -- Dependencies + "node_modules", + ".wrangler", + "CODEOWNERS", + ".golangci.yml", + + -- Build outputs + ".next", + ".parcel-cache", + + -- Task runners and tools + ".task", + ".devbox", + ".turbo", + ".jj", + ".spawn", + + -- IDE and editor + ".idea", + ".venv", + ".metadata", + ".dart_tool", + ".devcontainer", + + -- Build tools + ".gradle", + "gradle.bat", + "heighliner", + + -- Temporary + ".tmp", + "tmp", + + -- Logs + "sonr.log", + "junit.xml", + + -- macOS + ".DS_Store", + ".DocumentRevisions-V100", + ".Spotlight-V100", + ".TemporaryItems", + ".Trashes", + ".fseventsd", + ".timemachine", + + -- Templates + "DISCUSSION_TEMPLATE", + "ISSUE_TEMPLATE", + + -- License + "LICENSE", +} + +--- Patterns that should never be shown (pattern matching) +M.never_show_patterns = { + "*DS_Store", + ".obsidian", + ".pkl-lsp", + ".tsbuildinfo", + "package-lock.json", + ".prettierrc", + "node_modules", + ".DocumentRevisions-V100", + ".Spotlight-V100", + ".TemporaryItems", + ".Trashes", + ".fseventsd", + ".editorconfig", + "*.min.js", + "*.lock", + "*lock*", + "*.lockb", + "*.pulsar.go", + "*.pb.gorm.go", + "*.pb.gw.go", + "*_templ.go", + "*.tmp", + "*.work.*", + "*.sum", + ".parcel-cache", + "*.icns", + "*.ico", + "*.iml", + "Icon?", + "iCloud~", + "com~", + ".conform*", + ".null-ls_*", +} + +--- Directories to ignore when filtering files +M.ignored_directories = { + "contracts", + "crypto", + "api", + "chains", + "test", + "examples", + "scripts", + "bridge", + "client", + "translations", + "env", + ".husky", + ".worktrees", +} + +local EMPTY = {} + +--- Convert a glob (* and ? wildcards) to an anchored Lua pattern +--- @param glob string +--- @return string +local function glob_to_lua(glob) + return "^" .. glob:gsub("[%%%.%(%)%+%-%^%$%[%]]", "%%%0"):gsub("%*", ".*"):gsub("%?", ".") .. "$" +end + +--- @param globs string[] +--- @return string[] +local function compile_globs(globs) + local out = {} + for _, g in ipairs(globs) do + out[#out + 1] = glob_to_lua(g) + end + return out +end + +--- Find the git root for a directory by walking up (pure libuv, cached) +local root_cache = {} ---@type table +--- @param dir string +--- @return string? +local function git_root(dir) + local cached = root_cache[dir] + if cached ~= nil then return cached or nil end + local cur = dir + while cur and cur ~= "" do + if uv.fs_stat(cur .. "/.git") then + root_cache[dir] = cur + return cur + end + local parent = cur:match "(.+)/[^/]+$" + if not parent or parent == cur then break end + cur = parent + end + root_cache[dir] = false + return nil +end + +--- Read additional ignore patterns from the .rgignore file in the git root. +--- Cached per file and invalidated on mtime change. Safe in fast event contexts. +--- @param dir? string directory to resolve the git root from (defaults to cwd) +--- @return string[] patterns List of glob patterns from .rgignore +local rgignore_cache = {} ---@type table +function M.read_rgignore(dir) + local root = git_root(dir or uv.cwd()) + if not root then return EMPTY end + local path = root .. "/.rgignore" + local stat = uv.fs_stat(path) + if not stat then return EMPTY end + local cached = rgignore_cache[path] + if cached and cached.mtime == stat.mtime.sec then return cached.patterns end + local patterns = {} + local file = io.open(path, "r") + if file then + for line in file:lines() do + if line ~= "" and not line:match "^#" then + patterns[#patterns + 1] = (line:gsub("^%./", "")) + end + end + file:close() + end + rgignore_cache[path] = { mtime = stat.mtime.sec, patterns = patterns } + return patterns +end + +--- Get all hide patterns including rgignore +--- @param dir? string directory to resolve the git root from (defaults to cwd) +--- @return table patterns Combined list of all patterns to hide +function M.get_all_patterns(dir) + return vim.list_extend(vim.list_extend({}, M.patterns), M.read_rgignore(dir)) +end + +--- Create a filter for snacks picker `transform` options. +--- Snacks contract: return `false` to DROP an item, `true`/`nil` to keep it +--- (returning nil keeps the item!). Runs per item in a fast event context, +--- so only pure Lua and libuv are used here. +--- @return fun(item: snacks.picker.finder.Item): boolean +function M.create_picker_filter() + local static ---@type string[]? compiled static globs (lazy) + local last_raw, last_rg = nil, EMPTY ---@type string[]?, string[] + return function(item) + local file = item and item.file + if not file then return true end + + static = static or compile_globs(M.patterns) + local raw = M.read_rgignore(item.cwd) + if raw ~= last_raw then + last_raw, last_rg = raw, compile_globs(raw) + end + + -- Match every path segment (directories and basename) anchored, + -- so `test` drops `test/foo.lua` but never `latest.md`. + for seg in file:gmatch "[^/\\]+" do + for _, pat in ipairs(static) do + if seg:match(pat) then return false end + end + for _, pat in ipairs(last_rg) do + if seg:match(pat) then return false end + end + for _, dirname in ipairs(M.ignored_directories) do + if seg == dirname then return false end + end + end + return true + end +end + +return M diff --git a/dot_config/nvim/lua/lazy_setup.lua b/dot_config/nvim/lua/lazy_setup.lua new file mode 100644 index 0000000..8e1abfb --- /dev/null +++ b/dot_config/nvim/lua/lazy_setup.lua @@ -0,0 +1,32 @@ +require("lazy").setup({ + { + "AstroNvim/AstroNvim", + version = "^6", -- Remove version tracking to elect for nightly AstroNvim + import = "astronvim.plugins", + opts = { -- AstroNvim options must be set here with the `import` key + mapleader = " ", -- This ensures the leader key must be configured before Lazy is set up + maplocalleader = ",", -- This ensures the localleader key must be configured before Lazy is set up + icons_enabled = true, -- Set to false to disable icons (if no Nerd Font is available) + pin_plugins = nil, -- Default will pin plugins when tracking `version` of AstroNvim, set to true/false to override + update_notifications = true, -- Enable/disable notification about running `:Lazy update` twice to update pinned plugins + }, + }, + { import = "community" }, + { import = "plugins" }, +} --[[@as LazySpec]], { + -- Configure any other `lazy.nvim` configuration options here + install = { colorscheme = { "astrotheme", "habamax" } }, + ui = { backdrop = 100 }, + performance = { + rtp = { + -- disable some rtp plugins, add more to your liking + disabled_plugins = { + "gzip", + "netrwPlugin", + "tarPlugin", + "tohtml", + "zipPlugin", + }, + }, + }, +} --[[@as LazyConfig]]) diff --git a/dot_config/nvim/lua/plugins/astrocore.lua b/dot_config/nvim/lua/plugins/astrocore.lua new file mode 100644 index 0000000..5127e59 --- /dev/null +++ b/dot_config/nvim/lua/plugins/astrocore.lua @@ -0,0 +1,456 @@ +--- AstroCore configuration (AstroNvim v6 / Neovim 0.12) +--- Core settings, keymappings, and autocmds +--- @module plugins.astrocore + +local ignore_patterns = require "config.ignore_patterns" + +local file_filter = ignore_patterns.create_picker_filter() + +-- Open a snacks picker with trouble.nvim send-to-quickfix action bound to . +local function with_trouble(source, opts) + opts = opts or {} + opts.actions = require("trouble.sources.snacks").actions + opts.win = opts.win or {} + opts.win.input = opts.win.input or {} + opts.win.input.keys = vim.tbl_extend("force", opts.win.input.keys or {}, { + [""] = { "trouble_open", mode = { "n", "i" } }, + }) + require("snacks").picker[source](opts) +end + +return { + "AstroNvim/astrocore", + ---@type AstroCoreOpts + opts = { + sessions = { + autosave = { + last = true, + cwd = true, + }, + ignore = { + dirs = {}, + filetypes = { "gitcommit", "gitrebase" }, + buftypes = {}, + }, + }, + + rooter = { + detector = { + "lsp", + { ".git", "_darcs", ".hg", ".bzr", ".svn" }, + { + "package.json", + "lua", + "Cargo.toml", + "Pipfile", + "docs.json", + }, + }, + ignore = { + servers = { "golangci_lint_ls" }, + dirs = {}, + }, + autochdir = true, + scope = "global", + notify = false, + }, + + -- v6: features schema changed + -- diagnostics_mode (0-3) -> diagnostics = { virtual_text, virtual_lines } + -- large_buf and cmp are new keys + features = { + large_buf = { size = 1024 * 256, lines = 10000 }, + autopairs = true, + cmp = true, + diagnostics = { virtual_text = false, virtual_lines = false }, + highlighturl = true, + notifications = false, + }, + + -- vim.diagnostic.config() values applied when diagnostics are toggled on + diagnostics = { + virtual_text = true, + underline = true, + }, + + autocmds = { + custom_title = { + { + event = { "BufEnter", "DirChanged" }, + desc = "Update terminal title with org/repo format", + callback = function() + local filepath = vim.fn.expand "%:p" + local filename = vim.fn.expand "%:t" + + if filename == "" then + local cwd = vim.fn.getcwd() + local github_match = cwd:match "github%.com/([^/]+/[^/]+)" + if github_match then + vim.opt.titlestring = github_match + else + vim.opt.titlestring = "Nvim" + end + return + end + + local github_match = filepath:match "github%.com/([^/]+/[^/]+)" + + if github_match then + vim.opt.titlestring = string.format("%s | %s", filename, github_match) + else + vim.opt.titlestring = string.format("%s | Nvim", filename) + end + end, + }, + }, + restore_session = { + { + event = "VimEnter", + desc = "Restore previous directory session if neovim opened with no arguments", + nested = true, -- trigger other autocommands as buffers open + callback = function() + if vim.fn.argc(-1) == 0 then + require("resession").load( + vim.fn.getcwd(), + { dir = "dirsession", silence_errors = true } + ) + end + end, + }, + }, + }, + + options = { + opt = { + relativenumber = false, + number = true, + spell = false, + wrap = true, + clipboard = "unnamedplus", + title = true, + titlestring = "", + laststatus = 0, + }, + g = {}, + }, + + mappings = { + n = { + -- overrides AstroNvim's default = force quit (:q!) + [""] = { "wqa", desc = "Save all buffers and quit Neovim" }, + [""] = { "suspend", desc = "Suspend nvim (return with 'fg')" }, + ["ZZ"] = { "wqa", desc = "Save all buffers and quit Neovim" }, + [""] = { "wqa", desc = "Save all buffers and quit Neovim" }, + [""] = { "wabd", desc = "Save and close buffer" }, + ["F"] = { "za", desc = "Toggle fold under cursor" }, + -- v6: vim.diagnostic.goto_next/goto_prev deprecated in 0.11, use jump() + ["J"] = { + function() vim.diagnostic.jump { count = 1, float = true } end, + desc = "Next diagnostic", + }, + ["K"] = { + function() vim.diagnostic.jump { count = -1, float = true } end, + desc = "Previous diagnostic", + }, + ["L"] = { "BufferLineCycleNext", desc = "Next buffer" }, + ["H"] = { "BufferLineCyclePrev", desc = "Previous buffer" }, + ["e"] = { + function() require("snacks").picker.git_files { transform = file_filter } end, + desc = "Find git files", + }, + [""] = { + function() require("snacks").picker.smart() end, + desc = "Find files", + }, + ["b"] = { "BufferLinePick", desc = "Pick buffer" }, + ["f"] = { + function() require("snacks").picker.buffers() end, + desc = "Find buffers", + }, + [""] = { + function() + local current_buffer = vim.api.nvim_get_current_buf() + for _, buf in ipairs(vim.api.nvim_list_bufs()) do + if buf ~= current_buffer then vim.api.nvim_buf_delete(buf, { force = true }) end + end + end, + desc = "Close all buffers except the current one", + }, + ["g"] = { + function() + local groups = { "DEX", "DID", "DWN", "SVC", "Tests", "Actions", "Claude", "Docs", "Config" } + for _, group in ipairs(groups) do + vim.cmd("BufferLineGroupToggle " .. group) + end + end, + desc = "Toggle all buffer groups", + }, + ["l"] = { + function() require("snacks").picker.lines() end, + desc = "Find in line", + }, + ["o"] = { "!gh repo view --web", desc = "Open Repo on Web" }, + [""] = { "w", desc = "Save buffer" }, + [""] = { "wbd", desc = "Save and close buffer" }, + [""] = { "tabnext", desc = "Next Tab" }, + [""] = { "Octo issue list", desc = "List Issues" }, + [""] = { "Octo label", desc = "Manage Labels" }, + [""] = { "Octo pr list", desc = "List PRs" }, + ["s"] = { "Octo search", desc = "Octo Search" }, + [""] = { "!gh release list", desc = "List Releases" }, + ["d"] = { + function() require("snacks").picker.git_diff() end, + desc = "Search git diffs", + }, + [""] = { + function() require("snacks").picker.git_branches() end, + desc = "Search git branches", + }, + ["vv"] = { "gg0VG$", desc = "Select all contents in buffer" }, + ["T"] = { "gg", desc = "Go to top of file" }, + ["d"] = { + function() require("snacks").picker.diagnostics() end, + desc = "Find diagnostics", + }, + ["l"] = { + function() require("snacks").picker.lines() end, + desc = "Find in line", + }, + ["t"] = { + function() require("snacks").picker.lines() end, + desc = "Find in line", + }, + ["w"] = { + function() require("snacks").picker.grep() end, + desc = "Find word", + }, + ["f"] = { + function() require("snacks").picker.files { transform = file_filter } end, + desc = "Find files (cwd)", + }, + [""] = { + function() + require("snacks").picker.lsp_symbols { layout = { preset = "vscode", preview = "main" } } + end, + desc = "Find LSP symbols", + }, + ["o"] = { + function() require("snacks").picker.recent() end, + desc = "Find recent files", + }, + ["p"] = { + function() require("snacks").picker.projects() end, + desc = "Find projects", + }, + ["gc"] = { + function() + local ai_dir = vim.fn.getcwd() .. "/.ai" + if vim.fn.isdirectory(ai_dir) == 0 then + vim.notify("No .ai directory found in current project", vim.log.levels.WARN) + return + end + require("snacks").picker.files { + cwd = ai_dir, + transform = file_filter, + } + end, + desc = "Find AI files", + }, + ["g"] = { desc = "Find by Group" }, + ["gw"] = { + function() + local workflows_dir = vim.fn.getcwd() .. "/.github/workflows" + if vim.fn.isdirectory(workflows_dir) == 0 then + vim.notify("No .github/workflows directory found in current project", vim.log.levels.WARN) + return + end + require("snacks").picker.files { + cwd = workflows_dir, + transform = file_filter, + } + end, + desc = "Find GitHub Workflow files", + }, + ["gd"] = { + function() + require("snacks").picker.files { + cmd = "fd", + args = { "devbox.json|process-compose.yaml|Dockerfile|docker-compose.yaml|compose.yaml" }, + } + end, + desc = "Find Devbox/Docker files", + }, + ["gm"] = { + function() require("snacks").picker.files { cmd = "fd", args = { "Makefile" } } end, + desc = "Find Makefile files", + }, + ["gp"] = { + function() require("snacks").picker.files { cmd = "fd", args = { "package.json" } } end, + desc = "Find Package.json files", + }, + [""] = { + function() + if vim.b.toggle_number == 99 then + vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes("", true, false, true), "t", false) + return + end + local content = vim.fn.getreg "+" + vim.api.nvim_paste(content, true, -1) + end, + desc = "Paste from system clipboard", + }, + ["d"] = { + function() with_trouble("diagnostics") end, + desc = "Search diagnostics ( sends to Trouble)", + }, + ["a"] = { + function() vim.lsp.buf.code_action() end, + desc = "Show code actions", + }, + ["s"] = { + function() with_trouble("lsp_symbols") end, + desc = "Search LSP symbols ( sends to Trouble)", + }, + ["S"] = { + function() with_trouble("lsp_workspace_symbols") end, + desc = "Search LSP workspace symbols ( sends to Trouble)", + }, + [""] = { "SupermavenToggle", desc = "Toggle Supermaven" }, + + ["u"] = { desc = "UI Toggles" }, + ["ud"] = { + function() require("snacks").toggle.diagnostics():toggle() end, + desc = "Toggle Diagnostics", + }, + ["un"] = { + function() require("snacks").toggle.line_number():toggle() end, + desc = "Toggle Line Numbers", + }, + ["ur"] = { + function() require("snacks").toggle.option("relativenumber"):toggle() end, + desc = "Toggle Relative Numbers", + }, + ["uw"] = { + function() require("snacks").toggle.option("wrap"):toggle() end, + desc = "Toggle Line Wrap", + }, + ["us"] = { + function() require("snacks").toggle.option("spell"):toggle() end, + desc = "Toggle Spelling", + }, + ["ui"] = { + function() require("snacks").toggle.inlay_hints():toggle() end, + desc = "Toggle Inlay Hints", + }, + ["ut"] = { + function() require("snacks").toggle.treesitter():toggle() end, + desc = "Toggle Treesitter", + }, + ["uc"] = { + function() require("snacks").toggle.option("conceallevel", { off = 0, on = 2 }):toggle() end, + desc = "Toggle Conceal", + }, + ["uI"] = { + function() require("snacks").toggle.indent():toggle() end, + desc = "Toggle Indent Guides", + }, + ["uz"] = { + function() require("snacks").toggle.zen():toggle() end, + desc = "Toggle Zen Mode", + }, + ["uZ"] = { + function() require("snacks").toggle.zoom():toggle() end, + desc = "Toggle Zoom", + }, + ["ua"] = { + function() require("snacks").toggle.animate():toggle() end, + desc = "Toggle Animations", + }, + ["uS"] = { + function() require("snacks").toggle.scroll():toggle() end, + desc = "Toggle Smooth Scroll", + }, + ["up"] = { + function() require("snacks").toggle.profiler():toggle() end, + desc = "Toggle Profiler", + }, + ["uh"] = { + function() require("snacks").toggle.profiler_highlights():toggle() end, + desc = "Toggle Profiler Highlights", + }, + ["ux"] = { + function() require("snacks").toggle.dim():toggle() end, + desc = "Toggle Dim Inactive", + }, + ["uf"] = { + function() + vim.g.disable_autoformat = not vim.g.disable_autoformat + local status = vim.g.disable_autoformat and "disabled" or "enabled" + vim.notify("Format on save " .. status, vim.log.levels.INFO) + end, + desc = "Toggle Format on Save", + }, + -- v6: virtual_lines is native in vim.diagnostic.config, lsp_lines plugin no longer needed + ["ul"] = { + function() + local cfg = vim.diagnostic.config() or {} + local enabled = not cfg.virtual_lines + vim.diagnostic.config { virtual_lines = enabled, virtual_text = not enabled } + end, + desc = "Toggle Virtual Lines", + }, + }, + i = { + [""] = { "wa", desc = "Save buffer, and return to normal mode" }, + [""] = { "wabd", desc = "Save, close buffer, and return to normal mode" }, + [""] = { "wabd", desc = "Save, close buffer, and return to normal mode" }, + }, + v = { + [""] = { "wbd", desc = "Save and close buffer" }, + [""] = { "wbd", desc = "Save and close buffer" }, + ["D"] = { + function() + vim.cmd 'normal! "vy' + local selection = vim.fn.getreg "v" + local escaped = vim.fn.escape(selection, [[/\.*$^~[]]) + vim.cmd(string.format([[%%s/%s//ge]], escaped)) + vim.notify(string.format("Deleted all occurrences of: %s", selection), vim.log.levels.INFO) + end, + desc = "Delete all occurrences of selected text", + }, + ["R"] = { + function() + vim.cmd 'normal! "vy' + local selection = vim.fn.getreg "v" + local escaped = vim.fn.escape(selection, [[/\.*$^~[]]) + require("snacks").input({ + prompt = string.format("Replace '%s' with:", selection), + default = "", + }, function(replacement) + if replacement then + local escaped_replacement = vim.fn.escape(replacement, [[/\&~]]) + vim.cmd(string.format([[%%s/%s/%s/ge]], escaped, escaped_replacement)) + vim.notify(string.format("Replaced '%s' with '%s'", selection, replacement), vim.log.levels.INFO) + end + end) + end, + desc = "Replace all occurrences of selected text", + }, + }, + t = { + [""] = { function() vim.cmd 'normal! "+p' end, desc = "Paste from system clipboard" }, + ["f"] = { + function() require("snacks").picker.buffers() end, + desc = "Find buffers", + }, + [""] = { + function() + vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes([[]], true, false, true), "n", false) + vim.cmd "close" + end, + desc = "Exit terminal and close window", + }, + }, + }, + }, +} diff --git a/dot_config/nvim/lua/plugins/astrolsp.lua b/dot_config/nvim/lua/plugins/astrolsp.lua new file mode 100644 index 0000000..b8b121f --- /dev/null +++ b/dot_config/nvim/lua/plugins/astrolsp.lua @@ -0,0 +1,104 @@ +if true then return {} end -- WARN: REMOVE THIS LINE TO ACTIVATE THIS FILE + +-- AstroLSP allows you to customize the features in AstroNvim's LSP configuration engine +-- Configuration documentation can be found with `:h astrolsp` +-- NOTE: We highly recommend setting up the Lua Language Server (`:LspInstall lua_ls`) +-- as this provides autocomplete and documentation while editing + +---@type LazySpec +return { + "AstroNvim/astrolsp", + ---@type AstroLSPOpts + opts = { + -- Configuration table of features provided by AstroLSP + features = { + codelens = true, -- enable/disable codelens refresh on start + inlay_hints = false, -- enable/disable inlay hints on start + semantic_tokens = true, -- enable/disable semantic token highlighting + }, + -- customize lsp formatting options + formatting = { + -- control auto formatting on save + format_on_save = { + enabled = true, -- enable or disable format on save globally + allow_filetypes = { -- enable format on save for specified filetypes only + -- "go", + }, + ignore_filetypes = { -- disable format on save for specified filetypes + -- "python", + }, + }, + disabled = { -- disable formatting capabilities for the listed language servers + -- disable lua_ls formatting capability if you want to use StyLua to format your lua code + -- "lua_ls", + }, + timeout_ms = 1000, -- default format timeout + -- filter = function(client) -- fully override the default formatting function + -- return true + -- end + }, + -- enable servers that you already have installed without mason + servers = { + -- "pyright" + }, + -- customize language server configuration passed to `vim.lsp.config` + -- client specific configuration can also go in `lsp/` in your configuration root (see `:h lsp-config`) + config = { + -- ["*"] = { capabilities = {} }, -- modify default LSP client settings such as capabilities + }, + -- customize how language servers are attached + handlers = { + -- a function with the key `*` modifies the default handler, functions takes the server name as the parameter + -- ["*"] = function(server) vim.lsp.enable(server) end + + -- the key is the server that is being setup with `vim.lsp.config` + -- rust_analyzer = false, -- setting a handler to false will disable the set up of that language server + }, + -- Configure buffer local auto commands to add when attaching a language server + autocmds = { + -- first key is the `augroup` to add the auto commands to (:h augroup) + lsp_codelens_refresh = { + -- Optional condition to create/delete auto command group + -- can either be a string of a client capability or a function of `fun(client, bufnr): boolean` + -- condition will be resolved for each client on each execution and if it ever fails for all clients, + -- the auto commands will be deleted for that buffer + cond = "textDocument/codeLens", + -- cond = function(client, bufnr) return client.name == "lua_ls" end, + -- list of auto commands to set + { + -- events to trigger + event = { "InsertLeave", "BufEnter" }, + -- the rest of the autocmd options (:h nvim_create_autocmd) + desc = "Refresh codelens (buffer)", + callback = function(args) + if require("astrolsp").config.features.codelens then vim.lsp.codelens.enable(true, { bufnr = args.buf }) end + end, + }, + }, + }, + -- mappings to be set up on attaching of a language server + mappings = { + n = { + -- a `cond` key can provided as the string of a server capability to be required to attach, or a function with `client` and `bufnr` parameters from the `on_attach` that returns a boolean + gD = { + function() vim.lsp.buf.declaration() end, + desc = "Declaration of current symbol", + cond = "textDocument/declaration", + }, + ["uY"] = { + function() require("astrolsp.toggles").buffer_semantic_tokens() end, + desc = "Toggle LSP semantic highlight (buffer)", + cond = function(client) + return client:supports_method "textDocument/semanticTokens/full" and vim.lsp.semantic_tokens ~= nil + end, + }, + }, + }, + -- A custom `on_attach` function to be run after the default `on_attach` function + -- takes two parameters `client` and `bufnr` (`:h lsp-attach`) + on_attach = function(client, bufnr) + -- this would disable semanticTokensProvider for all clients + -- client.server_capabilities.semanticTokensProvider = nil + end, + }, +} diff --git a/dot_config/nvim/lua/plugins/astroui.lua b/dot_config/nvim/lua/plugins/astroui.lua new file mode 100644 index 0000000..0c49c1f --- /dev/null +++ b/dot_config/nvim/lua/plugins/astroui.lua @@ -0,0 +1,15 @@ + +-- AstroUI provides the basis for configuring the AstroNvim User Interface +-- Configuration documentation can be found with `:h astroui` +-- NOTE: We highly recommend setting up the Lua Language Server (`:LspInstall lua_ls`) +-- as this provides autocomplete and documentation while editing + +---@type LazySpec +return { + "AstroNvim/astroui", + ---@type AstroUIOpts + opts = { + -- change colorscheme + colorscheme = "cyberdream", + }, +} diff --git a/dot_config/nvim/lua/plugins/conform.lua b/dot_config/nvim/lua/plugins/conform.lua new file mode 100644 index 0000000..dd14af1 --- /dev/null +++ b/dot_config/nvim/lua/plugins/conform.lua @@ -0,0 +1,105 @@ +-- Run all safe LSP code actions advertised by attached servers before save, +-- then let conform format. The astrocommunity conform-nvim pack wires up +-- format_on_save; this just adds the pre-save code-action pass. + +-- Action kinds we consider safe to apply on save. Matches exact kinds and +-- any subkinds via "." prefix (LSP CodeActionKind is hierarchical). +-- Deliberately NOT the bare "source" umbrella: it covers non-idempotent +-- document rewrites (e.g. marksman's "Create Table of Contents"). +local SAFE_KINDS = { + "source.organizeImports", + "source.fixAll", + "quickfix", +} + +local function is_safe_kind(kind) + if not kind or kind == "" then return false end + for _, safe in ipairs(SAFE_KINDS) do + if kind == safe or vim.startswith(kind, safe .. ".") then return true end + end + return false +end + +-- Pull the kinds a given client advertises support for, filtered to safe ones. +-- If the server returns no kinds list, fall back to the top-level SAFE_KINDS so +-- it gets the chance to satisfy a broad "only" filter. +local function safe_kinds_for(client) + local provider = (client.server_capabilities or {}).codeActionProvider + if not provider then return {} end + local advertised = type(provider) == "table" and provider.codeActionKinds or nil + if not advertised or #advertised == 0 then return vim.deepcopy(SAFE_KINDS) end + local kinds = {} + for _, k in ipairs(advertised) do + if is_safe_kind(k) then table.insert(kinds, k) end + end + return kinds +end + +-- Collect LSP-shaped diagnostics so quickfix actions have something to bind to. +local function lsp_diagnostics(bufnr) + local out = {} + for _, d in ipairs(vim.diagnostic.get(bufnr)) do + if d.user_data and d.user_data.lsp then table.insert(out, d.user_data.lsp) end + end + return out +end + +-- Servers may ignore context.only (LSP allows it), so re-filter the returned +-- actions by kind before applying. Kind-less actions are skipped: we asked for +-- specific kinds, so anything untagged is not provably safe. +local function apply_result(client, bufnr, result) + if not result then return end + for _, action in ipairs(result) do + if is_safe_kind(action.kind) then + if action.edit then + vim.lsp.util.apply_workspace_edit(action.edit, client.offset_encoding or "utf-8") + end + if type(action.command) == "table" then + client:exec_cmd(action.command, { bufnr = bufnr }) + end + end + end +end + +local function apply_safe_actions(bufnr) + local clients = vim.lsp.get_clients { bufnr = bufnr, method = "textDocument/codeAction" } + if #clients == 0 then return end + local diagnostics = lsp_diagnostics(bufnr) + for _, client in ipairs(clients) do + local kinds = safe_kinds_for(client) + if #kinds > 0 then + local enc = client.offset_encoding or "utf-8" + local params = vim.lsp.util.make_range_params(0, enc) + params.context = { only = kinds, diagnostics = diagnostics, triggerKind = 2 } + local resp = client:request_sync("textDocument/codeAction", params, 1000, bufnr) + if resp and not resp.err then apply_result(client, bufnr, resp.result) end + end + end +end + +---@type LazySpec +return { + "stevearc/conform.nvim", + init = function() + vim.api.nvim_create_autocmd("BufWritePre", { + group = vim.api.nvim_create_augroup("user_lsp_safe_actions", { clear = true }), + desc = "Apply all safe LSP code actions (source.*, quickfix) before save", + callback = function(args) + if vim.bo[args.buf].buftype ~= "" then return end + if vim.g.disable_autoformat or vim.b[args.buf].disable_autoformat then return end + apply_safe_actions(args.buf) + end, + }) + end, + opts = function(_, opts) + -- Skip format-on-save for JavaScript buffers (.js/.mjs/.cjs); manual + -- :Format / lf still works. Wraps the astrocommunity pack's + -- format_on_save so its autoformat toggle handling is preserved. + local pack_format_on_save = opts.format_on_save + opts.format_on_save = function(bufnr) + if vim.bo[bufnr].filetype == "javascript" then return end + if type(pack_format_on_save) == "function" then return pack_format_on_save(bufnr) end + return pack_format_on_save + end + end, +} diff --git a/dot_config/nvim/lua/plugins/flash.lua b/dot_config/nvim/lua/plugins/flash.lua new file mode 100644 index 0000000..16c1941 --- /dev/null +++ b/dot_config/nvim/lua/plugins/flash.lua @@ -0,0 +1,76 @@ +--- flash.nvim: labeled jumps and enhanced motions +---@type LazySpec +return { + "folke/flash.nvim", + event = "VeryLazy", + ---@type Flash.Config + opts = { + modes = { + char = { + -- normal-mode `f` is the Vimium-style word jump and `F` is the + -- search-style jump below, so flash's char mode must not rebind + -- them; t/T/;/, stay enhanced + keys = { "t", "T", ";", "," }, + -- show jump labels on the char-motion matches (t/T) + jump_labels = true, + }, + -- label-enhanced regular `/` and `?` search; in cmdline + -- toggles it off for a plain search + search = { enabled = true }, + }, + }, + keys = { + { + "f", + mode = "n", + function() + -- Vimium-c style: label every visible word start immediately, + -- press a label to jump (no search pattern typed) + require("flash").jump { + search = { mode = "search", max_length = 0 }, + label = { after = false, before = { 0, 0 } }, + pattern = [[\<]], + } + end, + desc = "Jump to word (Vimium-style)", + }, + { + "F", + mode = "n", + function() + -- Search-style flash: type a regex pattern and labels appear on + -- every match. While selecting, ``/`` cycle to the + -- next/previous result, confirms the selection, a label jumps + -- directly, cancels back to the origin. The pattern is pushed + -- to search history and the `/` register, so `n`/`N` keep working + -- after the jump. + local current ---@type Flash.Match? + require("flash").jump { + search = { mode = "search" }, + jump = { history = true, register = true, nohlsearch = true }, + actions = { + [""] = function(state) + -- first press selects the current target, then cycle forward + current = current and state:find { match = current, forward = true, wrap = true } or state.target + if current then state:jump(current) end + end, + [""] = function(state) + current = state:find { match = current or state.target, forward = false, wrap = true } + if current then state:jump(current) end + end, + [""] = function(state) + state:jump(current) -- nil falls back to state.target + return false -- end the loop + end, + }, + } + end, + desc = "Flash search", + }, + { "s", mode = { "n", "x", "o" }, function() require("flash").jump() end, desc = "Flash" }, + { "S", mode = { "n", "x", "o" }, function() require("flash").treesitter() end, desc = "Flash Treesitter" }, + { "r", mode = "o", function() require("flash").remote() end, desc = "Remote Flash" }, + { "R", mode = { "o", "x" }, function() require("flash").treesitter_search() end, desc = "Treesitter Search" }, + { "", mode = { "c" }, function() require("flash").toggle() end, desc = "Toggle Flash Search" }, + }, +} diff --git a/dot_config/nvim/lua/plugins/kitty.lua b/dot_config/nvim/lua/plugins/kitty.lua new file mode 100644 index 0000000..443b39d --- /dev/null +++ b/dot_config/nvim/lua/plugins/kitty.lua @@ -0,0 +1,42 @@ +local function t_nav(fn) + return function() + vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes([[]], true, false, true), "n", false) + vim.schedule(fn) + end +end + +return { + "MunsMan/kitty-navigator.nvim", + -- Keep the kitty-side kittens in sync with the plugin. ~/.config/kitty is symlinked into the + -- dotfiles repo, so this copies navigate_kitty.py + pass_keys.py straight into the tracked config. + -- navigate_kitty.py is required for the nvim->kitty edge handoff (init.lua calls `kitty @ kitten + -- navigate_kitty.py `); pass_keys.py must match the `neighboring_window` map syntax in kitty.conf. + build = { + "cp navigate_kitty.py ~/.config/kitty", + "cp pass_keys.py ~/.config/kitty", + }, + -- Disable smart-splits navigation keys (AstroNvim injects these when KITTY_LISTEN_ON is set); + -- kitty-navigator owns . Resize bindings () are left intact. + specs = { + { + "AstroNvim/astrocore", + opts = function(_, opts) + local m = opts.mappings.n + m[""] = false + m[""] = false + m[""] = false + m[""] = false + end, + }, + }, + keys = { + {"", function()require("kitty-navigator").navigateLeft()end, desc = "Move left a Split", mode = {"n"}}, + {"", function()require("kitty-navigator").navigateDown()end, desc = "Move down a Split", mode = {"n"}}, + {"", function()require("kitty-navigator").navigateUp()end, desc = "Move up a Split", mode = {"n"}}, + {"", function()require("kitty-navigator").navigateRight()end, desc = "Move right a Split", mode = {"n"}}, + {"", t_nav(function()require("kitty-navigator").navigateLeft()end), desc = "Move left a Split", mode = {"t"}}, + {"", t_nav(function()require("kitty-navigator").navigateDown()end), desc = "Move down a Split", mode = {"t"}}, + {"", t_nav(function()require("kitty-navigator").navigateUp()end), desc = "Move up a Split", mode = {"t"}}, + {"", t_nav(function()require("kitty-navigator").navigateRight()end), desc = "Move right a Split", mode = {"t"}}, + }, +} diff --git a/dot_config/nvim/lua/plugins/mason.lua b/dot_config/nvim/lua/plugins/mason.lua new file mode 100644 index 0000000..2d79b5f --- /dev/null +++ b/dot_config/nvim/lua/plugins/mason.lua @@ -0,0 +1,28 @@ +if true then return {} end -- WARN: REMOVE THIS LINE TO ACTIVATE THIS FILE + +-- Customize Mason + +---@type LazySpec +return { + -- use mason-tool-installer for automatically installing Mason packages + { + "WhoIsSethDaniel/mason-tool-installer.nvim", + -- overrides `require("mason-tool-installer").setup(...)` + opts = { + -- Make sure to use the names found in `:Mason` + ensure_installed = { + -- install language servers + "lua-language-server", + + -- install formatters + "stylua", + + -- install debuggers + "debugpy", + + -- install any other package + "tree-sitter-cli", + }, + }, + }, +} diff --git a/dot_config/nvim/lua/plugins/none-ls.lua b/dot_config/nvim/lua/plugins/none-ls.lua new file mode 100644 index 0000000..2b0ed18 --- /dev/null +++ b/dot_config/nvim/lua/plugins/none-ls.lua @@ -0,0 +1,24 @@ +if true then return {} end -- WARN: REMOVE THIS LINE TO ACTIVATE THIS FILE + +-- Customize None-ls sources + +---@type LazySpec +return { + "nvimtools/none-ls.nvim", + opts = function(_, opts) + -- opts variable is the default configuration table for the setup function call + -- local null_ls = require "null-ls" + + -- Check supported formatters and linters + -- https://github.com/nvimtools/none-ls.nvim/tree/main/lua/null-ls/builtins/formatting + -- https://github.com/nvimtools/none-ls.nvim/tree/main/lua/null-ls/builtins/diagnostics + + -- Only insert new sources, do not replace the existing ones + -- (If you wish to replace, use `opts.sources = {}` instead of the `list_insert_unique` function) + opts.sources = require("astrocore").list_insert_unique(opts.sources, { + -- Set a formatter + -- null_ls.builtins.formatting.stylua, + -- null_ls.builtins.formatting.prettier, + }) + end, +} diff --git a/dot_config/nvim/lua/plugins/treesitter.lua b/dot_config/nvim/lua/plugins/treesitter.lua new file mode 100644 index 0000000..3a9afe9 --- /dev/null +++ b/dot_config/nvim/lua/plugins/treesitter.lua @@ -0,0 +1,24 @@ +if true then return {} end -- WARN: REMOVE THIS LINE TO ACTIVATE THIS FILE + +-- Customize Treesitter +-- -------------------- +-- Treesitter customizations are handled with AstroCore +-- as nvim-treesitter simply provides a download utility for parsers + +---@type LazySpec +return { + "AstroNvim/astrocore", + ---@type AstroCoreOpts + opts = { + treesitter = { + highlight = true, -- enable/disable treesitter based highlighting + indent = true, -- enable/disable treesitter based indentation + auto_install = true, -- enable/disable automatic installation of detected languages + ensure_installed = { + "lua", + "vim", + -- add more arguments for adding more treesitter parsers + }, + }, + }, +} diff --git a/dot_config/nvim/lua/plugins/user.lua b/dot_config/nvim/lua/plugins/user.lua new file mode 100644 index 0000000..71bf325 --- /dev/null +++ b/dot_config/nvim/lua/plugins/user.lua @@ -0,0 +1,98 @@ +--- User plugins: cursor animation and snacks.nvim (picker + dashboard) customization + +-- Confirm action for file-opening pickers: opens the selection as a +-- tab via `tabdrop` (:tab drop semantics — focus the buffer if it's already +-- visible in any tab, don't spawn a tab from an empty buffer, else open a +-- new tab). Explicit edit-cmd keybinds (/ vsplit, split) +-- carry `action.cmd` and are passed through untouched. +local function tab_confirm(picker, item, action) + local actions = require "snacks.picker.actions" + if action.cmd then return actions.jump(picker, item, action) end + return actions.jump(picker, item, vim.tbl_extend("force", {}, action, { cmd = "tabdrop" })) +end + +-- Tab-on-enter + vsplit-on-shift-enter for the file-opening pickers only; +-- location pickers (lsp_*, lines, diagnostics, grep) and sources with their +-- own confirm (git_branches, projects) keep their defaults. +local picker_sources = {} +for _, source in ipairs { "files", "git_files", "smart", "recent", "buffers" } do + picker_sources[source] = { + confirm = tab_confirm, + win = { + input = { keys = { [""] = { "edit_vsplit", mode = { "n", "i" } } } }, + list = { keys = { [""] = "edit_vsplit" } }, + }, + } +end + +-- Smart picker: anchor to the current git repository. `config` runs when +-- the picker opens: cwd becomes the repo root (so the `files` finder scans +-- the whole repo, not just a rooter-chdir'd subdir) and `filter.cwd` drops +-- buffers/recent entries from other repos. Outside a repo, plain cwd +-- behavior is kept. +picker_sources.smart.config = function(opts) + local root = require("snacks").git.get_root() + if root then + opts.cwd = root + opts.filter = vim.tbl_extend("force", opts.filter or {}, { cwd = true }) + end + return opts +end + +---@type LazySpec +return { + { "sphamba/smear-cursor.nvim", opts = {} }, + + -- terminals are handled by kitty now + { "akinsho/toggleterm.nvim", enabled = false }, + + { + "folke/snacks.nvim", + opts = { + picker = { + jump = { reuse_win = true }, + sources = picker_sources, + }, + dashboard = { + enabled = true, + preset = { + -- "prdlk.nvim" in ansi_regular block style + header = table.concat({ + "██████ ██████ ██████ ██ ██ ██ ███ ██ ██ ██ ██ ███ ███", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██ ██ ██ ██ ████ ████", + "██████ ██████ ██ ██ ██ █████ ██ ██ ██ ██ ██ ██ ██ ████ ██", + "██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██", + "██ ██ ██ ██████ ███████ ██ ██ ██ ██ ████ ████ ██ ██ ██", + }, "\n"), + }, + sections = { + { section = "header" }, + { section = "keys", gap = 1, padding = 1 }, + { + icon = " ", + desc = "Browse Repo", + padding = 1, + key = "b", + action = function() Snacks.gitbrowse() end, + }, + { + icon = " ", + desc = "Projects (ghq)", + padding = 1, + key = "p", + action = function() + local repos = vim.fn.systemlist "ghq list -p" + vim.ui.select(repos, { prompt = "Select repository" }, function(dir) + if not dir then return end + vim.cmd.cd(dir) + Snacks.picker.files() + end) + end, + }, + { section = "startup" }, + }, + }, + }, + }, + +} diff --git a/dot_config/nvim/lua/plugins/yazi.lua b/dot_config/nvim/lua/plugins/yazi.lua new file mode 100644 index 0000000..28e154c --- /dev/null +++ b/dot_config/nvim/lua/plugins/yazi.lua @@ -0,0 +1,91 @@ +--- Yazi as the default file manager (full neo-tree replacement) +---@type LazySpec +return { + { + "mikavilpas/yazi.nvim", + version = "*", -- latest stable release + event = "VeryLazy", + dependencies = { + { "nvim-lua/plenary.nvim", lazy = true }, + }, + keys = { + { + "-", + mode = { "n", "v" }, + "Yazi", + desc = "Open yazi at the current file", + }, + { + "cw", + "Yazi cwd", + desc = "Open yazi in nvim's working directory", + }, + { + -- replaces AstroNvim's o "Toggle Explorer Focus" (neo-tree) + "o", + "Yazi", + desc = "Open Explorer (yazi at current file)", + }, + { + "", + mode = { "n", "v", "t" }, + function() + -- true toggle: if a yazi window is already open, quit it instead + -- of nesting a second instance (yazi.nvim's toggle() always spawns) + for _, win in ipairs(vim.api.nvim_list_wins()) do + local buf = vim.api.nvim_win_get_buf(win) + if vim.bo[buf].filetype == "yazi" then + -- send `q` to the yazi process so it exits gracefully and + -- saves its state for the next resume + vim.api.nvim_chan_send(vim.bo[buf].channel, "q") + return + end + end + vim.cmd "Yazi toggle" + end, + desc = "Toggle yazi (resume last session)", + }, + }, + ---@type YaziConfig | {} + opts = { + -- yazi replaces netrw/neo-tree when opening directories (nvim .) + open_for_directories = true, + -- keep a single yazi tab: `true` would pass every visible split as an + -- extra path arg, and each one becomes a yazi tab that then persists + -- across toggles (resume), piling up tabs over time + open_multiple_tabs = false, + -- occupy the whole screen instead of a floating popup + floating_window_scaling_factor = 1, + yazi_floating_window_border = "none", + keymaps = { + show_help = "", + -- Open the hovered/selected file(s) as nvim splits. These are + -- buffer-local terminal-mode keymaps on the yazi buffer, so nvim + -- intercepts the chord before the yazi process ever sees it -- + -- nothing is needed in ~/.config/yazi/keymap.toml. Chords match + -- the telescope/snacks picker convention (//). + open_file_in_vertical_split = "", + open_file_in_horizontal_split = "", + -- NOTE: the default (open in tab) shadows the yazi-side + -- "plugin tv" bind from keymap.toml while inside nvim; + -- set open_file_in_tab = false to let reach yazi/tv instead + open_file_in_tab = "", + -- ~/.config/yazi/keymap.toml binds to quit inside yazi; + -- don't hijack it for the quickfix list (multi-select still goes + -- to the quickfix list via the default yazi_opened_multiple_files) + send_to_quickfix_list = false, + }, + integrations = { + -- copy-relative-path action inside snacks pickers too + picker_add_copy_relative_path_action = "snacks.picker", + }, + }, + init = function() + -- netrw is fully replaced by yazi (open_for_directories) + vim.g.loaded_netrwPlugin = 1 + end, + }, + + -- neo-tree is fully replaced by yazi.nvim + { "nvim-neo-tree/neo-tree.nvim", enabled = false }, +} diff --git a/dot_config/nvim/lua/polish.lua b/dot_config/nvim/lua/polish.lua new file mode 100644 index 0000000..cbcd1d6 --- /dev/null +++ b/dot_config/nvim/lua/polish.lua @@ -0,0 +1,2 @@ +-- This will run last in the setup process. +-- Pure lua; anything that doesn't fit the normal config locations goes here. diff --git a/dot_config/nvim/neovim.yml b/dot_config/nvim/neovim.yml new file mode 100644 index 0000000..b9235ab --- /dev/null +++ b/dot_config/nvim/neovim.yml @@ -0,0 +1,6 @@ +--- +base: lua51 + +globals: + vim: + any: true diff --git a/dot_config/nvim/prdlk.sh b/dot_config/nvim/prdlk.sh new file mode 100644 index 0000000..c733552 --- /dev/null +++ b/dot_config/nvim/prdlk.sh @@ -0,0 +1,24 @@ +#!/bin/bash +# Generated Bash ANSI Art + +ansi_art_lines=( + " ████ ██████ ████ ████ " + " ████░░ ████░████░░ ░░░░ " + "██████████ ████████ ██████████░░ ████░████░░████ ██████████ ████ ████ ██████ ██████████████ " + "████░░░░████ ████░░░░░░░████░░░░████░░ ████░████████░░░░ ████░░░░████ ████░░ ████░░ ████░████░░████░░████ " + "████░░ ████░████░░ ████░░ ████░░ ████░██████░░░░ ████░░ ████░████░░ ████░░ ████░████░░████░░████░░" + "████░░ ████░████░░ ████░░ ████░░ ████░████████ ████ ████░░ ████░░ ████████░░░░ ████░████░░████░░████░░" + "██████████░░░████░░ ██████████░░ ████░████░░████ ████░████░░ ████░░ ████░░░░ ████░████░░████░░████░░" + "████░░░░░░░░ ░░░░ ░░░░░░░░░░ ░░░░ ░░░░ ░░░░ ░░░░ ░░░░ ░░░░ ░░░░ ░░░░ ░░░░ ░░░░ ░░░░" + "████░░ " + " ░░░░ " +) + +display_ansi_art() { + for line in "${ansi_art_lines[@]}"; do + echo -e "$line" + done +} + +# Call the function +display_ansi_art diff --git a/dot_config/nvim/private_dot_gitignore b/dot_config/nvim/private_dot_gitignore new file mode 100644 index 0000000..88007b8 --- /dev/null +++ b/dot_config/nvim/private_dot_gitignore @@ -0,0 +1,4 @@ +/.omc +/.omc/state +/.understand-anything/tmp +/.understand-anything diff --git a/dot_config/nvim/selene.toml b/dot_config/nvim/selene.toml new file mode 100644 index 0000000..e7005c3 --- /dev/null +++ b/dot_config/nvim/selene.toml @@ -0,0 +1,8 @@ +std = "neovim" + +[rules] +global_usage = "allow" +if_same_then_else = "allow" +incorrect_standard_library_use = "allow" +mixed_table = "allow" +multiple_statements = "allow" diff --git a/dot_config/nvim/zed-keymap.jsonc b/dot_config/nvim/zed-keymap.jsonc new file mode 100644 index 0000000..585ba5c --- /dev/null +++ b/dot_config/nvim/zed-keymap.jsonc @@ -0,0 +1,77 @@ +// Zed keymap mirroring the AstroNvim config in this repo. +// Only binds with a real Zed equivalent are mapped; nvim-only features +// (lazygit, Octo, ClaudeCode, snacks scoped pickers, Overseer-specific +// pickers, in-line search) are skipped — see notes at the bottom. +// +// Docs: https://zed.dev/docs/key-bindings +[ + { + "context": "Workspace", + "bindings": { + "ctrl-e": "workspace::ToggleLeftDock", // toggle explorer + "ctrl-s": "workspace::Save", // save + "ctrl-m": "task::Spawn" // run task (Overseer) + } + }, + { + "context": "Editor && vim_mode == insert", + "bindings": { + "ctrl-s": "workspace::Save" // i_ + } + }, + { + "context": "VimControl && vim_mode == normal", + "bindings": { + // --- existing binds --- + "space space": "file_finder::Toggle", + "space w": "pane::CloseActiveItem", + "space o": "outline::Toggle", + "space g b": "git::Branch", + "space t": "terminal_panel::ToggleFocus", + "space g g": "git_panel::ToggleFocus", + "space /": "pane::DeploySearch", + "escape escape": "workspace::Save", + "shift-u": "vim::Redo", + "space h": "editor::ToggleInlayHints", + "space e": "pane::RevealInProjectPanel", + "ctrl-a": "editor::ToggleCodeActions", + "] f": "vim::NextMethodStart", + "[ f": "vim::PreviousMethodStart", + + // --- diagnostics / buffers / folds --- + "shift-j": "editor::GoToDiagnostic", // J next diagnostic + "shift-k": "editor::GoToPreviousDiagnostic", // K prev diagnostic + "shift-l": "pane::ActivateNextItem", // L next buffer + "shift-h": "pane::ActivatePreviousItem", // H prev buffer + "shift-f": "editor::ToggleFold", // F = za + "v v": "editor::SelectAll", // vv select whole buffer + + // --- save / close --- + "ctrl-s": "workspace::Save", + "ctrl-w": "pane::CloseActiveItem", // save+close -> close (Zed autosaves) + + // --- find ( prefix) --- + "ctrl-f f": "file_finder::Toggle", + "ctrl-f w": "pane::DeploySearch", + "ctrl-f d": "diagnostics::Deploy", + "ctrl-f o": "projects::OpenRecent", + "ctrl-f p": "projects::OpenRecent", + + // --- buffers ( prefix) --- + "ctrl-b f": "tab_switcher::Toggle", + + // --- actions / symbols ( prefix) --- + "ctrl-a a": "editor::ToggleCodeActions", + "ctrl-a d": "diagnostics::Deploy", + "ctrl-a s": "outline::Toggle", + "ctrl-a shift-s": "project_symbols::Toggle", + + // --- git ( prefix) --- + "ctrl-g g": "git_panel::ToggleFocus", + "ctrl-g b": "git::Branch", + + // --- terminal --- + "ctrl-j ctrl-j": "terminal_panel::ToggleFocus" + } + } +]