├── .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 |
--------------------------------------------------------------------------------