├── .gitignore ├── after ├── ftplugin │ ├── yaml.lua │ └── markdown.lua └── lsp │ └── lua_ls.lua ├── todo.md ├── README.md ├── snippets ├── typescript.json ├── package.json ├── README.txt └── markdown.json ├── lua ├── nvim-web-devicons.lua └── mylib │ ├── fold.lua │ ├── chezmoi_auto_apply.lua │ ├── persist_colorscheme.lua │ ├── autosize.lua │ └── obsidian.lua ├── init.local.lua.example ├── etc ├── notes.md └── graveyard.lua ├── about.md ├── AGENTS.md ├── mini-deps-snap └── init.lua /.gitignore: -------------------------------------------------------------------------------- 1 | doc/tags 2 | spell 3 | *.log 4 | init.local.lua 5 | -------------------------------------------------------------------------------- /after/ftplugin/yaml.lua: -------------------------------------------------------------------------------- 1 | vim.defer_fn(function() 2 | vim.opt_local.fixeol = true 3 | end, 0) -------------------------------------------------------------------------------- /todo.md: -------------------------------------------------------------------------------- 1 | persistence: 2 | 3 | - keybind to reset to dashboard 4 | - autoload on start 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Rico's Neovim Configuration 2 | 3 | ``` 4 | git clone https://github.com/rstacruz/vimfiles.git ~/.config/nvim 5 | ``` 6 | -------------------------------------------------------------------------------- /snippets/typescript.json: -------------------------------------------------------------------------------- 1 | { 2 | "Jsdoc": { 3 | "prefix": "jsdoc", 4 | "body": [ "/**", " * ${1}", " */" ] 5 | }, 6 | "AI comment": { 7 | "prefix": "ai", 8 | "body": [ "/* AI: ${1} */" ] 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /lua/nvim-web-devicons.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | -- shim for nvim-web-devicons 4 | -- Useful for Diffview: https://github.com/sindrets/diffview.nvim/pull/571/files 5 | M.get_icon = function(name, ext, opts) 6 | return require("mini.icons").get("file", name) 7 | end 8 | 9 | return M 10 | -------------------------------------------------------------------------------- /snippets/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "personal-snippets", 3 | "contributes": { 4 | "snippets": [ 5 | { "language": "markdown", "path": "./markdown.json" }, 6 | { "language": ["typescript", "javascript", "typescriptreact", "javascriptreact"], "path": "./typescript.json" } 7 | ] 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /after/ftplugin/markdown.lua: -------------------------------------------------------------------------------- 1 | vim.opt_local.cursorline = false -- doesn't look good with headlines 2 | vim.opt_local.spell = false -- I find spellcheck only useful when writing prose. toggle with leader-us 3 | vim.opt_local.wrap = false -- inline links make wrapping very weird. toggle with leader-uw 4 | vim.opt_local.relativenumber = false 5 | vim.opt_local.number = false -- toggle with leader-ul -------------------------------------------------------------------------------- /init.local.lua.example: -------------------------------------------------------------------------------- 1 | _G.Config.Languages.formatters_by_ft.typescript = { "biome", lsp_format = "fallback" } 2 | _G.Config.Languages.formatters_by_ft.typescriptreact = { "biome", lsp_format = "fallback" } 3 | _G.Config.Languages.formatters_by_ft.javascript = { "biome", lsp_format = "fallback" } 4 | _G.Config.Languages.formatters_by_ft.javascriptreact = { "biome", lsp_format = "fallback" } 5 | -------------------------------------------------------------------------------- /lua/mylib/fold.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | -- foldtext with syntax highlighting 4 | -- taken from LazyVim 5 | function M.foldtext() 6 | return vim.api.nvim_buf_get_lines(0, vim.v.lnum - 1, vim.v.lnum, false)[1] 7 | end 8 | 9 | function M.foldexpr() 10 | local ft = vim.bo.filetype 11 | if ft == "markdown" then 12 | return vim.treesitter.foldexpr() 13 | end 14 | return vim.lsp.foldexpr() or vim.treesitter.foldexpr() 15 | end 16 | 17 | return M 18 | -------------------------------------------------------------------------------- /snippets/README.txt: -------------------------------------------------------------------------------- 1 | example: 2 | 3 | ``` 4 | { 5 | "name": "personal-snippets", 6 | "contributes": { 7 | "snippets": [ 8 | { "language": "lua", "path": "./lua.json" } 9 | { "language": ["typescriptreact", "javascriptreact"], "path": "./react.json" } 10 | { "language": "all", "path": "./all.json" } 11 | ] 12 | } 13 | } 14 | 15 | -- lua.json 16 | { 17 | "foo": { 18 | "prefix": "foo", 19 | "body": [ 20 | "local ${1:foo} = ${2:bar}", 21 | "return ${3:baz}" 22 | ] 23 | } 24 | } 25 | ``` 26 | -------------------------------------------------------------------------------- /lua/mylib/chezmoi_auto_apply.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | -- Automatically run `chezmoi apply` after saving a chezmoi managed file 4 | M.setup = function() 5 | vim.api.nvim_create_autocmd("BufWritePost", { 6 | pattern = { os.getenv("HOME") .. "/.local/share/chezmoi/*" }, 7 | group = vim.api.nvim_create_augroup("auto_chezmoi", { clear = true }), 8 | callback = function() 9 | vim.fn.jobstart({ "chezmoi", "apply" }, { 10 | detach = true, 11 | on_exit = function(_, code) 12 | if code == 0 then 13 | vim.schedule(function() 14 | vim.notify("chezmoi apply: success", vim.log.levels.INFO) 15 | end) 16 | else 17 | vim.schedule(function() 18 | vim.notify("chezmoi apply: failed", vim.log.levels.ERROR) 19 | end) 20 | end 21 | end, 22 | }) 23 | end, 24 | }) 25 | end 26 | 27 | return M 28 | -------------------------------------------------------------------------------- /etc/notes.md: -------------------------------------------------------------------------------- 1 | ## Inspiration 2 | 3 | A lot of these Some favourite dotfiles I've seen: 4 | 5 | - lazyvim 6 | - https://github.com/LazyVim/LazyVim 7 | - https://github.com/LazyVim/LazyVim/blob/main/lua/lazyvim/plugins/extras/util/chezmoi.lua 8 | - echasnovski 9 | - https://github.com/echasnovski/nvim/blob/master/plugin/21_plugins.lua 10 | - swaykh 11 | - https://github.com/SwayKh/dotfiles/blob/9ccadaa8f672501aed4b50b64d12fc64c361c680/nvim/init.lua 12 | - https://github.com/SwayKh/dotfiles/blob/9ccadaa8f672501aed4b50b64d12fc64c361c680/nvim/lua/plugins/mini.lua 13 | - https://github.com/SwayKh/dotfiles/blob/9ccadaa8f672501aed4b50b64d12fc64c361c680/nvim/lua/plugins/blink.lua 14 | - zakwanhisham 15 | - https://github.com/zakwanhisham/dotfiles/blob/e39b5545552472cd26b55536a8cccdffa6b7dbf9/editor/.config/nvim/plugin/blink.lua 16 | 17 | ## To do 18 | 19 | - mason: auto install luacheck 20 | -------------------------------------------------------------------------------- /snippets/markdown.json: -------------------------------------------------------------------------------- 1 | { 2 | "Checkbox": { 3 | "prefix": "checkbox", 4 | "description": "Insert a markdown checkbox", 5 | "body": [ "- [ ] ${1}" ] 6 | }, 7 | "Plan": { 8 | "prefix": "plan", 9 | "description": "AI: Plan", 10 | "body": [ 11 | "Plan for the following change in this codebase.", 12 | "", 13 | "## Scope:", 14 | "", 15 | "${1}", 16 | "", 17 | "## Rationale:", 18 | "", 19 | "${2}", 20 | "", 21 | "## Refer to these references:", 22 | "", 23 | "${3}", 24 | "", 25 | "## But first, research (discovery):", 26 | "", 27 | "${4}" 28 | ] 29 | }, 30 | "Research": { 31 | "prefix": "Research", 32 | "description": "AI: Research", 33 | "body": [ 34 | "Produce research for the following concerns. (discovery)", 35 | "", 36 | "## Scope:", 37 | "", 38 | "${1}", 39 | "", 40 | "## Rationale:", 41 | "", 42 | "${2}", 43 | "", 44 | "## Refer to these references:", 45 | "", 46 | "${3}" 47 | ] 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /about.md: -------------------------------------------------------------------------------- 1 | # About this repo 2 | 3 | ## Key bindings 4 | 5 | - (insert) `` - accept completion (copilot, nvim-cmp) 6 | - (insert) `` - accept popup suggestion (blink) 7 | - `` - leader key 8 | 9 | ## Prior iterations 10 | 11 | This is the latest iteration of my Vim configuration. Previous versions include: 12 | 13 | - [v25.09](https://github.com/rstacruz/vimfiles/tree/v25.09) (2025-) - Neovim 0.11, mini.pack, single file 14 | - [v24.10](https://github.com/rstacruz/vimfiles/tree/v24.10) (2024-2025) - Neovim 0.11, LazyVim distribution 15 | - [v23.03](https://github.com/rstacruz/vimfiles/tree/v23.03) (2023-2024) - Neovim 0.8, LazyVim distribution 16 | - [v22.08](https://github.com/rstacruz/vimfiles/tree/v22.08) (2022-2023) - Neovim 0.6. Using Packer. Single file 17 | - [v20.02](https://github.com/rstacruz/vimfiles/tree/v20.02) (2020-2022) - Neovim and Vim 18 | - [v20.01](https://github.com/rstacruz/vimfiles.git/tree/v20.01) (2020) - Short-lived predecessor to v20.02 19 | - [v14.02](https://github.com/rstacruz/vimfiles.git/tree/v14.02) (2014-2020) - Based on bower then vim-plug 20 | - [v10.07](https://github.com/rstacruz/vimfiles.git/tree/v10.07) (2010-2011) - Based on pathogen 21 | -------------------------------------------------------------------------------- /AGENTS.md: -------------------------------------------------------------------------------- 1 | # Agent guidelines for this Neovim config 2 | 3 | ## Code style 4 | 5 | - Follow idiomatic Lua and Neovim plugin conventions. 6 | - Use `require("...")` for imports. 7 | - Prefer local modules and functions. 8 | - Use snake_case for variables and functions; PascalCase for modules. 9 | - Use early returns for error handling; avoid exceptions. 10 | - Add `---@param` and similar Lua annotations for types when helpful. 11 | - Keep functions small and descriptive. 12 | - Use descriptive, lowercase file names (e.g., `persist_colorscheme.lua`). 13 | 14 | Keymaps: 15 | 16 | - For keymap blocks with long lines, use `-- stylua: ignore start` ... `-- stylua: ignore end` comments around them. This prevents line wrapping. 17 | 18 | - Avoid inline functions that span more than one line. 19 | 20 | ```lua 21 | -- avoid 22 | vim.keymap.set("n", "xx", function() 23 | x() 24 | y() 25 | end, { desc = "xxx" }) 26 | 27 | -- consider 28 | local function descriptive_name() 29 | x() 30 | y() 31 | end 32 | 33 | vim.keymap.set("n", "xx", descriptive_name, { desc = "xxx" }) 34 | ``` 35 | 36 | Keymap descriptions: 37 | 38 | - Always have a `desc` for each keymap. 39 | - For keymaps that open a prompt, add ellipsis at the end. Eg, `LSP: rename symbol…` 40 | - For plugin keybindings, ensure `desc` is prefixed by the plugin, "Flash: jump to" rather than "Jump to". 41 | 42 | -------------------------------------------------------------------------------- /after/lsp/lua_ls.lua: -------------------------------------------------------------------------------- 1 | -- ┌────────────────────┐ 2 | -- │ LSP config example │ 3 | -- └────────────────────┘ 4 | -- 5 | -- This file contains configuration of 'lua_ls' language server. 6 | -- Source: https://github.com/LuaLS/lua-language-server 7 | -- 8 | -- It is used by `:h vim.lsp.enable()` and `:h vim.lsp.config()`. 9 | -- See `:h vim.lsp.Config` and `:h vim.lsp.ClientConfig` for all available fields. 10 | -- 11 | -- This config is designed for Lua's activity around Neovim. It provides only 12 | -- basic config and can be further improved. 13 | return { 14 | on_attach = function(client, buf_id) 15 | -- Reduce very long list of triggers for better 'mini.completion' experience 16 | client.server_capabilities.completionProvider.triggerCharacters = { ".", ":", "#", "(" } 17 | 18 | -- Use this function to define buffer-local mappings and behavior that depend 19 | -- on attached client or only makes sense if there is language server attached. 20 | end, 21 | -- LuaLS Structure of these settings comes from LuaLS, not Neovim 22 | settings = { 23 | Lua = { 24 | -- Define runtime properties. Use 'LuaJIT', as it is built into Neovim. 25 | runtime = { version = "LuaJIT", path = vim.split(package.path, ";") }, 26 | workspace = { 27 | -- Don't analyze code from submodules 28 | ignoreSubmodules = true, 29 | -- Add Neovim's methods for easier code writing 30 | library = { vim.env.VIMRUNTIME }, 31 | }, 32 | }, 33 | }, 34 | } 35 | -------------------------------------------------------------------------------- /lua/mylib/persist_colorscheme.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | -- Persist colorscheme across sessions 4 | -- 5 | -- Usage: 6 | -- require("persist_colorscheme").setup() 7 | 8 | -- Get cache file 9 | local function get_cache_file() 10 | local cache_path = vim.fn.stdpath("cache") 11 | local cache_file = cache_path .. "/colorscheme.lua" 12 | return cache_file 13 | end 14 | 15 | -- Save colorscheme to cache 16 | ---@param name string 17 | M.save = function(name) 18 | local cache_file = get_cache_file() 19 | local file = io.open(cache_file, "w") 20 | if not file then 21 | return 22 | end 23 | 24 | file:write("vim.opt.background = '" .. vim.o.background .. "'\n") 25 | file:write("vim.cmd('colorscheme " .. name .. "')") 26 | -- vim.g.colors_name 27 | file:close() 28 | end 29 | 30 | -- Load colorscheme from cache 31 | M.load = function() 32 | local cache_file = get_cache_file() 33 | 34 | if vim.fn.filereadable(cache_file) == 1 then 35 | dofile(cache_file) 36 | return 1 37 | end 38 | 39 | return 0 40 | end 41 | 42 | -- Setup autocmd to save colorscheme on change 43 | local function setup_autocmd() 44 | vim.api.nvim_create_autocmd("Colorscheme", { 45 | pattern = "*", 46 | group = vim.api.nvim_create_augroup("persist_colorscheme", { clear = true }), 47 | callback = function(ev) 48 | M.save(ev.match) 49 | end, 50 | }) 51 | end 52 | 53 | ---@class PersistColorschemeOpts 54 | ---@field fallback string? 55 | 56 | ---@param opts PersistColorschemeOpts 57 | M.setup = function(opts) 58 | local is_loaded = M.load() 59 | if is_loaded ~= 1 and opts.fallback then 60 | vim.cmd("colorscheme " .. opts.fallback) 61 | end 62 | setup_autocmd() 63 | end 64 | 65 | return M 66 | -------------------------------------------------------------------------------- /mini-deps-snap: -------------------------------------------------------------------------------- 1 | return { 2 | ["blame.nvim"] = "b87b8c820e4cec06fbbd2f946b7b35c45906ee0c", 3 | ["blink.cmp"] = "bae4bae0eedd1fa55f34b685862e94a222d5c6f8", 4 | ["conform.nvim"] = "b4aab989db276993ea5dcb78872be494ce546521", 5 | ["copilot.lua"] = "af3114aeb915beb14fcbc46849c7b08a5e3e2c1a", 6 | ["diffview.nvim"] = "4516612fe98ff56ae0415a259ff6361a89419b0a", 7 | ["flash.nvim"] = "b68bda044d68e4026c4e1ec6df3c5afd7eb8e341", 8 | ["github-nvim-theme"] = "c106c9472154d6b2c74b74565616b877ae8ed31d", 9 | ["guess-indent.nvim"] = "84a4987ff36798c2fc1169cbaff67960aed9776f", 10 | ["kanagawa.nvim"] = "debe91547d7fb1eef34ce26a5106f277fbfdd109", 11 | ["marks.nvim"] = "f353e8c08c50f39e99a9ed474172df7eddd89b72", 12 | ["mason-lspconfig.nvim"] = "7f9a39fcd2ac6e979001f857727d606888f5909c", 13 | ["mason.nvim"] = "7dc4facca9702f95353d5a1f87daf23d78e31c2a", 14 | ["mini.nvim"] = "80a11490e44a7fe8c911a3b4a827c56df3894058", 15 | ["nvim-lint"] = "0864f81c681e15d9bdc1156fe3a17bd07db5a3ed", 16 | ["nvim-lspconfig"] = "107c2458cdc780c4ed2c2b5e1b7800cd019010bd", 17 | ["nvim-treesitter"] = "42fc28ba918343ebfd5565147a42a26580579482", 18 | ["nvim-treesitter-context"] = "41847d3dafb5004464708a3db06b14f12bde548a", 19 | ["nvim-various-textobjs"] = "237763835b65bdc64f0e19a31c040321813e4333", 20 | ["obsidian.nvim"] = "9a0e71ddae5d0e531d86429699cdbf6059754d43", 21 | ["opencode.nvim"] = "98e176757933cabb09260521b25da2e549acc0cd", 22 | ["plenary.nvim"] = "b9fd5226c2f76c951fc8ed5923d85e4de065e509", 23 | ["render-markdown.nvim"] = "67f2c7c8850bb11eefa6b22054a6f4cef1146de2", 24 | ["snacks.nvim"] = "d67a47739dfc652cfcf66c59e929c704a854b37a", 25 | ["trouble.nvim"] = "f176232e7759c4f8abd923c21e3e5a5c76cd6837" 26 | } 27 | -------------------------------------------------------------------------------- /lua/mylib/autosize.lua: -------------------------------------------------------------------------------- 1 | -- autosize: resizes windows to 80 cols as you go 2 | -- just like `winwidth` but excludes sidebars and more 3 | -- 4 | -- example: 5 | -- require("mylib.autosize").setup() 6 | 7 | local settings = { 8 | excluded_filetypes = { 9 | "aerial", 10 | "AiderConsole", 11 | "Avante", 12 | "AvanteInput", 13 | "AvanteSelectedFiles", 14 | "DiffviewFiles", 15 | "minifiles", 16 | "neo-tree", 17 | "NvimTree", 18 | "Outline", 19 | "snacks_input", 20 | "snacks_picker_list", 21 | "TelescopePrompt", 22 | "TelescopeResults", 23 | }, 24 | min_width = 90, 25 | } 26 | 27 | local function is_floating(win_id) 28 | local win_config = vim.api.nvim_win_get_config(win_id) 29 | return win_config.relative ~= "" 30 | end 31 | 32 | local function run_autosize() 33 | if settings.min_width == -1 then 34 | return 35 | end 36 | 37 | -- don't run on vimdiff 38 | if vim.api.nvim_get_option_value("diff", { scope = "local" }) then 39 | return 40 | end 41 | 42 | -- if vim.api.nvim_get_option_value("buftype", { scope = "local" }) == "terminal" then 43 | -- return 44 | -- end 45 | 46 | local win_id = vim.api.nvim_get_current_win() 47 | 48 | if is_floating(win_id) then 49 | return 50 | end 51 | 52 | if vim.tbl_contains(settings.excluded_filetypes, vim.bo.filetype) then 53 | return 54 | end 55 | 56 | if vim.tbl_contains(settings.excluded_filetypes, vim.bo.filetype) then 57 | return 58 | end 59 | local before = vim.o.winwidth 60 | vim.o.winwidth = settings.min_width 61 | vim.o.winwidth = before 62 | vim.w.autosize_used = 1 63 | end 64 | 65 | local function setup(opts) 66 | settings = vim.tbl_extend("keep", settings, opts or {}) 67 | 68 | vim.api.nvim_create_autocmd("WinEnter", { 69 | group = vim.api.nvim_create_augroup("autosize", { clear = true }), 70 | callback = function() 71 | -- If a window has been autosized before, run it immediately 72 | if vim.w.autosize_used == 1 then 73 | return run_autosize() 74 | end 75 | 76 | -- Otherwise, for first time windows, wait for a while before 77 | -- autosizing. It might be a sidebar (eg, neotree) that hasn't 78 | -- loaded yet 79 | vim.defer_fn(run_autosize, 25) 80 | end, 81 | }) 82 | end 83 | 84 | return { setup = setup } 85 | -------------------------------------------------------------------------------- /lua/mylib/obsidian.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | M.get_obsidian_workspaces = function() 4 | local workspaces = {} 5 | local home = os.getenv("HOME") 6 | local vaults_dir = home .. "/Documents/Vaults/*" 7 | local items = vim.fn.glob(vaults_dir, false, true) 8 | 9 | for _, filepath in ipairs(items) do 10 | if vim.fn.isdirectory(filepath) == 1 then 11 | local name = vim.fn.fnamemodify(filepath, ":t") 12 | local item = { path = filepath, name = name } 13 | table.insert(workspaces, item) 14 | end 15 | end 16 | 17 | return workspaces 18 | end 19 | 20 | M.bind_keys = function(buf) 21 | -- stylua: ignore star 22 | vim.keymap.set("n", ".p", "Obsidian paste_img", { buffer = buf, desc = "Obsidian: Paste image" }) 23 | vim.keymap.set( 24 | "v", 25 | ".x", 26 | "Obsidian extract_note", 27 | { buffer = buf, desc = "Obsidian: Extract to..." } 28 | ) 29 | vim.keymap.set( 30 | "n", 31 | ".c", 32 | "Obsidian toggle_checkbox", 33 | { buffer = buf, desc = "Obsidian: Toggle checkbox" } 34 | ) 35 | vim.keymap.set( 36 | "n", 37 | ".s", 38 | "Obsidian follow_link vsplit", 39 | { buffer = buf, desc = "Obsidian: Follow link in vsplit" } 40 | ) 41 | vim.keymap.set( 42 | "n", 43 | ".R", 44 | "Obsidian backlinks", 45 | { buffer = buf, desc = "Obsidian: Show backlinks" } 46 | ) 47 | vim.keymap.set("n", ".r", "Obsidian rename", { buffer = buf, desc = "Obsidian: Rename..." }) 48 | vim.keymap.set("n", "gf", "Obsidian follow_link", { buffer = buf, desc = "Obsidian: Follow link" }) 49 | -- vim.keymap.set("n", "gr", "Obsidian backlinks", { buffer = buf, desc = "Obsidian: Show backlinks" }) 50 | -- stylua: ignore end 51 | end 52 | 53 | M.get_obsidian_options = function() 54 | local opts = { 55 | legacy_commands = false, 56 | workspaces = M.get_obsidian_workspaces(), 57 | notes_subdir = "Pages", 58 | new_notes_location = "notes_subdir", -- options: current_dir (default), notes_subdir 59 | ui = { enable = false }, -- use render-markdown.nvim instead 60 | templates = { folder = "Templates" }, 61 | completion = { blink = true }, 62 | attachments = { img_folder = "Media" }, -- default: "assets/imgs" 63 | preferred_link_style = "wiki", 64 | picker = { name = "snacks.pick" }, 65 | frontmatter = { func = M.note_frontmatter_func }, 66 | 67 | note_id_func = M.note_id_func, 68 | 69 | -- when using `gf` on a URL by mistake 70 | follow_url_func = vim.ui.open, 71 | follow_img_func = vim.ui.open, 72 | 73 | backlinks = { 74 | -- When 'true', using `:ObsidianBacklinks` will look for backlinks to the header 75 | -- under the cursor. Defaults to true. 76 | parse_headers = false, 77 | }, 78 | } 79 | 80 | return opts 81 | end 82 | 83 | M.note_id_func = function(raw_title) 84 | -- Default behaviour: return something like "124351678905-XYZX" 85 | if raw_title then 86 | local title = raw_title:gsub(":", ";") 87 | return title 88 | end 89 | 90 | return "Untitled-" .. tostring(os.time()) 91 | end 92 | 93 | M.note_frontmatter_func = function(note) 94 | local out = {} 95 | 96 | if note.metadata ~= nil and not vim.tbl_isempty(note.metadata) then 97 | for k, v in pairs(note.metadata) do 98 | out[k] = v 99 | end 100 | end 101 | 102 | -- Add createdAt: in the frontmatter 103 | if out.createdAt == nil then 104 | -- "2405 Page" -> 2024-05-01 105 | local datestamp = os.date("!%Y%m") 106 | ---@cast datestamp string 107 | if string.match(note.id, "^%d%d%d%d ") and string.sub(note.id, 1, 4) ~= string.sub(datestamp, 3, 9) then 108 | local year = "20" .. string.sub(note.id, 1, 2) 109 | local month = string.sub(note.id, 3, 4) 110 | local ymd = "" .. year .. "-" .. month .. "-01T00:00:00Z" 111 | out.createdAt = ymd 112 | else 113 | out.createdAt = os.date("!%Y-%m-%dT%TZ") 114 | end 115 | end 116 | 117 | -- Don't auto-add aliases 118 | -- Also, if alias is same as id, don't bother 119 | if note.aliases and next(note.aliases) ~= nil and (#note.aliases ~= 1 or note.aliases[1] ~= note.id) then 120 | out.aliases = note.aliases 121 | end 122 | 123 | -- Don't auto-add tags 124 | if note.tags and next(note.tags) ~= nil then 125 | out.tags = note.tags 126 | end 127 | 128 | return out 129 | end 130 | 131 | return M 132 | -------------------------------------------------------------------------------- /etc/graveyard.lua: -------------------------------------------------------------------------------- 1 | local add, now, later = MiniDeps.add, MiniDeps.now, MiniDeps.later 2 | local now_if_args = vim.fn.argc(-1) > 0 and now or later 3 | local now_if_no_args = vim.fn.argc(-1) > 0 and later or now 4 | 5 | later(function() -- smear-cursor 6 | if not vim.g.neovide then 7 | add({ source = "sphamba/smear-cursor.nvim" }) 8 | require("smear_cursor").setup({}) 9 | end 10 | end) 11 | 12 | -- interferes with mouse scroll 13 | later(function() -- mini.animate 14 | local animate = require("mini.animate") 15 | local fast = animate.gen_timing.cubic({ duration = 60, unit = "total" }) 16 | local xfast = animate.gen_timing.cubic({ duration = 20, unit = "total" }) 17 | 18 | animate.setup({ 19 | cursor = { enable = false }, 20 | scroll = { timing = fast }, 21 | resize = { timing = xfast }, 22 | }) 23 | end) 24 | 25 | later(function() -- opencode 26 | add({ source = "NickvanDyke/opencode.nvim", depends = { "folke/snacks.nvim" } }) 27 | 28 | local opencode = require("opencode") 29 | -- stylua: ignore start 30 | table.insert(CLUES, { mode = "n", keys = "o", desc = "+opencode" }) 31 | vim.keymap.set("n", "oa", function() opencode.ask("@cursor: ") end, { desc = "Ask opencode" }) 32 | vim.keymap.set("v", "oa", function() opencode.ask("@selection: ") end, { desc = "Ask opencode about selection" }) 33 | vim.keymap.set("n", "ot", function() opencode.toggle() end, { desc = "Toggle embedded opencode" }) 34 | vim.keymap.set("n", "on", function() opencode.command("session_new") end, { desc = "New session" }) 35 | vim.keymap.set("n", "oy", function() opencode.command("messages_copy") end, { desc = "Copy last message" }) 36 | vim.keymap.set({ "n", "v" }, "op", function() opencode.select_prompt() end, { desc = "Select prompt" }) 37 | vim.keymap.set("n", "", function() opencode.command("messages_half_page_up") end, { desc = "Scroll messages up" }) 38 | vim.keymap.set("n", "", function() opencode.command("messages_half_page_down") end, { desc = "Scroll messages down" }) 39 | -- stylua: ignore end 40 | end) 41 | 42 | -- measure startup time 43 | local start = (vim.uv or vim.loop).hrtime() 44 | now(function() 45 | vim.api.nvim_create_autocmd("VimEnter", { 46 | pattern = { "*" }, 47 | callback = function() 48 | local now = (vim.uv or vim.loop).hrtime() 49 | local delta = now - start 50 | local loadtime = string.format("Loaded in %.2f ms", delta / 1e6) 51 | vim.g.lol = loadtime -- display later? 52 | end, 53 | }) 54 | end) 55 | 56 | vim.api.nvim_create_autocmd("VimEnter", { 57 | pattern = { "*" }, 58 | callback = function() 59 | local now = (vim.uv or vim.loop).hrtime() 60 | local delta = now - start 61 | local loadtime = string.format("Loaded in %.2f ms", delta / 1e6) 62 | print(loadtime) 63 | vim.notify(loadtime) 64 | vim.g.lol = loadtime 65 | end, 66 | }) 67 | vim.api.nvim_create_autocmd("VimEnter", { 68 | pattern = { "*" }, 69 | callback = function() 70 | local now = (vim.uv or vim.loop).hrtime() 71 | local delta = now - start 72 | local loadtime = string.format("Loaded in %.2f ms", delta / 1e6) 73 | print(loadtime) 74 | vim.notify(loadtime) 75 | vim.g.lol = loadtime 76 | end, 77 | }) 78 | now_if_no_args(function() -- mini.starter 79 | local starter = require("mini.starter") 80 | 81 | local function get_banner() 82 | local cwd = vim.fn.fnamemodify(vim.fn.getcwd(), ":t") 83 | local logo = "" .. cwd .. "\n" .. string.rep("─", #cwd) 84 | return logo 85 | end 86 | 87 | local function get_footer() 88 | return " " 89 | end 90 | 91 | starter.setup({ 92 | evaluate_single = true, -- trigger on 1 keypress instead of having to press enter 93 | footer = get_footer, 94 | header = get_banner, 95 | query_updaters = "eq0123456789", 96 | content_hooks = { 97 | starter.gen_hook.adding_bullet(), -- line on the left 98 | starter.gen_hook.indexing("all", { "Builtin actions" }), -- numbers 99 | starter.gen_hook.aligning("center", "center"), 100 | }, 101 | }) 102 | end) 103 | 104 | later(function() -- render-markdown 105 | add({ source = "MeanderingProgrammer/render-markdown.nvim" }) 106 | require("render-markdown").setup({ 107 | heading = { 108 | -- default: 109 | -- signs = { "󰫎 " }, 110 | -- icons = { "󰲡 ", "󰲣 ", "󰲥 ", "󰲧 ", "󰲩 ", "󰲫 " }, 111 | icons = { "━ " }, 112 | signs = { "󰎤 ", "󰎩 ", "󰎬 ", "󰎮 ", "󰎰 ", "󰎵 " }, 113 | -- signs = { "󰎦 ", "󰎩 ", "󰎬 ", "󰎮 ", "󰎰 ", "󰎵 " }, -- nf-md-numeric_0_box_outline 114 | -- signs = { "󰎤 ", "󰎧 ", "󰎪 ", "󰎭 ", "󰎱 ", "󰎳 " }, -- nf-md-numeric_0_box 115 | -- icons = { "󰎤 ", "󰎧 ", "󰎪 ", "󰎭 ", "󰎱 ", "󰎳 " }, -- nf-md-numeric_0_box 116 | -- signs = { "Ⅰ", "Ⅱ", "Ⅲ", "Ⅳ", "󰲩", "󰲫" }, 117 | -- signs = { "∙", "∶", "∴", "∷", "󰲩", "󰲫" }, 118 | -- signs = { "━ " }, 119 | -- sign = false, 120 | }, 121 | checkbox = { 122 | -- "󰄲" -- nf-md-checkbox_marked 123 | -- "󰄳" -- nf-md-checkbox_marked_circle 124 | -- "󰄰" -- nf-md-checkbox_blank_circle_outline 125 | -- "󰸞" -- nf-md-check-bold 126 | -- "󰏤" -- nf-md-pause 127 | -- "󰜺" -- nf-md-cancel 128 | -- "󰄬" -- nf-md-check 129 | -- Comment = grey 130 | -- RenderMarkdownTodo = cyan? 131 | -- DiagnosticOk = green 132 | -- DiagnosticError = red 133 | -- DiagnosticInfo = cyan 134 | -- DiagnosticWarn = yellow 135 | -- RenderMarkdownHint, _Question - yellow 136 | -- RenderMarkdownSuccess - green 137 | -- RenderMarkdownInfo - blue 138 | -- RenderMarkdownError - red 139 | custom = { 140 | prog1 = { raw = "[1]", rendered = "󰂎", highlight = "DiagnosticInfo" }, 141 | prog2 = { raw = "[2]", rendered = "󱊡", highlight = "DiagnosticInfo" }, 142 | prog4 = { raw = "[4]", rendered = "󱊢", highlight = "DiagnosticInfo" }, 143 | prog8 = { raw = "[8]", rendered = "󱊣", highlight = "DiagnosticInfo" }, 144 | delegated = { raw = "[d]", rendered = "👤", highlight = "Comment" }, -- in progress, nf-md-texture_box 145 | }, 146 | }, 147 | }) 148 | end) 149 | 150 | -- Restore status line that was hidden earlier 151 | ---@param value number 152 | local function defer_laststatus_update_on_insert(value) 153 | local group = vim.api.nvim_create_augroup("restore", { clear = true }) 154 | vim.api.nvim_create_autocmd("InsertEnter", { 155 | group = group, 156 | callback = function() 157 | vim.opt.laststatus = value 158 | vim.api.nvim_del_augroup_by_id(group) 159 | end, 160 | }) 161 | end 162 | 163 | later(function() -- ts-comments 164 | -- when using `gc` to toggle comments, make it use the correct comment 165 | -- eg, {/* ... */} in JSX 166 | add({ source = "folke/ts-comments.nvim" }) 167 | require("ts-comments").setup({}) 168 | end) 169 | 170 | require("blink.cmp").setup({ 171 | keymap = { preset = "default", [""] = { "accept", "fallback" } }, 172 | completion = { documentation = { auto_show = true } }, 173 | fuzzy = { implementation = "prefer_rust" }, 174 | 175 | -- show signature help when typing ( 176 | signature = { enabled = true }, 177 | 178 | -- sources = { 179 | -- default = { "lsp", "path", "snippets", "buffer", "copilot" }, 180 | -- providers = { 181 | -- copilot = { 182 | -- name = "copilot", 183 | -- module = "blink-cmp-copilot", 184 | -- score_offset = 100, 185 | -- async = true, 186 | -- }, 187 | -- }, 188 | -- }, 189 | }) 190 | 191 | -- * mini-files: arrow keys https://github.com/nvim-mini/mini.nvim/blob/main/readmes/mini-files.md 192 | -------------------------------------------------------------------------------- /init.lua: -------------------------------------------------------------------------------- 1 | local start = (vim.uv or vim.loop).hrtime() 2 | 3 | -- Define config table to be able to pass data between scripts 4 | _G.Config = {} 5 | 6 | -- Start with `PROF=1 nvim` or `PROF=1 nvim file.txt` to see startup time 7 | if vim.env.PROF then 8 | local snacks = vim.fn.stdpath("data") .. "/lazy/snacks.nvim" 9 | vim.opt.rtp:append(snacks) 10 | require("snacks.profiler").startup({ startup = { event = "VimEnter" } }) 11 | end 12 | 13 | -- Load the `mylib` module 14 | local function setup_mini() 15 | local path_package = vim.fn.stdpath("data") .. "/site/" 16 | local mini_path = path_package .. "pack/deps/start/mini.nvim" 17 | 18 | if not vim.loop.fs_stat(mini_path) then 19 | vim.cmd('echo "Installing `mini.nvim`" | redraw') 20 | local clone_cmd = { "git", "clone", "--filter=blob:none", "https://github.com/nvim-mini/mini.nvim", mini_path } 21 | vim.fn.system(clone_cmd) 22 | vim.cmd("packadd mini.nvim | helptags ALL") 23 | vim.cmd('echo "Installed `mini.nvim`" | redraw') 24 | end 25 | require("mini.deps").setup({ path = { package = path_package } }) 26 | require("mini.basics").setup({ 27 | options = { 28 | extra_ui = true, -- winblend, listchars, pumheight, etc 29 | win_borders = "rounded", 30 | }, 31 | autocommands = { 32 | relnum_in_visual_mode = true, 33 | }, 34 | mappings = { 35 | windows = true, -- navigation with , resize with 36 | option_toggle_prefix = "u", 37 | }, 38 | silent = true, -- hide non-error feedback 39 | }) 40 | end 41 | 42 | setup_mini() 43 | local add, now, later = MiniDeps.add, MiniDeps.now, MiniDeps.later 44 | local no_args = vim.fn.argc(-1) == 0 and not vim.env.PROF 45 | local now_if_args = vim.fn.argc(-1) > 0 and now or later 46 | 47 | -- Config ------------------------------------------------------------------------------- 48 | 49 | -- Convenient config for all things related to language setup (LSP, etc) 50 | _G.Config.Languages = { 51 | -- stylua: ignore start 52 | treesitter = { "lua", "vimdoc", "javascript", "typescript", "markdown", "markdown_inline", "css", "astro", "bash", "git_config", "git_rebase", "gitattributes", "gitcommit", "gitignore", "graphql", "html", "jsdoc", "json", "tsx", "toml", "xml", "yaml", "c", "sql", "python" }, 53 | -- stylua: ignore end 54 | mason = { "prettier" }, -- , "copilot-language-server" }, 55 | -- tools (see :Mason) 56 | lsp = { "vtsls", "tailwindcss", "biome", "eslint" }, 57 | linters_by_ft = { 58 | lua = {}, -- luacheck 59 | }, 60 | formatters_by_ft = { 61 | lua = { "stylua" }, 62 | markdown = { "biome", "prettier" }, 63 | typescript = { "biome", "eslint_d", "prettierd", lsp_format = "fallback" }, 64 | typescriptreact = { "biome", "eslint_d", "prettierd", lsp_format = "fallback" }, 65 | javascript = { "biome", "eslint_d", "prettierd", lsp_format = "fallback" }, 66 | javascriptreact = { "biome", "eslint_d", "prettierd", lsp_format = "fallback" }, 67 | fish = { "fish_indent" }, 68 | sh = { "shfmt" }, 69 | }, 70 | } 71 | 72 | local CLUES = {} 73 | 74 | -- Termux: some tools are only available certain platforms 75 | local is_termux = string.find(vim.loop.os_uname().release, "android") 76 | if not is_termux then 77 | table.insert(_G.Config.Languages.lsp, "lua_ls") 78 | table.insert(_G.Config.Languages.mason, "stylua") 79 | end 80 | 81 | local is_mac = vim.loop.os_uname().sysname == "Darwin" 82 | if is_mac then 83 | table.insert(_G.Config.Languages.lsp, "kotlin_lsp") 84 | end 85 | 86 | -- Load init.local.lua if it exists 87 | local init_local = vim.fn.stdpath("config") .. "/init.local.lua" 88 | if vim.fn.filereadable(init_local) == 1 then 89 | dofile(init_local) 90 | end 91 | 92 | -- Core ---------------------------------------------------------------------------------- 93 | 94 | now(function() -- options 95 | vim.opt.shortmess:append("I") -- disable start screen 96 | vim.opt.laststatus = 0 -- to be set later 97 | vim.opt.tabstop = 2 98 | vim.opt.shiftwidth = 2 99 | vim.opt.foldlevel = 99 100 | vim.opt.updatetime = 500 -- time to show diagnostics 101 | vim.opt.swapfile = false 102 | vim.o.mousescroll = "ver:1" -- Slow mouse scroll 103 | vim.o.shortmess = "CFOSWaco" -- Disable some built-in completion messages 104 | vim.o.cursorlineopt = "screenline,number" -- Show cursor line per screen line 105 | vim.o.showmode = false -- Don't show mode in command line 106 | vim.o.iskeyword = "@,48-57,_,192-255,-" -- Treat dash as `word` textobject part 107 | vim.opt.fillchars = { 108 | foldopen = "", 109 | foldclose = "", 110 | fold = " ", 111 | foldsep = " ", 112 | diff = "╱", 113 | eob = " ", 114 | } 115 | vim.o.listchars = "extends:…,nbsp:␣,precedes:…,tab:> " 116 | 117 | if vim.fn.has("nvim-0.10") == 1 then 118 | vim.opt.smoothscroll = true 119 | vim.opt.foldexpr = "v:lua.require'mylib.fold'.foldexpr()" 120 | vim.opt.foldmethod = "expr" 121 | vim.opt.foldtext = "" 122 | else 123 | vim.opt.foldmethod = "indent" 124 | vim.opt.foldtext = "v:lua.require'mylib.fold'.foldtext()" 125 | end 126 | if vim.fn.has("nvim-0.11") == 1 then 127 | vim.opt.winblend = 3 -- reduce from 10 in mini.basics 128 | end 129 | if vim.g.neovide then 130 | vim.o.guifont = "Iosevka Medium:h12:w-0.3" 131 | vim.o.linespace = -2 132 | end 133 | end) 134 | -- stylua: ignore end 135 | 136 | MiniDeps.later(function() -- diagnostics 137 | -- Neovim has built-in support for showing diagnostic messages. This configures 138 | -- a more conservative display while still being useful. 139 | -- See `:h vim.diagnostic` and `:h vim.diagnostic.config()`. 140 | local diagnostic_opts = { 141 | -- Show signs on top of any other sign, but only for warnings and errors 142 | signs = { priority = 9999, severity = { min = "WARN", max = "ERROR" } }, 143 | 144 | -- Show all diagnostics as underline (for their messages type `ld`) 145 | underline = { severity = { min = "HINT", max = "ERROR" } }, 146 | 147 | -- Show more details immediately for errors on the current line 148 | virtual_lines = false, 149 | virtual_text = { 150 | current_line = true, 151 | severity = { min = "ERROR", max = "ERROR" }, 152 | }, 153 | 154 | -- Don't update diagnostics when typing 155 | update_in_insert = false, 156 | } 157 | 158 | vim.diagnostic.config(diagnostic_opts) 159 | end) 160 | 161 | now_if_args(function() -- tree sitter 162 | add({ 163 | source = "nvim-treesitter/nvim-treesitter", 164 | hooks = { 165 | post_checkout = function() 166 | vim.cmd("TSUpdate") 167 | end, 168 | }, 169 | }) 170 | 171 | require("nvim-treesitter.configs").setup({ 172 | ensure_installed = _G.Config.Languages.treesitter, 173 | indent = { enable = true }, 174 | highlight = { enable = true }, 175 | }) 176 | end) 177 | 178 | now(function() -- color scheme 179 | add({ source = "rebelot/kanagawa.nvim" }) 180 | add({ source = "projekt0n/github-nvim-theme" }) 181 | add({ source = "deparr/tairiki.nvim" }) -- tomorrow-night-like, light and dark versions 182 | require("mylib.persist_colorscheme").setup({ fallback = "miniautumn" }) 183 | end) 184 | 185 | now_if_args(function() -- guess-indent 186 | -- Detects indentation settings per file (spaces, tabs) 187 | add({ source = "NMAC427/guess-indent.nvim" }) 188 | require("guess-indent").setup() 189 | end) 190 | 191 | now(function() -- snacks: indent guides, dashboard 192 | add({ source = "folke/snacks.nvim" }) 193 | vim.g.snacks_animate = true 194 | 195 | vim.api.nvim_create_autocmd("VimEnter", { 196 | pattern = { "*" }, 197 | callback = function() 198 | local now = (vim.uv or vim.loop).hrtime() 199 | vim.g.loadtime = now - start 200 | end, 201 | }) 202 | 203 | require("snacks").dashboard.sections.cwd = function(opts) 204 | local cwd = vim.fn.fnamemodify(vim.fn.getcwd(), ":t") 205 | return { text = { cwd } } 206 | end 207 | 208 | require("snacks").dashboard.sections.startup = function(opts) 209 | local v = vim.version() 210 | local version = string.format("%d.%d.%d", v.major, v.minor, v.patch) 211 | local loadtime = vim.g.loadtime and string.format("%i ms", vim.g.loadtime / 1e6) or "" 212 | return { 213 | text = { 214 | { "Neovim " .. version, hl = "NonText" }, 215 | { " ", hl = "NonText" }, 216 | { "(" .. loadtime .. ")", hl = "NonText" }, 217 | }, 218 | } 219 | end 220 | 221 | local dashboard_opts = { 222 | formats = { 223 | key = function(item) 224 | return { { item.key, hl = "key" } } 225 | end, 226 | 227 | file = function(item, ctx) 228 | local cwd = vim.fn.fnamemodify(vim.fn.getcwd(), ":t") 229 | local fname = vim.fn.fnamemodify(item.file, ":~") 230 | 231 | -- strip cwd 232 | local cwd = vim.fn.fnamemodify(vim.fn.getcwd(), ":~") .. "/" 233 | fname = string.gsub(fname, cwd, "") 234 | 235 | fname = ctx.width and #fname > ctx.width and vim.fn.pathshorten(fname) or fname 236 | if #fname > ctx.width then 237 | local dir = vim.fn.fnamemodify(fname, ":h") 238 | local file = vim.fn.fnamemodify(fname, ":t") 239 | if dir and file then 240 | file = file:sub(-(ctx.width - #dir - 2)) 241 | fname = dir .. "/…" .. file 242 | end 243 | end 244 | local dir, file = fname:match("^(.*)/(.+)$") 245 | return dir and { { file, hl = "file" }, { " " .. dir .. "/", hl = "dir" } } 246 | or { { fname, hl = "file" } } 247 | end, 248 | }, 249 | width = 40, 250 | preset = { 251 | keys = { 252 | { action = ":ene", desc = "new file", key = "e" }, 253 | { action = ":lua require('persisted').load()", desc = "resume session", key = "r" }, 254 | { action = ":lua require('persisted').select()", desc = "load session…", key = "l" }, 255 | { action = ":DiffviewOpen", desc = "git status", key = "s" }, 256 | { action = ":q", desc = "quit", key = "q" }, 257 | }, 258 | }, 259 | sections = { 260 | { section = "cwd", padding = 1 }, 261 | { section = "recent_files", cwd = true, limit = 5, indent = 0, padding = 1 }, 262 | { section = "keys", indent = 0, padding = 1 }, 263 | { section = "startup", indent = 0, padding = 1 }, 264 | }, 265 | } 266 | 267 | require("snacks").setup({ 268 | dashboard = no_args and dashboard_opts or {}, 269 | input = { enabled = true }, -- for renames, etc 270 | -- indent = { enabled = true }, -- needs early setup 271 | picker = { enabled = true }, 272 | }) 273 | 274 | require("mini.indentscope").setup({ 275 | draw = { delay = 0, animation = require("mini.indentscope").gen_animation.none() }, 276 | symbol = "┊", 277 | }) 278 | end) 279 | 280 | -- Keymaps ------------------------------------------------------------------------------- 281 | 282 | later(function() -- keys, keymaps 283 | local function copy_git_link() 284 | local title = vim.fn.expand("%:.") 285 | local start_line = vim.fn.line("v") 286 | local end_line = vim.fn.line(".") 287 | if start_line == end_line then 288 | title = title .. "#L" .. start_line 289 | elseif start_line > end_line then 290 | title = title .. "#L" .. end_line .. "-" .. start_line 291 | else 292 | title = title .. "#L" .. start_line .. "-" .. end_line 293 | end 294 | 295 | Snacks.gitbrowse({ 296 | notify = false, 297 | open = function(url) 298 | local link = "[" .. title .. "](" .. url .. ")" 299 | vim.fn.setreg('"', link) 300 | vim.fn.setreg("+", link) 301 | vim.notify(" " .. link) 302 | end, 303 | }) 304 | end 305 | 306 | local function copy_git_url() 307 | Snacks.gitbrowse({ 308 | notify = false, 309 | open = function(url) 310 | vim.fn.setreg('"', url) 311 | vim.fn.setreg("+", url) 312 | vim.notify(" " .. url) 313 | end, 314 | }) 315 | end 316 | 317 | local function copy_path(opts) 318 | local str = vim.fn.expand(opts.expand) 319 | local start_line = vim.fn.line("v") 320 | local end_line = vim.fn.line(".") 321 | if opts and opts.range then 322 | if start_line == end_line then 323 | str = str .. "#L" .. start_line 324 | elseif start_line > end_line then 325 | str = str .. "#L" .. end_line .. "-" .. start_line 326 | else 327 | str = str .. "#L" .. start_line .. "-" .. end_line 328 | end 329 | end 330 | vim.fn.setreg('"', str) 331 | vim.fn.setreg("+", str) 332 | vim.notify(" " .. str) 333 | end 334 | 335 | local function copy_absolute_path() 336 | return copy_path({ expand = "%:p" }) 337 | end 338 | local function copy_absolute_path_range() 339 | return copy_path({ expand = "%:p", range = 1 }) 340 | end 341 | local function copy_relative_path() 342 | return copy_path({ expand = "%:." }) 343 | end 344 | local function copy_relative_path_range() 345 | return copy_path({ expand = "%:.", range = 1 }) 346 | end 347 | 348 | local function update_and_show_log() 349 | vim.cmd("DepsUpdate!") 350 | vim.cmd("DepsShowLog") 351 | end 352 | 353 | local function close_buffers_and_reset() 354 | Snacks.bufdelete.all() 355 | Snacks.dashboard.open() 356 | end 357 | 358 | -- Keymaps: see https://github.com/nvim-mini/MiniMax/blob/main/configs/nvim-0.11/plugin/20_keymaps.lua 359 | -- System clipboard 360 | vim.keymap.set("v", "", '"+y', { desc = "Copy to clipboard" }) 361 | vim.keymap.set("i", "", "+", { desc = "Paste from clipboard" }) 362 | 363 | -- Paste linewise before/after current line 364 | -- Usage: `yiw` to yank a word and `]p` to put it on the next line. 365 | vim.keymap.set("n", "[p", 'exe "put! " . v:register', { desc = "Paste above" }) 366 | vim.keymap.set("n", "]p", 'exe "put " . v:register', { desc = "Paste below" }) 367 | 368 | -- Make `23,` go to line 23. Easier to type than `23G` 369 | vim.keymap.set("n", ",", "G", { desc = "Go to line" }) 370 | vim.keymap.set("v", ",", "G", { desc = "Go to line" }) 371 | 372 | -- default keymaps for references, etc 373 | vim.keymap.del("n", "grt") 374 | vim.keymap.del("n", "gri") 375 | vim.keymap.del("n", "grr") 376 | vim.keymap.del("n", "gra") 377 | vim.keymap.del("n", "grn") 378 | 379 | -- Paste over currently selected text without yanking it 380 | vim.keymap.set("x", "p", '"_dP', { noremap = true, silent = true }) 381 | 382 | -- stylua: ignore start 383 | vim.keymap.set("n", "", function() Snacks.picker.git_files({ untracked = true }) end, { desc = "Open file in git..." }) 384 | vim.keymap.set("n", "", function() Snacks.picker.keymaps() end, { desc = "Open keymaps" }) 385 | 386 | vim.keymap.set("n", ",", function() Snacks.picker.buffers() end, { desc = "Switch buffer..." }) 387 | vim.keymap.set("n", "!s", "split ~/.scratchpad.mdH", { desc = "Open scratchpad" }) 388 | vim.keymap.set("n", "!c", "split CONTEXT.local.mdH", { desc = "Open context document" }) 389 | vim.keymap.set("n", "!g", function() vim.cmd("e " .. vim.fn.stdpath("config") .. "/etc/graveyard.lua") end, { desc = "Config: open config graveyard" }) 390 | table.insert(CLUES, { mode = "n", keys = "um", desc = "+dependencies" }) 391 | vim.keymap.set("n", "ums", "DepsSnapSave", { desc = "Deps: save snapshot" }) 392 | vim.keymap.set("n", "uml", "DepsSnapLoad", { desc = "Deps: load snapshot" }) 393 | vim.keymap.set("n", "umu", update_and_show_log, { desc = "Deps: update dependencies" }) 394 | vim.keymap.set("n", "cr", function() vim.lsp.buf.rename() end, { desc = "LSP: rename this..." }) 395 | vim.keymap.set("n", "e", function() Snacks.picker.explorer() end, { desc = "Open file browser (sidebar)" }) 396 | vim.keymap.set("n", "bo", function() Snacks.bufdelete.other() end, { desc = "Delete other buffers" }) 397 | vim.keymap.set("n", "qd", close_buffers_and_reset, { desc = "Delete all buffers and open dashboard" }) 398 | vim.keymap.set("n", "fp", function() Snacks.picker.projects() end, { desc = "Recent projects..." }) 399 | vim.keymap.set("n", "fr", function() Snacks.picker.recent({ hidden = true, filter = { cwd = true } }) end, { desc = "Recent files..." }) 400 | vim.keymap.set("n", "ff", function() Snacks.picker.files() end, { desc = "Open file..." }) 401 | vim.keymap.set("n", "gh", function() Snacks.gitbrowse() end, { desc = "Open GitHub in browser" }) 402 | vim.keymap.set("n", "gl", function() Snacks.picker.git_log_line() end, { desc = "Show git log for line" }) 403 | vim.keymap.set("n", "fyg", function() copy_git_url() end, { desc = "Copy: copy GitHub URL" }) 404 | vim.keymap.set("n", "fyG", function() copy_git_link() end, { desc = "Copy: copy GitHub link" }) 405 | vim.keymap.set("n", "fya", function() copy_absolute_path() end, { desc = "Copy: copy absolute path" }) 406 | vim.keymap.set("n", "fyr", function() copy_relative_path() end, { desc = "Copy: copy relative path" }) 407 | vim.keymap.set("n", "gs", function() Snacks.picker.git_status() end, { desc = "Files changed in Git (status)..." }) 408 | vim.keymap.set("n", "qq", "qa", { desc = "Close all and exit" }) 409 | vim.keymap.set("n", "sg", function() Snacks.picker.grep() end, { desc = "Search in files via grep..." }) 410 | vim.keymap.set("n", "sw", function() Snacks.picker.grep_word() end, { desc = "Search in files via grep for word..." }) 411 | vim.keymap.set("n", "sk", function() Snacks.picker.keymaps() end, { desc = "Open keymaps" }) 412 | vim.keymap.set("n", "ss", function() Snacks.picker.lsp_symbols() end, { desc = "LSP: show LSP symbols" }) 413 | vim.keymap.set("n", "s\"", function() Snacks.picker.registers() end, { desc = "Open registers" }) 414 | vim.keymap.set("n", "u,", function() vim.cmd("e " .. vim.fn.stdpath("config") .. "/init.lua") end, { desc = "Config: open settings" }) 415 | vim.keymap.set("n", "uC", function() Snacks.picker.colorschemes() end, { desc = "Change colorscheme" }) 416 | vim.keymap.set("n", "ux", function() Snacks.picker() end, { desc = "Snacks: choose picker" }) 417 | 418 | vim.keymap.set("v", "fyg", function() copy_git_link() end, { desc = "Copy: copy GitHub URL" }) 419 | vim.keymap.set("v", "fya", function() copy_absolute_path_range() end, { desc = "Copy: copy absolute path with line numbers" }) 420 | vim.keymap.set("v", "fyr", function() copy_relative_path_range() end, { desc = "Copy: copy relative path with line numbers" }) 421 | vim.keymap.set("v", "gh", function() Snacks.gitbrowse() end, { desc = "Open GitHub in browser" }) 422 | 423 | vim.keymap.set("x", "sw", function() Snacks.picker.grep_word() end, { desc = "Search in files via grep for word..." }) 424 | 425 | vim.keymap.set("n", "g.", function() vim.lsp.buf.code_action() end, { desc = "LSP: code action" }) 426 | vim.keymap.set("n", "gD", function() Snacks.picker.lsp_declarations() end, { desc = "LSP: go to declaration" }) 427 | vim.keymap.set("n", "gd", function() Snacks.picker.lsp_definitions() end, { desc = "LSP: go to definition" }) 428 | vim.keymap.set("n", "gI", function() Snacks.picker.lsp_implementations() end, { desc = "LSP: show implementation" }) 429 | vim.keymap.set("n", "gr", function() Snacks.picker.lsp_references() end, { desc = "LSP: show references" }) 430 | vim.keymap.set("n", "gy", function() Snacks.picker.lsp_type_definitions() end, { desc = "LSP: go to type definition" }) 431 | vim.keymap.set("n", "K", function() vim.lsp.buf.hover() end, { desc = "LSP: hover" }) 432 | 433 | vim.keymap.set("n", "", "bprevious", { desc = "Buffer: prev buffer" }) 434 | vim.keymap.set("n", "", "bnext", { desc = "Buffer: next buffer" }) 435 | 436 | -- Terminal escape 437 | vim.keymap.set("t", "", "") 438 | vim.keymap.set("t", "", "") 439 | -- stylua: ignore end 440 | end) 441 | 442 | later(function() -- terminal keymaps 443 | vim.keymap.set("n", "tn", "tabnew | term", { desc = "Terminal: new terminal tab" }) 444 | vim.keymap.set("n", "td", "tabclose", { desc = "Terminal: close current tab" }) 445 | end) 446 | 447 | -- Editing ------------------------------------------------------------------------------- 448 | 449 | later(function() -- editor: lsp features (blink, mason, lspconfig) 450 | add({ source = "Saghen/blink.cmp", checkout = "v1.6.0" }) 451 | add({ source = "mason-org/mason.nvim" }) 452 | add({ 453 | source = "neovim/nvim-lspconfig", 454 | depends = { "mason-org/mason.nvim", "saghen/blink.cmp" }, 455 | }) 456 | add({ 457 | source = "mason-org/mason-lspconfig.nvim", 458 | depends = { "mason-org/mason.nvim", "neovim/nvim-lspconfig" }, 459 | }) 460 | vim.lsp.config("vtsls", { 461 | settings = { 462 | typescript = { 463 | tsserver = { 464 | -- https://github.com/yioneko/vtsls/blob/175de18b59321d950cbc4c2c4cf55d5bb39b0675/README.md?plain=1#L123 465 | maxTsServerMemory = 8192, 466 | }, 467 | }, 468 | }, 469 | }) 470 | 471 | -- / - next or previous match 472 | -- - accept 473 | -- - accept 474 | require("blink.cmp").setup({ 475 | keymap = { 476 | -- -- to accept completions. To insert a new line instead, use 477 | -- -- or "] = { "accept", "fallback" }, 480 | 481 | preset = "super-tab", 482 | [""] = { 483 | function(cmp) 484 | if vim.b[vim.api.nvim_get_current_buf()].nes_state then 485 | cmp.hide() 486 | return ( 487 | require("copilot-lsp.nes").apply_pending_nes() 488 | and require("copilot-lsp.nes").walk_cursor_end_edit() 489 | ) 490 | end 491 | if cmp.snippet_active() then 492 | return cmp.accept() 493 | else 494 | return cmp.select_and_accept() 495 | end 496 | end, 497 | "snippet_forward", 498 | "fallback", 499 | }, 500 | }, 501 | 502 | -- Show documentation in completion 503 | completion = { documentation = { auto_show = true } }, 504 | 505 | -- Prefers native ("rust") but fallback to Lua implementation 506 | fuzzy = { implementation = "prefer_rust" }, 507 | 508 | -- show signature help when typing ( 509 | signature = { enabled = true }, 510 | 511 | sources = { 512 | default = { "lsp", "path", "snippets", "buffer", "copilot" }, 513 | providers = { 514 | copilot = { 515 | name = "copilot", 516 | module = "blink-copilot", 517 | score_offset = 100, 518 | async = true, 519 | }, 520 | }, 521 | }, 522 | }) 523 | 524 | -- Insert a newline without accepting completion. 525 | -- Useful for when completion popup is visible, but you need to start a new line. 526 | -- side effect is that it shows a blank space differently (placeholder) 527 | -- vim.keymap.set("i", "", "") 528 | 529 | -- Mason 530 | require("mason").setup({}) 531 | require("mason-lspconfig").setup({ ensure_installed = _G.Config.Languages.lsp }) 532 | 533 | -- Automatically pop up after `updatetime` milliseconds 534 | vim.api.nvim_create_autocmd("CursorHold", { 535 | callback = function() 536 | vim.diagnostic.open_float(nil, { focus = false }) 537 | end, 538 | }) 539 | 540 | -- Install Mason packages that aren't installed 541 | ---@param pkgs string[] 542 | local function mason_auto_install(pkgs) 543 | local mr = require("mason-registry") 544 | local pkgs_to_install = vim.tbl_filter(function(item) 545 | local pkg = mr.get_package(item) 546 | return pkg:is_installed() == false 547 | end, pkgs) 548 | 549 | if #pkgs_to_install ~= 0 then 550 | vim.cmd("MasonInstall " .. table.concat(pkgs_to_install, " ")) 551 | end 552 | end 553 | 554 | mason_auto_install(_G.Config.Languages.mason) 555 | 556 | -- Also see: 557 | -- 558 | -- * https://github.com/mason-org/mason.nvim?tab=readme-ov-file#configuration 559 | -- * https://neovim.io/doc/user/lsp.html#lsp-quickstart 560 | -- * https://github.com/neovim/nvim-lspconfig 561 | -- * https://www.lazyvim.org/extras/coding/blink 562 | end) 563 | 564 | later(function() -- editor: linting 565 | add({ 566 | source = "mfussenegger/nvim-lint", 567 | }) 568 | require("lint").linters_by_ft = _G.Config.Languages.linters_by_ft 569 | vim.api.nvim_create_autocmd({ "BufWritePost", "BufReadPost", "InsertLeave" }, { 570 | callback = function() 571 | require("lint").try_lint() 572 | end, 573 | }) 574 | end) 575 | 576 | later(function() -- editor: formatting 577 | add({ source = "stevearc/conform.nvim" }) 578 | require("conform").setup({ 579 | formatters_by_ft = _G.Config.Languages.formatters_by_ft, 580 | format_on_save = { 581 | -- These options will be passed to conform.format() 582 | timeout_ms = 500, 583 | lsp_format = "fallback", 584 | }, 585 | formatters = { 586 | prettier = { 587 | require_cwd = true, 588 | }, 589 | biome = { 590 | require_cwd = true, 591 | }, 592 | eslint = { 593 | require_cwd = true, 594 | }, 595 | }, 596 | }) 597 | 598 | -- stylua: ignore start 599 | vim.keymap.set("n", "cf", function() require("conform").format() end, { desc = "Format" }) 600 | -- stylua: ignore end 601 | 602 | vim.o.formatexpr = "v:lua.require'conform'.formatexpr()" 603 | 604 | vim.keymap.set("n", "!df", "ConformInfo", { desc = "Debug: conform formatter info" }) 605 | vim.keymap.set("n", "!dl", "LspInfo", { desc = "Debug: show lsp info" }) 606 | 607 | -- Also see: 608 | -- 609 | -- * https://github.com/stevearc/conform.nvim?tab=readme-ov-file#setup 610 | -- * https://www.lazyvim.org/plugins/formatting 611 | end) 612 | 613 | later(function() -- various-textobjs: vaq and more 614 | -- vaq - select all in quotes " ' ` 615 | -- vab - select all in brackets ( [ { < 616 | add({ source = "chrisgrieser/nvim-various-textobjs" }) 617 | require("various-textobjs").setup({}) 618 | end) 619 | 620 | later(function() -- treesitter-context 621 | add({ source = "nvim-treesitter/nvim-treesitter-context" }) 622 | require("treesitter-context").setup({ mode = "topline", max_lines = 5 }) 623 | 624 | -- Jump to context parent 625 | -- Also consider [t (treesitter parent) 626 | vim.keymap.set("n", "[t", function() 627 | require("treesitter-context").go_to_context(vim.v.count1) 628 | end, { desc = "Treesitter-context: Jump to context", silent = true }) 629 | 630 | -- Also see: 631 | -- https://github.com/nvim-treesitter/nvim-treesitter-context?tab=readme-ov-file#configuration 632 | end) 633 | 634 | -- UI ------------------------------------------------------------------------------------ 635 | 636 | now_if_args(function() -- mini.statusline 637 | local statusline = require("mini.statusline") 638 | 639 | local function active() 640 | local mode, mode_hl = statusline.section_mode({ trunc_width = 2000 }) 641 | local diagnostics = statusline.section_diagnostics({ trunc_width = 75 }) 642 | local lsp = statusline.section_lsp({ trunc_width = 75 }) 643 | local filename = statusline.section_filename({ trunc_width = 12 }) 644 | local search = statusline.section_searchcount({ trunc_width = 75 }) 645 | 646 | return statusline.combine_groups({ 647 | { hl = mode_hl, strings = { mode } }, 648 | "%<", -- Mark general truncate point 649 | { hl = "MiniStatuslineFilename", strings = { filename } }, 650 | "%=", -- End left alignment 651 | { hl = "MiniStatuslineInactive", strings = { diagnostics, lsp } }, 652 | { hl = "MiniStatuslineFileinfo", strings = { search } }, 653 | { hl = "MiniStatuslineInactive", strings = { "%2l:%-2v" } }, 654 | { hl = mode_hl, strings = { " " } }, 655 | }) 656 | end 657 | statusline.setup({ content = { active = active } }) 658 | 659 | -- Restore status line that was hidden earlier 660 | ---@param value number 661 | local function defer_laststatus_update_on_insert(value) 662 | local group = vim.api.nvim_create_augroup("restore", { clear = true }) 663 | vim.api.nvim_create_autocmd("InsertEnter", { 664 | group = group, 665 | callback = function() 666 | vim.opt.laststatus = value 667 | vim.api.nvim_del_augroup_by_id(group) 668 | end, 669 | }) 670 | end 671 | 672 | -- Show status line immediately when starting with a file 673 | if vim.fn.argc(-1) > 0 then 674 | vim.opt.laststatus = 2 675 | else 676 | defer_laststatus_update_on_insert(2) 677 | end 678 | end) 679 | 680 | later(function() -- mini.notify: toast notifications 681 | local notify = require("mini.notify") 682 | notify.setup({ 683 | window = { 684 | config = { 685 | anchor = "SE", 686 | col = vim.o.columns, 687 | row = vim.o.lines - 2, 688 | border = "rounded", 689 | }, 690 | }, 691 | content = { 692 | format = function(notif) 693 | -- Don't prepent timestamp 694 | return string.format("%s ", notif.msg) 695 | end, 696 | }, 697 | }) 698 | vim.notify = notify.make_notify({}) 699 | 700 | vim.keymap.set("n", "snh", function() 701 | notify.show_history() 702 | end, { desc = "Show notification history" }) 703 | 704 | -- https://github.com/nvim-mini/mini.notify 705 | end) 706 | 707 | later(function() -- mylib.autosize: resize window widths 708 | require("mylib.autosize").setup() 709 | end) 710 | 711 | later(function() -- trouble: diagnostics 712 | add({ source = "folke/trouble.nvim" }) 713 | require("trouble").setup({}) 714 | 715 | -- stylua: ignore start 716 | vim.keymap.set("n", "xx", "Trouble diagnostics toggle", { desc = "Show diagnostics" }) 717 | -- stylua: ignore end 718 | end) 719 | 720 | later(function() -- mini.files 721 | local MiniFiles = require("mini.files") 722 | MiniFiles.setup({ 723 | mappings = { 724 | go_out = "", -- default `h` 725 | go_in = "", -- default `l` 726 | go_in_plus = "", -- open (closes mini.files when used one a file) 727 | synchronize = "", -- save changes (default `=`) 728 | help = "?", -- default `g?` 729 | -- also: reveal_cwd (`@`) 730 | }, 731 | windows = { 732 | preview = true, 733 | width_nofocus = 12, 734 | width_focus = 40, 735 | width_preview = 40, 736 | }, 737 | }) 738 | 739 | local function explore_from_here() 740 | MiniFiles.open(vim.api.nvim_buf_get_name(0), false) 741 | MiniFiles.reveal_cwd() 742 | end 743 | 744 | -- stylua: ignore start 745 | vim.keymap.set("n", "-", function() explore_from_here() end, { desc = "Open file browser (mini)" }) 746 | -- stylua: ignore end 747 | end) 748 | 749 | -- Git ----------------------------------------------------------------------------------- 750 | 751 | later(function() -- diffview 752 | add({ 753 | source = "sindrets/diffview.nvim", 754 | }) 755 | 756 | -- stylua: ignore start 757 | vim.keymap.set("n", "gd", "DiffviewOpen", { desc = "Show diff" }) 758 | vim.keymap.set("n", "gD", "DiffviewOpen main...HEAD", { desc = "Show diff for branch" }) 759 | -- Within the view: 760 | -- cA - choose all 761 | -- cB/cO/cT - choose base / ours / theirs 762 | -- dX - delete conflict region 763 | -- [x ]x - next conflict 764 | -- L - open commit log panel 765 | -- 766 | -- s / - - stage or unstage 767 | -- S - stage all 768 | -- gf - open in previous tab 769 | -- - open in split 770 | -- gf - open in new tab 771 | -- 772 | -- stylua: ignore end 773 | end) 774 | 775 | later(function() -- blame 776 | add({ source = "FabijanZulj/blame.nvim" }) 777 | require("blame").setup({ blame_options = { "-w" } }) 778 | vim.keymap.set("n", "gb", "BlameToggle window", { desc = "Show git blame (window)" }) 779 | -- vim.keymap.set("n", "gB", "BlameToggle virtual", { desc = "Show git blame (virtual)" }) 780 | end) 781 | 782 | -- Markdown ------------------------------------------------------------------------------ 783 | 784 | later(function() -- render-markdown 785 | add({ source = "MeanderingProgrammer/render-markdown.nvim" }) 786 | require("render-markdown").setup({ 787 | render_modes = { "n", "v", "i", "c" }, 788 | heading = { 789 | icons = { "━ " }, 790 | signs = { "󰎤 ", "󰎩 ", "󰎬 ", "󰎮 ", "󰎰 ", "󰎵 " }, 791 | }, 792 | 793 | code = { 794 | sign = false, 795 | style = "normal", 796 | width = "block", 797 | position = "right", 798 | right_pad = 5, 799 | border = "thick", 800 | }, 801 | 802 | bullet = { 803 | enabled = true, 804 | icons = { "─", "─", "─", "─" }, -- default: { '●', '○', '◆', '◇' }, 805 | highlight = "DiagnosticInfo", -- 'RenderMarkdownBullet', 806 | }, 807 | 808 | checkbox = { 809 | unchecked = { icon = "□" }, 810 | checked = { icon = "󰸞", highlight = "DiagnosticOk" }, -- nf-md-check-bold 811 | custom = { 812 | wait = { raw = "[-]", rendered = "󰥔", highlight = "RenderMarkdownTodo" }, 813 | prio = { raw = "[!]", rendered = "󰄰", highlight = "DiagnosticError" }, -- high priority 814 | done = { raw = "[x]", rendered = "󰸞", highlight = "DiagnosticOk" }, 815 | fwd = { raw = "[>]", rendered = "󰒊", highlight = "Comment" }, -- nf-md-send 816 | sched = { raw = "[<]", rendered = "󰃰", highlight = "Comment" }, -- nf-md-calendar_clock 817 | cancel = { raw = "[~]", rendered = "󰏤", highlight = "DiagnosticWarn" }, 818 | info = { raw = "[i]", rendered = "󰋼", highlight = "DiagnosticInfo" }, -- nf-md-information -- `i` in obsidian 819 | idea = { raw = "[l]", rendered = "󰌵", highlight = "DiagnosticWarn" }, -- nf-md-lightbulb -- `I` in obsidian 820 | pro = { raw = "[p]", rendered = "󰔓", highlight = "DiagnosticOk" }, -- nf-md-thumb_up 821 | con = { raw = "[c]", rendered = "󰔑", highlight = "DiagnosticError" }, -- nf-md-thumb_down 822 | star = { raw = "[s]", rendered = "󰓎", highlight = "DiagnosticWarn" }, -- nf-md-star (asterisk * doesn't work) 823 | star2 = { raw = "[*]", rendered = "󰓎", highlight = "DiagnosticWarn" }, -- nf-md-star (asterisk * doesn't work) 824 | bookmark = { raw = "[b]", rendered = "󰃀", highlight = "DiagnosticWarn" }, -- nf-md-star (asterisk * doesn't work) 825 | half = { raw = "[/]", rendered = "󰿦", highlight = "Comment" }, -- in progress, nf-md-texture_box 826 | }, 827 | }, 828 | 829 | callout = { 830 | highlights = { raw = "[!HIGHLIGHTS]", rendered = "󰌶 Highlights ", highlight = "RenderMarkdownHint" }, 831 | tldr = { raw = "[!TLDR]", rendered = "󰌶 TLDR ", highlight = "RenderMarkdownHint" }, 832 | summary = { raw = "[!SUMMARY]", rendered = "󰌶 Summary ", highlight = "RenderMarkdownHint" }, 833 | }, 834 | 835 | link = { 836 | -- Fallback icon for 'inline_link' elements 837 | -- hyperlink = "󰌹 ", 838 | hyperlink = "", 839 | 840 | -- image = "󰥶 ", -- Inlined with 'image' elements 841 | -- email = "󰀓 ", -- Inlined with 'email_autolink' elements 842 | -- hyperlink = "󰌹 ", -- Fallback icon for 'inline_link' elements 843 | -- wiki = { icon = "󱗖 ", highlight = "RenderMarkdownWikiLink" }, 844 | wiki = { icon = "", highlight = "RenderMarkdownLink" }, 845 | 846 | custom = { 847 | -- web = { pattern = "^http[s]?://", icon = "󰖟 ", highlight = "RenderMarkdownLink" }, 848 | -- stylua: ignore start 849 | jira = { pattern = "^http[s]?://%a+.atlassian.net/browse", icon = "󰌃 ", highlight = "RenderMarkdownLink" }, 850 | conf = { pattern = "^http[s]?://%a+.atlassian.net/wiki", icon = " ", highlight = "RenderMarkdownLink" }, -- nf-fa-confluence 851 | slack = { pattern = "^http[s]?://%a+.slack.com", icon = "󰒱 ", highlight = "RenderMarkdownLink" }, -- nf-md-slack 852 | github = { pattern = "^http[s]?://github.com", icon = "󰊤 ", highlight = "RenderMarkdownLink" }, -- nf-md-github + ctrl-k 1M 853 | gitlab = { pattern = "^http[s]?://gitlab.com", icon = " ", highlight = "RenderMarkdownLink" }, -- nf-fa-gitlab 854 | trello = { pattern = "^http[s]?://trello.com", icon = "󰔲 ", highlight = "RenderMarkdownLink" }, 855 | miro = { pattern = "^http[s]?://miro.com", icon = "󰃥 ", highlight = "RenderMarkdownLink" }, 856 | datadog = { pattern = "^http[s]?://%a+.datadoghq.com", icon = "󰩃 ", highlight = "RenderMarkdownLink" }, 857 | figma = { pattern = "^http[s]?://%a+.figma.com", icon = " ", highlight = "RenderMarkdownLink" }, 858 | notion = { pattern = "^http[s]?://%a+.notion.so", icon = " ", highlight = "RenderMarkdownLink" }, 859 | googledrive = { pattern = "^http[s]?://drive.google.com", icon = "󰊶 ", highlight = "RenderMarkdownLink", }, 860 | web = { pattern = "^http[s]?://", icon = "󰏌 ", highlight = "RenderMarkdownLink" }, -- nf-md-open_in_new + ctrl-k 1M 861 | -- stylua: ignore end 862 | }, 863 | }, 864 | }) 865 | end) 866 | 867 | later(function() -- obsidian 868 | -- vaq - select all in quotes " ' ` 869 | -- vab - select all in brackets ( [ { < 870 | add({ 871 | source = "obsidian-nvim/obsidian.nvim", 872 | depends = { "nvim-lua/plenary.nvim" }, 873 | }) 874 | 875 | local Obsidian = require("mylib/obsidian") 876 | require("obsidian").setup(Obsidian.get_obsidian_options()) 877 | 878 | vim.api.nvim_create_autocmd("FileType", { 879 | group = vim.api.nvim_create_augroup("obsidian_keybindings", { clear = true }), 880 | pattern = { "markdown" }, 881 | callback = function(event) 882 | Obsidian.bind_keys(event.buf) 883 | end, 884 | }) 885 | end) 886 | 887 | -- AI ------------------------------------------------------------------------------------ 888 | 889 | later(function() -- copilot 890 | add({ source = "fang2hou/blink-copilot" }) 891 | add({ source = "copilotlsp-nvim/copilot-lsp" }) 892 | 893 | vim.g.copilot_nes_debounce = 500 894 | vim.lsp.enable("copilot_ls") 895 | vim.keymap.set("n", "", function() 896 | local bufnr = vim.api.nvim_get_current_buf() 897 | local state = vim.b[bufnr].nes_state 898 | if state then 899 | -- Try to jump to the start of the suggestion edit. 900 | -- If already at the start, then apply the pending suggestion and jump to the end of the edit. 901 | local _ = require("copilot-lsp.nes").walk_cursor_start_edit() 902 | or ( 903 | require("copilot-lsp.nes").apply_pending_nes() 904 | and require("copilot-lsp.nes").walk_cursor_end_edit() 905 | ) 906 | return nil 907 | else 908 | -- Resolving the terminal's inability to distinguish between `TAB` and `` in normal mode 909 | return "" 910 | end 911 | end, { desc = "Accept Copilot NES suggestion", expr = true }) 912 | 913 | -- add({ source = "zbirenbaum/copilot.lua" }) 914 | -- require("copilot").setup({ 915 | -- suggestion = { 916 | -- enabled = true, 917 | -- auto_trigger = true, 918 | -- keymap = { accept = "" }, 919 | -- }, 920 | -- nes = { 921 | -- enabled = true, 922 | -- keymap = { accept_and_goto = "", accept = false, dismiss = "" }, 923 | -- }, 924 | -- panel = { auto_refresh = true }, 925 | -- filetypes = { markdown = true }, 926 | -- }) 927 | 928 | vim.keymap.set("n", "!as", "Copilot panel", { desc = "Open Copilot suggestions panel" }) 929 | end) 930 | 931 | later(function() -- sidekick.nvim 932 | add({ source = "folke/sidekick.nvim" }) 933 | require("sidekick").setup({ 934 | nes = { 935 | enabled = false, 936 | }, 937 | cli = { 938 | mux = { 939 | enable = true, 940 | backend = "tmux", 941 | }, 942 | }, 943 | }) 944 | -- stylua: ignore start 945 | vim.keymap.set("n", "!sf", function() require("sidekick.cli").toggle({ focus = true }) end, { desc = "Sidekick: focus" }) 946 | vim.keymap.set("n", "ot", function() require("sidekick.cli").toggle({ name = "opencode", focus = true }) end, { desc = "Sidekick: toggle opencode" }) 947 | vim.keymap.set("n", "o.", function() require("sidekick.cli").send({ name = "opencode", msg = "{this}" }) end, { desc = "Sidekick: send this" }) 948 | vim.keymap.set("n", "of", function() require("sidekick.cli").send({ name = "opencode", msg = "{file}" }) end, { desc = "Sidekick: send file" }) 949 | vim.keymap.set("v", "o.", function() require("sidekick.cli").send({ name = "opencode", msg = "{selection}" }) end, { desc = "Sidekick: send visual select" }) 950 | -- stylua: ignore end 951 | end) 952 | 953 | -- Mini ---------------------------------------------------------------------------------- 954 | 955 | later(function() -- mini.clue: shows keyboard shortcuts 956 | local miniclue = require("mini.clue") 957 | miniclue.setup({ 958 | triggers = { 959 | -- Leader triggers 960 | { mode = "n", keys = "" }, 961 | { mode = "x", keys = "" }, 962 | -- Built-in completion 963 | { mode = "i", keys = "" }, 964 | -- jump 965 | { mode = "n", keys = "[" }, 966 | { mode = "n", keys = "]" }, 967 | -- `g` key 968 | { mode = "n", keys = "g" }, 969 | { mode = "x", keys = "g" }, 970 | -- Marks 971 | { mode = "n", keys = "'" }, 972 | { mode = "n", keys = "`" }, 973 | { mode = "x", keys = "'" }, 974 | { mode = "x", keys = "`" }, 975 | -- Registers 976 | { mode = "n", keys = '"' }, 977 | { mode = "x", keys = '"' }, 978 | { mode = "i", keys = "" }, 979 | { mode = "c", keys = "" }, 980 | -- Window commands 981 | { mode = "n", keys = "" }, 982 | -- `z` key 983 | { mode = "n", keys = "z" }, 984 | { mode = "x", keys = "z" }, 985 | }, 986 | window = { 987 | delay = 150, 988 | config = { width = 30 }, 989 | }, 990 | 991 | clues = { 992 | { mode = "n", keys = "u", desc = "+settings" }, 993 | { mode = "n", keys = "s", desc = "+search" }, 994 | { mode = "n", keys = "c", desc = "+code" }, 995 | { mode = "n", keys = "x", desc = "+diagnostics" }, 996 | { mode = "n", keys = "g", desc = "+git" }, 997 | { mode = "n", keys = "b", desc = "+buffer" }, 998 | { mode = "n", keys = "f", desc = "+file" }, 999 | { mode = "n", keys = "!", desc = "+experimental" }, 1000 | { mode = "n", keys = "!d", desc = "+debug" }, 1001 | { mode = "n", keys = "q", desc = "+quit" }, 1002 | -- Enhance this by adding descriptions for mapping groups 1003 | miniclue.gen_clues.builtin_completion(), 1004 | miniclue.gen_clues.g(), 1005 | miniclue.gen_clues.marks(), 1006 | miniclue.gen_clues.registers(), 1007 | miniclue.gen_clues.windows(), 1008 | miniclue.gen_clues.z(), 1009 | CLUES, 1010 | }, 1011 | }) 1012 | end) 1013 | 1014 | later(function() -- mini.etc 1015 | -- Better Around/Inside textobjects 1016 | -- 1017 | -- Examples 1018 | -- - va) - [V]isually select [A]round [)]paren 1019 | -- - yinq - [Y]ank [I]nside [N]ext [Q]uote 1020 | -- - ci' - [C]hange [I]nside [']quote 1021 | -- require("mini.ai").setup() 1022 | 1023 | -- Add/delete/replace surroundings (brackets, quotes, etc.) 1024 | -- 1025 | -- - sa) - surround add 1026 | -- - sd' - surround delete 1027 | -- - sr)} - surround replace 1028 | -- - saiw) - [S]urround [A]dd [I]nner [W]ord [)]Paren 1029 | require("mini.surround").setup({ 1030 | custom_surroundings = { 1031 | -- Markdown strong (visual select -> sas) 1032 | ["s"] = { output = { left = "**", right = "**" } }, 1033 | -- Markdown emphasis (visual select -> sae) 1034 | ["e"] = { output = { left = "_", right = "_" } }, 1035 | }, 1036 | }) 1037 | 1038 | -- [c ]c - next comment 1039 | -- [d ]d - next diagnostic 1040 | -- [i ]i - next nndent change 1041 | -- [h ]h - next Git hunk 1042 | -- [q ]q - next quickfix file 1043 | -- [l ]l - next loclist file 1044 | -- [t ]t - next treesitter node (eg, parent block) 1045 | require("mini.bracketed").setup() 1046 | 1047 | -- gS - toggle between one-line and multi-line for function arguments, tables, etc. 1048 | require("mini.splitjoin").setup() 1049 | 1050 | -- highlight word under cursor 1051 | require("mini.cursorword").setup() 1052 | 1053 | require("mini.git").setup() 1054 | require("mini.icons").setup() 1055 | require("mini.diff").setup() 1056 | end) 1057 | 1058 | later(function() -- marks 1059 | add({ source = "chentoast/marks.nvim" }) 1060 | 1061 | -- marks: highlights marks in the signcolumn, and shows a list of marks 1062 | -- similar to harpoon 1063 | require("marks").setup({}) 1064 | table.insert(CLUES, { mode = "n", keys = "m", desc = "+marks" }) 1065 | vim.keymap.set("n", "ml", "MarksListAll", { desc = "Marks: list all marks" }) 1066 | vim.keymap.set("n", "mb", "BookmarksListAll", { desc = "Marks: list bookmarks" }) 1067 | vim.keymap.set("n", "mx", "delmarks!", { desc = "Marks: delete all marks" }) 1068 | end) 1069 | 1070 | later(function() -- persistence 1071 | add({ source = "olimorris/persisted.nvim" }) 1072 | local persisted = require("persisted") 1073 | 1074 | -- if you invoke Neovim from a sub-directory then the git branch will not be 1075 | -- detected. This fixes that: 1076 | persisted.branch = function() 1077 | local branch = vim.fn.systemlist("git branch --show-current")[1] 1078 | return vim.v.shell_error == 0 and branch or nil 1079 | end 1080 | 1081 | persisted.setup({ 1082 | autostart = true, 1083 | follow_cwd = true, 1084 | use_git_branch = false, 1085 | save_dir = vim.fn.stdpath("data") .. "/sessions/", 1086 | should_save = function() -- equivalent to need = 0 (always save) 1087 | return true 1088 | end, 1089 | }) 1090 | 1091 | -- stylua: ignore start 1092 | vim.keymap.set("n", "ql", function() require("persisted").save(); require("persisted").select() end, { desc = "Session: load new..." }) 1093 | vim.keymap.set("n", "qL", function() require("persisted").load({ last = true }) end, { desc = "Session: load last session" }) 1094 | vim.keymap.set("n", "!qs", function() require("persisted").load() end, { desc = "Session: load current session" }) 1095 | vim.keymap.set("n", "!qd", function() require("persisted").stop() end, { desc = "Session: stop persistence" }) 1096 | -- stylua: ignore end 1097 | end) 1098 | 1099 | later(function() -- flash 1100 | add({ source = "folke/flash.nvim" }) 1101 | local flash = require("flash") 1102 | flash.setup({ 1103 | modes = { 1104 | -- when using `/` or `?` 1105 | search = { highlight = { backdrop = false } }, 1106 | -- when using f F t T ; , 1107 | char = { highlight = { backdrop = false } }, 1108 | }, 1109 | }) 1110 | vim.keymap.set("n", "S", flash.jump, { desc = "Flash: jump to" }) 1111 | vim.keymap.set("n", "bt", flash.treesitter, { desc = "Flash: select treesitter node" }) 1112 | end) 1113 | 1114 | later(function() -- chezmoi 1115 | require("mylib.chezmoi_auto_apply").setup() 1116 | end) 1117 | 1118 | now(function() -- flatten: allow `nvim` in terminal 1119 | add({ source = "willothy/flatten.nvim" }) 1120 | local flatten = require("flatten") 1121 | flatten.setup({}) 1122 | end) 1123 | 1124 | -- later(function() -- smear-cursor 1125 | -- if not vim.g.neovide then 1126 | -- add({ source = "sphamba/smear-cursor.nvim" }) 1127 | -- require("smear_cursor").setup({ 1128 | -- 1129 | -- cursor_color = "#ff8060", 1130 | -- -- particles_enabled = true, 1131 | -- -- stiffness = 0.5, 1132 | -- -- trailing_stiffness = 0.2, 1133 | -- -- trailing_exponent = 5, 1134 | -- -- damping = 0.6, 1135 | -- -- gradient_exponent = 0, 1136 | -- -- gamma = 1, 1137 | -- -- never_draw_over_target = true, -- if you want to actually see under the cursor 1138 | -- -- hide_target_hack = true, -- same 1139 | -- -- particle_spread = 1, 1140 | -- -- particles_per_second = 500, 1141 | -- -- particles_per_length = 50, 1142 | -- -- particle_max_lifetime = 800, 1143 | -- -- particle_max_initial_velocity = 20, 1144 | -- -- particle_velocity_from_cursor = 0.5, 1145 | -- -- particle_damping = 0.15, 1146 | -- -- particle_gravity = -50, 1147 | -- -- min_distance_emit_particles = 0, 1148 | -- }) 1149 | -- end 1150 | -- end) 1151 | 1152 | -- later(function() -- mini.animate 1153 | -- if not vim.g.neovide then 1154 | -- local animate = require("mini.animate") 1155 | -- local fast = animate.gen_timing.cubic({ duration = 60, unit = "total" }) 1156 | -- local xfast = animate.gen_timing.cubic({ duration = 20, unit = "total" }) 1157 | -- 1158 | -- animate.setup({ 1159 | -- cursor = { enable = false }, 1160 | -- scroll = { timing = fast }, 1161 | -- resize = { timing = xfast }, 1162 | -- }) 1163 | -- end 1164 | -- end) 1165 | 1166 | later(function() 1167 | if vim.env.UPDATE_DEPS then 1168 | vim.cmd("DepsUpdate! | DepsShowLog") 1169 | end 1170 | end) 1171 | --------------------------------------------------------------------------------