├── .editorconfig ├── .luarc.jsonc ├── .stylua.toml ├── README.md ├── after ├── ftplugin │ ├── c.lua │ ├── config.lua │ ├── cpp.lua │ ├── gitconfig.lua │ ├── go.lua │ ├── java.lua │ ├── javascript.lua │ ├── json.lua │ ├── jsonc.lua │ ├── lua.lua │ ├── markdown.lua │ ├── python.lua │ ├── qf.lua │ ├── rust.lua │ ├── solidity.lua │ ├── text.lua │ └── vim.lua └── lsp │ ├── ccls.lua │ ├── lua_ls.lua │ └── rust_analyzer.lua ├── css ├── highlight-gh-dark.css └── previm-gh-dark.css ├── init.lua ├── lazy-lock.json ├── lua ├── autocmd.lua ├── keymaps.lua ├── lazyplug.lua ├── lib │ └── jsonc.lua ├── lsp │ ├── diag.lua │ ├── icons.lua │ └── keymaps.lua ├── options.lua ├── plugins │ ├── blink.lua │ ├── cmp.lua │ ├── conform.lua │ ├── dap │ │ ├── cpp_rust.lua │ │ ├── go.lua │ │ ├── init.lua │ │ ├── lua.lua │ │ ├── python.lua │ │ └── ui.lua │ ├── devicons │ │ ├── init.lua │ │ └── setup.lua │ ├── diffview.lua │ ├── fugitive.lua │ ├── fzf-lua │ │ ├── cmds.lua │ │ ├── init.lua │ │ ├── mappings.lua │ │ └── setup.lua │ ├── gitsigns.lua │ ├── init.lua │ ├── lsp.lua │ ├── mini │ │ ├── indentscope.lua │ │ ├── init.lua │ │ ├── statusline.lua │ │ └── surround.lua │ ├── oil.lua │ ├── snacks │ │ ├── init.lua │ │ └── mappings.lua │ ├── treesitter.lua │ ├── ts-vimdoc.lua │ └── which_key.lua ├── term.lua └── utils.lua └── screenshot.png /.editorconfig: -------------------------------------------------------------------------------- 1 | # https://github.com/CppCXY/EmmyLuaCodeStyle/blob/master/lua.template.editorconfig 2 | # see https://github.com/CppCXY/EmmyLuaCodeStyle 3 | [*.lua] 4 | # [basic] 5 | 6 | # optional space/tab 7 | indent_style = space 8 | # if indent_style is space, this is valid 9 | indent_size = 2 10 | # if indent_style is tab, this is valid 11 | tab_width = 2 12 | # none/single/double 13 | quote_style = double 14 | 15 | continuation_indent = 4 16 | 17 | # this mean utf8 length , if this is 'unset' then the line width is no longer checked 18 | # this option decides when to chopdown the code 19 | max_line_length = 100 20 | 21 | # optional crlf/lf/cr/auto, if it is 'auto', in windows it is crlf other platforms are lf 22 | # in neovim the value 'auto' is not a valid option, please use 'unset' 23 | end_of_line = unset 24 | 25 | #optional keep/never/always/smart 26 | trailing_table_separator = keep 27 | 28 | # keep/remove/remove_table_only/remove_string_only 29 | call_arg_parentheses = keep 30 | 31 | detect_end_of_line = false 32 | 33 | # this will check text end with new line 34 | insert_final_newline = true 35 | 36 | # [space] 37 | space_around_table_field_list = true 38 | 39 | space_before_attribute = true 40 | 41 | space_before_function_open_parenthesis = false 42 | 43 | space_before_function_call_open_parenthesis = false 44 | 45 | space_before_closure_open_parenthesis = false 46 | 47 | # optional always/only_string/only_table/none 48 | # or true/false 49 | space_before_function_call_single_arg = true 50 | 51 | space_before_open_square_bracket = false 52 | 53 | space_inside_function_call_parentheses = false 54 | 55 | space_inside_function_param_list_parentheses = false 56 | 57 | space_inside_square_brackets = false 58 | 59 | # like t[#t+1] = 1 60 | space_around_table_append_operator = false 61 | 62 | ignore_spaces_inside_function_call = false 63 | 64 | space_before_inline_comment = 1 65 | 66 | # [operator space] 67 | space_around_math_operator = true 68 | 69 | space_after_comma = true 70 | 71 | space_after_comma_in_for_statement = true 72 | 73 | space_around_concat_operator = true 74 | 75 | # [align] 76 | 77 | align_call_args = false 78 | 79 | align_function_params = true 80 | 81 | align_continuous_inline_comment = true 82 | 83 | align_continuous_assign_statement = true 84 | 85 | align_continuous_rect_table_field = true 86 | 87 | align_if_branch = false 88 | 89 | align_array_table = true 90 | 91 | # [indent] 92 | 93 | never_indent_before_if_condition = false 94 | 95 | never_indent_comment_on_if_branch = false 96 | 97 | # [line space] 98 | 99 | # The following configuration supports four expressions 100 | # keep 101 | # fixed(n) 102 | # min(n) 103 | # max(n) 104 | # for eg. min(2) 105 | 106 | line_space_after_if_statement = keep 107 | 108 | line_space_after_do_statement = keep 109 | 110 | line_space_after_while_statement = keep 111 | 112 | line_space_after_repeat_statement = keep 113 | 114 | line_space_after_for_statement = keep 115 | 116 | line_space_after_local_or_assign_statement = keep 117 | 118 | line_space_after_function_statement = fixed(2) 119 | 120 | line_space_after_expression_statement = keep 121 | 122 | line_space_after_comment = keep 123 | 124 | # [line break] 125 | break_all_list_when_line_exceed = false 126 | 127 | auto_collapse_lines = false 128 | 129 | # [preference] 130 | ignore_space_after_colon = false 131 | 132 | remove_call_expression_list_finish_comma = false 133 | -------------------------------------------------------------------------------- /.luarc.jsonc: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/LuaLS/vscode-lua/master/setting/schema.json", 3 | "runtime.version": "LuaJIT", 4 | "diagnostics": { 5 | "enable": true, 6 | "globals": [ 7 | "vim", 8 | "describe", 9 | "pending", 10 | "it", 11 | "before_each", 12 | "after_each", 13 | ], 14 | "neededFileStatus": { 15 | "codestyle-check": "Any", 16 | }, 17 | }, 18 | "workspace": { 19 | "library": [ 20 | "lua", 21 | "$VIMRUNTIME/lua", 22 | "${3rd}/luv/library", 23 | "$HOME/Sources/nvim/fzf-lua/lua", 24 | "$HOME/Sources/nvim/smartyank.nvim/lua", 25 | "$HOME/Sources/nvim/ts-vimdoc.nvim/lua/", 26 | "$XDG_DATA_HOME/nvim/lazy/fzf-lua/lua", 27 | "$XDG_DATA_HOME/nvim/lazy/smartyank.nvim/lua", 28 | "$XDG_DATA_HOME/nvim/lazy/ts-vimdoc.nvim/lua", 29 | "$XDG_DATA_HOME/nvim/lazy/mini.nvim/lua", 30 | "$XDG_DATA_HOME/nvim/lazy/nvim-web-devicons/lua", 31 | "$XDG_DATA_HOME/nvim/lazy/telescope.nvim/lua", 32 | ], 33 | "checkThirdParty": false, 34 | "maxPreload": 2000, 35 | "preloadFileSize": 1000, 36 | }, 37 | "type": { 38 | "weakNilCheck": true, 39 | "weakUnionCheck": true, 40 | "castNumberToInteger": true, 41 | }, 42 | "telemetry.enable": false, 43 | } 44 | -------------------------------------------------------------------------------- /.stylua.toml: -------------------------------------------------------------------------------- 1 | column_width = 100 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | ![Neovim version](https://img.shields.io/badge/Neovim-0.11-57A143?style=flat-square&logo=neovim) 4 | 5 |
6 | 7 | ![screenshot](https://github.com/ibhagwan/nvim-lua/raw/main/screenshot.png) 8 | 9 | ## What's in this repo? 10 | 11 | **My personal neovim lua config (requires neovim >= `0.11`)** 12 | 13 | - Minimum changes to default key mapping 14 | - A good selection of carefully hand-picked plugins 15 | - Lazy load plugins where possible 16 | - Which-key to rule them all 17 | - Misc utilities and goodies 18 | 19 | ## Plugins & Packages 20 | 21 | - [lazy.nvim](https://github.com/folke/lazy.nvim): lua plugin 22 | manager to auto-install and update our plugins 23 | 24 | - [mason.nvim](https://github.com/williamboman/mason.nvim): 25 | automatic installation of LSP servers using the `:Mason` command 26 | 27 | ### Basics 28 | 29 | - [smartyank.nvim](https://github.com/ibhagwan/smartyank.nvim): only pollute 30 | the clipboard when you really mean it (written by me)! 31 | 32 | - [mini.surround](https://github.com/echasnovski/mini.nvim): adds the missing 33 | operators (`ds`, `cs`, `ys`) for dealing with pairs of "surroundings" 34 | (quotes, tags, etc) 35 | 36 | ### Git 37 | 38 | - [vim-fugitive](https://github.com/tpope/vim-fugitive): git porcelain and 39 | plumbing in one by tpope, the Swiss army knife of git 40 | 41 | - [gitsigns](https://github.com/lewis6991/gitsigns.nvim): git gutter indicators 42 | and hunk management 43 | 44 | - [diffview.nvim](https://github.com/sindrets/diffview.nvim): powerful git diff/merge 45 | tool (`gd|yd` to invoke for project|dotfiles) 46 | 47 | ### Coding, completion & LSP 48 | 49 | - [blink.cmp](https://github.com/Saghen/blink.cmp): autocompletion framework 50 | 51 | - [treesitter](https://github.com/nvim-treesitter/nvim-treesitter): text 52 | parsing library, provides better syntax highlighting and text-objects for 53 | different coding languages (e.g. `yaf` yank-a-function), see 54 | [treesitter.lua](https://github.com/ibhagwan/nvim-lua/blob/main/lua/plugins/treesitter.lua) 55 | for defined text objects 56 | 57 | - [conform.nvim](https://github.com/stevearc/conform.nvim): formatter 58 | plugin where LSP formatting isn't what you need, format `js|json|html` 59 | with `prettier` or lua with `stylua` using the `gQ` mapping. 60 | 61 | - [nvim-dap](https://github.com/mfussenegger/nvim-dap): 62 | set breakpoints and debug applications using Debug Adapter Protocol (DAP) 63 | 64 | - [fidget.nvim](https://github.com/j-hui/fidget.nvim): Eye candy LSP progress 65 | indicator above the status line (top right) 66 | 67 | ### Fuzzy search & file exploration 68 | 69 | - [fzf-lua](https://github.com/ibhagwan/fzf-lua): the original, tried and 70 | tested fuzzy finder, lua plugin that does pretty much everything, written by 71 | yours truly 72 | 73 | - [snacks.nvim](https://github.com/folke/snacks.nvim): amazing set of plugins 74 | from the great @folke, image previews using the kitty protocol, picker to rival 75 | no less than our own fzf-lua :-) 76 | 77 | - [oil.nvim](https://github.com/stevearc/oil.nvim): file explorer as a neovim 78 | buffer which also replaces netrw 79 | 80 | ### Misc 81 | 82 | - [mini.nvim](https://github.com/echasnovski/mini.nvim): a must have plugin from 83 | the great @echasnovski, used for statusline, indent lines, surround and much more 84 | 85 | - [which-key](https://github.com/folke/which-key.nvim): a must plugin in every 86 | setup, when leader key (and some built-ins) sequence is pressed and times out 87 | which-key will generate a help window with your keybinds and also let you 88 | continue the sequence at your own pace 89 | 90 | - [previm](https://github.com/previm/previm): live preview markdown files in 91 | the browser with `r` 92 | 93 | - [render-markdown.nvim](https://github.com/MeanderingProgrammer/render-markdown.nvim): 94 | magical plugin rendering markdown files inside neovim 95 | 96 | -------------------------------------------------------------------------------- /after/ftplugin/c.lua: -------------------------------------------------------------------------------- 1 | vim.bo.tabstop = 4 2 | vim.cmd [[setlocal path+=/usr/include/**,/usr/local/include/**]] 3 | -- remove {o|O} newline auto-comments 4 | vim.opt_local.formatoptions:remove("o") 5 | -------------------------------------------------------------------------------- /after/ftplugin/config.lua: -------------------------------------------------------------------------------- 1 | vim.bo.tabstop = 4 2 | vim.bo.textwidth = 0 3 | vim.bo.wrapmargin = 0 4 | -------------------------------------------------------------------------------- /after/ftplugin/cpp.lua: -------------------------------------------------------------------------------- 1 | vim.bo.tabstop = 4 2 | vim.cmd [[setlocal path+=/usr/include/**,/usr/local/include/**]] 3 | -- remove {o|O} newline auto-comments 4 | vim.opt_local.formatoptions:remove("o") 5 | -------------------------------------------------------------------------------- /after/ftplugin/gitconfig.lua: -------------------------------------------------------------------------------- 1 | vim.bo.tabstop = 2 2 | -------------------------------------------------------------------------------- /after/ftplugin/go.lua: -------------------------------------------------------------------------------- 1 | vim.bo.tabstop = 4 2 | vim.bo.expandtab = true 3 | vim.bo.copyindent = true 4 | vim.bo.preserveindent = true 5 | -- remove {o|O} newline auto-comments 6 | vim.opt_local.formatoptions:remove("o") 7 | -------------------------------------------------------------------------------- /after/ftplugin/java.lua: -------------------------------------------------------------------------------- 1 | vim.bo.tabstop = 4 2 | 3 | if not pcall(require, "jdtls") then 4 | return 5 | end 6 | 7 | local root_dir = require("jdtls.setup").find_root({ ".git", "mvnw", "gradlew" }) 8 | 9 | local config = { 10 | -- https://github.com/eclipse/eclipse.jdt.ls#running-from-the-command-line 11 | cmd = { 12 | "java", 13 | "-Declipse.application=org.eclipse.jdt.ls.core.id1", 14 | "-Dosgi.bundles.defaultStartLevel=4", 15 | "-Declipse.product=org.eclipse.jdt.ls.core.product", 16 | "-Dlog.protocol=true", 17 | "-Dlog.level=ALL", 18 | "-Xms1g", 19 | "--add-modules=ALL-SYSTEM", 20 | "--add-opens", "java.base/java.util=ALL-UNNAMED", 21 | "--add-opens", "java.base/java.lang=ALL-UNNAMED", 22 | "-jar", vim.fn.glob(vim.fn.stdpath("data") .. 23 | "/mason/packages/jdtls/plugins/org.eclipse.equinox.launcher_*.jar"), 24 | "-configuration", vim.fn.stdpath("data") .. "/mason/packages/jdtls/config_linux", 25 | "-data", (root_dir or vim.uv.cwd()) .. "/.jdtls", 26 | }, 27 | root_dir = root_dir 28 | } 29 | 30 | -- `:help vim.lsp.start_client` 31 | require("jdtls").start_or_attach(config) 32 | -------------------------------------------------------------------------------- /after/ftplugin/javascript.lua: -------------------------------------------------------------------------------- 1 | vim.bo.tabstop = 2 2 | -- remove {o|O} newline auto-comments 3 | vim.opt_local.formatoptions:remove("o") 4 | -------------------------------------------------------------------------------- /after/ftplugin/json.lua: -------------------------------------------------------------------------------- 1 | vim.bo.tabstop = 2 2 | -------------------------------------------------------------------------------- /after/ftplugin/jsonc.lua: -------------------------------------------------------------------------------- 1 | vim.bo.tabstop = 2 2 | -------------------------------------------------------------------------------- /after/ftplugin/lua.lua: -------------------------------------------------------------------------------- 1 | vim.bo.tabstop = 2 2 | vim.bo.textwidth = 120 3 | -- remove {o|O} newline auto-comments 4 | vim.opt_local.formatoptions:remove("o") 5 | -- remove auto-indent after 'end|until' 6 | -- set by '/usr/share/nvim/runtime/indent/lua.vim' 7 | -- this gets overwritten, moved to autocmd 8 | -- vim.cmd[[setlocal indentkeys-=0=end,0=until]] 9 | -------------------------------------------------------------------------------- /after/ftplugin/markdown.lua: -------------------------------------------------------------------------------- 1 | vim.bo.tabstop = 4 2 | vim.wo.spell = true 3 | 4 | -- Previm plugin 5 | vim.keymap.set({ "n", "v" }, "r", function() 6 | vim.cmd [[call previm#open(previm#make_preview_file_path("index.html"))]] 7 | end, { buffer = true, silent = true, desc = "open markdown preview (previm)" }) 8 | 9 | vim.api.nvim_create_autocmd(vim.g.previm_enable_realtime == 1 10 | and { "CursorHold", "CursorHoldI", "InsertLeave", "BufWritePost" } 11 | or { "BufWritePost" }, 12 | { 13 | group = vim.api.nvim_create_augroup("ibhagwan/PrevimRefresh", { clear = true }), 14 | buffer = 0, 15 | desc = "Previm refresh", 16 | callback = function() 17 | vim.cmd [[call previm#refresh()]] 18 | end, 19 | }) 20 | -------------------------------------------------------------------------------- /after/ftplugin/python.lua: -------------------------------------------------------------------------------- 1 | vim.bo.tabstop = 4 2 | vim.bo.textwidth = 0 3 | vim.bo.expandtab = true 4 | -- remove {o|O} newline auto-comments 5 | vim.opt_local.formatoptions:remove("o") 6 | -------------------------------------------------------------------------------- /after/ftplugin/qf.lua: -------------------------------------------------------------------------------- 1 | -- Always open QF window at the bottom 2 | -- vim.cmd[[wincmd J]] 3 | 4 | -- Quit vim if the last window is qf 5 | vim.cmd [[autocmd! BufEnter if winnr('$') < 2| q | endif]] 6 | 7 | vim.wo.scrolloff = 0 8 | vim.wo.wrap = false 9 | vim.wo.number = true 10 | vim.wo.relativenumber = false 11 | vim.wo.linebreak = true 12 | vim.wo.list = false 13 | vim.wo.cursorline = true 14 | vim.wo.spell = false 15 | vim.bo.buflisted = false 16 | 17 | vim.keymap.set("n", "[-", ":colder", { buffer = true }) 18 | vim.keymap.set("n", "]+", ":cnewer", { buffer = true }) 19 | -------------------------------------------------------------------------------- /after/ftplugin/rust.lua: -------------------------------------------------------------------------------- 1 | vim.bo.tabstop = 4 2 | -- remove {o|O} newline auto-comments 3 | vim.opt_local.formatoptions:remove("o") 4 | -------------------------------------------------------------------------------- /after/ftplugin/solidity.lua: -------------------------------------------------------------------------------- 1 | vim.bo.tabstop = 4 2 | -------------------------------------------------------------------------------- /after/ftplugin/text.lua: -------------------------------------------------------------------------------- 1 | vim.wo.spell = true 2 | vim.bo.tabstop = 4 3 | vim.bo.textwidth = 78 4 | -------------------------------------------------------------------------------- /after/ftplugin/vim.lua: -------------------------------------------------------------------------------- 1 | vim.bo.tabstop = 2 2 | -- remove {o|O} newline auto-comments 3 | vim.opt_local.formatoptions:remove("o") 4 | -------------------------------------------------------------------------------- /after/lsp/ccls.lua: -------------------------------------------------------------------------------- 1 | return { 2 | on_attach = function() print("ccls attached") end, 3 | init_options = { 4 | codeLens = { 5 | enabled = false, 6 | renderInline = false, 7 | localVariables = false, 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /after/lsp/lua_ls.lua: -------------------------------------------------------------------------------- 1 | -- Custom root dir function that ignores "$HOME/.git" for lua files in $HOME 2 | -- which will then run the LSP in single file mdoe, otherwise will err with: 3 | -- LSP[lua_ls] Your workspace is set to `$HOME`. 4 | -- Lua language server refused to load this directory. 5 | -- Please check your configuration. 6 | -- [learn more here](https://luals.github.io/wiki/faq#why-is-the-server-scanning-the-wrong-folder) 7 | -- Reuse ".../nvim-lspconfig/lua/lspconfig/configs/lua_ls" 8 | local root_dir = function(...) 9 | local lua_ls = require "lspconfig.configs.lua_ls".default_config 10 | local root = lua_ls.root_dir(...) 11 | -- NOTE: although returning `nil` here does nullify the "rootUri" property lua_ls still 12 | -- displays the error, I'm not sure if returning an empty string is the correct move as 13 | -- it generates "rootUri = "file://" but it does seem to quiet lua_ls and make it work 14 | -- as if it was started in single file mode 15 | return root and root ~= vim.env.HOME and root or "" 16 | end 17 | 18 | return { 19 | -- on_attach = function() print("lua_ls attached") end, 20 | root_dir = function(bufnr, cb_root_dir) 21 | local bname = vim.api.nvim_buf_get_name(bufnr) 22 | local root = root_dir(#bname > 0 and bname or vim.uv.cwd()) 23 | cb_root_dir(root) 24 | end, 25 | -- uncomment to enable trace logging into: 26 | -- "~/.local/share/nvim/mason/packages/lua-language-server/libexec/log/service.log" 27 | -- cmd = { "lua-language-server", "--loglevel=trace" }, 28 | settings = { 29 | Lua = { 30 | telemetry = { enable = false }, 31 | runtime = { version = "LuaJIT" }, 32 | -- removes the annoying "Do you need to configure your work environment as" 33 | -- when opening a lua project that doesn't have a '.luarc.json' 34 | workspace = { checkThirdParty = false }, 35 | diagnostics = { 36 | globals = { 37 | "vim", 38 | "require", 39 | }, 40 | }, 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /after/lsp/rust_analyzer.lua: -------------------------------------------------------------------------------- 1 | return { 2 | -- on_attach = function() print("rust_analyzer attached") end, 3 | -- use nightly rustfmt if exists 4 | -- https://github.com/rust-lang/rust-analyzer/issues/3627 5 | -- https://github.com/rust-lang/rust-analyzer/blob/master/docs/user/generated_config.adoc 6 | settings = { 7 | ["rust-analyzer"] = { 8 | -- check = { command = "clippy" }, 9 | -- Enable all features of a crate 10 | cargo = { features = "all" }, 11 | interpret = { tests = true }, 12 | rustfmt = { 13 | extraArgs = { "+nightly", }, 14 | -- overrideCommand = { 15 | -- "rustup", 16 | -- "run", 17 | -- "nightly", 18 | -- "--", 19 | -- "rustfmt", 20 | -- "--edition", 21 | -- "2021", 22 | -- "--", 23 | -- }, 24 | }, 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /css/highlight-gh-dark.css: -------------------------------------------------------------------------------- 1 | /*! 2 | highlight.js 3 | https://highlightjs.readthedocs.io/en/latest/css-classes-reference.html 4 | NOTE: we don't use '@import' as the css needs to be copied next to 5 | the generated HTML, this css is already copied by previm 6 | */ 7 | 8 | /*! 9 | Theme: Default 10 | Description: Original highlight.js style 11 | Author: (c) Ivan Sagalaev 12 | Maintainer: @highlightjs/core-team 13 | Website: https://highlightjs.org/ 14 | License: see project LICENSE 15 | Touched: 2021 16 | */ 17 | 18 | pre code.hljs { 19 | display: block; 20 | overflow-x: auto; 21 | padding: 0.5em; 22 | -webkit-text-size-adjust: none; 23 | } 24 | 25 | code.hljs { 26 | padding: 3px 5px; 27 | } 28 | 29 | .hljs { 30 | background: #2D333B; 31 | color: #ADBAC7; 32 | } 33 | 34 | .hljs-comment, 35 | .hljs-quote, 36 | .diff .hljs-header, 37 | .hljs-javadoc { 38 | color: #768390; 39 | font-style: italic; 40 | } 41 | 42 | .hljs-list, 43 | .hljs-subst, 44 | .hljs-operator, 45 | .hljs-keyword { 46 | color: #F47067; 47 | } 48 | 49 | .hljs-number, 50 | .hljs-literal, 51 | .hljs-variable, 52 | .hljs-template-variable, 53 | .hljs-tag .hljs-attr { 54 | color: #6CB6FF; 55 | /* color: #008080; */ 56 | } 57 | 58 | .hljs-hexcolor, 59 | .hljs-number, 60 | .hljs-literal, 61 | .hljs-variable, 62 | .hljs-template-variable, 63 | .hljs-tag .hljs-attr { 64 | color: #6CB6FF; 65 | /* color: #008080; */ 66 | } 67 | 68 | .hljs-tag .hljs-value, 69 | .hljs-phpdoc, 70 | .hljs-dartdoc, 71 | .tex .hljs-formula { 72 | color: #F48FB1; 73 | } 74 | .hljs-string, 75 | .hljs-doctag { 76 | color: #96D0FF; 77 | /* color: #7fdbca; */ 78 | } 79 | 80 | .hljs-title, 81 | .hljs-section, 82 | .hljs-selector-id { 83 | color: #fc514e; 84 | font-weight: bold; 85 | } 86 | 87 | .hljs-subst { 88 | font-weight: normal; 89 | } 90 | 91 | .hljs-type, 92 | .hljs-class .hljs-title { 93 | color: #458; 94 | font-weight: bold; 95 | } 96 | 97 | .hljs-tag, 98 | .hljs-name, 99 | .hljs-attribute { 100 | color: #000080; 101 | font-weight: normal; 102 | } 103 | 104 | .hljs-regexp, 105 | .hljs-link { 106 | color: #009926; 107 | } 108 | 109 | .hljs-symbol, 110 | .hljs-bullet { 111 | color: #990073; 112 | } 113 | 114 | .hljs-built_in, 115 | .hljs-builtin-name { 116 | color: #6CB6FF; 117 | } 118 | 119 | .hljs-preprocessor, 120 | .hljs-pragma, 121 | .hljs-pi, 122 | .hljs-doctype, 123 | .hljs-shebang, 124 | .hljs-cdata { 125 | color: #999; 126 | font-weight: bold; 127 | } 128 | 129 | .hljs-meta { 130 | color: #999; 131 | font-weight: bold; 132 | } 133 | 134 | .hljs-deletion { 135 | background: #fdd; 136 | } 137 | 138 | .hljs-addition { 139 | background: #dfd; 140 | } 141 | 142 | .hljs-emphasis { 143 | font-style: italic; 144 | } 145 | 146 | .hljs-strong { 147 | font-weight: bold; 148 | 149 | .hljs-chunk { 150 | color: #aaa; 151 | } 152 | -------------------------------------------------------------------------------- /css/previm-gh-dark.css: -------------------------------------------------------------------------------- 1 | #header { 2 | border-bottom: 1px solid #444C56; 3 | } 4 | 5 | #last-modified { 6 | padding-left: 10px; 7 | } 8 | 9 | html { 10 | background: #22272E; 11 | margin: 0; 12 | padding: 0; 13 | } 14 | 15 | body { 16 | font: 14px helvetica,arial,freesans,clean,sans-serif; 17 | line-height: 1.6; 18 | margin: 0 auto; 19 | padding: 20px; 20 | text-align: left; 21 | color: #ADBAC7; 22 | width: auto; 23 | max-width: 920px; 24 | } 25 | 26 | body > *:first-child { 27 | margin-top: 0 !important; 28 | } 29 | body > *:last-child { 30 | margin-bottom: 0 !important; 31 | } 32 | 33 | h1 { 34 | font-size: 28px; 35 | margin-bottom: 10px; 36 | color: #ADBAC7; 37 | } 38 | 39 | h2 { 40 | font-size: 24px; 41 | margin: 20px 0 10px; 42 | color: #ADBAC7; 43 | border-bottom: 1px solid #444C56; 44 | } 45 | 46 | h3 { 47 | font-size: 18px; 48 | margin: 20px 0 10px; 49 | } 50 | 51 | h4 { 52 | font-size: 16px; 53 | font-weight: bold; 54 | margin: 20px 0 10px; 55 | } 56 | 57 | h5 { 58 | font-size: 14px; 59 | font-weight: bold; 60 | margin: 20px 0 10px; 61 | } 62 | 63 | h6 { 64 | color: #9b9b9b; 65 | font-size: 14px; 66 | font-weight: bold; 67 | margin: 20px 0 10px; 68 | } 69 | 70 | hr { 71 | } 72 | 73 | p { 74 | margin: 15px 0; 75 | } 76 | 77 | pre, code { 78 | font: 12px 'Bitstream Vera Sans Mono','Courier',monospace; 79 | } 80 | 81 | .highlight pre, pre { 82 | background-color: #2D333B; 83 | border: 1px solid #2D333B; 84 | font-size: 13px; 85 | line-height: 19px; 86 | overflow: auto; 87 | border-radius: 3px; 88 | -moz-border-radius: 3px; 89 | -webkit-border-radius: 3px; 90 | padding: 6px 10px; 91 | } 92 | 93 | code { 94 | white-space: nowrap; 95 | border: 1px solid #2D333B; 96 | background-color: #3c434d; 97 | border-radius: 3px; 98 | -moz-border-radius: 3px; 99 | -webkit-border-radius: 3px; 100 | margin: 0 2px; 101 | padding: 0 5px; 102 | } 103 | 104 | pre code 105 | { 106 | white-space: pre; 107 | border: none; 108 | background: transparent; 109 | margin: 0; 110 | padding: 0; 111 | display: block; 112 | } 113 | 114 | a, a code { 115 | color: #539BF5; 116 | text-decoration: none; 117 | } 118 | 119 | blockquote 120 | { 121 | border-left: 4px solid #444C56; 122 | padding: 0 15px; 123 | color: #768390; 124 | margin: 15px 0; 125 | } 126 | blockquote > :first-child { 127 | margin-top: 0; 128 | } 129 | blockquote > :last-child { 130 | margin-bottom: 0; 131 | } 132 | 133 | table 134 | { 135 | font-size: 14px; 136 | border-collapse: collapse; 137 | margin: 20px 0 0; 138 | padding: 0; 139 | } 140 | 141 | table tr 142 | { 143 | border-top: 1px solid #444C56; 144 | background-color: #22272E; 145 | margin: 0; 146 | padding: 0; 147 | } 148 | 149 | table tr:nth-child(2n) 150 | { 151 | background-color: #2D333B; 152 | } 153 | table tr th[align="center"], table tr td[align="center"] { 154 | text-align: center; 155 | } 156 | table tr th[align="right"], table tr td[align="right"] { 157 | text-align: right; 158 | } 159 | table tr th, table tr td 160 | { 161 | border: 1px solid #444C56; 162 | text-align: left; 163 | margin: 0; 164 | padding: 6px 13px; 165 | } 166 | 167 | ul, ol 168 | { 169 | margin: 15px 0; 170 | } 171 | 172 | ul li, ol li 173 | { 174 | margin-top: 7px; 175 | margin-bottom: 7px; 176 | } 177 | 178 | dl { 179 | padding: 0; 180 | } 181 | dl dt { 182 | font-size: 14px; 183 | font-weight: bold; 184 | font-style: italic; 185 | padding: 0; 186 | margin: 15px 0 5px; 187 | } 188 | dl dt:first-child { 189 | padding: 0; 190 | } 191 | dl dt > :first-child { 192 | margin-top: 0; 193 | } 194 | dl dt > :last-child { 195 | margin-bottom: 0; 196 | } 197 | dl dd { 198 | margin: 0 0 15px; 199 | padding: 0 15px; 200 | } 201 | dl dd > :first-child { 202 | margin-top: 0; 203 | } 204 | dl dd > :last-child { 205 | margin-bottom: 0; 206 | } 207 | img { 208 | max-width: 100%; 209 | } 210 | 211 | .shadow { 212 | -webkit-box-shadow: 0 5px 15px #000; 213 | -moz-box-shadow: 0 5px 15px #000; 214 | box-shadow: 0 5px 15px #000; 215 | } 216 | 217 | input[type="checkbox"] + label { 218 | margin-left: 0.5ex; 219 | } 220 | 221 | @media screen and (min-width: 914px) { 222 | body { 223 | width: 854px; 224 | margin:0 auto; 225 | } 226 | } 227 | @media print { 228 | table, pre { 229 | page-break-inside: avoid; 230 | } 231 | } 232 | 233 | .code-lang { 234 | color: #eee; 235 | display: inline-block; 236 | background-color: #777; 237 | padding: 2px 4px; 238 | transform: translateY(-1em); 239 | word-break: break-all; 240 | margin-top: 6px; 241 | } 242 | -------------------------------------------------------------------------------- /init.lua: -------------------------------------------------------------------------------- 1 | local utils = require("utils") 2 | 3 | if vim.fn.has("nvim-0.10") ~= 1 then 4 | utils.warn("This config requires neovim 0.10 and above") 5 | vim.o.loadplugins = false 6 | vim.o.termguicolors = true 7 | return 8 | end 9 | 10 | require("options") 11 | require("autocmd") 12 | require("keymaps") 13 | require("term") 14 | 15 | -- Don't load plugins as root and use a different colorscheme 16 | if not utils.is_root() then 17 | require("lazyplug") 18 | end 19 | -------------------------------------------------------------------------------- /lazy-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "blink.cmp": { "branch": "main", "commit": "196711b89a97c953877d6c257c62f18920a970f3" }, 3 | "conform.nvim": { "branch": "master", "commit": "6feb2f28f9a9385e401857b21eeac3c1b66dd628" }, 4 | "diffview.nvim": { "branch": "main", "commit": "4516612fe98ff56ae0415a259ff6361a89419b0a" }, 5 | "fidget.nvim": { "branch": "main", "commit": "d9ba6b7bfe29b3119a610892af67602641da778e" }, 6 | "gitsigns.nvim": { "branch": "main", "commit": "8b729e489f1475615dc6c9737da917b3bc163605" }, 7 | "lazy.nvim": { "branch": "main", "commit": "6c3bda4aca61a13a9c63f1c1d1b16b9d3be90d7a" }, 8 | "lazydev.nvim": { "branch": "main", "commit": "2367a6c0a01eb9edb0464731cc0fb61ed9ab9d2c" }, 9 | "mason-lspconfig.nvim": { "branch": "main", "commit": "d24b3f1612e53f9d54d866b16bedab51813f2bf1" }, 10 | "mason.nvim": { "branch": "main", "commit": "8024d64e1330b86044fed4c8494ef3dcd483a67c" }, 11 | "mini.nvim": { "branch": "main", "commit": "9688298860d8ac7d334139c5fd599edbedeff3c0" }, 12 | "nvim-cmp": { "branch": "main", "commit": "b555203ce4bd7ff6192e759af3362f9d217e8c89" }, 13 | "nvim-dap": { "branch": "master", "commit": "b0f983507e3702f073bfe1516846e58b56d4e42f" }, 14 | "nvim-dap-python": { "branch": "master", "commit": "261ce649d05bc455a29f9636dc03f8cdaa7e0e2c" }, 15 | "nvim-dap-ui": { "branch": "master", "commit": "73a26abf4941aa27da59820fd6b028ebcdbcf932" }, 16 | "nvim-dap-virtual-text": { "branch": "master", "commit": "fbdb48c2ed45f4a8293d0d483f7730d24467ccb6" }, 17 | "nvim-jdtls": { "branch": "master", "commit": "c23f200fee469a415c77265ca55b496feb646992" }, 18 | "nvim-lspconfig": { "branch": "master", "commit": "3ea99227e316c5028f57a4d86a1a7fd01dd876d0" }, 19 | "nvim-nio": { "branch": "master", "commit": "21f5324bfac14e22ba26553caf69ec76ae8a7662" }, 20 | "nvim-treesitter": { "branch": "master", "commit": "42fc28ba918343ebfd5565147a42a26580579482" }, 21 | "nvim-treesitter-context": { "branch": "master", "commit": "404502e607c3b309e405be9112c438c721153372" }, 22 | "nvim-treesitter-textobjects": { "branch": "master", "commit": "0f051e9813a36481f48ca1f833897210dbcfffde" }, 23 | "nvim-web-devicons": { "branch": "master", "commit": "1fb58cca9aebbc4fd32b086cb413548ce132c127" }, 24 | "oil.nvim": { "branch": "master", "commit": "685cdb4ffa74473d75a1b97451f8654ceeab0f4a" }, 25 | "one-small-step-for-vimkind": { "branch": "main", "commit": "a2adadde3bcdb56a57dbba2a1f3eb39e24771f1c" }, 26 | "previm": { "branch": "master", "commit": "8d414bf9b38d2a7c65a313775e26c03a0169f67f" }, 27 | "render-markdown.nvim": { "branch": "main", "commit": "df64d5d5432e13026a79384ec4e2bab185fd4eb5" }, 28 | "snacks.nvim": { "branch": "main", "commit": "bc0630e43be5699bb94dadc302c0d21615421d93" }, 29 | "tokyonight.nvim": { "branch": "main", "commit": "c3ab53c3f544e4a04f2a05d43451fd9bedff51b4" }, 30 | "vim-fugitive": { "branch": "master", "commit": "4a745ea72fa93bb15dd077109afbb3d1809383f2" }, 31 | "vim-moonfly-colors": { "branch": "master", "commit": "e356d55cedb24a6c4251d83ce28e0e2010e99d2f" }, 32 | "vim-startuptime": { "branch": "master", "commit": "b6f0d93f6b8cf6eee0b4c94450198ba2d6a05ff6" }, 33 | "which-key.nvim": { "branch": "main", "commit": "370ec46f710e058c9c1646273e6b225acf47cbed" } 34 | } 35 | -------------------------------------------------------------------------------- /lua/autocmd.lua: -------------------------------------------------------------------------------- 1 | local utils = require("utils") 2 | local aucmd = vim.api.nvim_create_autocmd 3 | 4 | local function augroup(name, fnc) 5 | fnc(vim.api.nvim_create_augroup(name, { clear = true })) 6 | end 7 | 8 | if utils.is_root() then 9 | augroup("ibhagwan/SmartTextYankPost", function(g) 10 | -- highlight yanked text and copy to system clipboard 11 | -- TextYankPost is also called on deletion, limit to 12 | -- yanks via v:operator 13 | -- if we are connected over ssh also copy using OSC52 14 | aucmd("TextYankPost", { 15 | group = g, 16 | pattern = "*", 17 | -- command = "if has('clipboard') && v:operator=='y' && len(@0)>0 | " 18 | -- .. "let @+=@0 | endif | " 19 | -- .. "lua vim.highlight.on_yank{higroup='IncSearch', timeout=2000}" 20 | desc = "Copy to clipboard/tmux/OSC52", 21 | callback = function() 22 | local ok, yank_data = pcall(vim.fn.getreg, "0") 23 | local valid_yank = ok and #yank_data > 0 and vim.v.operator == "y" 24 | if valid_yank and vim.fn.has("clipboard") == 1 then 25 | pcall(vim.fn.setreg, "+", yank_data) 26 | end 27 | -- $SSH_CONNECTION doesn't pass over to 28 | -- root when using `su -`, copy indiscriminately 29 | if valid_yank and (vim.env.SSH_CONNECTION or utils.is_root()) then 30 | utils.osc52printf(yank_data) 31 | end 32 | if valid_yank and vim.env.TMUX then 33 | -- we use `-w` to also copy to client's clipboard 34 | vim.fn.system({ "tmux", "set-buffer", "-w", yank_data }) 35 | end 36 | vim.highlight.on_yank({ higroup = "IncSearch", timeout = 1000 }) 37 | end 38 | }) 39 | end) 40 | end 41 | 42 | augroup("ibhagwan/ColorScheme", function(g) 43 | aucmd("ColorSchemePre", { 44 | group = g, 45 | callback = function(_) 46 | vim.g.fzf_colors = nil 47 | end 48 | }) 49 | aucmd("ColorScheme", { 50 | group = g, 51 | callback = function(_) 52 | -- workaround for messed up block cursor on blink.sh 53 | vim.api.nvim_set_hl(0, "TermCursor", { default = false, reverse = true }) 54 | -- fix 'listchars' highlight on nightfly 55 | if vim.g.colors_name == "nightfly" or "moonfly" then 56 | vim.api.nvim_set_hl(0, "Whitespace", { default = false, link = "NonText" }) 57 | vim.api.nvim_set_hl(0, "FloatBorder", { default = false, link = "LineNr" }) 58 | vim.api.nvim_set_hl(0, "WinSeparator", { default = false, link = "FloatBorder" }) 59 | vim.api.nvim_set_hl(0, "ColorColumn", { default = false, link = "PmenuSbar" }) 60 | elseif vim.g.colors_name:match("tokyonight") then 61 | vim.api.nvim_set_hl(0, "WinSeparator", { default = false, link = "FloatBorder" }) 62 | end 63 | -- fzf-lua 64 | if type(vim.g.fzf_colors) == "table" then 65 | vim.g.fzf_colors = vim.tbl_deep_extend("keep", 66 | { ["bg+"] = { "bg", "Visual" } }, vim.g.fzf_colors) 67 | end 68 | vim.api.nvim_set_hl(0, "FzfLuaCursorLine", { default = false, link = "Visual" }) 69 | vim.api.nvim_set_hl(0, "FzfLuaFzfCursorLine", { default = false, link = "Visual" }) 70 | -- treesitter context 71 | vim.api.nvim_set_hl(0, "TreesitterContext", { default = false, link = "Visual" }) 72 | vim.api.nvim_set_hl(0, "TreesitterContextBottom", { default = false, underline = true }) 73 | -- render-markdown 74 | vim.api.nvim_set_hl(0, "RenderMarkdownCode", { default = false, link = "DiffChange" }) 75 | vim.api.nvim_set_hl(0, "RenderMarkdownH1Bg", { default = false, link = "Visual" }) 76 | vim.api.nvim_set_hl(0, "RenderMarkdownH2Bg", { default = false, link = "DiffText" }) 77 | vim.api.nvim_set_hl(0, "RenderMarkdownH3Bg", { default = false, link = "DiffAdd" }) 78 | vim.api.nvim_set_hl(0, "RenderMarkdownH4Bg", { default = false, link = "DiffAdd" }) 79 | vim.api.nvim_set_hl(0, "RenderMarkdownH5Bg", { default = false, link = "DiffAdd" }) 80 | vim.api.nvim_set_hl(0, "RenderMarkdownH6Bg", { default = false, link = "DiffAdd" }) 81 | end, 82 | }) 83 | end) 84 | 85 | -- disable mini.indentscope for certain filetype|buftype 86 | augroup("ibhagwan/MiniIndentscopeDisable", function(g) 87 | aucmd("BufEnter", { 88 | group = g, 89 | callback = function(_) 90 | if vim.bo.filetype == "fzf" 91 | or vim.bo.filetype == "help" 92 | or vim.bo.buftype == "nofile" 93 | or vim.bo.buftype == "terminal" 94 | then 95 | vim.b.miniindentscope_disable = true 96 | end 97 | end, 98 | }) 99 | end) 100 | 101 | augroup("ibhagwan/TermOptions", function(g) 102 | aucmd("TermOpen", 103 | { 104 | group = g, 105 | command = "setlocal listchars= nonumber norelativenumber" 106 | }) 107 | end) 108 | 109 | augroup("ibhagwan/ResizeWindows", function(g) 110 | aucmd("VimResized", 111 | { 112 | group = g, 113 | command = "tabdo wincmd =" 114 | }) 115 | end) 116 | 117 | augroup("ibhagwan/ToggleSearchHL", function(g) 118 | aucmd("InsertEnter", { 119 | group = g, 120 | callback = function() 121 | vim.schedule(function() vim.cmd("nohlsearch") end) 122 | end 123 | }) 124 | aucmd("CursorMoved", { 125 | group = g, 126 | callback = function() 127 | --[[ -- No bloat lua adpatation of: https://github.com/romainl/vim-cool 128 | local view, rpos = vim.fn.winsaveview(), vim.fn.getpos(".") 129 | assert(view.lnum == rpos[2]) 130 | assert(view.col + 1 == rpos[3]) 131 | -- Move the cursor to a position where (whereas in active search) pressing `n` 132 | -- brings us to the original cursor position, in a forward search / that means 133 | -- one column before the match, in a backward search ? we move one col forward 134 | vim.cmd(string.format("silent! keepjumps go%s", 135 | (vim.fn.line2byte(view.lnum) + view.col + 1 - (vim.v.searchforward == 1 and 2 or 0)))) 136 | -- Attempt to goto next match, if we're in an active search cursor position 137 | -- should be equal to original cursor position 138 | local ok, _ = pcall(vim.cmd, "silent! keepjumps norm! n") 139 | local insearch = ok and (function() 140 | local npos = vim.fn.getpos(".") 141 | return npos[2] == rpos[2] and npos[3] == rpos[3] 142 | end)() 143 | -- restore original view and position 144 | vim.fn.winrestview(view) 145 | if not insearch then 146 | vim.schedule(function() vim.cmd("nohlsearch") end) 147 | end ]] 148 | if vim.v.hlsearch == 1 and vim.fn.searchcount().exact_match == 0 then 149 | vim.schedule(function() vim.cmd.nohlsearch() end) 150 | end 151 | end 152 | }) 153 | end) 154 | 155 | augroup("ibhagwan/ActiveWinCursorLine", function(g) 156 | -- Highlight current line only on focused window 157 | local callback = function() 158 | local curwin = vim.api.nvim_get_current_win() 159 | for _, w in ipairs(vim.api.nvim_list_wins()) do 160 | vim.wo[w].cursorline = w == curwin 161 | end 162 | end 163 | aucmd({ "WinEnter", "BufEnter", "InsertLeave" }, { group = g, callback = callback }) 164 | aucmd({ "WinLeave", "BufLeave", "InsertEnter" }, { group = g, callback = callback }) 165 | end) 166 | 167 | -- goto last location when opening a buffer 168 | augroup("ibhagwan/BufLastLocation", function(g) 169 | aucmd("BufReadPost", { 170 | group = g, 171 | callback = function(e) 172 | -- skip fugitive commit message buffers 173 | local bufname = vim.api.nvim_buf_get_name(e.buf) 174 | if bufname:match("COMMIT_EDITMSG$") then return end 175 | local mark = vim.api.nvim_buf_get_mark(e.buf, '"') 176 | local line_count = vim.api.nvim_buf_line_count(e.buf) 177 | if mark[1] > 0 and mark[1] <= line_count then 178 | vim.cmd 'normal! g`"zz' 179 | end 180 | end, 181 | }) 182 | end) 183 | 184 | -- auto-delete fugitive buffers 185 | augroup("ibhagwan/Fugitive", function(g) 186 | aucmd("BufReadPost", { 187 | group = g, 188 | pattern = "fugitive:*", 189 | command = "set bufhidden=delete" 190 | }) 191 | end) 192 | 193 | -- Solidity abi JSON 194 | augroup("ibhagwan/SolidityABI", function(g) 195 | aucmd({ "BufRead", "BufNewFile" }, { 196 | group = g, 197 | pattern = "*.abi", 198 | command = "set filetype=jsonc" 199 | }) 200 | end) 201 | 202 | -- Display help|man in vertical splits and map 'q' to quit 203 | augroup("ibhagwan/Help", function(g) 204 | local function open_vert() 205 | -- do nothing for floating windows or if this is 206 | -- the fzf-lua minimized help window (height=1) 207 | local cfg = vim.api.nvim_win_get_config(0) 208 | if cfg and (cfg.external or cfg.relative and #cfg.relative > 0) 209 | or vim.api.nvim_win_get_height(0) == 1 then 210 | return 211 | end 212 | -- do not run if Diffview is open 213 | if vim.g.diffview_nvim_loaded and 214 | require "diffview.lib".get_current_view() then 215 | return 216 | end 217 | local width = math.floor(vim.o.columns * 0.75) 218 | vim.cmd("wincmd L") 219 | vim.cmd("vertical resize " .. width) 220 | vim.keymap.set("n", "q", "q", { buffer = true }) 221 | end 222 | 223 | aucmd("FileType", { 224 | group = g, 225 | pattern = "help,man", 226 | callback = open_vert, 227 | }) 228 | -- we also need this auto command or help 229 | -- still opens in a split on subsequent opens 230 | aucmd("BufEnter", { 231 | group = g, 232 | pattern = "*.txt", 233 | callback = function() 234 | if vim.bo.buftype == "help" then 235 | open_vert() 236 | end 237 | end 238 | }) 239 | aucmd("BufHidden", { 240 | group = g, 241 | pattern = "man://*", 242 | callback = function() 243 | if vim.bo.filetype == "man" then 244 | local bufnr = vim.api.nvim_get_current_buf() 245 | vim.defer_fn(function() 246 | if vim.api.nvim_buf_is_valid(bufnr) then 247 | vim.api.nvim_buf_delete(bufnr, { force = true }) 248 | end 249 | end, 0) 250 | end 251 | end 252 | }) 253 | end) 254 | 255 | -- https://vim.fandom.com/wiki/Avoid_scrolling_when_switch_buffers 256 | augroup("ibhagwan/DoNotAutoScroll", function(g) 257 | aucmd("BufLeave", { 258 | group = g, 259 | desc = "Avoid autoscroll when switching buffers", 260 | callback = function() 261 | -- at this stage, current buffer is the buffer we leave 262 | -- but the current window already changed, verify neither 263 | -- source nor destination are floating windows 264 | local from_buf = vim.api.nvim_get_current_buf() 265 | local from_win = vim.fn.bufwinid(from_buf) 266 | local to_win = vim.api.nvim_get_current_win() 267 | if not utils.win_is_float(to_win) and not utils.win_is_float(from_win) then 268 | vim.b.__VIEWSTATE = vim.fn.winsaveview() 269 | end 270 | end 271 | }) 272 | aucmd("BufEnter", { 273 | group = g, 274 | desc = "Avoid autoscroll when switching buffers", 275 | callback = function() 276 | if vim.b.__VIEWSTATE then 277 | local to_win = vim.api.nvim_get_current_win() 278 | if not utils.win_is_float(to_win) then 279 | vim.fn.winrestview(vim.b.__VIEWSTATE) 280 | end 281 | vim.b.__VIEWSTATE = nil 282 | end 283 | end 284 | }) 285 | end) 286 | 287 | augroup("ibhagwan/GQFormatter", function(g) 288 | aucmd({ "FileType", "LspAttach" }, 289 | { 290 | group = g, 291 | callback = function(e) 292 | -- execlude diffview and vim-fugitive 293 | if vim.bo.filetype == "fugitive" 294 | or e.file:match("^fugitive:") 295 | or require("plugins.diffview")._is_open() then 296 | return 297 | end 298 | require("plugins.conform")._set_gq_keymap(e) 299 | end, 300 | }) 301 | end) 302 | 303 | augroup("ibhagwan/LspAttach", function(g) 304 | aucmd({ "LspAttach" }, 305 | { 306 | group = g, 307 | callback = function(_) 308 | require("lsp.keymaps").setup() 309 | end, 310 | }) 311 | end) 312 | -------------------------------------------------------------------------------- /lua/keymaps.lua: -------------------------------------------------------------------------------- 1 | local map = vim.keymap.set 2 | local utils = require("utils") 3 | 4 | map("", "R", utils.reload_config, { silent = true, desc = "reload nvim configuration" }) 5 | 6 | map("", "r", function() 7 | vim.api.nvim_exec2("update", {}) 8 | vim.api.nvim_exec2("so %", {}) 9 | utils.info(string.format("Sourced '%s'", vim.fn.expand("%"))) 10 | end, { silent = true, desc = "source current file" }) 11 | 12 | -- Use ':Grep' or ':LGrep' to grep into quickfix|loclist 13 | -- without output or jumping to first match 14 | -- Use ':Grep %' to search only current file 15 | -- Use ':Grep %:h' to search the current file dir 16 | vim.cmd("command! -nargs=+ -complete=file Grep noautocmd grep! | redraw! | copen") 17 | vim.cmd("command! -nargs=+ -complete=file LGrep noautocmd lgrep! | redraw! | lopen") 18 | 19 | -- Fix common typos 20 | vim.cmd([[ 21 | cnoreabbrev W! w! 22 | cnoreabbrev W1 w! 23 | cnoreabbrev w1 w! 24 | cnoreabbrev Q! q! 25 | cnoreabbrev Q1 q! 26 | cnoreabbrev q1 q! 27 | cnoreabbrev Qa! qa! 28 | cnoreabbrev Qall! qall! 29 | cnoreabbrev Wa wa 30 | cnoreabbrev Wq wq 31 | cnoreabbrev wQ wq 32 | cnoreabbrev WQ wq 33 | cnoreabbrev wq1 wq! 34 | cnoreabbrev Wq1 wq! 35 | cnoreabbrev wQ1 wq! 36 | cnoreabbrev WQ1 wq! 37 | cnoreabbrev W w 38 | cnoreabbrev Q q 39 | cnoreabbrev Qa qa 40 | cnoreabbrev Qall qall 41 | ]]) 42 | 43 | -- root doesn't use plugins, use builtin FZF 44 | if require "utils".is_root() then 45 | -- vim.g.fzf_layout = { window = { ["width"] = 0.9, height = 0.6 } } 46 | map("n", "", "FZF", { desc = "FZF" }) 47 | end 48 | 49 | map({ "n", "v", "i" }, "", 50 | function() require("fzf-lua").complete_path({ file_icons = true }) end, 51 | { silent = true, desc = "Fuzzy complete path" }) 52 | 53 | map({ "n", "v", "i" }, "", 54 | function() require("fzf-lua").complete_line() end, 55 | { silent = true, desc = "Fuzzy complete line" }) 56 | 57 | map({ "n", "v", "i" }, "", 58 | function() require("fzf-lua").spell_suggest() end, 59 | { silent = true, desc = "Fuzzy complete path" }) 60 | 61 | -- to Save 62 | map({ "n", "v", "i" }, "", ":update", { silent = true, desc = "Save" }) 63 | 64 | -- w!! to save with sudo 65 | map("c", "w!!", require("utils").sudo_write, { silent = true }) 66 | 67 | -- Beginning and end of line in `:` command mode 68 | map("c", "", "", {}) 69 | map("c", "", "", {}) 70 | 71 | -- Terminal mappings 72 | map("t", "", [[]], {}) 73 | map("t", "", [['"'.nr2char(getchar()).'pi']], { expr = true }) 74 | 75 | -- TMUX aware navigation 76 | for _, k in ipairs({ "h", "j", "k", "l", "o" }) do 77 | map({ "n", "x", "t" }, string.format("", k), function() 78 | require("utils").tmux_aware_navigate(k, true) 79 | end, { silent = true }) 80 | end 81 | 82 | -- tmux like directional window resizes 83 | map("n", "=", "=", 84 | { silent = true, desc = "normalize split layout" }) 85 | map("n", "", "lua require'utils'.resize(false, -5)", 86 | { silent = true, desc = "horizontal split increase" }) 87 | map("n", "", "lua require'utils'.resize(false, 5)", 88 | { silent = true, desc = "horizontal split decrease" }) 89 | map("n", "", "lua require'utils'.resize(true, -5)", 90 | { silent = true, desc = "vertical split decrease" }) 91 | map("n", "", "lua require'utils'.resize(true, 5)", 92 | { silent = true, desc = "vertical split increase" }) 93 | 94 | -- Navigate buffers|tabs|quickfix|loclist 95 | for k, v in pairs({ 96 | t = { cmd = "tab", desc = "tab" }, 97 | b = not utils.__HAS_NVIM_011 and { cmd = "b", desc = "buffer" } or nil, 98 | q = not utils.__HAS_NVIM_011 and { cmd = "c", desc = "quickfix" } or nil, 99 | l = not utils.__HAS_NVIM_011 and { cmd = "l", desc = "location" } or nil, 100 | }) do 101 | map("n", "[" .. k:lower(), "" .. v.cmd .. "previous", { desc = "Previous " .. v.desc }) 102 | map("n", "]" .. k:lower(), "" .. v.cmd .. "next", { desc = "Next " .. v.desc }) 103 | map("n", "[" .. k:upper(), "" .. v.cmd .. "first", { desc = "First " .. v.desc }) 104 | map("n", "]" .. k:upper(), "" .. v.cmd .. "last", { desc = "Last " .. v.desc }) 105 | end 106 | 107 | -- Tab split acts similar to tmux 108 | map({ "n", "v" }, "ts", [[tab split]], { desc = "tab split" }) 109 | map({ "n", "v" }, "tz", [[tab split]], { desc = "tab split" }) 110 | 111 | -- Quickfix|loclist toggles 112 | map("n", "q", "lua require'utils'.toggle_qf('q')", 113 | { desc = "toggle quickfix list" }) 114 | map("n", "Q", "lua require'utils'.toggle_qf('l')", 115 | { desc = "toggle location list" }) 116 | 117 | -- shortcut to view :messages 118 | map({ "n", "v" }, "m", "messages", { desc = "open :messages" }) 119 | map({ "n", "v" }, "M", [[mes clear|echo "cleared :messages"]], 120 | { desc = "clear :messages" }) 121 | 122 | -- v|s act as | 123 | -- p|P paste from yank register (0) 124 | map({ "n", "v" }, "v", [["+p]], { desc = "paste AFTER from clipboard" }) 125 | map({ "n", "v" }, "V", [["+P]], { desc = "paste BEFORE from clipboard" }) 126 | map({ "n", "v" }, "s", [["*p]], { desc = "paste AFTER from primary" }) 127 | map({ "n", "v" }, "S", [["*P]], { desc = "paste BEFORE from primary" }) 128 | map({ "n", "v" }, "p", [["0p]], { desc = "paste AFTER from yank (reg:0)" }) 129 | map({ "n", "v" }, "P", [["0P]], { desc = "paste BEFORE from yank (reg:0)" }) 130 | 131 | -- Overloads for 'd|c' that don't pollute the unnamed registers 132 | -- map("n", "D", [["_D]], { desc = "blackhole 'D'" }) 133 | -- map("n", "C", [["_C]], { desc = "blackhole 'C'" }) 134 | -- map({ "n", "v" }, "c", [["_c]], { desc = "blackhole 'c'" }) 135 | 136 | -- keep visual selection when (de)indenting 137 | map("v", "<", "", ">gv", {}) 139 | 140 | -- Move selected lines up/down in visual mode 141 | map("x", "K", ":move '<-2gv=gv", {}) 142 | map("x", "J", ":move '>+1gv=gv", {}) 143 | 144 | -- Select last pasted/yanked text 145 | map("n", "g", "`[v`]", { desc = "visual select last yank/paste" }) 146 | 147 | -- Keep matches center screen when cycling with n|N 148 | map("n", "n", "nzzzv", { desc = "Fwd search '/' or '?'" }) 149 | map("n", "N", "Nzzzv", { desc = "Back search '/' or '?'" }) 150 | 151 | -- any jump over 5 modifies the jumplist 152 | -- so we can use to jump back and forth 153 | for _, c in ipairs({ 154 | { "k", "Line up" }, 155 | { "j", "Line down" }, 156 | }) do 157 | map("n", c[1], ([[(v:count > 5 ? "m'" . v:count : "") . '%s']]):format(c[1]), 158 | { expr = true, silent = true, desc = c[2] }) 159 | end 160 | 161 | -- move along visual lines, not numbered ones 162 | -- without interferring with {count} 163 | for _, m in ipairs({ "n", "v" }) do 164 | for _, c in ipairs({ 165 | { "", "k", "Visual line up" }, 166 | { "", "j", "Visual line down" } 167 | }) do 168 | map(m, c[1], ([[v:count == 0 ? 'g%s' : '%s']]):format(c[2], c[2]), 169 | { expr = true, silent = true, desc = c[3] }) 170 | end 171 | end 172 | 173 | -- Search and Replace 174 | -- 'c.' for word, 'c>' for WORD 175 | -- 'c.' in visual mode for selection 176 | map("n", "c.", [[:%s/\<\>//g]], 177 | { desc = "search and replace word under cursor" }) 178 | map("n", "c>", [[:%s/\V//g]], 179 | { desc = "search and replace WORD under cursor" }) 180 | map("x", "c.", 181 | [[:%s/\V=luaeval("require'utils'.get_visual_selection(true)")//g]], {}) 182 | 183 | -- Turn off search matches with double- 184 | map("n", "", ":nohlsearch", { silent = true }) 185 | 186 | -- Toggle display of richer `listchars` 187 | map("n", "'", function() 188 | if not _G._listchars then 189 | _G._listchars = vim.o.listchars 190 | vim.opt.listchars = { 191 | tab = "→ ", 192 | eol = "↲", 193 | nbsp = "␣", 194 | lead = "␣", 195 | space = "␣", 196 | trail = "•", 197 | extends = "⟩", -- » 198 | precedes = "⟨", -- « 199 | } 200 | else 201 | vim.o.listchars = _G._listchars 202 | _G._listchars = nil 203 | end 204 | end, { silent = true, desc = "toggle rich 'listchars' on/off" }) 205 | 206 | -- Toggle colored column at 81 207 | map("n", "|", function() 208 | if tonumber(vim.wo.colorcolumn) then 209 | vim.g._colorcolumn = vim.wo.colorcolumn 210 | vim.wo.colorcolumn = "" 211 | else 212 | assert(tonumber(vim.g._colorcolumn)) 213 | vim.wo.colorcolumn = vim.g._colorcolumn 214 | end 215 | end, { silent = true, desc = "toggle color column on/off" }) 216 | 217 | -- Change current working dir (:pwd) to curent file's folder 218 | map("n", "%", [[:lua require"utils".set_cwd()]], 219 | { silent = true, desc = "smart set cwd (git|file parent)" }) 220 | 221 | -- Map o & O to newline without insert mode 222 | map("n", "o", 223 | [[:call append(line("."), repeat([""], v:count1))]], 224 | { silent = true, desc = "newline below (no insert-mode)" }) 225 | map("n", "O", 226 | [[:call append(line(".")-1, repeat([""], v:count1))]], 227 | { silent = true, desc = "newline above (no insert-mode)" }) 228 | -------------------------------------------------------------------------------- /lua/lazyplug.lua: -------------------------------------------------------------------------------- 1 | -- bootstrap lazy.nvim 2 | local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim" 3 | if not vim.uv.fs_stat(lazypath) then 4 | print("Downloading folke/lazy.nvim...") 5 | vim.fn.system({ 6 | "git", 7 | "clone", 8 | "--filter=blob:none", 9 | "--single-branch", 10 | "https://github.com/folke/lazy.nvim.git", 11 | lazypath, 12 | }) 13 | print("Succesfully downloaded lazy.nvim.") 14 | end 15 | vim.opt.runtimepath:prepend(lazypath) 16 | 17 | local ok, lazy = pcall(require, "lazy") 18 | if not ok then 19 | require "utils".error("Error downloading lazy.nvim") 20 | return 21 | end 22 | 23 | lazy.setup("plugins", { 24 | defaults = { lazy = true }, 25 | dev = { 26 | path = "~/Sources/nvim", 27 | -- this is a blanket dev for all matching plugins since it 28 | -- doesn't check for the existence of the directory we now 29 | -- use the 'dev' property individually instead 30 | -- patterns = { "ibhagwan" }, 31 | }, 32 | install = { colorscheme = { "default" } }, 33 | checker = { enabled = false }, -- don't auto-check for plugin updates 34 | change_detection = { enabled = false }, -- don't auto-check for config updates 35 | ui = { 36 | backdrop = 101, 37 | border = "rounded", 38 | custom_keys = { 39 | ["l"] = false, 40 | ["t"] = false, 41 | }, 42 | }, 43 | debug = false, 44 | }) 45 | -------------------------------------------------------------------------------- /lua/lib/jsonc.lua: -------------------------------------------------------------------------------- 1 | -- https://github.com/actboy168/json.lua 2 | -- slightly modified to only decode jsonc 3 | local type = type 4 | local next = next 5 | local error = error 6 | local tonumber = tonumber 7 | local string_char = string.char 8 | local string_byte = string.byte 9 | local string_find = string.find 10 | local string_match = string.match 11 | local string_gsub = string.gsub 12 | local string_sub = string.sub 13 | local string_format = string.format 14 | 15 | local utf8_char 16 | 17 | if _VERSION == "Lua 5.1" or _VERSION == "Lua 5.2" then 18 | local math_floor = math.floor 19 | function utf8_char(c) 20 | if c <= 0x7f then 21 | return string_char(c) 22 | elseif c <= 0x7ff then 23 | return string_char(math_floor(c / 64) + 192, c % 64 + 128) 24 | elseif c <= 0xffff then 25 | return string_char( 26 | math_floor(c / 4096) + 224, 27 | math_floor(c % 4096 / 64) + 128, 28 | c % 64 + 128 29 | ) 30 | elseif c <= 0x10ffff then 31 | return string_char( 32 | math_floor(c / 262144) + 240, 33 | math_floor(c % 262144 / 4096) + 128, 34 | math_floor(c % 4096 / 64) + 128, 35 | c % 64 + 128 36 | ) 37 | end 38 | error(string_format("invalid UTF-8 code '%x'", c)) 39 | end 40 | else 41 | utf8_char = utf8.char 42 | end 43 | 44 | local json = {} 45 | 46 | local objectMt = {} 47 | 48 | function json.createEmptyObject() 49 | return setmetatable({}, objectMt) 50 | end 51 | 52 | function json.isObject(t) 53 | if t[1] ~= nil then 54 | return false 55 | end 56 | return next(t) ~= nil or getmetatable(t) == objectMt 57 | end 58 | 59 | if debug and debug.upvalueid then 60 | -- Generate a lightuserdata 61 | json.null = debug.upvalueid(json.createEmptyObject, 1) 62 | else 63 | json.null = function() end 64 | end 65 | 66 | local encode_escape_map = { 67 | ["\""] = "\\\"", 68 | ["\\"] = "\\\\", 69 | ["/"] = "\\/", 70 | ["\b"] = "\\b", 71 | ["\f"] = "\\f", 72 | ["\n"] = "\\n", 73 | ["\r"] = "\\r", 74 | ["\t"] = "\\t", 75 | } 76 | 77 | local decode_escape_set = {} 78 | local decode_escape_map = {} 79 | for k, v in next, encode_escape_map do 80 | decode_escape_map[v] = k 81 | decode_escape_set[string_byte(v, 2)] = true 82 | end 83 | 84 | local statusBuf 85 | local statusPos 86 | local statusTop 87 | local statusAry = {} 88 | local statusRef = {} 89 | 90 | local function find_line() 91 | local line = 1 92 | local pos = 1 93 | while true do 94 | local f, _, nl1, nl2 = string_find(statusBuf, "([\n\r])([\n\r]?)", pos) 95 | if not f then 96 | return line, statusPos - pos + 1 97 | end 98 | local newpos = f + ((nl1 == nl2 or nl2 == "") and 1 or 2) 99 | if newpos > statusPos then 100 | return line, statusPos - pos + 1 101 | end 102 | pos = newpos 103 | line = line + 1 104 | end 105 | end 106 | 107 | local function decode_error(msg) 108 | error(string_format("ERROR: %s at line %d col %d", msg, find_line()), 2) 109 | end 110 | 111 | local function get_word() 112 | return string_match(statusBuf, "^[^ \t\r\n%]},]*", statusPos) 113 | end 114 | 115 | local function skip_comment(b) 116 | if b ~= 47 --[[ '/' ]] then 117 | return 118 | end 119 | local c = string_byte(statusBuf, statusPos + 1) 120 | if c == 42 --[[ '*' ]] then 121 | -- block comment 122 | local pos = string_find(statusBuf, "*/", statusPos) 123 | if pos then 124 | statusPos = pos + 2 125 | else 126 | statusPos = #statusBuf + 1 127 | end 128 | return true 129 | elseif c == 47 --[[ '/' ]] then 130 | -- line comment 131 | local pos = string_find(statusBuf, "[\r\n]", statusPos) 132 | if pos then 133 | statusPos = pos 134 | else 135 | statusPos = #statusBuf + 1 136 | end 137 | return true 138 | end 139 | end 140 | 141 | local function next_byte() 142 | local pos = string_find(statusBuf, "[^ \t\r\n]", statusPos) 143 | if pos then 144 | statusPos = pos 145 | local b = string_byte(statusBuf, pos) 146 | if not skip_comment(b) then 147 | return b 148 | end 149 | return next_byte() 150 | end 151 | return -1 152 | end 153 | 154 | local function decode_unicode_surrogate(s1, s2) 155 | return utf8_char(0x10000 + (tonumber(s1, 16) - 0xd800) * 0x400 + (tonumber(s2, 16) - 0xdc00)) 156 | end 157 | 158 | local function decode_unicode_escape(s) 159 | return utf8_char(tonumber(s, 16)) 160 | end 161 | 162 | local function decode_string() 163 | local has_unicode_escape = false 164 | local has_escape = false 165 | local i = statusPos + 1 166 | while true do 167 | i = string_find(statusBuf, '[%z\1-\31\\"]', i) 168 | if not i then 169 | decode_error "expected closing quote for string" 170 | end 171 | local x = string_byte(statusBuf, i) 172 | if x < 32 then 173 | statusPos = i 174 | decode_error "control character in string" 175 | end 176 | if x == 34 --[[ '"' ]] then 177 | local s = string_sub(statusBuf, statusPos + 1, i - 1) 178 | if has_unicode_escape then 179 | s = string_gsub(string_gsub(s 180 | , "\\u([dD][89aAbB]%x%x)\\u([dD][c-fC-F]%x%x)", decode_unicode_surrogate) 181 | , "\\u(%x%x%x%x)", decode_unicode_escape) 182 | end 183 | if has_escape then 184 | s = string_gsub(s, "\\.", decode_escape_map) 185 | end 186 | statusPos = i + 1 187 | return s 188 | end 189 | --assert(x == 92 --[[ "\\" ]]) 190 | local nx = string_byte(statusBuf, i + 1) 191 | if nx == 117 --[[ "u" ]] then 192 | if not string_match(statusBuf, "^%x%x%x%x", i + 2) then 193 | statusPos = i 194 | decode_error "invalid unicode escape in string" 195 | end 196 | has_unicode_escape = true 197 | i = i + 6 198 | else 199 | if not decode_escape_set[nx] then 200 | statusPos = i 201 | decode_error("invalid escape char '" .. (nx and string_char(nx) or "") .. "' in string") 202 | end 203 | has_escape = true 204 | i = i + 2 205 | end 206 | end 207 | end 208 | 209 | local function decode_number() 210 | local num, c = string_match(statusBuf, "^([0-9]+%.?[0-9]*)([eE]?)", statusPos) 211 | if not num or string_byte(num, -1) == 0x2E --[[ "." ]] then 212 | decode_error("invalid number '" .. get_word() .. "'") 213 | end 214 | if c ~= "" then 215 | num = string_match(statusBuf, "^([^eE]*[eE][-+]?[0-9]+)[ \t\r\n%]},/]", statusPos) 216 | if not num then 217 | decode_error("invalid number '" .. get_word() .. "'") 218 | end 219 | end 220 | statusPos = statusPos + #num 221 | return tonumber(num) 222 | end 223 | 224 | local function decode_number_zero() 225 | local num, c = string_match(statusBuf, "^(.%.?[0-9]*)([eE]?)", statusPos) 226 | if not num or string_byte(num, -1) == 0x2E --[[ "." ]] or string_match(statusBuf, "^.[0-9]+", statusPos) then 227 | decode_error("invalid number '" .. get_word() .. "'") 228 | end 229 | if c ~= "" then 230 | num = string_match(statusBuf, "^([^eE]*[eE][-+]?[0-9]+)[ \t\r\n%]},/]", statusPos) 231 | if not num then 232 | decode_error("invalid number '" .. get_word() .. "'") 233 | end 234 | end 235 | statusPos = statusPos + #num 236 | return tonumber(num) 237 | end 238 | 239 | local function decode_number_negative() 240 | statusPos = statusPos + 1 241 | local c = string_byte(statusBuf, statusPos) 242 | if c then 243 | if c == 0x30 then 244 | return -decode_number_zero() 245 | elseif c > 0x30 and c < 0x3A then 246 | return -decode_number() 247 | end 248 | end 249 | decode_error("invalid number '" .. get_word() .. "'") 250 | end 251 | 252 | local function decode_true() 253 | if string_sub(statusBuf, statusPos, statusPos + 3) ~= "true" then 254 | decode_error("invalid literal '" .. get_word() .. "'") 255 | end 256 | statusPos = statusPos + 4 257 | return true 258 | end 259 | 260 | local function decode_false() 261 | if string_sub(statusBuf, statusPos, statusPos + 4) ~= "false" then 262 | decode_error("invalid literal '" .. get_word() .. "'") 263 | end 264 | statusPos = statusPos + 5 265 | return false 266 | end 267 | 268 | local function decode_null() 269 | if string_sub(statusBuf, statusPos, statusPos + 3) ~= "null" then 270 | decode_error("invalid literal '" .. get_word() .. "'") 271 | end 272 | statusPos = statusPos + 4 273 | return json.null 274 | end 275 | 276 | local function decode_array() 277 | statusPos = statusPos + 1 278 | local res = {} 279 | local chr = next_byte() 280 | if chr == 93 --[[ ']' ]] then 281 | statusPos = statusPos + 1 282 | return res 283 | end 284 | statusTop = statusTop + 1 285 | statusAry[statusTop] = true 286 | statusRef[statusTop] = res 287 | return res 288 | end 289 | 290 | local function decode_object() 291 | statusPos = statusPos + 1 292 | local res = {} 293 | local chr = next_byte() 294 | if chr == 125 --[[ ']' ]] then 295 | statusPos = statusPos + 1 296 | return json.createEmptyObject() 297 | end 298 | statusTop = statusTop + 1 299 | statusAry[statusTop] = false 300 | statusRef[statusTop] = res 301 | return res 302 | end 303 | 304 | local decode_uncompleted_map = { 305 | [string_byte '"'] = decode_string, 306 | [string_byte "0"] = decode_number_zero, 307 | [string_byte "1"] = decode_number, 308 | [string_byte "2"] = decode_number, 309 | [string_byte "3"] = decode_number, 310 | [string_byte "4"] = decode_number, 311 | [string_byte "5"] = decode_number, 312 | [string_byte "6"] = decode_number, 313 | [string_byte "7"] = decode_number, 314 | [string_byte "8"] = decode_number, 315 | [string_byte "9"] = decode_number, 316 | [string_byte "-"] = decode_number_negative, 317 | [string_byte "t"] = decode_true, 318 | [string_byte "f"] = decode_false, 319 | [string_byte "n"] = decode_null, 320 | [string_byte "["] = decode_array, 321 | [string_byte "{"] = decode_object, 322 | } 323 | local function unexpected_character() 324 | decode_error("unexpected character '" .. string_sub(statusBuf, statusPos, statusPos) .. "'") 325 | end 326 | local function unexpected_eol() 327 | decode_error("unexpected character ''") 328 | end 329 | 330 | local decode_map = {} 331 | for i = 0, 255 do 332 | decode_map[i] = decode_uncompleted_map[i] or unexpected_character 333 | end 334 | decode_map[-1] = unexpected_eol 335 | 336 | local function decode() 337 | return decode_map[next_byte()]() 338 | end 339 | 340 | local function decode_item() 341 | local top = statusTop 342 | local ref = statusRef[top] 343 | if statusAry[top] then 344 | ref[#ref + 1] = decode() 345 | else 346 | local key = decode_string() 347 | if next_byte() ~= 58 --[[ ':' ]] then 348 | decode_error "expected ':'" 349 | end 350 | statusPos = statusPos + 1 351 | ref[key] = decode() 352 | end 353 | if top == statusTop then 354 | repeat 355 | local chr = next_byte() 356 | statusPos = statusPos + 1 357 | if chr == 44 --[[ "," ]] then 358 | local c = next_byte() 359 | if statusAry[statusTop] then 360 | if c ~= 93 --[[ "]" ]] then return end 361 | else 362 | if c ~= 125 --[[ "}" ]] then return end 363 | end 364 | statusPos = statusPos + 1 365 | else 366 | if statusAry[statusTop] then 367 | if chr ~= 93 --[[ "]" ]] then decode_error "expected ']' or ','" end 368 | else 369 | if chr ~= 125 --[[ "}" ]] then decode_error "expected '}' or ','" end 370 | end 371 | end 372 | statusTop = statusTop - 1 373 | until statusTop == 0 374 | end 375 | end 376 | 377 | function json.decode(str) 378 | if type(str) ~= "string" then 379 | error("expected argument of type string, got " .. type(str)) 380 | end 381 | statusBuf = str 382 | statusPos = 1 383 | statusTop = 0 384 | if next_byte() == -1 then 385 | return json.null 386 | end 387 | local res = decode() 388 | while statusTop > 0 do 389 | decode_item() 390 | end 391 | if string_find(statusBuf, "[^ \t\r\n]", statusPos) then 392 | decode_error "trailing garbage" 393 | end 394 | return res 395 | end 396 | 397 | return json 398 | -------------------------------------------------------------------------------- /lua/lsp/diag.lua: -------------------------------------------------------------------------------- 1 | -- Diag config 2 | vim.diagnostic.config({ 3 | underline = true, 4 | update_in_insert = false, 5 | -- virtual_lines = require("utils").__HAS_NVIM_011 and { current_line = true } or nil, 6 | virtual_text = { 7 | spacing = 4, 8 | source = "if_many", 9 | severity = { 10 | min = vim.diagnostic.severity.HINT, 11 | }, 12 | -- format = function(diagnostic) 13 | -- if diagnostic.severity == vim.diagnostic.severity.ERROR then 14 | -- return string.format('E: %s', diagnostic.message) 15 | -- end 16 | -- return ("%s"):format(diagnostic.message) 17 | -- end, 18 | }, 19 | signs = { 20 | -- nvim 0.10.0 uses `nvim_buf_set_extmark` 21 | text = { 22 | [vim.diagnostic.severity.ERROR] = "", -- index:0 23 | [vim.diagnostic.severity.WARN] = "", -- index:1 24 | [vim.diagnostic.severity.INFO] = "", -- index:2 25 | [vim.diagnostic.severity.HINT] = "󰌵", -- index:3 26 | }, 27 | }, 28 | severity_sort = true, 29 | float = { 30 | show_header = false, 31 | source = "if_many", 32 | border = "rounded", 33 | }, 34 | }) 35 | -------------------------------------------------------------------------------- /lua/lsp/icons.lua: -------------------------------------------------------------------------------- 1 | -- LSP icons 2 | local icons = { 3 | Text = "󰊄", --  4 | Method = "󰊕", 5 | Function = "", 6 | Constructor = "", --  7 | Field = "", -- 󰆧  8 | Variable = "󰆧", -- 󰆧  󰈜 9 | Class = "󰌗", --  󰠱  10 | Interface = "", --  󰜰 11 | Module = "󰅩", 12 | Property = "", 13 | Unit = "󰜫", -- 󰆧   󰑭 14 | Value = "󰎠", 15 | Enum = "󰘨", -- 󰘨  󰕘 16 | EnumMember = "", 17 | Keyword = "󰌆", -- 󰌋 18 | Snippet = "󰘍", --󰅱 󰈙 19 | Color = "󰏘", -- 󰌁 󰏘  20 | File = "", 21 | Folder = "", 22 | Reference = "󰆑", -- 󰀾 󰈇 23 | Constant = "󰏿", -- 󰝅 󰔆   󰐀 󰏿 π 24 | Struct = "󰙅", --  25 | Event = "", 26 | Operator = "󰒕", -- 󰆕 27 | TypeParameter = "", 28 | } 29 | 30 | for kind, symbol in pairs(icons) do 31 | local kinds = vim.lsp.protocol.CompletionItemKind 32 | local index = kinds[kind] 33 | 34 | if index ~= nil then 35 | kinds[index] = symbol 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /lua/lsp/keymaps.lua: -------------------------------------------------------------------------------- 1 | local utils = require("utils") 2 | 3 | local map = function(mode, lhs, rhs, opts) 4 | opts = vim.tbl_extend("keep", opts, { silent = true, buffer = 0 }) 5 | vim.keymap.set(mode, lhs, rhs, opts) 6 | end 7 | 8 | local setup = function() 9 | for _, k in ipairs({ "l?", "k" }) do 10 | map("n", k, function() 11 | vim.diagnostic.open_float({ buffer = 0, scope = "line", border = "rounded" }) 12 | end, { desc = "show line diagnostic [LSP]" }) 13 | end 14 | 15 | map("n", "lh", function() 16 | local enabled = not vim.lsp.inlay_hint.is_enabled({}) 17 | vim.lsp.inlay_hint.enable(enabled) 18 | utils.info(string.format("LSP inlay hints %s.", enabled and "enabled" or "disabled")) 19 | end, { desc = "toggle inlay hints [LSP]" }) 20 | 21 | map("n", "lv", function() 22 | if not vim.b._diag_is_hidden then 23 | utils.info("Diagnostic virtual text is now hidden.") 24 | vim.diagnostic.hide() 25 | else 26 | utils.info("Diagnostic virtual text is now visible.") 27 | vim.diagnostic.show() 28 | end 29 | vim.b._diag_is_hidden = not vim.b._diag_is_hidden 30 | end, { desc = "toggle virtual text [LSP]" }) 31 | 32 | local wk = package.loaded["which-key"] 33 | if wk then 34 | wk.add({ 35 | "lh", 36 | desc = function() 37 | local enabled = vim.lsp.inlay_hint.is_enabled({}) 38 | return string.format("%s inlay hints [LSP]", enabled and "hide" or "show") 39 | end, 40 | buffer = 0, 41 | nowait = false, 42 | remap = false 43 | }) 44 | wk.add({ 45 | "lv", 46 | desc = function() 47 | return string.format("%s virtual text [LSP]", vim.b._diag_is_hidden and "show" or "hide") 48 | end, 49 | buffer = 0, 50 | nowait = false, 51 | remap = false 52 | }) 53 | end 54 | end 55 | 56 | return { setup = setup } 57 | -------------------------------------------------------------------------------- /lua/options.lua: -------------------------------------------------------------------------------- 1 | local utils = require("utils") 2 | 3 | vim.o.mouse = "" -- disable the mouse 4 | vim.o.termguicolors = true -- enable 24bit colors 5 | 6 | vim.o.updatetime = 250 -- decrease update time for CursorHold 7 | vim.o.fileformat = "unix" -- for EOL 8 | vim.o.switchbuf = "useopen,uselast" -- jump to already open buffers on `:cn|:cp` 9 | vim.o.fileencoding = "utf-8" 10 | 11 | vim.opt.matchpairs:append("<:>") -- add "<>" to '%' 12 | 13 | -- recursive :find in current dir 14 | vim.cmd([[set path=.,,,$PWD/**]]) 15 | 16 | -- DO NOT NEED ANY OF THIS, CRUTCH THAT POULLUTES REGISTERS 17 | -- vim clipboard copies to system clipboard 18 | -- unnamed = use the * register (cmd-s paste in our term) 19 | -- unnamedplus = use the + register (cmd-v paste in our term) 20 | -- vim.o.clipboard = 'unnamedplus' 21 | 22 | -- This featire isn't fully baked yet, `wait:0` causes 23 | -- even the `:messaeges` output to not show up at all 24 | -- if utils.__HAS_NVIM_011 then 25 | -- vim.o.mopt = "wait:0,history:1000" 26 | -- end 27 | 28 | vim.o.cmdheight = 2 -- cmdline heirequire('gitsigns.config').config.baseght 29 | vim.o.cmdwinheight = math.floor(vim.o.lines / 2) -- 'q:' window height 30 | vim.o.scrolloff = 3 -- min number of lines to keep between cursor and screen edge 31 | vim.o.sidescrolloff = 5 -- min number of cols to keep between cursor and screen edge 32 | vim.o.textwidth = 99 -- max inserted text width for paste operations 33 | vim.o.number = true -- show absolute line nvim.o. at the cursor pos 34 | vim.o.relativenumber = true -- otherwise, show relative numbers in the ruler 35 | vim.o.cursorline = true -- Show a line where the current cursor is 36 | vim.o.signcolumn = "yes" -- Show sign column as first column 37 | vim.o.colorcolumn = "100" -- mark column 100 38 | vim.o.breakindent = true -- start wrapped lines indented 39 | vim.o.linebreak = true -- do not break words on line wrap 40 | 41 | -- Characters to display on ':set list',explore glyphs using: 42 | -- `xfd -fa "InputMonoNerdFont:style:Regular"` or 43 | -- `xfd -fn "-misc-fixed-medium-r-semicondensed-*-13-*-*-*-*-*-iso10646-1"` 44 | -- input special chars with the sequence followed by the hex code 45 | vim.opt.listchars = { 46 | tab = "▏ ", 47 | trail = "·", 48 | extends = "»", 49 | precedes = "«", 50 | } 51 | vim.o.list = true 52 | vim.o.showbreak = "↪ " 53 | 54 | vim.opt.diffopt:append("linematch:60") -- As suggested by `:help diffopt` 55 | 56 | -- show menu even for one item do not auto select/insert 57 | vim.opt.completeopt = { 58 | "noselect", 59 | "menu", 60 | "menuone", 61 | "popup", 62 | utils.__HAS_NVIM_011 and "fuzzy" or nil, 63 | } 64 | 65 | vim.o.pumheight = 10 -- completion menu max height 66 | 67 | vim.o.joinspaces = true -- insert spaces after '.?!' when joining lines 68 | vim.o.smartindent = true -- add depending on syntax (C/C++) 69 | 70 | vim.o.tabstop = 4 -- Tab indentation levels every two columns 71 | vim.o.shiftwidth = 0 -- Use `tabstop` value for auto-indent 72 | vim.o.shiftround = true -- Always indent/outdent to nearest tabstop 73 | vim.o.expandtab = true -- Convert all tabs that are typed into spaces 74 | 75 | vim.opt.formatoptions = vim.opt.formatoptions 76 | - "a" -- auto-formatting 77 | - "t" -- auto-wrap text using 'textwidth' 78 | + "c" -- auto-wrap comments using 'textwidth' 79 | + "q" -- allow formatting comments w/ `gq` 80 | - "o" -- auto-continue comments on pressing `o|O` 81 | + "r" -- auto-continue comments on pressing `enter` 82 | + "n" -- recognize 'formatlistpat' while formatting 83 | + "j" -- auto-remove comments when joining lines 84 | - "2" -- disable heuristics in paragraph formatting 85 | 86 | vim.o.splitbelow = true -- ':new' ':split' below current 87 | vim.o.splitright = true -- ':vnew' ':vsplit' right of current 88 | 89 | vim.o.foldenable = true -- enable folding 90 | vim.o.foldlevelstart = 99 -- open all folds by default 91 | vim.o.foldmethod = "expr" -- use treesitter for folding 92 | vim.o.foldexpr = "v:lua.vim.treesitter.foldexpr()" 93 | 94 | vim.o.undofile = false -- no undo file 95 | vim.o.hidden = true -- do not unload buffer when abandoned 96 | vim.o.confirm = true -- confirm before loss of data with `:q` 97 | 98 | vim.o.ignorecase = true -- ignore case on search 99 | vim.o.smartcase = true -- case sensitive when search includes uppercase 100 | vim.o.showmatch = true -- highlight matching [{()}] 101 | vim.o.cpoptions = vim.o.cpoptions .. "x" -- stay on search item when 102 | 103 | vim.o.writebackup = false -- do not backup file before write 104 | vim.o.swapfile = false -- no swap file 105 | 106 | --[[ 107 | ShDa (viminfo for vim): session data history 108 | -------------------------------------------- 109 | ! - Save and restore global variables (their names should be without lowercase letter). 110 | ' - Specify the maximum number of marked files remembered. It also saves the jump list and the change list. 111 | < - Maximum of lines saved for each register. All the lines are saved if this is not included, <0 to disable pessistent registers. 112 | % - Save and restore the buffer list. You can specify the maximum number of buffer stored with a number. 113 | / or : - Number of search patterns and entries from the command-line history saved. vim.o.history is used if it’s not specified. 114 | f - Store file (uppercase) marks, use 'f0' to disable. 115 | s - Specify the maximum size of an item’s content in KiB (kilobyte). 116 | For the viminfo file, it only applies to register. 117 | For the shada file, it applies to all items except for the buffer list and header. 118 | h - Disable the effect of 'hlsearch' when loading the shada file. 119 | 120 | :oldfiles - all files with a mark in the shada file 121 | :rshada - read the shada file (:rviminfo for vim) 122 | :wshada - write the shada file (:wrviminfo for vim) 123 | ]] 124 | vim.o.shada = [[!,'100,<0,s100,h]] 125 | vim.o.sessionoptions = "blank,buffers,curdir,folds,help,tabpages,winsize" 126 | 127 | -- use ':grep' to send resulsts to quickfix 128 | -- use ':lgrep' to send resulsts to loclist 129 | if vim.fn.executable("rg") == 1 then 130 | vim.o.grepprg = "rg --vimgrep --no-heading --smart-case --hidden" 131 | vim.o.grepformat = "%f:%l:%c:%m" 132 | end 133 | 134 | -- Disable providers we do not care a about 135 | vim.g.loaded_python_provider = 0 136 | vim.g.loaded_ruby_provider = 0 137 | vim.g.loaded_perl_provider = 0 138 | vim.g.loaded_node_provider = 0 139 | 140 | -- Disable some in built plugins completely 141 | local disabled_built_ins = { 142 | "netrw", 143 | "netrwPlugin", 144 | "netrwSettings", 145 | "netrwFileHandlers", 146 | "gzip", 147 | "zip", 148 | "zipPlugin", 149 | "tar", 150 | "tarPlugin", 151 | "getscript", 152 | "getscriptPlugin", 153 | "vimball", 154 | "vimballPlugin", 155 | "2html_plugin", 156 | "logipat", 157 | "rrhelper", 158 | "spellfile_plugin", 159 | -- 'matchit', 160 | -- 'matchparen', 161 | } 162 | -- disable default fzf plugin if not 163 | -- root since we will be using fzf-lua 164 | if utils.is_root() and vim.uv.fs_stat("/usr/share/nvim/runtime/plugin/fzf.vim") then 165 | vim.opt.runtimepath:append("/usr/share/nvim/runtime") 166 | else 167 | -- table.insert(disabled_built_ins, "fzf") 168 | end 169 | for _, plugin in pairs(disabled_built_ins) do 170 | vim.g["loaded_" .. plugin] = 1 171 | end 172 | 173 | vim.g.markdown_fenced_languages = { 174 | "vim", 175 | "lua", 176 | "cpp", 177 | "sql", 178 | "python", 179 | "bash=sh", 180 | "console=sh", 181 | "javascript", 182 | "typescript", 183 | "js=javascript", 184 | "ts=typescript", 185 | "yaml", 186 | "json", 187 | } 188 | 189 | -- Map leader to 190 | vim.g.mapleader = " " 191 | vim.g.maplocalleader = " " 192 | 193 | -- MacOS clipboard 194 | if utils.is_darwin() then 195 | vim.g.clipboard = { 196 | name = "macOS-clipboard", 197 | copy = { 198 | ["+"] = "pbcopy", 199 | ["*"] = "pbcopy", 200 | }, 201 | paste = { 202 | ["+"] = "pbpaste", 203 | ["*"] = "pbpaste", 204 | }, 205 | } 206 | end 207 | 208 | -- OSC52 clipboard over ssh 209 | if vim.env.SSH_TTY then 210 | vim.g.clipboard = { 211 | name = "OSC 52", 212 | copy = { 213 | ["+"] = require("vim.ui.clipboard.osc52").copy("+"), 214 | ["*"] = require("vim.ui.clipboard.osc52").copy("*"), 215 | }, 216 | paste = { 217 | ["+"] = require("vim.ui.clipboard.osc52").paste("+"), 218 | ["*"] = require("vim.ui.clipboard.osc52").paste("*"), 219 | }, 220 | } 221 | end 222 | -------------------------------------------------------------------------------- /lua/plugins/blink.lua: -------------------------------------------------------------------------------- 1 | local M = { 2 | "saghen/blink.cmp", 3 | enabled = require("utils").USE_BLINK_CMP, 4 | build = "cargo +nightly build --release", 5 | event = { "InsertEnter", "CmdLineEnter" }, 6 | opts = { 7 | sources = { 8 | default = { "lazydev", "lsp", "path", "snippets", "buffer" }, 9 | providers = { 10 | lazydev = { 11 | name = "LazyDev", 12 | module = "lazydev.integrations.blink", 13 | -- make lazydev completions top priority (see `:h blink.cmp`) 14 | score_offset = 100, 15 | }, 16 | }, 17 | }, 18 | keymap = { 19 | [""] = { "accept", "fallback" }, 20 | -- [""] = { "hide", "fallback" }, 21 | -- [""] = { "cancel", "fallback" }, 22 | [""] = { "select_prev", "fallback" }, 23 | [""] = { "select_next", "fallback" }, 24 | [""] = { "cancel", "show", "fallback" }, 25 | [""] = { "select_prev", "fallback" }, 26 | [""] = { "select_next", "fallback" }, 27 | [""] = { "select_and_accept" }, 28 | [""] = { "show", "show_documentation", "hide_documentation" }, 29 | [""] = { "select_next", "snippet_forward", "fallback" }, 30 | [""] = { "select_prev", "snippet_backward", "fallback" }, 31 | [""] = { "scroll_documentation_up", "fallback" }, 32 | [""] = { "scroll_documentation_down", "fallback" }, 33 | }, 34 | cmdline = { 35 | enabled = true, 36 | completion = { 37 | menu = { auto_show = true }, 38 | list = { selection = { preselect = false, auto_insert = true } }, 39 | }, 40 | keymap = { 41 | [""] = { "accept", "fallback" }, 42 | [""] = { "hide", "fallback" }, 43 | [""] = { "select_next", "fallback" }, 44 | [""] = { "select_prev", "fallback" }, 45 | [""] = { "cancel", "fallback" }, 46 | [""] = { "select_and_accept" }, 47 | } 48 | }, 49 | completion = { 50 | list = { selection = { preselect = false, auto_insert = true } }, 51 | trigger = { show_in_snippet = false }, 52 | accept = { 53 | create_undo_point = true, 54 | auto_brackets = { enabled = true }, 55 | }, 56 | menu = { 57 | draw = { 58 | treesitter = { "lsp" }, 59 | columns = function(ctx) 60 | local ret = { { "kind_icon" }, { "label", "label_description", gap = 1 } } -- default 61 | -- Add kind, source to INSERT mode 62 | if ctx.mode ~= "cmdline" then 63 | table.insert(ret, { "kind", "source_name", gap = 1 }) 64 | end 65 | return ret 66 | end, 67 | } 68 | }, 69 | documentation = { 70 | auto_show = true, 71 | auto_show_delay_ms = 100, 72 | }, 73 | ghost_text = { enabled = true }, 74 | }, 75 | signature = { enabled = true } 76 | }, 77 | } 78 | 79 | return M 80 | -------------------------------------------------------------------------------- /lua/plugins/cmp.lua: -------------------------------------------------------------------------------- 1 | local M = { 2 | "hrsh7th/nvim-cmp", 3 | enabled = not require("utils").USE_BLINK_CMP, 4 | event = { "InsertEnter", "CmdLineEnter" }, 5 | dependencies = { 6 | "hrsh7th/cmp-path", 7 | "hrsh7th/cmp-buffer", 8 | "hrsh7th/cmp-cmdline", 9 | "hrsh7th/cmp-nvim-lsp", 10 | "hrsh7th/cmp-nvim-lua", 11 | }, 12 | } 13 | 14 | local winopts = { 15 | -- NOT REQUIRED 16 | -- Set left|right border chars to invisible spaces for scollbar 17 | -- border = { "", "", "", "\xc2\xa0", "", "", "", "\xc2\xa0" }, 18 | winhighlight = "Normal:Pmenu,FloatBorder:Pmenu,CursorLine:PmenuSel,Search:None", 19 | } 20 | 21 | M.config = function() 22 | local cmp = require("cmp") 23 | 24 | cmp.setup { 25 | snippet = { 26 | -- must use a snippet engine 27 | expand = function(args) 28 | vim.snippet.expand(args.body) 29 | end, 30 | }, 31 | 32 | window = { 33 | completion = winopts, 34 | documentation = winopts, 35 | -- completion = cmp.config.window.bordered(), 36 | -- documentation = cmp.config.window.bordered(), 37 | }, 38 | 39 | completion = { 40 | -- start completion immediately 41 | keyword_length = 1, 42 | }, 43 | 44 | sources = { 45 | { name = "nvim_lsp" }, 46 | { name = "nvim_lua" }, 47 | { name = "path" }, 48 | { name = "buffer" }, 49 | { 50 | name = "lazydev", 51 | group_index = 0, -- set group index to 0 to skip loading LuaLS completions 52 | }, 53 | }, 54 | 55 | ---@diagnostic disable-next-line: missing-fields 56 | view = { entries = { follow_cursor = true } }, 57 | 58 | -- we use 'comleteopt=...,noselect' but we still want cmp to autoselect 59 | -- an item if recommended by the LSP server (try with gopls, rust_analyzer) 60 | -- uncomment to disable 61 | -- preselect = cmp.PreselectMode.None, 62 | 63 | mapping = { 64 | [""] = cmp.mapping(cmp.mapping.select_prev_item(), { "i" }), 65 | [""] = cmp.mapping(cmp.mapping.select_next_item(), { "i" }), 66 | [""] = cmp.mapping(cmp.mapping.select_prev_item(), { "i", "c" }), 67 | [""] = cmp.mapping(cmp.mapping.select_next_item(), { "i", "c" }), 68 | [""] = cmp.mapping(cmp.mapping.select_prev_item(), { "i", "c" }), 69 | [""] = cmp.mapping(cmp.mapping.select_next_item(), { "i", "c" }), 70 | [""] = cmp.mapping(cmp.mapping.select_prev_item(), { "i", "c" }), 71 | [""] = cmp.mapping(cmp.mapping.select_next_item(), { "i", "c" }), 72 | [""] = cmp.mapping(cmp.mapping.scroll_docs(-4), { "i", "c" }), 73 | [""] = cmp.mapping(cmp.mapping.scroll_docs(4), { "i", "c" }), 74 | [""] = cmp.mapping(cmp.mapping.complete(), { "i" }), 75 | [""] = cmp.mapping( 76 | cmp.mapping.select_prev_item({ behavior = cmp.SelectBehavior.Select, count = 20 }), 77 | { "i", "c" } 78 | ), 79 | [""] = cmp.mapping( 80 | cmp.mapping.select_next_item({ behavior = cmp.SelectBehavior.Select, count = 20 }), 81 | { "i", "c" } 82 | ), 83 | [""] = cmp.mapping({ 84 | i = cmp.mapping.abort(), 85 | c = cmp.mapping.close(), 86 | }), 87 | [""] = cmp.mapping.confirm({ select = true, behavior = cmp.ConfirmBehavior.Replace }), 88 | -- [''] = cmp.mapping.confirm({ select = false, behavior = cmp.ConfirmBehavior.Insert }) 89 | -- close the cmp interface if no item is selected, I find it more 90 | -- intuitive when using LSP autoselect (instead of sending ) 91 | [""] = cmp.mapping(function(fallback) 92 | if cmp.visible() then 93 | if cmp.get_selected_entry() then 94 | cmp.confirm({ select = false, cmp.ConfirmBehavior.Insert }) 95 | else 96 | cmp.close() 97 | end 98 | else 99 | fallback() 100 | end 101 | end), 102 | }, 103 | 104 | formatting = { 105 | expandable_indicator = true, 106 | fields = { "kind", "abbr", "menu" }, 107 | format = function(entry, vim_item) 108 | local source_names = { 109 | path = "Path", 110 | buffer = "Buffer", 111 | cmdline = "Cmdline", 112 | nvim_lua = "Lua", 113 | nvim_lsp = "LSP", 114 | lazydev = "LazyDev", 115 | } 116 | 117 | vim_item.menu = ("%-10s [%s]"):format( 118 | vim_item.kind, 119 | source_names[entry.source.name] or entry.source.name) 120 | 121 | -- get the item kind icon from our LSP settings 122 | local kind_idx = vim.lsp.protocol.CompletionItemKind[vim_item.kind] 123 | if tonumber(kind_idx) > 0 then 124 | vim_item.kind = vim.lsp.protocol.CompletionItemKind[kind_idx] 125 | end 126 | 127 | -- set max width of the LSP item or we can't see the docs 128 | local max_width = math.floor(vim.o.columns * 0.40) 129 | vim_item.abbr = vim_item.abbr:sub(1, max_width) 130 | 131 | return vim_item 132 | end, 133 | }, 134 | 135 | -- DO NOT ENABLE 136 | -- just for testing with nvim native completion menu 137 | experimental = { 138 | native_menu = false, 139 | ghost_text = true, 140 | }, 141 | } 142 | 143 | -- Use buffer source for `/` (if you enabled `native_menu`, this won't work anymore). 144 | cmp.setup.cmdline("/", { 145 | sources = { 146 | { name = "buffer" } 147 | } 148 | }) 149 | 150 | -- Use cmdline & path source for ':' (if you enabled `native_menu`, this won't work anymore). 151 | cmp.setup.cmdline(":", { 152 | sources = cmp.config.sources({ 153 | { name = "path" } 154 | }, { 155 | { name = "cmdline" } 156 | }) 157 | }) 158 | end 159 | 160 | return M 161 | -------------------------------------------------------------------------------- /lua/plugins/conform.lua: -------------------------------------------------------------------------------- 1 | return { 2 | "stevearc/conform.nvim", 3 | enabled = vim.fn.has("nvim-0.10") == 1, 4 | event = "BufReadPost", 5 | config = function() 6 | require("conform").setup({ 7 | formatters_by_ft = { 8 | lua = { "stylua" }, 9 | python = { "black" }, 10 | css = { "prettier", "prettierd", stop_after_first = true }, 11 | html = { "prettier", "prettierd", stop_after_first = true }, 12 | yaml = { "prettier", "prettierd", stop_after_first = true }, 13 | jsonc = { "prettier", "prettierd", stop_after_first = true }, 14 | javascript = { "prettier", "prettierd", stop_after_first = true }, 15 | }, 16 | }) 17 | end, 18 | _set_gq_keymap = function(e) 19 | -- priortize LSP formatting as `gq` 20 | local lsp_has_formatting = false 21 | local lsp_clients = require("utils").lsp_get_clients({ bufnr = e.buf }) 22 | local lsp_keymap_set = function(m, c) 23 | vim.keymap.set(m, "gq", function() 24 | vim.lsp.buf.format({ async = true, bufnr = e.buf }) 25 | end, { 26 | silent = true, 27 | buffer = e.buf, 28 | desc = string.format("format document [LSP:%s]", c.name) 29 | }) 30 | end 31 | vim.tbl_map(function(c) 32 | if c:supports_method("textDocument/rangeFormatting", { bufnr = e.buf }) then 33 | lsp_keymap_set("x", c) 34 | lsp_has_formatting = true 35 | end 36 | if c:supports_method("textDocument/formatting", { bufnr = e.buf }) then 37 | lsp_keymap_set("n", c) 38 | lsp_has_formatting = true 39 | end 40 | end, lsp_clients) 41 | -- check conform.nvim for formatters: 42 | -- (1) if we have no LSP formatter map as `gq` 43 | -- (2) if LSP formatter exists, map as `gQ` 44 | local ok, conform = pcall(require, "conform") 45 | local formatters = ok and conform.list_formatters(e.buf) or {} 46 | if #formatters > 0 then 47 | vim.keymap.set("n", lsp_has_formatting and "gQ" or "gq", function() 48 | require("conform").format({ async = true, buffer = e.buf, lsp_fallback = false }) 49 | end, { 50 | silent = true, 51 | buffer = e.buf, 52 | desc = string.format("format document [%s]", formatters[1].name) 53 | }) 54 | end 55 | end 56 | } 57 | -------------------------------------------------------------------------------- /lua/plugins/dap/cpp_rust.lua: -------------------------------------------------------------------------------- 1 | local res, dap = pcall(require, "dap") 2 | if not res then 3 | return 4 | end 5 | 6 | local utils = require("utils") 7 | 8 | dap.adapters.gdb = { 9 | type = "executable", 10 | command = "gdb", 11 | args = { "-i", "dap" } 12 | } 13 | 14 | -- https://github.com/mfussenegger/nvim-dap/wiki/Debug-Adapter-installation#ccrust-via-lldb-vscode 15 | dap.adapters.lldb = { 16 | type = "executable", 17 | command = "/usr/bin/lldb-vscode", 18 | name = "lldb" 19 | } 20 | 21 | -- https://github.com/mfussenegger/nvim-dap/wiki/C-C---Rust-(gdb-via--vscode-cpptools) 22 | dap.adapters.cppdbg = { 23 | id = "cppdbg", 24 | type = "executable", 25 | command = vim.fn.stdpath("data") .. "/mason/bin/OpenDebugAD7", 26 | } 27 | 28 | dap.configurations.c = { 29 | { 30 | name = "[CPPDBG] Launch Executable", 31 | type = "cppdbg", 32 | request = "launch", 33 | program = utils.dap_pick_exec, 34 | cwd = "${workspaceFolder}", 35 | stopAtEntry = false, 36 | }, 37 | { 38 | name = "[CPPDBG] Launch Executable (External console)", 39 | type = "cppdbg", 40 | request = "launch", 41 | program = utils.dap_pick_exec, 42 | cwd = "${workspaceFolder}", 43 | stopAtEntry = false, 44 | externalConsole = true, 45 | }, 46 | { 47 | name = "[CPPDBG] Attach to process", 48 | type = "cppdbg", 49 | request = "attach", 50 | processId = utils.dap_pick_process, 51 | program = utils.dap_pick_exec, 52 | args = {}, 53 | }, 54 | { 55 | name = "[CPPDBG] Launch Neovim (Development build)", 56 | type = "cppdbg", 57 | request = "launch", 58 | program = function() 59 | local nvim_bin = vim.fn.expand("$HOME/Sources/nvim/neovim/build/bin/nvim") 60 | if not vim.uv.fs_stat(nvim_bin) then 61 | utils.warn(string.format("'%s' is not executable, aborting.", nvim_bin)) 62 | return dap.ABORT 63 | end 64 | -- The idea for the below is taken from: 65 | -- https://zignar.net/2023/02/17/debugging-neovim-with-neovim-and-nvim-dap/ 66 | -- Neovim sepaprated the TUI from the main process, launching neovim in fact 67 | -- spawns another process `nvim --embed`, if we want to debug nvim itself we 68 | -- need to attach to the subprocess, we can do so by adding a oneshot listener 69 | local key = "nvim-debug-subprocess" 70 | dap.listeners.after.initialize[key] = function(session) 71 | -- This is a oneshot listener, clear immediately 72 | dap.listeners.after.initialize[key] = nil 73 | -- Ensure our listeners are cleaned up after close 74 | session.on_close[key] = function() 75 | for _, handler in pairs(dap.listeners.after) do 76 | handler[key] = nil 77 | end 78 | end 79 | end 80 | -- Listen to event `process` to get the pid 81 | dap.listeners.after.event_process[key] = function(_, body) 82 | dap.listeners.after.event_process[key] = nil 83 | -- Wait for the child pid for 1 second and if valid launch 2nd "attach" session 84 | -- this event also gets called a second time for the child process but the pid 85 | -- will be nil the second time and will therefore do nothing 86 | local ppid = body.systemProcessId 87 | utils.info(string.format("Launched nvim process (ppid=%s)", ppid)) 88 | vim.wait(1000, function() 89 | return tonumber(vim.fn.system("ps -o pid= --ppid " .. tostring(ppid))) ~= nil 90 | end) 91 | local pid = tonumber(vim.fn.system("ps -o pid= --ppid " .. tostring(ppid))) 92 | utils.info( 93 | string.format("Attaching to nvim (ppid=%s) child process (pid=%s)", ppid, pid)) 94 | if pid then 95 | dap.run({ 96 | name = "Neovim embedded", 97 | type = "cppdbg", 98 | request = "attach", 99 | processId = pid, 100 | program = vim.fn.expand("$HOME/Sources/nvim/neovim/build/bin/nvim"), 101 | cwd = "${workspaceFolder}", 102 | externalConsole = false, 103 | }) 104 | end 105 | end 106 | return nvim_bin 107 | end, 108 | environment = function() 109 | -- https://code.visualstudio.com/docs/cpp/launch-json-reference 110 | return { 111 | -- Neovim needs it's source directory `runtimepath` 112 | { name = "VIMRUNTIME", value = vim.fn.expand("$HOME/Sources/nvim/neovim/runtime") } 113 | } 114 | end, 115 | cwd = "${workspaceFolder}", 116 | stopAtEntry = false, 117 | externalConsole = true, 118 | }, 119 | { 120 | name = "[CPPDBG] Attach to Neovim (Development build)", 121 | type = "cppdbg", 122 | request = "attach", 123 | program = function() 124 | local nvim_bin = vim.fn.expand("$HOME/Sources/nvim/neovim/build/bin/nvim") 125 | if not vim.uv.fs_stat(nvim_bin) then 126 | utils.warn(string.format("'%s' is not executable, aborting.", nvim_bin)) 127 | return dap.ABORT 128 | end 129 | return nvim_bin 130 | end, 131 | processId = function() 132 | -- attach to the `nvim --embed` process 133 | return utils.dap_pick_process( 134 | { winopts = { height = 0.30 } }, 135 | { filter = function(proc) return proc.name:match("nvim.*%-%-embed") end }) 136 | end, 137 | cwd = "${workspaceFolder}", 138 | stopAtEntry = false, 139 | }, 140 | { 141 | name = "[LLDB] Launch Executable", 142 | type = "lldb", 143 | request = "launch", 144 | program = utils.dap_pick_exec, 145 | cwd = "${workspaceFolder}", 146 | stopOnEntry = false, 147 | args = {}, 148 | }, 149 | { 150 | name = "[LLDB] Attach to process", 151 | type = "lldb", 152 | request = "attach", 153 | pid = utils.dap_pick_process, 154 | args = {}, 155 | }, 156 | { 157 | name = "[GDB] Launch Executable", 158 | type = "gdb", 159 | request = "launch", 160 | program = utils.dap_pick_exec, 161 | -- program = function() 162 | -- local bin 163 | -- vim.ui.input({ prompt = "Path to executable: " }, 164 | -- function(input) 165 | -- bin = vim.fn.expand(input) 166 | -- end) 167 | -- if type(bin) == "string" and vim.uv.fs_stat(bin) then 168 | -- return bin 169 | -- else 170 | -- -- ctrl-c'ing `vin.ui.input` returns "v:null" 171 | -- if bin ~= "v:null" and bin ~= "" then 172 | -- utils.warn(string.format("'%s' is not executable, aborting.", bin)) 173 | -- end 174 | -- return dap.ABORT 175 | -- end 176 | -- end, 177 | cwd = "${workspaceFolder}", 178 | stopAtBeginningOfMainSubprogram = false, 179 | }, 180 | { 181 | name = "[GDB] Attach to process", 182 | type = "gdb", 183 | request = "attach", 184 | pid = utils.dap_pick_process, 185 | args = {}, 186 | }, 187 | } 188 | 189 | dap.configurations.cpp = dap.configurations.c 190 | dap.configurations.rust = vim.deepcopy(dap.configurations.c) 191 | 192 | -- https://github.com/mfussenegger/nvim-dap/wiki/Debug-Adapter-installation#ccrust-via-lldb-vscode 193 | local build_env = function() 194 | local variables = {} 195 | for k, v in pairs(vim.fn.environ()) do 196 | table.insert(variables, string.format("%s=%s", k, v)) 197 | end 198 | return variables 199 | end 200 | 201 | local rustInitCommands = function() 202 | -- Find out where to look for the pretty printer Python module 203 | local rustc_sysroot = vim.fn.trim(vim.fn.system("rustc --print sysroot")) 204 | local script_import = 'command script import "' .. 205 | rustc_sysroot .. '/lib/rustlib/etc/lldb_lookup.py"' 206 | local commands_file = rustc_sysroot .. "/lib/rustlib/etc/lldb_commands" 207 | 208 | local commands = {} 209 | local file = io.open(commands_file, "r") 210 | if file then 211 | for line in file:lines() do 212 | table.insert(commands, line) 213 | end 214 | file:close() 215 | end 216 | table.insert(commands, 1, script_import) 217 | 218 | return commands 219 | end 220 | 221 | for i, c in ipairs(dap.configurations.rust) do 222 | if c.type == "lldb" then 223 | dap.configurations.rust[i] = vim.tbl_extend("force", dap.configurations.rust[i], { 224 | env = build_env, 225 | initCommands = rustInitCommands, 226 | }) 227 | end 228 | end 229 | -------------------------------------------------------------------------------- /lua/plugins/dap/go.lua: -------------------------------------------------------------------------------- 1 | local res, dap = pcall(require, "dap") 2 | if not res then 3 | return 4 | end 5 | 6 | dap.adapters.go = function(callback, config) 7 | local stdout = vim.uv.new_pipe(false) 8 | local handle 9 | local pid_or_err 10 | local host = config.host or "127.0.0.1" 11 | local port = config.port or "38697" 12 | local addr = string.format("%s:%s", host, port) 13 | local opts = { 14 | stdio = { nil, stdout }, 15 | args = { "dap", "-l", addr }, 16 | detached = true 17 | } 18 | handle, pid_or_err = vim.uv.spawn("dlv", opts, function(code) 19 | stdout:close() 20 | handle:close() 21 | if code ~= 0 then 22 | print("dlv exited with code", code) 23 | end 24 | end) 25 | assert(handle, "Error running dlv: " .. tostring(pid_or_err)) 26 | stdout:read_start(function(err, chunk) 27 | assert(not err, err) 28 | if chunk then 29 | vim.schedule(function() 30 | require("dap.repl").append(chunk) 31 | end) 32 | end 33 | end) 34 | -- Wait for delve to start 35 | vim.defer_fn( 36 | function() 37 | callback({ type = "server", host = "127.0.0.1", port = port }) 38 | end, 39 | 100) 40 | end 41 | 42 | dap.configurations.go = { 43 | { 44 | type = "go", 45 | name = "Debug", 46 | request = "launch", 47 | program = "${file}", 48 | }, 49 | { 50 | type = "go", 51 | name = "Debug Package", 52 | request = "launch", 53 | program = "${fileDirname}", 54 | }, 55 | { 56 | type = "go", 57 | name = "Attach", 58 | mode = "local", 59 | request = "attach", 60 | processId = require("dap.utils").pick_process, 61 | }, 62 | { 63 | type = "go", 64 | name = "Debug test", 65 | request = "launch", 66 | mode = "test", 67 | program = "${file}", 68 | }, 69 | { 70 | type = "go", 71 | name = "Debug test (go.mod)", 72 | request = "launch", 73 | mode = "test", 74 | program = "./${relativeFileDirname}", 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /lua/plugins/dap/init.lua: -------------------------------------------------------------------------------- 1 | local M = { 2 | "mfussenegger/nvim-dap", 3 | dependencies = { 4 | { 5 | "rcarriga/nvim-dap-ui", 6 | dependencies = { "nvim-neotest/nvim-nio" }, 7 | }, 8 | { "theHamsta/nvim-dap-virtual-text" }, 9 | { "jbyuki/one-small-step-for-vimkind" }, 10 | { "mfussenegger/nvim-dap-python" }, 11 | }, 12 | } 13 | 14 | local utils = require "utils" 15 | local BP_DB_PATH = vim.fn.stdpath("data") .. "/dap_bps.json" 16 | 17 | M._load_bps = function() 18 | local fp = io.open(BP_DB_PATH, "r") 19 | if not fp then 20 | utils.info("No breakpoint json-db present.") 21 | return 22 | end 23 | local json = fp:read("*a") 24 | local ok, bps = pcall(vim.json.decode, json) 25 | if not ok or type(bps) ~= "table" then 26 | utils.warn(string.format("Error parsing breakpoint json-db: %s", bps)) 27 | return 28 | end 29 | local path2bufnr = {} 30 | for _, bufnr in ipairs(vim.api.nvim_list_bufs()) do 31 | local path = vim.api.nvim_buf_get_name(bufnr) 32 | if type(bps[path]) == "table" and not vim.tbl_isempty(bps[path]) then 33 | path2bufnr[path] = bufnr 34 | end 35 | end 36 | -- no breakpoints in current buflist 37 | if vim.tbl_isempty(path2bufnr) then return end 38 | local bp_count = 0 39 | for path, buf_bps in pairs(bps) do 40 | local bufnr = tonumber(path2bufnr[path]) 41 | if bufnr then 42 | for _, bp in pairs(buf_bps) do 43 | bp_count = bp_count + 1 44 | local line = bp.line 45 | local opts = { 46 | condition = bp.condition, 47 | log_message = bp.logMessage, 48 | hit_condition = bp.hitCondition, 49 | } 50 | require("dap.breakpoints").set(opts, bufnr, line) 51 | end 52 | end 53 | end 54 | -- Load bps into active session (not just the UI) 55 | local session = require("dap").session() 56 | if session and bp_count > 0 then 57 | session:set_breakpoints(require("dap.breakpoints").get()) 58 | end 59 | utils.info(string.format("Loaded %d breakpoints in %d bufers.", 60 | bp_count, vim.tbl_count(path2bufnr))) 61 | end 62 | 63 | M._store_bps = function() 64 | local fp = io.open(BP_DB_PATH, "r") 65 | local json = fp and fp:read("*a") or "{}" 66 | local ok, bps = pcall(vim.json.decode, json) 67 | if not ok or type(bps) ~= "table" then 68 | bps = {} 69 | end 70 | local bp_count = 0 71 | local breakpoints_by_buf = require("dap.breakpoints").get() 72 | for bufnr, buf_bps in pairs(breakpoints_by_buf) do 73 | bp_count = bp_count + #buf_bps 74 | bps[vim.api.nvim_buf_get_name(bufnr)] = buf_bps 75 | end 76 | -- If buffer has no breakpoints, remove from the db 77 | for _, bufnr in ipairs(vim.api.nvim_list_bufs()) do 78 | if not breakpoints_by_buf[bufnr] then 79 | local path = vim.api.nvim_buf_get_name(bufnr) 80 | bps[path] = nil 81 | end 82 | end 83 | fp = io.open(BP_DB_PATH, "w") 84 | if fp then 85 | fp:write(vim.json.encode(bps)) 86 | fp:close() 87 | utils.info(string.format("Stored %d breakpoints in %d bufers.", 88 | bp_count, vim.tbl_count(breakpoints_by_buf))) 89 | end 90 | end 91 | 92 | M.init = function() 93 | vim.keymap.set({ "n", "v" }, 94 | "", 95 | function() require "dap".continue() end, 96 | { silent = true, desc = "DAP launch or continue" }) 97 | vim.keymap.set({ "n", "v" }, 98 | -- ``, test by pressing `` in INSERT mode 99 | "", 100 | function() require "osv".launch({ port = 8086 }) end, 101 | { silent = true, desc = "Start OSV Lua Debug Server" }) 102 | vim.keymap.set({ "n", "v" }, 103 | "", 104 | function() 105 | -- lazy load nvim-dap so `dapui.setup()` is called 106 | require "dap"; require "plugins.dap.ui".toggle() 107 | end, 108 | { silent = true, desc = "DAP toggle UI" }) 109 | vim.keymap.set({ "n", "v" }, 110 | -- Test by pressing `` in INSERT mode 111 | -- "", 112 | "", 113 | function() 114 | require "dap"; require "plugins.dap.ui".toggle(true, true) 115 | end, 116 | { silent = true, desc = "DAP toggle UI" }) 117 | vim.keymap.set({ "n", "v" }, 118 | "", 119 | function() require "dap".toggle_breakpoint() end, 120 | { silent = true, desc = "DAP toggle breakpoint" }) 121 | vim.keymap.set({ "n", "v" }, 122 | "", 123 | function() require "dap".step_over() end, 124 | { silent = true, desc = "DAP step over" }) 125 | vim.keymap.set({ "n", "v" }, 126 | "", 127 | function() require "dap".step_into() end, 128 | { silent = true, desc = "DAP step into" }) 129 | vim.keymap.set({ "n", "v" }, 130 | "", 131 | function() require "dap".step_out() end, 132 | { silent = true, desc = "DAP step out" }) 133 | vim.keymap.set({ "n", "v" }, 134 | "", 135 | function() require "dap".terminate() end, 136 | { silent = true, desc = "DAP Terminate" }) 137 | vim.keymap.set({ "n", "v" }, 138 | "dt", 139 | function() require "dap".terminate() end, 140 | { silent = true, desc = "DAP terminate" }) 141 | 142 | -- Conditional breakpoints 143 | vim.keymap.set({ "n", "v" }, 144 | "dc", 145 | function() require "dap".set_breakpoint(utils.input("Breakpoint condition: ")) end, 146 | { silent = true, desc = "DAP: set breakpoint with condition" }) 147 | vim.keymap.set({ "n", "v" }, 148 | "dl", 149 | function() require "dap".set_breakpoint(nil, nil, utils.input("Log point message: ")) end, 150 | { silent = true, desc = "DAP: set breakpoint with log point message" }) 151 | 152 | -- Load/Store breakpoint in a json-db 153 | vim.keymap.set({ "n", "v" }, 154 | "d-", 155 | M._load_bps, 156 | { silent = true, desc = "DAP load breakpoints" }) 157 | vim.keymap.set({ "n", "v" }, 158 | "d+", 159 | M._store_bps, 160 | { silent = true, desc = "DAP store breakpoints" }) 161 | 162 | vim.keymap.set({ "n", "v" }, 163 | "dr", 164 | function() require "dap".repl.toggle() end, 165 | { silent = true, desc = "DAP toggle debugger REPL" }) 166 | 167 | -- DAP-UI widgets 168 | vim.keymap.set({ "n", "v" }, 169 | "dk", 170 | function() require("dap.ui.widgets").hover() end, 171 | { silent = true, desc = "DAP Hover" }) 172 | vim.keymap.set({ "n", "v" }, 173 | "dp", 174 | function() require("dap.ui.widgets").preview() end, 175 | { silent = true, desc = "DAP Preview" }) 176 | vim.keymap.set({ "n", "v" }, 177 | "df", 178 | function() 179 | local widgets = require("dap.ui.widgets") 180 | widgets.centered_float(widgets.frames) 181 | end, 182 | { silent = true, desc = "DAP Frames" }) 183 | vim.keymap.set({ "n", "v" }, 184 | "ds", 185 | function() 186 | local widgets = require("dap.ui.widgets") 187 | widgets.centered_float(widgets.scopes) 188 | end, 189 | { silent = true, desc = "DAP Scopes" }) 190 | 191 | -- fzf-lua 192 | vim.keymap.set({ "n", "v" }, 193 | "d?", 194 | function() require "fzf-lua".dap_commands() end, 195 | { silent = true, desc = "DAP: fzf nvim-dap builtin commands" }) 196 | vim.keymap.set({ "n", "v" }, 197 | "db", 198 | function() require "fzf-lua".dap_breakpoints() end, 199 | { silent = true, desc = "DAP: fzf breakpoint list" }) 200 | vim.keymap.set({ "n", "v" }, 201 | "dF", 202 | function() require "fzf-lua".dap_frames() end, 203 | { silent = true, desc = "DAP: fzf frames" }) 204 | vim.keymap.set({ "n", "v" }, 205 | "dv", 206 | function() require "fzf-lua".dap_variables() end, 207 | { silent = true, desc = "DAP: fzf variables" }) 208 | vim.keymap.set({ "n", "v" }, 209 | "dx", 210 | function() require "fzf-lua".dap_configurations() end, 211 | { silent = true, desc = "DAP: fzf debugger configurations" } 212 | ) 213 | end 214 | 215 | 216 | M.config = function() 217 | local dap = require "dap" 218 | 219 | -- Lazy load fzf-lua to register_ui_select 220 | require("fzf-lua") 221 | 222 | -- Set logging level 223 | require("dap").set_log_level("DEBUG") 224 | 225 | -- configure dap-ui and language adapaters 226 | require "plugins.dap.ui".setup() 227 | require "plugins.dap.go" 228 | require "plugins.dap.lua" 229 | require "plugins.dap.python" 230 | require "plugins.dap.cpp_rust" 231 | 232 | -- Override the json decoder so we can support jsonc (comments, trailing commas) 233 | require("dap.ext.vscode").json_decode = require("lib.jsonc").decode 234 | 235 | -- Load configurations from `.launch.json` 236 | -- Example `.launch.json`: 237 | -- { 238 | -- "version": "0.2.0", 239 | -- "configurations": [ 240 | -- { 241 | -- "type": "lldb", 242 | -- "request": "launch", 243 | -- "name": "[LLDB] Launch '${workspaceFolder}/a.out'", 244 | -- "program": "a.out", 245 | -- "cwd": "${workspaceFolder}", 246 | -- "stopOnEntry": false 247 | -- } 248 | -- ] 249 | -- } 250 | require("dap.ext.vscode").load_launchjs(".launch.jsonc", { 251 | go = { "go" }, 252 | python = { "py" }, 253 | gdb = { "c", "cpp", "rust" }, 254 | lldb = { "c", "cpp", "rust" }, 255 | cppdbg = { "c", "cpp", "rust" }, 256 | }) 257 | 258 | -- Controls how stepping switches buffers 259 | dap.defaults.fallback.switchbuf = "useopen,uselast" 260 | 261 | -- Which terminal should be launched when `externalConsole = true` 262 | dap.defaults.fallback.external_terminal = { 263 | command = "/usr/bin/alacritty", 264 | args = { "-e" }, 265 | } 266 | 267 | -- links by default to DiagnosticVirtualTextXXX which linkx to Comment in nightgly 268 | vim.api.nvim_set_hl(0, "NvimDapVirtualText", { link = "Comment" }) 269 | vim.api.nvim_set_hl(0, "NvimDapVirtualTextInfo", { link = "DiagnosticInfo" }) 270 | vim.api.nvim_set_hl(0, "NvimDapVirtualTextError", { link = "DiagnosticError" }) 271 | vim.api.nvim_set_hl(0, "NvimDapVirtualTextChanged", { link = "DiagnosticWarn" }) 272 | 273 | -- configure nvim-dap-virtual-text 274 | local ok, dapvt = pcall(require, "nvim-dap-virtual-text") 275 | if ok and dapvt then 276 | dapvt.setup({ 277 | -- "inline" is also possible with nvim-0.10, IMHO is confusing 278 | virt_text_pos = "eol", 279 | }) 280 | end 281 | end 282 | 283 | return M 284 | -------------------------------------------------------------------------------- /lua/plugins/dap/lua.lua: -------------------------------------------------------------------------------- 1 | local res, dap = pcall(require, "dap") 2 | if not res then 3 | return 4 | end 5 | 6 | local nvim_server 7 | local nvim_chanID 8 | 9 | -- both deugging and execution is done on external headless instances 10 | -- we start a headless instance and then call ("osv").launch() which 11 | -- in turn starts another headless instance which will be the instance 12 | -- we connect to 13 | -- once the instance is running we can call `:luafile ` in order 14 | -- to start debugging 15 | local function dap_server(opts) 16 | assert(dap.adapters.nlua, 17 | "nvim-dap adapter configuration for nlua not found. " .. 18 | "Please refer to the README.md or :help osv.txt") 19 | 20 | -- server already started? 21 | if nvim_chanID then 22 | local pid = vim.fn.jobpid(nvim_chanID) 23 | vim.fn.rpcnotify(nvim_chanID, "nvim_exec_lua", [[return require"osv".stop()]]) 24 | vim.fn.jobstop(nvim_chanID) 25 | if type(vim.uv.os_getpriority(pid)) == "number" then 26 | vim.uv.kill(pid, 9) 27 | end 28 | nvim_chanID = nil 29 | end 30 | 31 | nvim_chanID = vim.fn.jobstart({ vim.v.progpath, "--embed", "--headless" }, { rpc = true }) 32 | assert(nvim_chanID, "Could not create neovim instance with jobstart!") 33 | 34 | local mode = vim.fn.rpcrequest(nvim_chanID, "nvim_get_mode") 35 | assert(not mode.blocking, "Neovim is waiting for input at startup. Aborting.") 36 | 37 | -- create the symlink from lazy 38 | local plugin_name = "one-small-step-for-vimkind" 39 | local plugin_dir = vim.fn.stdpath("data") .. "/site/pack/dap" 40 | assert(vim.fn.mkdir(plugin_dir, "p"), "Unable to create plugin dir") 41 | vim.uv.fs_symlink(vim.fn.stdpath("data") .. "/lazy", plugin_dir .. "/opt", { dir = true }) 42 | 43 | -- make sure OSV is loaded 44 | vim.fn.rpcrequest(nvim_chanID, "nvim_exec_lua", 45 | [[vim.opt.packpath:append({ vim.fn.stdpath("data") .. "/site" })]], {}) 46 | vim.fn.rpcrequest(nvim_chanID, "nvim_command", "packadd " .. plugin_name) 47 | 48 | nvim_server = vim.fn.rpcrequest(nvim_chanID, 49 | "nvim_exec_lua", 50 | [[return require"osv".launch(...)]], 51 | { opts }) 52 | 53 | vim.wait(100) 54 | 55 | -- print(("Server started on port %d, channel-id %d"):format(nvim_server.port, nvim_chanID)) 56 | return nvim_server 57 | end 58 | 59 | dap.adapters.nlua = function(callback, config) 60 | if not config.port then 61 | local server = dap_server() 62 | config.host = server.host 63 | config.port = server.port 64 | end 65 | callback({ type = "server", host = config.host, port = config.port }) 66 | if type(config.post) == "function" then 67 | config.post() 68 | end 69 | end 70 | 71 | 72 | dap.configurations.lua = { 73 | { 74 | type = "nlua", 75 | request = "attach", 76 | name = "Attach to running Neovim instance (localhost:8086)", 77 | host = "127.0.0.1", 78 | port = 8086, 79 | }, 80 | { 81 | type = "nlua", 82 | request = "attach", 83 | name = "Attach to running Neovim instance (prompt)", 84 | host = function() 85 | local val = vim.fn.input("Host [127.0.0.1]: ") 86 | return #val > 0 and val or "127.0.0.1" 87 | end, 88 | port = function() 89 | local val = vim.fn.input("Port [8086]: ") 90 | return #val > 0 and tonumber(val) or 8086 91 | end, 92 | }, 93 | { 94 | type = "nlua", 95 | name = "Debug current file", 96 | request = "attach", 97 | -- we acquire host/port in the adapters function above 98 | -- host = function() end, 99 | -- port = function() end, 100 | post = function() 101 | dap.listeners.after["setBreakpoints"]["osv"] = function(session, body) 102 | assert(nvim_chanID, "Fatal: neovim RPC channel is nil!") 103 | vim.fn.rpcnotify(nvim_chanID, "nvim_command", "luafile " .. vim.fn.expand("%:p")) 104 | -- clear the lisener or we get called in any dap-config run 105 | dap.listeners.after["setBreakpoints"]["osv"] = nil 106 | end 107 | -- for k, v in pairs(dap.listeners.after) do 108 | -- v["test"] = function() 109 | -- print(k, "called") 110 | -- end 111 | -- end 112 | end 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /lua/plugins/dap/python.lua: -------------------------------------------------------------------------------- 1 | local res, dap_python = pcall(require, "dap-python") 2 | if not res then 3 | return 4 | end 5 | 6 | local prefix = "" 7 | local bin_python = "/bin/python" 8 | 9 | if vim.fn.executable("pyenv") == 1 then 10 | local out = vim.fn.systemlist({ "pyenv", "prefix" }) 11 | if vim.v.shell_error == 0 and type(out[1]) == "string" then 12 | -- nvim-dap-python uses `os.getenv("VIRTUAL_ENV")` 13 | prefix = out[1] 14 | vim.env.VIRTUAL_ENV = out[1] 15 | end 16 | else 17 | -- check mason registry 18 | local ok, pkg = pcall(function() 19 | return require("mason-registry").get_package("debugpy") 20 | end) 21 | if ok and pkg and pkg:is_installed() then 22 | prefix = pkg:get_install_path() .. "/venv" 23 | end 24 | end 25 | 26 | dap_python.setup(prefix .. bin_python) 27 | dap_python.test_runner = prefix .. "/bin/pytest" 28 | 29 | local function getpid() 30 | local pid = require("dap.utils").pick_process({ filter = "python" }) 31 | if type(pid) == "thread" then 32 | -- returns a coroutine.create due to it being run from fzf-lua ui.select 33 | -- start the coroutine and wait for `coroutine.resume` (user selection) 34 | coroutine.resume(pid) 35 | pid = coroutine.yield(pid) 36 | end 37 | return pid 38 | end 39 | 40 | table.insert(require("dap").configurations.python, 4, { 41 | type = "python", 42 | request = "attach", 43 | name = "Attach to process", 44 | connect = function() 45 | -- https://github.com/microsoft/debugpy/#attaching-to-a-running-process-by-id 46 | local port = 5678 47 | local pid = getpid() 48 | local out = vim.fn.systemlist({ prefix .. bin_python, "-m", "debugpy", 49 | "--listen", "localhost:" .. tostring(port), "--pid", tostring(pid) }) 50 | assert(vim.v.shell_error == 0, table.concat(out, "\n")) 51 | return { port = port } 52 | end, 53 | }) 54 | -------------------------------------------------------------------------------- /lua/plugins/dap/ui.lua: -------------------------------------------------------------------------------- 1 | local res, dapui = pcall(require, "dapui") 2 | if not res then 3 | return 4 | end 5 | 6 | local utils = require("utils") 7 | local M = {} 8 | 9 | M.setup = function() 10 | ---@diagnostic disable-next-line: missing-fields 11 | dapui.setup({ 12 | layouts = { 13 | { 14 | position = "right", 15 | size = 0.40, 16 | elements = { 17 | { id = "scopes", size = 0.38, }, 18 | { id = "watches", size = 0.16 }, 19 | { id = "stacks", size = 0.28 }, 20 | { id = "breakpoints", size = 0.18 }, 21 | }, 22 | }, 23 | { 24 | position = "bottom", 25 | size = 0.30, 26 | elements = { 27 | { id = "repl", size = 0.60, }, 28 | { id = "console", size = 0.40 }, 29 | }, 30 | }, 31 | }, 32 | }) 33 | local dap = require("dap") 34 | dap.listeners.before.attach.dapui_config = function() 35 | M.open() 36 | end 37 | dap.listeners.before.launch.dapui_config = function() 38 | M.open() 39 | end 40 | dap.listeners.after.event_initialized.dapui_config = function() 41 | M.open() 42 | end 43 | dap.listeners.before.event_terminated.dapui_config = function(e) 44 | require("utils").info( 45 | string.format("program '%s' was terminated.", vim.fn.fnamemodify(e.config.program, ":t"))) 46 | end 47 | -- dap.listeners.before.event_exited.dapui_config = function(e) 48 | -- dapui.close() 49 | -- end 50 | end 51 | 52 | M.open = function(reset, tmux_zoom) 53 | if not M._is_open and tmux_zoom and not M._tmux_was_unzoomed then 54 | M._tmux_was_unzoomed = utils.tmux_zoom() 55 | if M._tmux_was_unzoomed then 56 | vim.cmd("sleep! 20m") 57 | end 58 | end 59 | dapui.open({ reset = reset == nil and true or reset }) 60 | M._is_open = true 61 | end 62 | 63 | M.close = function(tmux_zoom) 64 | dapui.close() 65 | if tmux_zoom and M._tmux_was_unzoomed then 66 | utils.tmux_unzoom() 67 | M._tmux_was_unzoomed = nil 68 | end 69 | M._is_open = nil 70 | end 71 | 72 | M.toggle = function(reset, tmux_zoom) 73 | if M._is_open then 74 | M.close(tmux_zoom) 75 | else 76 | M.open(reset, tmux_zoom) 77 | end 78 | end 79 | 80 | return M 81 | -------------------------------------------------------------------------------- /lua/plugins/devicons/init.lua: -------------------------------------------------------------------------------- 1 | return { 2 | "nvim-tree/nvim-web-devicons", 3 | config = function() 4 | require("plugins.devicons.setup") 5 | end 6 | } 7 | -------------------------------------------------------------------------------- /lua/plugins/devicons/setup.lua: -------------------------------------------------------------------------------- 1 | require("nvim-web-devicons").setup({ 2 | override_by_extension = { 3 | sol = { 4 | -- icon = "♦", 5 | icon = "", 6 | color = "#a074c4", 7 | name = "Sol" 8 | }, 9 | sh = { 10 | icon = "", 11 | color = "#89e051", 12 | cterm_color = "113", 13 | name = "Sh", 14 | }, 15 | md = { 16 | icon = "󰍔", 17 | color = "#dddddd", 18 | cterm_color = "239", 19 | name = "Md", 20 | }, 21 | norg = { 22 | icon = "", 23 | color = "#97eefc", 24 | name = "Neorg", 25 | }, 26 | }, 27 | }) 28 | -------------------------------------------------------------------------------- /lua/plugins/diffview.lua: -------------------------------------------------------------------------------- 1 | local utils = require("utils") 2 | 3 | local M = { 4 | "sindrets/diffview.nvim", 5 | cmd = { "DiffviewOpen", "DiffviewFileHistory" }, 6 | } 7 | 8 | M._is_open = function() 9 | return package.loaded.diffview and require("diffview.lib").get_current_view() 10 | end 11 | 12 | M._close = function() 13 | if M._is_open() then 14 | vim.cmd("DiffviewClose") 15 | return 0 16 | end 17 | end 18 | 19 | M._toggle = function(git_args) 20 | if M._is_open() then 21 | return M._close() 22 | else 23 | git_args = git_args or {} 24 | local git_cmd = { "git" } 25 | for _, arg in ipairs(git_args) do 26 | table.insert(git_cmd, arg:match("%$") and vim.fn.expand(arg) or arg) 27 | end 28 | table.insert(git_cmd, "status") 29 | local ret = vim.system(git_cmd):wait() 30 | if #ret.stderr > 0 then 31 | utils.warn(ret.stderr) 32 | return 33 | end 34 | local no_changes = ret.stdout:match("nothing to commit") 35 | local diffview_cmd = { "DiffviewOpen" } 36 | -- DiffviewOpen needs the args to be in the format of key=val 37 | for i = 1, #git_args, 2 do 38 | table.insert(diffview_cmd, string.format("%s=%s", git_args[i], git_args[i + 1])) 39 | end 40 | table.insert(diffview_cmd, no_changes and "HEAD~" or "HEAD") 41 | vim.cmd(table.concat(diffview_cmd, " ")) 42 | return 1 43 | end 44 | end 45 | 46 | M.init = function() 47 | vim.keymap.set({ "n", "x" }, "gd", 48 | function() 49 | if M._toggle() == 1 then 50 | M._tmux_was_unzoomed = utils.tmux_zoom() 51 | end 52 | end, 53 | { silent = true, desc = "Git diff (project)" }) 54 | 55 | vim.keymap.set({ "n", "x" }, "yd", 56 | function() 57 | if 58 | M._toggle({ 59 | "-c", "status.showUntrackedFiles=no", 60 | "--git-dir", "$HOME/dots/.git", 61 | "-C", "$HOME", 62 | }) == 1 63 | then 64 | M._tmux_was_unzoomed = utils.tmux_zoom() 65 | end 66 | end, 67 | { silent = true, desc = "Git diff (yadm)" }) 68 | end 69 | 70 | M.config = function() 71 | local gq_keymap_set_opts = { 72 | { "n", "v" }, "gq", M._close, { silent = true, desc = "Close Diffview" } 73 | } 74 | require("diffview").setup({ 75 | keymaps = { 76 | view = { gq_keymap_set_opts }, 77 | file_panel = { gq_keymap_set_opts }, 78 | file_history_panel = { gq_keymap_set_opts }, 79 | option_panel = { gq_keymap_set_opts }, 80 | }, 81 | hooks = { 82 | -- view_opened = function(_) end 83 | view_closed = function() 84 | if M._tmux_was_unzoomed then 85 | utils.tmux_unzoom() 86 | end 87 | -- remap `gq` to conform since we hijacked it to `DiffviewClose` 88 | for _, w in ipairs(vim.api.nvim_tabpage_list_wins(0)) do 89 | local b = vim.api.nvim_win_get_buf(w) 90 | require("plugins.conform")._set_gq_keymap({ buf = b }) 91 | end 92 | end 93 | } 94 | }) 95 | end 96 | 97 | return M 98 | -------------------------------------------------------------------------------- /lua/plugins/fugitive.lua: -------------------------------------------------------------------------------- 1 | local M = { 2 | "tpope/vim-fugitive", 3 | cmd = { "Git", "Dot", "Yit", "Gread", "Gwrite", "Gvdiffsplit", "Gdiffsplit" }, 4 | } 5 | 6 | M.init = function() 7 | local map = vim.keymap.set 8 | 9 | -- :Gedit will always send us back to the working copy 10 | -- and thus serves as a quasi back button 11 | map("n", "gg", ":Git", { silent = true, desc = "Git" }) 12 | map("n", "gr", ":Gread", { silent = true, desc = "Gread (reset)" }) 13 | map("n", "gw", ":Gwrite", { silent = true, desc = "Gwrite (stage)" }) 14 | -- map("n", "gb", ":Git blame", { silent = true, desc = "git blame" }) 15 | -- map('n', 'gc', ':Git commit', { silent = true }) 16 | -- map("n", "gD", ":Git diff", { silent = true, desc = "Git diff (project)" }) 17 | map("n", "gD", ":Gvdiffsplit!", { silent = true, desc = "Git diff (buffer)" }) 18 | map("n", "gp", ":Git push", { silent = true, desc = "Git push" }) 19 | map("n", "gP", ":Git pull", { silent = true, desc = "Git pull" }) 20 | map("n", "g+", ":Git stash push", { silent = true, desc = "Git stash push" }) 21 | map("n", "g-", ":Git stash pop", { silent = true, desc = "Git stash pop" }) 22 | map("n", "gl", ":Git log --stat %", { silent = true, desc = "Git log (buffer)" }) 23 | map("n", "gL", ":Git log --stat -n 100", 24 | { silent = true, desc = "Git log (project)" }) 25 | end 26 | 27 | M.config = function() 28 | -- fugitive shortcuts for yadm 29 | -- local yadm_repo = "$YADM_REPO" 30 | -- hack fugitive's worktree by using a symbolic link at $HOME 31 | local yadm_repo = "$HOME/.git" 32 | 33 | -- auto-complete for our custom fugitive Yadm command 34 | -- https://github.com/tpope/vim-fugitive/issues/1981#issuecomment-1113825991 35 | vim.cmd(([[ 36 | function! YadmComplete(A, L, P) abort 37 | return fugitive#Complete(a:A, a:L, a:P, {'git_dir': expand("%s")}) 38 | endfunction 39 | ]]):format(yadm_repo)) 40 | 41 | vim.cmd(( 42 | [[command! -bang -nargs=? -range=-1 -complete=customlist,YadmComplete Dot exe ]] 43 | .. [[fugitive#Command(, , +"", 0, "", , ]] 44 | .. [[{ 'git_dir': expand("%s") })]]):format(yadm_repo)) 45 | 46 | local function fugitive_command(nargs, cmd_name, cmd_fugitive, cmd_comp) 47 | vim.api.nvim_create_user_command(cmd_name, 48 | function(t) 49 | local bufnr = vim.api.nvim_get_current_buf() 50 | local buf_git_dir = vim.b.git_dir 51 | vim.b.git_dir = vim.fn.expand(yadm_repo) 52 | vim.cmd(cmd_fugitive .. " " .. t.args) 53 | -- after the fugitive window switch we must explicitly 54 | -- use the buffer num to restore the original 'git_dir' 55 | vim.b[bufnr].git_dir = buf_git_dir 56 | end, 57 | { 58 | nargs = nargs, 59 | complete = cmd_comp and string.format("customlist,%s", cmd_comp) or nil, 60 | } 61 | ) 62 | end 63 | 64 | -- fugitive_command("?", "Yadm", "Git", "fugitive#Complete") 65 | fugitive_command("?", "Yit", "Git", "YadmComplete") 66 | fugitive_command("*", "Yread", "Gread", "fugitive#ReadComplete") 67 | fugitive_command("*", "Yedit", "Gedit", "fugitive#EditComplete") 68 | fugitive_command("*", "Ywrite", "Gwrite", "fugitive#EditComplete") 69 | fugitive_command("*", "Ydiffsplit", "Gdiffsplit", "fugitive#EditComplete") 70 | fugitive_command("*", "Yhdiffsplit", "Ghdiffsplit", "fugitive#EditComplete") 71 | fugitive_command("*", "Yvdiffsplit", "Gvdiffsplit", "fugitive#EditComplete") 72 | fugitive_command(1, "YMove", "GMove", "fugitive#CompleteObject") 73 | fugitive_command(1, "YRename", "GRename", "fugitive#RenameComplete") 74 | fugitive_command(0, "YRemove", "GRemove") 75 | fugitive_command(0, "YUnlink", "GUnlink") 76 | fugitive_command(0, "YDelete", "GDelete") 77 | end 78 | 79 | return M 80 | -------------------------------------------------------------------------------- /lua/plugins/fzf-lua/cmds.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | local utils = require("utils") 4 | local fzf_lua = require("fzf-lua") 5 | 6 | function M.git_bcommits(opts) 7 | local function diffthis(action) 8 | return function(...) 9 | local curwin = vim.api.nvim_get_current_win() 10 | action(...) 11 | vim.cmd("windo diffthis") 12 | vim.api.nvim_set_current_win(curwin) 13 | end 14 | end 15 | 16 | opts.actions = { 17 | ["ctrl-v"] = diffthis(fzf_lua.actions.git_buf_vsplit), 18 | } 19 | return fzf_lua.git_bcommits(opts) 20 | end 21 | 22 | function M.git_status_tmuxZ(opts) 23 | opts = opts or {} 24 | 25 | -- Pre fzf v0.53: fzf-tmux script 26 | if fzf_lua.config.globals.fzf_bin == "fzf-tmux" then 27 | opts.fzf_tmux_opts = { ["-p"] = "100%,100%" } 28 | return fzf_lua.git_status(opts) 29 | end 30 | 31 | -- Post fzf v0.53: "--tmux" flag 32 | if fzf_lua.config.globals.fzf_opts["--tmux"] then 33 | opts.fzf_opts = opts.fzf_opts or {} 34 | opts.fzf_opts["--tmux"] = "100%,100%" 35 | return fzf_lua.git_status(opts) 36 | end 37 | 38 | opts.winopts = opts.winopts or {} 39 | opts.winopts.on_create = function(_) 40 | if opts._tmux_was_unzoomed == nil then 41 | opts._tmux_was_unzoomed = utils.tmux_zoom() 42 | end 43 | end 44 | opts.winopts.on_close = function() 45 | if opts._tmux_was_unzoomed then 46 | utils.tmux_unzoom() 47 | end 48 | end 49 | fzf_lua.git_status(opts) 50 | end 51 | 52 | function M.diagnostics_document(opts) 53 | opts = opts or {} 54 | opts.diag_source = #utils.lsp_get_clients({ bufnr = vim.api.nvim_get_current_buf() }) > 1 55 | and true or false 56 | opts.icon_padding = opts.diag_source and "" or " " 57 | fzf_lua.diagnostics_document(opts) 58 | end 59 | 60 | function M.diagnostics_workspace(opts) 61 | opts = opts or {} 62 | opts.diag_source = #utils.lsp_get_clients() > 1 and true or false 63 | opts.icon_padding = opts.diag_source and "" or " " 64 | fzf_lua.diagnostics_workspace(opts) 65 | end 66 | 67 | return M 68 | -------------------------------------------------------------------------------- /lua/plugins/fzf-lua/init.lua: -------------------------------------------------------------------------------- 1 | local M = { 2 | "ibhagwan/fzf-lua", 3 | dependencies = { "nvim-tree/nvim-web-devicons" }, 4 | dev = require("utils").is_dev("fzf-lua"), 5 | cmd = { "FzfLua", "TogglePickers" }, 6 | } 7 | 8 | function M.init() 9 | require("plugins.fzf-lua.mappings").map() 10 | end 11 | 12 | function M.config() 13 | -- Lazy load nvim-treesitter or help files err with: 14 | -- Query error at 2:4. Invalid node type "delimiter" 15 | -- This is due to fzf-lua calling `vim.treesitter.language.add` 16 | -- before nvim-treesitter is loaded 17 | pcall(require, "nvim-treesitter") 18 | require("plugins.fzf-lua.setup").setup() 19 | 20 | vim.api.nvim_create_user_command("TogglePickers", function() 21 | local utils = require("utils") 22 | utils.USE_SNACKS = not utils.USE_SNACKS 23 | utils.info(string.format("Main picker set to %s", utils.USE_SNACKS and "Snacks" or "FzfLua")) 24 | require("plugins.fzf-lua.mappings").map() 25 | require("plugins.snacks.mappings").map() 26 | end, {}) 27 | end 28 | 29 | return M 30 | -------------------------------------------------------------------------------- /lua/plugins/fzf-lua/mappings.lua: -------------------------------------------------------------------------------- 1 | -- yadm repo 2 | local yadm_git_opts = { 3 | cwd_header = false, 4 | cwd = "$HOME", 5 | git_dir = "$YADM_REPO", 6 | git_worktree = "$HOME", 7 | git_config = "status.showUntrackedFiles=no", 8 | } 9 | local yadm_grep_opts = { 10 | prompt = "YadmGrep❯ ", 11 | cwd_header = false, 12 | cwd = "$HOME", 13 | cmd = "git --git-dir=${YADM_REPO} -C ${HOME} grep -i --line-number --column --color=always", 14 | rg_glob = false, -- this isn't `rg` 15 | } 16 | 17 | local keys = { 18 | ---@format disable 19 | { ",", function() require "fzf-lua".buffers() end, desc = "Buffers" }, 20 | { "f?", function() require "fzf-lua".builtin() end, desc = "FzfLua Builtins", mode = { "n", "v" } }, 21 | { "fP", function() require "fzf-lua".profiles() end, desc = "FzfLua Profiles" }, 22 | { "f/", function() require "fzf-lua".search_history() end, desc = "Command History" }, 23 | { "f:", function() require "fzf-lua".command_history() end, desc = "Command History" }, 24 | { "fx", function() require "fzf-lua".commands() end, desc = "Commands" }, 25 | { "f0", function() require "fzf-lua".tmux_buffers() end, desc = "Tmux Buffers" }, 26 | { "", function() require "fzf-lua".helptags() end, desc = "Help Pages" }, 27 | -- find 28 | { "", function() require "fzf-lua".files() end, desc = "Find Files" }, 29 | { "", function() require "fzf-lua".zoxide() end, desc = "Zoxide" }, 30 | { "fp", function() require "fzf-lua".files({ cwd = vim.fn.stdpath("data") .. "/lazy" }) end, desc = "Find Plugin File" }, 31 | { "ff", function() require "fzf-lua".resume() end, desc = "Resume" }, 32 | { "fF", function() require "fzf-lua".resume() end, desc = "Resume" }, 33 | { "fH", function() require "fzf-lua".oldfiles() end, desc = "Oldfiles (All)" }, 34 | { "fh", function() require "fzf-lua".oldfiles({ cwd = vim.uv.cwd(), cwd_header = true, cwd_only = true }) end, desc = "Oldfiles (cwd)" }, 35 | -- git 36 | { "gb", function() require "fzf-lua".git_blame() end, desc = "Git Blame", mode = { "n", "v" } }, 37 | { "gB", function() require "fzf-lua".git_branches() end, desc = "Git Branches" }, 38 | { "gB", function() require "fzf-lua".git_branches() end, desc = "Git Branches" }, 39 | { "gc", function() require "fzf-lua".git_bcommits() end, desc = "Git Log", mode = { "n", "v" } }, 40 | { "gC", function() require "fzf-lua".git_commits() end, desc = "Git Log" }, 41 | { "gs", function() require "fzf-lua".git_status() end, desc = "Git Status" }, 42 | { "gt", function() require "fzf-lua".git_tags() end, desc = "Git Tags" }, 43 | { "gS", function() require "plugins.fzf-lua.cmds".git_status_tmuxZ({ 44 | winopts = { 45 | fullscreen = true, 46 | preview = { vertical = "down:70%", horizontal = "right:70%" } 47 | } 48 | }) end, desc = "Git Status" }, 49 | -- Grep 50 | { "fl", function() require "fzf-lua".live_grep() end, desc = "Grep" }, 51 | { "fB", function() require "fzf-lua".lgrep_curbuf() end, desc = "Buffers Grep" }, 52 | { "fw", function() require "fzf-lua".grep_cword() end, desc = "Grep word", mode = { "n", "v" } }, 53 | { "fW", function() require "fzf-lua".grep_cWORD() end, desc = "Grep WORD", mode = { "n", "v" } }, 54 | { "fv", function() require "fzf-lua".grep_visual() end, desc = "Grep Visual selection", mode = { "n", "v" } }, 55 | { "ft", function() require "fzf-lua".btags() end, desc = "Buffer Tags" }, 56 | { "fT", function() require "fzf-lua".tags() end, desc = "Tags" }, 57 | { "fb", function() require "fzf-lua".blines() end, desc = "Buffer Lines", mode = { "n", "v" } }, 58 | { "f3", function() require "fzf-lua".blines({ query = vim.fn.expand("") }) end, desc = "Buffer Lines (word)", mode = { "n" } }, 59 | { "f3", function() 60 | vim.cmd("visual") 61 | require "fzf-lua".blines({ query = require("utils").get_visual_selection() }) 62 | end, desc = "Buffer Lines (word)", mode = { "x" } }, 63 | { "/", function() require "fzf-lua".blines({ start = "cursor" }) end, desc = "Buffer Lines", mode = { "n", "v" } }, 64 | { "f8", function() require "fzf-lua".grep_curbuf({ search = vim.fn.expand("") }) end, desc = "Buffer Grep (word)" }, 65 | { "f8", function() 66 | require "fzf-lua".grep_curbuf({ search = require("utils").get_visual_selection() }) 67 | end, desc = "Buffer Grep (word)", mode = { "x" } }, 68 | { "f*", function() require "fzf-lua".grep_curbuf({ search = vim.fn.expand("") }) end, desc = "Buffer Grep (WORD)" }, 69 | -- search 70 | { 'f"', function() require "fzf-lua".registers() end, desc = "Registers" }, 71 | { "fa", function() require "fzf-lua".autocmds() end, desc = "Autocmds" }, 72 | { "fO", function() require "fzf-lua".highlights() end, desc = "Highlights" }, 73 | { "fj", function() require "fzf-lua".jumps() end, desc = "Jumps" }, 74 | { "fk", function() require "fzf-lua".keymaps() end, desc = "Keymaps" }, 75 | { "fq", function() require "fzf-lua".quickfix() end, desc = "Quickfix List" }, 76 | { "fQ", function() require "fzf-lua".loclist() end, desc = "Location List" }, 77 | { "fm", function() require "fzf-lua".marks() end, desc = "Marks" }, 78 | { "fM", function() require "fzf-lua".manpages() end, desc = "Man Pages" }, 79 | { "fo", function() require "fzf-lua".colorschemes({ winopts = { height = 0.45, width = 0.30 } }) end, desc = "Colorschemes" }, 80 | { "fz", function() require "fzf-lua".spell_suggest() end, desc = "Zoxide" }, 81 | -- LSP 82 | { "ll", function() require "fzf-lua".lsp_finder() end, desc = "LSP Finder" }, 83 | { "ld", function() require "fzf-lua".lsp_definitions() end, desc = "Goto Definition" }, 84 | { "lD", function() require "fzf-lua".lsp_declarations() end, desc = "Goto Declaration" }, 85 | { "lr", function() require "fzf-lua".lsp_references() end, nowait = true, desc = "References" }, 86 | { "lm", function() require "fzf-lua".lsp_implementations() end, desc = "Goto Implementation" }, 87 | { "ly", function() require "fzf-lua".lsp_typedefs() end, desc = "Goto T[y]pe Definition" }, 88 | { "ls", function() require "fzf-lua".lsp_document_symbols() end, desc = "LSP Symbols (buffer)" }, 89 | { "lS", function() require "fzf-lua".lsp_workspace_symbols() end, desc = "LSP Symbols (workspace)" }, 90 | { "la", function() require "fzf-lua".lsp_code_actions() end, desc = "Code Actions" }, 91 | { "lg", function() require "fzf-lua".diagnostics_document() end, desc = "Buffer Diagnostics" }, 92 | { "lG", function() require "fzf-lua".diagnostics_workspace() end, desc = "Workspace Diagnostics" }, 93 | { "lt", function() require "fzf-lua".treesitter() end, desc = "Treesitter" }, 94 | -- yadm 95 | { "yf", function() require "fzf-lua".git_files(vim.tbl_extend("force", yadm_git_opts, { prompt = "YadmFiles> " })) end, desc = "Yadm Files" }, 96 | { "yb", function() require "fzf-lua".git_branches(vim.tbl_extend("force", yadm_git_opts, { prompt = "YadmBranches> " })) end, desc = "Yadm Branches" }, 97 | { "yc", function() require "fzf-lua".git_bcommits(vim.tbl_extend("force", yadm_git_opts, { prompt = "YadmBCommits> " })) end, desc = "Yadm Log", mode = { "n", "v" } }, 98 | { "yC", function() require "fzf-lua".git_commits(vim.tbl_extend("force", yadm_git_opts, { prompt = "YadmCommits> " })) end, desc = "Yadm Log" }, 99 | { "yl", function() require "fzf-lua".live_grep(vim.tbl_extend("force", yadm_grep_opts, { prompt = "YadmGrep> " })) end, desc = "Yadm Grep" }, 100 | { "ys", function() require "fzf-lua".git_status(vim.tbl_extend("force", yadm_git_opts, { 101 | prompt = "YadmStatus> ", cmd = "git status -s" 102 | })) end, desc = "Yadm Status" }, 103 | { "yS", function() require "plugins.fzf-lua.cmds".git_status_tmuxZ(vim.tbl_extend("force", yadm_git_opts, { 104 | prompt = "YadmStatus> ", 105 | cmd = "git -c color.status=false --no-optional-locks status --porcelain=v1", 106 | winopts = { 107 | fullscreen = true, 108 | preview = { 109 | vertical = "down:70%", 110 | horizontal = "right:70%", 111 | } 112 | } })) end, desc = "Yadm Status" }, 113 | } 114 | 115 | return { 116 | map = function() 117 | for _, m in ipairs(keys) do 118 | local key = m[1] 119 | if require "utils".USE_SNACKS then 120 | key = key:gsub(",", ";") 121 | for _, k in ipairs({ "", "", "" }) do 122 | if key == k then key = "" .. k end 123 | end 124 | key = key:gsub("%l", function(x) 125 | return x:sub(1, -2) .. x:sub(-1):upper() 126 | end) 127 | end 128 | local opts = vim.deepcopy(m) 129 | opts[1], opts[2], opts.mode = nil, nil, nil 130 | vim.keymap.set(m.mode or "n", key, m[2], opts) 131 | end 132 | end 133 | } 134 | -------------------------------------------------------------------------------- /lua/plugins/fzf-lua/setup.lua: -------------------------------------------------------------------------------- 1 | local fzf_lua = require("fzf-lua") 2 | 3 | local img_prev_bin = (function() 4 | -- (1) Load the snacks image package directly so we don't have to wait for 5 | -- a file with images for fzf-lua snacks.image preview integration to work 6 | -- (2) If our terminal supports the kitty protocol set our image previewer 7 | -- to `nil` as it would be prioritized by fzf-lua over snacks.image 8 | if require("snacks.image").supports_terminal() then 9 | return nil 10 | else 11 | return vim.fn.executable("ueberzug") == 1 and { "ueberzug" } 12 | or vim.fn.executable("chafa") == 1 and { "chafa", "--format=symbols" } 13 | or vim.fn.executable("viu") == 1 and { "viu", "-b" } 14 | or nil 15 | end 16 | end)() 17 | 18 | -- return first matching highlight or nil 19 | local function hl_match(t) 20 | for _, h in ipairs(t) do 21 | local ok, hl = pcall(vim.api.nvim_get_hl, 0, { name = h, link = false }) 22 | if ok and type(hl) == "table" and (hl.fg or hl.bg) then 23 | return h 24 | end 25 | end 26 | end 27 | 28 | local symbol_hls = nil -- will be generated by symbol_hl fn 29 | local symbol_icons = { 30 | File = "", 31 | Module = "", 32 | Namespace = "󰅩", 33 | Package = "", 34 | Class = "󰌗", 35 | Method = "󰊕", 36 | Property = "", 37 | Field = "", 38 | Constructor = "", 39 | Enum = "", 40 | Interface = "󰠱", 41 | Function = "󰊕", 42 | Variable = "󰀫", 43 | Constant = "󰏿", 44 | String = "󰊄", 45 | Number = "󰎠", 46 | Boolean = "󰝖", 47 | Array = "", 48 | Object = "󰜫", 49 | Key = "󰌆", 50 | Null = "Ø", 51 | EnumMember = "", 52 | Struct = "󰙅", 53 | Event = "", 54 | Operator = "󰆕", 55 | TypeParameter = "", 56 | } 57 | 58 | local symbol_hl = function(s) 59 | if not symbol_hls then 60 | symbol_hls = {} 61 | for k, _ in pairs(symbol_icons) do 62 | symbol_hls[k] = hl_match({ "CmpItemKind" .. k, "@" .. k:lower(), k }) 63 | end 64 | -- fallback 65 | symbol_hls = vim.tbl_extend("keep", symbol_hls, { 66 | Object = "Label", 67 | Key = "Keyword", 68 | Array = "Directory", 69 | Null = "Float", 70 | Package = "@function", 71 | }) 72 | end 73 | return symbol_hls[s] 74 | end 75 | 76 | local default_opts = { 77 | { "border-fused", "hide" }, 78 | -- debug_tracelog = "~/fzf-lua-trace.log", 79 | -- fzf_opts = { ["--info"] = "default" }, 80 | -- fzf_opts = { ["--tmux"] = "80%,60%", ["--border"] = "rounded" }, 81 | fzf_colors = function(o) 82 | local is_tmux = o.fzf_bin and o.fzf_bin:match("tmux") or o.fzf_opts["--tmux"] 83 | if is_tmux then 84 | return { 85 | true, 86 | bg = "-1", 87 | gutter = "-1", 88 | border = { "fg", "Comment" }, 89 | header = { "fg", "Comment" }, 90 | separator = { "fg", "Comment" }, 91 | -- scrollbar = { "fg", "WarningMsg" }, 92 | } 93 | else 94 | return true 95 | end 96 | end, 97 | winopts = { 98 | -- split = "belowright new", 99 | -- split = "belowright vnew", 100 | -- split = "aboveleft new", 101 | -- split = "aboveleft vnew", 102 | -- height = 0.85, 103 | -- width = 0.80, 104 | -- row = 0.35, 105 | -- col = 0.55, 106 | -- border = { {'╭', 'IncSearch'}, {'─', 'IncSearch'}, 107 | -- {'╮', 'IncSearch'}, '│', '╯', '─', '╰', '│' }, 108 | -- treesitter = false, 109 | preview = { 110 | -- layout = "flex", 111 | -- layout = "vertical", 112 | -- layout = "horizontal", 113 | -- vertical = "down:50%", 114 | -- vertical = "up:50%", 115 | -- horizontal = "right:55%", 116 | -- horizontal = "left:60%", 117 | -- scrollbar = "float", 118 | -- scrolloff = -1, 119 | flip_columns = 120, 120 | }, 121 | on_create = function(e) 122 | -- disable miniindentscope 123 | vim.b.miniindentscope_disable = true 124 | vim.keymap.set("t", "", "", { buffer = e.bufnr, nowait = true }) 125 | vim.keymap.set("t", "", [['"'.nr2char(getchar()).'pi']], 126 | { buffer = e.bufnr, expr = true }) 127 | end, 128 | }, 129 | -- winopts = function() 130 | -- -- local split = "botright new" -- use for split under **all** windows 131 | -- -- local split = "belowright new" -- use for split under current windows 132 | -- -- local height = math.floor(vim.o.lines * 0.3) 133 | -- -- return { split = split .. " | resize " .. tostring(height) } 134 | -- return { split = "belowright new", preview = { flip_columns = 120 } } 135 | -- end, 136 | -- hls = function() 137 | -- return { 138 | -- border = hl_match({ "FloatBorder", "LineNr" }), 139 | -- preview_border = hl_match({ "FloatBorder", "LineNr" }), 140 | -- cursorline = "Visual", 141 | -- -- cursorlinenr = "Visual", 142 | -- dir_icon = hl_match({ "NightflyGreyBlue", "Directory" }), 143 | -- } 144 | -- end, 145 | previewers = { 146 | bat = { theme = "Coldark-Dark", args = "--color=always --style=default" }, 147 | builtin = { 148 | title_fnamemodify = function(s) return s end, 149 | ueberzug_scaler = "cover", 150 | extensions = { 151 | ["gif"] = img_prev_bin, 152 | ["png"] = img_prev_bin, 153 | ["jpg"] = img_prev_bin, 154 | ["jpeg"] = img_prev_bin, 155 | ["svg"] = { "chafa" }, 156 | } 157 | }, 158 | }, 159 | actions = { 160 | files = { 161 | true, 162 | ["ctrl-l"] = { fn = fzf_lua.actions.arg_add, exec_silent = true }, 163 | }, 164 | }, 165 | -- all providers inherit from defaults, easier than to set this individually 166 | -- for git diff, commits and bcommits (we have an override for lsp.code_actions) 167 | defaults = { formatter = { "path.dirname_first", v = 2 } }, 168 | buffers = { no_action_zz = true }, 169 | files = { fzf_opts = { ["--tiebreak"] = "end" } }, 170 | grep = { 171 | fzf_opts = { ["--history"] = vim.fs.joinpath(vim.fn.stdpath("data"), "fzf_search_hist") }, 172 | -- actions = { ["ctrl-g"] = false, ["ctrl-r"] = { fzf_lua.actions.grep_lgrep } }, 173 | }, 174 | -- tags = { actions = { ["ctrl-g"] = false, ["ctrl-r"] = { fzf_lua.actions.grep_lgrep } } }, 175 | git = { 176 | status = { winopts = { preview = { vertical = "down:70%", horizontal = "right:70%" } } }, 177 | commits = { winopts = { preview = { vertical = "down:60%", } } }, 178 | bcommits = { winopts = { preview = { vertical = "down:60%", } } }, 179 | branches = { 180 | -- cmd_add = { "git", "checkout", "-b" }, 181 | cmd_del = { "git", "branch", "--delete", "--force" }, 182 | winopts = { preview = { vertical = "down:75%", horizontal = "right:75%" } 183 | } 184 | }, 185 | }, 186 | lsp = { 187 | finder = { 188 | providers = { 189 | { "definitions", prefix = fzf_lua.utils.ansi_codes.green("def ") }, 190 | { "declarations", prefix = fzf_lua.utils.ansi_codes.magenta("decl") }, 191 | { "implementations", prefix = fzf_lua.utils.ansi_codes.green("impl") }, 192 | { "typedefs", prefix = fzf_lua.utils.ansi_codes.red("tdef") }, 193 | { "references", prefix = fzf_lua.utils.ansi_codes.blue("ref ") }, 194 | { "incoming_calls", prefix = fzf_lua.utils.ansi_codes.cyan("in ") }, 195 | { "outgoing_calls", prefix = fzf_lua.utils.ansi_codes.yellow("out ") }, 196 | }, 197 | }, 198 | symbols = { 199 | path_shorten = 1, 200 | symbol_icons = symbol_icons, 201 | symbol_hl = symbol_hl, 202 | -- actions = { ["ctrl-g"] = false, ["ctrl-r"] = { fzf_lua.actions.sym_lsym } }, 203 | }, 204 | code_actions = { 205 | winopts = { 206 | relative = "cursor", 207 | row = 1, 208 | col = 0, 209 | height = 0.4, 210 | preview = { vertical = "down:70%" } 211 | }, 212 | previewer = vim.fn.executable("delta") == 1 and "codeaction_native" or nil, 213 | preview_pager = "delta --width=$COLUMNS --hunk-header-style=omit --file-style=omit", 214 | }, 215 | }, 216 | diagnostics = { file_icons = false, path_shorten = 1, diag_source = true }, 217 | dir_icon = "", 218 | } 219 | 220 | return { 221 | setup = function() 222 | -- NOT NEEDED since fzf-lua commit 604eadf 223 | -- custom devicons setup file to be loaded when `multiprocess = true` 224 | -- fzf_lua.config._devicons_setup = "~/.config/nvim/lua/plugins/devicons/setup.lua" 225 | 226 | fzf_lua.setup(default_opts) 227 | 228 | -- register fzf-lua as vim.ui.select interface 229 | fzf_lua.register_ui_select(function(o, items) 230 | local min_h, max_h = 0.15, 0.70 231 | local preview = o.kind == "codeaction" and 0.20 or 0 232 | local h = (#items + 4) / vim.o.lines + preview 233 | if h < min_h then 234 | h = min_h 235 | elseif h > max_h then 236 | h = max_h 237 | end 238 | return { winopts = { height = h, width = 0.60, row = 0.40 } } 239 | end) 240 | 241 | vim.api.nvim_create_autocmd("ColorScheme", { 242 | callback = function() symbol_hls = nil end, 243 | group = vim.api.nvim_create_augroup("FzfLuaColor", { clear = true }) 244 | }) 245 | end 246 | } 247 | -------------------------------------------------------------------------------- /lua/plugins/gitsigns.lua: -------------------------------------------------------------------------------- 1 | local M = { 2 | "lewis6991/gitsigns.nvim", 3 | -- "VeryLazy" hides splash screen 4 | event = "BufReadPre", 5 | } 6 | 7 | M.config = function() 8 | require("gitsigns").setup { 9 | signs = { 10 | add = { text = "┃" }, 11 | change = { text = "┃" }, 12 | delete = { text = "_" }, 13 | topdelete = { text = "‾" }, 14 | changedelete = { text = "~" }, 15 | untracked = { text = "┆" }, 16 | }, 17 | signcolumn = true, -- Toggle with `:Gitsigns toggle_signs` 18 | numhl = false, -- Toggle with `:Gitsigns toggle_numhl` 19 | linehl = false, -- Toggle with `:Gitsigns toggle_linehl` 20 | word_diff = false, -- Toggle with `:Gitsigns toggle_word_diff` 21 | sign_priority = 4, -- Lower priorirty means diag signs supercede 22 | -- Use detached worktrees instead of `yadm` 23 | -- https://github.com/lewis6991/gitsigns.nvim/pull/600 24 | worktrees = not require("utils").__IS_WIN and { 25 | { 26 | toplevel = vim.env.HOME, 27 | gitdir = vim.env.HOME .. "/dots/.git" 28 | } 29 | } or nil, 30 | on_attach = function(bufnr) 31 | local gs = package.loaded.gitsigns 32 | 33 | local function map(mode, l, r, opts) 34 | opts = opts or {} 35 | opts.buffer = bufnr 36 | vim.keymap.set(mode, l, r, opts) 37 | end 38 | 39 | map("n", "]c", function() 40 | if vim.wo.diff then 41 | vim.cmd.normal({ "]c", bang = true }) 42 | else 43 | gs.nav_hunk("next") 44 | end 45 | end, { desc = "Next hunk" }) 46 | 47 | map("n", "[c", function() 48 | if vim.wo.diff then 49 | vim.cmd.normal({ "[c", bang = true }) 50 | else 51 | gs.nav_hunk("prev") 52 | end 53 | end, { desc = "Previous hunk" }) 54 | 55 | -- Actions 56 | map("n", "hs", gs.stage_hunk, { desc = "Stage hunk" }) 57 | map("n", "hr", gs.reset_hunk, { desc = "Reset hunk" }) 58 | map("v", "hs", function() gs.stage_hunk { vim.fn.line("."), vim.fn.line("v") } end, 59 | { desc = "Stage hunk" }) 60 | map("v", "hr", function() gs.reset_hunk { vim.fn.line("."), vim.fn.line("v") } end, 61 | { desc = "Reset hunk" }) 62 | map("n", "hu", gs.undo_stage_hunk, { desc = "Undo stage hunk" }) 63 | map("n", "hS", gs.stage_buffer, { desc = "Stage buffer" }) 64 | map("n", "hR", gs.reset_buffer, { desc = "Reset buffer" }) 65 | map("n", "hp", gs.preview_hunk_inline, { desc = "preview hunk (inline)" }) 66 | map("n", "hP", gs.preview_hunk, { desc = "preview hunk (float)" }) 67 | -- map "{h|y}b" to git blame 68 | for _, c in ipairs({ "h", "y" }) do 69 | map("n", string.format("%sb", c), 70 | function() gs.blame_line({ full = true }) end, { desc = "Line blame (float)" }) 71 | end 72 | map("n", "hB", gs.toggle_current_line_blame, { desc = "line blame (toggle)" }) 73 | map("n", "hd", gs.diffthis, { desc = "diff against the index" }) 74 | map("n", "hD", function() gs.diffthis("~1") end, 75 | { desc = "diff against previous commit" }) 76 | map("n", "hx", gs.toggle_deleted, { desc = "toggle deleted lines" }) 77 | 78 | -- Text object 79 | map({ "o", "x" }, "ih", ":Gitsigns select_hunk") 80 | map({ "n", "x" }, "[h", ":Gitsigns select_hunko^", 81 | { silent = true, desc = "Hunk top" }) 82 | map({ "n", "x" }, "]h", ":Gitsigns select_hunk^g_", 83 | { silent = true, desc = "Hunk bottom" }) 84 | end 85 | } 86 | end 87 | 88 | return M 89 | -------------------------------------------------------------------------------- /lua/plugins/init.lua: -------------------------------------------------------------------------------- 1 | return { 2 | { 3 | -- "bluz71/vim-nightfly-guicolors", 4 | "bluz71/vim-moonfly-colors", 5 | enabled = true, 6 | lazy = false, 7 | priority = 1000, 8 | config = function() 9 | vim.cmd.colorscheme("moonfly") 10 | -- vim.cmd.colorscheme("nightfly") 11 | end 12 | }, 13 | { 14 | "folke/tokyonight.nvim", 15 | enabled = false, 16 | lazy = false, 17 | priority = 1000, 18 | config = function() 19 | vim.cmd.colorscheme("tokyonight") 20 | end 21 | }, 22 | { 23 | "dstein64/vim-startuptime", 24 | cmd = "StartupTime", 25 | }, 26 | -- SmartYank (by me) 27 | { 28 | "ibhagwan/smartyank.nvim", 29 | config = function() 30 | require("smartyank").setup({ highlight = { timeout = 1000 } }) 31 | end, 32 | event = "VeryLazy", 33 | dev = require("utils").is_dev("smartyank.nvim") 34 | }, 35 | { 36 | "previm/previm", 37 | commit = "8d414bf9b38d2a7c65a313775e26c03a0169f67f", 38 | config = function() 39 | -- vim.g.previm_open_cmd = 'firefox'; 40 | vim.g.previm_open_cmd = "/shared/$USER/Applications/chromium/chrome"; 41 | vim.g.previm_enable_realtime = 0 42 | vim.g.previm_code_language_show = 1 43 | vim.g.previm_disable_default_css = 1 44 | vim.g.previm_custom_css_path = vim.fn.stdpath("config") .. "/css/previm-gh-dark.css" 45 | local hljs_ghdark_css = "highlight-gh-dark.css" 46 | vim.g.previm_extra_libraries = { { 47 | name = "highlight-gh-dark", 48 | files = { { 49 | type = "css", 50 | -- must use previm jailed path due to chrome running in firejail 51 | path = "_/css/lib/highlight-gh-dark.css", 52 | } }, 53 | } } 54 | -- Copy our custom code highlight css to the jailbreak folder 55 | if not vim.uv.fs_copyfile(vim.fn.stdpath("config") .. "/css/" .. hljs_ghdark_css, 56 | vim.fn.stdpath("data") .. "/lazy/previm/preview/_/css/lib/" .. hljs_ghdark_css) then 57 | require("utils").warn(string.format( 58 | "Unable to copy '%s' to previm jail.", hljs_ghdark_css)) 59 | end 60 | -- clear cache every time we open neovim 61 | vim.fn["previm#wipe_cache"]() 62 | end, 63 | ft = { "markdown" }, 64 | }, 65 | { 66 | "junegunn/fzf.vim", 67 | enabled = true, 68 | lazy = false, 69 | dev = require("utils").is_dev("fzf.vim"), 70 | -- windows doesn't have fzf runtime plugin 71 | dependencies = require("utils").__IS_WIN and { "junegunn/fzf" } or nil, 72 | }, 73 | { 74 | "MeanderingProgrammer/render-markdown.nvim", 75 | enabled = true, 76 | ft = "markdown", 77 | config = function() 78 | require("render-markdown").setup({ 79 | file_types = { "markdown" }, 80 | code = { 81 | sign = false, 82 | width = "block", 83 | right_pad = 4, 84 | position = "right", 85 | }, 86 | heading = { 87 | sign = false, 88 | -- icons = {}, 89 | }, 90 | }) 91 | end, 92 | }, 93 | } 94 | -------------------------------------------------------------------------------- /lua/plugins/lsp.lua: -------------------------------------------------------------------------------- 1 | local utils = require("utils") 2 | 3 | return { 4 | { 5 | "williamboman/mason-lspconfig.nvim", 6 | enabled = utils.__HAS_NVIM_011, 7 | event = { "VeryLazy", "BufReadPre" }, 8 | dependencies = { 9 | { "neovim/nvim-lspconfig" }, 10 | { "mason-org/mason.nvim" }, 11 | { "j-hui/fidget.nvim" }, 12 | }, 13 | config = function() 14 | -- Add the same capabilities to ALL server configurations. 15 | -- Refer to :h vim.lsp.config() for more information. 16 | vim.lsp.config("*", { 17 | capabilities = vim.lsp.protocol.make_client_capabilities() 18 | }) 19 | require("lsp.diag") 20 | require("lsp.icons") 21 | require("fidget").setup({}) 22 | require("mason").setup() 23 | require("mason-lspconfig").setup({ 24 | ensure_installed = not utils.is_NetBSD() 25 | and not utils.is_iSH() 26 | and { "lua_ls" } 27 | or nil, 28 | }) 29 | end, 30 | }, 31 | { 32 | "mfussenegger/nvim-jdtls", 33 | ft = "java", 34 | }, 35 | { 36 | "folke/lazydev.nvim", 37 | enabled = true, 38 | ft = "lua", 39 | opts = { 40 | library = { 41 | -- Load luvit types when the `vim.uv` word is found 42 | { path = "$(3rd)/luv/library", words = { "vim%.uv" } }, 43 | -- Always luad fzf-lua 44 | "fzf-lua", 45 | }, 46 | -- uncomment to disable when a .luarc.json file is found 47 | -- enabled = function(root_dir) 48 | -- return not vim.uv.fs_stat(root_dir .. "/.luarc.json") 49 | -- and not vim.uv.fs_stat(root_dir .. "/.luarc.jsonc") 50 | -- end, 51 | }, 52 | }, 53 | } 54 | -------------------------------------------------------------------------------- /lua/plugins/mini/indentscope.lua: -------------------------------------------------------------------------------- 1 | require("mini.indentscope").setup({ 2 | draw = { 3 | -- Delay (in ms) between event and start of drawing scope indicator 4 | delay = 100, 5 | -- Animation rule for scope's first drawing. A function which, given next 6 | -- and total step numbers, returns wait time (in ms). See 7 | -- |MiniIndentscope.gen_animation()| for builtin options. To not use 8 | -- animation, supply `require('mini.indentscope').gen_animation('none')`. 9 | animation = function(_, _) 10 | return 5 11 | end, 12 | }, 13 | -- Module mappings. Use `''` (empty string) to disable one. 14 | mappings = { 15 | -- Textobjects 16 | object_scope = "ii", 17 | object_scope_with_border = "ai", 18 | -- Motions (jump to respective border line; if not present - body line) 19 | goto_top = "[i", 20 | goto_bottom = "]i", 21 | }, 22 | -- Options which control computation of scope. Buffer local values can be 23 | -- supplied in buffer variable `vim.b.miniindentscope_options`. 24 | options = { 25 | -- Type of scope's border: which line(s) with smaller indent to 26 | -- categorize as border. Can be one of: 'both', 'top', 'bottom', 'none'. 27 | border = "both", 28 | -- Whether to use cursor column when computing reference indent. Useful to 29 | -- see incremental scopes with horizontal cursor movements. 30 | indent_at_cursor = true, 31 | -- Whether to first check input line to be a border of adjacent scope. 32 | -- Use it if you want to place cursor on function header to get scope of 33 | -- its body. 34 | try_as_border = true, 35 | }, 36 | -- Which character to use for drawing scope indicator 37 | -- alternative styles: ┆ ┊ ╎ 38 | symbol = "╎", 39 | }) 40 | 41 | vim.keymap.set("", [["]], 42 | "lua require'plugins.mini.indentscope'.btoggle()", 43 | { silent = true, desc = "toggle 'mini.indentscope' on/off" }) 44 | 45 | local M = {} 46 | 47 | M.toggle = function(bufnr) 48 | if bufnr then 49 | vim.b.miniindentscope_disable = not vim.b.miniindentscope_disable 50 | else 51 | vim.g.miniindentscope_disable = not vim.g.miniindentscope_disable 52 | end 53 | require("mini.indentscope").draw() 54 | end 55 | 56 | M.btoggle = function() 57 | M.toggle(vim.api.nvim_get_current_buf()) 58 | end 59 | 60 | return M 61 | -------------------------------------------------------------------------------- /lua/plugins/mini/init.lua: -------------------------------------------------------------------------------- 1 | local M = { 2 | -- vim-surround/sandwich, lua version 3 | -- mini also has an indent highlighter 4 | "echasnovski/mini.nvim", 5 | -- not using "VeryLazy" event as it bugs out the splashscreen 6 | -- https://github.com/echasnovski/mini.nvim/issues/238 7 | event = { "BufReadPost", "InsertEnter" } 8 | } 9 | 10 | function M.init() 11 | vim.keymap.set({ "n", "v" }, "tt", 12 | function() require("mini.test").run_file() end, 13 | { silent = true, desc = "Run tests in current file" }) 14 | vim.keymap.set({ "n", "v" }, "tl", 15 | function() require("mini.test").run_at_location() end, 16 | { silent = true, desc = "Run test at cursor" }) 17 | vim.keymap.set({ "n", "v" }, "ta", 18 | function() require("mini.test").run() end, 19 | { silent = true, desc = "Run all tests" }) 20 | end 21 | 22 | function M.config() 23 | require("plugins.mini.surround") 24 | require("plugins.mini.indentscope") 25 | require("plugins.mini.statusline").setup() 26 | require("mini.ai").setup() 27 | require("mini.test").setup({ 28 | collect = { 29 | find_files = function() 30 | return vim.fn.globpath("tests", "**/*_spec.lua", true, true) 31 | end, 32 | }, 33 | }) 34 | vim.api.nvim_create_user_command("MiniHipatternsToggle", function() 35 | local hipatterns = require("mini.hipatterns") 36 | local hex_from_colormap = function() 37 | local colormap = vim.api.nvim_get_color_map() 38 | return function(_, match) 39 | match = #match > 0 and match:sub(1, 1):upper() .. match:sub(2) or match 40 | local col = colormap[match] 41 | if col == nil then return nil end 42 | return hipatterns.compute_hex_color_group(string.format("#%06x", col), "bg") 43 | end 44 | end 45 | if vim.b.minihipatterns_disable ~= false then 46 | vim.b.minihipatterns_disable = false 47 | hipatterns.enable(0, { 48 | highlighters = { 49 | hex_color = hipatterns.gen_highlighter.hex_color(), 50 | word_color = { pattern = "%w+", group = hex_from_colormap() }, 51 | }, 52 | }) 53 | else 54 | hipatterns.disable(0) 55 | vim.b.minihipatterns_disable = true 56 | end 57 | end, {}) 58 | end 59 | 60 | return M 61 | -------------------------------------------------------------------------------- /lua/plugins/mini/statusline.lua: -------------------------------------------------------------------------------- 1 | local section_mode = function(opts) 2 | -- Mode section hacked with faux "snippet mode" 3 | local mode, mode_hl = MiniStatusline.section_mode({ trunc_width = opts.trunc_width }) 4 | if vim.snippet.active() then 5 | mode = MiniStatusline.is_truncated(opts.trunc_width) and "S" or "SNIPPET" 6 | mode_hl = "WildMenu" 7 | end 8 | mode = "%(" .. string.upper(mode) .. " %)" 9 | return mode, mode_hl 10 | end 11 | 12 | local section_diff = function(args) 13 | if MiniStatusline.is_truncated(args.trunc_width) then return "" end 14 | local summary = vim.b.minidiff_summary_string or vim.b.gitsigns_status 15 | return summary or "" 16 | end 17 | 18 | local section_fileicon = function(args) 19 | if MiniStatusline.is_truncated(args.trunc_width) then return "" end 20 | local fileinfo = MiniStatusline.section_fileinfo({}) 21 | -- If icon was added it would be a multibyte char in the first index 22 | -- in which case string.len would be greater than the number of chars 23 | local diff = string.len(fileinfo) - vim.fn.strchars(fileinfo) 24 | return diff == 0 and "" or string.sub(MiniStatusline.section_fileinfo({}), 1, diff + 1) 25 | end 26 | 27 | local section_fileinfo = function(args) 28 | if MiniStatusline.is_truncated(args.trunc_width) then return "" end 29 | local fileinfo = MiniStatusline.section_fileinfo({}) 30 | -- If icon was added it would be a multibyte char in the first index 31 | -- in which case string.len would be greater than the number of chars 32 | local diff = string.len(fileinfo) - vim.fn.strchars(fileinfo) 33 | return diff == 0 and fileinfo or string.sub(fileinfo, diff + 2) 34 | end 35 | 36 | local section_lsp = function(args) 37 | if MiniStatusline.is_truncated(args.trunc_width) then return "" end 38 | local names = {} 39 | for _, server in pairs(require("utils").lsp_get_clients({ bufnr = 0 }) or {}) do 40 | table.insert(names, server.name) 41 | end 42 | return table.concat(names, " ") 43 | end 44 | 45 | local section_diagnostics = function(args) 46 | if MiniStatusline.is_truncated(args.trunc_width) then return "" end 47 | local diags = { 48 | { "E", #vim.diagnostic.get(0, { severity = vim.diagnostic.severity.ERROR }) }, 49 | { "W", #vim.diagnostic.get(0, { severity = vim.diagnostic.severity.WARN }) }, 50 | { "H", #vim.diagnostic.get(0, { severity = vim.diagnostic.severity.HINT }) }, 51 | { "I", #vim.diagnostic.get(0, { severity = vim.diagnostic.severity.INFO }) }, 52 | } 53 | local ret = {} 54 | for _, info in ipairs(diags) do 55 | if info[2] > 0 and (not args.severity or args.severity == info[1]) then 56 | table.insert(ret, string.format("%s:%d", info[1], info[2])) 57 | end 58 | end 59 | return table.concat(ret, " ") 60 | end 61 | 62 | local active_content = function() 63 | local mode, mode_hl = section_mode({ trunc_width = 40 }) 64 | local git = MiniStatusline.section_git({ trunc_width = 40 }) 65 | local diff = section_diff({ trunc_width = 75, icon = "" }) 66 | local fileicon = section_fileicon({ trunc_width = 75 }) 67 | local fileinfo = section_fileinfo({ trunc_width = 140 }) 68 | local filename = MiniStatusline.section_filename({ trunc_width = 140 }) 69 | local lsp = section_lsp({ trunc_width = 75 }) 70 | -- local diagnostics = MiniStatusline.section_diagnostics({ trunc_width = 75 }) 71 | local diag_e = section_diagnostics({ trunc_width = 75, severity = "E" }) 72 | local diag_w = section_diagnostics({ trunc_width = 75, severity = "W" }) 73 | local diag_h = section_diagnostics({ trunc_width = 75, severity = "H" }) 74 | local diag_i = section_diagnostics({ trunc_width = 75, severity = "I" }) 75 | local location = MiniStatusline.section_location({ trunc_width = 75 }) 76 | local search = MiniStatusline.section_searchcount({ trunc_width = 75 }) 77 | 78 | 79 | return MiniStatusline.combine_groups({ 80 | { hl = mode_hl, strings = { mode } }, 81 | { hl = "MiniStatuslineDevinfo", strings = { git } }, 82 | { hl = "DiffAdd", strings = { diff } }, 83 | { hl = "MiniStatuslineFilename", strings = { "%=" } }, -- Right align 84 | { hl = "MiniStatuslineFilename", strings = { fileicon, filename } }, 85 | { hl = "MiniStatuslineFilename", strings = { "%=" } }, -- Left align 86 | { hl = "MiniStatuslineFileinfo", strings = { fileinfo } }, 87 | { hl = "MiniStatusLineModeOther", strings = { lsp } }, 88 | -- { hl = "MiniStatusLineModeReplace", strings = { diagnostics } }, 89 | { hl = "MiniStatusLineModeReplace", strings = { diag_e } }, 90 | { hl = "MiniStatusLineModeCommand", strings = { diag_w } }, 91 | { hl = "MiniStatusLineModeVisual", strings = { diag_h } }, 92 | { hl = "MiniStatusLineModeInsert", strings = { diag_i } }, 93 | { hl = "MiniStatusLineModeVisual", strings = { search } }, 94 | { hl = mode_hl, strings = { location } }, 95 | }) 96 | end 97 | 98 | local inactive_content = function() 99 | local filename = MiniStatusline.section_filename({ trunc_width = 140 }) 100 | return MiniStatusline.combine_groups({ 101 | { hl = "MiniStatuslineFilename", strings = { "%=" } }, -- Right align 102 | { hl = "MiniStatuslineFilename", strings = { filename } }, 103 | { hl = "MiniStatuslineFilename", strings = { "%=" } }, -- Left align 104 | }) 105 | end 106 | 107 | return { 108 | setup = function() 109 | require("mini.statusline").setup({ 110 | content = { 111 | active = active_content, 112 | inactive = inactive_content, 113 | }, 114 | }) 115 | end 116 | } 117 | -------------------------------------------------------------------------------- /lua/plugins/mini/surround.lua: -------------------------------------------------------------------------------- 1 | require("mini.surround").setup({ 2 | -- vim-surround style mappings 3 | -- left brackets add space around the text object 4 | -- 'ysiw(' foo -> ( foo ) 5 | -- 'ysiw)' foo -> (foo) 6 | custom_surroundings = { 7 | -- since mini.nvim#84 we no longer need to customize 8 | -- the left brackets, they are spaced by default 9 | -- https://github.com/echasnovski/mini.nvim/issues/84 10 | s = { 11 | -- lua bracketed string mapping 12 | -- 'ysiwS' foo -> [[foo]] 13 | input = { "%[%[().-()%]%]" }, 14 | output = { left = "[[", right = "]]" }, 15 | }, 16 | b = { 17 | -- replace b (brackets) with lua block comment 18 | -- 'viwSb' foo -> --[[ foo ]] 19 | -- 'dsb' --[[ foo ]] -> foo 20 | -- 'csb"' --[[ foo ]] -> "foo" 21 | input = { "%-%-%[%[%s?().-()%s?%]%]" }, 22 | output = { left = "--[[ ", right = " ]]" }, 23 | }, 24 | }, 25 | mappings = { 26 | add = "ys", 27 | delete = "ds", 28 | find = "", 29 | find_left = "", 30 | highlight = "gs", -- hijack 'gs' (sleep) for highlight 31 | replace = "cs", 32 | update_n_lines = "", -- bind for updating 'config.n_lines' 33 | }, 34 | -- Number of lines within which surrounding is searched 35 | n_lines = 68, 36 | -- Duration (in ms) of highlight when calling `MiniSurround.highlight()` 37 | highlight_duration = 2000, 38 | -- How to search for surrounding (first inside current line, then inside 39 | -- neighborhood). One of 'cover', 'cover_or_next', 'cover_or_prev', 40 | -- 'cover_or_nearest'. For more details, see `:h MiniSurround.config`. 41 | search_method = "cover_or_next", 42 | }) 43 | 44 | -- Remap adding surrounding to Visual mode selection 45 | vim.keymap.set("x", "S", [[:lua MiniSurround.add('visual')]], { silent = true }) 46 | 47 | -- unmap config generated `ys` mapping, prevents visual mode yank delay 48 | vim.keymap.del("x", "ys") 49 | 50 | -- Make special mapping for "add surrounding for line" 51 | vim.keymap.set("n", "yss", "ys_", { remap = true }) 52 | -------------------------------------------------------------------------------- /lua/plugins/oil.lua: -------------------------------------------------------------------------------- 1 | return { 2 | "stevearc/oil.nvim", 3 | enabled = true, 4 | cmd = { "Oil" }, 5 | keys = { 6 | { mode = { "n", "v" }, "-", "Oil", desc = "Open parent directory [Oil]" }, 7 | }, 8 | opts = { 9 | keymaps = { 10 | [""] = false, 11 | [""] = false, 12 | [""] = false, 13 | [""] = false, 14 | [""] = { "actions.select", opts = { horizontal = true } }, 15 | ["gq"] = { "actions.close", mode = "n" }, 16 | ["gp"] = "actions.preview", 17 | [""] = "actions.preview", 18 | }, 19 | }, 20 | } 21 | -------------------------------------------------------------------------------- /lua/plugins/snacks/init.lua: -------------------------------------------------------------------------------- 1 | local M = { 2 | "folke/snacks.nvim", 3 | enabled = true, 4 | event = "VeryLazy", 5 | } 6 | 7 | function M.init() 8 | require("plugins.snacks.mappings").map() 9 | end 10 | 11 | function M.config() 12 | local layouts = require("snacks.picker.config.layouts") 13 | for _, k in ipairs({ "default", "vertical" }) do 14 | layouts[k].layout.width = 0.8 15 | layouts[k].layout.height = 0.85 16 | end 17 | -- layouts.default.layout[1].width = 0.40 -- main win width 18 | -- layouts.vertical.layout[3].height = 0.45 -- prev win height 19 | require("snacks").setup({ 20 | ---@type snacks.image.Config 21 | image = { 22 | enabled = true, 23 | }, 24 | ---@type snacks.picker.Config 25 | picker = { 26 | ui_select = false, 27 | -- layout = { preset = "ivy" }, 28 | layout = { preset = function() return vim.o.columns >= 140 and "default" or "vertical" end }, 29 | ---@class snacks.picker.previewers.Config 30 | previewers = { 31 | git = { native = true }, 32 | }, 33 | win = { 34 | input = { 35 | keys = { 36 | -- [""] = { function() print("test") end, mode = { "i", "n" } }, 37 | [""] = false, 38 | [""] = false, 39 | [""] = { "close", mode = { "i", "n" } }, 40 | [""] = { "list_scroll_up", mode = { "i", "n" } }, 41 | [""] = { "list_scroll_down", mode = { "i", "n" } }, 42 | [""] = { "history_back", mode = { "i", "n" } }, 43 | [""] = { "history_forward", mode = { "i", "n" } }, 44 | [""] = { "preview_scroll_up", mode = { "i", "n" } }, 45 | [""] = { "preview_scroll_down", mode = { "i", "n" } }, 46 | [""] = { "toggle_help", mode = { "i", "n" } }, 47 | [""] = { "toggle_maximize", mode = { "i", "n" } }, 48 | [""] = { "toggle_preview", mode = { "i", "n" } }, 49 | } 50 | } 51 | } 52 | } 53 | }) 54 | end 55 | 56 | return M 57 | -------------------------------------------------------------------------------- /lua/plugins/snacks/mappings.lua: -------------------------------------------------------------------------------- 1 | local keys = { 2 | ---@format disable 3 | -- Image 4 | -- { "K", function() require "snacks".image.hover() end, desc = "Image Hover" }, 5 | -- Picker 6 | { ";", function() require "snacks".picker.buffers() end, desc = "Buffers" }, 7 | { "F?", function() require "snacks".picker.pickers() end, desc = "Snacks Pickers" }, 8 | { "F/", function() require "snacks".picker.search_history() end, desc = "Command History" }, 9 | { "F:", function() require "snacks".picker.command_history() end, desc = "Command History" }, 10 | { "Fx", function() require "snacks".picker.commands() end, desc = "Commands" }, 11 | { "", function() require "snacks".picker.help() end, desc = "Help Pages" }, 12 | -- find 13 | { "", function() require "snacks".picker.files() end, desc = "Find Files" }, 14 | { "", function() require "snacks".picker.zoxide() end, desc = "Zoxide" }, 15 | { "Fp", function() require "snacks".picker.files({ cwd = vim.fn.stdpath("data") .. "/lazy" }) end, desc = "Find Plugin File" }, 16 | { "Ff", function() require "snacks".picker.resume() end, desc = "Resume" }, 17 | { "FF", function() require "snacks".picker.resume() end, desc = "Resume" }, 18 | { "Fh", function() require "snacks".picker.recent() end, desc = "Recent" }, 19 | -- git 20 | { "Gf", function() require "snacks".picker.git_files() end, desc = "Find Git Files" }, 21 | { "GB", function() require "snacks".picker.git_branches() end, desc = "Git Branches" }, 22 | { "Gc", function() require "snacks".picker.git_log_file() end, desc = "Git Log" }, 23 | { "GC", function() require "snacks".picker.git_log() end, desc = "Git Log" }, 24 | { "Gs", function() require "snacks".picker.git_status() end, desc = "Git Status" }, 25 | -- Grep 26 | { "Fl", function() require "snacks".picker.grep() end, desc = "Grep" }, 27 | { "Fb", function() require "snacks".picker.lines() end, desc = "Buffer Lines" }, 28 | { "FB", function() require "snacks".picker.grep_buffers() end, desc = "Grep Open Buffers" }, 29 | { "Fw", function() require "snacks".picker.grep_word() end, desc = "Visual selection or word", mode = { "n", "v" } }, 30 | -- search 31 | { 'F"', function() require "snacks".picker.registers() end, desc = "Registers" }, 32 | { "Fa", function() require "snacks".picker.autocmds() end, desc = "Autocmds" }, 33 | { "FO", function() require "snacks".picker.highlights() end, desc = "Highlights" }, 34 | { "Fj", function() require "snacks".picker.jumps() end, desc = "Jumps" }, 35 | { "Fk", function() require "snacks".picker.keymaps() end, desc = "Keymaps" }, 36 | { "Fq", function() require "snacks".picker.qflist() end, desc = "Quickfix List" }, 37 | { "FQ", function() require "snacks".picker.loclist() end, desc = "Location List" }, 38 | { "Fm", function() require "snacks".picker.marks() end, desc = "Marks" }, 39 | { "FM", function() require "snacks".picker.man() end, desc = "Man Pages" }, 40 | { "Fo", function() require "snacks".picker.colorschemes() end, desc = "Colorschemes" }, 41 | { "FP", function() require "snacks".picker.projects() end, desc = "Projects" }, 42 | { "Fz", function() require "snacks".picker.spelling() end, desc = "Zoxide" }, 43 | -- LSP 44 | { "Ld", function() require "snacks".picker.lsp_definitions() end, desc = "Goto Definition" }, 45 | { "LD", function() require "snacks".picker.lsp_declarations() end, desc = "Goto Declaration" }, 46 | { "Lr", function() require "snacks".picker.lsp_references() end, nowait = true, desc = "References" }, 47 | { "Lm", function() require "snacks".picker.lsp_implementations() end, desc = "Goto Implementation" }, 48 | { "Ly", function() require "snacks".picker.lsp_type_definitions() end, desc = "Goto T[y]pe Definition" }, 49 | { "Ls", function() require "snacks".picker.lsp_symbols() end, desc = "LSP Symbols (buffer)" }, 50 | { "LS", function() require "snacks".picker.lsp_workspace_symbols() end, desc = "LSP Symbols (workspace)" }, 51 | { "Lg", function() require "snacks".picker.diagnostics_buffer() end, desc = "Buffer Diagnostics" }, 52 | { "LG", function() require "snacks".picker.diagnostics() end, desc = "Workspace Diagnostics" }, 53 | } 54 | 55 | return { 56 | map = function() 57 | for _, m in ipairs(keys) do 58 | local key = m[1] 59 | if require "utils".USE_SNACKS then 60 | key = key:gsub(";", ",") 61 | for _, k in ipairs({ "", "", "" }) do 62 | if key:match(k:gsub("%-", "%%-") .. "$") then key = k end 63 | end 64 | key = key:gsub("%u", function(x) 65 | return x:sub(1, -2) .. x:sub(-1):lower() 66 | end) 67 | end 68 | local opts = vim.deepcopy(m) 69 | opts[1], opts[2], opts.mode = nil, nil, nil 70 | vim.keymap.set(m.mode or "n", key, m[2], opts) 71 | end 72 | end 73 | } 74 | -------------------------------------------------------------------------------- /lua/plugins/treesitter.lua: -------------------------------------------------------------------------------- 1 | return { 2 | { 3 | "nvim-treesitter/nvim-treesitter-context", 4 | dependencies = { "nvim-treesitter/nvim-treesitter-textobjects" }, 5 | cmd = { "TSContextEnable", "TSContextDisable", "TSContextToggle" }, 6 | keys = { 7 | { 8 | "[C", 9 | function() 10 | require("treesitter-context").go_to_context(vim.v.count1) 11 | end, 12 | silent = true, 13 | desc = "Goto treesitter context" 14 | }, 15 | { 16 | "]C", 17 | function() require("treesitter-context").toggle() end, 18 | silent = true, 19 | desc = "Toggle treesitter context" 20 | }, 21 | }, 22 | opts = {}, 23 | config = function() 24 | require("treesitter-context").setup({ enable = true }) 25 | end, 26 | enabled = true 27 | }, 28 | { 29 | "nvim-treesitter/nvim-treesitter", 30 | -- treesitter requires a C compiler 31 | cond = not require("utils").is_NetBSD() and require("utils").have_compiler, 32 | event = "BufReadPost", 33 | cmd = { "TSUpdate", "TSUpdateSync" }, 34 | dependencies = { 35 | "nvim-treesitter/nvim-treesitter-textobjects", 36 | "nvim-treesitter/nvim-treesitter-context", 37 | }, 38 | build = function() 39 | -- build step is run independent of the condition 40 | -- make sure we have treesitter before running ':TSUpdate' 41 | if require("utils").have_compiler() then 42 | vim.cmd("TSUpdate") 43 | end 44 | end, 45 | config = function() 46 | require "nvim-treesitter.configs".setup { 47 | ensure_installed = { 48 | "bash", 49 | "c", 50 | "cpp", 51 | "go", 52 | "javascript", 53 | "typescript", 54 | "json", 55 | "jsonc", 56 | "jsdoc", 57 | "lua", 58 | "python", 59 | "rust", 60 | "html", 61 | "yaml", 62 | "css", 63 | "toml", 64 | "markdown", 65 | "markdown_inline", 66 | "solidity", 67 | "vimdoc", 68 | -- for `nvim-treesitter/playground` / `:InspectTree` 69 | "query", 70 | }, 71 | highlight = { enable = true }, 72 | incremental_selection = { 73 | enable = true, 74 | keymaps = { 75 | init_selection = "", 76 | node_incremental = "", 77 | node_decremental = "", 78 | scope_incremental = "", 79 | }, 80 | }, 81 | textobjects = { 82 | select = { 83 | enable = true, 84 | -- Automatically jump forward to textobj, similar to targets.vim 85 | lookahead = true, 86 | keymaps = { 87 | ["ac"] = { query = "@comment.outer", desc = "Select comment (outer)" }, 88 | ["ic"] = { query = "@comment.inner", desc = "Select comment (inner)" }, 89 | ["ao"] = { query = "@class.outer", desc = "Select class (outer)" }, 90 | ["io"] = { query = "@class.inner", desc = "Select class (inner)" }, 91 | ["af"] = { query = "@function.outer", desc = "Select function (outer)" }, 92 | ["if"] = { query = "@function.inner", desc = "Select function (inner)" }, 93 | ["ap"] = { query = "@parameter.outer", desc = "Select parameter (outer)" }, 94 | ["ip"] = { query = "@parameter.inner", desc = "Select parameter (inner)" }, 95 | ["as"] = { query = "@scope", query_group = "locals", desc = "Select language scope" } 96 | }, 97 | selection_modes = { 98 | -- default is charwise 'v' 99 | ["@parameter.inner"] = "v", -- charwise 100 | ["@parameter.outer"] = "v", -- charwise 101 | ["@function.inner"] = "V", -- linewise 102 | ["@function.outer"] = "V", -- linewise 103 | ["@class.inner"] = "V", -- linewise 104 | ["@class.outer"] = "V", -- linewise 105 | ["@scope"] = "v", -- charwise 106 | }, 107 | }, 108 | move = { 109 | enable = true, 110 | set_jumps = true, -- whether to set jumps in the jumplist 111 | goto_next = { -- jump to next start or end 112 | ["]f"] = { query = "@function.outer", desc = "Next function start|end" }, 113 | -- overrides next misspelled word 114 | -- ["]s"] = { query = "@scope", query_group = "locals", desc = "Next scope" }, 115 | }, 116 | goto_previous = { -- jump to previous start or end 117 | ["[f"] = { query = "@function.outer", desc = "Previous function start" }, 118 | -- overrides previous misspelled word 119 | -- ["[s"] = { query = "@scope", query_group = "locals", desc = "Previous scope" }, 120 | }, 121 | goto_next_start = { 122 | ["]F"] = { query = "@function.outer", desc = "Next function" }, 123 | ["]p"] = { query = "@parameter.inner", desc = "Next parameter" }, 124 | }, 125 | goto_previous_start = { 126 | ["[F"] = { query = "@function.outer", desc = "Previous function" }, 127 | ["[p"] = { query = "@parameter.inner", desc = "Previous parameter" }, 128 | }, 129 | }, 130 | }, 131 | } 132 | -- repeat `]f` moves with `;,` disabled as doesn't fallback to normal `;,` 133 | -- local ts_repeat_move = require "nvim-treesitter.textobjects.repeatable_move" 134 | -- vim.keymap.set({ "n", "x", "o" }, ";", ts_repeat_move.repeat_last_move) 135 | -- vim.keymap.set({ "n", "x", "o" }, ",", ts_repeat_move.repeat_last_move_opposite) 136 | end, 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /lua/plugins/ts-vimdoc.lua: -------------------------------------------------------------------------------- 1 | local M = { 2 | "ibhagwan/ts-vimdoc.nvim", 3 | dev = require("utils").is_dev("ts-vimdoc.nvim"), 4 | dependencies = { "nvim-treesitter/nvim-treesitter" }, 5 | } 6 | 7 | M.init = function() 8 | local function docgen(opts) 9 | local vimdoc = require("ts-vimdoc") 10 | opts.project_name = opts.project_name or vim.fn.fnamemodify(vim.uv.cwd(), ":t:r") 11 | opts.input_file = vim.fn.fnamemodify(opts.input_file, ":p") 12 | opts.output_file = opts.outpt_file or 13 | vim.fn.fnamemodify(string.format("doc/%s.txt", opts.project_name), ":p") 14 | if not vim.uv.fs_stat(opts.input_file) then 15 | require("utils").warn(("'%s' is inaccessible") 16 | :format(vim.fn.fnamemodify(opts.input_file, ":."))) 17 | return 18 | end 19 | if not vim.uv.fs_stat("doc") then 20 | vim.fn.mkdir("doc", "p") 21 | end 22 | vimdoc.docgen(vim.tbl_extend("keep", opts, { version = "For Neovim >= 0.8.0" })) 23 | require "utils".info(("Successfully generated %s") 24 | :format(vim.fn.fnamemodify(opts.output_file, ":."))) 25 | end 26 | 27 | vim.api.nvim_create_user_command("DocgenREADME", function() 28 | docgen({ 29 | input_file = "README.md", 30 | table_of_contents_lvl_min = 2, 31 | table_of_contents_lvl_max = 4, 32 | }) 33 | end, {}) 34 | 35 | vim.api.nvim_create_user_command("DocgenOPTIONS", function() 36 | docgen({ 37 | input_file = "OPTIONS.md", 38 | output_file = "doc/fzf-lua-opts.txt", 39 | project_name = "fzf-lua-opts", 40 | table_of_contents_lvl_min = 2, 41 | table_of_contents_lvl_max = 3, 42 | }) 43 | end, {}) 44 | end 45 | 46 | return M 47 | -------------------------------------------------------------------------------- /lua/plugins/which_key.lua: -------------------------------------------------------------------------------- 1 | local M = { 2 | "folke/which-key.nvim", 3 | enabled = vim.fn.has("nvim-0.9.4") == 1, 4 | event = "VeryLazy", 5 | } 6 | 7 | M.keys = { 8 | { 9 | mode = { "n", "v" }, 10 | "?", 11 | function() 12 | require("which-key").show({ global = true }) 13 | end, 14 | silent = true, 15 | desc = "Which-key root" 16 | } 17 | } 18 | 19 | M.config = function() 20 | local wk = require("which-key") 21 | wk.setup({ 22 | ---@type false | "classic" | "modern" | "helix" 23 | preset = "helix", 24 | plugins = { 25 | marks = true, -- shows a list of your marks on ' and ` 26 | registers = true, -- shows your registers on " in NORMAL or in INSERT mode 27 | -- the presets plugin, adds help for a bunch of default keybindings in Neovim 28 | -- No actual key bindings are created 29 | spelling = { 30 | enabled = true, -- enabling this will show WhichKey when pressing z= to spell suggest 31 | suggestions = 20 -- how many suggestions should be shown in the list? 32 | }, 33 | presets = { 34 | operators = false, -- adds help for operators like d, y, ... 35 | motions = false, -- adds help for motions 36 | text_objects = false, -- help for text objects triggered after entering an operator 37 | windows = true, -- default bindings on 38 | nav = true, -- misc bindings to work with windows 39 | z = true, -- bindings for folds, spelling and others prefixed with z 40 | g = true -- bindings for prefixed with g 41 | } 42 | }, 43 | icons = { mappings = false }, 44 | }) 45 | 46 | local opts = { nowait = false, remap = false } 47 | local add = { 48 | { "g", group = "Git", unpack(opts) }, 49 | { "G", group = "Git", unpack(opts) }, 50 | { "y", group = "Git (yadm)", unpack(opts) }, 51 | { "l", group = "Lsp", unpack(opts) }, 52 | { "L", group = "Lsp", unpack(opts) }, 53 | { "d", group = "Dap", unpack(opts) }, 54 | { "e", group = "Nvim-tree", unpack(opts) }, 55 | { "f", group = "Fzf", unpack(opts) }, 56 | { "F", group = "Telescope", unpack(opts) }, 57 | { "h", group = "Gitsigns", unpack(opts) }, 58 | { "", desc = "Launch scratch terminal", unpack(opts) }, 59 | { "", desc = "Tmux-aware win left", unpack(opts) }, 60 | { "", desc = "Tmux-aware win right", unpack(opts) }, 61 | { "", desc = "Tmux-aware win down", unpack(opts) }, 62 | { "", desc = "Tmux-aware win up", unpack(opts) }, 63 | { "", desc = "Tmux-aware win last", unpack(opts) }, 64 | { "", desc = "Treesitter incremental selection", unpack(opts) }, 65 | { "", desc = "Clear and redraw screen", unpack(opts) }, 66 | { "", desc = "Redo", unpack(opts) }, 67 | { "u", desc = "Undo", unpack(opts) }, 68 | { "U", desc = "Undo line", unpack(opts) }, 69 | { ".", desc = "Repeat last edit", unpack(opts) }, 70 | { "%", desc = "Cycle through matchit group", unpack(opts) }, 71 | { "[[", desc = "Previous class/object end", unpack(opts) }, 72 | { "[]", desc = "Previous class/object start", unpack(opts) }, 73 | { "][", desc = "Next class/object end", unpack(opts) }, 74 | { "]]", desc = "Next class/object start", unpack(opts) }, 75 | { "g$", desc = "goto visual line end", unpack(opts) }, 76 | { "g%", desc = "goto previous matching group", unpack(opts) }, 77 | { "g0", desc = "goto visual line start", unpack(opts) }, 78 | { "g8", desc = "print hex value under cursor", unpack(opts) }, 79 | { "g<", desc = "display last !command output", unpack(opts) }, 80 | { "g", desc = "print current curosr pos info", unpack(opts) }, 81 | { "gE", desc = "previous end of WORD", unpack(opts) }, 82 | { "gF", desc = "goto file:line under cursor", unpack(opts) }, 83 | { "gM", desc = "goto middle of text line", unpack(opts) }, 84 | { "gT", desc = "goto prev tab", unpack(opts) }, 85 | { "g_", desc = "goto last non-EOL char", unpack(opts) }, 86 | { "ga", desc = "print ascii value under cursor", unpack(opts) }, 87 | { "gt", desc = "goto next tab", unpack(opts) }, 88 | } 89 | wk.add(add) 90 | end 91 | 92 | return M 93 | -------------------------------------------------------------------------------- /lua/term.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | M._key = "" 4 | M._desc = "Toggle terminal" 5 | M._hl = "iBhagwanTerm" 6 | M._size = 0.4 7 | 8 | local function ensure_hl() 9 | if vim.tbl_isempty(vim.api.nvim_get_hl(0, { name = M._hl })) then 10 | local norm = vim.api.nvim_get_hl(0, { name = "Normal" }) 11 | vim.api.nvim_set_hl(0, M._hl, { 12 | default = false, fg = norm.fg, bg = "#1b2738", ctermfg = norm.ctermfg, ctermbg = "NONE" 13 | }) 14 | end 15 | return M._hl 16 | end 17 | 18 | local termopen = vim.fn.has("nvim-0.11") ~= 1 and vim.fn.termopen 19 | or function(cmd, opts) 20 | opts = opts or {} 21 | opts.term = true 22 | return vim.fn.jobstart(cmd, opts) 23 | end 24 | 25 | M.toggleterm = function() 26 | if not M._buf or not vim.api.nvim_buf_is_valid(M._buf) then 27 | M._buf = vim.api.nvim_create_buf(false, false) 28 | end 29 | 30 | M._win = vim.iter(vim.fn.win_findbuf(M._buf)):find(function(b_wid) 31 | return vim.iter(vim.api.nvim_tabpage_list_wins(0)):any(function(t_wid) 32 | return b_wid == t_wid 33 | end) 34 | end) or (function() 35 | M._new = true 36 | local height = math.floor(vim.o.lines * M._size) 37 | vim.cmd("botright split | resize " .. tostring(height)) 38 | local win = vim.api.nvim_get_current_win() 39 | vim.api.nvim_win_set_buf(win, M._buf) 40 | local hl = ensure_hl() 41 | vim.wo[win].winhl = string.format("Normal:%s,NormalNC:%s", hl, hl) 42 | return win 43 | end)() 44 | 45 | if M._new or vim.api.nvim_win_get_config(M._win).hide then 46 | M._new = nil 47 | vim.api.nvim_win_set_config(M._win, { hide = false }) 48 | vim.api.nvim_set_current_win(M._win) 49 | if vim.bo[M._buf].channel <= 0 then 50 | termopen({ vim.o.shell }) 51 | vim.keymap.set({ "n", "t" }, M._key, M.toggleterm, { buffer = M._buf, desc = M._desc }) 52 | end 53 | vim.cmd("startinsert") 54 | else 55 | vim.api.nvim_win_call(M._win, function() 56 | vim.cmd.hide() 57 | end) 58 | vim.api.nvim_set_current_win(vim.fn.win_getid(vim.fn.winnr("#"))) 59 | end 60 | end 61 | 62 | vim.keymap.set("n", M._key, M.toggleterm, { desc = M._desc }) 63 | 64 | 65 | return M 66 | -------------------------------------------------------------------------------- /lua/utils.lua: -------------------------------------------------------------------------------- 1 | -- help to inspect results, e.g.: 2 | -- ':lua _G.dump(vim.fn.getwininfo())' 3 | -- neovim 0.7 has 'vim.pretty_print()) 4 | function _G.dump(...) 5 | local objects = vim.tbl_map(vim.inspect, { ... }) 6 | print(unpack(objects)) 7 | end 8 | 9 | local DEV_DIR = "$HOME/Sources/nvim" 10 | 11 | local M = {} 12 | 13 | M.__HAS_NVIM_011 = vim.fn.has("nvim-0.11") == 1 14 | M.__IS_WIN = vim.fn.has("win32") == 1 or vim.fn.has("win64") == 1 15 | 16 | local fast_event_aware_notify = function(msg, level, opts) 17 | if vim.in_fast_event() then 18 | vim.schedule(function() 19 | vim.notify(msg, level, opts) 20 | end) 21 | else 22 | vim.notify(msg, level, opts) 23 | end 24 | end 25 | 26 | function M.info(msg) 27 | fast_event_aware_notify(msg, vim.log.levels.INFO, {}) 28 | end 29 | 30 | function M.warn(msg) 31 | fast_event_aware_notify(msg, vim.log.levels.WARN, {}) 32 | end 33 | 34 | function M.err(msg) 35 | fast_event_aware_notify(msg, vim.log.levels.ERROR, {}) 36 | end 37 | 38 | function M.is_root() 39 | return not M.__IS_WIN and vim.uv.getuid() == 0 40 | end 41 | 42 | function M.is_darwin() 43 | return vim.uv.os_uname().sysname == "Darwin" 44 | end 45 | 46 | function M.is_NetBSD() 47 | return vim.uv.os_uname().sysname == "NetBSD" 48 | end 49 | 50 | function M.is_iSH() 51 | return vim.uv.os_uname().release:match("%-ish$") ~= nil 52 | end 53 | 54 | M.USE_SNACKS = M.__IS_WIN or M.is_iSH() 55 | M.USE_BLINK_CMP = vim.fn.executable("cargo") == 1 and not M.is_NetBSD() 56 | 57 | function M.is_dev(path) 58 | return vim.uv.fs_stat(string.format("%s/%s", vim.fn.expand(DEV_DIR), path)) ~= nil 59 | end 60 | 61 | function M.shell_error() 62 | return vim.v.shell_error ~= 0 63 | end 64 | 65 | function M.have_compiler() 66 | if vim.fn.executable("cc") == 1 or 67 | vim.fn.executable("gcc") == 1 or 68 | vim.fn.executable("clang") == 1 or 69 | vim.fn.executable("cl") == 1 then 70 | return true 71 | end 72 | return false 73 | end 74 | 75 | function M.git_root(cwd, noerr) 76 | local cmd = { "git", "rev-parse", "--show-toplevel" } 77 | if cwd then 78 | table.insert(cmd, 2, "-C") 79 | table.insert(cmd, 3, vim.fn.expand(cwd)) 80 | end 81 | local output = vim.fn.systemlist(cmd) 82 | if M.shell_error() then 83 | if not noerr then M.info(unpack(output)) end 84 | return nil 85 | end 86 | return output[1] 87 | end 88 | 89 | function M.set_cwd(pwd) 90 | if not pwd then 91 | local parent = vim.fn.expand("%:h") 92 | pwd = M.git_root(parent, true) or parent 93 | end 94 | if vim.uv.fs_stat(pwd) then 95 | vim.cmd("cd " .. pwd) 96 | M.info(("pwd set to %s"):format(vim.fn.shellescape(pwd))) 97 | else 98 | M.warn(("Unable to set pwd to %s, directory is not accessible") 99 | :format(vim.fn.shellescape(pwd))) 100 | end 101 | end 102 | 103 | function M.get_visual_selection(nl_literal) 104 | -- this will exit visual mode 105 | -- use 'gv' to reselect the text 106 | local _, csrow, cscol, cerow, cecol 107 | local mode = vim.fn.mode() 108 | if mode == "v" or mode == "V" or mode == "" then 109 | -- if we are in visual mode use the live position 110 | _, csrow, cscol, _ = unpack(vim.fn.getpos(".")) 111 | _, cerow, cecol, _ = unpack(vim.fn.getpos("v")) 112 | if mode == "V" then 113 | -- visual line doesn't provide columns 114 | cscol, cecol = 0, 999 115 | end 116 | else 117 | -- otherwise, use the last known visual position 118 | _, csrow, cscol, _ = unpack(vim.fn.getpos("'<")) 119 | _, cerow, cecol, _ = unpack(vim.fn.getpos("'>")) 120 | end 121 | -- swap vars if needed 122 | if cerow < csrow then csrow, cerow = cerow, csrow end 123 | if cecol < cscol then cscol, cecol = cecol, cscol end 124 | local lines = vim.fn.getline(csrow, cerow) 125 | -- local n = cerow-csrow+1 126 | local n = #lines 127 | if n <= 0 then return "" end 128 | lines[n] = string.sub(lines[n], 1, cecol) 129 | lines[1] = string.sub(lines[1], cscol) 130 | return table.concat(lines, nl_literal and "\\n" or "\n") 131 | end 132 | 133 | -- 'q': find the quickfix window 134 | -- 'l': find all loclist windows 135 | function M.find_qf(type) 136 | local wininfo = vim.fn.getwininfo() 137 | local win_tbl = {} 138 | for _, win in pairs(wininfo) do 139 | local found = false 140 | if type == "l" and win["loclist"] == 1 then 141 | found = true 142 | end 143 | -- loclist window has 'quickfix' set, eliminate those 144 | if type == "q" and win["quickfix"] == 1 and win["loclist"] == 0 then 145 | found = true 146 | end 147 | if found then 148 | table.insert(win_tbl, { winid = win["winid"], bufnr = win["bufnr"] }) 149 | end 150 | end 151 | return win_tbl 152 | end 153 | 154 | -- open quickfix if not empty 155 | function M.open_qf() 156 | local qf_name = "quickfix" 157 | local qf_empty = function() return vim.tbl_isempty(vim.fn.getqflist()) end 158 | if not qf_empty() then 159 | vim.cmd("copen") 160 | vim.cmd("wincmd J") 161 | else 162 | print(string.format("%s is empty.", qf_name)) 163 | end 164 | end 165 | 166 | -- enum all non-qf windows and open 167 | -- loclist on all windows where not empty 168 | function M.open_loclist_all() 169 | local wininfo = vim.fn.getwininfo() 170 | local qf_name = "loclist" 171 | local qf_empty = function(winnr) return vim.tbl_isempty(vim.fn.getloclist(winnr)) end 172 | for _, win in pairs(wininfo) do 173 | if win["quickfix"] == 0 then 174 | if not qf_empty(win["winnr"]) then 175 | -- switch active window before ':lopen' 176 | vim.api.nvim_set_current_win(win["winid"]) 177 | vim.cmd("lopen") 178 | else 179 | print(string.format("%s is empty.", qf_name)) 180 | end 181 | end 182 | end 183 | end 184 | 185 | -- toggle quickfix/loclist on/off 186 | -- type='*': qf toggle and send to bottom 187 | -- type='l': loclist toggle (all windows) 188 | function M.toggle_qf(type) 189 | local windows = M.find_qf(type) 190 | if #windows > 0 then 191 | -- hide all visible windows 192 | for _, win in ipairs(windows) do 193 | vim.api.nvim_win_hide(win.winid) 194 | end 195 | else 196 | -- no windows are visible, attempt to open 197 | if type == "l" then 198 | M.open_loclist_all() 199 | else 200 | M.open_qf() 201 | end 202 | end 203 | end 204 | 205 | -- expand or minimize current buffer in a more natural direction (tmux-like) 206 | -- ':resize <+-n>' or ':vert resize <+-n>' increases or decreasese current 207 | -- window horizontally or vertically. When mapped to '' this 208 | -- can get confusing as left might actually be right, etc 209 | -- the below can be mapped to arrows and will work similar to the tmux binds 210 | -- map to: "lua require'utils'.resize(false, -5)" 211 | M.resize = function(vertical, margin) 212 | local cur_win = vim.api.nvim_get_current_win() 213 | -- go (possibly) right 214 | vim.cmd(string.format("wincmd %s", vertical and "l" or "j")) 215 | local new_win = vim.api.nvim_get_current_win() 216 | 217 | -- determine direction cond on increase and existing right-hand buffer 218 | local not_last = not (cur_win == new_win) 219 | local sign = margin > 0 220 | -- go to previous window if required otherwise flip sign 221 | if not_last == true then 222 | vim.cmd [[wincmd p]] 223 | else 224 | sign = not sign 225 | end 226 | 227 | local sign_str = sign and "+" or "-" 228 | local dir = vertical and "vertical " or "" 229 | local cmd = dir .. "resize " .. sign_str .. math.abs(margin) .. "" 230 | vim.cmd(cmd) 231 | end 232 | 233 | M.sudo_exec = function(cmd, print_output) 234 | vim.fn.inputsave() 235 | local password = vim.fn.inputsecret("Password: ") 236 | vim.fn.inputrestore() 237 | if not password or #password == 0 then 238 | M.warn("Invalid password, sudo aborted") 239 | return false 240 | end 241 | local out = vim.fn.system(string.format("sudo -p '' -S %s", cmd), password) 242 | if vim.v.shell_error ~= 0 then 243 | print("\r\n") 244 | M.err(out) 245 | return false 246 | end 247 | if print_output then print("\r\n", out) end 248 | return true 249 | end 250 | 251 | M.sudo_write = function(tmpfile, filepath) 252 | if not tmpfile then tmpfile = vim.fn.tempname() end 253 | if not filepath then filepath = vim.fn.expand("%") end 254 | if not filepath or #filepath == 0 then 255 | M.err("E32: No file name") 256 | return 257 | end 258 | -- `bs=1048576` is equivalent to `bs=1M` for GNU dd or `bs=1m` for BSD dd 259 | -- Both `bs=1M` and `bs=1m` are non-POSIX 260 | local cmd = string.format("dd if=%s of=%s bs=1048576", 261 | vim.fn.shellescape(tmpfile), 262 | vim.fn.shellescape(filepath)) 263 | -- no need to check error as this fails the entire function 264 | vim.api.nvim_exec2(string.format("write! %s", tmpfile), { output = true }) 265 | if M.sudo_exec(cmd) then 266 | -- refreshes the buffer and prints the "written" message 267 | vim.cmd.checktime() 268 | -- exit command mode 269 | vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes( 270 | "", true, false, true), "n", true) 271 | end 272 | vim.fn.delete(tmpfile) 273 | end 274 | 275 | M.osc52printf = function(...) 276 | local str = string.format(...) 277 | local base64 = vim.base64.encode(str) 278 | local osc52str = string.format("\x1b]52;c;%s\x07", base64) 279 | local bytes = vim.fn.chansend(vim.v.stderr, osc52str) 280 | assert(bytes > 0) 281 | M.info(string.format("[OSC52] %d chars copied (%d bytes)", #str, bytes)) 282 | end 283 | 284 | M.unload_modules = function(patterns) 285 | for _, p in ipairs(patterns) do 286 | if not p.mod and type(p[1]) == "string" then 287 | p = { mod = p[1], fn = p.fn } 288 | end 289 | local unloaded = false 290 | for m, _ in pairs(package.loaded) do 291 | if m:match(p.mod) then 292 | unloaded = true 293 | package.loaded[m] = nil 294 | M.info(string.format("UNLOADED module '%s'", m)) 295 | end 296 | end 297 | if unloaded and p.fn then 298 | p.fn() 299 | M.warn(string.format("RELOADED module '%s'", p.mod)) 300 | end 301 | end 302 | end 303 | 304 | M.reload_config = function() 305 | require("fzf-lua").deregister_ui_select() 306 | M.unload_modules({ 307 | { 308 | "^options$", 309 | fn = function() 310 | -- Ignore events or gitsigns croaks on "OptionSet" 311 | -- OptionSet Autocommands for "fileformat": attempt to yield across C-call 312 | local save_ei = vim.o.eventignore 313 | vim.o.eventignore = "all" 314 | require("options") 315 | vim.o.eventignore = save_ei 316 | end 317 | }, 318 | { "^autocmd$", fn = function() require("autocmd") end }, 319 | { "^keymaps$", fn = function() require("keymaps") end }, 320 | { "^utils$" }, 321 | { "^term$" }, 322 | { mod = "ts%-vimdoc" }, 323 | { mod = "smartyank", fn = function() require("smartyank") end }, 324 | { mod = "fzf%-lua", fn = function() require("plugins.fzf-lua.setup").setup() end }, 325 | { mod = "mini.statusline", fn = function() require("plugins.mini.statusline").setup() end }, 326 | { mod = "dap%.", fn = function() require("plugins.dap").config() end }, 327 | -- { 328 | -- mod = "snacks", 329 | -- fn = function() 330 | -- require("plugins.snacks").init() 331 | -- require("plugins.snacks").config() 332 | -- end 333 | -- }, 334 | }) 335 | -- re-source all language specific settings, scans all runtime files under 336 | -- '/usr/share/nvim/runtime/(indent|syntax)' and 'after/ftplugin' 337 | local ft = vim.bo.filetype 338 | vim.tbl_filter(function(s) 339 | for _, e in ipairs({ "vim", "lua" }) do 340 | if ft and #ft > 0 and vim.fs.normalize(s):match(("/%s.%s"):format(ft, e)) then 341 | local file = vim.fn.expand(s:match("[^: ]*$")) 342 | vim.cmd.source(file) 343 | M.warn("RESOURCED " .. vim.fn.fnamemodify(file, ":.")) 344 | return s 345 | end 346 | end 347 | return false 348 | end, vim.fn.split(vim.fn.execute("scriptnames"), "\n")) 349 | -- remove last search highlight 350 | vim.cmd("nohl") 351 | end 352 | 353 | M.win_is_float = function(winnr) 354 | local wincfg = vim.api.nvim_win_get_config(winnr) 355 | if wincfg and (wincfg.external or wincfg.relative and #wincfg.relative > 0) then 356 | return true 357 | end 358 | return false 359 | end 360 | 361 | M.tmux_aware_navigate = function(direction, no_wrap) 362 | local curwin = vim.api.nvim_get_current_win() 363 | -- First attempt to send a wincmd, skip if window is floating 364 | -- Do not skip "alt-h" due to fzf-lua's toggle_hidden default 365 | if not M.win_is_float(curwin) or direction == "h" then 366 | vim.cmd.wincmd(direction == "o" and "w" or direction) 367 | if not vim.env.TMUX or vim.api.nvim_get_current_win() ~= curwin then 368 | -- Stop here if no TMUX or wincmd switched windows 369 | return 370 | end 371 | end 372 | -- tmux exists and window wasn't switche 373 | -- forward the command to tmux 374 | local tmux_pane_flag = { 375 | ["h"] = "-L", 376 | ["j"] = "-D", 377 | ["k"] = "-U", 378 | ["l"] = "-R", 379 | ["o"] = "-l", 380 | } 381 | local tmux_pane_to = { 382 | ["h"] = "left", 383 | ["j"] = "bottom", 384 | ["k"] = "top", 385 | ["l"] = "right", 386 | } 387 | local args = { "tmux" } 388 | if no_wrap then 389 | table.insert(args, "if-shell") 390 | table.insert(args, "-F") 391 | table.insert(args, string.format("#{pane_at_%s}", tmux_pane_to[direction])) 392 | table.insert(args, "") 393 | table.insert(args, string.format("select-pane -t %s %s", 394 | vim.env.TMUX_PANE, tmux_pane_flag[direction])) 395 | else 396 | table.insert(args, "select-pane") 397 | table.insert(args, "-t") 398 | table.insert(args, vim.env.TMUX_PANE) 399 | table.insert(args, tmux_pane_flag[direction]) 400 | end 401 | vim.fn.system(args) 402 | end 403 | 404 | M.tmux_is_zoomed = function() 405 | if not vim.env.TMUX then return end 406 | local out = vim.fn.system({ "tmux", "display-message", "-p", "#{window_flags}" }) 407 | return type(out) == "string" and out:match("Z") and 1 or 0 408 | end 409 | 410 | M.tmux_toggle_Z = function() 411 | if not vim.env.TMUX then return end 412 | vim.fn.system({ "tmux", "resize-pane", "-Z" }) 413 | return true 414 | end 415 | 416 | M.tmux_zoom = function() 417 | if M.tmux_is_zoomed() == 0 then 418 | return M.tmux_toggle_Z() 419 | end 420 | end 421 | 422 | M.tmux_unzoom = function() 423 | if M.tmux_is_zoomed() == 1 then 424 | return M.tmux_toggle_Z() 425 | end 426 | end 427 | 428 | M.dap_pick_exec = function() 429 | local fzf = require("fzf-lua") 430 | return coroutine.create(function(dap_co) 431 | local dap_abort = function() coroutine.resume(dap_co, require("dap").ABORT) end 432 | local dap_run = function(exec) 433 | if type(exec) == "string" and vim.uv.fs_stat(exec) then 434 | -- Make full path 435 | exec = vim.fn.fnamemodify(exec, ":p") 436 | coroutine.resume(dap_co, exec) 437 | else 438 | if exec ~= "" then 439 | M.warn(string.format("'%s' is not executable, aborting.", exec)) 440 | end 441 | dap_abort() 442 | end 443 | end 444 | fzf.files({ 445 | cwd = vim.uv.cwd(), 446 | -- cwd_header = true, 447 | -- cwd_prompt = false, 448 | -- prompt = "DAP: Select Executable> ", 449 | git_icons = false, 450 | cmd = "fd --color=never --no-ignore --type x --hidden --follow --exclude .git", 451 | header = (":: %s to execute prompt"):format(fzf.utils.ansi_codes["yellow"]("")), 452 | winopts = { 453 | width = 0.65, 454 | height = 0.45, 455 | preview = { hidden = "hidden" }, 456 | title = { { " DAP: Select Executable to Debug ", "Cursor" } }, 457 | title_pos = "center", 458 | }, 459 | actions = { 460 | ["esc"] = dap_abort, 461 | ["ctrl-c"] = dap_abort, 462 | ["ctrl-g"] = false, 463 | ["ctrl-e"] = function(_, opts) dap_run(opts.last_query) end, 464 | ["default"] = function(sel) 465 | if not sel[1] then 466 | dap_abort() 467 | else 468 | dap_run(fzf.path.entry_to_file(sel[1]).path) 469 | end 470 | end, 471 | }, 472 | }) 473 | end) 474 | end 475 | 476 | M.dap_pick_process = function(fzflua_opts, getproc_opts) 477 | local fzf = require("fzf-lua") 478 | return coroutine.create(function(dap_co) 479 | local dap_abort = function() coroutine.resume(dap_co, require("dap").ABORT) end 480 | local procs = require("dap.utils").get_processes(getproc_opts) 481 | fzf.fzf_exec( 482 | function(fzf_cb) 483 | for _, p in pairs(procs) do 484 | fzf_cb(string.format("[%d] %s", p.pid, p.name)) 485 | end 486 | fzf_cb() 487 | end, 488 | vim.tbl_deep_extend("keep", fzflua_opts or {}, { 489 | winopts = { 490 | preview = { hidden = "hidden" }, 491 | title = { { " DAP: Select Process to Debug ", "Cursor" } }, 492 | title_pos = "center", 493 | }, 494 | actions = { 495 | ["esc"] = dap_abort, 496 | ["ctrl-c"] = dap_abort, 497 | ["default"] = function(sel) 498 | if not sel[1] then 499 | dap_abort() 500 | else 501 | local pid = tonumber(sel[1]:match("^%[(%d+)%]")) 502 | coroutine.resume(dap_co, pid) 503 | end 504 | end, 505 | }, 506 | })) 507 | end) 508 | end 509 | 510 | function M.input(prompt) 511 | local ok, res 512 | if vim.ui then 513 | ok, _ = pcall(vim.ui.input, { prompt = prompt }, 514 | function(input) 515 | res = input 516 | end) 517 | else 518 | ok, res = pcall(vim.fn.input, { prompt = prompt, cancelreturn = 3 }) 519 | if res == 3 then 520 | ok, res = false, nil 521 | end 522 | end 523 | return ok and res or nil 524 | end 525 | 526 | function M.lsp_get_clients(opts) 527 | ---@diagnostic disable-next-line: deprecated 528 | if M.__HAS_NVIM_011 then 529 | return vim.lsp.get_clients(opts) 530 | end 531 | ---@diagnostic disable-next-line: deprecated 532 | local clients = opts.bufnr and vim.lsp.buf_get_clients(opts.bufnr) 533 | or opts.id and { vim.lsp.get_client_by_id(opts.id) } 534 | or vim.lsp.get_clients(opts) 535 | return vim.tbl_map(function(client) 536 | return setmetatable({ 537 | supports_method = function(_, ...) return client.supports_method(...) end, 538 | request = function(_, ...) return client.request(...) end, 539 | request_sync = function(_, ...) return client.request_sync(...) end, 540 | }, { __index = client }) 541 | end, clients) 542 | end 543 | 544 | return M 545 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ibhagwan/nvim-lua/92866bd9a6859bda3c5c2bfe7f981f2bdec6de62/screenshot.png --------------------------------------------------------------------------------