├── .emmyrc.json ├── .gitignore ├── README.md ├── after ├── ftplugin │ ├── asm.lua │ ├── c.lua │ ├── go.lua │ ├── javascript.lua │ ├── json.lua │ ├── just.lua │ ├── lua.lua │ ├── make.lua │ ├── markdown.lua │ ├── rust.lua │ ├── scss.lua │ ├── terminal.lua │ ├── text.lua │ └── zig.lua ├── indent │ └── rust.vim └── queries │ └── lua │ └── field.scm ├── colors └── minimus.lua ├── init.lua ├── lazy-lock.json ├── lsp ├── _basedpyright.lua ├── _bashls.lua ├── _biome.lua ├── _bufls.lua ├── _clangd.lua ├── _cmake.lua ├── _dockerls.lua ├── _emmylua_ls.lua ├── _eslint.lua ├── _gleam.lua ├── _gopls.lua ├── _intelephense.lua ├── _jsonls.lua ├── _lua_ls.lua ├── _prismals.lua ├── _rust_analyzer.lua ├── _tailwindcss.lua ├── _taplo.lua ├── _ts_ls.lua └── _zls.lua ├── lua ├── configs │ ├── editor │ │ ├── oil.lua │ │ ├── otter.lua │ │ ├── trouble.lua │ │ └── wezterm.lua │ ├── git │ │ ├── git-conflict.lua │ │ ├── gitsigns.lua │ │ └── neogit.lua │ ├── navigation │ │ └── flash.lua │ ├── status │ │ ├── cokeline.lua │ │ ├── dropbar.lua │ │ ├── heirline.lua │ │ └── incline.lua │ ├── terminal │ │ ├── flatten.lua │ │ └── toggleterm.lua │ ├── ui │ │ ├── edgy.lua │ │ ├── icons.lua │ │ ├── murmur.lua │ │ └── noice.lua │ └── windows │ │ ├── bufresize.lua │ │ ├── focus.lua │ │ ├── mini-animate.lua │ │ ├── smart-splits.lua │ │ └── window-picker.lua ├── plugins │ ├── editor.lua │ ├── fun.lua │ ├── libraries.lua │ ├── lsp.lua │ ├── ui.lua │ └── util.lua ├── resession │ └── extensions │ │ └── edgy.lua └── willothy │ ├── ai.lua │ ├── autocmds.lua │ ├── commands.lua │ ├── completion.lua │ ├── debugging.lua │ ├── diagnostics.lua │ ├── formatting.lua │ ├── keymap.lua │ ├── lib │ ├── 1password.lua │ ├── command.lua │ ├── event.lua │ ├── fn.lua │ ├── fs.lua │ ├── hl.lua │ ├── http.lua │ ├── import-graph.lua │ ├── llm.lua │ ├── reactive.lua │ ├── tab.lua │ ├── theme.lua │ ├── trie.lua │ └── win.lua │ ├── linting.lua │ ├── lsp │ ├── capabilities.lua │ └── init.lua │ ├── macros.lua │ ├── sessions.lua │ ├── settings.lua │ ├── tasks.lua │ ├── terminal.lua │ ├── testing.lua │ ├── treesitter.lua │ └── ui │ ├── code_actions.lua │ ├── cursor.lua │ ├── diagnostics.lua │ ├── foldexpr.lua │ ├── icons.lua │ ├── intro.lua │ ├── mode.lua │ ├── scrollbar │ ├── geometry.lua │ ├── init.lua │ └── win.lua │ └── scrolleof.lua ├── queries ├── css │ └── highlights.scm ├── go │ ├── folds.scm │ ├── highlights.scm │ ├── indents.scm │ ├── injections.scm │ └── locals.scm ├── lua │ └── injections.scm └── rust │ └── injections.scm └── stylua.toml /.emmyrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "runtime": { 3 | "version": "LuaJIT" 4 | }, 5 | "workspace": { 6 | "library": [ 7 | "$VIMRUNTIME" 8 | ] 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | tags 2 | importgraph.dot 3 | .repro 4 | repro.lua 5 | Cargo.lock 6 | target/ 7 | lua/sidecar.so 8 | lua/plugins/nix.lua 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | : 2 |
3 |

nvim-config

4 |

My personal Neovim config

5 |
6 | 7 | I would not recommend using this - It's setup for me, 8 | I cannot guarantee that it will work for you, and I will not fix issues that 9 | do not occur on my computers. However, feel free to copy paste snippets 10 | to add to your own config :) 11 | 12 | > **Note**
13 | > Requires Neovim Nightly 14 | -------------------------------------------------------------------------------- /after/ftplugin/asm.lua: -------------------------------------------------------------------------------- 1 | local bo = vim.bo 2 | local wo = vim.wo 3 | 4 | bo.tabstop = 2 5 | bo.shiftwidth = 2 6 | bo.softtabstop = -1 7 | bo.expandtab = true 8 | bo.smartindent = false 9 | 10 | wo.wrap = false 11 | -------------------------------------------------------------------------------- /after/ftplugin/c.lua: -------------------------------------------------------------------------------- 1 | local bo = vim.bo 2 | local wo = vim.wo 3 | 4 | bo.tabstop = 2 5 | bo.shiftwidth = 2 6 | bo.softtabstop = -1 7 | bo.expandtab = true 8 | bo.smartindent = false 9 | 10 | wo.wrap = false 11 | wo.number = true 12 | wo.relativenumber = true 13 | -------------------------------------------------------------------------------- /after/ftplugin/go.lua: -------------------------------------------------------------------------------- 1 | local bo = vim.bo 2 | local wo = vim.wo 3 | 4 | bo.tabstop = 2 5 | bo.shiftwidth = 2 6 | bo.softtabstop = -1 7 | bo.expandtab = true 8 | bo.smartindent = false 9 | 10 | wo.wrap = false 11 | vim.bo.syntax = "go" 12 | -------------------------------------------------------------------------------- /after/ftplugin/javascript.lua: -------------------------------------------------------------------------------- 1 | 2 | local bo = vim.bo 3 | local wo = vim.wo 4 | 5 | bo.tabstop = 2 6 | bo.shiftwidth = 2 7 | bo.softtabstop = -1 8 | bo.expandtab = true 9 | bo.smartindent = false 10 | 11 | wo.wrap = false 12 | wo.number = true 13 | wo.relativenumber = true 14 | -------------------------------------------------------------------------------- /after/ftplugin/json.lua: -------------------------------------------------------------------------------- 1 | local bo = vim.bo 2 | local wo = vim.wo 3 | 4 | bo.tabstop = 2 5 | bo.shiftwidth = 2 6 | bo.softtabstop = -1 7 | bo.expandtab = true 8 | bo.smartindent = false 9 | 10 | wo.wrap = false 11 | wo.number = true 12 | wo.relativenumber = true 13 | -------------------------------------------------------------------------------- /after/ftplugin/just.lua: -------------------------------------------------------------------------------- 1 | local bo = vim.bo 2 | local wo = vim.wo 3 | 4 | bo.tabstop = 2 5 | bo.shiftwidth = 2 6 | bo.softtabstop = -1 7 | bo.expandtab = true 8 | bo.smartindent = false 9 | 10 | wo.wrap = false 11 | wo.number = true 12 | wo.relativenumber = true 13 | -------------------------------------------------------------------------------- /after/ftplugin/lua.lua: -------------------------------------------------------------------------------- 1 | local bo = vim.bo 2 | local wo = vim.wo 3 | 4 | bo.tabstop = 2 5 | bo.shiftwidth = 2 6 | bo.softtabstop = -1 7 | bo.expandtab = true 8 | bo.smartindent = false 9 | 10 | wo.wrap = false 11 | -------------------------------------------------------------------------------- /after/ftplugin/make.lua: -------------------------------------------------------------------------------- 1 | local bo = vim.bo 2 | local wo = vim.wo 3 | 4 | bo.tabstop = 4 5 | bo.shiftwidth = 4 6 | bo.softtabstop = -1 7 | bo.expandtab = false 8 | bo.smartindent = false 9 | 10 | wo.wrap = false 11 | wo.number = true 12 | wo.relativenumber = true 13 | -------------------------------------------------------------------------------- /after/ftplugin/markdown.lua: -------------------------------------------------------------------------------- 1 | local bo = vim.bo 2 | local wo = vim.wo 3 | 4 | bo.tabstop = 2 5 | bo.shiftwidth = 2 6 | bo.softtabstop = -1 7 | bo.expandtab = true 8 | bo.smartindent = false 9 | 10 | wo.wrap = false 11 | wo.number = true 12 | wo.relativenumber = true 13 | -------------------------------------------------------------------------------- /after/ftplugin/rust.lua: -------------------------------------------------------------------------------- 1 | local bo = vim.bo 2 | local wo = vim.wo 3 | 4 | bo.tabstop = 4 5 | bo.shiftwidth = 4 6 | bo.softtabstop = -1 7 | bo.expandtab = true 8 | bo.smartindent = false 9 | 10 | wo.wrap = false 11 | wo.number = true 12 | wo.relativenumber = true 13 | -------------------------------------------------------------------------------- /after/ftplugin/scss.lua: -------------------------------------------------------------------------------- 1 | 2 | local bo = vim.bo 3 | local wo = vim.wo 4 | 5 | bo.tabstop = 2 6 | bo.shiftwidth = 2 7 | bo.softtabstop = -1 8 | bo.expandtab = true 9 | bo.smartindent = false 10 | 11 | wo.wrap = false 12 | wo.number = true 13 | wo.relativenumber = true 14 | -------------------------------------------------------------------------------- /after/ftplugin/terminal.lua: -------------------------------------------------------------------------------- 1 | local bo = vim.bo 2 | local wo = vim.wo 3 | 4 | bo.tabstop = 4 5 | bo.shiftwidth = 4 6 | bo.softtabstop = -1 7 | bo.expandtab = false 8 | bo.smartindent = false 9 | 10 | wo.wrap = false 11 | wo.number = false 12 | wo.relativenumber = false 13 | -------------------------------------------------------------------------------- /after/ftplugin/text.lua: -------------------------------------------------------------------------------- 1 | local bo = vim.bo 2 | local wo = vim.wo 3 | 4 | bo.tabstop = 4 5 | bo.shiftwidth = 4 6 | bo.softtabstop = -1 7 | bo.expandtab = false 8 | bo.smartindent = false 9 | 10 | wo.wrap = true 11 | -------------------------------------------------------------------------------- /after/ftplugin/zig.lua: -------------------------------------------------------------------------------- 1 | local bo = vim.bo 2 | local wo = vim.wo 3 | 4 | bo.tabstop = 4 5 | bo.shiftwidth = 4 6 | bo.softtabstop = -1 7 | bo.expandtab = true 8 | bo.smartindent = false 9 | 10 | wo.wrap = false 11 | -------------------------------------------------------------------------------- /after/indent/rust.vim: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willothy/nvim-config/322aced24e2586892579db54243077c4e40149a1/after/indent/rust.vim -------------------------------------------------------------------------------- /after/queries/lua/field.scm: -------------------------------------------------------------------------------- 1 | ; ((field) @_start @_end 2 | ; (#make-range! "field" @_start @_end)) 3 | (field) @field 4 | -------------------------------------------------------------------------------- /init.lua: -------------------------------------------------------------------------------- 1 | vim.g.mapleader = " " 2 | vim.g.maplocalleader = "," -- TODO: I don't use this much. I should. 3 | 4 | local lazy_path = vim.fn.stdpath("data") .. "/lazy/lazy.nvim" 5 | 6 | vim.opt.rtp:prepend(lazy_path) 7 | 8 | -- -- Loading shada is SLOW, so we're going to load it manually, 9 | -- -- after UI-enter so it doesn't block startup. 10 | -- local shada = vim.o.shada 11 | -- vim.o.shada = "" 12 | -- vim.api.nvim_create_autocmd("User", { 13 | -- pattern = "VeryLazy", 14 | -- callback = function() 15 | -- vim.o.shada = shada 16 | -- pcall(vim.cmd.rshada, { bang = true }) 17 | -- end, 18 | -- }) 19 | 20 | require("lazy").setup({ 21 | { 22 | "settings", 23 | virtual = true, 24 | lazy = false, 25 | config = function() 26 | require("willothy.settings") 27 | end, 28 | }, 29 | { 30 | "autocmds", 31 | event = "VeryLazy", 32 | virtual = true, 33 | config = function() 34 | require("willothy.autocmds") 35 | end, 36 | }, 37 | { 38 | "keymap", 39 | event = "VeryLazy", 40 | virtual = true, 41 | config = function() 42 | require("willothy.keymap") 43 | end, 44 | }, 45 | { 46 | "commands", 47 | event = "VeryLazy", 48 | main = "willothy.commands", 49 | virtual = true, 50 | config = function() 51 | require("willothy.commands") 52 | end, 53 | }, 54 | { 55 | "macros", 56 | main = "willothy.macros", 57 | event = "VeryLazy", 58 | virtual = true, 59 | config = true, 60 | }, 61 | { 62 | "ui", 63 | virtual = true, 64 | event = "UiEnter", 65 | config = function() 66 | require("willothy.ui.scrollbar").setup() 67 | require("willothy.ui.scrolleof").setup() 68 | require("willothy.ui.code_actions").setup() 69 | require("willothy.ui.mode").setup() 70 | end, 71 | }, 72 | 73 | -- { 74 | -- "ziglang/zig.vim", 75 | -- event = "BufRead *.zig", 76 | -- }, 77 | 78 | { import = "plugins.editor" }, 79 | { import = "plugins.ui" }, 80 | { import = "plugins.libraries" }, 81 | { import = "plugins.lsp" }, 82 | { import = "plugins.util" }, 83 | { import = "plugins.fun" }, 84 | 85 | { 86 | "minimus", 87 | virtual = true, 88 | config = function() 89 | vim.cmd.colorscheme("minimus") 90 | end, 91 | event = "UiEnter", 92 | }, 93 | { 94 | "colorschemes", 95 | virtual = true, 96 | event = "VeryLazy", 97 | dependencies = { 98 | "folke/tokyonight.nvim", 99 | "eldritch-theme/eldritch.nvim", 100 | "diegoulloao/neofusion.nvim", 101 | "comfysage/evergarden", 102 | "ray-x/aurora", 103 | "S-Spektrum-M/odyssey.nvim", 104 | "AlexvZyl/nordic.nvim", 105 | { 106 | "0xstepit/flow.nvim", 107 | config = true, 108 | }, 109 | { 110 | "wtfox/jellybeans.nvim", 111 | config = true, 112 | }, 113 | }, 114 | }, 115 | "echasnovski/mini.colors", 116 | }, { 117 | defaults = { 118 | lazy = true, 119 | cond = not vim.g.started_by_firenvim, 120 | }, 121 | install = { 122 | missing = true, 123 | colorscheme = { "minimus" }, 124 | }, 125 | diff = { 126 | cmd = "diffview.nvim", 127 | }, 128 | ui = { 129 | border = "solid", 130 | }, 131 | dev = { 132 | path = "~/projects/lua/", 133 | }, 134 | git = { 135 | throttle = { 136 | enabled = true, 137 | rate = 10, 138 | duration = 150, 139 | }, 140 | }, 141 | pkg = { 142 | enabled = true, 143 | sources = { 144 | "lazy", 145 | "packspec", 146 | "rockspec", 147 | }, 148 | }, 149 | rocks = { 150 | enabled = true, 151 | hererocks = true, 152 | }, 153 | performance = { 154 | cache = { 155 | enabled = true, 156 | }, 157 | reset_packpath = true, 158 | rtp = { 159 | reset = true, 160 | disabled_plugins = { 161 | "gzip", 162 | "matchit", 163 | "matchparen", 164 | "netrwPlugin", 165 | "tarPlugin", 166 | "tohtml", 167 | "tutor", 168 | "zipPlugin", 169 | "man", 170 | "spellfile", 171 | }, 172 | }, 173 | }, 174 | }) 175 | 176 | require("willothy.lib.fs").hijack_netrw() 177 | -------------------------------------------------------------------------------- /lsp/_basedpyright.lua: -------------------------------------------------------------------------------- 1 | return { 2 | cmd = { "basedpyright-langserver", "--stdio" }, 3 | filetypes = { "python" }, 4 | root_markers = { "pyproject.toml", ".git" }, 5 | single_file_support = true, 6 | settings = { 7 | basedpyright = { 8 | analysis = { 9 | autoSearchPaths = true, 10 | useLibraryCodeForTypes = true, 11 | diagnosticMode = "openFilesOnly", 12 | }, 13 | }, 14 | }, 15 | } 16 | -------------------------------------------------------------------------------- /lsp/_bashls.lua: -------------------------------------------------------------------------------- 1 | return { 2 | cmd = { "bash-language-server", "start" }, 3 | settings = { 4 | bashIde = { 5 | -- Glob pattern for finding and parsing shell script files in the workspace. 6 | -- Used by the background analysis features across files. 7 | 8 | -- Prevent recursive scanning which will cause issues when opening a file 9 | -- directly in the home directory (e.g. ~/foo.sh). 10 | -- 11 | -- Default upstream pattern is "**/*@(.sh|.inc|.bash|.command)". 12 | globPattern = vim.env.GLOB_PATTERN or "*@(.sh|.inc|.bash|.command)", 13 | }, 14 | }, 15 | filetypes = { "zsh", "sh", "bash" }, 16 | root_markers = { ".git", ".zshrc", ".bashrc" }, 17 | single_file_support = true, 18 | } 19 | -------------------------------------------------------------------------------- /lsp/_biome.lua: -------------------------------------------------------------------------------- 1 | return { 2 | cmd = { "biome", "lsp-proxy" }, 3 | filetypes = { 4 | "astro", 5 | "css", 6 | "graphql", 7 | "javascript", 8 | "javascriptreact", 9 | "json", 10 | "jsonc", 11 | "svelte", 12 | "typescript", 13 | "typescript.tsx", 14 | "typescriptreact", 15 | "vue", 16 | }, 17 | root_markers = { "biome.json", "biome.jsonc" }, 18 | single_file_support = false, 19 | } 20 | -------------------------------------------------------------------------------- /lsp/_bufls.lua: -------------------------------------------------------------------------------- 1 | return { 2 | cmd = { "bufls", "serve" }, 3 | filetypes = { "proto" }, 4 | root_markers = { "buf.work.yaml", ".git" }, 5 | single_file_support = true, 6 | } 7 | -------------------------------------------------------------------------------- /lsp/_clangd.lua: -------------------------------------------------------------------------------- 1 | -- https://clangd.llvm.org/extensions.html#switch-between-sourceheader 2 | local function switch_source_header(bufnr) 3 | local util = require("lspconfig.util") 4 | 5 | bufnr = util.validate_bufnr(bufnr) 6 | local clangd_client = util.get_active_client_by_name(bufnr, "clangd") 7 | local params = { uri = vim.uri_from_bufnr(bufnr) } 8 | if clangd_client then 9 | clangd_client:request( 10 | "textDocument/switchSourceHeader", 11 | params, 12 | function(err, result) 13 | if err then 14 | error(tostring(err)) 15 | end 16 | if not result then 17 | print("Corresponding file cannot be determined") 18 | return 19 | end 20 | vim.api.nvim_command("edit " .. vim.uri_to_fname(result)) 21 | end, 22 | bufnr 23 | ) 24 | else 25 | print( 26 | "method textDocument/switchSourceHeader is not supported by any servers active on the current buffer" 27 | ) 28 | end 29 | end 30 | 31 | local function symbol_info() 32 | local util = require("lspconfig.util") 33 | 34 | local bufnr = vim.api.nvim_get_current_buf() 35 | local clangd_client = util.get_active_client_by_name(bufnr, "clangd") 36 | if 37 | not clangd_client 38 | or not clangd_client:supports_method("textDocument/symbolInfo") 39 | then 40 | return vim.notify("Clangd client not found", vim.log.levels.ERROR) 41 | end 42 | local win = vim.api.nvim_get_current_win() 43 | local params = 44 | vim.lsp.util.make_position_params(win, clangd_client.offset_encoding) 45 | clangd_client:request("textDocument/symbolInfo", params, function(err, res) 46 | if err or #res == 0 then 47 | -- Clangd always returns an error, there is not reason to parse it 48 | return 49 | end 50 | local container = string.format("container: %s", res[1].containerName) ---@type string 51 | local name = string.format("name: %s", res[1].name) ---@type string 52 | vim.lsp.util.open_floating_preview({ name, container }, "", { 53 | height = 2, 54 | width = math.max(string.len(name), string.len(container)), 55 | focusable = false, 56 | focus = false, 57 | border = "single", 58 | title = "Symbol Info", 59 | }) 60 | end, bufnr) 61 | end 62 | 63 | return { 64 | cmd = { "clangd" }, 65 | filetypes = { "c", "cpp", "objc", "objcpp", "cuda" }, 66 | root_markers = { 67 | ".clangd", 68 | ".clang-tidy", 69 | ".clang-format", 70 | "compile_commands.json", 71 | "compile_flags.txt", 72 | "configure.ac", -- AutoTools 73 | }, 74 | single_file_support = false, 75 | capabilities = { 76 | textDocument = { 77 | completion = { 78 | editsNearCursor = true, 79 | }, 80 | }, 81 | offsetEncoding = { "utf-8", "utf-16" }, 82 | }, 83 | on_attach = function(_, bufnr) 84 | vim.keymap.set("n", "gh", "ClangdSwitchSourceHeader", { 85 | lhs = "gh", 86 | mode = "n", 87 | silent = true, 88 | noremap = true, 89 | buffer = bufnr, 90 | desc = "header / source", 91 | }) 92 | end, 93 | commands = { 94 | ClangdSwitchSourceHeader = function() 95 | switch_source_header(0) 96 | end, 97 | ClangdShowSymbolInfo = function() 98 | symbol_info() 99 | end, 100 | }, 101 | } 102 | -------------------------------------------------------------------------------- /lsp/_cmake.lua: -------------------------------------------------------------------------------- 1 | return { 2 | cmd = { "cmake-language-server" }, 3 | filetypes = { "cmake" }, 4 | root_markers = { 5 | "CMakePresets.json", 6 | "CTestConfig.cmake", 7 | ".git", 8 | "build", 9 | "cmake", 10 | }, 11 | single_file_support = true, 12 | init_options = { 13 | buildDirectory = "build", 14 | }, 15 | } 16 | -------------------------------------------------------------------------------- /lsp/_dockerls.lua: -------------------------------------------------------------------------------- 1 | return { 2 | cmd = { "docker-langserver", "--stdio" }, 3 | filetypes = { "dockerfile" }, 4 | root_markers = { "Dockerfile" }, 5 | single_file_support = true, 6 | } 7 | -------------------------------------------------------------------------------- /lsp/_emmylua_ls.lua: -------------------------------------------------------------------------------- 1 | return { 2 | capabilties = require("willothy.lsp.capabilities").make_capabilities(), 3 | cmd = { "emmylua_ls" }, 4 | filetypes = { "lua" }, 5 | root_markers = { 6 | ".luarc.json", 7 | "luarc.json", 8 | ".git", 9 | }, 10 | settings = { 11 | runtime = { 12 | version = "LuaJIT", 13 | -- requirePattern = { "lua/?.lua", "lua/?/init.lua" }, 14 | }, 15 | workspace = { 16 | library = vim.api.nvim_get_runtime_file("lua/*.lua", true), 17 | }, 18 | }, 19 | } 20 | -------------------------------------------------------------------------------- /lsp/_eslint.lua: -------------------------------------------------------------------------------- 1 | return { 2 | cmd = { "vscode-eslint-language-server", "--stdio" }, 3 | filetypes = { 4 | "javascript", 5 | "javascriptreact", 6 | "javascript.jsx", 7 | "typescript", 8 | "typescriptreact", 9 | "typescript.tsx", 10 | "vue", 11 | "svelte", 12 | "astro", 13 | }, 14 | -- https://eslint.org/docs/user-guide/configuring/configuration-files#configuration-file-formats 15 | root_markers = { 16 | ".eslintrc", 17 | ".eslintrc.js", 18 | ".eslintrc.cjs", 19 | ".eslintrc.yaml", 20 | ".eslintrc.yml", 21 | ".eslintrc.json", 22 | "eslint.config.js", 23 | "eslint.config.mjs", 24 | "eslint.config.cjs", 25 | "eslint.config.ts", 26 | "eslint.config.mts", 27 | "eslint.config.cts", 28 | }, 29 | -- Refer to https://github.com/Microsoft/vscode-eslint#settings-options for documentation. 30 | settings = { 31 | validate = "on", 32 | packageManager = nil, 33 | useESLintClass = false, 34 | experimental = { 35 | useFlatConfig = false, 36 | }, 37 | codeActionOnSave = { 38 | enable = false, 39 | mode = "all", 40 | }, 41 | format = true, 42 | quiet = false, 43 | onIgnoredFiles = "off", 44 | rulesCustomizations = {}, 45 | run = "onType", 46 | problems = { 47 | shortenToSingleLine = false, 48 | }, 49 | -- nodePath configures the directory in which the eslint server should start its node_modules resolution. 50 | -- This path is relative to the workspace folder (root dir) of the server instance. 51 | nodePath = "", 52 | -- use the workspace folder location or the file location (if no workspace folder is open) as the working directory 53 | workingDirectory = { mode = "location" }, 54 | codeAction = { 55 | disableRuleComment = { 56 | enable = true, 57 | location = "separateLine", 58 | }, 59 | showDocumentation = { 60 | enable = true, 61 | }, 62 | }, 63 | }, 64 | } 65 | -------------------------------------------------------------------------------- /lsp/_gleam.lua: -------------------------------------------------------------------------------- 1 | return { 2 | cmd = { "gleam", "lsp" }, 3 | filetypes = { "gleam" }, 4 | root_markers = { ".git", "gleam.toml" }, 5 | } 6 | -------------------------------------------------------------------------------- /lsp/_gopls.lua: -------------------------------------------------------------------------------- 1 | return { 2 | cmd = { "gopls" }, 3 | filetypes = { "go", "gomod", "gowork", "gotmpl" }, 4 | root_markers = { "go.work", "go.mod", ".git" }, 5 | single_file_support = true, 6 | } 7 | -------------------------------------------------------------------------------- /lsp/_intelephense.lua: -------------------------------------------------------------------------------- 1 | return { 2 | cmd = { "intelephense", "--stdio" }, 3 | filetypes = { "php" }, 4 | root_markers = { 5 | "composer.json", 6 | ".git", 7 | }, 8 | } 9 | -------------------------------------------------------------------------------- /lsp/_jsonls.lua: -------------------------------------------------------------------------------- 1 | return { 2 | cmd = { "vscode-json-language-server", "--stdio" }, 3 | filetypes = { "json", "jsonc" }, 4 | init_options = { 5 | provideFormatter = true, 6 | }, 7 | root_markers = { ".git" }, 8 | single_file_support = true, 9 | } 10 | -------------------------------------------------------------------------------- /lsp/_lua_ls.lua: -------------------------------------------------------------------------------- 1 | return { 2 | capabilties = require("willothy.lsp.capabilities").make_capabilities(), 3 | settings = { 4 | Lua = { 5 | diagnostics = { 6 | enable = true, 7 | unusedLocalExclude = { 8 | "_*", 9 | }, 10 | globals = { "vim", "willothy" }, 11 | }, 12 | completion = { 13 | autoRequire = true, 14 | callSnippet = "Disable", 15 | displayContext = 2, 16 | }, 17 | format = { 18 | enable = true, 19 | }, 20 | hint = { 21 | enable = true, 22 | setType = true, 23 | arrayIndex = "Disable", 24 | await = true, 25 | paramName = "All", 26 | paramType = true, 27 | semicolon = "SameLine", 28 | }, 29 | runtime = { 30 | -- Tell the language server which version of Lua you're using 31 | -- (most likely LuaJIT in the case of Neovim) 32 | version = "LuaJIT", 33 | }, 34 | -- Make the server aware of Neovim runtime files 35 | workspace = { 36 | checkThirdParty = false, 37 | -- library = vim 38 | -- .iter( 39 | -- { 40 | -- vim.env.VIMRUNTIME, 41 | -- 42 | -- -- Depending on the usage, you might want to add additional paths here. 43 | -- "${3rd}/luv/library", 44 | -- -- "${3rd}/busted/library", 45 | -- }, 46 | -- vim.api.nvim_get_runtime_file("lua/vim/*", true) 47 | -- -- vim.api.nvim_get_runtime_file("lua/vim/iter", true) 48 | -- ) 49 | -- :flatten() 50 | -- :totable(), 51 | -- -- or pull in all of 'runtimepath'. NOTE: this is a lot slower and will cause issues when working on your own configuration (see https://github.com/neovim/nvim-lspconfig/issues/3189) 52 | -- -- library = vim.api.nvim_get_runtime_file("", true), 53 | }, 54 | }, 55 | }, 56 | cmd = { "lua-language-server" }, 57 | filetypes = { "lua" }, 58 | root_markers = { 59 | ".luarc.json", 60 | "luarc.json", 61 | ".git", 62 | }, 63 | } 64 | -------------------------------------------------------------------------------- /lsp/_prismals.lua: -------------------------------------------------------------------------------- 1 | return { 2 | cmd = { "prisma-language-server", "--stdio" }, 3 | filetypes = { "prisma" }, 4 | settings = { 5 | prisma = { 6 | prismaFmtBinPath = "", 7 | }, 8 | }, 9 | root_markers = { ".git", "package.json" }, 10 | } 11 | -------------------------------------------------------------------------------- /lsp/_rust_analyzer.lua: -------------------------------------------------------------------------------- 1 | local function reload_workspace(bufnr) 2 | local util = require("lspconfig.util") 3 | bufnr = util.validate_bufnr(bufnr) 4 | local clients = 5 | util.get_lsp_clients({ bufnr = bufnr, name = "rust_analyzer" }) 6 | for _, client in ipairs(clients) do 7 | vim.notify("Reloading Cargo Workspace") 8 | client:request("rust-analyzer/reloadWorkspace", nil, function(err) 9 | if err then 10 | error(tostring(err)) 11 | end 12 | vim.notify("Cargo workspace reloaded") 13 | end, 0) 14 | end 15 | end 16 | 17 | local function is_library(fname) 18 | local util = require("lspconfig.util") 19 | local user_home = vim.fs.normalize(vim.env.HOME) 20 | local cargo_home = os.getenv("CARGO_HOME") or user_home .. "/.cargo" 21 | local registry = cargo_home .. "/registry/src" 22 | local git_registry = cargo_home .. "/git/checkouts" 23 | 24 | local rustup_home = os.getenv("RUSTUP_HOME") or user_home .. "/.rustup" 25 | local toolchains = rustup_home .. "/toolchains" 26 | 27 | for _, item in ipairs({ toolchains, registry, git_registry }) do 28 | if util.path.is_descendant(item, fname) then 29 | local clients = util.get_lsp_clients({ name = "rust_analyzer" }) 30 | return #clients > 0 and clients[#clients].config.root_dir or nil 31 | end 32 | end 33 | end 34 | 35 | return { 36 | cmd = { "rust-analyzer" }, 37 | filetypes = { "rust" }, 38 | single_file_support = true, 39 | root_markers = { "Cargo.lock", "Cargo.toml", ".git" }, 40 | capabilities = { 41 | experimental = { 42 | serverStatusNotification = true, 43 | }, 44 | }, 45 | root_dir = function(bufnr, callback) 46 | coroutine.resume(coroutine.create(function() 47 | local fname = vim.api.nvim_buf_get_name(bufnr) 48 | 49 | local reuse_active = is_library(fname) 50 | if reuse_active then 51 | callback(reuse_active) 52 | return 53 | end 54 | 55 | local cargo_crate_dir = 56 | require("lspconfig.util").root_pattern("Cargo.toml")(fname) 57 | local cargo_workspace_root 58 | 59 | if cargo_crate_dir ~= nil then 60 | local cmd = { 61 | "cargo", 62 | "metadata", 63 | "--no-deps", 64 | "--format-version", 65 | "1", 66 | "--manifest-path", 67 | cargo_crate_dir .. "/Cargo.toml", 68 | } 69 | 70 | local result = require("lspconfig.async").run_command(cmd) 71 | 72 | if result and result[1] then 73 | result = vim.json.decode(table.concat(result, "")) 74 | if result["workspace_root"] then 75 | cargo_workspace_root = vim.fs.normalize(result["workspace_root"]) 76 | end 77 | end 78 | end 79 | 80 | callback( 81 | cargo_workspace_root 82 | or cargo_crate_dir 83 | or require("lspconfig.util").root_pattern("rust-project.json")(fname) 84 | or vim.fs.dirname( 85 | vim.fs.find(".git", { path = fname, upward = true })[1] 86 | ) 87 | ) 88 | end)) 89 | end, 90 | before_init = function(init_params, config) 91 | -- See https://github.com/rust-lang/rust-analyzer/blob/eb5da56d839ae0a9e9f50774fa3eb78eb0964550/docs/dev/lsp-extensions.md?plain=1#L26 92 | if config.settings and config.settings["rust-analyzer"] then 93 | init_params.initializationOptions = config.settings["rust-analyzer"] 94 | end 95 | end, 96 | } 97 | -------------------------------------------------------------------------------- /lsp/_tailwindcss.lua: -------------------------------------------------------------------------------- 1 | return { 2 | cmd = { "tailwindcss-language-server", "--stdio" }, 3 | root_markers = { 4 | "tailwind.config.js", 5 | "tailwind.config.cjs", 6 | "tailwind.config.mjs", 7 | "tailwind.config.ts", 8 | "postcss.config.js", 9 | "postcss.config.cjs", 10 | "postcss.config.mjs", 11 | "postcss.config.ts", 12 | "vite.config.ts", 13 | "package.json", 14 | }, 15 | -- filetypes copied and adjusted from tailwindcss-intellisense 16 | filetypes = { 17 | -- html 18 | "aspnetcorerazor", 19 | "astro", 20 | "astro-markdown", 21 | "blade", 22 | "clojure", 23 | "django-html", 24 | "htmldjango", 25 | "edge", 26 | "eelixir", -- vim ft 27 | "elixir", 28 | "ejs", 29 | "erb", 30 | "eruby", -- vim ft 31 | "gohtml", 32 | "gohtmltmpl", 33 | "haml", 34 | "handlebars", 35 | "hbs", 36 | "html", 37 | "htmlangular", 38 | "html-eex", 39 | "heex", 40 | "jade", 41 | "leaf", 42 | "liquid", 43 | "markdown", 44 | "mdx", 45 | "mustache", 46 | "njk", 47 | "nunjucks", 48 | "php", 49 | "razor", 50 | "slim", 51 | "twig", 52 | -- css 53 | "css", 54 | "less", 55 | "postcss", 56 | "sass", 57 | "scss", 58 | "stylus", 59 | "sugarss", 60 | -- js 61 | "javascript", 62 | "javascriptreact", 63 | "reason", 64 | "rescript", 65 | "typescript", 66 | "typescriptreact", 67 | -- mixed 68 | "vue", 69 | "svelte", 70 | "templ", 71 | }, 72 | settings = { 73 | tailwindCSS = { 74 | validate = true, 75 | lint = { 76 | cssConflict = "warning", 77 | invalidApply = "error", 78 | invalidScreen = "error", 79 | invalidVariant = "error", 80 | invalidConfigPath = "error", 81 | invalidTailwindDirective = "error", 82 | recommendedVariantOrder = "warning", 83 | }, 84 | classAttributes = { 85 | "class", 86 | "className", 87 | "class:list", 88 | "classList", 89 | "ngClass", 90 | }, 91 | includeLanguages = { 92 | eelixir = "html-eex", 93 | eruby = "erb", 94 | templ = "html", 95 | htmlangular = "html", 96 | }, 97 | }, 98 | }, 99 | } 100 | -------------------------------------------------------------------------------- /lsp/_taplo.lua: -------------------------------------------------------------------------------- 1 | return { 2 | cmd = { "taplo", "lsp", "stdio" }, 3 | filetypes = { "toml" }, 4 | single_file_support = true, 5 | root_markers = { 6 | ".git", 7 | "Cargo.toml", 8 | }, 9 | } 10 | -------------------------------------------------------------------------------- /lsp/_ts_ls.lua: -------------------------------------------------------------------------------- 1 | return { 2 | cmd = { "typescript-language-server", "--stdio" }, 3 | filetypes = { 4 | "javascript", 5 | "javascriptreact", 6 | "javascript.jsx", 7 | "typescript", 8 | "typescriptreact", 9 | "typescript.tsx", 10 | }, 11 | settings = { 12 | -- typescript = { 13 | -- inlayHints = { 14 | -- includeInlayParameterNameHints = "all", -- 'none' | 'literals' | 'all' 15 | -- includeInlayParameterNameHintsWhenArgumentMatchesName = true, 16 | -- includeInlayVariableTypeHints = true, 17 | -- includeInlayFunctionParameterTypeHints = true, 18 | -- includeInlayVariableTypeHintsWhenTypeMatchesName = true, 19 | -- includeInlayPropertyDeclarationTypeHints = true, 20 | -- includeInlayFunctionLikeReturnTypeHints = true, 21 | -- includeInlayEnumMemberValueHints = true, 22 | -- }, 23 | -- }, 24 | -- javascript = { 25 | -- inlayHints = { 26 | -- includeInlayParameterNameHints = "all", -- 'none' | 'literals' | 'all' 27 | -- includeInlayParameterNameHintsWhenArgumentMatchesName = true, 28 | -- includeInlayVariableTypeHints = true, 29 | -- includeInlayFunctionParameterTypeHints = true, 30 | -- includeInlayVariableTypeHintsWhenTypeMatchesName = true, 31 | -- includeInlayPropertyDeclarationTypeHints = true, 32 | -- includeInlayFunctionLikeReturnTypeHints = true, 33 | -- includeInlayEnumMemberValueHints = true, 34 | -- }, 35 | -- }, 36 | }, 37 | init_options = { 38 | hostInfo = "neovim", 39 | }, 40 | root_markers = { "package.json", "tsconfig.json", "jsconfig.json", ".git" }, 41 | single_file_support = true, 42 | } 43 | -------------------------------------------------------------------------------- /lsp/_zls.lua: -------------------------------------------------------------------------------- 1 | return { 2 | cmd = { "zls" }, 3 | filetypes = { "zig", "zir" }, 4 | root_markers = { "zls.json", "build.zig" }, 5 | single_file_support = false, 6 | } 7 | -------------------------------------------------------------------------------- /lua/configs/editor/oil.lua: -------------------------------------------------------------------------------- 1 | local Utils = require("oil.util") 2 | local oil = require("oil") 3 | 4 | oil.winbar = function() 5 | if vim.api.nvim_win_get_config(0).relative ~= "" then 6 | vim.wo[0].winbar = "" 7 | return "" 8 | end 9 | local path = oil.get_current_dir():gsub(vim.env.HOME, "~"):gsub("/$", "") 10 | local width = vim.api.nvim_win_get_width(0) 11 | local limit = width - 2 12 | if string.len(path) > limit then 13 | path = require("willothy.lib.fs").incremental_shorten(path, limit) 14 | end 15 | return path .. "/" 16 | end 17 | 18 | vim.api.nvim_create_autocmd("WinNew", { 19 | pattern = "oil://*", 20 | callback = function() 21 | -- ensure the window is the one that was current 22 | -- when the autocmd was executed, since the rest 23 | -- of the callback will executed later. 24 | local win = vim.api.nvim_get_current_win() 25 | vim.schedule(function() 26 | if 27 | vim.api.nvim_win_is_valid(win) 28 | and require("oil.util").is_oil_bufnr(vim.api.nvim_win_get_buf(win)) 29 | then 30 | vim.w[win].oil_opened = true 31 | end 32 | end) 33 | end, 34 | }) 35 | 36 | -- Auto-confirm changes on exit. 37 | -- If the user cancels the save, discard all changes. 38 | -- 39 | -- This is more like the behavior of mini.files, which I like. 40 | vim.api.nvim_create_autocmd("BufHidden", { 41 | pattern = "oil://*", 42 | callback = function(ev) 43 | if vim.v.exiting ~= vim.NIL then 44 | return 45 | end 46 | local buf = ev.buf 47 | if Utils.is_oil_bufnr(buf) then 48 | vim.schedule(function() 49 | if 50 | vim.iter(vim.api.nvim_list_wins()):any(function(win) 51 | return Utils.is_oil_bufnr(vim.api.nvim_win_get_buf(win)) 52 | end) 53 | then 54 | return 55 | end 56 | oil.save({ 57 | confirm = true, 58 | }, function(err) 59 | if err then 60 | if err == "Canceled" then 61 | oil.discard_all_changes() 62 | else 63 | vim.notify(err, vim.log.levels.WARN, { 64 | title = "Oil", 65 | }) 66 | end 67 | end 68 | end) 69 | end) 70 | end 71 | end, 72 | }) 73 | 74 | oil.setup({ 75 | default_file_explorer = false, 76 | buf_options = {}, 77 | keymaps = { 78 | [""] = "actions.select_split", 79 | [""] = { 80 | callback = function() 81 | oil.select({ 82 | vertical = true, 83 | split = "belowright", 84 | }) 85 | end, 86 | }, 87 | [""] = false, 88 | [""] = false, 89 | [""] = "actions.refresh", 90 | ["q"] = { 91 | callback = function() 92 | local win = vim.api.nvim_get_current_win() 93 | local alt = vim.api.nvim_win_call(win, function() 94 | return vim.fn.winnr("#") 95 | end) 96 | 97 | oil.close() 98 | 99 | if 100 | vim.api.nvim_win_is_valid(win) 101 | and vim.w[win].oil_opened 102 | and alt ~= 0 103 | then 104 | vim.api.nvim_win_close(win, false) 105 | end 106 | end, 107 | }, 108 | ["L"] = { 109 | callback = function() 110 | local win = vim.api.nvim_get_current_win() 111 | local entry = oil.get_cursor_entry() 112 | local vertical, split, tgt_win 113 | if entry and entry.type == "file" then 114 | tgt_win = require("flatten.core").smart_open() 115 | if not tgt_win then 116 | vertical = true 117 | split = "belowright" 118 | end 119 | end 120 | oil.select({ 121 | vertical = vertical, 122 | split = split, 123 | win = tgt_win, 124 | close = false, 125 | }, function() 126 | if vim.api.nvim_win_is_valid(win) then 127 | vim.api.nvim_set_current_win(win) 128 | end 129 | end) 130 | end, 131 | }, 132 | ["H"] = "actions.parent", 133 | [""] = "actions.select", 134 | }, 135 | win_options = { 136 | statuscolumn = "", 137 | signcolumn = "no", 138 | numberwidth = 1, 139 | number = false, 140 | relativenumber = false, 141 | winbar = "%{%v:lua.require('oil').winbar()%}", 142 | }, 143 | float = { 144 | border = "single", 145 | win_options = {}, 146 | }, 147 | }) 148 | -------------------------------------------------------------------------------- /lua/configs/editor/otter.lua: -------------------------------------------------------------------------------- 1 | require("otter").setup({ 2 | set_filetype = true, 3 | }) 4 | 5 | local otter = require("otter") 6 | 7 | vim.g.markdown_fenced_languages = { 8 | "lua", 9 | "rust", 10 | } 11 | 12 | vim.api.nvim_create_autocmd({ "BufEnter" }, { 13 | pattern = { "*.md" }, 14 | callback = function() 15 | otter.activate({ "rust", "lua" }, true) 16 | vim.api.nvim_buf_set_keymap( 17 | 0, 18 | "n", 19 | "gd", 20 | ":lua require'otter'.ask_definition()", 21 | { silent = true } 22 | ) 23 | vim.api.nvim_buf_set_keymap( 24 | 0, 25 | "n", 26 | "K", 27 | ":lua require'otter'.ask_hover()", 28 | { silent = true } 29 | ) 30 | end, 31 | }) 32 | -------------------------------------------------------------------------------- /lua/configs/editor/trouble.lua: -------------------------------------------------------------------------------- 1 | local trouble = require("trouble") 2 | 3 | ---@diagnostic disable-next-line: missing-fields 4 | trouble.setup({ 5 | pinned = true, 6 | focus = true, 7 | follow = false, 8 | restore = true, 9 | win = { 10 | type = "split", 11 | wo = { 12 | fillchars = vim.o.fillchars, 13 | cursorlineopt = "number", 14 | concealcursor = "nvic", 15 | }, 16 | }, 17 | indent_guides = true, 18 | multiline = true, 19 | preview = { 20 | type = "main", 21 | zindex = 50, 22 | wo = { 23 | -- winbar = "", 24 | -- statuscolumn = "%!v:lua.StatusCol()", 25 | -- statuscolumn = "%!v:lua.require('statuscol').get_statuscol_string()", 26 | list = true, 27 | number = true, 28 | relativenumber = false, 29 | }, 30 | }, 31 | modes = { 32 | definitions2 = { 33 | mode = "lsp_definitions", 34 | focus = true, 35 | sections = { 36 | ["lsp_definitions"] = { 37 | title = "LSP Definitions", 38 | icon = "", 39 | highlight = "TroubleLspDef", 40 | indent = 1, 41 | }, 42 | }, 43 | }, 44 | }, 45 | }) 46 | 47 | vim.api.nvim_set_hl(0, "TroubleNormalNC", { 48 | link = "TroubleNormal", 49 | }) 50 | 51 | for nvim_name, trouble_name in pairs({ 52 | references = "lsp_references", 53 | definition = "lsp_definitions", 54 | type_definition = "lsp_type_definitions", 55 | implementation = "lsp_implementations", 56 | document_symbol = "lsp_document_symbols", 57 | }) do 58 | vim.lsp.buf[nvim_name] = function() 59 | require("trouble").open({ 60 | mode = trouble_name, 61 | }) 62 | end 63 | end 64 | -------------------------------------------------------------------------------- /lua/configs/editor/wezterm.lua: -------------------------------------------------------------------------------- 1 | local wezterm = require("wezterm") 2 | 3 | wezterm.setup({}) 4 | 5 | require("willothy.lib.fn").create_command("Wezterm", { 6 | command = function() 7 | vim.notify("Wezterm {zoom, spawn}", vim.log.levels.INFO, { 8 | title = "Usage", 9 | }) 10 | end, 11 | subcommands = { 12 | zoom = { 13 | execute = function() 14 | wezterm.zoom_pane(wezterm.get_current_pane(), { 15 | toggle = true, 16 | }) 17 | end, 18 | }, 19 | spawn = { 20 | execute = function(...) 21 | vim.cmd(string.format("WeztermSpawn %s", table.concat({ ... }, " "))) 22 | end, 23 | }, 24 | }, 25 | }) 26 | -------------------------------------------------------------------------------- /lua/configs/git/git-conflict.lua: -------------------------------------------------------------------------------- 1 | require("git-conflict").setup({ 2 | default_mappings = true, -- disable buffer local mapping created by this plugin 3 | default_commands = true, -- disable commands created by this plugin 4 | disable_diagnostics = false, -- This will disable the diagnostics in a buffer whilst it is conflicted 5 | highlights = { 6 | incoming = "DiffAdd", 7 | current = "DiffText", 8 | }, 9 | }) 10 | -------------------------------------------------------------------------------- /lua/configs/git/gitsigns.lua: -------------------------------------------------------------------------------- 1 | local bar = require("willothy.ui.icons").git.signs.bar 2 | 3 | local gitsigns = require("gitsigns") 4 | 5 | local git = require("gitsigns.git") 6 | local _set_version = git._set_version 7 | git._set_version = function(version) 8 | pcall(_set_version, version) 9 | end 10 | 11 | gitsigns.setup({ 12 | signs = { 13 | untracked = { text = bar }, 14 | add = { text = bar }, 15 | change = { text = bar }, 16 | delete = { text = bar }, 17 | topdelete = { text = bar }, 18 | changedelete = { text = bar }, 19 | }, 20 | preview_config = { 21 | focusable = false, 22 | -- Options passed to nvim_open_win 23 | border = "single", 24 | style = "minimal", 25 | relative = "cursor", 26 | row = 0, 27 | col = 1, 28 | }, 29 | trouble = true, 30 | signcolumn = true, 31 | -- _extmark_signs = false, 32 | on_attach = vim.schedule_wrap(function(bufnr) 33 | vim.cmd.GitConflictRefresh() 34 | end), 35 | }) 36 | -------------------------------------------------------------------------------- /lua/configs/git/neogit.lua: -------------------------------------------------------------------------------- 1 | require("neogit").setup({ 2 | kind = "auto", 3 | disable_builtin_notifications = true, 4 | integrations = { 5 | diffview = true, 6 | }, 7 | log_view = { 8 | kind = "split", 9 | }, 10 | reflog_view = { 11 | kind = "split", 12 | }, 13 | }) 14 | -------------------------------------------------------------------------------- /lua/configs/navigation/flash.lua: -------------------------------------------------------------------------------- 1 | ---@diagnostic disable-next-line: missing-fields 2 | require("flash").setup({ 3 | modes = { 4 | char = { 5 | enabled = true, 6 | jump_labels = true, 7 | label = { exclude = "hjkliardc" }, 8 | keys = { 9 | "f", 10 | "F", 11 | "t", 12 | "T", 13 | -- remove ; and , and use clever-f style repeat 14 | }, 15 | config = function(opts) 16 | opts.autohide = vim.fn.mode(true):find("no") and vim.v.operator == "y" 17 | 18 | opts.jump_labels = opts.jump_labels 19 | and vim.v.count == 0 20 | and vim.fn.reg_executing() == "" 21 | and vim.fn.reg_recording() == "" 22 | end, 23 | }, 24 | }, 25 | }) 26 | -------------------------------------------------------------------------------- /lua/configs/status/dropbar.lua: -------------------------------------------------------------------------------- 1 | local dropbar = require("dropbar") 2 | local icons = require("willothy.ui.icons") 3 | 4 | local enable = function(buf, win) 5 | -- if 6 | -- require("cokeline.sidebar").get_win("left") == win 7 | -- or require("cokeline.sidebar").get_win("right") == win 8 | -- then 9 | -- return false 10 | -- end 11 | -- if vim.wo[win].diff then 12 | -- return false 13 | -- end 14 | local filetype = vim.bo[buf].filetype 15 | local disabled = { 16 | ["oil"] = true, 17 | ["trouble"] = true, 18 | ["qf"] = true, 19 | ["noice"] = true, 20 | ["dapui_scopes"] = true, 21 | ["dapui_breakpoints"] = true, 22 | ["dapui_stacks"] = true, 23 | ["dapui_watches"] = true, 24 | ["dapui_console"] = true, 25 | ["dap-repl"] = true, 26 | ["neocomposer-menu"] = true, 27 | } 28 | if disabled[filetype] then 29 | return false 30 | end 31 | if vim.api.nvim_win_get_config(win).zindex ~= nil then 32 | return vim.bo[buf].buftype == "terminal" 33 | and vim.bo[buf].filetype == "terminal" 34 | end 35 | return vim.bo[buf].buflisted == true 36 | and vim.bo[buf].buftype == "" 37 | and vim.api.nvim_buf_get_name(buf) ~= "" 38 | end 39 | 40 | local close = function() 41 | local menu = require("dropbar.utils").menu.get_current() 42 | if not menu then 43 | return 44 | end 45 | menu:close() 46 | end 47 | 48 | -- vim.api.nvim_set_hl(0, "DropBarHover", { 49 | -- link = "Visual", 50 | -- -- italic = true, 51 | -- -- underline = true, 52 | -- }) 53 | 54 | dropbar.setup({ 55 | sources = { 56 | terminal = { 57 | name = function(buf) 58 | local term = require("toggleterm.terminal").find(function(term) 59 | return term.bufnr == buf 60 | end) 61 | local name 62 | if term then 63 | name = term.display_name or term.cmd or term.name 64 | else 65 | name = vim.api.nvim_buf_get_name(buf) 66 | end 67 | return " " .. name 68 | end, 69 | name_hl = "Normal", 70 | }, 71 | lsp = { 72 | valid_symbols = { 73 | "File", 74 | "Module", 75 | "Namespace", 76 | -- "Package", 77 | "Class", 78 | "Method", 79 | "Property", 80 | "Field", 81 | "Constructor", 82 | "Enum", 83 | "Interface", 84 | "Function", 85 | "Variable", 86 | "Constant", 87 | "String", 88 | "Number", 89 | "Boolean", 90 | "Array", 91 | "Object", 92 | "Keyword", 93 | "Null", 94 | "EnumMember", 95 | "Struct", 96 | "Event", 97 | "Operator", 98 | "TypeParameter", 99 | }, 100 | max_depth = 6, 101 | }, 102 | treesitter = { 103 | valid_types = { 104 | -- "array", 105 | -- "boolean", 106 | -- "break_statement", 107 | "call", 108 | -- "case_statement", 109 | "class", 110 | "constant", 111 | -- "constructor", 112 | -- "continue_statement", 113 | -- "delete", 114 | -- "do_statement", 115 | "element", 116 | "enum", 117 | "enum_member", 118 | -- "event", 119 | -- "for_statement", 120 | "function", 121 | -- "h1_marker", 122 | -- "h2_marker", 123 | -- "h3_marker", 124 | -- "h4_marker", 125 | -- "h5_marker", 126 | -- "h6_marker", 127 | -- "if_statement", 128 | "interface", 129 | -- "keyword", 130 | -- "macro", 131 | "method", 132 | "module", 133 | "namespace", 134 | -- "null", 135 | -- "number", 136 | -- "operator", 137 | "package", 138 | -- "pair", 139 | "property", 140 | "reference", 141 | -- "repeat", 142 | -- "rule_set", 143 | -- "scope", 144 | -- "specifier", 145 | "struct", 146 | -- "switch_statement", 147 | -- "type", 148 | -- "type_parameter", 149 | -- "unit", 150 | -- "value", 151 | -- "variable", 152 | -- "while_statement", 153 | -- "declaration", 154 | "field", 155 | -- "identifier", 156 | -- "object", 157 | -- "statement", 158 | }, 159 | max_depth = 6, 160 | }, 161 | path = { 162 | preview = "previous", 163 | }, 164 | }, 165 | icons = { 166 | kinds = { 167 | symbols = icons.kinds, 168 | }, 169 | ui = { 170 | bar = { 171 | separator = string.format(" %s ", icons.separators.angle_quote.right), 172 | extends = icons.misc.ellipse, 173 | }, 174 | }, 175 | }, 176 | bar = { 177 | general = { 178 | -- enable = enable, 179 | attach_events = { 180 | -- "OptionSet", 181 | "BufWinEnter", 182 | "BufWritePost", 183 | -- "FileType", 184 | "BufEnter", 185 | -- "TermEnter", 186 | }, 187 | }, 188 | padding = { 189 | left = 0, 190 | right = 1, 191 | }, 192 | }, 193 | menu = { 194 | keymaps = { 195 | q = close, 196 | [""] = close, 197 | }, 198 | quick_navigation = true, 199 | scrollbar = { 200 | background = false, 201 | }, 202 | }, 203 | fzf = { 204 | prompt = "%#GitSignsAdd# ", 205 | keymaps = { 206 | [""] = function() 207 | require("dropbar.api").fuzzy_find_navigate("down") 208 | end, 209 | [""] = function() 210 | require("dropbar.api").fuzzy_find_navigate("up") 211 | end, 212 | }, 213 | }, 214 | }) 215 | 216 | ---@diagnostic disable-next-line: duplicate-set-field 217 | vim.ui.select = function(items, opts, on_choice) 218 | local title = opts.prompt 219 | if title then -- add padding 220 | if not vim.startswith(title, " ") then 221 | title = string.format(" %s", title) 222 | end 223 | if not vim.endswith(title, " ") then 224 | title = string.format("%s ", title) 225 | end 226 | end 227 | return require("dropbar.utils.menu").select( 228 | items, 229 | vim.tbl_deep_extend("force", opts, { 230 | prompt = title, 231 | }), 232 | on_choice 233 | ) 234 | end 235 | 236 | vim.go.winbar = "%{%v:lua.dropbar()%}" 237 | -------------------------------------------------------------------------------- /lua/configs/status/incline.lua: -------------------------------------------------------------------------------- 1 | -- selene: allow(unused_variable) 2 | local function get_search_term(props) 3 | if not props.focused then 4 | return "" 5 | end 6 | 7 | local count = vim.fn.searchcount({ recompute = 1, maxcount = -1 }) 8 | local contents = vim.fn.getreg("/") 9 | if string.len(contents) == 0 then 10 | return "" 11 | end 12 | 13 | return { 14 | { 15 | "? ", 16 | group = "TabLine", 17 | }, 18 | { 19 | (" %s "):format(contents), 20 | group = "IncSearch", 21 | }, 22 | { 23 | (" %d/%d "):format(count.current, count.total), 24 | group = "TabLine", 25 | }, 26 | } 27 | end 28 | 29 | -- selene: allow(unused_variable) 30 | local function get_diagnostic_label(props) 31 | local icons = { error = "", warn = "", info = "", hint = "" } 32 | local label = {} 33 | 34 | for severity, icon in pairs(icons) do 35 | local n = #vim.diagnostic.get( 36 | props.buf, 37 | { severity = vim.diagnostic.severity[string.upper(severity)] } 38 | ) 39 | if n > 0 then 40 | table.insert( 41 | label, 42 | { icon .. " " .. n .. " ", group = "DiagnosticSign" .. severity } 43 | ) 44 | end 45 | end 46 | if #label > 0 then 47 | table.insert(label, { "| " }) 48 | end 49 | return label 50 | end 51 | 52 | -- selene: allow(unused_variable) 53 | local function get_git_diff(props) 54 | local icons = { 55 | removed = { "", "GitSignsDelete" }, 56 | changed = { "", "GitSignsChange" }, 57 | added = { "", "GitSignsAdd" }, 58 | } 59 | local labels = {} 60 | local signs = vim.api.nvim_buf_get_var(props.buf, "gitsigns_status_dict") 61 | if not signs then 62 | return nil 63 | end 64 | for name, info in pairs(icons) do 65 | if signs[name] and tonumber(signs[name]) and signs[name] > 0 then 66 | table.insert(labels, { 67 | info[1] .. " " .. signs[name] .. " ", 68 | group = info[2], 69 | }) 70 | end 71 | end 72 | if #labels > 0 then 73 | table.insert(labels, { "| " }) 74 | end 75 | return labels 76 | end 77 | 78 | require("incline").setup({ 79 | window = { 80 | zindex = 500, 81 | padding = 1, 82 | padding_char = " ", 83 | margin = { 84 | horizontal = 0, 85 | vertical = 0, 86 | }, 87 | placement = { 88 | horizontal = "right", 89 | vertical = "bottom", 90 | }, 91 | overlap = { 92 | winbar = false, 93 | tabline = false, 94 | borders = true, 95 | statusline = false, 96 | }, 97 | }, 98 | hide = { 99 | -- cursorline = "focused_win", 100 | }, 101 | highlight = { 102 | groups = { 103 | InclineNormal = { 104 | default = true, 105 | group = "TabLine", 106 | }, 107 | InclineNormalNC = { 108 | default = true, 109 | group = "TabLine", 110 | }, 111 | }, 112 | }, 113 | debounce_threshold = { 114 | rising = 10, 115 | falling = 10, 116 | }, 117 | render = function(props) 118 | local filename = 119 | vim.fn.fnamemodify(vim.api.nvim_buf_get_name(props.buf), ":t") 120 | local ft_icon, ft_color = 121 | require("nvim-web-devicons").get_icon_color(filename) 122 | local modified = vim.api.nvim_get_option_value("modified", { 123 | buf = props.buf, 124 | }) and "bold,italic" or "bold" 125 | 126 | local buffer = { 127 | -- get_search_term(props), 128 | -- get_diagnostic_label(props), 129 | -- { " ", get_git_diff(props) or "" }, 130 | { ft_icon, " ", guifg = ft_color }, 131 | { 132 | filename, 133 | gui = modified, 134 | }, 135 | } 136 | return buffer 137 | end, 138 | }) 139 | -------------------------------------------------------------------------------- /lua/configs/terminal/flatten.lua: -------------------------------------------------------------------------------- 1 | ---@type Terminal? 2 | local saved_terminal 3 | 4 | require("flatten").setup({ 5 | window = { 6 | open = "smart", 7 | }, 8 | nest_if_no_args = true, 9 | integrations = { 10 | wezterm = true, 11 | }, 12 | hooks = { 13 | pipe_path = function() 14 | -- If running in a terminal inside Neovim: 15 | local nvim = vim.env.NVIM 16 | if nvim then 17 | return nvim 18 | end 19 | 20 | -- If running in a Wezterm terminal, 21 | -- all tabs/windows/os-windows in the same instance of wezterm will open in the first neovim instance 22 | local wezterm = vim.env.WEZTERM_UNIX_SOCKET 23 | if not wezterm then 24 | return 25 | end 26 | 27 | local addr = ("%s/%s"):format( 28 | vim.fn.stdpath("run"), 29 | "wezterm.nvim-" 30 | .. wezterm:match("gui%-sock%-(%d+)") 31 | .. "-" 32 | .. vim.fn.getcwd(-1):gsub("/", "_") 33 | ) 34 | if not vim.uv.fs_stat(addr) then 35 | vim.fn.serverstart(addr) 36 | end 37 | 38 | return addr 39 | end, 40 | should_block = function(argv) 41 | return vim.tbl_contains(argv, "-b") 42 | end, 43 | -- should_nest = function(argv) 44 | -- -- return vim.tbl_contains(argv, "-n") 45 | -- return false 46 | -- end, 47 | guest_data = function() 48 | return { 49 | pane = require("wezterm").get_current_pane(), 50 | } 51 | end, 52 | pre_open = function(_opts) 53 | local term = require("toggleterm.terminal") 54 | local id = term.get_focused_id() 55 | saved_terminal = term.get(id) 56 | end, 57 | no_files = function() 58 | -- If there's already a Neovim instance for this cwd, switch to it 59 | local wezterm = require("wezterm") 60 | 61 | local pane = wezterm.get_current_pane() 62 | if pane then 63 | require("wezterm").switch_pane.id(pane) 64 | end 65 | 66 | return { 67 | nest = false, 68 | block = false, 69 | } 70 | end, 71 | post_open = function(opts) 72 | local bufnr, winnr, ft, is_blocking, is_diff = 73 | opts.bufnr, opts.winnr, opts.filetype, opts.is_blocking, opts.is_diff 74 | 75 | if is_blocking and saved_terminal then 76 | -- Hide the terminal while it's blocking 77 | saved_terminal:close() 78 | elseif not is_diff then 79 | -- If it's a normal file, just switch to its window 80 | vim.api.nvim_set_current_win(winnr) 81 | 82 | -- If it's not in the current wezterm pane, switch to that pane. 83 | local wezterm = require("wezterm") 84 | 85 | local pane = wezterm.get_current_pane() 86 | if pane then 87 | require("wezterm").switch_pane.id(pane) 88 | end 89 | end 90 | 91 | -- If the file is a git commit, create one-shot autocmd to delete its buffer on write 92 | -- If you just want the toggleable terminal integration, ignore this bit 93 | if ft == "gitcommit" or ft == "gitrebase" then 94 | local version = vim.api.nvim_buf_get_changedtick(bufnr) 95 | -- vim.schedule(vim.cmd.startinsert) 96 | vim.api.nvim_create_autocmd("BufWritePost", { 97 | buffer = bufnr, 98 | callback = vim.schedule_wrap(function() 99 | if vim.api.nvim_buf_get_changedtick(bufnr) == version then 100 | return 101 | end 102 | require("bufdelete").bufdelete(bufnr, true) 103 | end), 104 | }) 105 | end 106 | end, 107 | -- After blocking ends (for a git commit, etc), reopen the terminal 108 | block_end = vim.schedule_wrap(function(opts) 109 | if type(opts.data) == "table" and opts.data.pane then 110 | require("wezterm").switch_pane.id(opts.data.pane) 111 | end 112 | if saved_terminal then 113 | saved_terminal:open() 114 | saved_terminal = nil 115 | end 116 | end), 117 | }, 118 | }) 119 | -------------------------------------------------------------------------------- /lua/configs/terminal/toggleterm.lua: -------------------------------------------------------------------------------- 1 | require("toggleterm").setup({ 2 | start_in_insert = false, 3 | close_on_exit = true, 4 | shade_terminals = false, 5 | auto_scroll = false, 6 | persist_size = false, 7 | on_create = function(term) 8 | vim.bo[term.bufnr].filetype = "terminal" 9 | local win 10 | vim.api.nvim_create_autocmd("BufEnter", { 11 | buffer = term.bufnr, 12 | callback = function() 13 | win = vim.api.nvim_get_current_win() 14 | end, 15 | }) 16 | vim.api.nvim_create_autocmd("TermClose", { 17 | buffer = term.bufnr, 18 | once = true, 19 | callback = function() 20 | -- when leaving, open another terminal buffer in the same window 21 | local terms = vim 22 | .iter(vim.api.nvim_list_bufs()) 23 | :filter(function(buf) 24 | return vim.bo[buf].buftype == "terminal" and buf ~= term.bufnr 25 | end) 26 | :totable() 27 | 28 | local win_bufs = vim 29 | .iter(vim.api.nvim_list_wins()) 30 | :map(vim.api.nvim_win_get_buf) 31 | :fold({}, function(acc, v) 32 | acc[v] = v 33 | return acc 34 | end) 35 | 36 | local target 37 | for _, t in ipairs(terms) do 38 | -- fall back to the first term if no hidden terms are found 39 | target = target or t 40 | if win_bufs[t] == nil then 41 | target = t -- use the first hidden term if found 42 | break 43 | end 44 | end 45 | 46 | if win and target and vim.api.nvim_buf_is_valid(target) then 47 | vim.api.nvim_win_set_buf(win, target) 48 | vim.api.nvim_create_autocmd("WinEnter", { 49 | once = true, 50 | callback = vim.schedule_wrap(function() 51 | if win and vim.api.nvim_win_is_valid(win) then 52 | vim.api.nvim_set_current_win(win) 53 | win = nil 54 | end 55 | end), 56 | }) 57 | end 58 | end, 59 | }) 60 | end, 61 | }) 62 | -------------------------------------------------------------------------------- /lua/configs/ui/edgy.lua: -------------------------------------------------------------------------------- 1 | local function get_height() 2 | return math.floor((vim.o.lines / 3.5) + 0.5) 3 | end 4 | 5 | ---@type table 6 | local View = {} 7 | View.__index = View 8 | 9 | ---@param props table 10 | function View.new(props) 11 | return setmetatable(props, View) 12 | end 13 | 14 | ---@param props table 15 | function View:extend(props) 16 | return setmetatable(props, { __index = self }) 17 | end 18 | 19 | local bottom = View.new({ 20 | size = { height = get_height }, 21 | filter = function(_buf, win) 22 | return vim.api.nvim_win_get_config(win).zindex == nil 23 | end, 24 | }) 25 | 26 | local sidebar = View.new({ 27 | size = { 28 | width = function() 29 | if vim.o.columns > 120 then 30 | return math.floor((vim.o.columns * 0.1) + 0.5) 31 | else 32 | return math.floor((vim.o.columns * 0.25) + 0.5) 33 | end 34 | end, 35 | }, 36 | }) 37 | 38 | function _G._edgywb() 39 | if not package.loaded.dropbar then 40 | return "" 41 | end 42 | local win = vim.api.nvim_get_current_win() 43 | local buf = vim.api.nvim_get_current_buf() 44 | local bar = _G.dropbar.bars[buf][win] 45 | if vim.bo[buf].bt == "terminal" and not vim.b[buf].__dropbar_ready then 46 | local old = bar.update_hover_hl 47 | ---@diagnostic disable-next-line: duplicate-set-field 48 | bar.update_hover_hl = function(self, col) 49 | if col ~= nil then 50 | col = col - 3 51 | end 52 | return old(self, col) 53 | end 54 | vim.b[buf].__dropbar_ready = true 55 | end 56 | -- _G.edgy_winbar() .. 57 | return _G.dropbar():gsub(" %s+$", "") 58 | end 59 | 60 | local function is_float(win) 61 | return vim.api.nvim_win_get_config(win).zindex ~= nil 62 | end 63 | 64 | local trouble = bottom:extend({ 65 | ft = "trouble", 66 | }) 67 | 68 | local function get_rhs_width() 69 | if vim.o.columns > 120 then 70 | return math.floor(vim.o.columns * 0.35) 71 | else 72 | return math.floor(vim.o.columns * 0.6) 73 | end 74 | end 75 | 76 | local neogit = View.new({ 77 | size = { 78 | width = get_rhs_width, 79 | }, 80 | }) 81 | 82 | local opts = { 83 | -- right = { 84 | -- terminal:extend({ 85 | -- open = function() 86 | -- willothy.terminal.vertical:open() 87 | -- end, 88 | -- filter = function(buf, win) 89 | -- if not terminal.filter(buf, win) then 90 | -- return false 91 | -- end 92 | -- local term = require("toggleterm.terminal").find(function(term) 93 | -- return term.bufnr == buf 94 | -- end) 95 | -- return (vim.bo[buf].buftype == "terminal" and term == nil) 96 | -- or (term ~= nil and term.direction == "vertical") 97 | -- end, 98 | -- size = { 99 | -- width = get_rhs_width, 100 | -- }, 101 | -- }), 102 | -- }, 103 | -- left = { 104 | -- }, 105 | bottom = { 106 | { 107 | ft = "terminal", 108 | title = "%{%v:lua._edgywb()%}", 109 | wo = { 110 | number = false, 111 | relativenumber = false, 112 | cursorline = false, 113 | winhighlight = "Normal:Normal", 114 | }, 115 | open = function() 116 | require("willothy.terminal").main:open() 117 | end, 118 | filter = function(buf, win) 119 | if vim.bo[buf].buftype ~= "terminal" or is_float(win) then 120 | return false 121 | end 122 | local term = require("toggleterm.terminal").find(function(term) 123 | return term.bufnr == buf 124 | end) 125 | return (vim.bo[buf].buftype == "terminal" and term == nil) 126 | or (term ~= nil and term.direction == "horizontal") 127 | end, 128 | size = { height = get_height }, 129 | }, 130 | bottom:extend({ 131 | ft = "dapui_console", 132 | title = "Debug Console", 133 | wo = { winbar = " Debug Console" }, 134 | open = function() 135 | require("dapui").open() 136 | end, 137 | }), 138 | bottom:extend({ 139 | ft = "dap-repl", 140 | title = "Debug REPL", 141 | wo = { winbar = false, statuscolumn = "" }, 142 | open = function() 143 | require("dapui").open() 144 | end, 145 | }), 146 | bottom:extend({ 147 | ft = "neotest-output-panel", 148 | title = "Neotest output", 149 | open = function() 150 | require("neotest").output_panel.open() 151 | end, 152 | }), 153 | -- bottom:extend({ 154 | -- ft = "noice", 155 | -- open = function() 156 | -- vim.cmd.Noice() 157 | -- end, 158 | -- }), 159 | bottom:extend({ 160 | ft = "qf", 161 | title = "QuickFix", 162 | wo = { 163 | winhighlight = "Normal:Normal", 164 | }, 165 | open = function() 166 | vim.cmd.copen() 167 | end, 168 | }), 169 | bottom:extend({ 170 | ft = "spectre_panel", 171 | title = "Spectre", 172 | wo = { 173 | number = false, 174 | relativenumber = false, 175 | }, 176 | open = function() 177 | require("spectre").open() 178 | end, 179 | }), 180 | trouble, 181 | }, 182 | 183 | options = { 184 | -- left = { size = 0.25 }, 185 | bottom = { size = get_height }, 186 | }, 187 | 188 | exit_when_last = false, 189 | close_when_all_hidden = true, 190 | 191 | wo = { 192 | winhighlight = "", 193 | }, 194 | 195 | keys = { 196 | ["Q"] = false, 197 | }, 198 | 199 | animate = { 200 | enabled = true, 201 | fps = 60, 202 | cps = 160, 203 | }, 204 | } 205 | 206 | for _, pos in ipairs({ "top", "bottom", "left", "right" }) do 207 | opts[pos] = opts[pos] or {} 208 | table.insert(opts[pos] --[[@as table]], { 209 | ft = "snacks_terminal", 210 | size = { height = get_height }, 211 | -- title = "%{b:snacks_terminal.id}: %{b:term_title}", 212 | title = "%{%v:lua.dropbar()%}: %{b:term_title}", 213 | 214 | -- title = "%{%v:lua._edgywb()%}", 215 | wo = { 216 | number = false, 217 | relativenumber = false, 218 | cursorline = false, 219 | winhighlight = "Normal:Normal", 220 | }, 221 | filter = function(_buf, win) 222 | return vim.w[win].snacks_win 223 | and vim.w[win].snacks_win.position == pos 224 | and vim.w[win].snacks_win.relative == "editor" 225 | and not vim.w[win].trouble_preview 226 | end, 227 | }) 228 | end 229 | 230 | require("edgy").setup(opts) 231 | -------------------------------------------------------------------------------- /lua/configs/ui/icons.lua: -------------------------------------------------------------------------------- 1 | require("nvim-web-devicons").setup({ 2 | override_by_filename = { 3 | terminal = { 4 | -- icon = "", 5 | icon = "", 6 | color = "#7ec699", 7 | name = "ToggleTerm", 8 | }, 9 | }, 10 | }) 11 | -------------------------------------------------------------------------------- /lua/configs/ui/murmur.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | local au = vim.api.nvim_create_augroup("murmur_au", { clear = true }) 4 | local enabled = true 5 | local active_win 6 | 7 | vim.api.nvim_set_hl(0, "Cursorword", {}) 8 | 9 | require("murmur").setup({ 10 | exclude_filetypes = { 11 | "neo-tree", 12 | "noice", 13 | "SidebarNvim", 14 | "terminal", 15 | "trouble", 16 | }, 17 | cursor_rgb = "Cursorword", 18 | cursor_rgb_current = "Cursorword", 19 | cursor_rgb_always_use_config = true, 20 | callbacks = { 21 | function() 22 | if active_win and vim.api.nvim_win_is_valid(active_win) then 23 | vim.api.nvim_win_close(active_win, true) 24 | end 25 | active_win = nil 26 | end, 27 | }, 28 | }) 29 | 30 | -- TODO: this functionality should not be 31 | -- in the plugin config. 32 | function M.enable() 33 | enabled = true 34 | end 35 | 36 | function M.disable() 37 | enabled = false 38 | M.hide() 39 | end 40 | 41 | function M.toggle() 42 | enabled = not enabled 43 | if not enabled then 44 | M.hide() 45 | end 46 | end 47 | 48 | function M.hide() 49 | if active_win and vim.api.nvim_win_is_valid(active_win) then 50 | vim.api.nvim_win_close(active_win, true) 51 | end 52 | active_win = nil 53 | end 54 | 55 | function M.show() 56 | if 57 | (active_win and vim.api.nvim_win_is_valid(active_win)) 58 | or (vim.w.cursor_word == "" or vim.w.cursor_word == nil) 59 | or (require("noice.lsp.docs").get("hover"):win() ~= nil) 60 | or not enabled 61 | then 62 | return 63 | end 64 | 65 | local buf, win = vim.diagnostic.open_float({ 66 | scope = "cursor", 67 | close_events = { 68 | "InsertEnter", 69 | "TextChanged", 70 | "BufLeave", 71 | }, 72 | }) 73 | if not buf or not win then 74 | return 75 | end 76 | active_win = win 77 | vim.api.nvim_create_autocmd("WinClosed", { 78 | group = au, 79 | buffer = buf, 80 | once = true, 81 | callback = function() 82 | active_win = nil 83 | end, 84 | }) 85 | end 86 | 87 | local docs = require("noice.lsp.docs") 88 | local get = docs.get 89 | ---@diagnostic disable-next-line: duplicate-set-field 90 | docs.get = function(name) 91 | if name == "hover" then 92 | M.hide() 93 | end 94 | return get(name) 95 | end 96 | 97 | require("snacks").toggle 98 | .new({ 99 | name = "Diagnostic float", 100 | get = function() 101 | return enabled 102 | end, 103 | set = function(value) 104 | enabled = value 105 | if enabled then 106 | M.show() 107 | else 108 | M.hide() 109 | end 110 | end, 111 | }) 112 | :map("uF") 113 | 114 | -- To create IDE-like no blinking diagnostic message with `cursor` scope. (should be paired with the callback above) 115 | vim.api.nvim_create_autocmd("CursorHold", { 116 | group = au, 117 | pattern = "*", 118 | callback = M.show, 119 | }) 120 | -------------------------------------------------------------------------------- /lua/configs/ui/noice.lua: -------------------------------------------------------------------------------- 1 | local require = require("noice.util.lazy") 2 | 3 | ---@diagnostic disable-next-line: missing-fields 4 | require("noice").setup({ 5 | status = { 6 | -- progress = { 7 | -- event = "lsp", 8 | -- kind = "progress", 9 | -- }, 10 | }, 11 | presets = { 12 | long_message_to_split = true, 13 | inc_rename = true, 14 | }, 15 | smart_move = { 16 | enabled = true, 17 | }, 18 | views = { 19 | split = {}, 20 | mini = { 21 | win_options = { 22 | winblend = 0, 23 | }, 24 | }, 25 | cmdline_popup = { 26 | position = { 27 | row = "35%", 28 | col = "50%", 29 | }, 30 | border = { 31 | style = "solid", 32 | }, 33 | win_options = { 34 | winhighlight = { 35 | Normal = "NormalFloat", 36 | }, 37 | }, 38 | size = { 39 | width = "auto", 40 | height = "auto", 41 | }, 42 | }, 43 | hover = { 44 | silent = true, 45 | border = { 46 | style = "solid", 47 | padding = { 0, 0 }, 48 | }, 49 | }, 50 | popup = { 51 | border = { 52 | style = "solid", 53 | padding = { 0, 0 }, 54 | }, 55 | }, 56 | popupmenu = { 57 | relative = "editor", 58 | position = { 59 | row = "40%", 60 | col = "50%", 61 | }, 62 | size = { 63 | width = 79, 64 | height = 10, 65 | }, 66 | border = { 67 | style = "solid", 68 | padding = { 0, 0 }, 69 | }, 70 | }, 71 | }, 72 | lsp = { 73 | --override markdown rendering so that **cmp** and other plugins use **Treesitter** 74 | override = { 75 | ["vim.lsp.util.convert_input_to_markdown_lines"] = true, 76 | ["vim.lsp.util.stylize_markdown"] = true, 77 | ["cmp.entry.get_documentation"] = true, 78 | }, 79 | progress = { 80 | enabled = false, 81 | view = "mini", 82 | }, 83 | signature = { 84 | enabled = true, 85 | auto_open = { 86 | enabled = true, 87 | trigger = true, 88 | throttle = 50, 89 | }, 90 | opts = {}, 91 | }, 92 | hover = { 93 | silent = true, 94 | enabled = true, 95 | border = { 96 | style = "solid", 97 | padding = { 0, 0 }, 98 | }, 99 | }, 100 | }, 101 | cmdline = { 102 | view = "cmdline_popup", 103 | -- view = "cmdline", 104 | format = { 105 | cmdline = { 106 | pattern = "^:", 107 | icon = "", 108 | -- icon = ":", 109 | lang = "vim", 110 | }, 111 | search_down = { 112 | view = "cmdline", 113 | }, 114 | search_up = { 115 | view = "cmdline", 116 | }, 117 | python = { 118 | pattern = { "^:%s*pyt?h?o?n?%s+", "^:%s*py?t?h?o?n%s*=%s*" }, 119 | icon = "󰌠", 120 | lang = "python", 121 | title = " python ", 122 | }, 123 | }, 124 | }, 125 | popupmenu = { 126 | enabled = true, 127 | backend = "nui", 128 | }, 129 | messages = { 130 | enabled = true, -- enables the Noice messages UI 131 | view = "notify", -- default view for messages 132 | view_error = "notify", -- view for errors 133 | view_warn = "notify", -- view for warnings 134 | view_history = "messages", -- view for :messages 135 | view_search = "virtualtext", -- view for search count messages. Set to `false` to disable 136 | }, 137 | notify = { 138 | enabled = true, 139 | }, 140 | routes = {}, 141 | }) 142 | -------------------------------------------------------------------------------- /lua/configs/windows/bufresize.lua: -------------------------------------------------------------------------------- 1 | require("bufresize").setup({ 2 | register = { 3 | trigger_events = { "BufWinEnter", "WinEnter" }, 4 | keys = {}, 5 | }, 6 | resize = { 7 | trigger_events = { 8 | "VimResized", 9 | }, 10 | increment = 1, 11 | }, 12 | }) 13 | -------------------------------------------------------------------------------- /lua/configs/windows/focus.lua: -------------------------------------------------------------------------------- 1 | local focus = require("focus") 2 | 3 | local ignore_filetypes = { 4 | ["oil"] = true, 5 | ["neo-tree"] = true, 6 | ["dap-repl"] = true, 7 | ["neotest-summary"] = true, 8 | SidebarNvim = true, 9 | trouble = true, 10 | terminal = true, 11 | dapui_console = true, 12 | dapui_watches = true, 13 | dapui_stacks = true, 14 | dapui_breakpoints = true, 15 | dapui_scopes = true, 16 | NeogitStatus = true, 17 | NeogitLogView = true, 18 | NeogitPopup = true, 19 | NeogitCommitMessage = true, 20 | OverseerList = true, 21 | fish = true, 22 | Avante = true, 23 | AvanteInput = true, 24 | AvanteSelectedFiles = true, 25 | } 26 | 27 | local function should_disable(buf, win) 28 | win = win or vim.fn.bufwinid(buf) 29 | if win and require("edgy").get_win(win) then 30 | return true 31 | else 32 | return ignore_filetypes[vim.bo[buf].filetype] 33 | end 34 | end 35 | 36 | vim.api.nvim_create_autocmd("FileType", { 37 | callback = function(args) 38 | local buf = args.buf 39 | if should_disable(buf) then 40 | vim.b[buf].focus_disable = true 41 | else 42 | vim.b[buf].focus_disable = nil 43 | end 44 | end, 45 | }) 46 | 47 | for _, win in ipairs(vim.api.nvim_list_wins()) do 48 | local buf = vim.api.nvim_win_get_buf(win) 49 | if 50 | vim.api.nvim_win_get_config(win).zindex == nil and should_disable(buf, win) 51 | then 52 | vim.b[buf].focus_disable = true 53 | end 54 | end 55 | 56 | vim.schedule(function() 57 | focus.setup({ 58 | ui = { 59 | cursorline = false, 60 | signcolumn = false, 61 | winhighlight = false, 62 | }, 63 | autoresize = { 64 | center_hsplits = false, 65 | }, 66 | }) 67 | end) 68 | -------------------------------------------------------------------------------- /lua/configs/windows/mini-animate.lua: -------------------------------------------------------------------------------- 1 | local subresize = function(easing) 2 | return function(sizes_from, sizes_to) 3 | -- Don't animate single window 4 | if #vim.tbl_keys(sizes_from) == 1 then 5 | return {} 6 | end 7 | 8 | -- Compute number of steps 9 | local n_steps = 0 10 | for win_id, dims_from in pairs(sizes_from) do 11 | local height_absdiff = 12 | math.abs(sizes_to[win_id].height - dims_from.height) 13 | local width_absdiff = math.abs(sizes_to[win_id].width - dims_from.width) 14 | n_steps = math.max(n_steps, height_absdiff, width_absdiff) 15 | end 16 | if n_steps <= 1 then 17 | return {} 18 | end 19 | 20 | -- Make subresize array 21 | local res = {} 22 | for i = 1, n_steps do 23 | local coef = i / n_steps 24 | local sub_res = {} 25 | for win_id, dims_from in pairs(sizes_from) do 26 | sub_res[win_id] = { 27 | height = easing(dims_from.height, sizes_to[win_id].height, coef), 28 | width = easing(dims_from.width, sizes_to[win_id].width, coef), 29 | } 30 | end 31 | res[i] = sub_res 32 | end 33 | 34 | return res 35 | end 36 | end 37 | 38 | ---@alias ease_fn fun(from: integer, to: integer, coef: number): integer 39 | 40 | ---@type table 41 | local easing = {} 42 | 43 | function easing.linear(from, to, coef) 44 | return math.floor(from + (to - from) * coef) 45 | end 46 | 47 | function easing.ease_in(from, to, coef) 48 | return math.floor(from + (to - from) * coef ^ 2) 49 | end 50 | 51 | function easing.ease_out(from, to, coef) 52 | return math.floor(from + (to - from) * (1 - (1 - coef) ^ 2)) 53 | end 54 | 55 | local function duration(ms) 56 | return function(_, n) 57 | return ms / n 58 | end 59 | end 60 | 61 | local anim = require("mini.animate") 62 | anim.setup({ 63 | open = { 64 | enable = false, 65 | winconfig = anim.gen_winconfig.wipe({ direction = "from_edge" }), 66 | winblend = anim.gen_winblend.linear({ from = 100, to = 0 }), 67 | timing = anim.gen_timing.quartic({ 68 | easing = "in", 69 | duration = 500, 70 | unit = "total", 71 | }), 72 | }, 73 | close = { 74 | enable = false, 75 | winconfig = anim.gen_winconfig.wipe(), 76 | winblend = anim.gen_winblend.linear({ from = 40, to = 100 }), 77 | timing = anim.gen_timing.quartic({ 78 | easing = "out", 79 | duration = 400, 80 | unit = "total", 81 | }), 82 | }, 83 | cursor = { enable = false }, 84 | scroll = { 85 | enable = false, 86 | }, 87 | resize = { 88 | enable = true, 89 | timing = duration(20), 90 | subresize = subresize(easing.ease_out), 91 | }, 92 | }) 93 | vim.api.nvim_exec_autocmds("WinScrolled", {}) 94 | -------------------------------------------------------------------------------- /lua/configs/windows/smart-splits.lua: -------------------------------------------------------------------------------- 1 | local smart_splits = require("smart-splits") 2 | 3 | smart_splits.setup({ 4 | at_edge = "stop", 5 | resize_mode = { 6 | hooks = { 7 | on_leave = function(...) 8 | ---@diagnostic disable-next-line: redundant-parameter 9 | require("bufresize").register(...) 10 | end, 11 | }, 12 | }, 13 | ignore_events = { 14 | "WinResized", 15 | "BufWinEnter", 16 | "BufEnter", 17 | "WinEnter", 18 | }, 19 | }) 20 | -------------------------------------------------------------------------------- /lua/configs/windows/window-picker.lua: -------------------------------------------------------------------------------- 1 | require("window-picker").setup({ 2 | show_prompt = false, 3 | hint = "floating-big-letter", 4 | filter_rules = { 5 | autoselect_one = false, 6 | include_current_win = false, 7 | bo = { 8 | filetype = { 9 | "noice", 10 | }, 11 | buftype = { 12 | "nofile", 13 | "nowrite", 14 | }, 15 | }, 16 | }, 17 | selection_chars = "asdfwertzxcv", 18 | create_chars = "hjkl", 19 | picker_config = { 20 | floating_big_letter = { 21 | font = { 22 | -- pick chars 23 | w = "w", 24 | a = "a", 25 | s = "s", 26 | d = "d", 27 | f = "f", 28 | e = "e", 29 | r = "r", 30 | t = "t", 31 | z = "z", 32 | x = "x", 33 | c = "c", 34 | v = "v", 35 | -- create chars 36 | h = "h", 37 | j = "j", 38 | k = "k", 39 | l = "l", 40 | }, 41 | window = { 42 | config = { 43 | border = "none", 44 | }, 45 | options = { 46 | winhighlight = "NormalFloat:TabLineSel,FloatBorder:TabLineSel", 47 | }, 48 | }, 49 | }, 50 | }, 51 | }) 52 | -------------------------------------------------------------------------------- /lua/plugins/fun.lua: -------------------------------------------------------------------------------- 1 | return { 2 | { 3 | "nvzone/typr", 4 | dependencies = { 5 | "nvzone/volt", 6 | }, 7 | opts = {}, 8 | cmd = { "Typr", "TyprStats" }, 9 | }, 10 | { 11 | "willothy/strat-hero.nvim", 12 | opts = {}, 13 | cmd = "StratHero", 14 | }, 15 | { 16 | "seandewar/killersheep.nvim", 17 | opts = {}, 18 | cmd = "KillKillKill", 19 | }, 20 | { 21 | "seandewar/nvimesweeper", 22 | config = true, 23 | cmd = "Nvimesweeper", 24 | }, 25 | -- -- Sound effects 26 | -- { 27 | -- "jackplus-xyz/player-one.nvim", 28 | -- opts = { 29 | -- is_enabled = true, 30 | -- theme = "chiptune", 31 | -- }, 32 | -- event = "VeryLazy", 33 | -- }, 34 | } 35 | -------------------------------------------------------------------------------- /lua/plugins/libraries.lua: -------------------------------------------------------------------------------- 1 | return { 2 | "famiu/bufdelete.nvim", 3 | { 4 | "nvim-telescope/telescope-fzf-native.nvim", 5 | build = "make", 6 | }, 7 | { 8 | "https://github.com/leafo/magick", 9 | build = "rockspec", 10 | }, 11 | { 12 | "MunifTanjim/nui.nvim", 13 | -- dir = "~/projects/lua/nui.nvim/", 14 | }, 15 | { 16 | -- "willothy/nui-components.nvim", 17 | "grapp-dev/nui-components.nvim", 18 | -- dir = "~/projects/lua/nui-components.nvim/", 19 | }, 20 | { 21 | "kkharji/sqlite.lua", 22 | -- dir = "~/projects/lua/sqlite.lua/", 23 | }, 24 | { 25 | "willothy/llvm-nvim", 26 | dir = "~/projects/lua/llm-nvim", 27 | config = function() end, 28 | }, 29 | -- { 30 | -- "willothy/async-sqlite", 31 | -- 32 | -- -- see lazy.nvim docs (`config.dev`): https://lazy.folke.io/configuration 33 | -- dir = "~/projects/lua/async-sqlite.nvim/", 34 | -- 35 | -- -- optional, see `lua/async-sqlite/init.lua` 36 | -- dependencies = "saghen/blink.download", 37 | -- 38 | -- build = "cargo build --release", 39 | -- }, 40 | -- { 41 | -- "willothy/lua-std", 42 | -- dir = "~/projects/lua/lua-std/", 43 | -- config = true, 44 | -- }, 45 | -- { 46 | -- "relua", 47 | -- dir = "~/projects/lua/relua/", 48 | -- config = function() 49 | -- require("relua").setup({ 50 | -- db_path = vim.fn.stdpath("data") .. "/databases/relua.db", 51 | -- }) 52 | -- end, 53 | -- event = "VeryLazy", 54 | -- }, 55 | { 56 | "willothy/durable.nvim", 57 | dependencies = { 58 | "kkharji/sqlite.lua", 59 | }, 60 | event = "VeryLazy", 61 | config = true, 62 | }, 63 | "nvim-lua/plenary.nvim", 64 | { 65 | "nvim-neotest/nvim-nio", 66 | name = "nio", 67 | }, 68 | -- { 69 | -- "willothy/libsql-lua", 70 | -- -- name = "libsql-lua", 71 | -- dir = "~/projects/cxx/libsql-lua/", 72 | -- -- build = "make", 73 | -- }, 74 | } 75 | -------------------------------------------------------------------------------- /lua/plugins/lsp.lua: -------------------------------------------------------------------------------- 1 | return { 2 | -- DEVELOPMENT & TESTING -- 3 | { 4 | "ThePrimeagen/refactoring.nvim", 5 | config = true, 6 | cmd = "Refactor", 7 | }, 8 | { 9 | -- "willothy/lazydev.nvim", 10 | "folke/lazydev.nvim", 11 | -- enabled = false, 12 | dependencies = { 13 | "Bilal2453/luvit-meta", -- type defs for vim.uv 14 | }, 15 | ft = "lua", 16 | config = function() 17 | require("lazydev").setup({ 18 | -- exclude = { 19 | -- "~/projects/lua", 20 | -- }, 21 | integrations = { 22 | lspconfig = true, 23 | }, 24 | library = { 25 | "luvit-meta/library", 26 | vim.env.VIMRUNTIME, 27 | "~/projects/lua/llm-nvim/", 28 | -- unpack(vim.api.nvim_get_runtime_file("lua/llm/*", true)), 29 | }, 30 | }) 31 | end, 32 | }, 33 | { 34 | "davidmh/mdx.nvim", 35 | config = true, 36 | lazy = false, 37 | }, 38 | { 39 | "nvim-neotest/neotest", 40 | config = function() 41 | require("willothy.testing") 42 | end, 43 | dependencies = { 44 | "rouge8/neotest-rust", 45 | }, 46 | cmd = "Neotest", 47 | }, 48 | -- LSP UI -- 49 | { 50 | "j-hui/fidget.nvim", 51 | opts = { 52 | progress = { 53 | display = { 54 | overrides = { 55 | rust_analyzer = { name = "rust-analyzer" }, 56 | lua_ls = { name = "lua-ls" }, 57 | }, 58 | }, 59 | }, 60 | }, 61 | event = "LspAttach", 62 | }, 63 | { 64 | "smjonas/inc-rename.nvim", 65 | config = function() 66 | require("inc_rename").setup({ 67 | show_message = false, 68 | post_hook = function(opts) 69 | local nrenames, nfiles = unpack(vim 70 | .iter(opts) 71 | :map(function(_, renames) 72 | return vim.tbl_count(renames) 73 | end) 74 | :fold({ 0, 0 }, function(acc, val) 75 | acc[1] = acc[1] + val 76 | acc[2] = acc[2] + 1 77 | return acc 78 | end)) 79 | vim.notify( 80 | string.format( 81 | "%d instance%s in %d files", 82 | nrenames, 83 | nrenames == 1 and "" or "s", 84 | nfiles 85 | ), 86 | vim.log.levels.INFO, 87 | { 88 | title = "RENAMED", 89 | } 90 | ) 91 | end, 92 | }) 93 | end, 94 | cmd = "IncRename", 95 | }, 96 | -- LANGUAGE SERVERS & RELATED TOOLS -- 97 | { 98 | "williamboman/mason.nvim", 99 | cmd = "Mason", 100 | config = true, 101 | }, 102 | { 103 | "neovim/nvim-lspconfig", 104 | config = function() 105 | require("willothy.lsp") 106 | end, 107 | event = "VeryLazy", 108 | }, 109 | -- { 110 | -- "Zeioth/garbage-day.nvim", 111 | -- config = true, 112 | -- event = "LspAttach", 113 | -- }, 114 | -- { 115 | -- "p00f/clangd_extensions.nvim", 116 | -- config = true, 117 | -- event = "LspAttach", 118 | -- }, 119 | -- DIAGNOSTICS & FORMATTING -- 120 | { 121 | "stevearc/conform.nvim", 122 | config = function() 123 | require("willothy.formatting") 124 | end, 125 | event = "BufWritePre", 126 | }, 127 | { 128 | "mfussenegger/nvim-lint", 129 | config = function() 130 | require("willothy.linting") 131 | end, 132 | event = "VeryLazy", 133 | }, 134 | { 135 | "dgagn/diagflow.nvim", 136 | config = function() 137 | require("willothy.diagnostics") 138 | end, 139 | event = "DiagnosticChanged", 140 | }, 141 | { 142 | "boltlessengineer/sense.nvim", 143 | init = function() 144 | vim.g.sense_nvim = { 145 | -- show hint in statuscolumn, but not in the window itself 146 | presets = { 147 | virtualtext = { 148 | enabled = false, 149 | }, 150 | statuscolumn = { 151 | enabled = true, 152 | }, 153 | }, 154 | } 155 | end, 156 | event = "DiagnosticChanged", 157 | }, 158 | -- COMPLETION -- 159 | { 160 | "Saghen/blink.cmp", 161 | dependencies = { 162 | "Saghen/blink.compat", 163 | -- "giuxtaposition/blink-cmp-copilot", 164 | "fang2hou/blink-copilot", 165 | "copilotlsp-nvim/copilot-lsp", 166 | 167 | -- "Kaiser-Yang/blink-cmp-avante", 168 | 169 | -- "rafamadriz/friendly-snippets", 170 | "Saecki/crates.nvim", 171 | "windwp/nvim-ts-autotag", 172 | }, 173 | event = { "InsertEnter", "CmdlineEnter" }, 174 | build = "cargo build --release", 175 | config = function() 176 | require("willothy.completion") 177 | end, 178 | }, 179 | { 180 | "windwp/nvim-autopairs", 181 | opts = { 182 | disable_filetype = { "snacks_picker_input" }, 183 | }, 184 | event = "InsertEnter", 185 | }, 186 | -- AI 187 | { 188 | "copilotlsp-nvim/copilot-lsp", 189 | dependencies = { 190 | "fang2hou/blink-copilot", 191 | }, 192 | -- dir = "~/projects/lua/copilot-lsp", 193 | init = function() 194 | vim.g.copilot_nes_debounce = 250 195 | vim.lsp.enable("copilot_ls") 196 | end, 197 | }, 198 | { 199 | "kylechui/nvim-surround", 200 | config = true, 201 | event = "VeryLazy", 202 | }, 203 | -- DEBUGGING -- 204 | { 205 | "mfussenegger/nvim-dap", 206 | dependencies = { 207 | "theHamsta/nvim-dap-virtual-text", 208 | }, 209 | }, 210 | { 211 | "rcarriga/nvim-dap-ui", 212 | dependencies = { 213 | "mfussenegger/nvim-dap", 214 | }, 215 | config = function() 216 | require("willothy.debugging") 217 | end, 218 | }, 219 | -- Individual debugger plugins 220 | "jbyuki/one-small-step-for-vimkind", 221 | } 222 | -------------------------------------------------------------------------------- /lua/plugins/ui.lua: -------------------------------------------------------------------------------- 1 | return { 2 | -- LAYOUT / CORE UI -- 3 | { 4 | "folke/which-key.nvim", 5 | opts = { 6 | delay = 50, 7 | preset = "helix", 8 | win = { 9 | border = "single", 10 | wo = { 11 | winhl = "FloatTitle:NormalFloat", 12 | }, 13 | }, 14 | }, 15 | event = "VeryLazy", 16 | }, 17 | { 18 | "nvim-tree/nvim-web-devicons", 19 | config = function() 20 | require("configs.ui.icons") 21 | end, 22 | }, 23 | { 24 | "folke/edgy.nvim", 25 | -- "willothy/edgy.nvim", 26 | -- event = "VeryLazy", 27 | config = function() 28 | require("configs.ui.edgy") 29 | end, 30 | }, 31 | { 32 | "sphamba/smear-cursor.nvim", 33 | enabled = false, 34 | event = "VeryLazy", 35 | opts = { 36 | -- legacy_computing_symbols_support = true, 37 | }, 38 | }, 39 | { 40 | "folke/noice.nvim", 41 | dependencies = { "folke/snacks.nvim" }, 42 | event = "UiEnter", 43 | config = function() 44 | require("configs.ui.noice") 45 | end, 46 | }, 47 | { 48 | "MeanderingProgrammer/render-markdown.nvim", 49 | config = function() 50 | require("render-markdown").setup({ 51 | file_types = { "markdown", "Avante", "snacks_notif" }, 52 | on = { 53 | attach = vim.schedule_wrap(function() 54 | require("render-markdown").enable() 55 | end), 56 | }, 57 | }) 58 | end, 59 | ft = { "markdown", "Avante", "snacks_notif" }, 60 | }, 61 | -- { 62 | -- "3rd/image.nvim", 63 | -- config = function() 64 | -- require("image").setup({}) 65 | -- end, 66 | -- }, 67 | { 68 | "HakonHarnes/img-clip.nvim", 69 | event = "VeryLazy", 70 | opts = { 71 | default = { 72 | embed_image_as_base64 = false, 73 | prompt_for_file_name = false, 74 | drag_and_drop = { 75 | insert_mode = true, 76 | }, 77 | }, 78 | }, 79 | }, 80 | -- SCOPE / CURSORWORD -- 81 | { 82 | "nyngwang/murmur.lua", 83 | event = "VeryLazy", 84 | config = function() 85 | require("configs.ui.murmur") 86 | end, 87 | }, 88 | -- SIDEBARS -- 89 | { 90 | "folke/trouble.nvim", 91 | cmd = "Trouble", 92 | config = function() 93 | require("configs.editor.trouble") 94 | end, 95 | }, 96 | -- WINDOWS -- 97 | { 98 | "nvim-focus/focus.nvim", 99 | dependencies = { 100 | { 101 | "echasnovski/mini.animate", 102 | optional = true, 103 | }, 104 | }, 105 | config = function() 106 | vim.api.nvim_create_autocmd("WinEnter", { 107 | once = true, 108 | callback = function() 109 | require("configs.windows.focus") 110 | end, 111 | }) 112 | end, 113 | event = "VeryLazy", 114 | }, 115 | { 116 | "echasnovski/mini.animate", 117 | enabled = false, 118 | config = function() 119 | require("configs.windows.mini-animate") 120 | end, 121 | event = "VeryLazy", 122 | }, 123 | { 124 | "willothy/nvim-window-picker", 125 | config = function() 126 | require("configs.windows.window-picker") 127 | end, 128 | }, 129 | { 130 | "mrjones2014/smart-splits.nvim", 131 | config = function() 132 | require("configs.windows.smart-splits") 133 | end, 134 | event = "VeryLazy", 135 | }, 136 | { 137 | "kwkarlwang/bufresize.nvim", 138 | config = function() 139 | require("configs.windows.bufresize") 140 | end, 141 | }, 142 | { 143 | "stevearc/stickybuf.nvim", 144 | event = "VeryLazy", 145 | opts = { 146 | get_auto_pin = function(bufnr) 147 | -- Shell terminals will all have ft `terminal`, and can be switched between. 148 | -- They should be pinned by filetype only, not bufnr. 149 | if vim.bo[bufnr].filetype == "terminal" then 150 | return "filetype" 151 | end 152 | -- Non-shell terminals should be pinned by bufnr, not filetype. 153 | if vim.bo[bufnr].buftype == "terminal" then 154 | return "bufnr" 155 | end 156 | return require("stickybuf").should_auto_pin(bufnr) 157 | end, 158 | }, 159 | }, 160 | -- STATUS -- 161 | { 162 | -- "willothy/nvim-cokeline", 163 | "plax-00/nvim-cokeline", 164 | -- branch = "pick-close-multiple", 165 | config = function() 166 | require("configs.status.cokeline") 167 | end, 168 | priority = 100, 169 | event = "UiEnter", 170 | }, 171 | { 172 | "rebelot/heirline.nvim", 173 | config = function() 174 | require("configs.status.heirline") 175 | end, 176 | priority = 100, 177 | event = "UiEnter", 178 | }, 179 | { 180 | "Bekaboo/dropbar.nvim", 181 | -- dir = "~/projects/lua/dropbar.nvim/", 182 | dependencies = { 183 | "nvim-telescope/telescope-fzf-native.nvim", 184 | }, 185 | -- version = "10", 186 | config = function() 187 | require("configs.status.dropbar") 188 | end, 189 | -- config = true, 190 | event = "UiEnter", 191 | }, 192 | -- COLORS -- 193 | { 194 | "willothy/nvim-colorizer.lua", 195 | -- dir = "~/projects/lua/nvim-colorizer.lua/", 196 | config = function() 197 | require("colorizer").setup({ 198 | user_default_options = { 199 | mode = "inline", 200 | names = false, 201 | virtualtext = "■ ", 202 | }, 203 | }) 204 | end, 205 | init = function() 206 | vim.api.nvim_create_autocmd("User", { 207 | pattern = "VeryLazy", 208 | callback = function() 209 | require("snacks").toggle 210 | .new({ 211 | name = "Colorizer", 212 | get = function() 213 | return require("colorizer").is_buffer_attached(0) ~= nil 214 | end, 215 | set = function(enabled) 216 | if enabled then 217 | require("colorizer").attach_to_buffer(0) 218 | else 219 | require("colorizer").detach_from_buffer(0) 220 | end 221 | end, 222 | }) 223 | :map("uc") 224 | end, 225 | }) 226 | end, 227 | cmd = "ColorizerToggle", 228 | }, 229 | { 230 | "rktjmp/lush.nvim", 231 | cmd = "Lushify", 232 | }, 233 | } 234 | -------------------------------------------------------------------------------- /lua/plugins/util.lua: -------------------------------------------------------------------------------- 1 | vim.g.nvim_ghost_autostart = 0 2 | 3 | return { 4 | { 5 | "jbyuki/venn.nvim", 6 | cmd = "VBox", 7 | }, 8 | { 9 | "rafcamlet/nvim-luapad", 10 | config = true, 11 | cmd = "Luapad", 12 | }, 13 | { 14 | -- TODO: Look for Lua-based and automatic alternative 15 | "lambdalisue/suda.vim", 16 | cmd = { "SudaRead", "SudaWrite" }, 17 | }, 18 | { 19 | "mbbill/undotree", 20 | cmd = { "UndotreeToggle", "UndotreeShow" }, 21 | }, 22 | { 23 | "nmac427/guess-indent.nvim", 24 | opts = { auto_cmd = true }, 25 | event = "VeryLazy", 26 | }, 27 | { 28 | "kawre/leetcode.nvim", 29 | opts = { 30 | lang = "rust", 31 | plugins = { 32 | non_standalone = true, 33 | }, 34 | }, 35 | cmd = "Leet", 36 | }, 37 | { 38 | "AckslD/nvim-neoclip.lua", 39 | opts = { 40 | enable_persistent_history = true, 41 | continuous_sync = true, 42 | }, 43 | event = "VeryLazy", 44 | }, 45 | { 46 | "rawnly/gist.nvim", 47 | config = true, 48 | cmd = { "GistCreate", "GistCreateFromFile", "GistsList" }, 49 | }, 50 | { 51 | "Saecki/crates.nvim", 52 | event = "BufRead Cargo.toml", 53 | }, 54 | -- -- Like crates.nvim but for package.json, but seems to be not as good yet 55 | -- { 56 | -- "vuki656/package-info.nvim", 57 | -- event = "BufRead package.json", 58 | -- opts = { 59 | -- -- fallback to pnpm if auto-detection doesn't work 60 | -- notifications = false, 61 | -- package_manager = "pnpm", 62 | -- }, 63 | -- }, 64 | { 65 | "tzachar/highlight-undo.nvim", 66 | config = true, 67 | event = "VeryLazy", 68 | }, 69 | { 70 | "nvchad/minty", 71 | lazy = true, 72 | }, 73 | { 74 | "krady21/compiler-explorer.nvim", 75 | config = true, 76 | cmd = { 77 | "CECompile", 78 | "CECompileLive", 79 | }, 80 | }, 81 | { 82 | "jokajak/keyseer.nvim", 83 | config = true, 84 | cmd = "KeySeer", 85 | }, 86 | { 87 | "pwntester/octo.nvim", 88 | config = true, 89 | cmd = "Octo", 90 | }, 91 | { 92 | "gbprod/stay-in-place.nvim", 93 | config = true, 94 | event = "VeryLazy", 95 | }, 96 | } 97 | -------------------------------------------------------------------------------- /lua/resession/extensions/edgy.lua: -------------------------------------------------------------------------------- 1 | -- NOTE: You cannot use upvalues in the functions used to open 2 | -- windows, since they cannot be serialized. 3 | -- 4 | -- Example edgy config: 5 | -- ```lua 6 | -- -- If you want to require a module, you must do it inside the function. 7 | -- -- This will result in an error when loading, as `t` is an upvalue and 8 | -- -- will not be valid when the function is loaded from session storage. 9 | -- -- local t = require("toggleterm.terminal") 10 | -- 11 | -- require("edgy").setup({ 12 | -- bottom = { 13 | -- { 14 | -- ft = "terminal", 15 | -- open = function() 16 | -- -- this is the correct way to require a module when 17 | -- -- the function will be saved / restored. 18 | -- local t = require("toggleterm.terminal") 19 | -- local term = t.get_or_create_term(0) 20 | -- if not term:is_open() then 21 | -- term:open() 22 | -- end 23 | -- end, 24 | -- }, 25 | -- }, 26 | -- }) 27 | -- ``` 28 | local M = {} 29 | 30 | ---@param winid integer 31 | local function save_win(winid) 32 | local win = require("edgy").get_win(winid) 33 | if win and win.view.open then 34 | local open = win.view.open 35 | if type(open) == "string" then 36 | open = function() 37 | vim.api.nvim_exec2(open, { output = false }) 38 | end 39 | end 40 | return { 41 | ft = win.view.ft, 42 | open = string.dump(open --[[@as fun()]]), 43 | -- save tabs by index and not id, since IDs may be different when the session is restored 44 | tabpage = vim.api.nvim_tabpage_get_number( 45 | vim.api.nvim_win_get_tabpage(winid) 46 | ), 47 | } 48 | end 49 | end 50 | 51 | ---@param tabpage integer 52 | local function save_tab(tabpage) 53 | local wins = {} 54 | 55 | for _, win in ipairs(vim.api.nvim_tabpage_list_wins(tabpage)) do 56 | local info = save_win(win) 57 | if info then 58 | table.insert(wins, info) 59 | end 60 | end 61 | 62 | return wins 63 | end 64 | 65 | local function save_all() 66 | local wins = {} 67 | 68 | for _, tab in ipairs(vim.api.nvim_list_tabpages()) do 69 | vim.list_extend(wins, save_tab(tab)) 70 | end 71 | 72 | return wins 73 | end 74 | 75 | local function modify_opts() 76 | local curwin = vim.api.nvim_get_current_win() 77 | local splitkeep = vim.o.splitkeep 78 | local animate = require("edgy.config").animate.enabled 79 | 80 | vim.o.splitkeep = "topline" 81 | require("edgy.config").animate.enabled = false 82 | 83 | return vim.schedule_wrap(function() 84 | -- ensure "main" windows don't jump if edgy windows flicker 85 | vim.o.splitkeep = splitkeep 86 | -- disable edgy animation while opening windows (TODO: does this work?) 87 | require("edgy.config").animate.enabled = animate 88 | -- restore the original window 89 | vim.api.nvim_set_current_win(curwin) 90 | end) 91 | end 92 | 93 | function M.on_save(opts) 94 | if opts.tabpage then 95 | return { 96 | scope = "tabpage", 97 | wins = save_tab(opts.tabpage), 98 | } 99 | else 100 | return { 101 | scope = "global", 102 | wins = save_all(), 103 | } 104 | end 105 | end 106 | 107 | local first_tab 108 | 109 | function M.on_pre_load() 110 | first_tab = #vim.api.nvim_list_tabpages() - 1 111 | end 112 | 113 | function M.on_post_load(data) 114 | local restore_opts 115 | -- call with the tabpage as the current tabpage 116 | local tabs = vim.api.nvim_list_tabpages() 117 | for _, win_info in ipairs(data.wins or {}) do 118 | local f = loadstring(win_info.open) 119 | if 120 | f and (data.scope == "tabpage" or tabs[win_info.tabpage + first_tab]) 121 | then 122 | if not restore_opts then 123 | restore_opts = modify_opts() 124 | end 125 | local tab 126 | if data.scope == "global" then 127 | tab = tabs[win_info.tabpage + first_tab] 128 | else 129 | tab = 0 130 | end 131 | vim.api.nvim_win_call(vim.api.nvim_tabpage_get_win(tab), function() 132 | if not pcall(f) then 133 | vim.schedule(function() 134 | vim.notify( 135 | "[resession] Failed to open " .. win_info.ft, 136 | vim.log.levels.WARN 137 | ) 138 | end) 139 | end 140 | end) 141 | end 142 | end 143 | if restore_opts then 144 | restore_opts() 145 | end 146 | end 147 | 148 | return M 149 | -------------------------------------------------------------------------------- /lua/willothy/ai.lua: -------------------------------------------------------------------------------- 1 | local function setup(key) 2 | vim.env["ANTHROPIC_API_KEY"] = key 3 | ---@diagnostic disable-next-line: missing-fields 4 | require("avante").setup({ 5 | provider = "claude", 6 | disabled_tools = { "python" }, 7 | claude = { 8 | -- model = "claude-3-7-sonnet-20250219", 9 | -- model = "claude-3-5-sonnet-20241022", 10 | }, 11 | behaviour = { 12 | -- Whether to enable Claude Text Editor Tool Mode. Default false. 13 | -- enable_claude_text_editor_tool_mode = true, 14 | }, 15 | windows = { 16 | sidebar_header = { 17 | rounded = false, 18 | }, 19 | edit = { 20 | border = "solid", 21 | }, 22 | ask = { 23 | border = "solid", 24 | }, 25 | }, 26 | }) 27 | vim.cmd("highlight default link AvanteSuggestion PmenuSel") 28 | end 29 | 30 | local key = require("durable").kv.get("anthropic-api-key") 31 | if key ~= nil then 32 | setup(key) 33 | return 34 | end 35 | 36 | require("willothy.lib.1password").read( 37 | "op://Personal/Anthropic API Key/credential", 38 | vim.schedule_wrap(function(res) 39 | res = vim.trim(res) 40 | setup(res) 41 | require("durable").kv.set("anthropic-api-key", res) 42 | end) 43 | ) 44 | -------------------------------------------------------------------------------- /lua/willothy/autocmds.lua: -------------------------------------------------------------------------------- 1 | local autocmd = vim.api.nvim_create_autocmd 2 | 3 | local group = 4 | vim.api.nvim_create_augroup("willothy.autocmds", { clear = true }) 5 | 6 | ---The reason why the `FileChangedShell` event was triggered. 7 | ---Can be used in an autocommand to decide what to do and/or what 8 | ---to set v:fcs_choice to. 9 | --- 10 | ---Possible values: 11 | --- deleted file no longer exists 12 | --- conflict file contents, mode or timestamp was 13 | --- changed and buffer is modified 14 | --- changed file contents has changed 15 | --- mode mode of file changed 16 | --- time only file timestamp changed 17 | --- 18 | ---@enum FileChangeReason 19 | local FileChangeReason = { 20 | --- File no longer exists. 21 | Deleted = "deleted", 22 | --- File contents, mode or timestamp was changed and buffer is modified. 23 | Conflict = "conflict", 24 | --- File contents has changed. 25 | Changed = "changed", 26 | --- Mode of file changed. 27 | Mode = "mode", 28 | --- Only file timestamp changed. 29 | Time = "time", 30 | } 31 | 32 | ---@enum FileChangeChoice 33 | local FileChangeChoice = { 34 | --- Reload the buffer (does not work if 35 | --- the file was deleted). 36 | Reload = "reload", 37 | --- Reload the buffer and detect the 38 | --- values for options such as 39 | --- 'fileformat', 'fileencoding', 'binary' 40 | --- (does not work if the file was 41 | --- deleted). 42 | Edit = "edit", 43 | --- Ask the user what to do, as if there 44 | --- was no autocommand. Except that when 45 | --- only the timestamp changed nothing 46 | --- will happen. 47 | Ask = "ask", 48 | --- Nothing, the autocommand should do 49 | --- everything that needs to be done. 50 | None = "", 51 | } 52 | 53 | ---@alias FileChangeHandler fun(ev: vim.api.keyset.create_autocmd.callback_args): FileChangeChoice 54 | 55 | local function coalesce_aggregate(timeout, fn) 56 | local results = {} 57 | local running = false 58 | 59 | return function(...) 60 | if not running then 61 | running = true 62 | 63 | vim.defer_fn(function() 64 | running = false 65 | 66 | local complete = results 67 | results = {} 68 | 69 | fn(complete) 70 | end, timeout) 71 | end 72 | 73 | table.insert(results, { ... }) 74 | end 75 | end 76 | 77 | local delete_notifier = coalesce_aggregate(250, function(results) 78 | -- Aggregate results to check if multiple files were deleted 79 | -- in a short time frame. 80 | if #results > 1 then 81 | vim.notify( 82 | string.format( 83 | "%d file%s deleted on disk. Buffer%s will be unloaded.\n%s", 84 | #results, 85 | #results == 1 and "" or "s", 86 | #results == 1 and "" or "s", 87 | vim 88 | .iter(results) 89 | :map(function(result) 90 | return string.format( 91 | "- %s", 92 | string.gsub(result[1].file, vim.pesc(vim.env.HOME), "~") 93 | ) 94 | end) 95 | :join("\n") 96 | ), 97 | vim.log.levels.WARN, 98 | {} 99 | ) 100 | else 101 | vim.notify( 102 | string.format( 103 | "File %s deleted on disk. Buffer will be unloaded.", 104 | string.gsub(results[1][1].file, vim.pesc(vim.env.HOME), "~") 105 | ), 106 | vim.log.levels.WARN, 107 | {} 108 | ) 109 | end 110 | end) 111 | 112 | ---@type table 113 | local FILE_CHANGE_HANDLERS = { 114 | [FileChangeReason.Deleted] = function(ev) 115 | delete_notifier(ev) 116 | return FileChangeChoice.None 117 | end, 118 | [FileChangeReason.Conflict] = function(ev) 119 | if vim.bo[ev.buf].modified then 120 | return FileChangeChoice.Ask 121 | else 122 | vim.notify( 123 | "File changed on disk, but the buffer is not modified. Reloading buffer.", 124 | vim.log.levels.WARN, 125 | {} 126 | ) 127 | return FileChangeChoice.Reload 128 | end 129 | end, 130 | [FileChangeReason.Changed] = function() 131 | return FileChangeChoice.Reload 132 | end, 133 | [FileChangeReason.Mode] = function() 134 | vim.notify( 135 | "File mode changed on disk. This may affect how the file is accessed.", 136 | vim.log.levels.INFO, 137 | {} 138 | ) 139 | return FileChangeChoice.Reload 140 | end, 141 | [FileChangeReason.Time] = function() 142 | return FileChangeChoice.None 143 | end, 144 | } 145 | 146 | local autocmds = { 147 | { 148 | "LspAttach", 149 | callback = function(args) 150 | local bufnr = args.buf 151 | local client = vim.lsp.get_client_by_id(args.data.client_id) 152 | 153 | if not client then 154 | return 155 | end 156 | 157 | if client:supports_method("textDocument/foldingRange") then 158 | vim.api.nvim_set_option_value("foldexpr", "v:lua.vim.lsp.foldexpr()", { 159 | scope = "local", 160 | }) 161 | end 162 | 163 | if 164 | vim.lsp.inlay_hint 165 | and client:supports_method("textDocument/inlayHint") 166 | then 167 | vim.lsp.inlay_hint.enable(true, { 168 | bufnr = bufnr, 169 | }) 170 | end 171 | end, 172 | }, 173 | { 174 | "BufWritePost", 175 | callback = function(ev) 176 | if vim.bo[ev.buf].modifiable and vim.bo[ev.buf].buftype == "" then 177 | require("mini.trailspace").trim() 178 | end 179 | end, 180 | }, 181 | { 182 | "FileType", 183 | callback = function(ev) 184 | -- if vim.bo[ev.buf].buftype ~= "" then 185 | -- vim.api.nvim_buf_call(ev.buf, require("mini.trailspace").unhighlight) 186 | -- end 187 | if vim.bo[ev.buf].buftype ~= "" then 188 | return 189 | end 190 | local parsers = require("nvim-treesitter.parsers") 191 | local ft = vim.bo[ev.buf].filetype 192 | local lang = parsers.ft_to_lang(ft) 193 | if not lang then 194 | vim.notify_once( 195 | "No language config for filetype '" .. ft .. "'", 196 | vim.log.levels.WARN, 197 | {} 198 | ) 199 | return 200 | end 201 | if parsers.has_parser(lang) then 202 | vim.treesitter.start(ev.buf, lang) 203 | end 204 | end, 205 | }, 206 | { 207 | { "BufRead", "BufNewFile" }, 208 | pattern = { "*.rasi" }, 209 | callback = function(ev) 210 | local buf = ev.buf 211 | vim.bo[buf].filetype = "rasi" 212 | end, 213 | }, 214 | { 215 | "FileChangedShell", 216 | callback = function(ev) 217 | vim.v.fcs_choice = FILE_CHANGE_HANDLERS[vim.v.fcs_reason](ev) 218 | end, 219 | }, 220 | { 221 | { "BufLeave", "BufWinLeave" }, 222 | callback = function(ev) 223 | if vim.bo[ev.buf].filetype == "lazy" then 224 | require("lazy.view").view:close({}) 225 | end 226 | end, 227 | }, 228 | } 229 | 230 | for _, v in ipairs(autocmds) do 231 | local event = v[1] 232 | v[1] = nil 233 | v.group = group 234 | autocmd(event, v) 235 | end 236 | -------------------------------------------------------------------------------- /lua/willothy/commands.lua: -------------------------------------------------------------------------------- 1 | local commands = { 2 | Detach = { 3 | function() 4 | vim.system({ "sesh", "detach" }, {}, function(obj) 5 | if obj.code ~= 0 then 6 | vim.notify("Failed to detach from session", vim.log.levels.ERROR, {}) 7 | end 8 | end) 9 | end, 10 | }, 11 | Capture = { 12 | function(args) 13 | local function max_length(list) 14 | local max = 0 15 | for _, v in ipairs(list) do 16 | if #v > max then 17 | max = #v 18 | end 19 | end 20 | return max 21 | end 22 | local result = vim.api.nvim_exec2(args.args, { output = true }) 23 | if result.output then 24 | if args.bang then 25 | vim.fn.setreg("*", result.output) 26 | return 27 | end 28 | result = vim.split(result.output, "\n") 29 | local buf, win = vim.lsp.util.open_floating_preview(result, "", { 30 | focus = true, 31 | focusable = true, 32 | border = "solid", 33 | wrap = true, 34 | width = math.min( 35 | 60, 36 | vim.o.columns - 10, 37 | math.max(max_length(result), 10) 38 | ), 39 | }) 40 | vim.wo[win].winhl = "FloatBorder:NormalFloat" 41 | vim.api.nvim_set_current_win(win) 42 | vim.keymap.set("n", "q", function() 43 | if vim.api.nvim_win_is_valid(win) then 44 | vim.api.nvim_win_close(win, true) 45 | end 46 | end, { buffer = buf }) 47 | vim.api.nvim_create_autocmd("BufLeave", { 48 | buffer = buf, 49 | once = true, 50 | callback = function() 51 | if vim.api.nvim_win_is_valid(win) then 52 | vim.api.nvim_win_close(win, true) 53 | end 54 | end, 55 | }) 56 | end 57 | end, 58 | desc = "Capture command output", 59 | nargs = "?", 60 | bang = true, 61 | }, 62 | CurrentDirRTP = { 63 | function() 64 | local cwd = vim.fn.getcwd(-1) 65 | vim.opt.rtp:prepend(cwd) 66 | end, 67 | desc = "Add the cwd to vim's runtime path", 68 | }, 69 | ReloadOnSave = { 70 | function(args) 71 | local mod = args.args 72 | 73 | local buf = vim.api.nvim_get_current_buf() 74 | vim.api.nvim_create_autocmd("BufWritePost", { 75 | buffer = buf, 76 | callback = function() 77 | package.loaded[mod] = nil 78 | require(mod) 79 | end, 80 | }) 81 | end, 82 | nargs = 1, 83 | }, 84 | Reload = { 85 | function(args) 86 | local util = require("willothy.debug") 87 | if args and args["args"] ~= "" then 88 | util.reload(args["args"]) 89 | else 90 | util.reload(util.current_mod()) 91 | end 92 | end, 93 | nargs = "?", 94 | desc = "Reload the current module", 95 | }, 96 | Bd = { 97 | function() 98 | require("snacks").bufdelete.delete({ 99 | wipe = true, 100 | }) 101 | if 102 | vim.api.nvim_buf_get_name(0) == "" 103 | and vim.api.nvim_buf_line_count(0) <= 1 104 | and vim.api.nvim_buf_get_lines(0, 0, -1, false)[1] == "" 105 | then 106 | vim.bo.buflisted = false 107 | vim.bo.bufhidden = "wipe" 108 | end 109 | end, 110 | desc = "Close the current buffer", 111 | }, 112 | Bda = { 113 | function() 114 | require("snacks").bufdelete.all({ 115 | wipe = true, 116 | }) 117 | if 118 | vim.api.nvim_buf_get_name(0) == "" 119 | and vim.api.nvim_buf_line_count(0) <= 1 120 | and vim.api.nvim_buf_get_lines(0, 0, -1, false)[1] == "" 121 | then 122 | vim.bo.buflisted = false 123 | vim.bo.bufhidden = "wipe" 124 | end 125 | end, 126 | desc = "Close all buffers", 127 | }, 128 | LuaAttach = { 129 | function() 130 | require("luapad").attach() 131 | end, 132 | desc = "Attach a Lua REPL to the current buffer", 133 | }, 134 | LuaDetach = { 135 | function() 136 | require("luapad").detach() 137 | end, 138 | desc = "Detach the Lua REPL from the current buffer", 139 | }, 140 | Scratch = { 141 | function(args) 142 | local scratch = require("snacks").scratch 143 | if #args.fargs == 0 then 144 | scratch() 145 | return 146 | end 147 | local cmd = { 148 | list = function() 149 | scratch.list() 150 | end, 151 | select = function() 152 | scratch.select() 153 | end, 154 | open = function(open_args) 155 | local kv = {} 156 | for _, str in ipairs(open_args) do 157 | local k, v = str:match("([^=]+)=(.*)") 158 | kv[k] = v 159 | end 160 | 161 | scratch.open(kv) 162 | end, 163 | } 164 | cmd[args.fargs[1]]({ unpack(args.fargs, 2) }) 165 | end, 166 | nargs = "*", 167 | desc = "Open a scratch buffer", 168 | }, 169 | BrowserSwitch = { 170 | function() 171 | require("willothy.lib.fs").set_browser() 172 | end, 173 | desc = "Select a browser", 174 | }, 175 | Sync = { 176 | function() 177 | require("lazy").sync() 178 | end, 179 | desc = "Update plugins", 180 | }, 181 | Q = { "q", desc = ":q, common cmdline typo" }, 182 | W = { "w", desc = ":w, common cmdline typo" }, 183 | Color = { 184 | function() 185 | require("minty.huefy").open() 186 | end, 187 | desc = "Color picker", 188 | }, 189 | } 190 | 191 | for name, cmd in pairs(commands) do 192 | local command = cmd[1] 193 | cmd[1] = nil 194 | vim.api.nvim_create_user_command(name, command, cmd) 195 | end 196 | -------------------------------------------------------------------------------- /lua/willothy/debugging.lua: -------------------------------------------------------------------------------- 1 | local dap = require("dap") 2 | require("overseer").enable_dap(true) 3 | 4 | dap.listeners.after.event_initialized["dapui_config"] = function() 5 | require("dapui").open() 6 | require("nvim-dap-virtual-text").refresh() 7 | end 8 | dap.listeners.after.disconnect["dapui_config"] = function() 9 | require("dap.repl").close() 10 | require("dapui").close() 11 | require("nvim-dap-virtual-text").refresh() 12 | end 13 | dap.listeners.before.event_terminated["dapui_config"] = function() 14 | require("dapui").close() 15 | require("nvim-dap-virtual-text").refresh() 16 | end 17 | dap.listeners.before.event_exited["dapui_config"] = function() 18 | require("dapui").close() 19 | require("nvim-dap-virtual-text").refresh() 20 | end 21 | 22 | dap.configurations.rust = { 23 | { 24 | name = "Launch", 25 | type = "codelldb", 26 | request = "launch", 27 | program = function() 28 | return vim.fn.input( 29 | "Path to executable: ", 30 | vim.fn.getcwd() .. "/", 31 | "file" 32 | ) 33 | end, 34 | cwd = "${workspaceFolder}", 35 | stopOnEntry = false, 36 | args = {}, 37 | }, 38 | } 39 | dap.adapters.codelldb = { 40 | type = "server", 41 | port = "${port}", 42 | executable = { 43 | command = "/home/willothy/.local/share/nvim/mason/bin/codelldb", 44 | args = { "--port", "${port}" }, 45 | }, 46 | } 47 | 48 | dap.configurations.lua = { 49 | { 50 | type = "nlua", 51 | request = "attach", 52 | name = "Attach to running Neovim instance", 53 | }, 54 | } 55 | 56 | dap.adapters.nlua = function(callback, config) 57 | callback({ 58 | type = "server", 59 | ---@diagnostic disable-next-line: undefined-field 60 | host = config.host or "127.0.0.1", 61 | ---@diagnostic disable-next-line: undefined-field 62 | port = config.port or 8086, 63 | }) 64 | end 65 | 66 | require("dapui").setup({ 67 | icons = { expanded = "", collapsed = "", current_frame = "" }, 68 | mappings = { 69 | -- Use a table to apply multiple mappings 70 | expand = { "", "<2-LeftMouse>" }, 71 | open = "o", 72 | remove = "d", 73 | edit = "e", 74 | repl = "r", 75 | toggle = "t", 76 | }, 77 | element_mappings = {}, 78 | expand_lines = vim.fn.has("nvim-0.7") == 1, 79 | force_buffers = true, 80 | layouts = { 81 | { 82 | -- You can change the order of elements in the sidebar 83 | elements = { 84 | -- Provide IDs as strings or tables with "id" and "size" keys 85 | { 86 | id = "scopes", 87 | size = 0.25, -- Can be float or integer > 1 88 | }, 89 | { id = "breakpoints", size = 0.25 }, 90 | { id = "stacks", size = 0.25 }, 91 | { id = "watches", size = 0.25 }, 92 | }, 93 | size = 0.2, 94 | position = "left", -- Can be "left" or "right" 95 | }, 96 | { 97 | elements = { 98 | "repl", 99 | "console", 100 | }, 101 | size = 0.25, 102 | position = "bottom", -- Can be "bottom" or "top" 103 | }, 104 | }, 105 | floating = { 106 | max_height = nil, 107 | max_width = nil, 108 | border = "single", 109 | mappings = { 110 | ["close"] = { "q", "" }, 111 | }, 112 | }, 113 | controls = { 114 | enabled = true, 115 | element = "repl", 116 | icons = { 117 | pause = "", 118 | play = "", 119 | step_into = "", 120 | step_over = "", 121 | step_out = "", 122 | step_back = "", 123 | run_last = "", 124 | terminate = "", 125 | disconnect = "", 126 | }, 127 | }, 128 | render = { 129 | max_type_length = nil, -- Can be integer or nil. 130 | max_value_lines = 100, -- Can be integer or nil. 131 | indent = 1, 132 | }, 133 | }) 134 | 135 | -- local M = {} 136 | -- 137 | -- M.launchers = { 138 | -- lua = function() 139 | -- require("osv").run_this() 140 | -- end, 141 | -- } 142 | -- 143 | -- function M.launch() 144 | -- local filetype = vim.bo.filetype 145 | -- local launch = require("configs.debugging").launchers[filetype] 146 | -- if launch then 147 | -- local ok, res = pcall(launch) 148 | -- if not ok then 149 | -- vim.notify( 150 | -- ("Failed to start debugger for %s: %s"):format(filetype, res), 151 | -- "error" 152 | -- ) 153 | -- end 154 | -- else 155 | -- vim.notify(("No debugger available for %s"):format(filetype), "warn") 156 | -- end 157 | -- end 158 | -- 159 | -- function M.is_active() 160 | -- return require("dap").session() ~= nil 161 | -- end 162 | -- 163 | -- return M 164 | -------------------------------------------------------------------------------- /lua/willothy/diagnostics.lua: -------------------------------------------------------------------------------- 1 | local icons = require("willothy.ui.icons") 2 | 3 | local signs = { 4 | DapBreakpoint = { 5 | text = icons.dap.breakpoint.data, 6 | icon = icons.dap.breakpoint.data, 7 | texthl = "DiagnosticSignError", 8 | }, 9 | DapBreakpointCondition = { 10 | text = icons.dap.breakpoint.conditional, 11 | icon = icons.dap.breakpoint.conditional, 12 | texthl = "DiagnosticSignWarn", 13 | }, 14 | DapLogPoint = { 15 | text = icons.dap.breakpoint.log, 16 | icon = icons.dap.breakpoint.log, 17 | texthl = "DiagnosticSignInfo", 18 | }, 19 | DapStopped = { 20 | text = icons.dap.action.stop, 21 | icon = icons.dap.action.stop, 22 | texthl = "DiagnosticSignInfo", 23 | }, 24 | DapBreakpointRejected = { 25 | text = icons.dap.breakpoint.unsupported, 26 | icon = icons.dap.breakpoint.unsupported, 27 | texthl = "DiagnosticSignWarn", 28 | }, 29 | } 30 | 31 | for name, def in pairs(signs) do 32 | vim.fn.sign_define(name, def) 33 | end 34 | 35 | vim.diagnostic.config({ 36 | severity_sort = true, 37 | update_in_insert = true, 38 | underline = true, 39 | signs = { 40 | text = { 41 | [vim.diagnostic.severity.ERROR] = icons.diagnostics.Error, 42 | [vim.diagnostic.severity.WARN] = icons.diagnostics.Warn, 43 | [vim.diagnostic.severity.INFO] = icons.diagnostics.Info, 44 | [vim.diagnostic.severity.HINT] = icons.diagnostics.Hint, 45 | }, 46 | }, 47 | float = { 48 | header = setmetatable({}, { 49 | __index = function(_, k) 50 | local arr = { 51 | string.format( 52 | "Diagnostics: %s %s", 53 | require("nvim-web-devicons").get_icon_by_filetype(vim.bo.filetype), 54 | vim.bo.filetype 55 | ), 56 | "Title", 57 | } 58 | return arr[k] 59 | end, 60 | }), 61 | source = true, 62 | border = "solid", 63 | focusable = false, 64 | }, 65 | }) 66 | 67 | local opts = { 68 | placement = "top", 69 | scope = "line", 70 | update_event = { 71 | "DiagnosticChanged", 72 | -- "BufReadPost", 73 | "TextChanged", 74 | "BufEnter", 75 | }, 76 | render_event = { 77 | "DiagnosticChanged", 78 | "TextChanged", 79 | "CursorMoved", 80 | "CursorHold", 81 | "BufEnter", 82 | }, 83 | format = function(diag) 84 | local levels = { 85 | [1] = "Error", 86 | [2] = "Warn", 87 | [3] = "Info", 88 | [4] = "Trace", 89 | } 90 | 91 | local icon = icons.diagnostics[levels[diag.severity]] 92 | 93 | return string.format( 94 | "%s%s%s", 95 | icon or "", 96 | icon and " " or "", 97 | diag.message or "" 98 | ) 99 | end, 100 | } 101 | 102 | require("diagflow").setup(opts) 103 | -------------------------------------------------------------------------------- /lua/willothy/formatting.lua: -------------------------------------------------------------------------------- 1 | ---@type conform.setupOpts 2 | local opts = { 3 | formatters_by_ft = { 4 | -- sql = { 5 | -- stop_after_first = true, 6 | -- "sqlfmt", 7 | -- }, 8 | lua = { 9 | stop_after_first = true, 10 | "stylua", 11 | }, 12 | c = { 13 | stop_after_first = true, 14 | "uncrustify", 15 | "clang_format", 16 | }, 17 | cpp = { 18 | stop_after_first = true, 19 | "clang_format", 20 | }, 21 | cuda = { 22 | stop_after_first = true, 23 | "clang_format", 24 | }, 25 | rust = { 26 | stop_after_first = true, 27 | "rustfmt", 28 | }, 29 | javascript = { 30 | stop_after_first = true, 31 | "prettier", 32 | }, 33 | typescript = { 34 | stop_after_first = true, 35 | "prettier", 36 | }, 37 | typescriptreact = { 38 | stop_after_first = true, 39 | "prettier", 40 | }, 41 | tsx = { 42 | stop_after_first = true, 43 | "prettier", 44 | }, 45 | css = { 46 | stop_after_first = true, 47 | "prettier", 48 | }, 49 | scss = { 50 | stop_after_first = true, 51 | "prettier", 52 | }, 53 | html = { 54 | stop_after_first = true, 55 | "prettier", 56 | }, 57 | markdown = { 58 | stop_after_first = true, 59 | "markdownlint", 60 | "prettier", 61 | }, 62 | json = { 63 | stop_after_first = true, 64 | "jq", 65 | "prettier", 66 | }, 67 | toml = { 68 | stop_after_first = true, 69 | "taplo", 70 | "prettier", 71 | }, 72 | jsonc = { 73 | stop_after_first = true, 74 | "jq", 75 | "prettier", 76 | }, 77 | proto = { 78 | stop_after_first = true, 79 | "buf", 80 | "protolint", 81 | }, 82 | nasm = { 83 | stop_after_first = true, 84 | "asmfmt", 85 | }, 86 | asm = { 87 | stop_after_first = true, 88 | "asmfmt", 89 | }, 90 | nix = { 91 | stop_after_first = true, 92 | "nixpkgs_fmt", 93 | }, 94 | just = { "just" }, 95 | -- python = { "black" }, 96 | python = { "blue" }, 97 | bzl = { "buildifier" }, 98 | }, 99 | -- format_on_save = { 100 | -- timeout_ms = 500, 101 | -- lsp_fallback = true, 102 | -- }, 103 | format_on_save = function(bufnr) 104 | -- Disable with a global or buffer-local variable 105 | if vim.g.disable_autoformat or vim.b[bufnr].disable_autoformat then 106 | return 107 | end 108 | return { timeout_ms = 500, lsp_fallback = true } 109 | end, 110 | format_after_save = function(bufnr) 111 | -- Disable with a global or buffer-local variable 112 | if vim.g.disable_autoformat or vim.b[bufnr].disable_autoformat then 113 | return 114 | end 115 | return { timeout_ms = 500, lsp_fallback = true } 116 | end, 117 | } 118 | 119 | vim.o.formatexpr = "v:lua.require'conform'.formatexpr()" 120 | 121 | local conform = require("conform") 122 | 123 | conform.setup(opts) 124 | 125 | conform.formatters.rustfmt = { 126 | meta = { 127 | url = "https://github.com/rust-lang/rustfmt", 128 | description = "A tool for formatting rust code according to style guidelines.", 129 | }, 130 | command = "rustfmt", 131 | args = { "--emit=stdout", "--edition=2021" }, 132 | } 133 | 134 | conform.formatters.uncrustify = { 135 | meta = { 136 | url = "https://github.com/uncrustify/uncrustify", 137 | description = "A source code beautifier for C, C++, C#, ObjectiveC, D, Java, Pawn and Vala.", 138 | }, 139 | command = "uncrustify", 140 | args = function(self, ctx) 141 | return { 142 | "-q", 143 | "-l", 144 | vim.bo[ctx.buf].filetype:upper(), 145 | "-c", 146 | vim.uv.cwd() .. "/src/uncrustify.cfg", 147 | } 148 | end, 149 | } 150 | 151 | local toggle = require("snacks").toggle.new({ 152 | name = "Format on save", 153 | get = function() 154 | return not vim.g.disable_autoformat 155 | end, 156 | set = function(enabled) 157 | vim.g.disable_autoformat = not enabled 158 | end, 159 | }) 160 | 161 | toggle:map("uf") 162 | 163 | require("willothy.lib.fn").create_command("Format", { 164 | desc = "Manage formatting", 165 | bang = true, 166 | command = function() 167 | require("conform").format({ 168 | lsp_fallback = true, 169 | }) 170 | end, 171 | subcommands = { 172 | disable = { 173 | execute = function() 174 | toggle:set(true) 175 | end, 176 | }, 177 | enable = { 178 | execute = function() 179 | toggle:set(false) 180 | end, 181 | }, 182 | toggle = { 183 | execute = function() 184 | toggle:toggle() 185 | end, 186 | }, 187 | }, 188 | }) 189 | -------------------------------------------------------------------------------- /lua/willothy/lib/1password.lua: -------------------------------------------------------------------------------- 1 | local CLI_NAME = "op" 2 | 3 | local M = {} 4 | 5 | function M.read(key, callback) 6 | vim.system({ 7 | CLI_NAME, 8 | "read", 9 | key, 10 | }, { 11 | text = true, 12 | }, function(obj) 13 | if obj.code ~= 0 then 14 | callback(nil, obj.stderr) 15 | return 16 | end 17 | callback(obj.stdout, nil) 18 | end) 19 | end 20 | 21 | function M.read_sync(key) 22 | local obj = vim 23 | .system({ 24 | CLI_NAME, 25 | "read", 26 | key, 27 | }, { 28 | text = true, 29 | }) 30 | :wait() 31 | 32 | if obj.code ~= 0 then 33 | return nil, obj.stderr 34 | end 35 | return obj.stdout, nil 36 | end 37 | 38 | return M 39 | -------------------------------------------------------------------------------- /lua/willothy/lib/command.lua: -------------------------------------------------------------------------------- 1 | ---@param str string 2 | ---@return table, string[] 3 | local function parse(str) 4 | local parsed = vim.api.nvim_parse_cmd(str, {}) 5 | 6 | ---@type string[] 7 | local args = {} 8 | ---@type table 9 | local flags = {} 10 | 11 | for _, arg in ipairs(parsed.args) do 12 | local k, v = string.match(arg, "^(%w+)=(^w*)") 13 | if k then 14 | flags[k] = v 15 | else 16 | table.insert(args, arg) 17 | end 18 | end 19 | 20 | return args, flags 21 | end 22 | 23 | ---@class Command 24 | ---@field private _subcommands table 25 | ---@field private _execute fun(args: table): nil 26 | local Command = {} 27 | Command.__index = Command 28 | 29 | ---@param execute? fun(args: table) 30 | ---@return Command 31 | function Command.new(execute) 32 | return setmetatable({ 33 | _subcommands = {}, 34 | _execute = execute or function() end, 35 | }, Command) 36 | end 37 | 38 | ---@param name string 39 | ---@param command Command 40 | ---@return self 41 | function Command:subcommand(name, command) 42 | self._subcommands[name] = command 43 | return self 44 | end 45 | 46 | ---@param commands table 47 | ---@return self 48 | function Command:subcommands(commands) 49 | for name, command in pairs(commands) do 50 | self._subcommands[name] = command 51 | end 52 | return self 53 | end 54 | 55 | function Command:_complete(args, flags) 56 | if args[#args] == "" then 57 | return vim.tbl_keys(self._subcommands) 58 | end 59 | 60 | local prefix = args[#args] or "" 61 | 62 | for k, v in pairs(self._subcommands) do 63 | if k == prefix then 64 | return v:_complete(vim.iter(args):skip(1):totable(), flags) 65 | end 66 | 67 | if string.match(k, "^" .. prefix) then 68 | table.insert(args, k) 69 | end 70 | end 71 | end 72 | 73 | function Command:complete(str) 74 | local args, flags = parse(str) 75 | 76 | return self:_complete(args, flags) 77 | end 78 | 79 | function Command:bind(name) 80 | -- ArgLead, CmdLine, CursorPos 81 | vim.api.nvim_create_user_command(name, function(args) 82 | return self._execute({}) 83 | -- return self:complete(args.args) 84 | end, { 85 | ---@param arg_lead string 86 | ---@param cmdline string 87 | ---@param cursor_pos number 88 | complete = function(arg_lead, cmdline, cursor_pos) 89 | return self:complete(cmdline .. " " .. arg_lead) 90 | end, 91 | nargs = "*", 92 | }) 93 | end 94 | 95 | local cmd = Command.new(function() 96 | vim.print("test") 97 | end):subcommand( 98 | "two", 99 | Command.new(function() 100 | vim.print("foo subcmd") 101 | end) 102 | ) 103 | 104 | cmd:bind("Foo") 105 | -------------------------------------------------------------------------------- /lua/willothy/lib/fs.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | ---@type table 4 | M.browsers = { 5 | mini = function(target) 6 | require("mini.files").open(target) 7 | end, 8 | oil = function(target) 9 | -- TODO: maybe upstream this into Oil? I think it would be nice to have. 10 | require("oil").open(target) 11 | 12 | -- if 13 | -- vim.bo.filetype == "oil" 14 | -- and require("plenary.path"):new(target):absolute() 15 | -- == require("oil").get_current_dir():gsub("/$", "") 16 | -- then 17 | -- return 18 | -- end 19 | -- vim.cmd.vsplit() 20 | -- require("oil").open(target) 21 | -- local win = vim.api.nvim_get_current_win() 22 | -- local buf = vim.api.nvim_get_current_buf() 23 | -- 24 | -- local winhl = vim.api.nvim_get_option_value("winhighlight", { 25 | -- win = win, 26 | -- }) 27 | -- winhl = winhl:gsub("NormalNC:%w+,?", "") 28 | -- if winhl == "" then 29 | -- winhl = "NormalNC:Normal" 30 | -- else 31 | -- winhl = winhl .. ",NormalNC:Normal" 32 | -- end 33 | -- vim.api.nvim_set_option_value("winhighlight", winhl, { 34 | -- win = win, 35 | -- }) 36 | -- 37 | -- vim.api.nvim_create_autocmd("BufLeave", { 38 | -- buffer = buf, 39 | -- callback = function() 40 | -- if 41 | -- vim.api.nvim_win_is_valid(win) 42 | -- -- only close the window if the buffer has changed 43 | -- -- basically we treat this autocmd as "BufWinLeave" but for 44 | -- -- all windows containing oil buffers, not just the last one. 45 | -- and vim.api.nvim_win_get_buf(win) ~= buf 46 | -- then 47 | -- vim.api.nvim_win_close(win, true) 48 | -- else 49 | -- -- delete the buffer immediately if its no longer displayed 50 | -- -- fixes icons not showing with edgy.nvim 51 | -- vim.schedule(function() 52 | -- if 53 | -- vim.api.nvim_buf_is_valid(buf) 54 | -- and #vim.fn.getbufinfo(buf)[1].windows == 0 55 | -- then 56 | -- vim.api.nvim_buf_delete(buf, {}) 57 | -- if 58 | -- vim.api.nvim_win_is_valid(win) 59 | -- and not require("oil.util").is_oil_bufnr( 60 | -- vim.api.nvim_win_get_buf(win) 61 | -- ) 62 | -- then 63 | -- vim.api.nvim_win_close(win, true) 64 | -- end 65 | -- end 66 | -- end) 67 | -- end 68 | -- end, 69 | -- }) 70 | end, 71 | } 72 | 73 | ---@type fun(target: string?) 74 | M.browser = M.browsers.oil 75 | 76 | ---@param buf integer 77 | function M.hijack_dir_buf(buf) 78 | if vim.bo[buf].buftype ~= "" then 79 | return 80 | end 81 | 82 | local bufnr = buf 83 | local uv = vim.uv or vim.loop 84 | local bufname = vim.api.nvim_buf_get_name(bufnr) 85 | if bufname == "" then 86 | return 87 | end 88 | local stat = uv.fs_stat(bufname) 89 | if not stat or stat.type ~= "directory" then 90 | return 91 | end 92 | 93 | if bufnr then 94 | -- ensure no buffers remain with the directory name 95 | require("bufdelete").bufdelete(bufnr) 96 | end 97 | 98 | vim.schedule(function() 99 | M.browse(bufname) 100 | end) 101 | end 102 | 103 | function M.hijack_netrw() 104 | vim.g.loaded_netrw = 1 105 | vim.g.loaded_netrwPlugin = 1 106 | if vim.fn.exists("#FileExplorer") == 1 then 107 | vim.api.nvim_clear_autocmds({ group = "FileExplorer" }) 108 | end 109 | 110 | vim.api.nvim_create_autocmd("BufAdd", { 111 | group = vim.api.nvim_create_augroup("ExplHijackDirBuf", { clear = true }), 112 | pattern = "*", 113 | nested = true, 114 | callback = function(ev) 115 | M.hijack_dir_buf(ev.buf) 116 | end, 117 | desc = "Hijack netrw with switchable file browser", 118 | }) 119 | 120 | local argc = vim.fn.argc() 121 | if argc ~= 1 then 122 | return 123 | end 124 | 125 | local last_win 126 | local n_wins = 0 127 | for _, win in ipairs(vim.api.nvim_list_wins()) do 128 | if vim.api.nvim_win_get_config(win).zindex == nil then 129 | -- TODO: do I only want to hijack buffers when there's one window? 130 | if n_wins >= 1 then 131 | return 132 | end 133 | n_wins = n_wins + 1 134 | last_win = last_win or win 135 | end 136 | end 137 | if n_wins == 1 then 138 | M.hijack_dir_buf(vim.api.nvim_win_get_buf(last_win)) 139 | end 140 | end 141 | 142 | function M.set_browser() 143 | local a = require("nio") 144 | a.run(function() 145 | local options = {} 146 | for browser in pairs(M.browsers) do 147 | table.insert(options, browser) 148 | end 149 | local item = a.ui.select(options, { 150 | prompt = "Browsers", 151 | }) 152 | if not item then 153 | return 154 | end 155 | M.browser = M.browsers[item] or M.browser 156 | end) 157 | end 158 | 159 | ---@param target? string | string[] | fun():string 160 | ---@param browser? string 161 | function M.browse(target, browser) 162 | if target == nil then 163 | target = vim.fn.getcwd() 164 | elseif type(target) == "function" then 165 | target = target() 166 | elseif type(target) == "table" then 167 | target = table.concat(target, "/") 168 | end 169 | local browse 170 | if browser then 171 | browse = M.browsers[browser] or M.browser 172 | else 173 | browse = M.browser 174 | end 175 | browse(target) 176 | end 177 | 178 | ---@param path string 179 | ---@param length integer 180 | ---@param hint string? 181 | function M.incremental_shorten(path, length, hint) 182 | local sep = require("plenary.path").path.sep 183 | local segments = vim.fn.split(path, sep) 184 | local len = #segments 185 | local strlen = string.len(path) 186 | while strlen > length do 187 | local all_short = true 188 | for i = 1, len do 189 | local l = vim.fn.strcharlen(segments[i]) 190 | if l > 1 then 191 | if hint then 192 | if vim.fn.strcharpart(segments[i], l - 1, 1) == hint then 193 | segments[i] = vim.fn.strcharpart(segments[i], 0, l - 2) .. hint 194 | else 195 | segments[i] = vim.fn.strcharpart(segments[i], 0, l - 1) .. hint 196 | strlen = strlen + 1 197 | end 198 | else 199 | segments[i] = vim.fn.strcharpart(segments[i], 0, l - 1) 200 | end 201 | strlen = strlen - 1 202 | all_short = false 203 | end 204 | if strlen <= length then 205 | break 206 | end 207 | end 208 | if all_short then 209 | -- the path cannot be shortened any further 210 | -- if we don't break here, we'll be stuck in an infinite loop 211 | break 212 | end 213 | end 214 | return table.concat(segments, sep) 215 | end 216 | 217 | ---@param path string 218 | ---@return boolean 219 | function M.is_root(path) 220 | if string.sub(package["config"], 1, 1) == "\\" then 221 | return string.match(path, "^[A-Z]:\\?$") 222 | end 223 | return path == "/" 224 | end 225 | 226 | function M.project_root() 227 | return vim.fs.dirname(vim.fs.find(".git", { 228 | path = vim.fn.getcwd(-1), 229 | upward = true, 230 | })[1]) 231 | end 232 | 233 | ---@param dir string? 234 | ---@return string? 235 | function M.crate_root(dir) 236 | local file = vim.fs.find("Cargo.toml", { 237 | upward = true, 238 | type = "directory", 239 | path = dir or vim.fn.getcwd(-1), 240 | })[1] 241 | if not file then 242 | return 243 | end 244 | return vim.fs.dirname(file) 245 | end 246 | 247 | function M.parent_crate() 248 | local root = M.crate_root() 249 | if root == nil then 250 | return 251 | end 252 | local parent = M.crate_root(root .. "../") 253 | if parent == nil then 254 | vim.notify("No parent crate found") 255 | end 256 | return parent 257 | end 258 | 259 | function M.open_project_toml() 260 | local root = M.crate_root() 261 | if root == nil then 262 | return 263 | end 264 | vim.api.nvim_command("edit " .. string.format("%s", root) .. "/Cargo.toml") 265 | end 266 | 267 | return M 268 | -------------------------------------------------------------------------------- /lua/willothy/lib/hl.lua: -------------------------------------------------------------------------------- 1 | local bit = require("bit") 2 | 3 | local M = {} 4 | 5 | local cache = { 6 | hex = {}, 7 | groups = {}, 8 | } 9 | 10 | -- Invalidate the cache on colorscheme change 11 | vim.api.nvim_create_autocmd("ColorschemePre", { 12 | group = vim.api.nvim_create_augroup( 13 | "cokeline_color_cache", 14 | { clear = true } 15 | ), 16 | callback = function() 17 | cache.groups = {} 18 | end, 19 | }) 20 | 21 | ---@param rgb integer 22 | ---@return string hex 23 | function M.hex(rgb) 24 | if type(rgb) ~= "number" then 25 | return rgb and tostring(rgb) or nil 26 | end 27 | if cache.hex[rgb] then 28 | return cache.hex[rgb] 29 | end 30 | local band, lsr = bit.band, bit.rshift 31 | 32 | local r = lsr(band(rgb, 0xff0000), 16) 33 | local g = lsr(band(rgb, 0x00ff00), 8) 34 | local b = band(rgb, 0x0000ff) 35 | 36 | local res = ("#%02x%02x%02x"):format(r, g, b) 37 | cache.hex[rgb] = res 38 | return res 39 | end 40 | 41 | ---Alias to `vim.api.nvim_get_hl_id_by_name` 42 | M.hl_id = vim.api.nvim_get_hl_id_by_name 43 | 44 | ---Alias to `vim.api.nvim_get_hl_by_id` 45 | M.hl_by_id = vim.api.nvim_get_hl 46 | 47 | ---@param group string | integer 48 | ---@return vim.api.keyset.get_hl_info 49 | function M.hl(group) 50 | if not group then 51 | error("hl: group is required") 52 | end 53 | if cache.groups[group] then 54 | return cache.groups[group] 55 | end 56 | local hl = vim.api.nvim_get_hl( 57 | 0, 58 | type(group) == "number" and { id = group, link = false } 59 | or { name = group, link = false } 60 | ) 61 | hl = M.sanitize(hl) 62 | cache.groups[group] = hl 63 | return hl 64 | end 65 | 66 | ---@param hl vim.api.keyset.get_hl_info | table 67 | ---@return vim.api.keyset.get_hl_info 68 | function M.sanitize(hl) 69 | for k, v in pairs(hl) do 70 | if type(v) == "number" then 71 | hl[k] = M.hex(v) 72 | end 73 | end 74 | return hl 75 | end 76 | 77 | ---@param group string | integer 78 | ---@param attr string 79 | ---@return any? 80 | function M.fetch_attr(group, attr) 81 | return M.hl(group)[attr] 82 | end 83 | 84 | ---@param group string | integer 85 | ---@param attr string? 86 | ---@return any? 87 | function M.get(group, attr) 88 | local hl = M.hl(group) 89 | if hl and attr then 90 | return hl[attr] 91 | end 92 | return hl 93 | end 94 | 95 | return M 96 | -------------------------------------------------------------------------------- /lua/willothy/lib/http.lua: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willothy/nvim-config/322aced24e2586892579db54243077c4e40149a1/lua/willothy/lib/http.lua -------------------------------------------------------------------------------- /lua/willothy/lib/import-graph.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | local Graph = {} 4 | Graph.__index = Graph 5 | 6 | function Graph.new(opts) 7 | opts = opts or {} 8 | return setmetatable({ 9 | identity_fn = opts.identity_fn or function(node) 10 | return node 11 | end, 12 | modules = {}, 13 | imports = {}, 14 | }, Graph) 15 | end 16 | 17 | function Graph:add_module(node) 18 | local identity = self.identity_fn(node) 19 | if not self.modules[identity] then 20 | self.modules[identity] = node 21 | end 22 | end 23 | 24 | function Graph:add_import(module, imported) 25 | local module_id = self.identity_fn(module) 26 | local import_module_id = self.identity_fn(imported) 27 | 28 | if not self.imports[module_id] then 29 | self.imports[module_id] = {} 30 | end 31 | 32 | self.imports[module_id][import_module_id] = true 33 | end 34 | 35 | function Graph:to_text() 36 | local lines = {} 37 | 38 | for module_id, imports in pairs(self.imports) do 39 | table.insert(lines, module_id) 40 | for import_id in pairs(imports) do 41 | table.insert(lines, " └─ " .. import_id) 42 | end 43 | table.insert(lines, "") 44 | end 45 | 46 | return table.concat(lines, "\n") 47 | end 48 | 49 | function Graph:to_graphviz() 50 | local lines = { "digraph G {", " node [shape=box];" } 51 | 52 | for module_id, imports in pairs(self.imports) do 53 | for import_id, _ in pairs(imports) do 54 | table.insert( 55 | lines, 56 | string.format(' "%s" -> "%s";', module_id, import_id) 57 | ) 58 | end 59 | end 60 | 61 | table.insert(lines, "}") 62 | return table.concat(lines, "\n") 63 | end 64 | 65 | function M.patch_require() 66 | local original_require = _G.require 67 | 68 | local imports = Graph.new() 69 | 70 | _G.import_graph = imports 71 | 72 | ---@diagnostic disable-next-line: duplicate-set-field 73 | _G.require = function(module) 74 | local info = debug.getinfo(2, "S") 75 | local current = info.source 76 | 77 | imports:add_module(module) 78 | imports:add_module(current) 79 | 80 | imports:add_import(current, module) 81 | 82 | return original_require(module) 83 | end 84 | end 85 | 86 | return M 87 | -------------------------------------------------------------------------------- /lua/willothy/lib/llm.lua: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/willothy/nvim-config/322aced24e2586892579db54243077c4e40149a1/lua/willothy/lib/llm.lua -------------------------------------------------------------------------------- /lua/willothy/lib/reactive.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | ---@class willothy.Rx 4 | ---@field signal fun(initial: any): fun(): any, fun(new_value: any): nil 5 | ---@field effect fun(effect_fn: fun()): nil 6 | 7 | ---Create a new reactive system instance 8 | ---@return willothy.Rx 9 | function M.create_system() 10 | ---Current computation being tracked 11 | ---@type fun()|nil 12 | local current_effect = nil 13 | 14 | ---Run a function with dependency tracking 15 | ---@param effect_fn fun() Function to track dependencies for 16 | local function run_effect(effect_fn) 17 | local prev_effect = current_effect 18 | current_effect = effect_fn 19 | effect_fn() 20 | current_effect = prev_effect 21 | end 22 | 23 | ---Create a new reactive value 24 | ---@generic T 25 | ---@param initial T Initial value 26 | ---@return fun(): T 27 | ---@return fun(new_value: T): nil 28 | local function signal(initial) 29 | local value = initial 30 | 31 | ---@type table 32 | local dependencies = {} 33 | 34 | local function get() 35 | if current_effect then 36 | dependencies[current_effect] = true 37 | end 38 | return value 39 | end 40 | 41 | local function set(new_value) 42 | if value == new_value then 43 | return 44 | end 45 | 46 | value = new_value 47 | 48 | for effect in pairs(dependencies) do 49 | -- Remove from dependencies 50 | -- (will be added again when get() is called) 51 | dependencies[effect] = nil 52 | 53 | run_effect(effect) 54 | end 55 | end 56 | 57 | return get, set 58 | end 59 | 60 | return { 61 | signal = signal, 62 | effect = run_effect, 63 | } 64 | end 65 | 66 | return M 67 | -------------------------------------------------------------------------------- /lua/willothy/lib/tab.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | function M.switch_by_step(dir) 4 | local step = vim.v.count1 5 | step = step * dir 6 | 7 | local current_tab = vim.api.nvim_get_current_tabpage() 8 | local all_tabs = vim.api.nvim_list_tabpages() 9 | 10 | local next 11 | for i, tab in ipairs(all_tabs) do 12 | if tab == current_tab then 13 | next = all_tabs[(i + step - 1) % #all_tabs + 1] 14 | end 15 | end 16 | 17 | if next then 18 | vim.api.nvim_set_current_tabpage(next) 19 | end 20 | end 21 | 22 | return M 23 | -------------------------------------------------------------------------------- /lua/willothy/lib/theme.lua: -------------------------------------------------------------------------------- 1 | ---@class willothy.ThemeSpec 2 | ---@field background string -- main editor background 3 | ---@field background_dark string -- floats, popups, sidebar background 4 | ---@field background_light string -- visual selection background 5 | ---@field surface string -- secondary UI surfaces (scrollbars) 6 | ---@field surface_light string -- lighter surface elements (thumbs) 7 | ---@field separator string -- separators and borders 8 | ---@field text string -- primary text color 9 | ---@field text_light string -- lighter text for comments, status 10 | ---@field text_dark string -- darker text for selected text 11 | ---@field line_nr string -- line number color 12 | ---@field inline string -- inline elements (comments) 13 | ---@field property string -- property names (fields) 14 | ---@field variable string -- variable names 15 | ---@field constant string -- constants and enums 16 | ---@field func string -- function names 17 | ---@field keyword string -- keywords, statements 18 | ---@field operator string -- operators, punctuation 19 | ---@field special string -- accent color for UI states 20 | ---@field type string -- type names 21 | ---@field comment string -- comment text 22 | ---@field string string -- string literals 23 | ---@field number string -- numeric literals 24 | ---@field diff table -- diff colors 25 | ---@field diff.add string -- diff added background 26 | ---@field diff.change string -- diff changed background 27 | ---@field diff.delete string -- diff deleted background 28 | ---@field diagnostic table -- diagnostic colors 29 | ---@field diagnostic.error string -- error highlight 30 | ---@field diagnostic.warn string -- warning highlight 31 | ---@field diagnostic.info string -- info highlight 32 | ---@field diagnostic.hint string -- hint highlight 33 | 34 | local M = {} 35 | 36 | ---Generate and apply a colorscheme from a spec 37 | ---@param name string 38 | ---@param s willothy.ThemeSpec 39 | function M.generate(name, s) 40 | local groups = { 41 | -- Core 42 | Normal = { fg = s.text, bg = s.background }, 43 | NormalNC = { fg = s.text_light, bg = s.background_dark }, 44 | Visual = { bg = s.background_light }, 45 | 46 | -- Floating 47 | NormalFloat = { fg = s.text, bg = s.background_dark }, 48 | FloatBorder = { fg = s.separator, bg = s.background_dark }, 49 | Pmenu = { fg = s.text, bg = s.background_dark }, 50 | PmenuSel = { fg = s.text_dark, bg = s.surface_light }, 51 | PmenuSbar = { bg = s.surface }, 52 | PmenuThumb = { bg = s.surface_light }, 53 | 54 | -- UI Elements 55 | WinSeparator = { fg = s.separator }, 56 | Title = { fg = s.property, bold = true }, 57 | LineNr = { fg = s.line_nr }, 58 | CursorLineNr = { fg = s.number, bold = true }, 59 | SignColumn = { bg = s.background }, 60 | Folded = { fg = s.text_light, bg = s.background_dark }, 61 | FoldColumn = { fg = s.text_light }, 62 | ColorColumn = { bg = s.background_light }, 63 | CursorLine = { bg = s.background_light }, 64 | CursorColumn = { bg = s.background_light }, 65 | EndOfBuffer = { fg = s.background_dark }, 66 | Whitespace = { fg = s.surface_light }, 67 | 68 | -- Status & Tabs 69 | StatusLine = { fg = s.text, bg = s.surface }, 70 | StatusLineNC = { fg = s.text_light, bg = s.background_dark }, 71 | TabLine = { fg = s.text_light, bg = s.background_dark }, 72 | TabLineSel = { fg = s.number, bg = s.background }, 73 | TabLineFill = { bg = s.background_dark }, 74 | 75 | -- WinBar 76 | WinBar = { fg = s.text_light, bg = s.background_dark }, 77 | WinBarNC = { fg = s.text_light, bg = s.background_dark }, 78 | 79 | -- Messages 80 | ModeMsg = { fg = s.special, bold = true }, 81 | MoreMsg = { fg = s.special }, 82 | MsgArea = { fg = s.text_light, bg = s.background }, 83 | 84 | -- Search & Matches using accent 85 | IncSearch = { fg = s.text_dark, bg = s.special, bold = true }, 86 | Search = { fg = s.text_dark, bg = s.special }, 87 | MatchParen = { fg = s.special, underline = true }, 88 | Substitute = { fg = s.text_dark, bg = s.special }, 89 | 90 | -- Diagnostics 91 | DiagnosticError = { fg = s.diagnostic.error }, 92 | DiagnosticWarn = { fg = s.diagnostic.warn }, 93 | DiagnosticInfo = { fg = s.diagnostic.info }, 94 | DiagnosticHint = { fg = s.diagnostic.hint }, 95 | DiagnosticUnderlineError = { sp = s.diagnostic.error, underline = true }, 96 | DiagnosticUnderlineWarn = { sp = s.diagnostic.warn, underline = true }, 97 | DiagnosticUnderlineInfo = { sp = s.diagnostic.info, underline = true }, 98 | DiagnosticUnderlineHint = { sp = s.diagnostic.hint, underline = true }, 99 | 100 | -- Diff 101 | DiffAdd = { bg = s.diff.add }, 102 | DiffChange = { bg = s.diff.change }, 103 | DiffDelete = { bg = s.diff.delete }, 104 | 105 | -- LSP UI 106 | LspReferenceRead = { bg = s.background_dark }, 107 | LspReferenceWrite = { bg = s.background_dark }, 108 | LspCodeLens = { fg = s.text_light }, 109 | LspCodeLensSeparator = { fg = s.separator }, 110 | LspSignatureActiveParameter = { fg = s.special }, 111 | LspInlayHint = { fg = s.text_light }, 112 | 113 | -- Syntax 114 | Constant = { fg = s.constant }, 115 | Identifier = { fg = s.variable }, 116 | Function = { fg = s.func }, 117 | Statement = { fg = s.keyword }, 118 | Operator = { fg = s.operator }, 119 | Type = { fg = s.type }, 120 | 121 | -- Treesitter 122 | ["@comment"] = { fg = s.comment, italic = true }, 123 | ["@constant"] = { fg = s.constant }, 124 | ["@constant.builtin"] = { fg = s.constant }, 125 | ["@string"] = { fg = s.string }, 126 | ["@number"] = { fg = s.number }, 127 | ["@boolean"] = { fg = s.number }, 128 | ["@float"] = { fg = s.number }, 129 | ["@identifier"] = { fg = s.variable }, 130 | ["@property"] = { fg = s.property }, 131 | ["@parameter"] = { fg = s.variable }, 132 | ["@function"] = { fg = s.func }, 133 | ["@keyword"] = { fg = s.keyword }, 134 | ["@operator"] = { fg = s.operator }, 135 | ["@type"] = { fg = s.type }, 136 | ["@type.builtin"] = { fg = s.type }, 137 | ["@tag"] = { fg = s.special }, 138 | ["@punctuation.delimiter"] = { fg = s.operator }, 139 | ["@punctuation.bracket"] = { fg = s.operator }, 140 | ["@text.literal"] = { fg = s.text }, 141 | } 142 | 143 | require("mini.colors") 144 | .as_colorscheme({ 145 | name = name, 146 | groups = groups, 147 | terminal = {}, 148 | }) 149 | :apply() 150 | end 151 | 152 | return M 153 | -------------------------------------------------------------------------------- /lua/willothy/lib/trie.lua: -------------------------------------------------------------------------------- 1 | ---@class Trie 2 | ---@field private children table 3 | ---@field private is_end boolean 4 | local Trie = {} 5 | Trie.__index = Trie 6 | Trie.__newindex = function() 7 | error("Cannot inject fields into Trie") 8 | end 9 | Trie.__metatable = "Trie" 10 | 11 | ---@return Trie 12 | function Trie.new() 13 | return setmetatable({ 14 | children = {}, 15 | is_end = false, 16 | }, Trie) 17 | end 18 | 19 | ---@param iterable string[] | table | Iter | fun(...:any):...:string 20 | function Trie.from_iter(iterable) 21 | local self = Trie.new() 22 | for str in vim.iter(iterable) do 23 | self:insert(str) 24 | end 25 | return self 26 | end 27 | 28 | ---@param s string 29 | function Trie:insert(s) 30 | local node = self 31 | for i = 1, #s do 32 | local ch = s:sub(i, i) 33 | 34 | if not node.children[ch] then 35 | node.children[ch] = Trie.new() 36 | end 37 | node = node.children[ch] 38 | end 39 | node.is_end = true 40 | end 41 | 42 | ---@param s string 43 | ---@return boolean 44 | function Trie:has(s) 45 | local node = self 46 | for ch in s:gmatch(".") do 47 | local next = node.children[ch] 48 | if not next then 49 | return false 50 | end 51 | node = node.children[ch] 52 | end 53 | return node.is_end 54 | end 55 | 56 | ---@private 57 | function Trie:_remove(s, i) 58 | if i == #s then 59 | self.is_end = false 60 | return #self.children == 0 61 | end 62 | local ch = s:sub(i, i) 63 | local next 64 | if self.children[ch] then 65 | next = self.children[ch]:_remove(s, i + 1) 66 | if next then 67 | self.children[ch] = nil 68 | end 69 | end 70 | return next and not self.is_end and #self.children == 0 71 | end 72 | 73 | function Trie:remove(s) 74 | self:_remove(s, 1) 75 | end 76 | 77 | ---@private 78 | function Trie:_matches(prefix, results, i) 79 | if i > #prefix then 80 | for byte = 96, 122 do 81 | local ch = string.char(byte) 82 | local child = self.children[ch] 83 | if child then 84 | if child.is_end then 85 | table.insert(results, prefix .. ch) 86 | end 87 | child:_matches(prefix .. ch, results, i + 1) 88 | end 89 | end 90 | else 91 | local ch = prefix:sub(i, i) 92 | if self.children[ch] then 93 | return self.children[ch]:_matches(prefix, results, i + 1) 94 | end 95 | end 96 | 97 | return results 98 | end 99 | 100 | function Trie:matches(prefix) 101 | return self:_matches(prefix, {}, 1) 102 | end 103 | 104 | return Trie 105 | -------------------------------------------------------------------------------- /lua/willothy/lib/win.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | function M.pick_focus() 4 | local win = require("window-picker").pick_window({ 5 | filter_rules = { 6 | bo = { 7 | buftype = {}, 8 | }, 9 | include_current_win = false, 10 | }, 11 | }) 12 | if not win then 13 | return 14 | end 15 | vim.api.nvim_set_current_win(win) 16 | end 17 | 18 | function M.pick_create() 19 | local win = require("window-picker").pick_window({ 20 | filter_rules = { 21 | bo = { 22 | buftype = {}, 23 | }, 24 | include_current_win = true, 25 | autoselect_one = true, 26 | }, 27 | or_create = true, 28 | }) 29 | if not win then 30 | return 31 | end 32 | vim.api.nvim_set_current_win(win) 33 | end 34 | 35 | function M.pick_swap() 36 | local win = require("window-picker").pick_window({ 37 | filter_rules = { 38 | autoselect_one = false, 39 | bo = { 40 | buftype = { 41 | "nofile", 42 | "nowrite", 43 | "prompt", 44 | }, 45 | }, 46 | }, 47 | }) 48 | if not win then 49 | return 50 | end 51 | local curwin = vim.api.nvim_get_current_win() 52 | if 53 | require("stickybuf").is_pinned(win) 54 | or require("stickybuf").is_pinned(curwin) 55 | then 56 | -- hack to fix window dimming 57 | vim.api.nvim_set_current_win(curwin) 58 | return 59 | end 60 | 61 | local buf = vim.api.nvim_win_get_buf(win) 62 | local curbuf = vim.api.nvim_get_current_buf() 63 | if win == curwin then 64 | return 65 | end 66 | local cur_view = vim.api.nvim_win_call(curwin, vim.fn.winsaveview) 67 | local tgt_view = vim.api.nvim_win_call(win, vim.fn.winsaveview) 68 | if buf ~= curbuf then 69 | vim.api.nvim_win_set_buf(win, curbuf) 70 | vim.api.nvim_win_set_buf(curwin, buf) 71 | end 72 | vim.api.nvim_win_call(curwin, function() 73 | vim.fn.winrestview(tgt_view) 74 | end) 75 | vim.api.nvim_win_call(win, function() 76 | vim.fn.winrestview(cur_view) 77 | end) 78 | end 79 | 80 | function M.pick_close() 81 | local win = require("window-picker").pick_window({ 82 | filter_rules = { 83 | include_current_win = true, 84 | autoselect_one = false, 85 | }, 86 | }) 87 | if not win then 88 | return 89 | end 90 | local ok, res = pcall(vim.api.nvim_win_close, win, false) 91 | if not ok then 92 | if 93 | vim.startswith(res --[[@as string]], "Vim:E444") 94 | then 95 | vim.ui.select({ "Close", "Cancel" }, { 96 | prompt = "Close window?", 97 | }, function(i) 98 | if i == "Close" then 99 | vim.api.nvim_exec2("qa!", { output = true }) 100 | end 101 | end) 102 | else 103 | vim.notify("could not close window", vim.log.levels.WARN) 104 | end 105 | end 106 | end 107 | 108 | function M.is_float(win) 109 | return vim.api.nvim_win_get_config(win).zindex ~= nil 110 | end 111 | 112 | function M.is_focusable(win) 113 | if M.is_float(win) then 114 | return vim.api.nvim_win_get_config(win).focusable 115 | end 116 | return true 117 | end 118 | 119 | function M.iter() 120 | return vim.iter(vim.api.nvim_list_wins()) 121 | end 122 | 123 | function M.close(win) 124 | if not M.is_last(win) then 125 | vim.api.nvim_win_close(win, true) 126 | end 127 | end 128 | 129 | function M.is_last(win) 130 | local layout = vim.api.nvim_win_call(win, vim.fn.winlayout) 131 | return layout[1] == "leaf" 132 | end 133 | 134 | function M.close_floats() 135 | M.iter():filter(M.is_float):each(M.close) 136 | end 137 | 138 | function M.close_all() 139 | for _, win in ipairs(vim.api.nvim_list_wins()) do 140 | M.close(win) 141 | end 142 | end 143 | 144 | function M.open(buf, config, enter) 145 | config = vim.tbl_deep_extend("force", { 146 | relative = "cursor", 147 | row = 1, 148 | col = 1, 149 | width = 40, 150 | height = 10, 151 | style = "minimal", 152 | border = "solid", 153 | }, config or {}) 154 | buf = buf or vim.api.nvim_create_buf(false, true) 155 | 156 | return vim.api.nvim_open_win(buf, enter or false, config), buf 157 | end 158 | 159 | ---@param win integer, 160 | ---@param fn fun(conf: vim.api.keyset.win_config): vim.api.keyset.win_config? 161 | function M.update_config(win, fn) 162 | local config = vim.api.nvim_win_get_config(win) 163 | local res = fn(config) 164 | if res ~= nil then 165 | config = res 166 | end 167 | vim.api.nvim_win_set_config(win, config) 168 | end 169 | 170 | return M 171 | -------------------------------------------------------------------------------- /lua/willothy/linting.lua: -------------------------------------------------------------------------------- 1 | local lint = require("lint") 2 | 3 | lint.linters_by_ft = { 4 | -- lua = { "selene" }, 5 | -- proto = { "protolint" }, 6 | markdown = { "markdownlint" }, 7 | zsh = { "shellcheck" }, 8 | json = { "jsonlint" }, 9 | } 10 | 11 | local group = 12 | vim.api.nvim_create_augroup("willothy.nvim-lint", { clear = true }) 13 | 14 | vim.api.nvim_create_autocmd({ 15 | "TextChangedI", 16 | "InsertLeave", 17 | }, { 18 | group = group, 19 | callback = require("willothy.lib.fn").debounce_trailing(function() 20 | lint.try_lint() 21 | end, 1000), 22 | }) 23 | 24 | vim.api.nvim_create_autocmd({ 25 | "TextChanged", 26 | "BufWritePost", 27 | }, { 28 | group = group, 29 | callback = function() 30 | lint.try_lint() 31 | end, 32 | }) 33 | -------------------------------------------------------------------------------- /lua/willothy/lsp/capabilities.lua: -------------------------------------------------------------------------------- 1 | local function make_capabilities() 2 | local capabilities = vim.lsp.protocol.make_client_capabilities() 3 | 4 | capabilities.textDocument.foldingRange = { 5 | dynamicRegistration = false, 6 | lineFoldingOnly = true, 7 | } 8 | 9 | capabilities.textDocument.formatting = { 10 | dynamicRegistration = false, 11 | } 12 | 13 | capabilities.textDocument.semanticTokens.augmentsSyntaxTokens = false 14 | 15 | capabilities.textDocument.completion.completionItem = { 16 | contextSupport = true, 17 | snippetSupport = true, 18 | deprecatedSupport = true, 19 | commitCharactersSupport = true, 20 | resolveSupport = { 21 | properties = { 22 | "documentation", 23 | "detail", 24 | "additionalTextEdits", 25 | }, 26 | }, 27 | labelDetailsSupport = true, 28 | documentationFormat = { "markdown", "plaintext" }, 29 | } 30 | 31 | -- send actions with hover request 32 | capabilities.experimental = { 33 | hoverActions = true, 34 | hoverRange = true, 35 | serverStatusNotification = true, 36 | -- snippetTextEdit = true, -- not supported yet 37 | codeActionGroup = true, 38 | ssr = true, 39 | commands = { 40 | "rust-analyzer.runSingle", 41 | "rust-analyzer.debugSingle", 42 | "rust-analyzer.showReferences", 43 | "rust-analyzer.gotoLocation", 44 | "editor.action.triggerParameterHints", 45 | }, 46 | } 47 | 48 | return require("blink.cmp").get_lsp_capabilities(capabilities, true) 49 | end 50 | 51 | return { 52 | make_capabilities = make_capabilities, 53 | } 54 | -------------------------------------------------------------------------------- /lua/willothy/lsp/init.lua: -------------------------------------------------------------------------------- 1 | vim.lsp.config("*", { 2 | capabilities = require("willothy.lsp.capabilities").make_capabilities(), 3 | }) 4 | 5 | require("mason").setup() 6 | 7 | local disabled = { 8 | emmylua_ls = true, 9 | -- lua_ls = true, 10 | } 11 | 12 | local configured = { 13 | "basedpyright", 14 | "bashls", 15 | "biome", 16 | "bufls", 17 | "clangd", 18 | "cmake", 19 | "dockerls", 20 | "emmylua_ls", 21 | "eslint", 22 | "gleam", 23 | "gopls", 24 | "intelephense", 25 | "jsonls", 26 | "lua_ls", 27 | "prismals", 28 | "rust_analyzer", 29 | "tailwindcss", 30 | "taplo", 31 | "ts_ls", 32 | "zls", 33 | "postgres_lsp", -- supabase-community/postgres-language-server 34 | } 35 | 36 | local function init() 37 | vim.iter(configured):each(vim.schedule_wrap(function(server_name) 38 | if disabled[server_name] then 39 | return 40 | end 41 | vim.lsp.enable(server_name) 42 | end)) 43 | end 44 | 45 | if vim.g.did_very_lazy then 46 | vim.schedule(init) 47 | else 48 | vim.api.nvim_create_autocmd("User", { 49 | pattern = "VeryLazy", 50 | once = true, 51 | callback = vim.schedule_wrap(init), 52 | }) 53 | end 54 | -------------------------------------------------------------------------------- /lua/willothy/settings.lua: -------------------------------------------------------------------------------- 1 | local o = vim.o 2 | local opt = vim.opt 3 | local icons = require("willothy.ui.icons") 4 | 5 | o.cmdheight = 0 6 | o.scrolloff = 16 7 | 8 | o.shell = "fish" 9 | 10 | o.number = true 11 | o.relativenumber = true 12 | -- o.signcolumn = "yes" 13 | -- o.signcolumn = "number" 14 | o.signcolumn = "auto" 15 | -- o.relativenumber = false 16 | -- o.number = false 17 | 18 | o.shortmess = "filnxoOCFIsw" 19 | o.virtualedit = "block" 20 | 21 | o.wrap = false 22 | o.autoread = true 23 | o.showtabline = 2 24 | o.laststatus = 3 25 | 26 | o.swapfile = true 27 | o.backup = false 28 | o.undofile = true 29 | 30 | o.hlsearch = false 31 | o.incsearch = true 32 | o.cursorline = true 33 | o.cursorlineopt = "number" 34 | 35 | o.modeline = false 36 | 37 | o.timeout = true 38 | o.timeoutlen = 250 39 | 40 | o.updatetime = 500 41 | o.mousemodel = "extend" 42 | o.mousetime = 200 43 | o.mousemoveevent = true 44 | 45 | o.conceallevel = 3 46 | o.confirm = true 47 | 48 | o.foldcolumn = "1" 49 | o.foldlevel = 99 50 | o.foldlevelstart = 99 51 | o.foldenable = true 52 | -- o.foldopen = "block,hor,mark,percent,quickfix,search,tag,undo" 53 | o.foldopen = "block,mark,percent,quickfix,search,tag,undo" 54 | o.foldmethod = "expr" 55 | 56 | o.foldtext = "" 57 | 58 | vim.api.nvim_create_autocmd("User", { 59 | once = true, 60 | pattern = "VeryLazy", 61 | callback = function() 62 | -- just to keep treesitter lazy-loaded 63 | o.foldexpr = "v:lua.willothy.ui.foldexpr()" 64 | end, 65 | }) 66 | 67 | o.syntax = "off" 68 | 69 | o.splitkeep = "screen" 70 | -- o.splitkeep = "cursor" 71 | 72 | -- Pcall to avoid errors on older versions of nvim 73 | o.smoothscroll = true 74 | 75 | o.mousescroll = "ver:1,hor:6" 76 | 77 | o.tabstop = 2 78 | o.shiftwidth = 2 79 | o.softtabstop = 2 80 | o.expandtab = true 81 | 82 | -- o.indentkeys = o.indentkeys .. ",!0\t" 83 | 84 | o.winborder = "none" 85 | 86 | opt.fillchars = { 87 | horiz = "─", 88 | horizup = "┴", 89 | horizdown = "┬", 90 | vert = "│", 91 | vertleft = "┤", 92 | vertright = "├", 93 | verthoriz = "┼", 94 | fold = "⠀", 95 | eob = " ", 96 | diff = "┃", 97 | msgsep = " ", 98 | foldsep = " ", 99 | foldclose = icons.fold.closed, 100 | foldopen = icons.fold.open, 101 | } 102 | 103 | vim.o.clipboard = "unnamedplus" 104 | 105 | -- Wezterm doesn't support OSC 52 yet :( 106 | -- 107 | -- vim.g.clipboard = { 108 | -- name = "OSC 52", 109 | -- copy = { 110 | -- ["+"] = require("vim.ui.clipboard.osc52").copy("+"), 111 | -- ["*"] = require("vim.ui.clipboard.osc52").copy("*"), 112 | -- }, 113 | -- paste = { 114 | -- ["+"] = require("vim.ui.clipboard.osc52").paste("+"), 115 | -- ["*"] = require("vim.ui.clipboard.osc52").paste("*"), 116 | -- }, 117 | -- } 118 | -------------------------------------------------------------------------------- /lua/willothy/tasks.lua: -------------------------------------------------------------------------------- 1 | local opts = { 2 | strategy = { 3 | "toggleterm", 4 | use_shell = false, 5 | direction = "horizontal", 6 | open_on_start = false, 7 | close_on_exit = false, 8 | quit_on_exit = "success", 9 | }, 10 | form = { 11 | border = "solid", 12 | win_opts = { 13 | winblend = 0, 14 | winhl = "FloatBorder:NormalFloat", 15 | }, 16 | }, 17 | confirm = { 18 | border = "solid", 19 | win_opts = { 20 | winblend = 0, 21 | winhl = "FloatBorder:NormalFloat", 22 | }, 23 | }, 24 | task_win = { 25 | border = "solid", 26 | win_opts = { 27 | winblend = 0, 28 | winhl = "FloatBorder:NormalFloat", 29 | }, 30 | }, 31 | help_win = { 32 | border = "solid", 33 | win_opts = { 34 | winblend = 0, 35 | winhl = "FloatBorder:NormalFloat", 36 | }, 37 | }, 38 | task_list = { 39 | direction = "right", 40 | win_opts = { 41 | winblend = 0, 42 | winhl = "FloatBorder:NormalFloat,WinBar:EdgyWinBar", 43 | }, 44 | }, 45 | } 46 | 47 | local overseer = require("overseer") 48 | 49 | overseer.setup(opts) 50 | 51 | vim.api.nvim_create_user_command("OverseerFloatLast", function() 52 | local tasks = overseer.list_tasks({ recent_first = true }) 53 | if vim.tbl_isempty(tasks) then 54 | vim.notify("No tasks found", vim.log.levels.WARN) 55 | else 56 | overseer.run_action(tasks[1], "open float") 57 | end 58 | end, {}) 59 | 60 | local float = require("snacks").win.new({ 61 | border = "solid", 62 | relative = "editor", 63 | width = 30, 64 | height = function(self) 65 | if self:buf_valid() then 66 | local buf_height = vim.api.nvim_buf_line_count(self.buf) 67 | return math.min(buf_height, 30) 68 | end 69 | return 0 70 | end, 71 | row = 2, 72 | col = function() 73 | return vim.o.columns - 30 - 3 74 | end, 75 | 76 | show = false, 77 | backdrop = false, 78 | 79 | title_pos = "center", 80 | title = { { "  Tasks ", "OverseerTitle" } }, 81 | fixbuf = true, 82 | 83 | on_buf = function(self) 84 | overseer.list_tasks() 85 | local buf = require("overseer.task_list").get_or_create_bufnr() 86 | vim.bo[buf].filetype = "OverseerList" 87 | self.buf = buf 88 | 89 | vim.api.nvim_create_autocmd("User", { 90 | pattern = "OverseerListUpdate", 91 | callback = vim.schedule_wrap(function() 92 | if self.buf and vim.api.nvim_buf_is_valid(self.buf) then 93 | self:update() 94 | end 95 | end), 96 | }) 97 | end, 98 | 99 | on_win = function(self) 100 | self:on( 101 | "VimResized", 102 | vim.schedule_wrap(function() 103 | self:update() 104 | end), 105 | { 106 | buffer = self.buf, 107 | } 108 | ) 109 | 110 | local error = vim.api.nvim_get_hl(0, { 111 | name = "DiagnosticInfo", 112 | link = false, 113 | }) 114 | local title = vim.api.nvim_get_hl(0, { 115 | name = "Normal", 116 | link = false, 117 | }) 118 | vim.api.nvim_set_hl(0, "OverseerTitle", { 119 | bg = error.fg, 120 | fg = title.bg, 121 | }) 122 | end, 123 | }) 124 | 125 | local toggle = require("snacks").toggle.new({ 126 | name = "Overseer Menu", 127 | notify = false, 128 | get = function() 129 | return float:valid() 130 | and float.win == require("overseer.window").get_win_id() 131 | end, 132 | set = vim.schedule_wrap(function(open) 133 | if open == false then 134 | float:hide() 135 | else 136 | float:show() 137 | end 138 | end), 139 | }) 140 | 141 | toggle:map("uo") 142 | 143 | return toggle 144 | -------------------------------------------------------------------------------- /lua/willothy/terminal.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | local BaseTerminal = require("toggleterm.terminal").Terminal 4 | 5 | require("configs.terminal.toggleterm") 6 | 7 | -- require("toggleterm.constants").FILETYPE = "terminal" 8 | require("toggleterm.constants").FILETYPE = "terminal" 9 | 10 | local Terminal = BaseTerminal:new({ 11 | -- cmd = "zsh", 12 | hidden = false, 13 | close_on_exit = true, 14 | start_in_insert = false, 15 | persist_size = false, 16 | shade_terminals = false, 17 | auto_scroll = false, 18 | highlights = { 19 | Normal = { link = "Normal" }, 20 | FloatBorder = { link = "NormalFloat" }, 21 | }, 22 | float_opts = { 23 | border = "solid", 24 | }, 25 | }) 26 | 27 | ---@param opts TermCreateArgs 28 | ---@return Terminal 29 | ---@diagnostic disable-next-line: inject-field 30 | function Terminal:extend(opts) 31 | opts = opts or {} 32 | self.__index = self 33 | return setmetatable(opts--[[@as Terminal]], self) 34 | end 35 | 36 | M.float = Terminal:extend({ 37 | display_name = "floating", 38 | direction = "float", 39 | on_create = function(term) 40 | local buf = term.bufnr 41 | vim.keymap.set("n", "q", function() 42 | term:close() 43 | end, { buffer = buf }) 44 | end, 45 | }) 46 | 47 | M.main = Terminal:extend({ 48 | display_name = "main", 49 | direction = "horizontal", 50 | -- cmd = "tmux new -A -s nvim-main", 51 | }) 52 | 53 | M.vertical = Terminal:extend({ 54 | display_name = "secondary", 55 | direction = "vertical", 56 | }) 57 | 58 | M.xplr = Terminal:extend({ 59 | display_name = "xplr", 60 | cmd = "xplr", 61 | direction = "float", 62 | }) 63 | 64 | M.yazi = Terminal:extend({ 65 | display_name = "yazi", 66 | cmd = "yazi", 67 | direction = "vertical", 68 | }) 69 | 70 | M.py = Terminal:extend({ 71 | display_name = "python", 72 | cmd = "python3", 73 | }) 74 | 75 | M.lua = Terminal:extend({ 76 | display_name = "lua", 77 | cmd = "lua", 78 | }) 79 | 80 | function M.job(cmd) 81 | local args = {} 82 | if type(cmd) == "table" then 83 | args = cmd 84 | cmd = table.remove(cmd, 1) 85 | end 86 | local overseer = require("overseer") 87 | ---@type overseer.Task 88 | local task = overseer.new_task({ 89 | cmd = cmd, 90 | args = args, 91 | name = cmd, 92 | }) 93 | task:subscribe( 94 | "on_complete", 95 | vim.schedule_wrap(function() 96 | if not overseer.window.is_open() then 97 | overseer.open({ enter = false, direction = "left" }) 98 | end 99 | end) 100 | ) 101 | task:start() 102 | return task 103 | end 104 | 105 | function M.toggle() 106 | M.main:toggle() 107 | end 108 | 109 | function M.toggle_float() 110 | M.float:toggle() 111 | end 112 | 113 | function M.send_to_main(cmd) 114 | local a = require("nio") 115 | a.run(function() 116 | if not cmd then 117 | cmd = a.ui.input({ 118 | prompt = "$ ", 119 | completion = "shellcmd", 120 | highlight = function() end, 121 | }) 122 | end 123 | 124 | if not M.main.bufnr or not vim.api.nvim_buf_is_valid(M.main.bufnr) then 125 | M.main:spawn() 126 | a.sleep(200) 127 | end 128 | if type(cmd) == "string" then 129 | cmd = cmd .. "\r" 130 | elseif type(cmd) == "table" then 131 | cmd[#cmd] = cmd[#cmd] .. "\r" 132 | end 133 | M.main:send(cmd) 134 | end) 135 | end 136 | 137 | function M.cargo_build() 138 | local p = require("fidget").progress.handle.create({ 139 | title = "Compiling", 140 | lsp_client = { 141 | name = "cargo", 142 | }, 143 | }) 144 | vim.system( 145 | { 146 | "cargo", 147 | "build", 148 | }, 149 | { 150 | text = true, 151 | stderr = vim.schedule_wrap(function(_, data) 152 | data = data or "" 153 | data = data:gsub("^%s+", ""):gsub("%s+$", "") 154 | data = vim.split(data, "\n")[1] 155 | if data:sub(1, #"Compiling") == "Compiling" then 156 | local crate, version = data:match("Compiling ([^ ]+) v([^ ]+)") 157 | p:report({ 158 | message = string.format("%s %s", crate, version), 159 | }) 160 | end 161 | end), 162 | }, 163 | vim.schedule_wrap(function() 164 | p:finish() 165 | end) 166 | ) 167 | end 168 | 169 | function M.get_direction(buf, win) 170 | win = win or vim.fn.bufwinid(buf) 171 | if not win then 172 | return 173 | end 174 | if vim.api.nvim_win_get_config(win).zindex ~= nil then 175 | return "float" 176 | end 177 | 178 | local layout = vim.fn.winlayout() 179 | 180 | local queue = { layout } 181 | local direction 182 | local last 183 | local current 184 | repeat 185 | last = current 186 | current = table.remove(queue, 1) 187 | if not current then 188 | break 189 | end 190 | if current[1] ~= "leaf" then 191 | for _, child in ipairs(current[2]) do 192 | if child[1] == "leaf" then 193 | if child[2] == win then 194 | direction = current[1] 195 | break 196 | end 197 | else 198 | table.insert(queue, child) 199 | end 200 | end 201 | end 202 | until current == nil 203 | 204 | if last == layout then 205 | if direction == "col" then 206 | direction = "horizontal" 207 | elseif direction == "row" then 208 | direction = "vertical" 209 | end 210 | else 211 | if direction == "col" then 212 | direction = "vertical" 213 | elseif direction == "row" then 214 | direction = "horizontal" 215 | end 216 | end 217 | return direction 218 | end 219 | 220 | return M 221 | -------------------------------------------------------------------------------- /lua/willothy/testing.lua: -------------------------------------------------------------------------------- 1 | ---@diagnostic disable-next-line: missing-fields 2 | require("neotest").setup({ 3 | adapters = { 4 | require("neotest-rust"), 5 | -- require("neotest-plenary"), 6 | }, 7 | consumers = { 8 | ---@diagnostic disable-next-line: assign-type-mismatch 9 | overseer = require("neotest.consumers.overseer"), 10 | }, 11 | ---@diagnostic disable-next-line: missing-fields 12 | summary = { 13 | enabled = true, 14 | animated = true, 15 | }, 16 | diagnostic = { 17 | enabled = true, 18 | severity = vim.diagnostic.severity.ERROR, 19 | }, 20 | status = { 21 | enabled = true, 22 | virtual_text = true, 23 | signs = true, 24 | }, 25 | }) 26 | -------------------------------------------------------------------------------- /lua/willothy/treesitter.lua: -------------------------------------------------------------------------------- 1 | ---@diagnostic disable: missing-fields 2 | -- require("tree-sitter-just").setup({}) 3 | 4 | require("nvim-treesitter.configs").setup({ 5 | -- A list of parser names, or "all" 6 | ensure_installed = { 7 | "query", 8 | "javascript", 9 | "typescript", 10 | "c", 11 | "go", 12 | "cpp", 13 | "lua", 14 | "rust", 15 | "bash", 16 | "markdown", 17 | "markdown_inline", 18 | "gitcommit", 19 | "gitignore", 20 | "git_rebase", 21 | "git_config", 22 | "jsonc", 23 | "json", 24 | }, 25 | sync_install = false, 26 | auto_install = true, 27 | highlight = { 28 | enable = true, 29 | -- disable = { 30 | -- "css", 31 | -- "scss", 32 | -- }, 33 | -- additional_vim_regex_highlighting = false, 34 | }, 35 | indent = { 36 | enable = true, 37 | }, 38 | injections = { enable = true }, 39 | context_commentstring = { 40 | config = { 41 | javascript = { 42 | __default = "// %s", 43 | jsx_element = "{/* %s */}", 44 | jsx_fragment = "{/* %s */}", 45 | jsx_attribute = "// %s", 46 | comment = "// %s", 47 | }, 48 | typescript = { __default = "// %s", __multiline = "/* %s */" }, 49 | }, 50 | }, 51 | textobjects = { 52 | select = { 53 | enable = true, 54 | lookahead = true, 55 | keymaps = { 56 | ["is"] = { query = "@statement.inner", desc = "statement" }, 57 | ["as"] = { query = "@statement.outer", desc = "statement" }, 58 | ["ic"] = { query = "@class.inner", desc = "class" }, 59 | ["ac"] = { query = "@class.outer", desc = "class" }, 60 | ["iF"] = { query = "@function.inner", desc = "function" }, 61 | ["aF"] = { query = "@function.outer", desc = "function" }, 62 | }, 63 | selection_modes = { 64 | ["@parameter.outer"] = "v", 65 | -- ["@function.outer"] = "V", 66 | ["@class.outer"] = "", 67 | }, 68 | }, 69 | swap = { 70 | enable = true, 71 | }, 72 | move = { 73 | enable = true, 74 | goto_next_start = { 75 | ["]f"] = { query = "@function.outer", desc = "function" }, 76 | ["]c"] = { query = "@call.outer", desc = "call" }, 77 | }, 78 | goto_previous_start = { 79 | ["[f"] = { query = "@function.outer", desc = "function" }, 80 | ["[c"] = { query = "@call.outer", desc = "call" }, 81 | }, 82 | }, 83 | }, 84 | }) 85 | -------------------------------------------------------------------------------- /lua/willothy/ui/cursor.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | local noice = require("noice.util.hacks") 4 | local prev_cursor 5 | 6 | function M.hide_cursor() 7 | prev_cursor = noice._guicursor 8 | noice._guicursor = nil 9 | end 10 | 11 | function M.show_cursor() 12 | noice._guicursor = prev_cursor 13 | prev_cursor = nil 14 | end 15 | 16 | return M 17 | -------------------------------------------------------------------------------- /lua/willothy/ui/foldexpr.lua: -------------------------------------------------------------------------------- 1 | -- Foldexpr v1: largely based on `vim.treesitter.foldexpr`, and works by 2 | -- hijacking its upvalues to get the foldinfos table. 3 | -- 4 | -- local foldinfos 5 | -- 6 | -- for i = 1, 5 do 7 | -- -- hacky way to get the foldinfos table 8 | -- -- I do not want to reimplement treesitter folding myself lol 9 | -- local name, val = 10 | -- debug.getupvalue(require("vim.treesitter._fold").foldexpr, i) 11 | -- if name == "foldinfos" then 12 | -- foldinfos = val 13 | -- break 14 | -- end 15 | -- end 16 | -- 17 | -- --- 18 | -- ---This works for things that end with 'end', but not for tables. 19 | -- 20 | -- ---@param lnum? integer 21 | -- ---@return string 22 | -- return function(lnum) 23 | -- local ts = vim.treesitter.foldexpr(lnum) 24 | -- 25 | -- -- fallback to default treesitter foldexpr if we 26 | -- -- can't find the foldinfos table in its upvalues 27 | -- if not foldinfos then 28 | -- return ts 29 | -- end 30 | -- 31 | -- lnum = lnum or vim.v.lnum 32 | -- local bufnr = vim.api.nvim_get_current_buf() 33 | -- 34 | -- if not foldinfos[bufnr] then 35 | -- return ts 36 | -- end 37 | -- 38 | -- ---@type TS.FoldInfo 39 | -- local info = foldinfos[bufnr] 40 | -- 41 | -- local fold_raw = info.levels0[lnum] 42 | -- if not fold_raw then 43 | -- return "0" 44 | -- end 45 | -- 46 | -- local next_raw = info.levels0[lnum + 1] 47 | -- if next_raw and next_raw < fold_raw then 48 | -- return tostring(math.max(0, fold_raw - 1)) 49 | -- end 50 | -- 51 | -- return info.levels[lnum] or "0" 52 | -- end 53 | 54 | -- Foldexpr v2: largely lifted from nvim-treesitter/nvim-treesitter 55 | -- 56 | -- Works more consistently than v1, works with large tables, but is likely slower. 57 | local api = vim.api 58 | local tsutils, query, parsers 59 | local folds_levels 60 | 61 | local function init() 62 | tsutils = require("nvim-treesitter.ts_utils") 63 | query = require("nvim-treesitter.query") 64 | parsers = require("nvim-treesitter.parsers") 65 | 66 | -- This is cached on buf tick to avoid computing that multiple times 67 | -- Especially not for every line in the file when `zx` is hit 68 | folds_levels = tsutils.memoize_by_buf_tick(function(bufnr) 69 | local max_fold_level = api.nvim_get_option_value("foldnestmax", { 70 | win = 0, 71 | }) 72 | local trim_level = function(level) 73 | if level > max_fold_level then 74 | return max_fold_level 75 | end 76 | return level 77 | end 78 | 79 | local parser = parsers.get_parser(bufnr) 80 | 81 | if not parser then 82 | return {} 83 | end 84 | 85 | local matches = query.get_capture_matches_recursively(bufnr, function(lang) 86 | if query.has_folds(lang) then 87 | return "@fold", "folds" 88 | elseif query.has_locals(lang) then 89 | return "@scope", "locals" 90 | end 91 | end) 92 | 93 | -- start..stop is an inclusive range 94 | 95 | ---@type table 96 | local start_counts = {} 97 | ---@type table 98 | local stop_counts = {} 99 | 100 | local prev_start = -1 101 | local prev_stop = -1 102 | 103 | local min_fold_lines = api.nvim_get_option_value("foldminlines", { 104 | win = 0, 105 | }) 106 | 107 | for _, match in ipairs(matches) do 108 | local start, stop, stop_col ---@type integer, integer, integer 109 | if match.metadata and match.metadata.range then 110 | start, _, stop, stop_col = unpack(match.metadata.range) ---@type integer, integer, integer, integer 111 | else 112 | start, _, stop, stop_col = match.node:range() ---@type integer, integer, integer, integer 113 | end 114 | 115 | -- Show the last line of the fold (foldtext already shows the first line) 116 | stop = math.max(start, stop - 1) 117 | 118 | if stop_col == 0 then 119 | stop = stop - 1 120 | end 121 | 122 | local fold_length = stop - start + 1 123 | local should_fold = fold_length > min_fold_lines 124 | 125 | -- Fold only multiline nodes that are not exactly the same as previously met folds 126 | -- Checking against just the previously found fold is sufficient if nodes 127 | -- are returned in preorder or postorder when traversing tree 128 | if should_fold and not (start == prev_start and stop == prev_stop) then 129 | start_counts[start] = (start_counts[start] or 0) + 1 130 | stop_counts[stop] = (stop_counts[stop] or 0) + 1 131 | prev_start = start 132 | prev_stop = stop 133 | end 134 | end 135 | 136 | ---@type string[] 137 | local levels = {} 138 | local current_level = 0 139 | 140 | -- We now have the list of fold opening and closing, fill the gaps and mark where fold start 141 | for lnum = 0, api.nvim_buf_line_count(bufnr) do 142 | local prefix = "" 143 | 144 | local last_trimmed_level = trim_level(current_level) 145 | current_level = current_level + (start_counts[lnum] or 0) 146 | local trimmed_level = trim_level(current_level) 147 | current_level = current_level - (stop_counts[lnum] or 0) 148 | local next_trimmed_level = trim_level(current_level) 149 | 150 | -- Determine if it's the start/end of a fold 151 | -- NB: vim's fold-expr interface does not have a mechanism to indicate that 152 | -- two (or more) folds start at this line, so it cannot distinguish between 153 | -- ( \n ( \n )) \n (( \n ) \n ) 154 | -- versus 155 | -- ( \n ( \n ) \n ( \n ) \n ) 156 | -- If it did have such a mechanism, (trimmed_level - last_trimmed_level) 157 | -- would be the correct number of starts to pass on. 158 | if trimmed_level - last_trimmed_level > 0 then 159 | prefix = ">" 160 | elseif trimmed_level - next_trimmed_level > 0 then 161 | -- Ending marks tend to confuse vim more than it helps, particularly when 162 | -- the fold level changes by at least 2; we can uncomment this if 163 | -- vim's behavior gets fixed. 164 | -- prefix = "<" 165 | prefix = "" 166 | end 167 | 168 | levels[lnum + 1] = prefix .. tostring(trimmed_level) 169 | end 170 | 171 | return levels 172 | end) 173 | end 174 | 175 | return function(lnum) 176 | if not tsutils then 177 | init() 178 | end 179 | 180 | lnum = lnum or vim.v.lnum 181 | 182 | if lnum == nil or not parsers.has_parser() then 183 | return "0" 184 | end 185 | 186 | local buf = api.nvim_get_current_buf() 187 | 188 | local levels = folds_levels(buf) 189 | 190 | if not levels then 191 | return "0" 192 | end 193 | 194 | return levels[lnum] or "0" 195 | end 196 | -------------------------------------------------------------------------------- /lua/willothy/ui/icons.lua: -------------------------------------------------------------------------------- 1 | local Icons = {} 2 | 3 | Icons.kinds = { 4 | Method = " ", 5 | Function = "󰡱 ", 6 | Constructor = " ", 7 | Field = " ", 8 | Variable = " ", 9 | Class = " ", 10 | Property = " ", 11 | Interface = " ", 12 | Enum = " ", 13 | EnumMember = " ", 14 | Reference = " ", 15 | Struct = " ", 16 | Event = " ", 17 | Constant = "󰏿 ", 18 | Keyword = " ", 19 | 20 | Module = "󰏗 ", 21 | Package = "󰏗 ", 22 | Namespace = "󰅩 ", 23 | 24 | Unit = " ", 25 | Value = "󰎠 ", 26 | String = " ", 27 | Number = "󰎠 ", 28 | Boolean = " ", 29 | Array = " ", 30 | Object = " ", 31 | Key = "󱕵 ", 32 | Null = " ", 33 | 34 | Text = " ", 35 | Snippet = " ", 36 | Color = "󰏘 ", 37 | File = "󰈮 ", 38 | Folder = "󰉋 ", 39 | Operator = " ", 40 | TypeParameter = " ", 41 | Copilot = " ", 42 | Cody = " ", 43 | Supermaven = " ", 44 | } 45 | 46 | Icons.diagnostics = { 47 | errors = "󰞏", -- 48 | warnings = "", -- "",-- 49 | hints = "󱐌", --"󰮔", -- 󱐌 50 | info = "", 51 | } 52 | Icons.diagnostics.Error = Icons.diagnostics.errors 53 | Icons.diagnostics.Warn = Icons.diagnostics.warnings 54 | Icons.diagnostics.Hint = Icons.diagnostics.hints 55 | Icons.diagnostics.Info = Icons.diagnostics.info 56 | 57 | Icons.lsp = { 58 | action_hint = "", 59 | } 60 | 61 | Icons.git = { 62 | diff = { 63 | added = "", 64 | modified = "󰆗", 65 | removed = "", 66 | }, 67 | signs = { 68 | bar = "┃", 69 | untracked = "•", 70 | }, 71 | branch = "", 72 | copilot = "", 73 | copilot_err = "", 74 | copilot_warn = "", 75 | } 76 | 77 | Icons.dap = { 78 | breakpoint = { 79 | conditional = "", 80 | data = "", 81 | func = "", 82 | log = "", 83 | unsupported = "", 84 | }, 85 | action = { 86 | continue = "", 87 | coverage = "", 88 | disconnect = "", 89 | line_by_line = "", 90 | pause = "", 91 | rerun = "", 92 | restart = "", 93 | restart_frame = "", 94 | reverse_continue = "", 95 | start = "", 96 | step_back = "", 97 | step_into = "", 98 | step_out = "", 99 | step_over = "", 100 | stop = "", 101 | }, 102 | stackframe = "", 103 | stackframe_active = "", 104 | console = "", 105 | } 106 | 107 | Icons.actions = { 108 | close_hexagon = "󰅜", 109 | close_round = "󰅙", 110 | close_outline = "󰅚", 111 | close = "󰅖", 112 | close_box = "󰅗", 113 | } 114 | 115 | Icons.menu = { 116 | actions = { 117 | outline = { 118 | left = "󰨂", 119 | right = "󰨃", 120 | up = "󰚷", 121 | down = "󰚶", 122 | swap = "󰩥", 123 | filter = "󱃦", 124 | }, 125 | filled = { 126 | up = "󰍠", 127 | down = "󰍝", 128 | left = "󰍞", 129 | right = "󰍟", 130 | swap = "󰩤", 131 | filter = "󱃥", 132 | }, 133 | }, 134 | hamburger = "󰍜", 135 | hamburger_open = "󰮫", 136 | } 137 | 138 | Icons.fold = { 139 | open = "", 140 | closed = "", 141 | } 142 | 143 | Icons.separators = { 144 | angle_quote = { 145 | left = "«", 146 | right = "»", 147 | }, 148 | chevron = { 149 | left = "", 150 | right = "", 151 | down = "", 152 | }, 153 | circle = { 154 | left = "", 155 | right = "", 156 | }, 157 | arrow = { 158 | left = "", 159 | right = "", 160 | }, 161 | slant = { 162 | left = "", 163 | right = "", 164 | }, 165 | bar = { 166 | left = "⎸", 167 | right = "⎹", 168 | }, 169 | } 170 | 171 | Icons.blocks = { 172 | left = { 173 | "▏", 174 | "▎", 175 | "▍", 176 | "▌", 177 | "▋", 178 | "▊", 179 | "▉", 180 | "█", 181 | }, 182 | right = { 183 | eighth = "▕", 184 | half = "▐", 185 | full = "█", 186 | }, 187 | } 188 | 189 | Icons.misc = { 190 | datetime = "󱛡 ", 191 | modified = "●", 192 | fold = "⮓", 193 | newline = "", 194 | circle = "", 195 | circle_filled = "", 196 | circle_slash = "", 197 | ellipse = "…", 198 | ellipse_dbl = "", 199 | kebab = "", 200 | tent = "⛺", 201 | comma = "󰸣", 202 | hook = "󰛢", 203 | hook_disabled = "󰛣", 204 | } 205 | 206 | return Icons 207 | -------------------------------------------------------------------------------- /lua/willothy/ui/mode.lua: -------------------------------------------------------------------------------- 1 | local api = vim.api 2 | 3 | local M = {} 4 | 5 | local mode_names = { 6 | ["n"] = "Normal", 7 | ["no"] = "Normal", 8 | ["i"] = "Insert", 9 | ["v"] = "Visual", 10 | ["vo"] = "Visual", 11 | ["V"] = "Visual", 12 | [""] = "Visual", 13 | ["s"] = "Select", 14 | ["S"] = "Select", 15 | ["R"] = "Replace", 16 | ["c"] = "Command", 17 | ["t"] = "Terminal", 18 | -- ["nt"] = "TerminalNormal", 19 | ["nt"] = "Normal", 20 | } 21 | 22 | -- stylua: ignore start 23 | local mode_short_names = { 24 | ['n'] = 'NO', 25 | ['no'] = 'OP', 26 | ['nov'] = 'OC', 27 | ['noV'] = 'OL', 28 | ['no\x16'] = 'OB', 29 | ['\x16'] = 'VB', 30 | ['niI'] = 'IN', 31 | ['niR'] = 'RE', 32 | ['niV'] = 'RV', 33 | ['nt'] = 'NT', 34 | ['ntT'] = 'TM', 35 | ['v'] = 'VI', 36 | ['vs'] = 'VI', 37 | ['V'] = 'VL', 38 | ['Vs'] = 'VL', 39 | ['\x16s'] = 'VB', 40 | ['s'] = 'SE', 41 | ['S'] = 'SL', 42 | ['\x13'] = 'SB', 43 | ['i'] = 'IN', 44 | ['ic'] = 'IC', 45 | ['ix'] = 'IX', 46 | ['R'] = 'RE', 47 | ['Rc'] = 'RC', 48 | ['Rx'] = 'RX', 49 | ['Rv'] = 'RV', 50 | ['Rvc'] = 'RC', 51 | ['Rvx'] = 'RX', 52 | ['c'] = 'CO', 53 | ['cv'] = 'CV', 54 | ['r'] = 'PR', 55 | ['rm'] = 'PM', 56 | ['r?'] = 'P?', 57 | ['!'] = 'SH', 58 | ['t'] = 'TE', 59 | } 60 | -- stylua: ignore end 61 | 62 | local cache = {} 63 | 64 | ---@param ignore_cache? boolean 65 | function M.get_color(ignore_cache) 66 | local mode = mode_names[api.nvim_get_mode().mode] or "Normal" 67 | local hl = mode .. "Mode" 68 | 69 | if cache[hl] and not ignore_cache then 70 | return cache[hl] 71 | end 72 | 73 | -- If the highlight group doesn't exist, return an empty table. 74 | -- We don't want to cache an empty table because it might be created later. 75 | if vim.fn.hlID(hl) == 0 then 76 | return {} 77 | end 78 | 79 | cache[hl] = vim.api.nvim_get_hl(0, { 80 | name = hl, 81 | link = false, 82 | create = false, 83 | }) 84 | 85 | return cache[hl] 86 | end 87 | 88 | function M.get_name() 89 | return mode_names[api.nvim_get_mode().mode] or "Normal" 90 | end 91 | 92 | function M.get_short_name() 93 | return mode_short_names[api.nvim_get_mode().mode] or "NO" 94 | end 95 | 96 | local function highlight() 97 | local turquoise = "#5de4c7" 98 | local pale_azure = "#89ddff" 99 | local lemon_chiffon = "#fffac2" 100 | local lavender_pink = "#fcc5e9" 101 | local peach = "#FAB387" 102 | 103 | vim.api.nvim_set_hl(0, "NormalMode", { fg = turquoise }) 104 | vim.api.nvim_set_hl(0, "InsertMode", { fg = pale_azure }) 105 | vim.api.nvim_set_hl(0, "VisualMode", { fg = lemon_chiffon }) 106 | vim.api.nvim_set_hl(0, "ReplaceMode", { fg = lavender_pink }) 107 | vim.api.nvim_set_hl(0, "TerminalMode", { fg = peach }) 108 | vim.api.nvim_set_hl(0, "CommandMode", { fg = peach }) 109 | 110 | vim.api.nvim_set_hl(0, "CursorLineNr", { link = "CurrentMode" }) 111 | end 112 | 113 | function M.setup() 114 | local group = vim.api.nvim_create_augroup("willothy/mode", { clear = true }) 115 | 116 | vim.schedule(function() 117 | vim.api.nvim_set_hl(0, "CurrentMode", M.get_color()) 118 | 119 | highlight() 120 | 121 | vim.api.nvim_create_autocmd({ "ModeChanged" }, { 122 | group = group, 123 | callback = function() 124 | vim.api.nvim_set_hl(0, "CurrentMode", M.get_color()) 125 | end, 126 | }) 127 | 128 | vim.api.nvim_create_autocmd("ColorSchemePre", { 129 | group = group, 130 | callback = function() 131 | cache = {} 132 | end, 133 | }) 134 | vim.api.nvim_create_autocmd("ColorScheme", { 135 | group = group, 136 | callback = function() 137 | highlight() 138 | end, 139 | }) 140 | end) 141 | end 142 | 143 | return M 144 | -------------------------------------------------------------------------------- /lua/willothy/ui/scrollbar/geometry.lua: -------------------------------------------------------------------------------- 1 | --- Helper for calculating placement of the scrollbar thumb and gutter 2 | 3 | --- @class willothy.ScrollbarGeometry 4 | --- @field width number 5 | --- @field height number 6 | --- @field row number 7 | --- @field col number 8 | --- @field zindex number 9 | --- @field relative string 10 | --- @field win number 11 | 12 | local M = {} 13 | 14 | --- @param target_win number 15 | --- @return number 16 | local function get_win_buf_height(target_win) 17 | local buf = vim.api.nvim_win_get_buf(target_win) 18 | 19 | -- not wrapping, so just get the line count 20 | if not vim.wo[target_win].wrap then 21 | return vim.api.nvim_buf_line_count(buf) 22 | end 23 | 24 | local width = vim.api.nvim_win_get_width(target_win) 25 | local lines = vim.api.nvim_buf_get_lines(buf, 0, -1, false) 26 | local height = 0 27 | for _, l in ipairs(lines) do 28 | height = height + math.max(1, (math.ceil(vim.fn.strwidth(l) / width))) 29 | end 30 | return height 31 | end 32 | 33 | --- @param border string|string[] 34 | --- @return number 35 | local function get_col_offset(border) 36 | -- we only need an extra offset when working with a padded window 37 | if 38 | type(border) == "table" 39 | and border[1] == " " 40 | and border[4] == " " 41 | and border[7] == " " 42 | and border[8] == " " 43 | then 44 | return 1 45 | end 46 | return 0 47 | end 48 | 49 | --- Gets the starting line, handling line wrapping if enabled 50 | --- @param target_win number 51 | --- @param width number 52 | --- @return number 53 | local get_content_start_line = function(target_win, width) 54 | local start_line = math.max(1, vim.fn.line("w0", target_win)) 55 | if not vim.wo[target_win].wrap then 56 | return start_line 57 | end 58 | 59 | local bufnr = vim.api.nvim_win_get_buf(target_win) 60 | local wrapped_start_line = 1 61 | for _, text in 62 | ipairs(vim.api.nvim_buf_get_lines(bufnr, 0, start_line - 1, false)) 63 | do 64 | -- nvim_buf_get_lines sometimes returns a blob. see hrsh7th/nvim-cmp#2050 65 | if vim.fn.type(text) == vim.v.t_blob then 66 | text = vim.fn.string(text) 67 | end 68 | wrapped_start_line = wrapped_start_line 69 | + math.max(1, math.ceil(vim.fn.strdisplaywidth(text) / width)) 70 | end 71 | return wrapped_start_line 72 | end 73 | 74 | --- @param target_win number 75 | --- @return { should_hide: boolean, thumb: willothy.ScrollbarGeometry, gutter: willothy.ScrollbarGeometry } 76 | function M.get_geometry(target_win) 77 | local config = vim.api.nvim_win_get_config(target_win) 78 | local width = config.width 79 | local height = assert(config.height) 80 | local zindex = config.zindex or 30 81 | 82 | local buf_height = get_win_buf_height(target_win) 83 | local thumb_height = 84 | math.max(1, math.floor(height * height / buf_height + 0.5) - 1) 85 | 86 | local start_line = get_content_start_line(target_win, width or 1) 87 | start_line = math.min(start_line, buf_height) 88 | 89 | local pct = (start_line - 1) / (buf_height - height) 90 | local thumb_offset = 91 | math.floor((pct * (math.max(height, thumb_height) - thumb_height)) + 0.5) 92 | thumb_offset = math.min(thumb_offset, height - thumb_height - 1) 93 | 94 | local common_geometry = { 95 | width = 1, 96 | row = thumb_offset, 97 | col = width + get_col_offset(config.border) - 1, 98 | relative = "win", 99 | win = target_win, 100 | } 101 | 102 | return { 103 | should_hide = height >= buf_height, 104 | thumb = vim.tbl_deep_extend( 105 | "force", 106 | common_geometry, 107 | { height = thumb_height, zindex = zindex + 2 } 108 | ), 109 | gutter = vim.tbl_deep_extend( 110 | "force", 111 | common_geometry, 112 | { row = 0, height = height, zindex = zindex + 1 } 113 | ), 114 | } 115 | end 116 | 117 | return M 118 | -------------------------------------------------------------------------------- /lua/willothy/ui/scrollbar/init.lua: -------------------------------------------------------------------------------- 1 | --- @class willothy.ScrollbarConfig 2 | --- @field enable_gutter boolean 3 | 4 | --- @class willothy.Scrollbar 5 | --- @field win willothy.ScrollbarWin 6 | --- 7 | --- @field new fun(opts: willothy.ScrollbarConfig): willothy.Scrollbar 8 | --- @field is_visible fun(self: willothy.Scrollbar): boolean 9 | --- @field update fun(self: willothy.Scrollbar, target_win: number | nil) 10 | local Scrollbar = {} 11 | 12 | function Scrollbar.new(opts) 13 | local self = setmetatable({}, { __index = Scrollbar }) 14 | self.win = require("willothy.ui.scrollbar.win").new(opts) 15 | return self 16 | end 17 | 18 | function Scrollbar:is_visible() 19 | return self.win:is_visible() 20 | end 21 | 22 | function Scrollbar:update(target_win) 23 | if target_win == nil or not vim.api.nvim_win_is_valid(target_win) then 24 | return self.win:hide() 25 | end 26 | 27 | local geometry = 28 | require("willothy.ui.scrollbar.geometry").get_geometry(target_win) 29 | if geometry.should_hide then 30 | return self.win:hide() 31 | end 32 | 33 | self.win:show_thumb(geometry.thumb) 34 | self.win:show_gutter(geometry.gutter) 35 | end 36 | 37 | local ScrollBarManager = { 38 | bars = {}, 39 | } 40 | 41 | function ScrollBarManager.update() 42 | local tabpage = vim.api.nvim_get_current_tabpage() 43 | 44 | vim 45 | .iter(vim.api.nvim_tabpage_list_wins(tabpage)) 46 | :filter(function(win) 47 | local buf = vim.api.nvim_win_get_buf(win) 48 | 49 | -- short-circuit here to avoid creating win config objects unnecessarily 50 | return vim.bo[buf].buftype == "" and vim.bo[buf].filetype ~= "noice" 51 | end) 52 | :filter(function(win) 53 | return vim.api.nvim_win_get_config(win).relative == "" 54 | end) 55 | :each(function(win) 56 | if not ScrollBarManager.bars[win] then 57 | local bar = Scrollbar.new({ 58 | enable_gutter = false, 59 | }) 60 | ScrollBarManager.bars[win] = bar 61 | end 62 | ScrollBarManager.bars[win]:update(win) 63 | end) 64 | 65 | vim 66 | .iter(pairs(ScrollBarManager.bars)) 67 | :filter(function(w) 68 | return vim.api.nvim_win_is_valid(w) == false 69 | or vim.api.nvim_win_get_tabpage(w) ~= tabpage 70 | end) 71 | :each(function(w, bar) 72 | ScrollBarManager.bars[w] = nil 73 | bar:update() 74 | end) 75 | end 76 | 77 | function ScrollBarManager.clear() 78 | vim.iter(ScrollBarManager.bars):each(function(w) 79 | ScrollBarManager.bars[w]:update() 80 | ScrollBarManager.bars[w] = nil 81 | end) 82 | end 83 | 84 | function ScrollBarManager.setup(opts) 85 | ScrollBarManager.options = opts or {} 86 | vim.api.nvim_create_autocmd({ 87 | "WinNew", 88 | "WinClosed", 89 | "CursorMoved", 90 | "CursorMovedI", 91 | "WinScrolled", 92 | "WinResized", 93 | "VimResized", 94 | }, { 95 | callback = vim.schedule_wrap(function() 96 | ScrollBarManager.update() 97 | end), 98 | }) 99 | 100 | vim.schedule(ScrollBarManager.update) 101 | end 102 | 103 | return ScrollBarManager 104 | -------------------------------------------------------------------------------- /lua/willothy/ui/scrollbar/win.lua: -------------------------------------------------------------------------------- 1 | --- Manages creating/updating scrollbar gutter and thumb windows 2 | 3 | --- @class willothy.ScrollbarWin 4 | --- @field enable_gutter boolean 5 | --- @field thumb_win? number 6 | --- @field gutter_win? number 7 | --- @field buf? number 8 | --- 9 | --- @field new fun(opts: willothy.ScrollbarConfig): willothy.ScrollbarWin 10 | --- @field is_visible fun(self: willothy.ScrollbarWin): boolean 11 | --- @field show_thumb fun(self: willothy.ScrollbarWin, geometry: willothy.ScrollbarGeometry) 12 | --- @field show_gutter fun(self: willothy.ScrollbarWin, geometry: willothy.ScrollbarGeometry) 13 | --- @field hide_thumb fun(self: willothy.ScrollbarWin) 14 | --- @field hide_gutter fun(self: willothy.ScrollbarWin) 15 | --- @field hide fun(self: willothy.ScrollbarWin) 16 | --- @field _make_win fun(self: willothy.ScrollbarWin, geometry: willothy.ScrollbarGeometry, hl_group: string): number 17 | --- @field redraw_if_needed fun(self: willothy.ScrollbarWin) 18 | local ScrollbarWindow = {} 19 | 20 | ScrollbarWindow.__index = ScrollbarWindow 21 | 22 | ---@param opts willothy.ScrollbarConfig 23 | ---@return willothy.ScrollbarWin 24 | function ScrollbarWindow.new(opts) 25 | return setmetatable(opts, ScrollbarWindow) --[[@as willothy.ScrollbarWin]] 26 | end 27 | 28 | function ScrollbarWindow:is_visible() 29 | return self.thumb_win ~= nil and vim.api.nvim_win_is_valid(self.thumb_win) 30 | end 31 | 32 | function ScrollbarWindow:show_thumb(geometry) 33 | -- create window if it doesn't exist 34 | if 35 | self.thumb_win == nil or not vim.api.nvim_win_is_valid(self.thumb_win) 36 | then 37 | self.thumb_win = self:_make_win(geometry, "PmenuThumb") 38 | else 39 | -- update with the geometry 40 | local thumb_existing_config = vim.api.nvim_win_get_config(self.thumb_win) 41 | local thumb_config = 42 | vim.tbl_deep_extend("force", thumb_existing_config, geometry) 43 | vim.api.nvim_win_set_config(self.thumb_win, thumb_config) 44 | end 45 | 46 | self:redraw_if_needed() 47 | end 48 | 49 | function ScrollbarWindow:show_gutter(geometry) 50 | if not self.enable_gutter then 51 | return 52 | end 53 | 54 | -- create window if it doesn't exist 55 | if 56 | self.gutter_win == nil or not vim.api.nvim_win_is_valid(self.gutter_win) 57 | then 58 | self.gutter_win = self:_make_win(geometry, "PmenuSbar") 59 | else 60 | -- update with the geometry 61 | local gutter_existing_config = vim.api.nvim_win_get_config(self.gutter_win) 62 | local gutter_config = 63 | vim.tbl_deep_extend("force", gutter_existing_config, geometry) 64 | vim.api.nvim_win_set_config(self.gutter_win, gutter_config) 65 | end 66 | 67 | self:redraw_if_needed() 68 | end 69 | 70 | function ScrollbarWindow:hide_thumb() 71 | if self.thumb_win and vim.api.nvim_win_is_valid(self.thumb_win) then 72 | vim.api.nvim_win_close(self.thumb_win, true) 73 | self.thumb_win = nil 74 | self:redraw_if_needed() 75 | end 76 | end 77 | 78 | function ScrollbarWindow:hide_gutter() 79 | if self.gutter_win and vim.api.nvim_win_is_valid(self.gutter_win) then 80 | vim.api.nvim_win_close(self.gutter_win, true) 81 | self.gutter_win = nil 82 | self:redraw_if_needed() 83 | end 84 | end 85 | 86 | function ScrollbarWindow:hide() 87 | self:hide_thumb() 88 | self:hide_gutter() 89 | end 90 | 91 | function ScrollbarWindow:_make_win(geometry, hl_group) 92 | if self.buf == nil or not vim.api.nvim_buf_is_valid(self.buf) then 93 | self.buf = vim.api.nvim_create_buf(false, true) 94 | end 95 | 96 | local win_config = vim.tbl_deep_extend("force", geometry, { 97 | style = "minimal", 98 | focusable = false, 99 | noautocmd = true, 100 | }) 101 | local win = vim.api.nvim_open_win( 102 | self.buf, 103 | false, 104 | win_config --[[@as vim.api.keyset.win_config]] 105 | ) 106 | vim.api.nvim_set_option_value( 107 | "winhighlight", 108 | "Normal:" .. hl_group .. ",EndOfBuffer:" .. hl_group, 109 | { win = win } 110 | ) 111 | return win 112 | end 113 | 114 | function ScrollbarWindow:redraw_if_needed() 115 | vim.schedule(function() 116 | if 117 | self.gutter_win ~= nil and vim.api.nvim_win_is_valid(self.gutter_win) 118 | then 119 | vim.api.nvim__redraw({ win = self.gutter_win, valid = true }) 120 | end 121 | if self.thumb_win ~= nil and vim.api.nvim_win_is_valid(self.thumb_win) then 122 | vim.api.nvim__redraw({ win = self.thumb_win, valid = true }) 123 | end 124 | end) 125 | end 126 | 127 | return ScrollbarWindow 128 | -------------------------------------------------------------------------------- /lua/willothy/ui/scrolleof.lua: -------------------------------------------------------------------------------- 1 | -- Allows scrolloff to be applied when scrolling past the end of a file. 2 | -- 3 | -- Hijacks wo:scrolloff, and sets it to min(scrolloff, winheight / 2) 4 | -- to ensure that scroll is always consistent between windows of different sizes. 5 | -- 6 | -- This will be updated with the window, and the original scrolloff value can be 7 | -- found in `vim.wo[winid].original_scrolloff`. 8 | 9 | local M = {} 10 | 11 | local disabled = false 12 | 13 | local disabled_ft = { 14 | terminal = true, 15 | } 16 | local last_win, last_line 17 | local function check_eof_scrolloff() 18 | if disabled then 19 | return 20 | end 21 | local filetype = vim.api.nvim_get_option_value("filetype", { 22 | scope = "local", 23 | }) 24 | local buftype = vim.api.nvim_get_option_value("buftype", { 25 | scope = "local", 26 | }) 27 | if disabled_ft[filetype] or buftype ~= "" then 28 | return 29 | end 30 | 31 | local win = vim.api.nvim_get_current_win() 32 | local cursor_line = vim.api.nvim_win_get_cursor(win)[1] 33 | 34 | -- Don't do anything if we've only moved horizontally 35 | if 36 | last_win ~= nil 37 | and last_line ~= nil 38 | and last_win == win 39 | and last_line == cursor_line 40 | then 41 | return 42 | end 43 | 44 | local view = vim.fn.winsaveview() 45 | if not view then 46 | return 47 | end 48 | 49 | last_win = win 50 | last_line = cursor_line 51 | 52 | local win_cur_line = vim.fn.winline() 53 | local win_height = vim.fn.winheight(0) 54 | local scrolloff 55 | if vim.w[0].original_scrolloff then 56 | scrolloff = vim.w[0].original_scrolloff 57 | else 58 | scrolloff = vim.wo[0].scrolloff 59 | end 60 | scrolloff = math.min(scrolloff, math.floor(win_height / 2)) 61 | if vim.wo[0].scrolloff ~= scrolloff then 62 | if vim.w[0].original_scrolloff == nil then 63 | vim.w[0].original_scrolloff = vim.wo[0].scrolloff 64 | end 65 | vim.wo[0].scrolloff = scrolloff 66 | end 67 | local visual_distance_to_eof = win_height - win_cur_line 68 | 69 | if visual_distance_to_eof < scrolloff then 70 | local goal = view.topline + scrolloff - visual_distance_to_eof 71 | view.topline = goal 72 | vim.fn.winrestview(view) 73 | elseif win_cur_line < scrolloff then 74 | local goal = view.topline - (scrolloff - win_cur_line) 75 | view.topline = goal 76 | vim.fn.winrestview(view) 77 | end 78 | end 79 | 80 | M.disable = function() 81 | disabled = true 82 | end 83 | 84 | M.enable = function() 85 | disabled = false 86 | end 87 | 88 | M.toggle = function() 89 | disabled = not disabled 90 | end 91 | 92 | M.setup = function() 93 | local scrollEOF_group = 94 | vim.api.nvim_create_augroup("ScrollEOF", { clear = true }) 95 | 96 | vim.api.nvim_create_autocmd({ "CursorMoved", "CursorMovedI" }, { 97 | group = scrollEOF_group, 98 | callback = function() 99 | check_eof_scrolloff() 100 | end, 101 | }) 102 | 103 | require("snacks").toggle 104 | .new({ 105 | name = "ScrollEOF", 106 | get = function() 107 | return not disabled 108 | end, 109 | set = function(enabled) 110 | disabled = not enabled 111 | check_eof_scrolloff() 112 | end, 113 | }) 114 | :map("uG") 115 | 116 | check_eof_scrolloff() 117 | end 118 | 119 | M.check = check_eof_scrolloff 120 | 121 | return M 122 | -------------------------------------------------------------------------------- /queries/css/highlights.scm: -------------------------------------------------------------------------------- 1 | [ 2 | "@media" 3 | "@charset" 4 | "@namespace" 5 | "@supports" 6 | "@keyframes" 7 | (at_keyword) 8 | ] @keyword.directive 9 | 10 | "@import" @keyword.import 11 | 12 | [ 13 | (to) 14 | (from) 15 | ] @keyword 16 | 17 | (comment) @comment @spell 18 | 19 | (tag_name) @tag 20 | 21 | (class_name) @type 22 | 23 | (id_name) @constant 24 | 25 | [ 26 | (property_name) 27 | (feature_name) 28 | ] @property 29 | 30 | [ 31 | (nesting_selector) 32 | (universal_selector) 33 | ] @character.special 34 | 35 | (function_name) @function 36 | 37 | [ 38 | "~" 39 | ">" 40 | "+" 41 | "-" 42 | "*" 43 | "/" 44 | "=" 45 | "^=" 46 | "|=" 47 | "~=" 48 | "$=" 49 | "*=" 50 | ] @operator 51 | 52 | [ 53 | "and" 54 | "or" 55 | "not" 56 | "only" 57 | ] @keyword.operator 58 | 59 | (important) @keyword.modifier 60 | 61 | (attribute_selector 62 | (plain_value) @string) 63 | 64 | (pseudo_element_selector 65 | "::" 66 | (tag_name) @attribute) 67 | 68 | (pseudo_class_selector 69 | (class_name) @attribute) 70 | 71 | (attribute_name) @tag.attribute 72 | 73 | (namespace_name) @module 74 | 75 | ((property_name) @variable 76 | (#lua-match? @variable "^[-][-]")) 77 | 78 | ((plain_value) @variable 79 | (#lua-match? @variable "^[-][-]")) 80 | 81 | [ 82 | (string_value) 83 | (color_value) 84 | (unit) 85 | ] @string 86 | 87 | (integer_value) @number 88 | 89 | (float_value) @number.float 90 | 91 | [ 92 | "#" 93 | "," 94 | "." 95 | ":" 96 | "::" 97 | ";" 98 | ] @punctuation.delimiter 99 | 100 | [ 101 | "{" 102 | ")" 103 | "(" 104 | "}" 105 | "[" 106 | "]" 107 | ] @punctuation.bracket 108 | -------------------------------------------------------------------------------- /queries/go/folds.scm: -------------------------------------------------------------------------------- 1 | [ 2 | (const_declaration) 3 | (expression_switch_statement) 4 | (expression_case) 5 | (default_case) 6 | (type_switch_statement) 7 | (type_case) 8 | (for_statement) 9 | (func_literal) 10 | (function_declaration) 11 | (if_statement) 12 | (import_declaration) 13 | (method_declaration) 14 | (type_declaration) 15 | (var_declaration) 16 | (composite_literal) 17 | (literal_element) 18 | (block) 19 | ] @fold 20 | -------------------------------------------------------------------------------- /queries/go/highlights.scm: -------------------------------------------------------------------------------- 1 | ; Forked from tree-sitter-go 2 | ; Copyright (c) 2014 Max Brunsfeld (The MIT License) 3 | ; 4 | ; Identifiers 5 | (type_identifier) @type 6 | 7 | (type_spec 8 | name: (type_identifier) @type.definition) 9 | 10 | (field_identifier) @property 11 | 12 | (identifier) @variable 13 | 14 | (package_identifier) @module 15 | 16 | (parameter_declaration 17 | (identifier) @variable.parameter) 18 | 19 | (variadic_parameter_declaration 20 | (identifier) @variable.parameter) 21 | 22 | (label_name) @label 23 | 24 | (const_spec 25 | name: (identifier) @constant) 26 | 27 | ; Function calls 28 | (call_expression 29 | function: (identifier) @function.call) 30 | 31 | (call_expression 32 | function: (selector_expression 33 | field: (field_identifier) @function.method.call)) 34 | 35 | ; Function definitions 36 | (function_declaration 37 | name: (identifier) @function) 38 | 39 | (method_declaration 40 | name: (field_identifier) @function.method) 41 | 42 | (method_elem 43 | name: (field_identifier) @function.method) 44 | 45 | ; Constructors 46 | ((call_expression 47 | (identifier) @constructor) 48 | (#lua-match? @constructor "^[nN]ew.+$")) 49 | 50 | ((call_expression 51 | (identifier) @constructor) 52 | (#lua-match? @constructor "^[mM]ake.+$")) 53 | 54 | ; Operators 55 | [ 56 | "--" 57 | "-" 58 | "-=" 59 | ":=" 60 | "!" 61 | "!=" 62 | "..." 63 | "*" 64 | "*" 65 | "*=" 66 | "/" 67 | "/=" 68 | "&" 69 | "&&" 70 | "&=" 71 | "&^" 72 | "&^=" 73 | "%" 74 | "%=" 75 | "^" 76 | "^=" 77 | "+" 78 | "++" 79 | "+=" 80 | "<-" 81 | "<" 82 | "<<" 83 | "<<=" 84 | "<=" 85 | "=" 86 | "==" 87 | ">" 88 | ">=" 89 | ">>" 90 | ">>=" 91 | "|" 92 | "|=" 93 | "||" 94 | "~" 95 | ] @operator 96 | 97 | ; Keywords 98 | [ 99 | "break" 100 | "const" 101 | "continue" 102 | "default" 103 | "defer" 104 | "goto" 105 | "range" 106 | "select" 107 | "var" 108 | "fallthrough" 109 | ] @keyword 110 | 111 | [ 112 | "type" 113 | "struct" 114 | "interface" 115 | ] @keyword.type 116 | 117 | "func" @keyword.function 118 | 119 | "return" @keyword.return 120 | 121 | "go" @keyword.coroutine 122 | 123 | "for" @keyword.repeat 124 | 125 | [ 126 | "import" 127 | "package" 128 | ] @keyword.import 129 | 130 | [ 131 | "else" 132 | "case" 133 | "switch" 134 | "if" 135 | ] @keyword.conditional 136 | 137 | ; Builtin types 138 | [ 139 | "chan" 140 | "map" 141 | ] @type.builtin 142 | 143 | ((type_identifier) @type.builtin 144 | (#any-of? @type.builtin 145 | "any" "bool" "byte" "comparable" "complex128" "complex64" "error" "float32" "float64" "int" 146 | "int16" "int32" "int64" "int8" "rune" "string" "uint" "uint16" "uint32" "uint64" "uint8" 147 | "uintptr")) 148 | 149 | ; Builtin functions 150 | ((identifier) @function.builtin 151 | (#any-of? @function.builtin 152 | "append" "cap" "clear" "close" "complex" "copy" "delete" "imag" "len" "make" "max" "min" "new" 153 | "panic" "print" "println" "real" "recover")) 154 | 155 | ; Delimiters 156 | "." @punctuation.delimiter 157 | 158 | "," @punctuation.delimiter 159 | 160 | ":" @punctuation.delimiter 161 | 162 | ";" @punctuation.delimiter 163 | 164 | "(" @punctuation.bracket 165 | 166 | ")" @punctuation.bracket 167 | 168 | "{" @punctuation.bracket 169 | 170 | "}" @punctuation.bracket 171 | 172 | "[" @punctuation.bracket 173 | 174 | "]" @punctuation.bracket 175 | 176 | ; Literals 177 | (interpreted_string_literal) @string 178 | 179 | (raw_string_literal) @string 180 | 181 | (rune_literal) @string 182 | 183 | (escape_sequence) @string.escape 184 | 185 | (int_literal) @number 186 | 187 | (float_literal) @number.float 188 | 189 | (imaginary_literal) @number 190 | 191 | [ 192 | (true) 193 | (false) 194 | ] @boolean 195 | 196 | [ 197 | (nil) 198 | (iota) 199 | ] @constant.builtin 200 | 201 | (keyed_element 202 | . 203 | (literal_element 204 | (identifier) @variable.member)) 205 | 206 | (field_declaration 207 | name: (field_identifier) @variable.member) 208 | 209 | ; Comments 210 | (comment) @comment @spell 211 | 212 | ; Doc Comments 213 | (source_file 214 | . 215 | (comment)+ @comment.documentation) 216 | 217 | (source_file 218 | (comment)+ @comment.documentation 219 | . 220 | (const_declaration)) 221 | 222 | (source_file 223 | (comment)+ @comment.documentation 224 | . 225 | (function_declaration)) 226 | 227 | (source_file 228 | (comment)+ @comment.documentation 229 | . 230 | (type_declaration)) 231 | 232 | (source_file 233 | (comment)+ @comment.documentation 234 | . 235 | (var_declaration)) 236 | 237 | ; Spell 238 | ((interpreted_string_literal) @spell 239 | (#not-has-parent? @spell import_spec)) 240 | -------------------------------------------------------------------------------- /queries/go/indents.scm: -------------------------------------------------------------------------------- 1 | [ 2 | (import_declaration) 3 | (const_declaration) 4 | (var_declaration) 5 | (type_declaration) 6 | (func_literal) 7 | (literal_value) 8 | (expression_case) 9 | (communication_case) 10 | (type_case) 11 | (default_case) 12 | (block) 13 | (call_expression) 14 | (parameter_list) 15 | (field_declaration_list) 16 | (interface_type) 17 | ] @indent.begin 18 | 19 | (literal_value 20 | "}" @indent.branch) 21 | 22 | (block 23 | "}" @indent.branch) 24 | 25 | (field_declaration_list 26 | "}" @indent.branch) 27 | 28 | (interface_type 29 | "}" @indent.branch) 30 | 31 | (const_declaration 32 | ")" @indent.branch) 33 | 34 | (import_spec_list 35 | ")" @indent.branch) 36 | 37 | (var_spec_list 38 | ")" @indent.branch) 39 | 40 | [ 41 | "}" 42 | ")" 43 | ] @indent.end 44 | 45 | (parameter_list 46 | ")" @indent.branch) 47 | 48 | (comment) @indent.ignore 49 | -------------------------------------------------------------------------------- /queries/go/injections.scm: -------------------------------------------------------------------------------- 1 | ((comment) @injection.content 2 | (#set! injection.language "comment")) 3 | 4 | (call_expression 5 | (selector_expression) @_function 6 | (#any-of? @_function 7 | "regexp.Match" "regexp.MatchReader" "regexp.MatchString" "regexp.Compile" "regexp.CompilePOSIX" 8 | "regexp.MustCompile" "regexp.MustCompilePOSIX") 9 | (argument_list 10 | . 11 | [ 12 | (raw_string_literal) 13 | (interpreted_string_literal) 14 | ] @injection.content 15 | (#offset! @injection.content 0 1 0 -1) 16 | (#set! injection.language "regex"))) 17 | 18 | ((comment) @injection.content 19 | (#match? @injection.content "/\\*!([a-zA-Z]+:)?re2c") 20 | (#set! injection.language "re2c")) 21 | 22 | ((call_expression 23 | function: (selector_expression 24 | field: (field_identifier) @_method) 25 | arguments: (argument_list 26 | . 27 | (interpreted_string_literal) @injection.content)) 28 | (#any-of? @_method "Printf" "Sprintf" "Fatalf" "Scanf" "Errorf" "Skipf" "Logf") 29 | (#set! injection.language "printf")) 30 | 31 | ((call_expression 32 | function: (selector_expression 33 | field: (field_identifier) @_method) 34 | arguments: (argument_list 35 | (_) 36 | . 37 | (interpreted_string_literal) @injection.content)) 38 | (#any-of? @_method "Fprintf" "Fscanf" "Appendf" "Sscanf") 39 | (#set! injection.language "printf")) 40 | -------------------------------------------------------------------------------- /queries/go/locals.scm: -------------------------------------------------------------------------------- 1 | ((function_declaration 2 | name: (identifier) @local.definition.function) ; @function 3 | ) 4 | 5 | ((method_declaration 6 | name: (field_identifier) @local.definition.method) ; @function.method 7 | ) 8 | 9 | (short_var_declaration 10 | left: (expression_list 11 | (identifier) @local.definition.var)) 12 | 13 | (var_spec 14 | name: (identifier) @local.definition.var) 15 | 16 | (parameter_declaration 17 | (identifier) @local.definition.var) 18 | 19 | (variadic_parameter_declaration 20 | (identifier) @local.definition.var) 21 | 22 | (for_statement 23 | (range_clause 24 | left: (expression_list 25 | (identifier) @local.definition.var))) 26 | 27 | (const_declaration 28 | (const_spec 29 | name: (identifier) @local.definition.var)) 30 | 31 | (type_declaration 32 | (type_spec 33 | name: (type_identifier) @local.definition.type)) 34 | 35 | ; reference 36 | (identifier) @local.reference 37 | 38 | (type_identifier) @local.reference 39 | 40 | (field_identifier) @local.reference 41 | 42 | ((package_identifier) @local.reference 43 | (#set! reference.kind "namespace")) 44 | 45 | (package_clause 46 | (package_identifier) @local.definition.namespace) 47 | 48 | (import_spec_list 49 | (import_spec 50 | name: (package_identifier) @local.definition.namespace)) 51 | 52 | ; Call references 53 | ((call_expression 54 | function: (identifier) @local.reference) 55 | (#set! reference.kind "call")) 56 | 57 | ((call_expression 58 | function: (selector_expression 59 | field: (field_identifier) @local.reference)) 60 | (#set! reference.kind "call")) 61 | 62 | ((call_expression 63 | function: (parenthesized_expression 64 | (identifier) @local.reference)) 65 | (#set! reference.kind "call")) 66 | 67 | ((call_expression 68 | function: (parenthesized_expression 69 | (selector_expression 70 | field: (field_identifier) @local.reference))) 71 | (#set! reference.kind "call")) 72 | 73 | ; Scopes 74 | (func_literal) @local.scope 75 | 76 | (source_file) @local.scope 77 | 78 | (function_declaration) @local.scope 79 | 80 | (if_statement) @local.scope 81 | 82 | (block) @local.scope 83 | 84 | (expression_switch_statement) @local.scope 85 | 86 | (for_statement) @local.scope 87 | 88 | (method_declaration) @local.scope 89 | -------------------------------------------------------------------------------- /queries/lua/injections.scm: -------------------------------------------------------------------------------- 1 | ((function_call 2 | name: (_) @_vimcmd_identifier 3 | arguments: 4 | (arguments 5 | (string 6 | content: _ @injection.content) 7 | ) 8 | ) 9 | (#set! injection.language "graphql") 10 | (#any-of? @_vimcmd_identifier "gql")) 11 | 12 | -------------------------------------------------------------------------------- /queries/rust/injections.scm: -------------------------------------------------------------------------------- 1 | (call_expression 2 | (scoped_identifier 3 | path: (identifier) @path (#eq? @path "sqlx") 4 | name: (identifier) @name (#any-of? @name "query" "query_as" "query_scalar")) 5 | 6 | (arguments 7 | (string_literal 8 | (string_content) @injection.content)) 9 | 10 | (#set! injection.language "sql") 11 | ) 12 | 13 | 14 | (call_expression 15 | (generic_function 16 | (scoped_identifier 17 | path: (identifier) @path (#eq? @path "sqlx") 18 | name: (identifier) @name (#any-of? @name "query" "query_as" "query_scalar"))) 19 | 20 | (arguments 21 | (string_literal 22 | (string_content) @injection.content)) 23 | 24 | (#set! injection.language "sql") 25 | ) 26 | -------------------------------------------------------------------------------- /stylua.toml: -------------------------------------------------------------------------------- 1 | column_width = 79 2 | line_endings = "Unix" 3 | indent_type = "Spaces" 4 | indent_width = 2 5 | quote_style = "AutoPreferDouble" 6 | call_parentheses = "Always" 7 | collapse_simple_statement = "Never" 8 | 9 | [sort_requires] 10 | enabled = true 11 | --------------------------------------------------------------------------------