diff --git a/.gitignore b/.gitignore index 68486fc3d35..bfc720f63cc 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,6 @@ spell/ # lazy-lock.json in version control - see https://lazy.folke.io/usage/lockfile # For the official `nvim-lua/kickstart.nvim` git repository, we leave it ignored to avoid unneeded # merge conflicts. -lazy-lock.json +# lazy-lock.json .DS_Store diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000000..620486be3dd --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,173 @@ +# AGENTS.md + +Agent-facing contributor guide for this Neovim config repository. + +## Repository Snapshot + +- Project type: personal/forked `kickstart.nvim` configuration. +- Primary language: Lua. +- Entry point: `init.lua`. +- Additional modules: `lua/custom/**` and `lua/kickstart/**`. +- Plugin manager: `lazy.nvim`. +- Lockfile: `lazy-lock.json` (tracks plugin versions). + +## Sources Used For This Guide + +- `README.md` +- `.stylua.toml` +- `init.lua` +- `lua/custom/**/*.lua` +- `lua/kickstart/**/*.lua` + +## Cursor/Copilot Rules + +- No `.cursorrules` file found. +- No `.cursor/rules/` directory found. +- No `.github/copilot-instructions.md` found. +- Therefore: follow this `AGENTS.md` + existing repository conventions. + +## Environment and Prerequisites + +- Neovim target: latest stable or nightly (README states this explicitly). +- External tools commonly expected: + - `git`, `make`, `unzip`, `rg` (checked by `lua/kickstart/health.lua`). + - Often useful: `fd`, `tree-sitter` CLI. + - Formatters used by config: `stylua`, `prettier`, `bean-format`. +- If tooling is missing, prefer graceful degradation over hard failure. + +## Build / Lint / Test Commands + +This repo is a config repo, so "build" and "test" are mostly lint/health/smoke checks. + +## Quick Command Matrix + +- Full format check: + - `stylua --check .` +- Apply Lua formatting: + - `stylua .` +- Sync/install plugins headlessly: + - `nvim --headless "+Lazy! sync" +qa` +- Run kickstart health checks headlessly: + - `nvim --headless "+checkhealth kickstart" +qa` +- Open once for runtime/plugin errors (manual smoke): + - `nvim` + +## Single-Test Guidance (Important) + +There is no formal unit-test framework (no `busted`, `plenary` test suite, `make test`, etc.). + +Use one of these "single check" equivalents when you need narrow validation: + +- Single file syntax check (if Lua compiler available): + - `luac -p path/to/file.lua` +- Single module load check in Neovim: + - `nvim --headless "+lua require('custom.plugins.opencode')" +qa` + - Replace module path as needed. +- Single feature health check: + - `nvim --headless "+checkhealth kickstart" +qa` + - Read only the section relevant to your change. + +If a task asks for "run one test", use a module-load or syntax check above and report it as a targeted smoke test. + +## Validation Order For Agents + +When making code changes, run checks in this order unless task says otherwise: + +1. `stylua --check .` +2. Targeted module/syntax check for changed file(s). +3. `nvim --headless "+checkhealth kickstart" +qa` for broader sanity. + +If a command is unavailable locally, state it clearly and provide the exact command for humans/CI. +If a command is unavailable locally, state it clearly and provide the exact command for the local machine. + +## Code Style: Formatting + +Formatting rules are authoritative in `.stylua.toml`: + +- Indentation: 2 spaces. +- Max column width: 160. +- Line endings: Unix. +- Quote style: `AutoPreferSingle` (prefer single quotes). +- Call parentheses: `None` where valid (`require 'x'` style). +- Collapse simple statements: always. + +Never hand-format against Stylua output; run Stylua instead. + +## Code Style: Imports and Module Structure + +- Prefer local requires near first use: + - `local builtin = require 'telescope.builtin'` +- For plugin specs, return a Lua table from module root: + - `return { ... }` +- Keep plugin declarations declarative (`opts`) unless imperative setup is necessary (`config = function() ... end`). +- In `init.lua`, follow existing kickstart ordering and comment style. +- Avoid introducing new top-level globals. + +## Code Style: Types and Annotations + +This repo uses LuaLS annotations heavily. Preserve and extend when useful: + +- `---@module 'lazy'` +- `---@type LazySpec` +- Plugin-specific types where available (e.g. `Gitsigns.Config`, `conform.setupOpts`). +- Use `---@diagnostic disable-next-line: ...` only when justified and narrowly scoped. + +Do not remove useful annotations just to reduce lines. + +## Code Style: Naming Conventions + +- Modules/files: + - lower_snake_case for filenames where practical. + - plugin files grouped by feature under `lua/custom/plugins/enable/` (active) and `lua/custom/plugins/disable/` (inactive). +- Local variables/functions: + - descriptive lower_snake_case. + - short names only for conventional temporary values (`buf`, `opts`, `client`). +- Augroup names: + - stable, descriptive strings (e.g. `'NeoTreeAutoRefresh'`, `'kickstart-lsp-highlight'`). +- Keymap descriptions: + - concise, action-oriented, consistent bracket hints used by which-key. + +## Code Style: Error Handling and Resilience + +- Prefer non-fatal behavior for optional dependencies. + - Example pattern already used: `pcall(require('telescope').load_extension, 'fzf')`. +- Guard external tool usage with `vim.fn.executable(...)` when needed. +- Use `vim.notify(..., vim.log.levels.WARN/ERROR)` for actionable user-facing issues. +- Keep startup robust: avoid hard errors for optional plugin features. + +## Plugin and Dependency Conventions + +- Add plugins through `lazy` specs, keeping structure consistent with existing blocks. +- When adding a new plugin, prefer putting it under `lua/custom/plugins/enable/*.lua` (or other custom modules) instead of editing upstream kickstart blocks in `init.lua`, to minimize merge conflicts with upstream updates. +- To disable a custom plugin without deleting its spec, move it to `lua/custom/plugins/disable/`. +- Prefer minimal configuration first (`opts = {}`), then extend only if needed. +- Respect platform guards (e.g., `make` checks, Windows conditionals). +- Do not edit lockfile manually; let lazy manage `lazy-lock.json` updates. + +## Custom Keymap and Layout Conventions + +- Keep **custom** keymaps in `lua/custom/keymaps.lua`. +- Keep upstream kickstart keymaps in `init.lua` unless intentionally overriding behavior. +- If adding a new `` namespace in `lua/custom/keymaps.lua`, also register its group in which-key (`init.lua` `which-key` spec). +- Keep layout/window helper logic centralized in `lua/custom/layout.lua` (for example `focus_main_window()`), and reuse it from plugin modules instead of duplicating sidebar/window filtering logic. +- If a plugin needs startup window choreography, call `require('custom.layout')` helpers rather than re-implementing window focus rules. + +## Editing and Change Scope + +- Keep changes minimal and local to the requested task. +- Do not refactor unrelated sections opportunistically. +- Preserve existing comments unless they become inaccurate. +- Maintain ASCII unless file already relies on Unicode symbols/icons. + +## Change Hygiene For Agents + +- Before finalizing: run formatting + at least one targeted runtime check. +- Mention exactly what you validated and what you could not validate. +- If no formal tests exist, explicitly say "no unit test suite in this repo". +- Include file paths in reports so reviewers can jump directly. + +## When Unsure + +- Follow existing patterns in nearby files first. +- Prefer reversible, low-risk changes. +- Ask for clarification only when ambiguity materially changes behavior. diff --git a/README.md b/README.md index 093e42a6dd9..35113e9069e 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,7 @@ External Requirements: - Language Setup: - If you want to write Typescript, you need `npm` - If you want to write Golang, you will need `go` + - If you want to lint Markdown, install `markdownlint-cli2` (via `npm i -g markdownlint-cli2` or Mason) - etc. > [!NOTE] diff --git a/init.lua b/init.lua index cacc5b7b1c3..c92e8b415ab 100644 --- a/init.lua +++ b/init.lua @@ -91,7 +91,7 @@ vim.g.mapleader = ' ' vim.g.maplocalleader = ' ' -- Set to true if you have a Nerd Font installed and selected in the terminal -vim.g.have_nerd_font = false +vim.g.have_nerd_font = true -- [[ Setting options ]] -- See `:help vim.o` @@ -127,7 +127,7 @@ vim.o.ignorecase = true vim.o.smartcase = true -- Keep signcolumn on by default -vim.o.signcolumn = 'yes' +vim.o.signcolumn = 'yes:1' -- Decrease update time vim.o.updatetime = 250 @@ -138,6 +138,8 @@ vim.o.timeoutlen = 300 -- Configure how new splits should be opened vim.o.splitright = true vim.o.splitbelow = true +-- Doesn't work well with WezTerm mux server; disable for now and revisit later. +vim.opt.termsync = false -- Sets how neovim will display certain whitespace characters in the editor. -- See `:help 'list'` @@ -184,7 +186,7 @@ vim.diagnostic.config { virtual_lines = false, -- Text shows up underneath the line, with virtual lines -- Auto open the float, so you can easily read the errors when jumping with `[d` and `]d` - jump = { float = true }, + jump = { on_jump = function() vim.diagnostic.open_float() end }, } vim.keymap.set('n', 'q', vim.diagnostic.setloclist, { desc = 'Open diagnostic [Q]uickfix list' }) @@ -230,6 +232,10 @@ vim.api.nvim_create_autocmd('TextYankPost', { callback = function() vim.hl.on_yank() end, }) +require 'custom.beancount' +require 'custom.keymaps' +require 'custom.treesitter' + -- [[ Install `lazy.nvim` plugin manager ]] -- See `:help lazy.nvim.txt` or https://github.com/folke/lazy.nvim for more info local lazypath = vim.fn.stdpath 'data' .. '/lazy/lazy.nvim' @@ -318,6 +324,7 @@ require('lazy').setup({ spec = { { 's', group = '[S]earch', mode = { 'n', 'v' } }, { 't', group = '[T]oggle' }, + { 'w', group = '[W]indows' }, { 'h', group = 'Git [H]unk', mode = { 'n', 'v' } }, -- Enable gitsigns recommended keymaps first { 'gr', group = 'LSP Actions', mode = { 'n' } }, }, @@ -621,6 +628,12 @@ require('lazy').setup({ if path ~= vim.fn.stdpath 'config' and (vim.uv.fs_stat(path .. '/.luarc.json') or vim.uv.fs_stat(path .. '/.luarc.jsonc')) then return end end + local config_path = vim.fn.stdpath 'config' + local runtime_files = vim.tbl_filter( + function(file) return file ~= config_path and file ~= (config_path .. '/after') end, + vim.api.nvim_get_runtime_file('', true) + ) + client.config.settings.Lua = vim.tbl_deep_extend('force', client.config.settings.Lua, { runtime = { version = 'LuaJIT', @@ -630,7 +643,7 @@ require('lazy').setup({ checkThirdParty = false, -- NOTE: this is a lot slower and will cause issues when working on your own configuration. -- See https://github.com/neovim/nvim-lspconfig/issues/3189 - library = vim.tbl_extend('force', vim.api.nvim_get_runtime_file('', true), { + library = vim.tbl_extend('force', runtime_files, { '${3rd}/luv/library', '${3rd}/busted/library', }), @@ -653,6 +666,7 @@ require('lazy').setup({ local ensure_installed = vim.tbl_keys(servers or {}) vim.list_extend(ensure_installed, { -- You can add other tools here that you want Mason to install + 'prettier', }) require('mason-tool-installer').setup { ensure_installed = ensure_installed } @@ -696,6 +710,9 @@ require('lazy').setup({ end, formatters_by_ft = { lua = { 'stylua' }, + json = { 'prettier' }, + yaml = { 'prettier' }, + beancount = { 'bean-format' }, -- Conform can also run multiple formatters sequentially -- python = { "isort", "black" }, -- @@ -930,17 +947,17 @@ require('lazy').setup({ -- Uncomment any of the lines below to enable them (you will need to restart nvim). -- -- require 'kickstart.plugins.debug', - -- require 'kickstart.plugins.indent_line', - -- require 'kickstart.plugins.lint', - -- require 'kickstart.plugins.autopairs', - -- require 'kickstart.plugins.neo-tree', - -- require 'kickstart.plugins.gitsigns', -- adds gitsigns recommended keymaps + require 'kickstart.plugins.indent_line', + require 'kickstart.plugins.lint', + require 'kickstart.plugins.autopairs', + require 'kickstart.plugins.neo-tree', + require 'kickstart.plugins.gitsigns', -- adds gitsigns recommended keymaps - -- NOTE: The import below can automatically add your own plugins, configuration, etc from `lua/custom/plugins/*.lua` + -- NOTE: The import below automatically loads enabled custom plugins from `lua/custom/plugins/enable/*.lua` -- This is the easiest way to modularize your config. -- -- Uncomment the following line and add your plugins to `lua/custom/plugins/*.lua` to get going. - -- { import = 'custom.plugins' }, + { import = 'custom.plugins.enable' }, -- -- For additional information with loading, sourcing and examples see `:help lazy.nvim-🔌-plugin-spec` -- Or use telescope! diff --git a/lazy-lock.json b/lazy-lock.json new file mode 100644 index 00000000000..2ecc8013f54 --- /dev/null +++ b/lazy-lock.json @@ -0,0 +1,34 @@ +{ + "LuaSnip": { "branch": "master", "commit": "5a1e39223db9a0498024a77b8441169d260c8c25" }, + "auto-session": { "branch": "main", "commit": "62437532b38495551410b3f377bcf4aaac574ebe" }, + "blame.nvim": { "branch": "main", "commit": "179da7aaacce7c52874af636255ede72dd6fe796" }, + "blink.cmp": { "branch": "main", "commit": "451168851e8e2466bc97ee3e026c3dcb9141ce07" }, + "codediff.nvim": { "branch": "main", "commit": "832f1ecc5f8b15a44cf7537e31d3266d657775b1" }, + "conform.nvim": { "branch": "master", "commit": "086a40dc7ed8242c03be9f47fbcee68699cc2395" }, + "diffview.nvim": { "branch": "main", "commit": "4516612fe98ff56ae0415a259ff6361a89419b0a" }, + "fidget.nvim": { "branch": "main", "commit": "889e2e96edef4e144965571d46f7a77bcc4d0ddf" }, + "gitsigns.nvim": { "branch": "main", "commit": "e1fb5425c8812214209b3f24eaa582c6c552cf98" }, + "guess-indent.nvim": { "branch": "main", "commit": "84a4987ff36798c2fc1169cbaff67960aed9776f" }, + "indent-blankline.nvim": { "branch": "master", "commit": "d28a3f70721c79e3c5f6693057ae929f3d9c0a03" }, + "lazy.nvim": { "branch": "main", "commit": "85c7ff3711b730b4030d03144f6db6375044ae82" }, + "mason-lspconfig.nvim": { "branch": "main", "commit": "25f609e7fca78af7cede4f9fa3af8a94b1c4950b" }, + "mason-tool-installer.nvim": { "branch": "main", "commit": "443f1ef8b5e6bf47045cb2217b6f748a223cf7dc" }, + "mason.nvim": { "branch": "main", "commit": "44d1e90e1f66e077268191e3ee9d2ac97cc18e65" }, + "mini.nvim": { "branch": "main", "commit": "3923662bf3d6ca49a9503f8d7196ea0450983e6a" }, + "neo-tree.nvim": { "branch": "main", "commit": "84c75e7a7e443586f60508d12fc50f90d9aee14e" }, + "nui.nvim": { "branch": "main", "commit": "de740991c12411b663994b2860f1a4fd0937c130" }, + "nvim-autopairs": { "branch": "master", "commit": "59bce2eef357189c3305e25bc6dd2d138c1683f5" }, + "nvim-lint": { "branch": "master", "commit": "4b03656c09c1561f89b6aa0665c15d292ba9499d" }, + "nvim-lspconfig": { "branch": "master", "commit": "8e2084bf5e40c79c1f42210a6ef96a0a4793a763" }, + "nvim-treesitter": { "branch": "main", "commit": "539abf6da5ee8702e37b82cc953131dadd570da2" }, + "nvim-web-devicons": { "branch": "master", "commit": "40e9d5a6cc3db11b309e39593fc7ac03bb844e38" }, + "opencode.nvim": { "branch": "main", "commit": "df533d6da724109bf08446392db860fdceddbd0c" }, + "plenary.nvim": { "branch": "master", "commit": "b9fd5226c2f76c951fc8ed5923d85e4de065e509" }, + "telescope-fzf-native.nvim": { "branch": "main", "commit": "6fea601bd2b694c6f2ae08a6c6fab14930c60e2c" }, + "telescope-ui-select.nvim": { "branch": "master", "commit": "6e51d7da30bd139a6950adf2a47fda6df9fa06d2" }, + "telescope.nvim": { "branch": "master", "commit": "cfb85dcf7f822b79224e9e6aef9e8c794211b20b" }, + "todo-comments.nvim": { "branch": "main", "commit": "31e3c38ce9b29781e4422fc0322eb0a21f4e8668" }, + "toggleterm.nvim": { "branch": "main", "commit": "50ea089fc548917cc3cc16b46a8211833b9e3c7c" }, + "tokyonight.nvim": { "branch": "main", "commit": "cdc07ac78467a233fd62c493de29a17e0cf2b2b6" }, + "which-key.nvim": { "branch": "main", "commit": "3aab2147e74890957785941f0c1ad87d0a44c15a" } +} diff --git a/lua/custom/beancount.lua b/lua/custom/beancount.lua new file mode 100644 index 00000000000..d91d2acb12c --- /dev/null +++ b/lua/custom/beancount.lua @@ -0,0 +1,11 @@ +local warned_missing_bean_format = false + +vim.api.nvim_create_autocmd('FileType', { + pattern = 'beancount', + group = vim.api.nvim_create_augroup('beancount-format-check', { clear = true }), + callback = function() + if warned_missing_bean_format or vim.fn.executable 'bean-format' == 1 then return end + warned_missing_bean_format = true + vim.notify('bean-format not found. Install it with: uv tool install beancount', vim.log.levels.WARN, { title = 'conform.nvim' }) + end, +}) diff --git a/lua/custom/keymaps.lua b/lua/custom/keymaps.lua new file mode 100644 index 00000000000..60c62fd0528 --- /dev/null +++ b/lua/custom/keymaps.lua @@ -0,0 +1 @@ +vim.keymap.set('n', 'wR', function() require('custom.layout').reset_ide_layout() end, { desc = '[W]indows: [R]eset IDE layout' }) diff --git a/lua/custom/layout.lua b/lua/custom/layout.lua new file mode 100644 index 00000000000..4aa858ba4dc --- /dev/null +++ b/lua/custom/layout.lua @@ -0,0 +1,147 @@ +local M = {} + +local sidebar_filetypes = { + ['neo-tree'] = true, + ['snacks_layout_box'] = true, + ['snacks_terminal'] = true, + ['toggleterm'] = true, + ['opencode'] = true, +} + +function M.is_main_window(win) + if not win or not vim.api.nvim_win_is_valid(win) then return false end + + local buf = vim.api.nvim_win_get_buf(win) + local ft = vim.bo[buf].filetype + local bt = vim.bo[buf].buftype + return not sidebar_filetypes[ft] and bt == '' +end + +function M.focus_main_window() + for _, win in ipairs(vim.api.nvim_tabpage_list_wins(0)) do + if M.is_main_window(win) then + vim.api.nvim_set_current_win(win) + return true + end + end + + return false +end + +local function find_window_by_filetype(filetype) + for _, win in ipairs(vim.api.nvim_tabpage_list_wins(0)) do + local buf = vim.api.nvim_win_get_buf(win) + if vim.bo[buf].filetype == filetype then return win end + end + + return nil +end + +local function is_opencode_window(win) + if not win or not vim.api.nvim_win_is_valid(win) then return false end + + local buf = vim.api.nvim_win_get_buf(win) + local ft = vim.bo[buf].filetype + if ft == 'opencode' then return true end + + local bt = vim.bo[buf].buftype + if bt ~= 'terminal' then return false end + + if ft ~= '' and ft ~= 'snacks_terminal' and ft ~= 'toggleterm' then return false end + + local buf_name = string.lower(vim.api.nvim_buf_get_name(buf)) + return buf_name:find('opencode', 1, true) ~= nil +end + +local function find_opencode_window() + for _, win in ipairs(vim.api.nvim_tabpage_list_wins(0)) do + if is_opencode_window(win) then return win end + end + + return nil +end + +function M.capture_focus() + local current_win = vim.api.nvim_get_current_win() + if not M.is_main_window(current_win) then return nil end + + local buf = vim.api.nvim_win_get_buf(current_win) + local buf_name = vim.api.nvim_buf_get_name(buf) + if buf_name == '' then return nil end + + return { + buf_name = buf_name, + } +end + +function M.restore_focus(focus) + if type(focus) ~= 'table' then return M.focus_main_window() end + + local target_buf_name = focus.buf_name + if type(target_buf_name) ~= 'string' or target_buf_name == '' then return M.focus_main_window() end + + for _, win in ipairs(vim.api.nvim_tabpage_list_wins(0)) do + if M.is_main_window(win) then + local buf = vim.api.nvim_win_get_buf(win) + if vim.api.nvim_buf_get_name(buf) == target_buf_name then + vim.api.nvim_set_current_win(win) + return true + end + end + end + + return M.focus_main_window() +end + +local function ensure_neotree_window() + local neotree_win = find_window_by_filetype 'neo-tree' + if neotree_win then return neotree_win end + + pcall( + function() + require('neo-tree.command').execute { + action = 'show', + source = 'filesystem', + position = 'left', + reveal = true, + } + end + ) + + return find_window_by_filetype 'neo-tree' +end + +local function ensure_opencode_window() + local opencode_win = find_opencode_window() + if opencode_win then return opencode_win end + + pcall(function() require('opencode').toggle() end) + + vim.wait(1000, function() return find_opencode_window() ~= nil end, 50) + return find_opencode_window() +end + +function M.reset_ide_layout(opts) + opts = opts or {} + + local neotree_win = ensure_neotree_window() + local opencode_win = ensure_opencode_window() + + local total_columns = vim.o.columns + local left_width = math.max(20, math.floor(total_columns * 0.10)) + local right_width = math.max(40, math.floor(total_columns * 0.25)) + local min_main_width = 40 + + if left_width + right_width + min_main_width > total_columns then + local available_sidebar_width = math.max(total_columns - min_main_width, 0) + left_width = math.floor(available_sidebar_width * (10 / 35)) + right_width = available_sidebar_width - left_width + end + + if neotree_win and vim.api.nvim_win_is_valid(neotree_win) then vim.api.nvim_win_set_width(neotree_win, left_width) end + if opencode_win and vim.api.nvim_win_is_valid(opencode_win) then vim.api.nvim_win_set_width(opencode_win, right_width) end + + if opts.focus_main ~= false then M.focus_main_window() end +end + +return M diff --git a/lua/custom/plugins/disable/oil.lua b/lua/custom/plugins/disable/oil.lua new file mode 100644 index 00000000000..58333f30a60 --- /dev/null +++ b/lua/custom/plugins/disable/oil.lua @@ -0,0 +1,85 @@ +return { + 'stevearc/oil.nvim', + ---@module 'oil' + ---@type oil.SetupOpts + opts = { + default_file_explorer = true, + columns = { 'icon' }, + keymaps = { + [''] = function() + local oil = require 'oil' + local actions = require 'oil.actions' + local entry = oil.get_cursor_entry() + + if not entry then return end + + if entry.type == 'directory' then + actions.select.callback() + return + end + + local sidebar_win = vim.api.nvim_get_current_win() + local sidebar_pos = vim.api.nvim_win_get_position(sidebar_win) + local target_win = nil + + for _, win in ipairs(vim.api.nvim_tabpage_list_wins(0)) do + if win ~= sidebar_win then + local buf = vim.api.nvim_win_get_buf(win) + if vim.bo[buf].filetype ~= 'oil' then + local pos = vim.api.nvim_win_get_position(win) + if pos[2] > sidebar_pos[2] then + if not target_win then + target_win = win + else + local target_pos = vim.api.nvim_win_get_position(target_win) + if pos[2] < target_pos[2] then target_win = win end + end + end + end + end + end + + if not target_win then + vim.cmd 'vsplit' + target_win = vim.api.nvim_get_current_win() + vim.api.nvim_set_current_win(sidebar_win) + end + + local dir = oil.get_current_dir() + if not dir then return end + + local path = vim.fs.joinpath(dir, entry.name) + vim.api.nvim_set_current_win(target_win) + vim.cmd.edit(vim.fn.fnameescape(path)) + end, + }, + }, + keys = { + { + 'e', + function() + for _, win in ipairs(vim.api.nvim_list_wins()) do + local buf = vim.api.nvim_win_get_buf(win) + if vim.bo[buf].filetype == 'oil' then + vim.api.nvim_win_close(win, true) + return + end + end + + local current_win = vim.api.nvim_get_current_win() + vim.cmd 'topleft vsplit' + local sidebar_win = vim.api.nvim_get_current_win() + require('oil').open() + vim.api.nvim_win_set_width(sidebar_win, 20) + vim.wo[sidebar_win].winfixwidth = true + vim.api.nvim_set_current_win(current_win) + end, + desc = 'Toggle Oil sidebar', + }, + }, + -- Optional dependencies + dependencies = { { 'nvim-mini/mini.icons', opts = {} } }, + -- dependencies = { "nvim-tree/nvim-web-devicons" }, -- use if you prefer nvim-web-devicons + -- Lazy loading is not recommended because it is very tricky to make it work correctly in all situations. + lazy = false, +} diff --git a/lua/custom/plugins/disable/persistence.lua b/lua/custom/plugins/disable/persistence.lua new file mode 100644 index 00000000000..bac8e9a238f --- /dev/null +++ b/lua/custom/plugins/disable/persistence.lua @@ -0,0 +1,15 @@ +return { + { + 'folke/persistence.nvim', + event = 'BufReadPre', + opts = { + need = 1, + }, + keys = { + { 'qs', function() require('persistence').load() end, desc = '[Q]uit: restore [S]ession' }, + { 'qS', function() require('persistence').select() end, desc = '[Q]uit: restore [S]ession (picker)' }, + { 'ql', function() require('persistence').load { last = true } end, desc = '[Q]uit: restore [L]ast session' }, + { 'qd', function() require('persistence').stop() end, desc = '[Q]uit: [D]isable session save' }, + }, + }, +} diff --git a/lua/custom/plugins/disable/tree.lua b/lua/custom/plugins/disable/tree.lua new file mode 100644 index 00000000000..49926caa038 --- /dev/null +++ b/lua/custom/plugins/disable/tree.lua @@ -0,0 +1,9 @@ +return { + 'nvim-tree/nvim-tree.lua', + version = '*', + lazy = false, + dependencies = { + 'nvim-tree/nvim-web-devicons', + }, + config = function() require('nvim-tree').setup {} end, +} diff --git a/lua/custom/plugins/enable/auto_session.lua b/lua/custom/plugins/enable/auto_session.lua new file mode 100644 index 00000000000..c1f2cd08347 --- /dev/null +++ b/lua/custom/plugins/enable/auto_session.lua @@ -0,0 +1,55 @@ +return { + { + 'rmagatti/auto-session', + lazy = false, + init = function() + -- Required by auto-session for filetype and highlighting to survive session restore + vim.o.sessionoptions = 'blank,buffers,curdir,folds,help,tabpages,winsize,winpos,terminal,localoptions' + end, + keys = { + -- Will use Telescope if installed or a vim.ui.select picker otherwise + { 'wr', 'AutoSession search', desc = 'Session search' }, + { 'ws', 'AutoSession save', desc = 'Save session' }, + { 'wa', 'AutoSession toggle', desc = 'Toggle autosave' }, + }, + + ---enables autocomplete for opts + ---@module 'auto-session' + ---@type AutoSession.Config + opts = { + suppressed_dirs = { '~/', '~/Downloads', '/' }, + -- Keep sessions even when only a directory/sidebar is open (IDE-style usage). + auto_delete_empty_sessions = false, + save_extra_data = function(_) + local ok, layout = pcall(require, 'custom.layout') + if not ok then return nil end + + local focus = layout.capture_focus() + if not focus then return nil end + + return vim.fn.json_encode { + focus = focus, + } + end, + restore_extra_data = function(_, extra_data) + local ok_json, decoded = pcall(vim.fn.json_decode, extra_data) + if not ok_json or type(decoded) ~= 'table' then return end + + local ok_layout, layout = pcall(require, 'custom.layout') + if not ok_layout then return end + + local focus = decoded.focus + if type(focus) == 'table' then vim.schedule(function() layout.restore_focus(focus) end) end + end, + post_restore_cmds = { + function(_) + local ok_layout, layout = pcall(require, 'custom.layout') + if not ok_layout then return end + + vim.schedule(function() layout.reset_ide_layout { focus_main = false } end) + end, + }, + -- log_level = 'debug', + }, + }, +} diff --git a/lua/custom/plugins/enable/blame.lua b/lua/custom/plugins/enable/blame.lua new file mode 100644 index 00000000000..91346d02aa8 --- /dev/null +++ b/lua/custom/plugins/enable/blame.lua @@ -0,0 +1,11 @@ +---@module 'lazy' +---@type LazySpec +return { + { + 'FabijanZulj/blame.nvim', + opts = {}, + keys = { + { 'tl', 'BlameToggle', desc = '[T]oggle B[l]ame side window' }, + }, + }, +} diff --git a/lua/custom/plugins/enable/codediff.lua b/lua/custom/plugins/enable/codediff.lua new file mode 100644 index 00000000000..a189ffdb14a --- /dev/null +++ b/lua/custom/plugins/enable/codediff.lua @@ -0,0 +1,4 @@ +return { + 'esmuellert/codediff.nvim', + cmd = 'CodeDiff', +} diff --git a/lua/custom/plugins/enable/diffview.lua b/lua/custom/plugins/enable/diffview.lua new file mode 100644 index 00000000000..f118f2a9cd3 --- /dev/null +++ b/lua/custom/plugins/enable/diffview.lua @@ -0,0 +1,17 @@ +return { + 'sindrets/diffview.nvim', + cmd = { + 'DiffviewOpen', + 'DiffviewClose', + 'DiffviewToggleFiles', + 'DiffviewFocusFiles', + }, + keys = { + { 'gd', 'DiffviewOpen', desc = 'Diffview Open' }, + { 'gq', 'DiffviewClose', desc = 'Diffview Close' }, + }, + dependencies = { + 'nvim-lua/plenary.nvim', + 'nvim-tree/nvim-web-devicons', + }, +} diff --git a/lua/custom/plugins/enable/lint.lua b/lua/custom/plugins/enable/lint.lua new file mode 100644 index 00000000000..9f6f459ca05 --- /dev/null +++ b/lua/custom/plugins/enable/lint.lua @@ -0,0 +1,23 @@ +return { + { + 'mfussenegger/nvim-lint', + ft = { 'markdown' }, + opts = { + linters = { + markdownlint = { + args = { '--disable', 'MD013', '--' }, + }, + ['markdownlint-cli2'] = { + prepend_args = { '--disable', 'MD013', '--' }, + }, + }, + }, + config = function(_, opts) + local lint = require 'lint' + + for linter_name, linter_config in pairs(opts.linters or {}) do + lint.linters[linter_name] = vim.tbl_deep_extend('force', lint.linters[linter_name] or {}, linter_config) + end + end, + }, +} diff --git a/lua/custom/plugins/enable/neotree.lua b/lua/custom/plugins/enable/neotree.lua new file mode 100644 index 00000000000..520f47d645c --- /dev/null +++ b/lua/custom/plugins/enable/neotree.lua @@ -0,0 +1,61 @@ +return { + { + 'nvim-neo-tree/neo-tree.nvim', + opts = function(_, opts) + opts.enable_git_status = true + + opts.default_component_configs = vim.tbl_deep_extend('force', opts.default_component_configs or {}, { + git_status = { + symbols = { + added = 'A', + modified = 'M', + deleted = 'D', + renamed = 'R', + untracked = 'U', + ignored = 'I', + unstaged = '*', + staged = '+', + conflict = '!', + }, + }, + }) + + opts.filesystem = vim.tbl_deep_extend('force', opts.filesystem or {}, { + use_libuv_file_watcher = true, + follow_current_file = { enabled = true }, + filtered_items = { + visible = false, + hide_dotfiles = true, + hide_gitignored = true, + hide_hidden = true, + always_show = { + '.github', + }, + }, + window = { + mappings = { + ['R'] = 'refresh', + ['H'] = 'toggle_hidden', + }, + }, + }) + end, + config = function(_, opts) + require('neo-tree').setup(opts) + + local group = vim.api.nvim_create_augroup('NeoTreeAutoRefresh', { clear = true }) + vim.api.nvim_create_autocmd({ 'BufWritePost', 'FocusGained' }, { + group = group, + callback = function() + for _, win in ipairs(vim.api.nvim_list_wins()) do + local buf = vim.api.nvim_win_get_buf(win) + if vim.bo[buf].filetype == 'neo-tree' then + vim.cmd 'silent! Neotree refresh' + return + end + end + end, + }) + end, + }, +} diff --git a/lua/custom/plugins/enable/opencode.lua b/lua/custom/plugins/enable/opencode.lua new file mode 100644 index 00000000000..b007fef9584 --- /dev/null +++ b/lua/custom/plugins/enable/opencode.lua @@ -0,0 +1,82 @@ +return { + 'nickjvandyke/opencode.nvim', + version = '*', -- Latest stable release + dependencies = { + { + -- `snacks.nvim` integration is recommended, but optional + ---@module "snacks" <- Loads `snacks.nvim` types for configuration intellisense + 'folke/snacks.nvim', + optional = true, + opts = { + input = {}, -- Enhances `ask()` + picker = { -- Enhances `select()` + actions = { + opencode_send = function(...) return require('opencode').snacks_picker_send(...) end, + }, + win = { + input = { + keys = { + [''] = { 'opencode_send', mode = { 'n', 'i' } }, + }, + }, + }, + }, + }, + }, + }, + config = function() + local opencode_cmd = [[zsh -ic 'opencode --port']] + + ---@type opencode.Opts + vim.g.opencode_opts = { + server = { + start = function() + require('opencode.terminal').open(opencode_cmd, { + split = 'right', + width = math.max(40, math.floor(vim.o.columns * 0.4)), + }) + end, + stop = function() require('opencode.terminal').close() end, + toggle = function() + require('opencode.terminal').toggle(opencode_cmd, { + split = 'right', + width = math.max(40, math.floor(vim.o.columns * 0.4)), + }) + end, + }, + } + + vim.o.autoread = true -- Required for `opts.events.reload` + + -- Recommended/example keymaps + vim.keymap.set({ 'n', 'x' }, '', function() require('opencode').ask('@this: ', { submit = true }) end, { desc = 'Ask opencode…' }) + vim.keymap.set({ 'n', 'x' }, '', function() require('opencode').select() end, { desc = 'Execute opencode action…' }) + vim.keymap.set({ 'n', 't' }, '', function() require('opencode').toggle() end, { desc = 'Toggle opencode' }) + + vim.keymap.set({ 'n', 'x' }, 'go', function() return require('opencode').operator '@this ' end, { desc = 'Add range to opencode', expr = true }) + vim.keymap.set('n', 'goo', function() return require('opencode').operator '@this ' .. '_' end, { desc = 'Add line to opencode', expr = true }) + + vim.keymap.set('n', '', function() require('opencode').command 'session.half.page.up' end, { desc = 'Scroll opencode up' }) + vim.keymap.set('n', '', function() require('opencode').command 'session.half.page.down' end, { desc = 'Scroll opencode down' }) + + -- You may want these if you use the opinionated `` and `` keymaps above — otherwise consider `o…` (and remove terminal mode from the `toggle` keymap) + vim.keymap.set('n', '+', '', { desc = 'Increment under cursor', noremap = true }) + vim.keymap.set('n', '-', '', { desc = 'Decrement under cursor', noremap = true }) + + -- Disabled: startup session/window orchestration is handled by persistence workflow. + -- vim.api.nvim_create_autocmd('VimEnter', { + -- callback = function() + -- local layout = require 'custom.layout' + -- local argv = vim.fn.argv() + -- local first_arg = argv[1] + -- if vim.fn.argc() == 1 and type(first_arg) == 'string' and vim.fn.isdirectory(first_arg) == 1 then + -- vim.schedule(function() + -- layout.focus_main_window() + -- require('opencode').toggle() + -- layout.focus_main_window() + -- end) + -- end + -- end, + -- }) + end, +} diff --git a/lua/custom/plugins/enable/toggleterm.lua b/lua/custom/plugins/enable/toggleterm.lua new file mode 100644 index 00000000000..ae94cfb8355 --- /dev/null +++ b/lua/custom/plugins/enable/toggleterm.lua @@ -0,0 +1,19 @@ +---@module 'lazy' +---@type LazySpec +return { + { + 'akinsho/toggleterm.nvim', + version = '*', + cmd = { 'ToggleTerm', 'TermExec' }, + keys = { + { 'tt', 'ToggleTerm', desc = '[T]oggle [T]erminal' }, + }, + opts = { + open_mapping = [[]], + direction = 'float', + float_opts = { + border = 'curved', + }, + }, + }, +} diff --git a/lua/custom/plugins/init.lua b/lua/custom/plugins/init.lua index b3ddcfdd3aa..eb0f0ba8904 100644 --- a/lua/custom/plugins/init.lua +++ b/lua/custom/plugins/init.lua @@ -1,7 +1,8 @@ --- You can add your own plugins here or in other files in this directory! --- I promise not to create any merge conflicts in this directory :) +-- Custom plugin layout (nginx style): +-- - enable/*.lua : loaded by lazy via `import = 'custom.plugins.enable'` +-- - disable/*.lua: not imported; keep plugin specs here to disable quickly -- --- See the kickstart.nvim README for more information +-- See init.lua import config for details. ---@module 'lazy' ---@type LazySpec diff --git a/lua/custom/treesitter.lua b/lua/custom/treesitter.lua new file mode 100644 index 00000000000..fecd00b275a --- /dev/null +++ b/lua/custom/treesitter.lua @@ -0,0 +1,11 @@ +local extra_parsers = { 'bicep', 'beancount' } + +vim.api.nvim_create_autocmd('User', { + pattern = 'VeryLazy', + once = true, + callback = function() + local ok, treesitter = pcall(require, 'nvim-treesitter') + if not ok then return end + treesitter.install(extra_parsers) + end, +}) diff --git a/lua/kickstart/plugins/gitsigns.lua b/lua/kickstart/plugins/gitsigns.lua index 1d8c50c37db..98ef39e69ec 100644 --- a/lua/kickstart/plugins/gitsigns.lua +++ b/lua/kickstart/plugins/gitsigns.lua @@ -10,6 +10,9 @@ return { ---@type Gitsigns.Config ---@diagnostic disable-next-line: missing-fields opts = { + watch_gitdir = { follow_files = true }, + attach_to_untracked = true, + update_debounce = 100, on_attach = function(bufnr) local gitsigns = require 'gitsigns'