From 843d1ca3a1e85667ba56bad022ec67668e2c8fb39394a7b6271f1ab1a0d10b6a Mon Sep 17 00:00:00 2001 From: Timothy Pidashev Date: Thu, 16 Apr 2026 01:16:58 -0700 Subject: [PATCH] Initial rewrite Signed-off-by: Timothy Pidashev --- README.md | 48 +++++- TODO.md | 74 ++++++++++ Taskfile.yml | 36 +++++ init.lua | 6 + lsp/apex_ls.lua | 17 +++ lsp/bashls.lua | 5 + lsp/cssls.lua | 10 ++ lsp/gopls.lua | 12 ++ lsp/html.lua | 10 ++ lsp/htmx.lua | 5 + lsp/jsonls.lua | 6 + lsp/lua_ls.lua | 18 +++ lsp/marksman.lua | 5 + lsp/pyright.lua | 14 ++ lsp/taplo.lua | 5 + lsp/templ.lua | 5 + lsp/ts_ls.lua | 5 + lsp/yamlls.lua | 11 ++ lua/timmypidashev/keymaps.lua | 10 ++ lua/timmypidashev/langs.lua | 24 +++ lua/timmypidashev/options.lua | 33 +++++ lua/timmypidashev/plugins.lua | 75 ++++++++++ lua/timmypidashev/plugins/animate.lua | 14 ++ lua/timmypidashev/plugins/autopairs.lua | 3 + lua/timmypidashev/plugins/barbar.lua | 95 ++++++++++++ lua/timmypidashev/plugins/blink.lua | 33 +++++ lua/timmypidashev/plugins/camouflage.lua | 10 ++ lua/timmypidashev/plugins/conform.lua | 28 ++++ lua/timmypidashev/plugins/darkbox.lua | 4 + lua/timmypidashev/plugins/fzf.lua | 17 +++ lua/timmypidashev/plugins/gitsigns.lua | 13 ++ lua/timmypidashev/plugins/harpoon.lua | 15 ++ lua/timmypidashev/plugins/indentscope.lua | 13 ++ lua/timmypidashev/plugins/lint.lua | 18 +++ lua/timmypidashev/plugins/lsp.lua | 79 ++++++++++ lua/timmypidashev/plugins/lualine.lua | 67 +++++++++ lua/timmypidashev/plugins/mason.lua | 45 ++++++ lua/timmypidashev/plugins/noice.lua | 29 ++++ lua/timmypidashev/plugins/oil.lua | 137 ++++++++++++++++++ lua/timmypidashev/plugins/render-markdown.lua | 14 ++ lua/timmypidashev/plugins/starter.lua | 135 +++++++++++++++++ lua/timmypidashev/plugins/surround.lua | 1 + lua/timmypidashev/plugins/tinyglimmer.lua | 9 ++ lua/timmypidashev/plugins/toggleterm.lua | 8 + lua/timmypidashev/plugins/treesitter.lua | 17 +++ lua/timmypidashev/plugins/undotree.lua | 1 + lua/timmypidashev/plugins/videre.lua | 3 + lua/timmypidashev/plugins/whichkey.lua | 4 + nvim-pack-lock.json | 41 ++++++ scripts/bootstrap.lua | 29 ++++ 50 files changed, 1315 insertions(+), 1 deletion(-) create mode 100644 TODO.md create mode 100644 Taskfile.yml create mode 100644 init.lua create mode 100644 lsp/apex_ls.lua create mode 100644 lsp/bashls.lua create mode 100644 lsp/cssls.lua create mode 100644 lsp/gopls.lua create mode 100644 lsp/html.lua create mode 100644 lsp/htmx.lua create mode 100644 lsp/jsonls.lua create mode 100644 lsp/lua_ls.lua create mode 100644 lsp/marksman.lua create mode 100644 lsp/pyright.lua create mode 100644 lsp/taplo.lua create mode 100644 lsp/templ.lua create mode 100644 lsp/ts_ls.lua create mode 100644 lsp/yamlls.lua create mode 100644 lua/timmypidashev/keymaps.lua create mode 100644 lua/timmypidashev/langs.lua create mode 100644 lua/timmypidashev/options.lua create mode 100644 lua/timmypidashev/plugins.lua create mode 100644 lua/timmypidashev/plugins/animate.lua create mode 100644 lua/timmypidashev/plugins/autopairs.lua create mode 100644 lua/timmypidashev/plugins/barbar.lua create mode 100644 lua/timmypidashev/plugins/blink.lua create mode 100644 lua/timmypidashev/plugins/camouflage.lua create mode 100644 lua/timmypidashev/plugins/conform.lua create mode 100644 lua/timmypidashev/plugins/darkbox.lua create mode 100644 lua/timmypidashev/plugins/fzf.lua create mode 100644 lua/timmypidashev/plugins/gitsigns.lua create mode 100644 lua/timmypidashev/plugins/harpoon.lua create mode 100644 lua/timmypidashev/plugins/indentscope.lua create mode 100644 lua/timmypidashev/plugins/lint.lua create mode 100644 lua/timmypidashev/plugins/lsp.lua create mode 100644 lua/timmypidashev/plugins/lualine.lua create mode 100644 lua/timmypidashev/plugins/mason.lua create mode 100644 lua/timmypidashev/plugins/noice.lua create mode 100644 lua/timmypidashev/plugins/oil.lua create mode 100644 lua/timmypidashev/plugins/render-markdown.lua create mode 100644 lua/timmypidashev/plugins/starter.lua create mode 100644 lua/timmypidashev/plugins/surround.lua create mode 100644 lua/timmypidashev/plugins/tinyglimmer.lua create mode 100644 lua/timmypidashev/plugins/toggleterm.lua create mode 100644 lua/timmypidashev/plugins/treesitter.lua create mode 100644 lua/timmypidashev/plugins/undotree.lua create mode 100644 lua/timmypidashev/plugins/videre.lua create mode 100644 lua/timmypidashev/plugins/whichkey.lua create mode 100644 nvim-pack-lock.json create mode 100644 scripts/bootstrap.lua diff --git a/README.md b/README.md index 7134e83..2eb201a 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,48 @@ -# nvim +# neovimrc +My personal Neovim config. Built for Neovim 0.12+ using the native +`vim.pack` package manager and the modern `vim.lsp.config` API — no +lazy.nvim, no nvim-lspconfig. + +## Install + +```fish +task install +``` + +## Plugins + +- [darkbox.nvim](https://git.timmypidashev.dev/timmypidashev/darkbox.nvim) — custom theme +- [lualine.nvim](https://github.com/nvim-lualine/lualine.nvim) — statusline +- [nvim-web-devicons](https://github.com/nvim-tree/nvim-web-devicons) +- [gitsigns.nvim](https://github.com/lewis6991/gitsigns.nvim) +- [nvim-treesitter](https://github.com/nvim-treesitter/nvim-treesitter) (main branch) +- [mason.nvim](https://github.com/mason-org/mason.nvim) +- [mason-tool-installer.nvim](https://github.com/WhoIsSethDaniel/mason-tool-installer.nvim) +- [vim-wakatime](https://github.com/wakatime/vim-wakatime) +- [harpoon 2](https://github.com/ThePrimeagen/harpoon/tree/harpoon2) — file bookmarks +- [plenary.nvim](https://github.com/nvim-lua/plenary.nvim) — harpoon dep +- [barbar.nvim](https://github.com/romgrk/barbar.nvim) — tabline +- [oil.nvim](https://github.com/stevearc/oil.nvim) — file explorer as buffer +- [blink.cmp](https://github.com/saghen/blink.cmp) — completion +- [fzf-lua](https://github.com/ibhagwan/fzf-lua) — fuzzy picker +- [conform.nvim](https://github.com/stevearc/conform.nvim) — format on save +- [nvim-lint](https://github.com/mfussenegger/nvim-lint) — linter runner +- [which-key.nvim](https://github.com/folke/which-key.nvim) — keymap hints +- [nvim-autopairs](https://github.com/windwp/nvim-autopairs) +- [nvim-surround](https://github.com/kylechui/nvim-surround) +- [toggleterm.nvim](https://github.com/akinsho/toggleterm.nvim) — floating terminal +- [undotree](https://github.com/mbbill/undotree) +- [noice.nvim](https://github.com/folke/noice.nvim) — cmdline + notifications UI +- [nvim-notify](https://github.com/rcarriga/nvim-notify) — notification backend +- [nui.nvim](https://github.com/MunifTanjim/nui.nvim) — noice dep +- [mini.indentscope](https://github.com/nvim-mini/mini.indentscope) — indent scope line +- [mini.animate](https://github.com/nvim-mini/mini.animate) — cursor / scroll / resize animation +- [camouflage.nvim](https://github.com/zeybek/camouflage.nvim) — mask secrets in config files +- [videre.nvim](https://github.com/Owen-Dechow/videre.nvim) — JSON/YAML graph explorer +- [tiny-glimmer.nvim](https://github.com/rachartier/tiny-glimmer.nvim) — yank/paste/undo glow +- [verse.nvim](https://git.timmypidashev.dev/timmypidashev/verse.nvim) — daily Bible verse + +--- + +Made with ❤️ by [timmypidashev](https://timmypidashev.dev) diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..fb834c8 --- /dev/null +++ b/TODO.md @@ -0,0 +1,74 @@ +# TODO + +## Barbar sidebar offset + alignment still broken + +**Symptoms:** +- Oil sidebar label shows literal `Files` (static), not the expected fish-shortened + cwd (`~/P/t/neovimrc`). +- Sidebar width does not shrink to fit longest entry — stays at default ~35 cols + even when the longest filename is much shorter. +- Bar colors (`BufferCurrent*`, `BufferOffset`) visibly mismatch after darkbox + edits and restart. + +**Context:** +- `lua/timmypidashev/plugins/oil.lua` has `fish_cwd()` and `fit_width()` helpers + wired to `bufferline.api.set_offset(width, text)`. Expected to be called on + `BufWinEnter` / `BufReadPost` / `TextChanged` for oil buffers, plus retry loop + `maybe_fit()` polling for buffer populate. +- `lua/timmypidashev/plugins/barbar.lua` sets `sidebar_filetypes.oil` + + `exclude_ft = { "oil" }`. +- Darkbox `BufferOffset` was updated to `fg = foreground, bold = true` to match + `BufferCurrent`; separator glyph changed from `▎` to `│`. + +**Hypotheses to verify:** +1. `task dev` was not run, so the copied `~/.config/nvim/lua/…` lacks the rtp + prepend + oil changes. Check: + `:lua =vim.api.nvim_get_runtime_file("colors/darkbox.lua", true)[1]` + must show `~/Projects/timmypidashev/darkbox.nvim/…`. +2. Leftover pack clone of darkbox is winning over local rtp: + `~/.local/share/nvim/site/pack/core/opt/darkbox.nvim` may need removal. +3. `fit_width` / `maybe_fit` never fire because the autocmd's buffer filter + or pattern isn't matching oil buffers as expected. +4. `bufferline.api.set_offset` text arg is silently ignored or `api` module + name differs across barbar versions. + +**Next steps:** +- Instrument with `vim.notify` inside `maybe_fit`, `fit_width`, and `set_offset` + to confirm they run and with what values. +- Try `require("barbar").api` vs `require("bufferline.api")` — the module path + has changed between barbar releases. +- Consider dropping barbar's offset mechanism and rendering the label inline at + the top of the oil buffer using extmarks instead. + +--- + +## Opening markdown files has ~2s blank / freeze + +**Symptoms:** +- Opening `README.md` (and likely other markdown files) shows a blank buffer for + ~2 seconds before content/syntax renders. + +**Likely causes (to investigate in order):** +1. **marksman** LSP spawning and blocking initial render. Check + `:LspLog` timestamps on attach. +2. **markdownlint-cli2** lint run on `BufReadPost` via `nvim-lint` — + `lua/timmypidashev/plugins/lint.lua` triggers `try_lint` on `BufReadPost`. + This can shell out synchronously if misconfigured. +3. **Treesitter** `markdown` + `markdown_inline` parsing both on the same buffer + — our `FileType` autocmd starts treesitter; main branch install is async, + but re-parse on large markdown can be slow first time. +4. **conform.nvim** format_on_save preload of `prettierd` binary (less likely + on read, but worth eliminating). +5. **blink.cmp** Lua fuzzy impl cold-startup (we disabled rust fallback). + +**Diagnostic steps:** +- `:lua vim.lsp.set_log_level("debug")` then `:LspLog` after opening the file. +- Compare times: `nvim --clean README.md` vs current config. +- Disable plugins one-by-one (`marksman`, `nvim-lint`, treesitter) to isolate. +- `:Profile` (via `:h profile`) around `:e README.md`. + +**Likely fix candidates:** +- Switch `nvim-lint` trigger from `BufReadPost` to `BufWritePost` only. +- Detach marksman if no `.marksman.toml` / `.git` root — or remove it from the + enabled LSP list entirely if unused. +- Debounce treesitter highlight start for markdown. diff --git a/Taskfile.yml b/Taskfile.yml new file mode 100644 index 0000000..5ec750b --- /dev/null +++ b/Taskfile.yml @@ -0,0 +1,36 @@ +version: '3' + +tasks: + dev: + desc: Copy init.lua, lua/, lsp/ into ~/.config/nvim (run after edits) + cmds: + - mkdir -p ~/.config/nvim + - rm -rf ~/.config/nvim/init.lua ~/.config/nvim/lua ~/.config/nvim/lsp + - cp {{.ROOT_DIR}}/init.lua ~/.config/nvim/init.lua + - cp -R {{.ROOT_DIR}}/lua ~/.config/nvim/lua + - cp -R {{.ROOT_DIR}}/lsp ~/.config/nvim/lsp + + install: + desc: Copy init.lua, lua/, lsp/ into ~/.config/nvim (no symlinks) and bootstrap + cmds: + - mkdir -p ~/.config/nvim + - rm -rf ~/.config/nvim/lua ~/.config/nvim/lsp ~/.config/nvim/init.lua + - cp {{.ROOT_DIR}}/init.lua ~/.config/nvim/init.lua + - cp -R {{.ROOT_DIR}}/lua ~/.config/nvim/lua + - cp -R {{.ROOT_DIR}}/lsp ~/.config/nvim/lsp + - nvim --headless "+luafile {{.ROOT_DIR}}/scripts/bootstrap.lua" + + uninstall: + desc: Nuke config, plugins, mason, treesitter parsers, state, cache + cmds: + - rm -rf ~/.config/nvim + - rm -rf ~/.local/share/nvim + - rm -rf ~/.local/state/nvim + - rm -rf ~/.cache/nvim + + update: + desc: Update plugins, LSP tools, and treesitter parsers + cmds: + - nvim --headless "+lua vim.pack.update({}, { force = true })" +qall + - nvim --headless "+MasonToolsUpdate" +qall + - nvim --headless "+lua require('nvim-treesitter').update()" +qall diff --git a/init.lua b/init.lua new file mode 100644 index 0000000..077a0a0 --- /dev/null +++ b/init.lua @@ -0,0 +1,6 @@ +vim.g.mapleader = " " +vim.g.maplocalleader = "\\" + +require("timmypidashev.options") +require("timmypidashev.keymaps") +require("timmypidashev.plugins") diff --git a/lsp/apex_ls.lua b/lsp/apex_ls.lua new file mode 100644 index 0000000..d9eca43 --- /dev/null +++ b/lsp/apex_ls.lua @@ -0,0 +1,17 @@ +-- Apex LSP: download the JAR from https://github.com/forcedotcom/salesforcedx-vscode-apex +-- then set APEX_JAR env var to its path (e.g. ~/.local/share/apex-jorje-lsp.jar). +local jar = vim.env.APEX_JAR or (vim.env.HOME .. "/.local/share/apex-jorje-lsp.jar") + +return { + cmd = { + "java", + "-cp", jar, + "-Ddebug.internal.errors=true", + "-Ddebug.semantic.errors=false", + "-Ddebug.completion.statistics=false", + "-Dlwc.typegeneration.disabled=true", + "apex.jorje.lsp.ApexLanguageServerLauncher", + }, + filetypes = { "apex" }, + root_markers = { "sfdx-project.json", ".git" }, +} diff --git a/lsp/bashls.lua b/lsp/bashls.lua new file mode 100644 index 0000000..04a2a3a --- /dev/null +++ b/lsp/bashls.lua @@ -0,0 +1,5 @@ +return { + cmd = { "bash-language-server", "start" }, + filetypes = { "sh", "bash" }, + root_markers = { ".git" }, +} diff --git a/lsp/cssls.lua b/lsp/cssls.lua new file mode 100644 index 0000000..5050ed7 --- /dev/null +++ b/lsp/cssls.lua @@ -0,0 +1,10 @@ +return { + cmd = { "vscode-css-language-server", "--stdio" }, + filetypes = { "css", "scss", "less" }, + root_markers = { "package.json", ".git" }, + settings = { + css = { validate = true }, + scss = { validate = true }, + less = { validate = true }, + }, +} diff --git a/lsp/gopls.lua b/lsp/gopls.lua new file mode 100644 index 0000000..a357a77 --- /dev/null +++ b/lsp/gopls.lua @@ -0,0 +1,12 @@ +return { + cmd = { "gopls" }, + filetypes = { "go", "gomod", "gowork", "gotmpl" }, + root_markers = { "go.work", "go.mod", ".git" }, + settings = { + gopls = { + analyses = { unusedparams = true }, + staticcheck = true, + gofumpt = true, + }, + }, +} diff --git a/lsp/html.lua b/lsp/html.lua new file mode 100644 index 0000000..cd824f0 --- /dev/null +++ b/lsp/html.lua @@ -0,0 +1,10 @@ +return { + cmd = { "vscode-html-language-server", "--stdio" }, + filetypes = { "html", "templ" }, + root_markers = { "package.json", ".git" }, + init_options = { + configurationSection = { "html", "css", "javascript" }, + embeddedLanguages = { css = true, javascript = true }, + provideFormatter = true, + }, +} diff --git a/lsp/htmx.lua b/lsp/htmx.lua new file mode 100644 index 0000000..92849e6 --- /dev/null +++ b/lsp/htmx.lua @@ -0,0 +1,5 @@ +return { + cmd = { "htmx-lsp" }, + filetypes = { "html", "templ" }, + root_markers = { "package.json", "go.mod", ".git" }, +} diff --git a/lsp/jsonls.lua b/lsp/jsonls.lua new file mode 100644 index 0000000..664a448 --- /dev/null +++ b/lsp/jsonls.lua @@ -0,0 +1,6 @@ +return { + cmd = { "vscode-json-language-server", "--stdio" }, + filetypes = { "json", "jsonc" }, + root_markers = { ".git" }, + init_options = { provideFormatter = true }, +} diff --git a/lsp/lua_ls.lua b/lsp/lua_ls.lua new file mode 100644 index 0000000..c359d1d --- /dev/null +++ b/lsp/lua_ls.lua @@ -0,0 +1,18 @@ +return { + cmd = { "lua-language-server" }, + filetypes = { "lua" }, + root_markers = { ".luarc.json", ".luarc.jsonc", ".git" }, + settings = { + Lua = { + runtime = { version = "LuaJIT" }, + workspace = { + checkThirdParty = false, + library = { + vim.env.VIMRUNTIME, + "${3rd}/luv/library", + }, + }, + telemetry = { enable = false }, + }, + }, +} diff --git a/lsp/marksman.lua b/lsp/marksman.lua new file mode 100644 index 0000000..a5d413b --- /dev/null +++ b/lsp/marksman.lua @@ -0,0 +1,5 @@ +return { + cmd = { "marksman", "server" }, + filetypes = { "markdown", "markdown.mdx" }, + root_markers = { ".marksman.toml", ".git" }, +} diff --git a/lsp/pyright.lua b/lsp/pyright.lua new file mode 100644 index 0000000..2d4b7da --- /dev/null +++ b/lsp/pyright.lua @@ -0,0 +1,14 @@ +return { + cmd = { "pyright-langserver", "--stdio" }, + filetypes = { "python" }, + root_markers = { "pyproject.toml", "setup.py", "setup.cfg", "requirements.txt", "Pipfile", "pyrightconfig.json", ".git" }, + settings = { + python = { + analysis = { + autoSearchPaths = true, + useLibraryCodeForTypes = true, + diagnosticMode = "openFilesOnly", + }, + }, + }, +} diff --git a/lsp/taplo.lua b/lsp/taplo.lua new file mode 100644 index 0000000..f48d87a --- /dev/null +++ b/lsp/taplo.lua @@ -0,0 +1,5 @@ +return { + cmd = { "taplo", "lsp", "stdio" }, + filetypes = { "toml" }, + root_markers = { ".git" }, +} diff --git a/lsp/templ.lua b/lsp/templ.lua new file mode 100644 index 0000000..61f83a1 --- /dev/null +++ b/lsp/templ.lua @@ -0,0 +1,5 @@ +return { + cmd = { "templ", "lsp" }, + filetypes = { "templ" }, + root_markers = { "go.mod", ".git" }, +} diff --git a/lsp/ts_ls.lua b/lsp/ts_ls.lua new file mode 100644 index 0000000..bd9b1fd --- /dev/null +++ b/lsp/ts_ls.lua @@ -0,0 +1,5 @@ +return { + cmd = { "typescript-language-server", "--stdio" }, + filetypes = { "javascript", "javascriptreact", "typescript", "typescriptreact" }, + root_markers = { "package.json", "tsconfig.json", "jsconfig.json", ".git" }, +} diff --git a/lsp/yamlls.lua b/lsp/yamlls.lua new file mode 100644 index 0000000..960ff40 --- /dev/null +++ b/lsp/yamlls.lua @@ -0,0 +1,11 @@ +return { + cmd = { "yaml-language-server", "--stdio" }, + filetypes = { "yaml", "yaml.docker-compose", "yaml.gitlab" }, + root_markers = { ".git" }, + settings = { + yaml = { + keyOrdering = false, + schemaStore = { enable = true, url = "https://www.schemastore.org/api/json/catalog.json" }, + }, + }, +} diff --git a/lua/timmypidashev/keymaps.lua b/lua/timmypidashev/keymaps.lua new file mode 100644 index 0000000..993c59f --- /dev/null +++ b/lua/timmypidashev/keymaps.lua @@ -0,0 +1,10 @@ +local map = vim.keymap.set + +-- Reload config +map("n", "", "source %", { desc = "Source current file" }) + +-- Save +map({ "n", "i" }, "", "w", { desc = "Save" }) + +-- Clear search highlight +map("n", "", "nohlsearch") diff --git a/lua/timmypidashev/langs.lua b/lua/timmypidashev/langs.lua new file mode 100644 index 0000000..b13c5bb --- /dev/null +++ b/lua/timmypidashev/langs.lua @@ -0,0 +1,24 @@ +return { + -- nvim / scripting + "lua", "luadoc", "vim", "vimdoc", "query", + "bash", "fish", + -- web + "javascript", "typescript", "tsx", "jsdoc", + "html", "css", "scss", + -- backend + "python", + "go", "gomod", "gosum", "gowork", + "rust", "c", "cpp", "java", + "apex", "soql", + -- templating + "templ", + -- docs / data + "markdown", "markdown_inline", + "json", "yaml", "toml", "xml", + -- infra / tooling + "dockerfile", "make", "cmake", "sql", + -- git + "gitcommit", "gitignore", "git_config", "gitattributes", "git_rebase", "diff", + -- misc + "regex", +} diff --git a/lua/timmypidashev/options.lua b/lua/timmypidashev/options.lua new file mode 100644 index 0000000..4a4e718 --- /dev/null +++ b/lua/timmypidashev/options.lua @@ -0,0 +1,33 @@ +local opt = vim.o + +-- Indent +opt.tabstop = 2 +opt.softtabstop = 2 +opt.shiftwidth = 2 +opt.expandtab = true +opt.autoindent = true + +-- UI +opt.number = true +opt.relativenumber = true +opt.termguicolors = true +opt.laststatus = 3 +opt.wrap = false +opt.signcolumn = "yes:1" +opt.numberwidth = 2 +opt.fillchars = "eob: " -- hide ~ markers on end-of-buffer lines + +-- Search +opt.ignorecase = true +opt.smartcase = true + +-- Editing +opt.undofile = true +opt.backupcopy = "yes" + +-- Neovide +if vim.g.neovide then + vim.opt.guifont = { "ComicCode Nerd Font", ":h16" } + vim.g.neovide_scale_factor = 1.0 + vim.g.neovide_scroll_animation_length = 0.3 +end diff --git a/lua/timmypidashev/plugins.lua b/lua/timmypidashev/plugins.lua new file mode 100644 index 0000000..658e75e --- /dev/null +++ b/lua/timmypidashev/plugins.lua @@ -0,0 +1,75 @@ +-- Native 0.12 package manager (vim.pack) +vim.pack.add({ + { src = "https://git.timmypidashev.dev/timmypidashev/darkbox.nvim" }, + { src = "https://github.com/nvim-tree/nvim-web-devicons" }, + { src = "https://github.com/nvim-lualine/lualine.nvim" }, + { src = "https://github.com/lewis6991/gitsigns.nvim" }, + { src = "https://github.com/nvim-treesitter/nvim-treesitter", version = "main" }, + { src = "https://github.com/mason-org/mason.nvim" }, + { src = "https://github.com/WhoIsSethDaniel/mason-tool-installer.nvim" }, + { src = "https://github.com/wakatime/vim-wakatime" }, + { src = "https://github.com/nvim-lua/plenary.nvim" }, + { src = "https://github.com/ThePrimeagen/harpoon", version = "harpoon2" }, + { src = "https://github.com/romgrk/barbar.nvim" }, + { src = "https://github.com/stevearc/oil.nvim" }, + + -- Completion + { src = "https://github.com/saghen/blink.cmp", version = vim.version.range("1") }, + + -- Picker + { src = "https://github.com/ibhagwan/fzf-lua" }, + + -- Formatter / Linter + { src = "https://github.com/stevearc/conform.nvim" }, + { src = "https://github.com/mfussenegger/nvim-lint" }, + + -- Quality of life + { src = "https://github.com/folke/which-key.nvim" }, + { src = "https://github.com/windwp/nvim-autopairs" }, + { src = "https://github.com/kylechui/nvim-surround" }, + { src = "https://github.com/akinsho/toggleterm.nvim" }, + { src = "https://github.com/mbbill/undotree" }, + + -- UI + { src = "https://github.com/MunifTanjim/nui.nvim" }, + { src = "https://github.com/rcarriga/nvim-notify" }, + { src = "https://github.com/folke/noice.nvim" }, + { src = "https://github.com/nvim-mini/mini.indentscope" }, + { src = "https://github.com/nvim-mini/mini.animate" }, + + -- Fancy + { src = "https://github.com/zeybek/camouflage.nvim" }, + { src = "https://github.com/Owen-Dechow/videre.nvim" }, + { src = "https://github.com/rachartier/tiny-glimmer.nvim" }, + { src = "https://github.com/MeanderingProgrammer/render-markdown.nvim" }, +}) + +-- Local dev plugins +vim.opt.rtp:prepend(vim.fn.expand("~/Projects/timmypidashev/verse.nvim")) + +require("timmypidashev.plugins.darkbox") +require("timmypidashev.plugins.lualine") +require("timmypidashev.plugins.gitsigns") +require("timmypidashev.plugins.treesitter") +require("timmypidashev.plugins.mason") +require("timmypidashev.plugins.blink") +require("timmypidashev.plugins.lsp") +require("timmypidashev.plugins.starter") +require("timmypidashev.plugins.harpoon") +require("timmypidashev.plugins.barbar") +require("timmypidashev.plugins.oil") +require("timmypidashev.plugins.fzf") +require("timmypidashev.plugins.conform") +require("timmypidashev.plugins.lint") +require("timmypidashev.plugins.whichkey") +require("timmypidashev.plugins.autopairs") +require("timmypidashev.plugins.surround") +require("timmypidashev.plugins.toggleterm") +require("timmypidashev.plugins.undotree") +require("timmypidashev.plugins.noice") +require("timmypidashev.plugins.indentscope") +-- require("timmypidashev.plugins.animate") -- disabled: scroll/resize jank +require("timmypidashev.plugins.camouflage") +require("timmypidashev.plugins.videre") +require("timmypidashev.plugins.tinyglimmer") +require("timmypidashev.plugins.render-markdown") diff --git a/lua/timmypidashev/plugins/animate.lua b/lua/timmypidashev/plugins/animate.lua new file mode 100644 index 0000000..e7f9254 --- /dev/null +++ b/lua/timmypidashev/plugins/animate.lua @@ -0,0 +1,14 @@ +-- Disabled: mini.animate was causing scroll lag and general jank. +-- Re-enable by uncommenting and requiring setup below. +-- +-- local animate = require("mini.animate") +-- animate.setup({ +-- cursor = { +-- enable = true, +-- timing = animate.gen_timing.linear({ duration = 80, unit = "total" }), +-- }, +-- scroll = { enable = false }, +-- resize = { enable = true }, +-- open = { enable = false }, +-- close = { enable = false }, +-- }) diff --git a/lua/timmypidashev/plugins/autopairs.lua b/lua/timmypidashev/plugins/autopairs.lua new file mode 100644 index 0000000..81218d9 --- /dev/null +++ b/lua/timmypidashev/plugins/autopairs.lua @@ -0,0 +1,3 @@ +require("nvim-autopairs").setup({ + check_ts = true, +}) diff --git a/lua/timmypidashev/plugins/barbar.lua b/lua/timmypidashev/plugins/barbar.lua new file mode 100644 index 0000000..6611118 --- /dev/null +++ b/lua/timmypidashev/plugins/barbar.lua @@ -0,0 +1,95 @@ +vim.g.barbar_auto_setup = false + +require("barbar").setup({ + animation = true, + auto_hide = false, + tabpages = true, + exclude_ft = { "dashboard", "oil" }, + icons = { + buffer_index = true, + button = false, -- hide the close × on tabs + filetype = { enabled = true }, + -- Barbar's default preset uses a different separator glyph for inactive + -- tabs (▎ vs │), which visibly shifts the pipe when switching tabs. + -- Explicitly set all states to the same glyph. + separator = { left = "│", right = "" }, + inactive = { separator = { left = "│", right = "" } }, + visible = { separator = { left = "│", right = "" } }, + current = { separator = { left = "│", right = "" } }, + separator_at_end = false, + modified = { button = "●" }, + pinned = { button = "", filename = true }, + }, +}) + +-- Personal barbar highlight tweaks on top of darkbox: +-- 1. All separator pipes share one muted color (matches WinSeparator). +-- 2. Active tab's index number is green so the selected tab pops. +local function barbar_highlights() + local bg = vim.api.nvim_get_hl(0, { name = "Normal", link = false }).bg + local win_sep = vim.api.nvim_get_hl(0, { name = "WinSeparator", link = false }) + local sep_fg = win_sep and win_sep.fg + + if sep_fg then + for _, group in ipairs({ + "BufferCurrentSign", + "BufferVisibleSign", + "BufferInactiveSign", + "BufferAlternateSign", + }) do + vim.api.nvim_set_hl(0, group, { fg = sep_fg, bg = bg }) + end + end + + local green = vim.api.nvim_get_hl(0, { name = "DarkboxGreen", link = false }).fg + if green then + vim.api.nvim_set_hl(0, "BufferCurrentIndex", { fg = green, bg = bg, bold = true }) + end +end + +barbar_highlights() +vim.api.nvim_create_autocmd("ColorScheme", { + callback = barbar_highlights, +}) + +local map = vim.keymap.set +local opts = { silent = true } + +-- If the current window is the oil sidebar, refocus the main editor window +-- first. Otherwise barbar commands (BufferGoto, BufferClose, etc.) act on the +-- oil window instead of the file area. +local function focus_main() + if vim.bo.filetype ~= "oil" then return end + 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 ~= "oil" then + vim.api.nvim_set_current_win(win) + return + end + end +end + +local function barbar_cmd(cmd) + return function() + focus_main() + vim.cmd(cmd) + end +end + +-- Leader + N → jump to tab N +for i = 1, 9 do + map("n", "" .. i, barbar_cmd("BufferGoto " .. i), opts) +end +map("n", "0", barbar_cmd("BufferLast"), opts) + +-- Navigation +map("n", ",", barbar_cmd("BufferPrevious"), opts) +map("n", ".", barbar_cmd("BufferNext"), opts) + +-- Reorder +map("n", "<", barbar_cmd("BufferMovePrevious"), opts) +map("n", ">", barbar_cmd("BufferMoveNext"), opts) + +-- Close +map("n", "c", barbar_cmd("BufferClose"), opts) +map("n", "bp", barbar_cmd("BufferPin"), opts) diff --git a/lua/timmypidashev/plugins/blink.lua b/lua/timmypidashev/plugins/blink.lua new file mode 100644 index 0000000..d1df8bf --- /dev/null +++ b/lua/timmypidashev/plugins/blink.lua @@ -0,0 +1,33 @@ +require("blink.cmp").setup({ + keymap = { preset = "super-tab" }, + appearance = { nerd_font_variant = "mono" }, + sources = { + default = { "lsp", "path", "snippets", "buffer" }, + }, + completion = { + menu = { border = "single" }, + documentation = { + auto_show = true, + auto_show_delay_ms = 200, + window = { border = "single" }, + }, + ghost_text = { enabled = true }, + }, + signature = { + enabled = true, + window = { border = "single" }, + }, + fuzzy = { implementation = "lua" }, +}) + +-- Personal preference: darkbox links BlinkCmpMenuSelection to PmenuSel (blue), +-- which is too bright. Re-bind to a dim background_2 stripe. +local function dim_selection() + local bg2 = vim.api.nvim_get_hl(0, { name = "DarkboxBg2", link = false }).fg + if bg2 then + vim.api.nvim_set_hl(0, "BlinkCmpMenuSelection", { bg = bg2, bold = true }) + end +end + +dim_selection() +vim.api.nvim_create_autocmd("ColorScheme", { callback = dim_selection }) diff --git a/lua/timmypidashev/plugins/camouflage.lua b/lua/timmypidashev/plugins/camouflage.lua new file mode 100644 index 0000000..aa8561f --- /dev/null +++ b/lua/timmypidashev/plugins/camouflage.lua @@ -0,0 +1,10 @@ +require("camouflage").setup({ + enabled = true, + mask_char = "*", + -- Override the default pattern list. Defaults mask *.yaml, *.json, *.sh, + -- *.toml, *.properties etc. which grabs false positives like Taskfile.yml + -- and package-lock.json. Only mask real env files. + patterns = { + { file_pattern = { ".env*", "*.env", ".envrc" }, parser = "env" }, + }, +}) diff --git a/lua/timmypidashev/plugins/conform.lua b/lua/timmypidashev/plugins/conform.lua new file mode 100644 index 0000000..c6bb8a6 --- /dev/null +++ b/lua/timmypidashev/plugins/conform.lua @@ -0,0 +1,28 @@ +require("conform").setup({ + formatters_by_ft = { + lua = { "stylua" }, + javascript = { "prettierd", "prettier", stop_after_first = true }, + typescript = { "prettierd", "prettier", stop_after_first = true }, + javascriptreact = { "prettierd", "prettier", stop_after_first = true }, + typescriptreact = { "prettierd", "prettier", stop_after_first = true }, + json = { "prettierd" }, + yaml = { "prettierd" }, + markdown = { "prettierd" }, + html = { "prettierd" }, + css = { "prettierd" }, + scss = { "prettierd" }, + go = { "goimports", "gofumpt" }, + python = { "ruff_organize_imports", "ruff_format" }, + sh = { "shfmt" }, + bash = { "shfmt" }, + templ = { "templ" }, + }, + format_on_save = { + timeout_ms = 1500, + lsp_format = "fallback", + }, +}) + +vim.keymap.set({ "n", "v" }, "fm", function() + require("conform").format({ async = true, lsp_format = "fallback" }) +end, { desc = "Format buffer" }) diff --git a/lua/timmypidashev/plugins/darkbox.lua b/lua/timmypidashev/plugins/darkbox.lua new file mode 100644 index 0000000..9f1ff57 --- /dev/null +++ b/lua/timmypidashev/plugins/darkbox.lua @@ -0,0 +1,4 @@ +require("darkbox").setup({ + contrast = "retro", +}) +vim.cmd.colorscheme("darkbox") diff --git a/lua/timmypidashev/plugins/fzf.lua b/lua/timmypidashev/plugins/fzf.lua new file mode 100644 index 0000000..41834c9 --- /dev/null +++ b/lua/timmypidashev/plugins/fzf.lua @@ -0,0 +1,17 @@ +local fzf = require("fzf-lua") +fzf.setup({ + winopts = { border = "rounded", preview = { border = "rounded" } }, +}) + +local map = vim.keymap.set +map("n", "ff", fzf.files, { desc = "Find files" }) +map("n", "fg", fzf.live_grep, { desc = "Live grep" }) +map("n", "fb", fzf.buffers, { desc = "Buffers" }) +map("n", "fh", fzf.helptags, { desc = "Help tags" }) +map("n", "fk", fzf.keymaps, { desc = "Keymaps" }) +map("n", "fs", fzf.lsp_document_symbols, { desc = "Document symbols" }) +map("n", "fS", fzf.lsp_workspace_symbols, { desc = "Workspace symbols" }) +map("n", "fd", fzf.diagnostics_document, { desc = "Diagnostics (buffer)" }) +map("n", "fD", fzf.diagnostics_workspace, { desc = "Diagnostics (workspace)" }) +map("n", "fr", fzf.resume, { desc = "Resume last picker" }) +map("n", "fo", fzf.oldfiles, { desc = "Recent files" }) diff --git a/lua/timmypidashev/plugins/gitsigns.lua b/lua/timmypidashev/plugins/gitsigns.lua new file mode 100644 index 0000000..0ed21b7 --- /dev/null +++ b/lua/timmypidashev/plugins/gitsigns.lua @@ -0,0 +1,13 @@ +require("gitsigns").setup({ + signs = { + add = { text = "+" }, + change = { text = "~" }, + delete = { text = "_" }, + topdelete = { text = "‾" }, + changedelete = { text = "~" }, + untracked = { text = "+" }, + }, + signs_staged_enable = false, -- don't distinguish staged vs unstaged in signcolumn + signcolumn = true, + attach_to_untracked = true, +}) diff --git a/lua/timmypidashev/plugins/harpoon.lua b/lua/timmypidashev/plugins/harpoon.lua new file mode 100644 index 0000000..45ae42e --- /dev/null +++ b/lua/timmypidashev/plugins/harpoon.lua @@ -0,0 +1,15 @@ +local harpoon = require("harpoon") +harpoon:setup() + +local map = vim.keymap.set + +map("n", "a", function() harpoon:list():add() end, { desc = "Harpoon add file" }) +map("n", "", function() harpoon.ui:toggle_quick_menu(harpoon:list()) end, { desc = "Harpoon menu" }) + +map("n", "", function() harpoon:list():select(1) end, { desc = "Harpoon slot 1" }) +map("n", "", function() harpoon:list():select(2) end, { desc = "Harpoon slot 2" }) +map("n", "", function() harpoon:list():select(3) end, { desc = "Harpoon slot 3" }) +map("n", "", function() harpoon:list():select(4) end, { desc = "Harpoon slot 4" }) + +map("n", "", function() harpoon:list():next() end, { desc = "Harpoon next" }) +map("n", "", function() harpoon:list():prev() end, { desc = "Harpoon prev" }) diff --git a/lua/timmypidashev/plugins/indentscope.lua b/lua/timmypidashev/plugins/indentscope.lua new file mode 100644 index 0000000..92820b4 --- /dev/null +++ b/lua/timmypidashev/plugins/indentscope.lua @@ -0,0 +1,13 @@ +require("mini.indentscope").setup({ + symbol = "│", + draw = { + delay = 50, + animation = require("mini.indentscope").gen_animation.none(), + }, + options = { try_as_border = true }, +}) + +vim.api.nvim_create_autocmd("FileType", { + pattern = { "dashboard", "oil", "help", "man", "ministarter", "lazy", "mason", "notify" }, + callback = function() vim.b.miniindentscope_disable = true end, +}) diff --git a/lua/timmypidashev/plugins/lint.lua b/lua/timmypidashev/plugins/lint.lua new file mode 100644 index 0000000..7029fa0 --- /dev/null +++ b/lua/timmypidashev/plugins/lint.lua @@ -0,0 +1,18 @@ +local lint = require("lint") + +lint.linters_by_ft = { + javascript = { "eslint_d" }, + typescript = { "eslint_d" }, + javascriptreact = { "eslint_d" }, + typescriptreact = { "eslint_d" }, + python = { "ruff" }, + sh = { "shellcheck" }, + bash = { "shellcheck" }, + markdown = { "markdownlint-cli2" }, +} + +vim.api.nvim_create_autocmd({ "BufWritePost", "InsertLeave" }, { + callback = function() lint.try_lint() end, +}) + +vim.keymap.set("n", "l", function() lint.try_lint() end, { desc = "Lint buffer" }) diff --git a/lua/timmypidashev/plugins/lsp.lua b/lua/timmypidashev/plugins/lsp.lua new file mode 100644 index 0000000..9e9da22 --- /dev/null +++ b/lua/timmypidashev/plugins/lsp.lua @@ -0,0 +1,79 @@ +vim.filetype.add({ + extension = { + cls = "apex", + trigger = "apex", + apex = "apex", + }, +}) + +-- Advertise blink completion capabilities to all servers +vim.lsp.config("*", { + capabilities = require("blink.cmp").get_lsp_capabilities(), +}) + +vim.lsp.enable({ + "lua_ls", + "ts_ls", + "pyright", + "gopls", + "marksman", + "templ", + "html", + "cssls", + "jsonls", + "yamlls", + "taplo", + "bashls", + "apex_ls", + "htmx", +}) + +vim.diagnostic.config({ + virtual_text = true, + signs = { + text = { + [vim.diagnostic.severity.ERROR] = "e", + [vim.diagnostic.severity.WARN] = "w", + [vim.diagnostic.severity.INFO] = "i", + [vim.diagnostic.severity.HINT] = "h", + }, + }, + underline = true, + update_in_insert = false, + severity_sort = true, + float = { border = "single" }, +}) + +-- Give LSP hover/signature/rename floats the same dim border + black bg +-- everything else uses. +vim.lsp.buf.hover = (function(orig) + return function(opts) + opts = opts or {} + opts.border = opts.border or "single" + return orig(opts) + end +end)(vim.lsp.buf.hover) + +vim.lsp.buf.signature_help = (function(orig) + return function(opts) + opts = opts or {} + opts.border = opts.border or "single" + return orig(opts) + end +end)(vim.lsp.buf.signature_help) + +vim.api.nvim_create_autocmd("LspAttach", { + callback = function(ev) + local opts = { buffer = ev.buf } + local map = vim.keymap.set + map("n", "gd", vim.lsp.buf.definition, opts) + map("n", "gD", vim.lsp.buf.declaration, opts) + map("n", "gr", vim.lsp.buf.references, opts) + map("n", "gi", vim.lsp.buf.implementation, opts) + map("n", "K", vim.lsp.buf.hover, opts) + map("n", "rn", vim.lsp.buf.rename, opts) + map("n", "ca", vim.lsp.buf.code_action, opts) + map("n", "[d", function() vim.diagnostic.jump({ count = -1, float = true }) end, opts) + map("n", "]d", function() vim.diagnostic.jump({ count = 1, float = true }) end, opts) + end, +}) diff --git a/lua/timmypidashev/plugins/lualine.lua b/lua/timmypidashev/plugins/lualine.lua new file mode 100644 index 0000000..b6c56b9 --- /dev/null +++ b/lua/timmypidashev/plugins/lualine.lua @@ -0,0 +1,67 @@ +-- Custom lualine theme: pure black backgrounds everywhere, only the mode +-- indicator (section `a`) gets a colored accent. Colors pulled from darkbox's +-- retro palette so the statusline stays in sync with the editor. +local bg = "#000000" +local fg = "#bdae93" -- retro_foreground +local dim = "#a89984" -- foreground_4 +local mode_colors = { + normal = "#689d6a", -- retro_aqua + insert = "#458588", -- retro_blue + visual = "#b16286", -- retro_purple + replace = "#cc241d", -- retro_red + command = "#d79921", -- retro_yellow +} + +-- Mode indicator: colored capital letters, no block background. +local function mode_section(color) + return { fg = color, bg = bg, gui = "bold" } +end + +local darkbox_theme = { + normal = { + a = mode_section(mode_colors.normal), + b = { fg = fg, bg = bg }, + c = { fg = fg, bg = bg }, + }, + insert = { a = mode_section(mode_colors.insert) }, + visual = { a = mode_section(mode_colors.visual) }, + replace = { a = mode_section(mode_colors.replace) }, + command = { a = mode_section(mode_colors.command) }, + inactive = { + a = { fg = dim, bg = bg, gui = "bold" }, + b = { fg = dim, bg = bg }, + c = { fg = dim, bg = bg }, + }, +} + +require("lualine").setup({ + options = { + icons_enabled = true, + theme = darkbox_theme, + component_separators = { left = "", right = "" }, + section_separators = { left = "", right = "" }, + globalstatus = true, + always_divide_middle = true, + refresh = { + statusline = 1000, + tabline = 1000, + winbar = 1000, + }, + }, + sections = { + lualine_a = { "mode" }, + lualine_b = { "diff", "diagnostics" }, + lualine_c = {}, + lualine_x = {}, + lualine_y = {}, + lualine_z = { "branch" }, + }, + inactive_sections = { + lualine_a = {}, + lualine_b = {}, + lualine_c = { "filename" }, + lualine_x = { "location", "encoding", "fileformat", "filetype" }, + lualine_y = {}, + lualine_z = { "progress" }, + }, +}) diff --git a/lua/timmypidashev/plugins/mason.lua b/lua/timmypidashev/plugins/mason.lua new file mode 100644 index 0000000..15bf883 --- /dev/null +++ b/lua/timmypidashev/plugins/mason.lua @@ -0,0 +1,45 @@ +require("mason").setup({ + ui = { + border = "rounded", + icons = { + package_installed = "✓", + package_pending = "➜", + package_uninstalled = "✗", + }, + }, +}) + +require("mason-tool-installer").setup({ + ensure_installed = { + -- LSP servers (mason package names) + "lua-language-server", + "typescript-language-server", + "pyright", + "gopls", + "marksman", + "templ", + "html-lsp", + "css-lsp", + "json-lsp", + "yaml-language-server", + "taplo", + "bash-language-server", + "htmx-lsp", + -- Apex LSP not in mason registry — install JAR manually, set APEX_JAR env + + -- Formatters + "stylua", + "prettierd", + "gofumpt", + "goimports", + "shfmt", + + -- Linters + "eslint_d", + "ruff", + "shellcheck", + "markdownlint-cli2", + }, + auto_update = false, + run_on_start = true, +}) diff --git a/lua/timmypidashev/plugins/noice.lua b/lua/timmypidashev/plugins/noice.lua new file mode 100644 index 0000000..d1f117c --- /dev/null +++ b/lua/timmypidashev/plugins/noice.lua @@ -0,0 +1,29 @@ +require("notify").setup({ + background_colour = "#000000", + render = "compact", + stages = "fade", + timeout = 2500, +}) +vim.notify = require("notify") + +require("noice").setup({ + cmdline = { + view = "cmdline", -- classic bottom cmdline instead of floating popup + }, + lsp = { + override = { + ["vim.lsp.util.convert_input_to_markdown_lines"] = true, + ["vim.lsp.util.stylize_markdown"] = true, + ["cmp.entry.get_documentation"] = true, + }, + signature = { enabled = false }, -- blink handles this + hover = { enabled = true }, + }, + presets = { + bottom_search = true, + command_palette = false, -- keep cmdline at the bottom, not floating + long_message_to_split = true, + inc_rename = false, + lsp_doc_border = true, + }, +}) diff --git a/lua/timmypidashev/plugins/oil.lua b/lua/timmypidashev/plugins/oil.lua new file mode 100644 index 0000000..8260aa8 --- /dev/null +++ b/lua/timmypidashev/plugins/oil.lua @@ -0,0 +1,137 @@ +require("oil").setup({ + default_file_explorer = true, + delete_to_trash = true, + skip_confirm_for_simple_edits = false, + view_options = { + show_hidden = true, + }, + keymaps = { + ["g?"] = "actions.show_help", + [""] = { + desc = "Open in main window if sidebar, else select", + callback = function() + local oil = require("oil") + local entry = oil.get_cursor_entry() + if not entry then return end + + -- If this oil window isn't a sidebar, use default behavior. + if not vim.wo.winfixwidth then + oil.select() + return + end + + -- Directory: navigate within sidebar. + if entry.type == "directory" then + oil.select() + return + end + + -- File: find a non-oil window to open in. + local main_win + for _, w in ipairs(vim.api.nvim_tabpage_list_wins(0)) do + local b = vim.api.nvim_win_get_buf(w) + if vim.bo[b].filetype ~= "oil" then + main_win = w + break + end + end + + local path = oil.get_current_dir() .. entry.name + if main_win then + vim.api.nvim_set_current_win(main_win) + vim.cmd("edit " .. vim.fn.fnameescape(path)) + else + vim.cmd("rightbelow vsplit " .. vim.fn.fnameescape(path)) + end + end, + }, + [""] = { "actions.select", opts = { vertical = true } }, + [""] = { "actions.select", opts = { horizontal = true } }, + [""] = { "actions.select", opts = { tab = true } }, + [""] = "actions.preview", + [""] = "actions.close", + [""] = "actions.refresh", + ["-"] = "actions.parent", + ["_"] = "actions.open_cwd", + ["`"] = "actions.cd", + ["~"] = { "actions.cd", opts = { scope = "tab" } }, + ["gs"] = "actions.change_sort", + ["gx"] = "actions.open_external", + ["g."] = "actions.toggle_hidden", + ["g\\"] = "actions.toggle_trash", + }, + use_default_keymaps = false, +}) + +local SIDEBAR_WIDTH = 30 + +-- Fish-style shortened cwd: ~/P/t/neovimrc +local function fish_cwd() + local cwd = vim.fn.getcwd() + local home = vim.env.HOME or "" + if home ~= "" and cwd:sub(1, #home) == home then + cwd = "~" .. cwd:sub(#home + 1) + end + local parts = vim.split(cwd, "/", { plain = true }) + for i = 1, #parts - 1 do + local p = parts[i] + if p ~= "" and p ~= "~" then + parts[i] = p:sub(1, 1) + end + end + return table.concat(parts, "/") +end + +local function find_sidebar() + 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" and vim.api.nvim_win_get_config(win).relative == "" then + return win + end + end +end + +local function set_offset(width) + local ok, api = pcall(require, "barbar.api") + if ok then api.set_offset(width, fish_cwd()) end +end + +local function open_sidebar() + local existing = find_sidebar() + if existing then + vim.api.nvim_set_current_win(existing) + return + end + -- Suppress redraw so we don't flash the parent window's buffer in the new + -- split before :Oil takes over. + local save_lazyredraw = vim.o.lazyredraw + vim.o.lazyredraw = true + vim.cmd("topleft vsplit | vertical resize " .. SIDEBAR_WIDTH .. " | Oil") + vim.wo.number = false + vim.wo.relativenumber = false + vim.wo.signcolumn = "no" + vim.wo.winfixwidth = true + set_offset(SIDEBAR_WIDTH) + vim.o.lazyredraw = save_lazyredraw + vim.cmd("redraw") +end + +local function close_sidebar() + local existing = find_sidebar() + if existing then + vim.api.nvim_win_close(existing, true) + set_offset(0) + end +end + +-- Keep the barbar label in sync with cwd changes. +vim.api.nvim_create_autocmd("DirChanged", { + callback = function() + if find_sidebar() then set_offset(SIDEBAR_WIDTH) end + end, +}) + +vim.keymap.set("n", "-", "Oil", { desc = "Oil parent dir (current window)" }) +vim.keymap.set("n", "e", open_sidebar, { desc = "Open / focus Oil sidebar" }) +vim.keymap.set("n", "E", close_sidebar, { desc = "Close Oil sidebar" }) +vim.keymap.set("n", "o", "w", { desc = "Cycle windows" }) diff --git a/lua/timmypidashev/plugins/render-markdown.lua b/lua/timmypidashev/plugins/render-markdown.lua new file mode 100644 index 0000000..e080ce4 --- /dev/null +++ b/lua/timmypidashev/plugins/render-markdown.lua @@ -0,0 +1,14 @@ +require("render-markdown").setup({ + completions = { blink = { enabled = true } }, + code = { + sign = false, + width = "block", + right_pad = 1, + }, + heading = { + sign = false, + -- Drop the full-line backgrounds; let icon + colored text carry the heading. + backgrounds = {}, + width = "block", + }, +}) diff --git a/lua/timmypidashev/plugins/starter.lua b/lua/timmypidashev/plugins/starter.lua new file mode 100644 index 0000000..4b4fe30 --- /dev/null +++ b/lua/timmypidashev/plugins/starter.lua @@ -0,0 +1,135 @@ +local verse = require("verse") +verse.setup() + +vim.api.nvim_set_hl(0, "DashHeader", { link = "DarkboxYellow" }) +vim.api.nvim_set_hl(0, "VerseText", { link = "Comment" }) +vim.api.nvim_set_hl(0, "VerseRef", { link = "DarkboxAqua" }) + +local ns = vim.api.nvim_create_namespace("dash") + +-- Classic NVIM dashboard ASCII logo. +local logo = { + "███╗ ██╗██╗ ██╗██╗███╗ ███╗", + "████╗ ██║██║ ██║██║████╗ ████║", + "██╔██╗ ██║██║ ██║██║██╔████╔██║", + "██║╚██╗██║╚██╗ ██╔╝██║██║╚██╔╝██║", + "██║ ╚████║ ╚████╔╝ ██║██║ ╚═╝ ██║", + "╚═╝ ╚═══╝ ╚═══╝ ╚═╝╚═╝ ╚═╝", +} + +local function build() + local entries = {} + for _, line in ipairs(logo) do + table.insert(entries, { text = line, hl = "DashHeader" }) + end + table.insert(entries, { text = "" }) + table.insert(entries, { text = tostring(os.date("%A, %B %d %Y")), hl = "DashHeader" }) + + local v = verse.get() + if v then + table.insert(entries, { text = "" }) + for line in (verse.format()):gmatch("([^\n]*)\n?") do + local hl = line:match("^— ") and "VerseRef" or "VerseText" + table.insert(entries, { text = line, hl = hl }) + end + end + return entries +end + +local function render(buf, win) + if not (vim.api.nvim_buf_is_valid(buf) and vim.api.nvim_win_is_valid(win)) then return end + local entries = build() + local width = vim.api.nvim_win_get_width(win) + local height = vim.api.nvim_win_get_height(win) + local top = math.max(0, math.floor((height - #entries) / 3)) + + local lines, marks = {}, {} + for _ = 1, top do table.insert(lines, "") end + for _, e in ipairs(entries) do + local pad = math.max(0, math.floor((width - vim.fn.strdisplaywidth(e.text)) / 2)) + local text = string.rep(" ", pad) .. e.text + table.insert(lines, text) + if e.hl and e.text ~= "" then + table.insert(marks, { row = #lines - 1, s = pad, e = pad + #e.text, hl = e.hl }) + end + end + + vim.bo[buf].modifiable = true + vim.api.nvim_buf_set_lines(buf, 0, -1, false, lines) + vim.bo[buf].modifiable = false + + vim.api.nvim_buf_clear_namespace(buf, ns, 0, -1) + for _, m in ipairs(marks) do + vim.api.nvim_buf_set_extmark(buf, ns, m.row, m.s, { + end_col = m.e, + hl_group = m.hl, + }) + end +end + +local function open() + -- Remember the initial startup buffer so we can wipe it once the dashboard + -- takes over — otherwise it lingers as an unnamed `[buffer 1]` and shows up + -- in the tabline whenever the dashboard is later replaced by a real file. + local startup_buf = vim.api.nvim_get_current_buf() + + local buf = vim.api.nvim_create_buf(false, true) + vim.bo[buf].buftype = "nofile" + vim.bo[buf].bufhidden = "wipe" + vim.bo[buf].swapfile = false + vim.bo[buf].filetype = "dashboard" + vim.api.nvim_set_current_buf(buf) + + if startup_buf ~= buf + and vim.api.nvim_buf_is_valid(startup_buf) + and vim.api.nvim_buf_get_name(startup_buf) == "" + and not vim.bo[startup_buf].modified + and vim.api.nvim_buf_line_count(startup_buf) <= 1 + then + pcall(vim.api.nvim_buf_delete, startup_buf, { force = true }) + end + + local win = vim.api.nvim_get_current_win() + vim.wo[win].number = false + vim.wo[win].relativenumber = false + vim.wo[win].signcolumn = "no" + vim.wo[win].cursorline = false + vim.wo[win].list = false + vim.wo[win].statuscolumn = "" + + -- Hide tabline while dashboard is shown + local prev_showtabline = vim.o.showtabline + vim.o.showtabline = 0 + + -- Restore tabline only when the dashboard buffer is actually gone, not just + -- when focus moves away (e.g. opening oil sidebar). BufLeave fires too eagerly. + vim.api.nvim_create_autocmd({ "BufWipeout", "BufHidden" }, { + buffer = buf, + once = true, + callback = function() vim.o.showtabline = prev_showtabline end, + }) + + render(buf, win) + + verse.on_update(function() + if vim.api.nvim_buf_is_valid(buf) then render(buf, win) end + end) + + vim.api.nvim_create_autocmd({ "VimResized", "WinResized", "WinNew", "WinClosed" }, { + callback = function() + if not (vim.api.nvim_buf_is_valid(buf) and vim.api.nvim_win_is_valid(win)) then + return + end + if vim.api.nvim_win_get_buf(win) ~= buf then return end + render(buf, win) + end, + }) +end + +vim.api.nvim_create_autocmd("VimEnter", { + callback = function() + if vim.fn.argc() == 0 and vim.api.nvim_buf_get_name(0) == "" and vim.bo.filetype == "" then + open() + end + end, +}) diff --git a/lua/timmypidashev/plugins/surround.lua b/lua/timmypidashev/plugins/surround.lua new file mode 100644 index 0000000..446eb1b --- /dev/null +++ b/lua/timmypidashev/plugins/surround.lua @@ -0,0 +1 @@ +require("nvim-surround").setup({}) diff --git a/lua/timmypidashev/plugins/tinyglimmer.lua b/lua/timmypidashev/plugins/tinyglimmer.lua new file mode 100644 index 0000000..90b8e1f --- /dev/null +++ b/lua/timmypidashev/plugins/tinyglimmer.lua @@ -0,0 +1,9 @@ +require("tiny-glimmer").setup({ + default_animation = "fade", + overwrite = { + yank = { enabled = true }, + paste = { enabled = true }, + undo = { enabled = true }, + redo = { enabled = true }, + }, +}) diff --git a/lua/timmypidashev/plugins/toggleterm.lua b/lua/timmypidashev/plugins/toggleterm.lua new file mode 100644 index 0000000..5a3e78f --- /dev/null +++ b/lua/timmypidashev/plugins/toggleterm.lua @@ -0,0 +1,8 @@ +require("toggleterm").setup({ + direction = "float", + float_opts = { border = "curved" }, + start_in_insert = true, + close_on_exit = true, +}) + +vim.keymap.set({ "n", "t" }, "", "ToggleTerm", { desc = "Toggle terminal" }) diff --git a/lua/timmypidashev/plugins/treesitter.lua b/lua/timmypidashev/plugins/treesitter.lua new file mode 100644 index 0000000..23d0502 --- /dev/null +++ b/lua/timmypidashev/plugins/treesitter.lua @@ -0,0 +1,17 @@ +local langs = require("timmypidashev.langs") + +require("nvim-treesitter").install(langs) + +vim.filetype.add({ + extension = { templ = "templ" }, +}) + +vim.api.nvim_create_autocmd("FileType", { + callback = function(args) + local ft = vim.bo[args.buf].filetype + local lang = vim.treesitter.language.get_lang(ft) or ft + if pcall(vim.treesitter.start, args.buf, lang) then + vim.bo[args.buf].indentexpr = "v:lua.require'nvim-treesitter'.indentexpr()" + end + end, +}) diff --git a/lua/timmypidashev/plugins/undotree.lua b/lua/timmypidashev/plugins/undotree.lua new file mode 100644 index 0000000..47c4228 --- /dev/null +++ b/lua/timmypidashev/plugins/undotree.lua @@ -0,0 +1 @@ +vim.keymap.set("n", "u", function() vim.cmd.UndotreeToggle() end, { desc = "Toggle undotree" }) diff --git a/lua/timmypidashev/plugins/videre.lua b/lua/timmypidashev/plugins/videre.lua new file mode 100644 index 0000000..9ca4525 --- /dev/null +++ b/lua/timmypidashev/plugins/videre.lua @@ -0,0 +1,3 @@ +require("videre").setup({}) + +vim.keymap.set("n", "vj", "Videre", { desc = "Videre JSON graph" }) diff --git a/lua/timmypidashev/plugins/whichkey.lua b/lua/timmypidashev/plugins/whichkey.lua new file mode 100644 index 0000000..06daf0c --- /dev/null +++ b/lua/timmypidashev/plugins/whichkey.lua @@ -0,0 +1,4 @@ +require("which-key").setup({ + preset = "modern", + delay = 400, +}) diff --git a/nvim-pack-lock.json b/nvim-pack-lock.json new file mode 100644 index 0000000..6b2efdf --- /dev/null +++ b/nvim-pack-lock.json @@ -0,0 +1,41 @@ +{ + "plugins": { + "darkbox.nvim": { + "rev": "c9851bc173f169a0b7cd3c5ecb3133d6eba937fe", + "src": "https://github.com/timmypidashev/darkbox.nvim" + }, + "gitsigns.nvim": { + "rev": "8d82c240f190fc33723d48c308ccc1ed8baad69d", + "src": "https://github.com/lewis6991/gitsigns.nvim" + }, + "lualine.nvim": { + "rev": "a905eeebc4e63fdc48b5135d3bf8aea5618fb21c", + "src": "https://github.com/nvim-lualine/lualine.nvim" + }, + "mason-tool-installer.nvim": { + "rev": "443f1ef8b5e6bf47045cb2217b6f748a223cf7dc", + "src": "https://github.com/WhoIsSethDaniel/mason-tool-installer.nvim" + }, + "mason.nvim": { + "rev": "b03fb0f20bc1d43daf558cda981a2be22e73ac42", + "src": "https://github.com/mason-org/mason.nvim" + }, + "mini.starter": { + "rev": "7bdc9decc8b623f245c1e42a64bc41e61d574c5e", + "src": "https://github.com/nvim-mini/mini.starter" + }, + "nvim-treesitter": { + "rev": "4916d6592ede8c07973490d9322f187e07dfefac", + "src": "https://github.com/nvim-treesitter/nvim-treesitter", + "version": "'main'" + }, + "nvim-web-devicons": { + "rev": "c72328a5494b4502947a022fe69c0c47e53b6aa6", + "src": "https://github.com/nvim-tree/nvim-web-devicons" + }, + "vim-wakatime": { + "rev": "cb7ba055330245b3a9d29f8bb4b82aeb2d52e580", + "src": "https://github.com/wakatime/vim-wakatime" + } + } +} diff --git a/scripts/bootstrap.lua b/scripts/bootstrap.lua new file mode 100644 index 0000000..e539564 --- /dev/null +++ b/scripts/bootstrap.lua @@ -0,0 +1,29 @@ +-- Synchronous bootstrap: waits for mason + treesitter installs, then quits. +-- Assumes init.lua already ran (vim.pack.add auto-cloned plugins). + +local TIMEOUT = 600000 + +-- Mason tool install +local mason_done = false +vim.api.nvim_create_autocmd("User", { + pattern = "MasonToolsUpdateCompleted", + once = true, + callback = function() + mason_done = true + end, +}) +pcall(function() vim.cmd("MasonToolsInstall") end) +vim.wait(TIMEOUT, function() + return mason_done +end, 200) + +-- Treesitter parsers +local ok_ts, ts = pcall(require, "nvim-treesitter") +if ok_ts then + local handle = ts.install(require("timmypidashev.langs")) + if handle and handle.wait then + handle:wait(TIMEOUT) + end +end + +vim.cmd("qall!")