diff --git a/README.md b/README.md index e6afaa6..33e6725 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,14 @@ # Moshe Avni's DotFiles -(also, how to bootstrap a brand new Mac laptop) + + + ## Usage -0. Install xcode-select (for basically everything...) +### (also, how to bootstrap a brand new Mac laptop) + +0. Install xcode-select (for basically everything…) ```bash xcode-select --install @@ -15,8 +19,9 @@ ```bash /bin/bash -c \ "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)" + eval "$(/opt/homebrew/bin/brew shellenv)" # to make brew available before we load `~/.zshrc` that has "$PATH" brew update - brew install git + brew install git stow ``` 2. Clone this repo: @@ -26,71 +31,74 @@ cd ~ && git clone git@github.com:mosheavni/dotfiles.git .dotfiles && cd .dotfiles ``` -3. Install brew dependencies (generated with `brew bundle dump`) - - ```bash - brew bundle - ``` - -4. Install [asdf-vm](https://asdf-vm.com/guide/getting-started.html) and its - plugins +3. Install [antidote](https://antidote.sh/) ```bash - git clone https://github.com/asdf-vm/asdf.git ~/.asdf --branch v0.13.1 + git clone --depth=1 https://github.com/mattmc3/antidote.git ${ZDOTDIR:-~}/.antidote ``` - > **_NOTE:_** Reload shell +4. Stow the .dotfiles and reload the shell: ```bash - while read -r plugin_line;do - asdf plugin-add $(awk '{print $1}' <<<"$plugin_line") - done < asdf/.tool-versions - asdf install + ./start.sh + source ~/.zshrc ``` -5. Open [Wezterm](https://wezfurlong.org/wezterm/index.html) and start using a real terminal. - -6. Install [antidote](https://antidote.sh/) +5. Install brew dependencies (generated with `brew bundle dump`) ```bash - git clone --depth=1 https://github.com/mattmc3/antidote.git ${ZDOTDIR:-~}/.antidote + brew bundle ``` -7. Run `./start.sh` to create the symlinks between the repo dir and the home dir: +6. Open [Wezterm](https://wezfurlong.org/wezterm/index.html) and start using a real terminal. -8. Install npm packages +7. Install [asdf-vm](https://asdf-vm.com/guide/getting-started.html) plugins ```bash - npm install -g $(printf "%s " $( Install requires Neovim 0.10+. Always review the code before installing a configuration. + +Clone the repository and install the plugins: + +```sh +git clone git@github.com:mosheavni/dotfiles ~/.config/mosheavni/dotfiles +``` + +Open Neovim with this config: + +```sh +NVIM_APPNAME=mosheavni/dotfiles/nvim/.config/nvim nvim +``` ## Additional stuff @@ -98,10 +106,8 @@ - Download and install [docker](https://www.docker.com/products/docker-desktop) -- Change clipy shortcuts, and load snippets +- Change clipy and maccy shortcuts, and load snippets - Install [magnet](https://apps.apple.com/us/app/magnet/id441258766?mt=12) -- Install [Mac Media Key Forwarder](https://github.com/milgra/macmediakeyforwarder) - - Install Snagit diff --git a/asdf/.tool-versions b/asdf/.tool-versions index c4cf975..e40f577 100644 --- a/asdf/.tool-versions +++ b/asdf/.tool-versions @@ -5,12 +5,12 @@ golang 1.23.1 helm 3.14.4 java corretto-8.422.05.1 jq 1.6 -kubectl 1.30.10 +kubectl 1.31.7 kubectx 0.9.5 minikube 1.32.0 mysql 8.0.34 neovim nightly -nodejs 20.10.0 +nodejs 20.17.0 python 3.11.8 semver 3.4.0 terraform 1.5.5 @@ -23,4 +23,4 @@ maven 3.6.3 lua 5.1.5 rust stable vim 9.1.0958 -nodejs 20.10.0 +ripgrep 14.1.1 diff --git a/git/.gitconfig b/git/.gitconfig index f20c40e..8802443 100644 --- a/git/.gitconfig +++ b/git/.gitconfig @@ -2,8 +2,101 @@ excludesfile = ~/.gitignore_global pager = delta editor = vim + [user] - email = ofekn@netapp.com - name = onacht + name = ofek nacht + email = ofek.nacht@flexera.com + +[checkout] + defaultRemote = origin + [pull] - rebase = false + rebase = false + +[help] + autocorrect = 1 + +[branch] + autosetupmerge = always + +[alias] + log = log --pretty=log + lb = log --graph --simplify-by-decoration --pretty=shorter --all --notes --date-order --relative-date + adog = log --all --decorate --oneline --graph + +[init] + defaultBranch = master + +[interactive] + diffFilter = delta --color-only + +[delta] + features = decorations arctic-fox + navigate = true # use n and N to move between diff sections + line-numbers = true + light = false # set to true if you're in a terminal w/ a light background color (e.g. the default macOS terminal) + side-by-side = true + +[delta "arctic-fox"] + dark = true + syntax-theme = Nord + file-added-label = [+] + file-copied-label = [==] + file-modified-label = [*] + file-removed-label = [-] + file-renamed-label = [->] + file-style = omit + hunk-header-decoration-style = "#5E81AC" ul + hunk-header-file-style = blue ul bold + hunk-header-line-number-style = yellow box bold + hunk-header-style = file line-number syntax bold italic + plus-style = brightgreen + plus-emph-style = black green + minus-style = brightred + minus-emph-style = black red + line-numbers = true + line-numbers-minus-style = brightred + line-numbers-plus-style = brightgreen + line-numbers-left-style = "#5E81AC" + line-numbers-right-style = "#5E81AC" + line-numbers-zero-style = "#4C566A" + zero-style = syntax + whitespace-error-style = black bold + blame-code-style = syntax + blame-format = "{author:<18} {commit:<6} {timestamp:<15}" + blame-palette = "#2E3440" "#3B4252" "#434C5E" + merge-conflict-begin-symbol = ~ + merge-conflict-end-symbol = ~ + merge-conflict-ours-diff-header-style = yellow bold + merge-conflict-ours-diff-header-decoration-style = "#5E81AC" box + merge-conflict-theirs-diff-header-style = yellow bold + merge-conflict-theirs-diff-header-decoration-style = "#5E81AC" box + +[delta "interactive"] + keep-plus-minus-markers = false + +[delta "decorations"] + commit-decoration-style = blue ol + commit-style = raw + file-style = omit + hunk-header-decoration-style = blue box + hunk-header-file-style = red + hunk-header-line-number-style = "#067a00" + hunk-header-style = file line-number syntax + +[merge] + conflictstyle = diff3 + +[diff] + colorMoved = default + +[color] + branch = true + decorate = true + diff = true + grep = true + interactive = true + pager = true + showbranch = true + status = true + ui = true diff --git a/node/.default-npm-packages b/node/.default-npm-packages index 09eaef0..7f0325b 100644 --- a/node/.default-npm-packages +++ b/node/.default-npm-packages @@ -3,5 +3,6 @@ eslint eslint-config-react-app neovim npm +quicktype typescript yarn diff --git a/nvim/.config/nvim/Makefile b/nvim/.config/nvim/Makefile new file mode 100644 index 0000000..24b345d --- /dev/null +++ b/nvim/.config/nvim/Makefile @@ -0,0 +1,7 @@ +.PHONY: test prepare + +prepare: + git clone --depth 1 https://github.com/nvim-lua/plenary.nvim ../plenary.nvim + +test: + nvim --headless --noplugin -u scripts/minimal_init.vim -c "PlenaryBustedDirectory lua/tests/ { minimal_init = './scripts/minimal_init.vim' }" diff --git a/nvim/.config/nvim/ftplugin/fugitive.lua b/nvim/.config/nvim/after/ftplugin/fugitive.lua similarity index 96% rename from nvim/.config/nvim/ftplugin/fugitive.lua rename to nvim/.config/nvim/after/ftplugin/fugitive.lua index 49bad00..dbc8897 100644 --- a/nvim/.config/nvim/ftplugin/fugitive.lua +++ b/nvim/.config/nvim/after/ftplugin/fugitive.lua @@ -1,7 +1,7 @@ local git_funcs = require 'user.git' vim.schedule(function() - local buf = git_funcs.get_fugitive_buffer() + local buf = git_funcs.get_fugitive_buffer() or 0 vim.api.nvim_buf_set_keymap(buf, 'n', 't', '', { noremap = true, silent = true, diff --git a/nvim/.config/nvim/ftplugin/fugitiveblame.lua b/nvim/.config/nvim/ftplugin/fugitiveblame.lua index 17f508e..1bcba3d 100644 --- a/nvim/.config/nvim/ftplugin/fugitiveblame.lua +++ b/nvim/.config/nvim/ftplugin/fugitiveblame.lua @@ -41,9 +41,9 @@ vim.schedule(function() local commit_hash = get_commit_hash() vim.cmd 'wincmd p' -- selene: allow(undefined_variable) - Snacks.gitbrowse.open { + require('user.gitbrowse').open { what = 'commit', - branch = commit_hash, + commit = commit_hash, } end, }) diff --git a/nvim/.config/nvim/ftplugin/k8s_containers.lua b/nvim/.config/nvim/ftplugin/k8s_containers.lua index 7f3f3a0..85abbb2 100644 --- a/nvim/.config/nvim/ftplugin/k8s_containers.lua +++ b/nvim/.config/nvim/ftplugin/k8s_containers.lua @@ -1,5 +1,5 @@ local mappings = require 'kubectl.mappings' -local pod_view = require 'kubectl.views.pods' +local pod_view = require 'kubectl.resources.pods' local tables = require 'kubectl.utils.tables' vim.api.nvim_buf_set_keymap(0, 'n', '(kubectl.browse)', '', { diff --git a/nvim/.config/nvim/ftplugin/k8s_fallback.lua b/nvim/.config/nvim/ftplugin/k8s_fallback.lua deleted file mode 100644 index f0527e2..0000000 --- a/nvim/.config/nvim/ftplugin/k8s_fallback.lua +++ /dev/null @@ -1,29 +0,0 @@ -local commands = require 'kubectl.actions.commands' -local tables = require 'kubectl.utils.tables' - -vim.schedule(function() - vim.api.nvim_buf_set_keymap(0, 'n', '(kubectl.select)', '', { - noremap = true, - silent = true, - desc = 'Go to application', - callback = function() - local _, buf_name = pcall(vim.api.nvim_buf_get_var, 0, 'buf_name') - local lower_buf_name = string.lower(buf_name) - - if lower_buf_name == 'applications' or lower_buf_name == 'applications.argoproj.io' then - local name = tables.getCurrentSelection(2) - if not name then - return - end - local ingress_host = commands.shell_command( - 'kubectl', - { 'get', 'ingress', '-n', 'argocd', '-l', 'app.kubernetes.io/component=server', '-o', 'jsonpath={.items[].spec.rules[].host}' } - ) - - local final_host = string.format('https://%s/applications/argocd/%s', ingress_host, name) - vim.notify('Opening ' .. final_host) - vim.ui.open(final_host) - end - end, - }) -end) diff --git a/nvim/.config/nvim/ftplugin/k8s_pods.lua b/nvim/.config/nvim/ftplugin/k8s_pods.lua deleted file mode 100644 index bedda21..0000000 --- a/nvim/.config/nvim/ftplugin/k8s_pods.lua +++ /dev/null @@ -1,32 +0,0 @@ -local ResourceBuilder = require 'kubectl.resourcebuilder' -local commands = require 'kubectl.actions.commands' -local pod_view = require 'kubectl.views.pods' - -vim.schedule(function() - vim.api.nvim_buf_set_keymap(0, 'n', 'gk', '', { - noremap = true, - silent = true, - desc = 'Kill prompt', - callback = function() - local name, ns = pod_view.getCurrentSelection() - local builder = ResourceBuilder:new 'kubectl_drain' - local pod_def = { - ft = 'k8s_kill_pod', - display = string.format('Kill pod: %s/%s?', ns, name), - resource = ns .. '/' .. name, - cmd = { 'delete', 'pod', name, '-n', ns }, - } - local data = { - { text = 'cascade:', value = 'background', options = { 'background', 'orphan', 'foreground' }, cmd = '--cascade', type = 'option' }, - { text = 'dry run:', value = 'none', options = { 'none', 'server', 'client' }, cmd = '--dry-run', type = 'option' }, - { text = 'grade period:', value = '-1', cmd = '--grace-period', type = 'option' }, - { text = 'timeout:', value = '0s', cmd = '--timeout', type = 'option' }, - { text = 'force:', value = 'false', options = { 'false', 'true' }, cmd = '--force', type = 'flag' }, - } - - builder:action_view(pod_def, data, function(args) - commands.shell_command_async('kubectl', args) - end) - end, - }) -end) diff --git a/nvim/.config/nvim/ftplugin/ruby.lua b/nvim/.config/nvim/ftplugin/ruby.lua new file mode 100644 index 0000000..eb9e1c6 --- /dev/null +++ b/nvim/.config/nvim/ftplugin/ruby.lua @@ -0,0 +1,53 @@ +local function sort_brewfile() + if vim.fn.expand '%:t' ~= 'Brewfile' then + return + end + + local lines = vim.api.nvim_buf_get_lines(0, 0, -1, false) + local taps = {} + local brews = {} + local casks = {} + local others = {} + + for _, line in ipairs(lines) do + if line:match '^tap ' then + table.insert(taps, line) + elseif line:match '^brew ' then + table.insert(brews, line) + elseif line:match '^cask ' then + table.insert(casks, line) + else + table.insert(others, line) + end + end + + table.sort(taps) + table.sort(brews) + table.sort(casks) + + local sorted_lines = {} + for _, line in ipairs(taps) do + table.insert(sorted_lines, line) + end + for _, line in ipairs(brews) do + table.insert(sorted_lines, line) + end + for _, line in ipairs(casks) do + table.insert(sorted_lines, line) + end + for _, line in ipairs(others) do + table.insert(sorted_lines, line) + end + + vim.api.nvim_buf_set_lines(0, 0, -1, false, sorted_lines) +end + +if vim.fn.expand '%:t' == 'Brewfile' then + -- autocmd BufWritePre + + vim.api.nvim_create_autocmd('BufWritePre', { + group = vim.api.nvim_create_augroup('SortBrewfile', { clear = true }), + pattern = 'Brewfile', + callback = sort_brewfile, + }) +end diff --git a/nvim/.config/nvim/init.lua b/nvim/.config/nvim/init.lua index 25332e4..a46e093 100644 --- a/nvim/.config/nvim/init.lua +++ b/nvim/.config/nvim/init.lua @@ -5,4 +5,3 @@ require 'user.options' require 'user.keymaps' require('lazy').setup('plugins', require('user.lazy').config) require 'user.autocommands' -require 'user.number-separators' diff --git a/nvim/.config/nvim/lazy-lock.json b/nvim/.config/nvim/lazy-lock.json index 70a0a54..d093003 100644 --- a/nvim/.config/nvim/lazy-lock.json +++ b/nvim/.config/nvim/lazy-lock.json @@ -1,116 +1,107 @@ { - "2048.nvim": { "branch": "main", "commit": "aee7c41a9a9473117465132768ae4b4da97435c3" }, - "CopilotChat.nvim": { "branch": "main", "commit": "16d897fd43d07e3b54478ccdb2f8a16e4df4f45a" }, + "CopilotChat.nvim": { "branch": "main", "commit": "44f37584bdd2d1b984e4ce64f5378a773ba0fa1e" }, "CursorLineCurrentWindow": { "branch": "master", "commit": "b4eeea98b0d139772969fd243a8802a9883fd2a8" }, - "LuaSnip": { "branch": "master", "commit": "faf3c94a44508cec1b961406d36cc65113ff3b98" }, - "SchemaStore.nvim": { "branch": "main", "commit": "0098dde21296a454ae1426f9ac47340dd38c27ce" }, - "auto-indent.nvim": { "branch": "main", "commit": "46801cf8857d42a20a73c40b0a5d3dfe8b2b6192" }, - "carbon-now.nvim": { "branch": "main", "commit": "f13c3c611a6aa22cca028bfe2cec63c7f67f2c87" }, - "cellular-automaton.nvim": { "branch": "main", "commit": "1606e9d5d04ff254023c3f3c62842d065708d6d3" }, + "LuaSnip": { "branch": "master", "commit": "de10d8414235b0a8cabfeba60d07c24304e71f5c" }, + "SchemaStore.nvim": { "branch": "main", "commit": "a28cc71857f11a7370d81f5ba3c1f5bb8383b732" }, + "avante.nvim": { "branch": "main", "commit": "5e4eb8652b4776f139282a343f04a3a8c7f23cb0" }, + "blink.download": { "branch": "main", "commit": "d95072328fb8293cc62c2574b0de2026e74d6f92" }, "cmp-buffer": { "branch": "main", "commit": "b74fab3656eea9de20a9b8116afa3cfc4ec09657" }, "cmp-calc": { "branch": "main", "commit": "5947b412da67306c5b68698a02a846760059be2e" }, "cmp-cmdline": { "branch": "main", "commit": "d126061b624e0af6c3a556428712dd4d4194ec6d" }, "cmp-dap": { "branch": "master", "commit": "ea92773e84c0ad3288c3bc5e452ac91559669087" }, - "cmp-git": { "branch": "main", "commit": "483ffb9a7471409a841df099d7c13556234365a4" }, - "cmp-nvim-lsp": { "branch": "main", "commit": "99290b3ec1322070bcfb9e846450a46f6efa50f0" }, + "cmp-git": { "branch": "main", "commit": "b24309c386c9666c549a1abaedd4956541676d06" }, + "cmp-nvim-lsp": { "branch": "main", "commit": "a8912b88ce488f411177fc8aed358b04dc246d7b" }, "cmp-nvim-lsp-signature-help": { "branch": "main", "commit": "031e6ba70b0ad5eee49fd2120ff7a2e325b17fa7" }, - "cmp-nvim-lua": { "branch": "main", "commit": "f12408bdb54c39c23e67cab726264c10db33ada8" }, - "cmp-path": { "branch": "main", "commit": "c6635aae33a50d6010bf1aa756ac2398a2d54c32" }, + "cmp-path": { "branch": "main", "commit": "c642487086dbd9a93160e1679a1327be111cbc25" }, "cmp-tabnine": { "branch": "main", "commit": "c0167cdc86c15e782c5461ee62aebee89231c2ed" }, "cmp_luasnip": { "branch": "master", "commit": "98d9cb5c2c38532bd9bdb481067b20fea8f32e90" }, - "conform.nvim": { "branch": "master", "commit": "6feb2f28f9a9385e401857b21eeac3c1b66dd628" }, - "copilot.lua": { "branch": "master", "commit": "a620a5a97b73faba009a8160bab2885316e1451c" }, + "conform.nvim": { "branch": "master", "commit": "973f3cb73887d510321653044791d7937c7ec0fa" }, + "copilot.lua": { "branch": "master", "commit": "0f2fd3829dd27d682e46c244cf48d9715726f612" }, "diffview.nvim": { "branch": "main", "commit": "4516612fe98ff56ae0415a259ff6361a89419b0a" }, - "fidget.nvim": { "branch": "main", "commit": "d9ba6b7bfe29b3119a610892af67602641da778e" }, + "fidget.nvim": { "branch": "main", "commit": "2cb5edb2dd6700a958a446b20bb2be04d318da9d" }, "friendly-snippets": { "branch": "main", "commit": "572f5660cf05f8cd8834e096d7b4c921ba18e175" }, - "fzf-lua": { "branch": "main", "commit": "97f665a38d73a6541d98b542c79b4bead8e85d4c" }, + "fzf-lua": { "branch": "main", "commit": "2f9e1af8cbff922543c2f259040d518517b8e294" }, "git-conflict.nvim": { "branch": "main", "commit": "4bbfdd92d547d2862a75b4e80afaf30e73f7bbb4" }, - "git-dev.nvim": { "branch": "master", "commit": "f8894349d04b2d922f2d170c5d66db729236d86f" }, - "gitsigns.nvim": { "branch": "main", "commit": "8b729e489f1475615dc6c9737da917b3bc163605" }, - "hardtime.nvim": { "branch": "main", "commit": "1a3648a53002c2911ccb88e9c9f876cdc6c43ad6" }, + "gitsigns.nvim": { "branch": "main", "commit": "6e3c66548035e50db7bd8e360a29aec6620c3641" }, + "go.nvim": { "branch": "master", "commit": "3279d15d146ab2eae6ad9bc930f3c8fc62a3697b" }, + "guihua.lua": { "branch": "master", "commit": "87bea7b98429405caf2a0ce4d029b027bb017c70" }, "jsonpath.nvim": { "branch": "main", "commit": "2d889eb9c39893e030e902b854122a1615c042ea" }, - "kubectl.nvim": { "branch": "main", "commit": "47e8d739c5016809a16eb760a02812eddee9f763" }, + "kubectl.nvim": { "branch": "main", "commit": "efce9ba09a76666e60cdd1d10b24aee664fedb7b" }, "lazy.nvim": { "branch": "main", "commit": "6c3bda4aca61a13a9c63f1c1d1b16b9d3be90d7a" }, "lazydev.nvim": { "branch": "main", "commit": "2367a6c0a01eb9edb0464731cc0fb61ed9ab9d2c" }, - "leap.nvim": { "branch": "main", "commit": "189102b07cdd24de3bd7132e57da8c9614c385fc" }, - "leetcode.nvim": { "branch": "master", "commit": "db7e1cd6b9191b34b4c1f2f96e4e3949cde9f951" }, + "leap.nvim": { "branch": "main", "commit": "02bf52e49c72cc5dabb53ec9494d10d304f0b2c9" }, + "leetcode.nvim": { "branch": "master", "commit": "422b6beb4a64eca0524fbff94edd9550c156afc5" }, "linediff.vim": { "branch": "main", "commit": "ddae71ef5f94775d101c1c70032ebe8799f32745" }, "lspkind-nvim": { "branch": "master", "commit": "d79a1c3299ad0ef94e255d045bed9fa26025dab6" }, - "lualine.nvim": { "branch": "master", "commit": "0c6cca9f2c63dadeb9225c45bc92bb95a151d4af" }, + "lualine.nvim": { "branch": "master", "commit": "b8c23159c0161f4b89196f74ee3a6d02cdc3a955" }, "luvit-meta": { "branch": "main", "commit": "1df30b60b1b4aecfebc785aa98943db6c6989716" }, "markdown-preview.nvim": { "branch": "master", "commit": "a923f5fc5ba36a3b17e289dc35dc17f66d0548ee" }, - "markview.nvim": { "branch": "main", "commit": "68c9603b6f88fd962444f8579024418fe5e170f1" }, - "mason-lspconfig.nvim": { "branch": "main", "commit": "d24b3f1612e53f9d54d866b16bedab51813f2bf1" }, + "markview.nvim": { "branch": "main", "commit": "2d68c060ad4387d9895577af43c3ace41a80de5d" }, "mason-null-ls.nvim": { "branch": "main", "commit": "de19726de7260c68d94691afb057fa73d3cc53e7" }, - "mason-nvim-dap.nvim": { "branch": "main", "commit": "4c2cdc69d69fe00c15ae8648f7e954d99e5de3ea" }, - "mason.nvim": { "branch": "main", "commit": "8024d64e1330b86044fed4c8494ef3dcd483a67c" }, - "mini.ai": { "branch": "main", "commit": "b91997d220086e92edc1fec5ce82094dcc234291" }, - "mini.cursorword": { "branch": "main", "commit": "ab63cc74975f4e107dbb490f6d3040eacfc1c023" }, - "mini.hipatterns": { "branch": "main", "commit": "e5083df391171dc9d8172645606f8496d9443374" }, - "mini.indentscope": { "branch": "main", "commit": "8af2569a7d7fd37300dfa760e44e71efbbf322fd" }, - "mini.notify": { "branch": "main", "commit": "3a06b21dd0b335b95d125eae813276113b5e9ce7" }, - "mini.operators": { "branch": "main", "commit": "b79a95958c57e127704bbeb1dc11a511954fc888" }, - "mini.pairs": { "branch": "main", "commit": "69864a2efb36c030877421634487fd90db1e4298" }, - "mini.splitjoin": { "branch": "main", "commit": "70240d5d3881ff1b2b1d7902450f98a6fa303800" }, - "mini.surround": { "branch": "main", "commit": "5aab42fcdcf31fa010f012771eda5631c077840a" }, + "mason-nvim-dap.nvim": { "branch": "main", "commit": "86389a3dd687cfaa647b6f44731e492970034baa" }, + "mason.nvim": { "branch": "main", "commit": "7dc4facca9702f95353d5a1f87daf23d78e31c2a" }, + "mcphub.nvim": { "branch": "main", "commit": "424e8c988e2e958da6f2182e40d52ee54bb18b8c" }, + "mini.ai": { "branch": "main", "commit": "1cd4f021a05c29acd4ab511c0981da14217daf38" }, + "mini.cursorword": { "branch": "main", "commit": "52834085b4ad175a050343cd96c4517def711cc5" }, + "mini.hipatterns": { "branch": "main", "commit": "2b78f3d475d60ea1793a6d595ff65a0db9ac3a67" }, + "mini.indentscope": { "branch": "main", "commit": "f1567c6f46c250b22f4ad1b847a042464742b11d" }, + "mini.notify": { "branch": "main", "commit": "e18f9fc783c7a26fab9c8a094d97dc768e56f558" }, + "mini.operators": { "branch": "main", "commit": "e72b5afec4c339aebd601a56e9ae3ef13d2f5d1a" }, + "mini.pairs": { "branch": "main", "commit": "42407ccb80ec59c84e7c91d815f42ed90a8cc093" }, + "mini.splitjoin": { "branch": "main", "commit": "51909e9883ab206f5a92deb9ca685317387586a4" }, + "mini.surround": { "branch": "main", "commit": "7a8606333affe7ce637a0ba91bbafc46fc42bfa0" }, "nginx.vim": { "branch": "master", "commit": "cffaec54f0c7f9518de053634413a20e90eac825" }, "none-ls-shellcheck.nvim": { "branch": "main", "commit": "0f84461241e76e376a95fb7391deac82dc3efdbf" }, - "none-ls.nvim": { "branch": "main", "commit": "db2a48b79cfcdab8baa5d3f37f21c78b6705c62e" }, - "nui.nvim": { "branch": "main", "commit": "f535005e6ad1016383f24e39559833759453564e" }, - "nvim-bqf": { "branch": "main", "commit": "e20417d5e589e03eaaaadc4687904528500608be" }, + "none-ls.nvim": { "branch": "main", "commit": "5fcb73913a9290f78097e34420fe0e6130c5c33c" }, + "nui.nvim": { "branch": "main", "commit": "de740991c12411b663994b2860f1a4fd0937c130" }, + "nvim-autopairs": { "branch": "master", "commit": "23320e75953ac82e559c610bec5a90d9c6dfa743" }, + "nvim-bqf": { "branch": "main", "commit": "17680cda3538913e88dd4c6456c837db9ace40ae" }, "nvim-cmp": { "branch": "main", "commit": "b5311ab3ed9c846b585c0c15b7559be131ec4be9" }, - "nvim-dap": { "branch": "master", "commit": "b0f983507e3702f073bfe1516846e58b56d4e42f" }, - "nvim-dap-go": { "branch": "main", "commit": "8763ced35b19c8dc526e04a70ab07c34e11ad064" }, + "nvim-dap": { "branch": "master", "commit": "a479e25ed5b5d331fb46ee4b9e160ff02ac64310" }, "nvim-dap-python": { "branch": "master", "commit": "261ce649d05bc455a29f9636dc03f8cdaa7e0e2c" }, - "nvim-dap-ui": { "branch": "master", "commit": "73a26abf4941aa27da59820fd6b028ebcdbcf932" }, + "nvim-dap-ui": { "branch": "master", "commit": "cf91d5e2d07c72903d052f5207511bf7ecdb7122" }, "nvim-dap-virtual-text": { "branch": "master", "commit": "fbdb48c2ed45f4a8293d0d483f7730d24467ccb6" }, "nvim-dap-vscode-js": { "branch": "main", "commit": "03bd29672d7fab5e515fc8469b7d07cc5994bbf6" }, - "nvim-hlslens": { "branch": "main", "commit": "1e650c136f9f9d5fefe34947ad27c80010d3321f" }, - "nvim-lspconfig": { "branch": "master", "commit": "3ea99227e316c5028f57a4d86a1a7fd01dd876d0" }, - "nvim-lua-gf": { "branch": "main", "commit": "ca712497b2bab6351518917be219e9bfd8d63e4f" }, + "nvim-hlslens": { "branch": "main", "commit": "95ced18ebaf19c1bdb64982a614ee8a9d7ee8e51" }, + "nvim-lspconfig": { "branch": "master", "commit": "45ff1914044de7dbd4cd85053dc09f47312a2f4d" }, "nvim-luaref": { "branch": "main", "commit": "9cd3ed50d5752ffd56d88dd9e395ddd3dc2c7127" }, "nvim-navic": { "branch": "master", "commit": "f887d794a0f4594882814d7780980a949200a238" }, "nvim-nio": { "branch": "master", "commit": "21f5324bfac14e22ba26553caf69ec76ae8a7662" }, "nvim-pqf": { "branch": "main", "commit": "148ee2ca8b06d83fd9bf6f9b9497724ad39a07d6" }, - "nvim-tree.lua": { "branch": "master", "commit": "ebcaccda1c575fa19a8087445276e6671e2b9b37" }, + "nvim-tree.lua": { "branch": "master", "commit": "feb901a09e0a0caf4f98eb18b70404c898d1df1e" }, "nvim-treesitter": { "branch": "master", "commit": "42fc28ba918343ebfd5565147a42a26580579482" }, - "nvim-treesitter-context": { "branch": "master", "commit": "404502e607c3b309e405be9112c438c721153372" }, - "nvim-treesitter-textobjects": { "branch": "master", "commit": "0f051e9813a36481f48ca1f833897210dbcfffde" }, + "nvim-treesitter-context": { "branch": "master", "commit": "dca8726fea2c14e1ce6adbaa76a04816fbfaff61" }, + "nvim-treesitter-textobjects": { "branch": "master", "commit": "71385f191ec06ffc60e80e6b0c9a9d5daed4824c" }, "nvim-ts-autotag": { "branch": "main", "commit": "a1d526af391f6aebb25a8795cbc05351ed3620b5" }, - "nvim-ufo": { "branch": "main", "commit": "3c7a3570e9c9dc198a2ad4491b0b0e51c4d4ba08" }, - "nvim-web-devicons": { "branch": "master", "commit": "1fb58cca9aebbc4fd32b086cb413548ce132c127" }, - "oil.nvim": { "branch": "master", "commit": "685cdb4ffa74473d75a1b97451f8654ceeab0f4a" }, - "oklch-color-picker.nvim": { "branch": "master", "commit": "21975260558453158161c054bc9fd0191af609a6" }, - "plenary.nvim": { "branch": "master", "commit": "857c5ac632080dba10aae49dba902ce3abf91b35" }, + "nvim-ufo": { "branch": "main", "commit": "80fe8215ba566df2fbf3bf4d25f59ff8f41bc0e1" }, + "nvim-web-devicons": { "branch": "master", "commit": "c2599a81ecabaae07c49ff9b45dcd032a8d90f1a" }, + "octo.nvim": { "branch": "master", "commit": "86192674d685cf9076d0228e50d281f840d2c889" }, + "oil.nvim": { "branch": "master", "commit": "bbad9a76b2617ce1221d49619e4e4b659b3c61fc" }, + "oklch-color-picker.nvim": { "branch": "master", "commit": "e4ea564534a7a80257ee7c2ddb9e60715e60c43c" }, + "plenary.nvim": { "branch": "master", "commit": "b9fd5226c2f76c951fc8ed5923d85e4de065e509" }, "promise-async": { "branch": "main", "commit": "119e8961014c9bfaf1487bf3c2a393d254f337e2" }, - "rose-pine": { "branch": "main", "commit": "6b9840790cc7acdfadde07f308d34b62dd9cc675" }, + "rose-pine": { "branch": "main", "commit": "72befaffeac38db7bdd49e0549eaa2c4806dd878" }, + "smart-splits.nvim": { "branch": "master", "commit": "52ee119752ee7e98a2a62f0f172926b5fbbc28a5" }, "snacks.nvim": { "branch": "main", "commit": "bc0630e43be5699bb94dadc302c0d21615421d93" }, "sqlite.lua": { "branch": "master", "commit": "50092d60feb242602d7578398c6eb53b4a8ffe7b" }, - "statuscol.nvim": { "branch": "0.10", "commit": "b0de00dfcf583bdd94284d844aac1b134d47c578" }, - "switch.vim": { "branch": "main", "commit": "f4b831b86e1e65af8c4f8be6434e15bef98d111e" }, + "statuscol.nvim": { "branch": "main", "commit": "c46172d0911aa5d49ba5f39f4351d1bb7aa289cc" }, + "switch.vim": { "branch": "main", "commit": "4017a58f7ed8a2d76a288e36130affe8eb55e83a" }, "template-string.nvim": { "branch": "main", "commit": "da5f326b65fb74fd068aa1d8b55461b64c8fb23b" }, - "term-edit.nvim": { "branch": "master", "commit": "b190a91178ef98beefac2ec0823db047978ea327" }, - "treemonkey.nvim": { "branch": "main", "commit": "1fe117cc25b283c33209136e8d48cea078939d6d" }, - "treesitter-terraform-doc.nvim": { "branch": "main", "commit": "130e0e47d4d436e9efefb786613f83c4498ec309" }, - "trouble.nvim": { "branch": "main", "commit": "85bedb7eb7fa331a2ccbecb9202d8abba64d37b3" }, + "treemonkey.nvim": { "branch": "main", "commit": "b13e9024f51c8a12ce2196fb49ceb793338ed66e" }, + "treesitter-terraform-doc.nvim": { "branch": "main", "commit": "e959c1f19f12642da05587443523dc09cf7991a8" }, "ts-comments.nvim": { "branch": "main", "commit": "1bd9d0ba1d8b336c3db50692ffd0955fe1bb9f0c" }, - "twilight.nvim": { "branch": "main", "commit": "1584c0b0a979b71fd86b18d302ba84e9aba85b1b" }, - "vim-bufsurf": { "branch": "master", "commit": "e6dbc7ad66c7e436e5eb20d304464e378bd7f28c" }, - "vim-dadbod": { "branch": "master", "commit": "e95afed23712f969f83b4857a24cf9d59114c2e6" }, - "vim-dadbod-completion": { "branch": "master", "commit": "a8dac0b3cf6132c80dc9b18bef36d4cf7a9e1fe6" }, - "vim-dadbod-ui": { "branch": "master", "commit": "460432301a5cb280ea265ddfa15c9f3dcd1d26b7" }, + "typr": { "branch": "main", "commit": "bdd9ef7143702851edd1dac85101e0a537056d2f" }, + "vim-dadbod": { "branch": "master", "commit": "9f0ca8bcef704659820a95c3bbd2c262583a66a1" }, "vim-dirdiff": { "branch": "master", "commit": "84bc8999fde4b3c2d8b228b560278ab30c7ea4c9" }, "vim-easy-align": { "branch": "master", "commit": "9815a55dbcd817784458df7a18acacc6f82b1241" }, - "vim-fugitive": { "branch": "master", "commit": "4a745ea72fa93bb15dd077109afbb3d1809383f2" }, - "vim-kubernetes": { "branch": "master", "commit": "f477818c30efe4914e9a047ba1105b524d78e1b5" }, + "vim-fugitive": { "branch": "master", "commit": "61b51c09b7c9ce04e821f6cf76ea4f6f903e3cf4" }, "vim-lister": { "branch": "master", "commit": "ec903e615f867f910cd8adc73f5906d521f3f214" }, - "vim-matchup": { "branch": "master", "commit": "56c714495ec7f40cf2c7e92fb124067c7951e650" }, - "vim-sh-indent": { "branch": "master", "commit": "982fbbc34f9c66af1f6b58b643d0f4ef32b7d8c8" }, + "vim-matchup": { "branch": "master", "commit": "b4efd6a97380b99bb9f5eb80c9002e061402c288" }, "vim-startuptime": { "branch": "master", "commit": "b6f0d93f6b8cf6eee0b4c94450198ba2d6a05ff6" }, "vim-swap": { "branch": "master", "commit": "9358bfdc5e377aa13e7c2c2dd8699ba32b0dcf83" }, - "wezterm-types": { "branch": "main", "commit": "1518752906ba3fac0060d9efab6e4d3ec15d4b5a" }, - "winresizer": { "branch": "master", "commit": "9bd559a03ccec98a458e60c705547119eb5350f3" }, - "yaml-companion.nvim": { "branch": "main", "commit": "70321546727e4f04e86075a54afe8e6ef40286dc" }, - "yaml.nvim": { "branch": "main", "commit": "20ee341ecb17dfa9110437490e903b51f5295225" }, + "volt": { "branch": "main", "commit": "7b8c5e790120d9f08c8487dcb80692db6d2087a1" }, + "wezterm-types": { "branch": "main", "commit": "f2f0d0f6e60ddeb34b70b3f1312b922d4a04bbad" }, + "yaml-companion.nvim": { "branch": "main", "commit": "eecef97d2d825576cc1a830e9ec939e9218357e1" }, + "yaml.nvim": { "branch": "main", "commit": "09bbf7d936606d97f2c1d2e51171a602bc76b65e" }, "yanky.nvim": { "branch": "main", "commit": "04775cc6e10ef038c397c407bc17f00a2f52b378" } } diff --git a/nvim/.config/nvim/lua/core/init.lua b/nvim/.config/nvim/lua/core/init.lua index c361817..12e4c3a 100644 --- a/nvim/.config/nvim/lua/core/init.lua +++ b/nvim/.config/nvim/lua/core/init.lua @@ -15,65 +15,58 @@ function _G.P(v, r) if r then print(vim.inspect(v)) else - vim.notify(vim.inspect(v), 2, { + vim.notify(vim.inspect(v), vim.log.levels.WARN, { title = 'P debug', icon = '✎', }) end return v end + local original_vim_print = vim.print +---@diagnostic disable-next-line: duplicate-set-field vim.print = function(...) local str = type(...) == 'table' and vim.inspect(...) or ... original_vim_print(str) end --- Write a temporary file, optionally delete on exit, set filetype and open in a --- new buffer --- @param should_delete boolean whether to delete the file on exit --- @param ft string filetype to set --- @params new boolean whether to open in a new buffer --- @params vertical boolean whether to open in a vertical split (only if new is true) --- @returns nil +---Write a temporary file with specified options +---@param opts? {should_delete?: boolean, ft?: string, new?: boolean, vertical?: boolean} +---@return string tmp The path to the temporary file function _G.tmp_write(opts) - local defaults = { + opts = opts or {} + local final_opts = vim.tbl_deep_extend('force', { should_delete = true, ft = nil, new = true, vertical = false, - } - local final_opts = vim.tbl_extend('force', defaults, opts) - local should_delete = final_opts.should_delete - local ft = final_opts.ft - local new = final_opts.new - local vertical = final_opts.vertical + }, opts) + local tmp = vim.fn.tempname() - if new then - if vertical then - vim.cmd 'vnew' - else - vim.cmd 'new' - end + + if final_opts.new then + vim.cmd(final_opts.vertical and 'vnew' or 'new') end - if ft then - local extension = require('user.utils').filetype_to_extension[ft] or ft - vim.api.nvim_set_option_value('filetype', ft, { buf = 0 }) + if final_opts.ft then + local extension = require('user.utils').filetype_to_extension[final_opts.ft] or final_opts.ft + vim.bo.filetype = final_opts.ft tmp = tmp .. '.' .. extension end - vim.cmd(string.format('write %s', tmp)) + + vim.cmd('write ' .. vim.fn.fnameescape(tmp)) vim.cmd 'edit' - -- Create autocmd to delete the file on exit - if should_delete then - vim.api.nvim_create_autocmd({ 'VimLeavePre' }, { + if final_opts.should_delete then + vim.api.nvim_create_autocmd('VimLeavePre', { buffer = 0, - command = 'delete("' .. tmp .. '")', + callback = function() + vim.fn.delete(tmp) + end, }) end return tmp end --- leader key - before mapping lsp maps vim.g.mapleader = ' ' vim.g.maplocalleader = ' ' diff --git a/nvim/.config/nvim/lua/core/lazy-bootstrap.lua b/nvim/.config/nvim/lua/core/lazy-bootstrap.lua index 9ab1a67..f2b6792 100644 --- a/nvim/.config/nvim/lua/core/lazy-bootstrap.lua +++ b/nvim/.config/nvim/lua/core/lazy-bootstrap.lua @@ -1,5 +1,5 @@ local lazypath = vim.fn.stdpath 'data' .. '/lazy/lazy.nvim' -if not vim.loop.fs_stat(lazypath) then +if not vim.uv.fs_stat(lazypath) then vim .system({ 'git', diff --git a/nvim/.config/nvim/lua/plugins/ai.lua b/nvim/.config/nvim/lua/plugins/ai.lua new file mode 100644 index 0000000..ffe9776 --- /dev/null +++ b/nvim/.config/nvim/lua/plugins/ai.lua @@ -0,0 +1,181 @@ +local model = 'gpt-5' +return { + { + 'zbirenbaum/copilot.lua', + event = { 'InsertEnter' }, + config = function() + require('copilot').setup { + copilot_node_command = 'node', + filetypes = { ['*'] = true }, + panel = { + enabled = true, + auto_refresh = false, + keymap = { + jump_prev = '[[', + jump_next = ']]', + accept = '', + refresh = 'gr', + open = '', + }, + }, + suggestion = { + auto_trigger = true, + keymap = { + accept = '', + }, + }, + } + end, + }, + { + 'ravitemer/mcphub.nvim', + cmd = { 'MCPHub' }, + dependencies = { + 'nvim-lua/plenary.nvim', + }, + build = 'npm install -g mcp-hub@latest', -- Installs `mcp-hub` node binary globally + opts = { + extensions = { + avante = { + make_slash_commands = true, -- make /slash commands from MCP server prompts + }, + copilotchat = { + enabled = true, + convert_tools_to_functions = true, -- Convert MCP tools to CopilotChat functions + convert_resources_to_functions = true, -- Convert MCP resources to CopilotChat functions + add_mcp_prefix = true, -- Add "mcp_" prefix to function names + }, + }, + }, + }, + { + 'CopilotC-Nvim/CopilotChat.nvim', + enabled = true, + cmd = { + 'CopilotChat', + 'CopilotChatAgents', + 'CopilotChatClose', + 'CopilotChatCommit', + 'CopilotChatCommitStaged', + 'CopilotChatDebugInfo', + 'CopilotChatDocs', + 'CopilotChatExplain', + 'CopilotChatFix', + 'CopilotChatFixDiagnostic', + 'CopilotChatLoad', + 'CopilotChatModels', + 'CopilotChatOpen', + 'CopilotChatOptimize', + 'CopilotChatReset', + 'CopilotChatReview', + 'CopilotChatSave', + 'CopilotChatStop', + 'CopilotChatTests', + 'CopilotChatToggle', + }, + dependencies = { + 'zbirenbaum/copilot.lua', + 'nvim-lua/plenary.nvim', + 'ravitemer/mcphub.nvim', + }, + build = 'make tiktoken', + opts = { + -- https://docs.github.com/en/copilot/using-github-copilot/ai-models/choosing-the-right-ai-model-for-your-task + model = model, + question_header = ' User ', + answer_header = ' Copilot ', + error_header = ' Error ', + }, + keys = { + { 'ccc', 'CopilotChat', mode = { 'n', 'v' } }, + { 'ccs', 'CopilotChatStop' }, + { + 'ccp', + function() + local actions = require 'CopilotChat.actions' + require('CopilotChat.integrations.fzflua').pick(actions.prompt_actions()) + end, + desc = 'CopilotChat - Prompt actions', + mode = { 'n', 'v' }, + }, + }, + }, + { + 'yetone/avante.nvim', + enabled = false, + version = false, -- Never set this value to "*"! Never! + keys = { + { 'ccc', 'AvanteChat', mode = { 'n', 'v' } }, + { 'ccs', 'AvanteStop' }, + }, + cmd = { 'AvanteChat' }, + opts = { + disabled_tools = { + 'list_files', -- Built-in file operations + 'search_files', + 'read_file', + 'create_file', + 'rename_file', + 'delete_file', + 'create_dir', + 'rename_dir', + 'delete_dir', + 'bash', -- Built-in terminal access + }, + provider = 'copilot', + providers = { + copilot = { + -- model = 'gpt-4.1', + model = model, + }, + }, + + -- mcphub + -- system_prompt as function ensures LLM always has latest MCP server state + -- This is evaluated for every message, even in existing chats + system_prompt = function() + local hub = require('mcphub').get_hub_instance() + return hub and hub:get_active_servers_prompt() or '' + end, + -- Using function prevents requiring mcphub before it's loaded + custom_tools = function() + return { + require('mcphub.extensions.avante').mcp_tool(), + } + end, + }, + -- if you want to build from source then do `make BUILD_FROM_SOURCE=true` + build = 'make', + -- build = "powershell -ExecutionPolicy Bypass -File Build.ps1 -BuildFromSource false" -- for windows + dependencies = { + 'nvim-treesitter/nvim-treesitter', + 'nvim-lua/plenary.nvim', + 'MunifTanjim/nui.nvim', + { + -- support for image pasting + 'HakonHarnes/img-clip.nvim', + event = 'VeryLazy', + opts = { + -- recommended settings + default = { + embed_image_as_base64 = false, + prompt_for_file_name = false, + drag_and_drop = { + insert_mode = true, + }, + -- required for Windows users + use_absolute_path = true, + }, + }, + }, + -- { + -- -- Make sure to set this up properly if you have lazy=true + -- 'MeanderingProgrammer/render-markdown.nvim', + -- opts = { + -- file_types = { 'markdown', 'Avante' }, + -- }, + -- ft = { 'markdown', 'Avante' }, + -- }, + }, + }, +} diff --git a/nvim/.config/nvim/lua/plugins/cmpconf.lua b/nvim/.config/nvim/lua/plugins/cmpconf.lua index 4034e42..4dc75b8 100644 --- a/nvim/.config/nvim/lua/plugins/cmpconf.lua +++ b/nvim/.config/nvim/lua/plugins/cmpconf.lua @@ -2,11 +2,10 @@ local M = { 'hrsh7th/nvim-cmp', version = false, -- last release is way too old - enabled = true, event = { 'InsertEnter', 'CmdlineEnter' }, dependencies = { 'rafamadriz/friendly-snippets', - { 'L3MON4D3/LuaSnip', build = 'make install_jsregexp' }, + { 'L3MON4D3/LuaSnip', build = 'make install_jsregexp' }, 'saadparwaiz1/cmp_luasnip', 'onsails/lspkind-nvim', { 'tzachar/cmp-tabnine', build = './install.sh' }, @@ -14,38 +13,9 @@ local M = { 'hrsh7th/cmp-calc', 'hrsh7th/cmp-cmdline', 'hrsh7th/cmp-nvim-lsp', - 'hrsh7th/cmp-nvim-lua', 'hrsh7th/cmp-path', 'petertriho/cmp-git', 'hrsh7th/cmp-nvim-lsp-signature-help', - { - 'zbirenbaum/copilot.lua', - config = function() - vim.schedule(function() - require('copilot').setup { - copilot_node_command = '/usr/local/bin/node', - filetypes = { ['*'] = true }, - panel = { - enabled = true, - auto_refresh = false, - keymap = { - jump_prev = '[[', - jump_next = ']]', - accept = '', - refresh = 'gr', - open = '', - }, - }, - suggestion = { - auto_trigger = true, - keymap = { - accept = '', - }, - }, - } - end) - end, - }, }, } @@ -67,7 +37,6 @@ M.config = function() git = '[Git]', luasnip = '[Snpt]', nvim_lsp = '[LSP]', - nvim_lua = '[Vim]', path = '[Path]', calc = '[Calc]', ['vim-dadbod-completion'] = '[DB]', @@ -80,6 +49,13 @@ M.config = function() local custom_kinds_hl = {} vim.api.nvim_set_hl(0, 'CmpItemKindTabNine', { link = 'Green' }) cmp.setup { + enabled = function() + local ft = vim.api.nvim_get_option_value('filetype', { buf = 0 }) + if string.find(ft, 'k8s_') then + return false + end + return true + end, native_menu = false, view = { entries = { @@ -180,17 +156,16 @@ M.config = function() }, }, sources = cmp.config.sources { - { name = 'nvim_lsp', priority = 100 }, + { name = 'nvim_lsp_signature_help', priority = 101 }, + { name = 'nvim_lsp', priority = 100 }, { name = 'luasnip' }, - { name = 'nvim_lua' }, { name = 'lazydev', group_index = 0, -- set group index to 0 to skip loading LuaLS completions }, - { name = 'nvim_lsp_signature_help' }, { name = 'cmp_tabnine' }, { name = 'path' }, - { name = 'buffer', keyword_length = 4 }, + { name = 'buffer', keyword_length = 4 }, { name = 'calc' }, }, snippet = { @@ -204,7 +179,7 @@ M.config = function() }, } - cmp.setup.filetype('gitcommit', { + cmp.setup.filetype({ 'gitcommit', 'octo' }, { sources = cmp.config.sources({ { name = 'git' }, }, { @@ -249,6 +224,9 @@ M.config = function() }), }) + local cmp_autopairs = require 'nvim-autopairs.completion.cmp' + cmp.event:on('confirm_done', cmp_autopairs.on_confirm_done()) + require('luasnip.loaders.from_vscode').lazy_load() require('luasnip.loaders.from_vscode').lazy_load { paths = '~/.config/nvim/snippets' } end diff --git a/nvim/.config/nvim/lua/plugins/conform.lua b/nvim/.config/nvim/lua/plugins/conform.lua index 7a05141..df6c1ac 100644 --- a/nvim/.config/nvim/lua/plugins/conform.lua +++ b/nvim/.config/nvim/lua/plugins/conform.lua @@ -58,6 +58,7 @@ return { vim.ui.select(vim.list_extend(lsp_fmts, conform_fmts), { prompt = 'Select LSP client', + title = 'LSP clients', format_item = function(client) return client.name end, @@ -88,7 +89,7 @@ return { ---@module "conform" ---@type conform.setupOpts opts = { - log_level = vim.log.levels.DEBUG, + log_level = vim.log.levels.INFO, formatters_by_ft = { Jenkinsfile = { 'npm-groovy-lint' }, astro = { 'prettierd' }, @@ -106,7 +107,13 @@ return { less = { 'prettierd' }, lua = { 'stylua' }, markdown = { 'cbfmt', 'injected', 'markdownlint' }, - python = { 'isort', 'black' }, + python = function(bufnr) + if require('conform').get_formatter_info('ruff_format', bufnr).available then + return { 'ruff_format' } + else + return { 'isort', 'black' } + end + end, scss = { 'prettierd' }, sh = { 'shfmt' }, svelte = { 'prettierd' }, @@ -131,6 +138,6 @@ return { formatters = {}, }, init = function() - vim.o.formatexpr = "v:lua.require'conform'.formatexpr()" + vim.o.formatexpr = "v:lua.require'conform'.formatexpr({timeout_ms=5000})" end, } diff --git a/nvim/.config/nvim/lua/plugins/dap.lua b/nvim/.config/nvim/lua/plugins/dap.lua index 5264095..6ea2fbd 100644 --- a/nvim/.config/nvim/lua/plugins/dap.lua +++ b/nvim/.config/nvim/lua/plugins/dap.lua @@ -5,16 +5,19 @@ local actions = function() ['continue (F5)'] = function() dap.continue() end, - ['step over'] = function() + ['step over (do)'] = function() dap.step_over() end, - ['step into'] = function() + ['step into (di)'] = function() dap.step_into() end, - ['step out'] = function() + ['step out (dO)'] = function() dap.step_out() end, - ['toggle breakpoint'] = function() + ['terminate (dq)'] = function() + dap.terminate() + end, + ['toggle breakpoint (db)'] = function() dap.toggle_breakpoint() end, ['clear all breakpoints'] = function() @@ -26,7 +29,7 @@ local actions = function() ['run last'] = function() dap.run_last() end, - ['ui'] = function() + ['ui (du)'] = function() dapui.toggle() end, ['log level trace'] = function() @@ -58,20 +61,40 @@ local M = { 'mxsdev/nvim-dap-vscode-js', 'theHamsta/nvim-dap-virtual-text', 'jay-babu/mason-nvim-dap.nvim', - { 'leoluz/nvim-dap-go', lazy = true }, }, } M.keys = { { '', 'lua require("dap").continue()' }, - { 'bp', 'lua require("dap").toggle_breakpoint()' }, + { 'db', 'lua require("dap").toggle_breakpoint()' }, } M.config = function() + local dap = require 'dap' + local dapui = require 'dapui' + local dap_python = require 'dap-python' local cmp = require 'cmp' local mason_nvim_dap = require 'mason-nvim-dap' + + dapui.setup() + dap.listeners.after.event_initialized['dapui_config'] = function() + dapui.open {} + end + dap.listeners.before.event_terminated['dapui_config'] = function() + dapui.close {} + end + dap.listeners.before.event_exited['dapui_config'] = function() + dapui.close {} + end + + ---@diagnostic disable-next-line: missing-fields + require('nvim-dap-virtual-text').setup { + commented = true, -- Show virtual text alongside comment + } + ---@diagnostic disable-next-line: missing-fields mason_nvim_dap.setup { + automatic_installation = true, ensure_installed = { 'bash', 'chrome', @@ -84,56 +107,56 @@ M.config = function() -- all sources with no handler get passed here -- Keep original functionality - require('mason-nvim-dap').default_setup(config) + mason_nvim_dap.default_setup(config) end, }, } - -- mason_nvim_dap.setup_handlers() - local dap = require 'dap' - local dapui = require 'dapui' - dapui.setup() - require('nvim-dap-virtual-text').setup { enabled = true } + dap_python.setup 'python3' + dap.adapters.bashdb = { + type = 'executable', + command = vim.fn.stdpath 'data' .. '/mason/packages/bash-debug-adapter/bash-debug-adapter', + name = 'bashdb', + } + local dap_conf_sh = { + { + type = 'bashdb', + request = 'launch', + name = 'Launch file', + showDebugOutput = true, + pathBashdb = vim.fn.stdpath 'data' .. '/mason/packages/bash-debug-adapter/extension/bashdb_dir/bashdb', + pathBashdbLib = vim.fn.stdpath 'data' .. '/mason/packages/bash-debug-adapter/extension/bashdb_dir', + trace = true, + file = '${file}', + program = '${file}', + cwd = '${workspaceFolder}', + pathCat = 'cat', + pathBash = '/opt/homebrew/bin/bash', + pathMkfifo = 'mkfifo', + pathPkill = 'pkill', + args = {}, + env = {}, + terminalKind = 'integrated', + }, + } + dap.configurations.sh = dap_conf_sh + dap.configurations.bash = dap_conf_sh - vim.fn.sign_define('DapBreakpoint', { text = '🛑', texthl = '', linehl = '', numhl = '' }) - vim.fn.sign_define('DapBreakpointRejected', { text = '❓', texthl = '', linehl = '', numhl = '' }) - vim.fn.sign_define('DapStopped', { text = '⭕️', texthl = '', linehl = '', numhl = '' }) + vim.fn.sign_define('DapBreakpoint', { text = '', texthl = 'DiagnosticSignError', linehl = '', numhl = '' }) + vim.fn.sign_define('DapBreakpointRejected', { text = '', texthl = 'DiagnosticSignError', linehl = '', numhl = '' }) + vim.fn.sign_define('DapStopped', { text = '', texthl = 'DiagnosticSignWarn', linehl = 'Visual', numhl = 'DiagnosticSignWarn' }) -- Actions local the_actions = actions() require('user.menu').add_actions('DAP', the_actions) vim.keymap.set('n', 'dm', function() - vim.ui.select(vim.tbl_keys(the_actions), { prompt = 'Choose DAP action' }, function(choice) + vim.ui.select(vim.tbl_keys(the_actions), { prompt = 'Choose DAP action', title = 'DAP Actions' }, function(choice) if choice then the_actions[choice]() end end) end) - -- Python - require('dap-python').setup(vim.fn.stdpath 'data' .. '/mason/packages/debugpy/venv/bin/python3') - - table.insert(dap.configurations.python, { - justMyCode = false, - }) - - -- lua - dap.configurations.lua = { - { - type = 'nlua', - request = 'attach', - name = 'Attach to running Neovim instance', - }, - } - - dap.adapters.nlua = function(callback, config) - ---@diagnostic disable-next-line: undefined-field - callback { type = 'server', host = config.host or '127.0.0.1', port = config.port or 8086 } - end - - -- go - require('dap-go').setup() - ------------- -- Set CMP -- ------------- @@ -143,12 +166,48 @@ M.config = function() { name = 'dap' }, }, }) + ---@diagnostic disable-next-line: missing-fields cmp.setup { enabled = function() return vim.api.nvim_get_option_value('buftype', { buf = 0 }) ~= 'prompt' or require('cmp_dap').is_dap_buffer() end, } + + ------------- + -- keymaps -- + ------------- + local opts = { noremap = true, silent = true, buffer = true } + + -- Continue / Start + vim.keymap.set('n', 'dc', function() + dap.continue() + end, opts) + + -- Step Over + vim.keymap.set('n', 'do', function() + dap.step_over() + end, opts) + + -- Step Into + vim.keymap.set('n', 'di', function() + dap.step_into() + end, opts) + + -- Step Out + vim.keymap.set('n', 'dO', function() + dap.step_out() + end, opts) + + -- Keymap to terminate debugging + vim.keymap.set('n', 'dq', function() + dap.terminate() + end, opts) + + -- Toggle DAP UI + vim.keymap.set('n', 'du', function() + dapui.toggle() + end, opts) end return M diff --git a/nvim/.config/nvim/lua/plugins/databases.lua b/nvim/.config/nvim/lua/plugins/databases.lua index 2e4fce3..2271f4d 100644 --- a/nvim/.config/nvim/lua/plugins/databases.lua +++ b/nvim/.config/nvim/lua/plugins/databases.lua @@ -1,6 +1,7 @@ local M = { { 'tpope/vim-dadbod', + enabled = false, cmd = { 'DB', 'DBUI', @@ -14,6 +15,31 @@ local M = { 'DBCompletionClearCache', }, init = function() + vim.g.db_ui_use_nerd_fonts = 1 + local sql_helpers = { + Count = 'SELECT COUNT(*) FROM `{table}`;', + List = 'SELECT * FROM `{table}` LIMIT 10;', + Indexes = 'SHOW INDEXES FROM `{table}`;', + ForeignKeys = 'SHOW CREATE TABLE `{table}`;', + PrimaryKeys = 'SHOW KEYS FROM `{table}` WHERE Key_name = "PRIMARY";', + } + vim.g.db_ui_table_helpers = { + sqlite = sql_helpers, + mysql = sql_helpers, + mongodb = { + Count = 'db.{table}.countDocuments({});', + List = 'db.{table}.find().limit(10).toArray();', + Indexes = 'db.{table}.getIndexes();', + Collections = 'db.getCollectionNames();', + DatabaseInfo = 'db.stats();', + }, + redis = { + Keys = 'KEYS *;', + List = 'LRANGE {table} 0 10;', -- Assuming you're using a list type + Hash = 'HGETALL {table};', -- Assuming you're using a hash type + DatabaseInfo = 'INFO;', + }, + } require('user.menu').add_actions('Database', { ['Add Connection'] = function() vim.cmd 'DBUIAddConnection' @@ -39,18 +65,8 @@ local M = { }) end, dependencies = { - { - 'kristijanhusak/vim-dadbod-ui', - config = function() - vim.g.db_ui_table_helpers = { - sqlite = { - Schema = '.schema "{table}"', - Count = 'SELECT COUNT(*) FROM "{table}"', - }, - } - end, - }, - 'kristijanhusak/vim-dadbod-completion', + { 'kristijanhusak/vim-dadbod-ui' }, + { 'kristijanhusak/vim-dadbod-completion', ft = { 'sql', 'mysql', 'plsql' }, lazy = true }, }, }, } diff --git a/nvim/.config/nvim/lua/plugins/functionality.lua b/nvim/.config/nvim/lua/plugins/functionality.lua index c20d7c5..51d3740 100644 --- a/nvim/.config/nvim/lua/plugins/functionality.lua +++ b/nvim/.config/nvim/lua/plugins/functionality.lua @@ -12,83 +12,37 @@ local M = { end, }, { - 'mosheavni/vim-kubernetes', - ft = 'yaml', - config = function() - require('user.menu').add_actions('Kubernetes', { - ['Apply (:KubeApply)'] = function() - vim.cmd [[KubeApply]] - end, - ['Apply Directory (:KubeApplyDir)'] = function() - vim.cmd [[KubeApplyDir]] - end, - ['Create (:KubeCreate)'] = function() - vim.cmd [[KubeCreate]] - end, - ['Decode Secret (:KubeDecodeSecret)'] = function() - vim.cmd [[KubeDecodeSecret]] - end, - ['Delete (:KubeDelete)'] = function() - vim.cmd [[KubeDelete]] - end, - ['Delete Dir (:KubeDeleteDir)'] = function() - vim.cmd [[KubeDeleteDir]] - end, - ['Encode Secret (:KubeEncodeSecret)'] = function() - vim.cmd [[KubeEncodeSecret]] - end, - ['Recreate (:KubeRecreate)'] = function() - vim.cmd [[KubeRecreate]] - end, - }) - end, - }, - { - 'chomosuke/term-edit.nvim', - event = 'TermOpen', - opts = { - prompt_end = '%$ ', - }, - version = '1.*', - }, - { - 'mosheavni/vim-dirdiff', + 'mosheavni/vim-dirdiff', -- todo convert to difftool cmd = { 'DirDiff' }, init = function() require('user.menu').add_actions('Diff', { ['Between 2 directories'] = function() local pretty_print = require('user.utils').pretty_print - vim.ui.input({ prompt = 'Directory A: ' }, function(a) - if not a or a == '' then - pretty_print 'Canceled.' - return - end - vim.ui.input({ prompt = 'Directory B: ' }, function(b) - if not b or b == '' then + vim.defer_fn(function() + vim.ui.input({ prompt = 'Directory A: ' }, function(a) + if not a or a == '' then pretty_print 'Canceled.' return end - vim.cmd('DirDiff ' .. a .. ' ' .. b) + vim.defer_fn(function() + vim.ui.input({ prompt = 'Directory B: ' }, function(b) + if not b or b == '' then + pretty_print 'Canceled.' + return + end + vim.cmd('DirDiff ' .. a .. ' ' .. b) + end) + end, 100) end) - end) + end, 100) end, }) end, }, { - 'simeji/winresizer', - keys = { '' }, - config = function() - vim.g.winresizer_vert_resize = 4 - vim.g.winresizer_start_key = '' - end, - init = function() - require('user.menu').add_actions(nil, { - ['Resize window ()'] = function() - vim.fn.feedkeys(vim.keycode '') - end, - }) - end, + 'windwp/nvim-autopairs', + event = 'InsertEnter', + opts = {}, }, { 'iamcco/markdown-preview.nvim', @@ -107,27 +61,64 @@ local M = { end, }, { - 'AndrewRadev/linediff.vim', - cmd = { 'Linediff' }, - }, - { - 'ellisonleao/carbon-now.nvim', - cmd = 'CarbonNow', - opts = { open_cmd = 'open' }, + 'gbprod/yanky.nvim', + dependencies = { 'kkharji/sqlite.lua' }, + cmd = { 'YankyRingHistory' }, + keys = { + 'yy', + { 'p', '(YankyPutAfter)', mode = { 'n', 'x' } }, + { 'P', '(YankyPutBefore)', mode = { 'n', 'x' } }, + { '', '(YankyCycleForward)' }, + { '', '(YankyCycleBackward)' }, + { 'y', 'YankyRingHistory' }, + }, + opts = { + ring = { + history_length = 100, + storage = 'sqlite', + sync_with_numbered_registers = true, + cancel_event = 'update', + }, + }, init = function() - require('user.menu').add_actions('Carbon', { - ['Create a beautiful image of the code'] = function() - vim.cmd.CarbonNow() + require('user.menu').add_actions('Yanky', { + ['Yank history'] = function() + vim.cmd 'YankyRingHistory' end, }) end, }, + { + 'AndrewRadev/linediff.vim', + cmd = { 'Linediff' }, + }, { 'stevearc/oil.nvim', cmd = { 'Oil' }, - ---@module 'oil' - ---@type oil.SetupOpts - opts = {}, + keys = { + { '', "lua require('oil').open_float()" }, + }, + opts = { + -- Configuration for the floating window in oil.open_float + float = { + -- optionally override the oil buffers window title with custom function: fun(winid: integer): string + get_win_title = nil, + -- preview_split: Split direction: "auto", "left", "right", "above", "below". + -- max_width and max_height can be integers or a float between 0 and 1 (e.g. 0.4 for 40%) + max_width = 0.6, + max_height = 0.7, + preview_split = 'right', + -- This is the config that will be passed to nvim_open_win. + -- Change values here to customize the layout + override = function(conf) + return conf + end, + }, + view_options = { + -- Show files and directories that start with "." + show_hidden = true, + }, + }, }, { 'kawre/leetcode.nvim', @@ -159,6 +150,27 @@ local M = { }, }, }, + { + 'mrjones2014/smart-splits.nvim', + config = function() + require('smart-splits').setup() + vim.keymap.set('n', '', require('smart-splits').resize_left) + vim.keymap.set('n', '', require('smart-splits').resize_down) + vim.keymap.set('n', '', require('smart-splits').resize_up) + vim.keymap.set('n', '', require('smart-splits').resize_right) + -- moving between splits + vim.keymap.set('n', '', require('smart-splits').move_cursor_left) + vim.keymap.set('n', '', require('smart-splits').move_cursor_down) + vim.keymap.set('n', '', require('smart-splits').move_cursor_up) + vim.keymap.set('n', '', require('smart-splits').move_cursor_right) + end, + }, + { + 'nvzone/typr', + dependencies = 'nvzone/volt', + opts = {}, + cmd = { 'Typr', 'TyprStats' }, + }, } return M diff --git a/nvim/.config/nvim/lua/plugins/fzf.lua b/nvim/.config/nvim/lua/plugins/fzf.lua index 442d504..111c8ad 100644 --- a/nvim/.config/nvim/lua/plugins/fzf.lua +++ b/nvim/.config/nvim/lua/plugins/fzf.lua @@ -9,13 +9,11 @@ return { { 'hh', ':FzfLua help_tags', silent = true }, { 'i', ':FzfLua oldfiles', silent = true }, { - 'ccp', - function() - local actions = require 'CopilotChat.actions' - require('CopilotChat.integrations.fzflua').pick(actions.prompt_actions()) - end, - desc = 'CopilotChat - Prompt actions', - mode = { 'n', 'v' }, + '', + require('fzf-lua').complete_path, + mode = 'i', + silent = true, + desc = 'Fuzzy complete path', }, { '', @@ -25,30 +23,25 @@ return { require('fzf-lua').git_branches { actions = { - ['default'] = function(selected) - actions.git_switch(selected) - require('user.git').reload_fugitive_index() - end, + ['default'] = { + fn = function(selected, opts) + actions.git_switch(selected, opts) + require('user.git').reload_fugitive_index() + end, + header = 'switch', + }, -- perform checkout instead of switch ['ctrl-s'] = { fn = function(selected) - local branch = selected[1] - local _, ret, stderr = require('user.utils').get_os_command_output({ 'git', 'checkout', branch }, vim.fn.getcwd()) - require('user.git').reload_fugitive_index() - if ret == 0 then - utils.info('Switched to branch ' .. branch) - return - else - local msg = string.format('Error when switching to branch: %s. Git returned:\n%s', branch, table.concat(stderr or {}, '\n')) - utils.err(msg) - end + local branch = vim.trim(selected[1]) + require('user.git').checkout(branch) end, reload = false, - header = 'switch', + header = 'checkout', }, ['ctrl-y'] = { fn = function(selected) - local branch = selected[1] + local branch = vim.trim(selected[1]) vim.fn.setreg('+', branch) utils.info('Yanked branch name ' .. branch) end, @@ -58,9 +51,9 @@ return { ['ctrl-r'] = { fn = function(selected) require('fzf-lua.utils').fzf_exit() - local branch = selected[1] + local branch = vim.trim(selected[1]) vim.defer_fn(function() - vim.ui.input({ prompt = 'Rename branch: ', default = selected[1] }, function(new_name) + vim.ui.input({ prompt = 'Rename branch: ', default = branch }, function(new_name) if not new_name or new_name == '' then utils.warn 'Action aborted' return @@ -83,8 +76,8 @@ return { }, ['ctrl-x'] = { fn = function(selected) - local branch = selected[1] - vim.ui.select({ 'Yes', 'No' }, { prompt = 'Are you sure you want to delete the branch ' .. branch }, function(yes_or_no) + local branch = vim.trim(selected[1]) + vim.ui.select({ 'Yes', 'No' }, { prompt = 'Are you sure you want to delete the branch ' .. branch .. '?' }, function(yes_or_no) if yes_or_no == 'No' then utils.warn 'Action aborted' return @@ -121,38 +114,33 @@ return { header = 'delete', }, }, - cmd = [=[git for-each-ref --sort=-committerdate --format="%(refname:short)" | grep -n . | sed "s?origin/??g" | sort -t: -k2 -u | sort -n | cut -d: -f2]=], + cmd = 'git-branches.zsh', } end, }, { '/', - function() - require('fzf-lua').live_grep { - multiprocess = true, - rg_opts = [=[--column --line-number --hidden --no-heading --color=always --smart-case --max-columns=4096 -g '!.git' -e]=], - } - end, + require('fzf-lua').live_grep, }, }, cmd = { 'FzfLua', 'ListFilesFromBranch' }, config = function() require('fzf-lua').setup { 'default-title', - previewers = { - builtin = { - syntax_limit_b = 1024 * 100, -- 100KB - extensions = { - png = { 'viu', '-b' }, - jpg = { 'viu', '-b' }, - }, - }, + fzf_opts = { + ['--cycle'] = true, + ['--history'] = vim.fn.stdpath 'data' .. '/fzf-lua-history', -- - next, - previous + }, + files = { + git_icons = true, }, oldfiles = { cwd_only = true, include_current_session = true, }, grep = { + multiprocess = true, + RIPGREP_CONFIG_PATH = vim.env.HOME .. '/.ripgreprc', -- One thing I missed from Telescope was the ability to live_grep and the -- run a filter on the filenames. -- Ex: Find all occurrences of "enable" but only in the "plugins" directory. @@ -162,10 +150,12 @@ return { rg_glob = true, -- enable glob parsing glob_flag = '--iglob', -- case insensitive globs glob_separator = '%s%-%-', -- query separator pattern (lua): ' --' + hidden = true, }, keymap = { fzf = { ['ctrl-q'] = 'select-all+accept' } }, } - require('fzf-lua').register_ui_select(function(_, items) + + require('fzf-lua').register_ui_select(function(opts, items) local min_h, max_h = 0.15, 0.70 local h = (#items + 4) / vim.o.lines if h < min_h then @@ -173,7 +163,9 @@ return { elseif h > max_h then h = max_h end - return { winopts = { height = h, width = 0.60, row = 0.40 } } + opts.title = opts.title or 'Select' + + return { winopts = { title = opts.title, height = h, width = 0.60, row = 0.40 } } end) end, } diff --git a/nvim/.config/nvim/lua/plugins/git.lua b/nvim/.config/nvim/lua/plugins/git.lua index 7a352ca..7a43020 100644 --- a/nvim/.config/nvim/lua/plugins/git.lua +++ b/nvim/.config/nvim/lua/plugins/git.lua @@ -9,7 +9,9 @@ local actions = function() end, ['Create Pull Request (pr in git buffer)'] = git_funcs.create_pull_request, ['Checkout new branch (:Gcb {new_branch})'] = function() - git_funcs.create_new_branch { args = '' } + vim.defer_fn(function() + git_funcs.create_new_branch { args = '' } + end, 100) end, ['Set upstream to HEAD'] = git_funcs.set_upstream_head, ['Blame'] = function() @@ -50,14 +52,16 @@ local actions = function() ['Delete tag'] = git_funcs.ui_select_delete_tag, ['Find in all commits'] = function() local rev_list = vim.fn.FugitiveExecute({ 'rev-list', '--all' }).stdout - vim.ui.input({ prompt = 'Enter search term: ' }, function(search_term) - if not search_term then - git_funcs.prnt 'Canceled.' - return - end - git_funcs.prnt('Searching for ' .. search_term .. ' in all commits...') - vim.cmd('silent Ggrep ' .. vim.fn.fnameescape(search_term) .. ' ' .. table.concat(rev_list, ' ')) - end) + vim.defer_fn(function() + vim.ui.input({ prompt = 'Enter search term: ' }, function(search_term) + if not search_term then + git_funcs.prnt 'Canceled.' + return + end + git_funcs.prnt('Searching for ' .. search_term .. ' in all commits...') + vim.cmd('silent Ggrep ' .. vim.fn.fnameescape(search_term) .. ' ' .. table.concat(rev_list, ' ')) + end) + end, 100) end, ['Push (:Gp)'] = git_funcs.push, ['Pull (:Gl)'] = git_funcs.pull, @@ -72,9 +76,14 @@ end local diff_actions = { ['[Diffview] Diff File History'] = function() - vim.ui.input({ prompt = 'Enter file path (empty for all files, % for current): ' }, function(file_to_check) - vim.cmd('DiffviewFileHistory ' .. file_to_check) - end) + vim.defer_fn(function() + vim.ui.input({ prompt = 'Enter file path (empty for all files, % for current): ' }, function(file_to_check) + if not file_to_check then + return + end + vim.cmd('DiffviewFileHistory ' .. file_to_check) + end) + end, 100) end, ['[Diffview] Diff with branch'] = function() git_funcs.ui_select_remotes(function(remote) @@ -86,6 +95,9 @@ local diff_actions = { ['[Diffview] Diff close'] = function() vim.cmd 'DiffviewClose' end, + ['[Diffview] stashes'] = function() + vim.cmd 'DiffviewFileHistory -g --range=stash' + end, } local fugitive_config = function() @@ -103,17 +115,22 @@ local fugitive_config = function() -- Toggle fugitive -- --------------------- vim.keymap.set('n', 'gg', function() - local _, fugitive_buf = pcall(vim.fn.bufname, '.git/') - if fugitive_buf == '' then - vim.cmd 'Git' - else - local bufnr = vim.fn.bufnr(fugitive_buf) - if vim.bo[bufnr].buflisted then - vim.cmd('bd ' .. fugitive_buf) - else - vim.cmd 'Git' + local to_close = {} + for _, win in ipairs(vim.api.nvim_list_wins()) do + local buf = vim.api.nvim_win_get_buf(win) + if vim.bo[buf].filetype == 'fugitive' then + to_close[#to_close + 1] = win end end + if #to_close > 0 then + for _, win in ipairs(to_close) do + if vim.api.nvim_win_is_valid(win) then + pcall(vim.api.nvim_win_close, win, true) + end + end + return + end + vim.cmd 'Git' end) -------------------------------- @@ -183,7 +200,7 @@ local fugitive_config = function() vim.keymap.set('n', 'gm', function() local git_actions = require('user.menu').get_actions { prefix = 'Git' } - vim.ui.select(vim.tbl_keys(git_actions), { prompt = 'Choose git action: ' }, function(choice) + vim.ui.select(vim.tbl_keys(git_actions), { title = 'Git actions', prompt = 'Choose git action: ' }, function(choice) if not choice then utils.pretty_print('Canceled.', 'Git Actions', '') return @@ -220,42 +237,6 @@ local M = { 'Cpr', }, }, - { - 'moyiz/git-dev.nvim', - opts = { - ephemeral = false, - read_only = false, - opener = function(dir) - vim.cmd('NvimTreeOpen ' .. vim.fn.fnameescape(dir)) - end, - }, - keys = { - { - 'go', - function() - local repo = vim.fn.input 'Repository name / URI: ' - if repo ~= '' then - require('git-dev').open(repo) - end - end, - desc = '[O]pen a remote git repository', - }, - }, - config = function(_, opts) - require('user.menu').add_actions('Git', { - ['Open a remote git repository (go)'] = function() - vim.ui.input({ prompt = 'Enter git repository URL: ' }, function(url) - if not url then - return - end - require('git-dev').open(url) - end) - end, - }) - require('git-dev').setup(opts) - end, - }, - { 'akinsho/git-conflict.nvim', version = '*', @@ -304,8 +285,18 @@ local M = { }, keys = { -- { 'gd', 'DiffviewFileHistory', mode = { 'n', 'v' }, desc = 'Diffview files' }, - { 'gd', diff_actions['[Diffview] Diff File History'], mode = 'n', desc = 'Diffview files' }, - { 'gd', ':DiffviewFileHistory', mode = 'v', desc = 'Diffview selection' }, + { + 'gd', + diff_actions['[Diffview] Diff File History'], + mode = 'n', + desc = 'Diffview files', + }, + { + 'gd', + ':DiffviewFileHistory', + mode = 'v', + desc = 'Diffview selection', + }, }, config = function() require 'diffview' diff --git a/nvim/.config/nvim/lua/plugins/hlslens.lua b/nvim/.config/nvim/lua/plugins/hlslens.lua deleted file mode 100644 index 21193ea..0000000 --- a/nvim/.config/nvim/lua/plugins/hlslens.lua +++ /dev/null @@ -1,13 +0,0 @@ -return { - 'kevinhwang91/nvim-hlslens', - keys = { - { 'n', [[execute('normal! ' . v:count1 . 'n')lua require('hlslens').start()zz]] }, - { 'N', [[execute('normal! ' . v:count1 . 'N')lua require('hlslens').start()zz]] }, - { '*', [[*lua require('hlslens').start()]] }, - { '#', [[#lua require('hlslens').start()]] }, - { 'g*', [[g*lua require('hlslens').start()]] }, - { 'g#', [[g#lua require('hlslens').start()]] }, - }, - event = 'CmdlineEnter', - opts = {}, -} diff --git a/nvim/.config/nvim/lua/plugins/init.lua b/nvim/.config/nvim/lua/plugins/init.lua index c35cac6..a6f545e 100644 --- a/nvim/.config/nvim/lua/plugins/init.lua +++ b/nvim/.config/nvim/lua/plugins/init.lua @@ -1,46 +1,26 @@ +---@class PluginSpec[] +--- Plugin specifications for Lazy.nvim plugin manager +--- This file contains the core plugin configurations for Neovim local M = { - ------------------------------------ - -- Language Server Protocol (LSP) -- - ------------------------------------ { - 'folke/trouble.nvim', - opts = {}, - cmd = 'Trouble', - }, - { - 'sam4llis/nvim-lua-gf', - ft = 'lua', - }, - { - 'NStefan002/2048.nvim', - cmd = 'Play2048', - config = true, + 'nvim-lua/plenary.nvim', + cmd = { + 'PlenaryBustedFile', + 'PlenaryBustedDirectory', + }, + keys = { + { 'tf', 'PlenaryBustedFile %', mode = 'n' }, + }, }, { 'milisims/nvim-luaref', ft = 'lua', }, + { 'Bilal2453/luvit-meta', lazy = true }, { 'chr4/nginx.vim', ft = 'nginx', }, - { - 'ton/vim-bufsurf', - event = { 'BufReadPre', 'BufNewFile' }, - keys = { - { ']b', '(buf-surf-forward)' }, - { '[b', '(buf-surf-back)' }, - }, - }, - { - 'chrisbra/vim-sh-indent', - ft = { 'sh', 'bash', 'zsh' }, - }, - { 'Bilal2453/luvit-meta', lazy = true }, - - -------------- - -- Quickfix -- - -------------- { 'yorickpeterse/nvim-pqf', opts = {}, @@ -56,10 +36,6 @@ local M = { 'kevinhwang91/nvim-bqf', ft = 'qf', }, - - ----------------------- - -- Text Manipulation -- - ----------------------- { 'junegunn/vim-easy-align', keys = { { 'ga', '(EasyAlign)', mode = { 'v', 'n' } } }, @@ -85,7 +61,7 @@ local M = { vim.fn['switch#NormalizedCase'] { 'enable', 'disable' }, vim.fn['switch#NormalizedCase'] { 'Always', 'Never' }, vim.fn['switch#NormalizedCase'] { 'debug', 'info', 'warning', 'error', 'critical' }, - { '==', '!=' }, + { '==', '!=', '~=' }, { [fk] = [=[\=toupper(submatch(1)) . submatch(2)]=], [sk] = [=[\=tolower(substitute(submatch(0), '\(\l\)\(\u\)', '\1_\2', 'g'))]=], @@ -141,23 +117,6 @@ local M = { { 'S', '(leap-backward-to)' }, }, }, - { - 'atusy/treemonkey.nvim', - keys = { - { - 'm', - function() - require 'nvim-treesitter.configs' - ---@diagnostic disable-next-line: missing-fields - require('treemonkey').select { - ignore_injections = false, - action = require('treemonkey.actions').unite_selection, - } - end, - mode = { 'x', 'o' }, - }, - }, - }, { 'axelvc/template-string.nvim', ft = { 'typescript', 'javascript', 'typescriptreact', 'javascriptreact', 'python' }, @@ -165,60 +124,26 @@ local M = { config = true, }, { - 'machakann/vim-swap', + 'kevinhwang91/nvim-hlslens', keys = { - { 'sw', '(swap-interactive)', mode = { 'n', 'v' } }, - { 'g<', '(swap-prev)' }, - { 'g>', '(swap-next)' }, + { 'n', [[execute('normal! ' . v:count1 . 'n')lua require('hlslens').start()zzzv]] }, + { 'N', [[execute('normal! ' . v:count1 . 'N')lua require('hlslens').start()zzzv]] }, + { '*', [[*lua require('hlslens').start()N]] }, + { '#', [[#lua require('hlslens').start()n]] }, + { 'g*', [[g*lua require('hlslens').start()]] }, + { 'g#', [[g#lua require('hlslens').start()]] }, }, - init = function() - vim.g.swap_no_default_key_mappings = true - end, - }, - { - 'vidocqh/auto-indent.nvim', - event = 'InsertEnter', + event = 'CmdlineEnter', opts = {}, }, { - 'CopilotC-Nvim/CopilotChat.nvim', - cmd = { - 'CopilotChat', - 'CopilotChatAgents', - 'CopilotChatClose', - 'CopilotChatCommit', - 'CopilotChatCommitStaged', - 'CopilotChatDebugInfo', - 'CopilotChatDocs', - 'CopilotChatExplain', - 'CopilotChatFix', - 'CopilotChatFixDiagnostic', - 'CopilotChatLoad', - 'CopilotChatModels', - 'CopilotChatOpen', - 'CopilotChatOptimize', - 'CopilotChatReset', - 'CopilotChatReview', - 'CopilotChatSave', - 'CopilotChatStop', - 'CopilotChatTests', - 'CopilotChatToggle', - }, - dependencies = { - { 'zbirenbaum/copilot.lua' }, -- or github/copilot.vim - { 'nvim-lua/plenary.nvim' }, -- for curl, log wrapper - }, - build = 'make tiktoken', -- Only on MacOS or Linux - opts = { - model = 'claude-3.5-sonnet', - question_header = ' User ', -- Header to use for user questions - answer_header = ' Copilot ', -- Header to use for AI answers - error_header = ' Error ', -- Header to use for errors - }, + 'machakann/vim-swap', keys = { - { 'ccc', 'CopilotChat', mode = { 'n', 'v' } }, - { 'ccs', 'CopilotChatStop' }, + { 'sw', '(swap-interactive)', mode = { 'n', 'v' } }, }, + init = function() + vim.g.swap_no_default_key_mappings = true + end, }, -- DONE ✅ diff --git a/nvim/.config/nvim/lua/plugins/kubectl.lua b/nvim/.config/nvim/lua/plugins/kubectl.lua index e026a25..03439e5 100644 --- a/nvim/.config/nvim/lua/plugins/kubectl.lua +++ b/nvim/.config/nvim/lua/plugins/kubectl.lua @@ -1,16 +1,25 @@ return { 'Ramilito/kubectl.nvim', + version = '2.*', + dependencies = 'saghen/blink.download', + dev = false, opts = { - log_level = vim.log.levels.DEBUG, - diff = { bin = 'kdiff' }, - headers = true, - hints = true, - context = true, - heartbeat = true, - kubernetes_versions = true, + kubectl_cmd = { + persist_context_change = true, + }, auto_refresh = { enabled = true, + interval = 300, -- milliseconds + }, + + headers = { + enabled = true, + hints = true, + context = true, + heartbeat = true, }, + -- log_level = vim.log.levels.DEBUG, + diff = { bin = 'kdiff' }, filter = { apply_on_select_from_history = false, max_history = 100, @@ -20,20 +29,24 @@ return { timestamps = false, prefix = false, }, - lineage = { - enabled = false, - }, - completion = { - follow_cursor = false, + alias = { + apply_on_select_from_history = true, + max_history = 30, }, + -- lineage = { + -- enabled = false, + -- }, + -- completion = { + -- follow_cursor = false, + -- }, }, cmd = { 'Kubectl', 'Kubectx', 'Kubens' }, keys = { { 'k', 'lua require("kubectl").toggle()' }, - { '', '(kubectl.kill)', ft = 'k8s_*' }, - { '7', '(kubectl.view_nodes)', ft = 'k8s_*' }, - { '8', '(kubectl.view_overview)', ft = 'k8s_*' }, - { '', '(kubectl.view_top)', ft = 'k8s_*' }, + { '7', '(kubectl.view_nodes)', ft = 'k8s_*' }, + { '8', '(kubectl.view_daemonsets)', ft = 'k8s_*' }, + { '9', '(kubectl.view_statefulsets)', ft = 'k8s_*' }, + { '', '(kubectl.view_top)', ft = 'k8s_*' }, { 'Z', function() @@ -47,6 +60,7 @@ return { state.setFilter(faults_filter) end, desc = 'Toggle faults', + ft = 'k8s_*', }, }, init = function() @@ -56,6 +70,31 @@ return { pattern = 'k8s_*', callback = function() vim.opt.titlestring = 'k8s: %t' + if vim.bo.filetype == 'k8s_yaml' then + vim.bo.filetype = 'yaml' + end + end, + }) + vim.api.nvim_create_autocmd('User', { + group = group, + pattern = 'K8sContextChanged', + callback = function(ctx) + vim.system({ 'kubectl', 'config', 'use-context', ctx.data.context }, { text = true }, function(results) + if not results then + vim.notify(results, vim.log.levels.INFO) + end + end) + end, + }) + vim.api.nvim_create_autocmd('User', { + group = group, + pattern = 'K8sResourceSelected', + callback = function(ctx) + local kubectl_user = require 'user.kubectl' + local kind = ctx.data.kind + if kubectl_user[kind] and kubectl_user[kind].select then + kubectl_user[kind].select(ctx.data.name, ctx.data.ns) + end end, }) end, diff --git a/nvim/.config/nvim/lua/plugins/look-and-feel.lua b/nvim/.config/nvim/lua/plugins/look-and-feel.lua index 995676a..9d53180 100644 --- a/nvim/.config/nvim/lua/plugins/look-and-feel.lua +++ b/nvim/.config/nvim/lua/plugins/look-and-feel.lua @@ -6,77 +6,26 @@ local M = { ------------------- -- Colorscheme -- ------------------- - { - 'navarasu/onedark.nvim', - enabled = false, - config = function() - require('onedark').setup { - style = 'dark', - highlights = { - EndOfBuffer = { fg = '#61afef' }, - }, - } - require('onedark').load() - end, - }, { 'rose-pine/neovim', name = 'rose-pine', opts = { + variant = 'moon', -- auto, main, moon, dawn styles = { bold = true, italic = true, transparency = true, }, + highlight_groups = { + StatusLine = { fg = 'love', bg = 'love', blend = 10 }, + StatusLineNC = { fg = 'subtle', bg = 'surface' }, + }, }, config = function(_, opts) require('rose-pine').setup(opts) vim.cmd [[colorscheme rose-pine]] end, }, - { - 'sainnhe/sonokai', - enabled = false, - config = function() - vim.cmd [[ - let g:sonokai_style = 'shusia' - let g:sonokai_transparent_background = 1 - colorscheme sonokai - ]] - vim.api.nvim_set_hl(0, 'WinSeparator', { fg = '#797D7D' }) - require('user.menu').add_actions('Colorscheme', { - ['Toggle Sonokai Style'] = function() - local styles = { 'default', 'atlantis', 'andromeda', 'shusia', 'maia', 'espresso' } - local current_value = vim.g.sonokai_style - local index = require('user.utils').tbl_get_next(styles, current_value) - vim.g.sonokai_style = styles[index] - vim.cmd [[colorscheme sonokai]] - require('user.utils').pretty_print('Set sonokai_style to ' .. styles[index]) - end, - }) - end, - }, - { - 'sainnhe/gruvbox-material', - enabled = false, - config = function() - -- load the colorscheme here - vim.cmd [[ - let g:gruvbox_material_better_performance = 1 - let g:gruvbox_material_background = 'hard' " soft | medium | hard - colorscheme gruvbox-material - ]] - end, - }, - { - 'tiagovla/tokyodark.nvim', - enabled = false, - opts = {}, - config = function(_, opts) - require('tokyodark').setup(opts) -- calling setup is optional - vim.cmd [[colorscheme tokyodark]] - end, - }, ----------- -- other -- @@ -84,6 +33,15 @@ local M = { { 'nvim-tree/nvim-web-devicons', lazy = true, + opts = { + override_by_extension = { + hcl = { + icon = '', + color = '#7182D0', + name = 'HCL', + }, + }, + }, }, { 'eero-lehtinen/oklch-color-picker.nvim', @@ -93,13 +51,7 @@ local M = { }, }, keys = { - { 'p', 'lua require("oklch-color-picker").pick_under_cursor()' }, - }, - }, - { - 'eandrju/cellular-automaton.nvim', - keys = { - { 'fml', 'CellularAutomaton make_it_rain' }, + { 'pc', 'lua require("oklch-color-picker").pick_under_cursor()' }, }, }, { @@ -113,14 +65,8 @@ local M = { }) end, }, - { - 'folke/twilight.nvim', - cmd = { 'Twilight', 'TwilightEnable', 'TwilightDisable' }, - opts = {}, - }, { 'luukvbaal/statuscol.nvim', - branch = '0.10', event = { 'BufReadPre', 'BufNewFile' }, config = function() local builtin = require 'statuscol.builtin' @@ -138,14 +84,45 @@ local M = { } end, }, + { + 'kevinhwang91/nvim-ufo', + dependencies = { 'kevinhwang91/promise-async' }, + event = 'BufReadPost', + keys = { + { 'fo', 'lua require("ufo").openAllFolds()' }, + { 'fc', 'lua require("ufo").closeAllFolds()' }, + { 'fp', 'lua require("ufo").peekFoldedLinesUnderCursor()' }, + }, + opts = { + open_fold_hl_timeout = 0, + }, + + init = function() + ---@diagnostic disable-next-line: inject-field + vim.o.foldcolumn = '1' -- '0' is not bad + ---@diagnostic disable-next-line: inject-field + vim.o.foldlevel = 99 -- Using ufo provider need a large value, feel free to decrease the value + ---@diagnostic disable-next-line: inject-field + vim.o.foldlevelstart = 99 + ---@diagnostic disable-next-line: inject-field + vim.o.foldenable = true + end, + }, { 'vim-scripts/CursorLineCurrentWindow', event = 'BufReadPost', }, { 'OXY2DEV/markview.nvim', - ft = 'markdown', -- If you decide to lazy-load anyway - opts = { initial_state = false }, + lazy = true, + -- For `nvim-treesitter` users. + priority = 49, + opts = { + preview = { + filetypes = { 'markdown', 'codecompanion' }, + ignore_buftypes = {}, + }, + }, }, } diff --git a/nvim/.config/nvim/lua/plugins/lsp.lua b/nvim/.config/nvim/lua/plugins/lsp.lua index fd9c516..c8e3594 100644 --- a/nvim/.config/nvim/lua/plugins/lsp.lua +++ b/nvim/.config/nvim/lua/plugins/lsp.lua @@ -4,13 +4,12 @@ local M = { } M.init = require('user.lsp.config').init - M.config = require('user.lsp.config').setup M.dependencies = { 'nvimtools/none-ls.nvim', { - 'williamboman/mason.nvim', + 'mason-org/mason.nvim', cmd = 'Mason', keys = { { 'cm', 'Mason', desc = 'Mason' } }, build = ':MasonUpdate', @@ -20,7 +19,6 @@ M.dependencies = { }, }, }, - 'williamboman/mason-lspconfig.nvim', { 'j-hui/fidget.nvim', opts = { @@ -40,12 +38,7 @@ M.dependencies = { config = function(_, opts) local navic = require 'nvim-navic' navic.setup(opts) - _G.get_winbar = function() - return vim.api.nvim_win_get_config(0).relative == '' and require('nvim-navic').get_location() or vim.fn.expand '%:~:.' - end - - -- vim.o.winbar = "%{%v:lua.require'nvim-navic'.get_location()%}" - vim.o.winbar = '%{%v:lua._G.get_winbar()%}' + vim.o.winbar = "%{%v:lua.require'nvim-navic'.get_location()%}" end, }, } @@ -93,14 +86,41 @@ local language_specific_plugins = { library = { -- See the configuration section for more details -- Load luvit types when the `vim.uv` word is found - { path = 'luvit-meta/library', words = { 'vim%.uv' } }, + { path = '${3rd}/luv/library', words = { 'vim%.uv' } }, + { path = 'snacks.nvim', words = { 'Snacks' } }, { path = 'wezterm-types', mods = { 'wezterm' } }, }, }, - -- config = function(_,opts) - -- require("lazydev").setup(opts) - -- require('cmp').register_source('lazydev', ) - -- end + }, + { + 'ray-x/go.nvim', + dependencies = { -- optional packages + 'ray-x/guihua.lua', + 'neovim/nvim-lspconfig', + 'nvim-treesitter/nvim-treesitter', + }, + opts = { + lsp_cfg = true, + lsp_gofumpt = true, + lsp_inlay_hints = { + enable = false, + }, + dap_vt = true, + }, + config = function(_, opts) + require('go').setup(opts) + local format_sync_grp = vim.api.nvim_create_augroup('GoFormat', {}) + vim.api.nvim_create_autocmd('BufWritePre', { + pattern = '*.go', + callback = function() + require('go.format').goimports() + end, + group = format_sync_grp, + }) + end, + event = { 'CmdlineEnter' }, + ft = { 'go', 'gomod' }, + build = ':lua require("go.install").update_all_sync()', -- if you need to install/update all binaries }, } diff --git a/nvim/.config/nvim/lua/plugins/lualine.lua b/nvim/.config/nvim/lua/plugins/lualine.lua index a1c9c67..236ec59 100644 --- a/nvim/.config/nvim/lua/plugins/lualine.lua +++ b/nvim/.config/nvim/lua/plugins/lualine.lua @@ -8,19 +8,35 @@ M.config = function() -- Color table for highlights -- stylua: ignore + -- local colors = { + -- bg = '#202328', + -- fg = '#bbc2cf', + -- aqua = '#6EB0A3', + -- yellow = '#ECBE7B', + -- cyan = '#008080', + -- darkblue = '#081633', + -- green = '#a9b665', + -- orange = '#FF8800', + -- violet = '#a9a1e1', + -- magenta = '#c678dd', + -- blue = '#51afef', + -- red = '#ec5f67', + -- } + +local palette = require("rose-pine.palette") local colors = { - bg = '#202328', - fg = '#bbc2cf', - aqua = '#6EB0A3', - yellow = '#ECBE7B', - cyan = '#008080', - darkblue = '#081633', - green = '#a9b665', - orange = '#FF8800', - violet = '#a9a1e1', - magenta = '#c678dd', - blue = '#51afef', - red = '#ec5f67', + bg = palette.base, + fg = palette.text, + aqua = palette.rose, -- Changed to rose for better contrast + yellow = palette.gold, + cyan = palette.foam, -- Changed to foam for better visibility + darkblue = palette.surface, + green = palette.pine, + orange = palette.iris, -- Changed to iris for better distinction + violet = palette.iris, + magenta = palette.rose, -- Changed to rose for consistency + blue = palette.foam, + red = palette.love, } local conditions = { @@ -40,7 +56,7 @@ M.config = function() end, } - local my_branch = { 'branch', icon = '', color = { fg = colors.violet, gui = 'bold' } } + local my_branch = { 'branch', icon = '', color = { fg = colors.aqua, gui = 'bold' } } local borders = { left = { @@ -99,7 +115,6 @@ M.config = function() } -- lualine - local navic = require 'nvim-navic' local config = { options = { -- Disable sections and component separators @@ -163,25 +178,25 @@ M.config = function() icon = '', color = function() local mode_color = { - n = colors.red, - i = colors.green, - v = colors.blue, - [''] = colors.blue, - V = colors.blue, - c = colors.magenta, - no = colors.red, - s = colors.orange, - S = colors.orange, - ic = colors.yellow, - R = colors.violet, - Rv = colors.violet, - cv = colors.red, - ce = colors.red, - r = colors.cyan, - rm = colors.cyan, - ['r?'] = colors.cyan, - ['!'] = colors.red, - t = colors.cyan, + n = palette.rose, -- Normal mode + i = palette.pine, -- Insert mode + v = palette.foam, -- Visual mode + [''] = palette.foam, -- Visual block + V = palette.foam, -- Visual line + c = palette.love, -- Command mode + no = palette.love, -- Normal operator pending + s = palette.gold, -- Select mode + S = palette.gold, -- Select line + ic = palette.iris, -- Insert completion + R = palette.iris, -- Replace mode + Rv = palette.iris, -- Virtual replace + cv = palette.love, -- Ex mode + ce = palette.love, -- Normal ex + r = palette.foam, -- Hit-enter prompt + rm = palette.foam, -- More prompt + ['r?'] = palette.foam, -- Confirm + ['!'] = palette.love, -- Shell + t = palette.pine, -- Terminal } return { fg = mode_color[vim.fn.mode()] } end, @@ -201,9 +216,9 @@ M.config = function() sources = { 'nvim_diagnostic' }, symbols = { error = ' ', warn = ' ', info = ' ' }, diagnostics_color = { - color_error = { fg = colors.red }, - color_warn = { fg = colors.yellow }, - color_info = { fg = colors.cyan }, + color_error = { fg = palette.love }, + color_warn = { fg = palette.gold }, + color_info = { fg = palette.foam }, }, } @@ -230,7 +245,7 @@ M.config = function() ins_left { 'filename', cond = conditions.buffer_not_empty, - color = { fg = colors.aqua, gui = 'bold' }, + color = { fg = 'subtle', gui = 'bold' }, } ------------------- diff --git a/nvim/.config/nvim/lua/plugins/mini.lua b/nvim/.config/nvim/lua/plugins/mini.lua index d39d661..707bfee 100644 --- a/nvim/.config/nvim/lua/plugins/mini.lua +++ b/nvim/.config/nvim/lua/plugins/mini.lua @@ -93,6 +93,13 @@ local M = { { the_king .. 'surround', version = false, + keys = { + { 'S', 'lua MiniSurround.add("visual")', mode = 'v' }, + { 'yss' }, + { 'ys' }, + { 'ds' }, + { 'cs' }, + }, opts = { mappings = { add = 'ys', @@ -137,8 +144,13 @@ local M = { { the_king .. 'pairs', version = false, + enabled = false, -- use nvim-autopairs instead event = 'InsertEnter', - opts = {}, + opts = { + mappings = { + ['`'] = { action = 'closeopen', pair = '``', neigh_pattern = '[^\\`].', register = { cr = false } }, + }, + }, }, { the_king .. 'operators', diff --git a/nvim/.config/nvim/lua/plugins/null-ls.lua b/nvim/.config/nvim/lua/plugins/null-ls.lua index 69ac504..da9f824 100644 --- a/nvim/.config/nvim/lua/plugins/null-ls.lua +++ b/nvim/.config/nvim/lua/plugins/null-ls.lua @@ -8,11 +8,9 @@ local M = { } M.config = function() local null_ls = require 'null-ls' - local default_on_attach = require('user.lsp.on-attach').default -- null-ls null_ls.setup { - on_attach = default_on_attach, debug = true, sources = { null_ls.builtins.code_actions.gitsigns, @@ -21,7 +19,6 @@ M.config = function() require('user.lsp.code-actions').toggle_function_params, require('user.lsp.code-actions').library_current_branch, require 'none-ls-shellcheck.code_actions', - null_ls.builtins.diagnostics.golangci_lint, null_ls.builtins.diagnostics.hadolint, null_ls.builtins.diagnostics.markdownlint, null_ls.builtins.diagnostics.proselint, diff --git a/nvim/.config/nvim/lua/plugins/octo.lua b/nvim/.config/nvim/lua/plugins/octo.lua new file mode 100644 index 0000000..1129d01 --- /dev/null +++ b/nvim/.config/nvim/lua/plugins/octo.lua @@ -0,0 +1,53 @@ +local octo_string = 'Octo: ' +return { + 'pwntester/octo.nvim', + -- dev = true, + keys = { { 'o', 'Octo', desc = 'Octo menu' } }, + cmd = 'Octo', + -- dependencies = { + -- 'nvim-lua/plenary.nvim', + -- 'nvim-telescope/telescope.nvim', + -- }, + opts = { + use_local_fs = true, -- use local files on right side of reviews + enable_builtin = true, -- shows a list of builtin actions when no action is provided + default_merge_method = 'squash', + default_delete_branch = true, -- whether to delete branch when merging pull request with either `Octo pr merge` or from picker (can be overridden with `delete`/`nodelete` argument to `Octo pr merge`) + picker = 'fzf-lua', + picker_config = { + use_emojis = true, + }, + users = 'assignable', -- Users for assignees or reviewers. Values: "search" | "mentionable" | "assignable" + pull_requests = { + use_branch_name_as_title = true, -- sets branch name to be the name for the PR + }, + mappings = { + notification = { + read = { lhs = '' }, + done = { lhs = '' }, + unsubscribe = { lhs = '' }, + }, + }, + }, + config = function(_, opts) + local octo_config = require 'octo.config' + local default_opts = octo_config.get_default_values() + for _, actions in pairs(default_opts.mappings) do + for _, details in pairs(actions) do + if details.desc then + details.desc = octo_string .. details.desc + end + end + end + -- require('octo').setup(opts) + -- setup octo and merge opts with default opts + require('octo').setup(vim.tbl_deep_extend('force', default_opts, opts)) + end, + init = function() + -- keymap gk to open fzf.lua's keymaps with a ready prompt for + -- "Octo: " to see all octo keymaps when you press gk + vim.keymap.set('n', 'gk', function() + require('fzf-lua').keymaps { query = octo_string } + end, { desc = 'Octo: Keymaps' }) + end, +} diff --git a/nvim/.config/nvim/lua/plugins/snacks.lua b/nvim/.config/nvim/lua/plugins/snacks.lua index 4f8624b..b9973fa 100644 --- a/nvim/.config/nvim/lua/plugins/snacks.lua +++ b/nvim/.config/nvim/lua/plugins/snacks.lua @@ -25,20 +25,6 @@ return { }, }, keys = { - { - '.', - function() - Snacks.scratch() - end, - desc = 'Toggle Scratch Buffer', - }, - { - 'S', - function() - Snacks.scratch.select() - end, - desc = 'Select Scratch Buffer', - }, { 'bd', function() @@ -93,30 +79,6 @@ return { desc = 'Prev Reference', mode = { 'n', 't' }, }, - { - 'gh', - function() - Snacks.gitbrowse.open() - end, - }, - { - 'N', - desc = 'Neovim News', - function() - Snacks.win { - file = vim.api.nvim_get_runtime_file('doc/news.txt', false)[1], - width = 0.6, - height = 0.6, - wo = { - spell = false, - wrap = false, - signcolumn = 'yes', - statuscolumn = ' ', - conceallevel = 3, - }, - } - end, - }, }, init = function() vim.api.nvim_create_user_command('Rename', function() diff --git a/nvim/.config/nvim/lua/plugins/tree.lua b/nvim/.config/nvim/lua/plugins/tree.lua index f51db84..ebc1e6d 100644 --- a/nvim/.config/nvim/lua/plugins/tree.lua +++ b/nvim/.config/nvim/lua/plugins/tree.lua @@ -19,7 +19,7 @@ local function on_attach(bufnr) sort_current = sort_current + 1 end api.tree.reload() - require('user.utils').pretty_print('Sort Method: ' .. SORT_METHODS[sort_current]) + vim.notify('Sort Method: ' .. SORT_METHODS[sort_current]) end -- mark operation @@ -48,21 +48,6 @@ local function on_attach(bufnr) end end) end - local mark_remove = function() - local marks = api.marks.list() - if #marks == 0 then - table.insert(marks, api.tree.get_node_under_cursor()) - end - vim.ui.input({ prompt = string.format('Remove/Delete %s files? [y/n] ', #marks) }, function(input) - if input == 'y' then - for _, node in ipairs(marks) do - api.fs.remove(node) - end - api.marks.clear() - api.tree.reload() - end - end) - end local mark_copy = function() local marks = api.marks.list() if #marks == 0 then @@ -115,6 +100,8 @@ local function on_attach(bufnr) vim.keymap.del('n', 's', { buffer = bufnr }) vim.keymap.del('n', '', { buffer = bufnr }) vim.keymap.del('n', 'bd', { buffer = bufnr }) + vim.keymap.del('n', 'm', { buffer = bufnr }) + vim.keymap.del('n', 'bmv', { buffer = bufnr }) vim.keymap.set('n', 'h', lefty, opts 'Left') vim.keymap.set('n', '', lefty, opts 'Left') @@ -127,8 +114,6 @@ local function on_attach(bufnr) vim.keymap.set('n', 'K', mark_move_k, opts 'Toggle Bookmark Up') vim.keymap.set('n', 'dd', mark_cut, opts 'Cut File(s)') - vim.keymap.set('n', 'df', mark_trash, opts 'Trash File(s)') - vim.keymap.set('n', 'dF', mark_remove, opts 'Remove File(s)') vim.keymap.set('n', 'yy', mark_copy, opts 'Copy File(s)') vim.keymap.set('n', 'mv', api.marks.bulk.move, opts 'Move Bookmarked') @@ -143,6 +128,34 @@ local function on_attach(bufnr) vim.system({ 'mv', file_src, file_out }, { text = true }):wait() end vim.keymap.set('n', 'r', move_file_to, opts 'Move File To') + + vim.keymap.set('n', 'Z', function() + local node = api.tree.get_node_under_cursor() + local file_path = node['absolute_path'] + local file_dir = vim.fn.fnamemodify(file_path, ':h') + local file_type = vim.trim(vim.system({ 'file', '--mime-type', '-b', file_path }, { text = true }):wait().stdout) + + local function run(cmd) + local ok = vim.system(cmd, { text = true }):wait() + if ok.code ~= 0 then + vim.notify('Extraction failed: ' .. table.concat(cmd, ' '), vim.log.levels.ERROR) + return false + end + return true + end + + if file_type == 'application/gzip' then + run { 'tar', 'xzf', file_path, '-C', file_dir } + elseif file_type == 'application/zip' then + run { 'unzip', file_path, '-d', file_dir } + elseif file_type == 'application/x-bzip2' then + run { 'tar', 'xjf', file_path, '-C', file_dir } + else + vim.notify('Unsupported file type for extraction: ' .. file_type, vim.log.levels.WARN) + return + end + vim.notify('Extracted: ' .. file_path) + end, opts 'Extract File') end local M = { diff --git a/nvim/.config/nvim/lua/plugins/treesitter.lua b/nvim/.config/nvim/lua/plugins/treesitter.lua index 8c05da1..915063d 100644 --- a/nvim/.config/nvim/lua/plugins/treesitter.lua +++ b/nvim/.config/nvim/lua/plugins/treesitter.lua @@ -5,21 +5,40 @@ local actions = function() end, } end + local M = { 'nvim-treesitter/nvim-treesitter', build = function() pcall(require('nvim-treesitter.install').update { with_sync = true }) end, dependencies = { + 'OXY2DEV/markview.nvim', 'nvim-treesitter/nvim-treesitter-textobjects', - 'Afourcat/treesitter-terraform-doc.nvim', + { 'Afourcat/treesitter-terraform-doc.nvim', ft = 'terraform', cmd = 'OpenDoc' }, 'nvim-treesitter/nvim-treesitter-context', { 'folke/ts-comments.nvim', opts = {} }, { 'windwp/nvim-ts-autotag', - ft = { 'html', 'javascript', 'jsx', 'markdown', 'typescript', 'xml' }, + ft = { 'html', 'javascript', 'jsx', 'markdown', 'typescript', 'xml', 'markdown' }, opts = {}, }, + { + 'atusy/treemonkey.nvim', + keys = { + { + 'm', + function() + require 'nvim-treesitter.configs' + ---@diagnostic disable-next-line: missing-fields + require('treemonkey').select { + ignore_injections = false, + action = require('treemonkey.actions').unite_selection, + } + end, + mode = { 'x', 'o' }, + }, + }, + }, }, event = 'BufReadPost', } @@ -37,10 +56,14 @@ M.opts = { 'gitcommit', 'gitignore', 'go', + 'gomod', + 'gosum', 'gotmpl', + 'gowork', 'graphql', 'groovy', 'hcl', + 'helm', 'hjson', 'html', 'http', @@ -96,6 +119,7 @@ M.config = function(_, opts) ---@diagnostic disable-next-line: missing-fields require('nvim-treesitter.configs').setup(opts) + vim.treesitter.language.register('markdown', 'octo') vim.opt.foldmethod = 'expr' vim.opt.foldexpr = 'nvim_treesitter#foldexpr()' diff --git a/nvim/.config/nvim/lua/plugins/ufo.lua b/nvim/.config/nvim/lua/plugins/ufo.lua deleted file mode 100644 index dc8d039..0000000 --- a/nvim/.config/nvim/lua/plugins/ufo.lua +++ /dev/null @@ -1,26 +0,0 @@ -local M = { - 'kevinhwang91/nvim-ufo', - dependencies = { 'kevinhwang91/promise-async' }, - event = 'BufReadPost', - keys = { - { 'fo', 'lua require("ufo").openAllFolds()' }, - { 'fc', 'lua require("ufo").closeAllFolds()' }, - { 'fp', 'lua require("ufo").peekFoldedLinesUnderCursor()' }, - }, - opts = { - open_fold_hl_timeout = 0, - }, - - init = function() - ---@diagnostic disable-next-line: inject-field - vim.o.foldcolumn = '1' -- '0' is not bad - ---@diagnostic disable-next-line: inject-field - vim.o.foldlevel = 99 -- Using ufo provider need a large value, feel free to decrease the value - ---@diagnostic disable-next-line: inject-field - vim.o.foldlevelstart = 99 - ---@diagnostic disable-next-line: inject-field - vim.o.foldenable = true - end, -} - -return M diff --git a/nvim/.config/nvim/lua/plugins/yanky.lua b/nvim/.config/nvim/lua/plugins/yanky.lua deleted file mode 100644 index e64f136..0000000 --- a/nvim/.config/nvim/lua/plugins/yanky.lua +++ /dev/null @@ -1,30 +0,0 @@ -local M = { - 'gbprod/yanky.nvim', - dependencies = { 'kkharji/sqlite.lua' }, - cmd = { 'YankyRingHistory' }, - keys = { - 'yy', - { 'p', '(YankyPutAfter)', mode = { 'n', 'x' } }, - { 'P', '(YankyPutBefore)', mode = { 'n', 'x' } }, - { '', '(YankyCycleForward)' }, - { '', '(YankyCycleBackward)' }, - { 'y', 'YankyRingHistory' }, - }, - opts = { - ring = { - history_length = 100, - storage = 'sqlite', - sync_with_numbered_registers = true, - cancel_event = 'update', - }, - }, - init = function() - require('user.menu').add_actions('Yanky', { - ['Yank history'] = function() - vim.cmd 'YankyRingHistory' - end, - }) - end, -} - -return M diff --git a/nvim/.config/nvim/lua/tests/utils_spec.lua b/nvim/.config/nvim/lua/tests/utils_spec.lua new file mode 100644 index 0000000..af40ac3 --- /dev/null +++ b/nvim/.config/nvim/lua/tests/utils_spec.lua @@ -0,0 +1,9 @@ +--# selene: allow(undefined_variable) +local utils = require 'user.utils' +local eq = assert.are.same + +describe('user.utils', function() + it('return an emoji of country', function() + eq(utils.country_os_to_emoji 'US', '🇺🇸') + end) +end) diff --git a/nvim/.config/nvim/lua/user/actions.lua b/nvim/.config/nvim/lua/user/actions.lua index 67c46a7..1d490b2 100644 --- a/nvim/.config/nvim/lua/user/actions.lua +++ b/nvim/.config/nvim/lua/user/actions.lua @@ -8,37 +8,43 @@ local find_in_project = function(opts) } local bang = opts.literal_search and '' or '!' local noautocmd_str = opts.noautocmd and 'noautocmd ' or '' - vim.ui.input({ prompt = 'Enter search term (blank for word under cursor): ' }, function(search_term) - local original_search_term = search_term - if search_term then - search_term = ' ' .. search_term - end + vim.defer_fn(function() + vim.ui.input({ prompt = 'Enter search term (blank for word under cursor)❯ ' }, function(search_term) + local original_search_term = search_term + if search_term then + search_term = ' ' .. search_term + end - vim.cmd(noautocmd_str .. 'RipGrepCWORD' .. bang .. search_term) - opts.callback(original_search_term) - end) + vim.cmd(noautocmd_str .. 'RipGrepCWORD' .. bang .. search_term) + opts.callback(original_search_term) + end) + end, 100) end local search_and_replace = function(literal_search) find_in_project { literal_search = literal_search, callback = function(search_term) - vim.ui.input({ prompt = 'Enter Replace term: ' }, function(replace_term) - if not replace_term then - pretty_print 'Canceled.' - return - end - vim.ui.input({ - prompt = 'Enter flags (g=global, c=confirm, i=case insensitive, e=ignore errors, n=only count): ', - default = 'gce', - }, function(flags) - if not flags then + vim.defer_fn(function() + vim.ui.input({ prompt = 'Enter Replace term❯ ' }, function(replace_term) + if not replace_term then pretty_print 'Canceled.' return end - vim.cmd('silent noautocmd cdo %s?' .. search_term .. '?' .. replace_term .. '?' .. flags) + vim.defer_fn(function() + vim.ui.input({ + prompt = 'Enter flags (g=global, c=confirm, i=case insensitive, e=ignore errors, n=only count)❯ ', + default = 'gce', + }, function(flags) + if not flags then + pretty_print 'Canceled.' + return + end + vim.cmd('silent noautocmd cdo %s?' .. search_term .. '?' .. replace_term .. '?' .. flags) + end) + end, 100) end) - end) + end, 100) end, noautocmd = true, } @@ -53,10 +59,10 @@ return { ['Find in pwd (regex search) ()'] = function() find_in_project { literal_search = false } end, - ['Search and Replace in pwd (literal search)'] = function() + ['Search and Replace in cwd (literal search)'] = function() search_and_replace(true) end, - ['Search and Replace in pwd (regex search)'] = function() + ['Search and Replace in cwd (regex search)'] = function() search_and_replace(false) end, ['Replace word under cursor (r)'] = function() @@ -80,11 +86,6 @@ return { ['[Folds] Close all folds (fc)'] = function() vim.cmd 'normal! zM' end, - ['[Terraform] Add -target macro to register q'] = function() - -- set q register to -target - vim.fn.setreg('q', [[yss'I-target=A \j]]) - pretty_print('Macro q set to -target', 'Terraform') - end, ['[Terraform] Remove terragrunt files'] = function() require('lazy').load { plugins = { 'vim-fugitive' } } local scan_dir = require 'plenary.scandir' @@ -107,6 +108,9 @@ return { v? will be ?d %s?\v^\s*#\s*(.*) will be (.*)?\1 \2? ]] + -- set q register to -target + vim.fn.setreg('q', [[yss'I-target=A \j]]) + pretty_print('Macro q set to -target', 'Terraform') end, ['Basic groovy format'] = function() vim.cmd.BasicGroovyFormat() @@ -122,6 +126,7 @@ return { end local text = [[ +--# selene: allow(undefined_variable) -- Reload the file when it changes on disk local group = vim.api.nvim_create_augroup("ReloadModule", {clear = true}) vim.api.nvim_create_autocmd("BufWritePost", { diff --git a/nvim/.config/nvim/lua/user/additional-schemas.lua b/nvim/.config/nvim/lua/user/additional-schemas.lua index 18a5404..a1d362a 100644 --- a/nvim/.config/nvim/lua/user/additional-schemas.lua +++ b/nvim/.config/nvim/lua/user/additional-schemas.lua @@ -6,6 +6,7 @@ local M = { Accept = 'application/vnd.github+json', ['X-GitHub-Api-Version'] = '2022-11-28', }, + schema_modeline = '# yaml-language-server: $schema=', } M.schema_url = 'https://raw.githubusercontent.com/' .. M.schemas_catalog .. '/' .. M.schema_catalog_branch @@ -36,6 +37,9 @@ end M.crds_as_schemas = function() local schemas = {} + if not M.all_crds or #M.all_crds == 0 then + M.init() + end for _, crd in ipairs(M.all_crds) do local crd_name = '[datreeio] ' .. crd:gsub('%.json$', ''):gsub('/', '-'):gsub('_', '-') local schema_url = { @@ -48,13 +52,13 @@ M.crds_as_schemas = function() end M.list_schemas = function() - vim.ui.select(M.all_crds, { prompt = 'Select schema: ' }, function(selection) + vim.ui.select(M.all_crds, { title = 'Schemas', prompt = 'Select schema❯ ' }, function(selection) if not selection then require('user.utils').pretty_print 'Canceled.' return end local schema_url = M.schema_url .. '/' .. selection - local schema_modeline = '# yaml-language-server: $schema=' .. schema_url + local schema_modeline = M.schema_modeline .. schema_url vim.api.nvim_buf_set_lines(0, 0, 0, false, { schema_modeline }) vim.notify('Added schema modeline: ' .. schema_modeline) end) diff --git a/nvim/.config/nvim/lua/user/autocommands.lua b/nvim/.config/nvim/lua/user/autocommands.lua index 33fa76b..9b3f632 100644 --- a/nvim/.config/nvim/lua/user/autocommands.lua +++ b/nvim/.config/nvim/lua/user/autocommands.lua @@ -14,7 +14,7 @@ autocmd({ 'FocusGained', 'TermClose', 'TermLeave' }, { }) autocmd('FileChangedShellPost', { - desc = 'Actions when the file is changed outside of Neovim', + desc = 'Reload when the file is changed outside of Neovim', group = reload_file_group, callback = function() vim.notify('File changed, reloading the buffer', vim.log.levels.WARN) @@ -41,6 +41,7 @@ autocmd('UIEnter', { end, }) +-- VeryLazy autocmd('User', { desc = 'Setup non-critical stuff after lazy has loaded', group = first_load, @@ -119,15 +120,6 @@ autocmd('BufReadPost', { end, }) -autocmd('DirChanged', { - group = buffer_settings, - callback = function() - local cwd = vim.fn.getcwd() - local hostname = vim.fn.hostname() - os.execute('printf "\\033]7;file://' .. hostname .. cwd .. '\\033\\\\"') - end, -}) - -- Special filetypes local special_filetypes = augroup 'SpecialFiletype' autocmd({ 'FileType' }, { diff --git a/nvim/.config/nvim/lua/user/difftool.lua b/nvim/.config/nvim/lua/user/difftool.lua new file mode 100644 index 0000000..a6c91e1 --- /dev/null +++ b/nvim/.config/nvim/lua/user/difftool.lua @@ -0,0 +1,291 @@ +---@class DiffLayout +---@field left_win integer|nil Window handle for left diff +---@field right_win integer|nil Window handle for right diff + +---@class DiffToolModule +local M = {} + +---@type DiffLayout +local layout = { + left_win = nil, + right_win = nil, +} + +-- Set up a consistent layout with two diff windows and quickfix at bottom +---@return boolean setup_needed True if new layout needed to be created +local function setup_layout() + if layout.left_win and vim.api.nvim_win_is_valid(layout.left_win) then + return false + end + + -- Save current window as left window + layout.left_win = vim.api.nvim_get_current_win() + + -- Create right window + vim.cmd 'vsplit' + layout.right_win = vim.api.nvim_get_current_win() + return true +end + +---@param winnr integer Window handle +---@param file string File path +local function edit_in(winnr, file) + vim.api.nvim_win_call(winnr, function() + local current = vim.fs.abspath(vim.api.nvim_buf_get_name(vim.api.nvim_win_get_buf(winnr))) + + -- Check if the current buffer is already the target file + if current == (file and vim.fs.abspath(file) or '') then + return + end + + -- Read the file into the buffer + vim.cmd.edit(vim.fn.fnameescape(file)) + end) +end + +-- Diff two files +---@param left_file string Path to left file +---@param right_file string Path to right file +local function diff_files(left_file, right_file) + setup_layout() + edit_in(layout.left_win, left_file) + edit_in(layout.right_win, right_file) + + -- Apply diff settings efficiently + vim.cmd 'diffoff!' + local diffthis = vim.cmd.diffthis + vim.api.nvim_win_call(layout.left_win, diffthis) + vim.api.nvim_win_call(layout.right_win, diffthis) +end + +---@class FileMapping +---@field left string|nil +---@field right string|nil + +---@param dir string Directory path +---@param is_left boolean Whether this is the left directory +---@param all_paths table Accumulated path mappings +local function process_directory(dir, is_left, all_paths) + local files = vim.fs.find(function() + return true + end, { + limit = math.huge, + path = dir, + follow = false, + }) + + for _, full_path in ipairs(files) do + if vim.fn.isdirectory(full_path) == 0 then + local rel_path = full_path:sub(#dir + 1) + full_path = vim.fn.resolve(full_path) + all_paths[rel_path] = all_paths[rel_path] or { left = nil, right = nil } + if is_left then + all_paths[rel_path].left = full_path + else + all_paths[rel_path].right = full_path + end + end + end +end + +---@param left_file string|nil Path to left file +---@param right_file string|nil Path to right file +---@return boolean files_differ True if files have different content +local function files_differ(left_file, right_file) + -- If one file doesn't exist, they differ + if not left_file or not right_file then + return true + end + + -- Check if files exist + if vim.fn.filereadable(left_file) == 0 or vim.fn.filereadable(right_file) == 0 then + return true + end + + -- Read and compare file contents + local left_lines = vim.fn.readfile(left_file) + local right_lines = vim.fn.readfile(right_file) + + -- Quick check: different number of lines + if #left_lines ~= #right_lines then + return true + end + + -- Compare line by line + for i = 1, #left_lines do + if left_lines[i] ~= right_lines[i] then + return true + end + end + + return false +end + +---@param left_dir string Left directory path +---@param right_dir string Right directory path +local function diff_directories(left_dir, right_dir) + setup_layout() + + ---@type table + local all_paths = {} + + -- Process both directories + process_directory(left_dir, true, all_paths) + process_directory(right_dir, false, all_paths) + + -- Convert to quickfix entries + ---@type table[] Quickfix entries + local qf_entries = {} + + for rel_path, files in pairs(all_paths) do + local status, left_file, right_file = 'M', files.left, files.right + + if not left_file then + status = 'A' -- Added (only in right) + left_file = left_dir .. rel_path + elseif not right_file then + status = 'D' -- Deleted (only in left) + right_file = right_dir .. rel_path + end + + -- Only add to quickfix if files actually differ + if files_differ(left_file, right_file) then + qf_entries[#qf_entries + 1] = { + filename = right_file, + text = status, + user_data = { + diff = true, + rel = rel_path, + left = left_file, + right = right_file, + left_dir = left_dir, + right_dir = right_dir, + }, + } + end + end + + -- Sort entries by filename for consistency + table.sort(qf_entries, function(a, b) + return a.user_data.rel < b.user_data.rel + end) + + vim.fn.setqflist({}, 'r', { + ---@diagnostic disable-next-line: assign-type-mismatch + nr = '$', + title = 'DiffTool', + items = qf_entries, + quickfixtextfunc = function(info) + local items = vim.fn.getqflist({ id = info.id, items = 1 }).items + local out = {} + for i = info.start_idx, info.end_idx do + local entry = items[i] + local status_text = entry.text + local description = '' + + if entry.user_data and entry.user_data.diff then + if status_text == 'A' then + description = ' (only in ' .. vim.fn.fnamemodify(entry.user_data.right_dir, ':t') .. ')' + elseif status_text == 'D' then + description = ' (missing from ' .. vim.fn.fnamemodify(entry.user_data.right_dir, ':t') .. ')' + end + end + + out[#out + 1] = status_text .. description .. ' ' .. entry.user_data.rel + end + return out + end, + }) + + vim.cmd 'botright copen' + vim.cmd.cfirst() +end + +---Setup the difftool with highlighting and commands +function M.setup() + local ns_id = vim.api.nvim_create_namespace 'difftool_qf' + + -- Pre-compile patterns for performance + local patterns = { + add = '^A ', + delete = '^D ', + modify = '^M ', + } + + -- Define highlight groups globally (not in namespace) + vim.api.nvim_set_hl(0, 'DiffToolAdd', { link = 'GitSignsAdd' }) + vim.api.nvim_set_hl(0, 'DiffToolDelete', { link = 'GitSignsDelete' }) + vim.api.nvim_set_hl(0, 'DiffToolModify', { link = 'GitSignsChange' }) + + vim.api.nvim_create_autocmd('BufWinEnter', { + pattern = 'quickfix', + callback = function() + local bufnr = vim.api.nvim_get_current_buf() + vim.api.nvim_buf_clear_namespace(bufnr, ns_id, 0, -1) + + local lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false) + for i, line in ipairs(lines) do + local hl_group + if line:match(patterns.add) then + hl_group = 'DiffToolAdd' + elseif line:match(patterns.delete) then + hl_group = 'DiffToolDelete' + elseif line:match(patterns.modify) then + hl_group = 'DiffToolModify' + end + + if hl_group then + vim.api.nvim_buf_set_extmark(bufnr, ns_id, i - 1, 0, { + hl_group = hl_group, + end_col = #line, + priority = 1000, + strict = false, + }) + end + end + end, + }) + + vim.api.nvim_create_autocmd('BufWinEnter', { + pattern = '*', + callback = function(args) + local qf_info = vim.fn.getqflist { idx = 0 } + local qf_list = vim.fn.getqflist() + local entry = qf_list[qf_info.idx] + + -- Check if the entry is a diff entry + if not (entry and entry.user_data and entry.user_data.diff and args.buf == entry.bufnr) then + return + end + + vim.schedule(function() + diff_files(entry.user_data.left, entry.user_data.right) + end) + end, + }) + + vim.api.nvim_create_user_command('DiffTool', function(opts) + if #opts.fargs < 2 then + vim.notify('Usage: DiffTool ', vim.log.levels.ERROR) + return + end + + local left, right = opts.fargs[1], opts.fargs[2] + local is_dir = vim.fn.isdirectory(left) == 1 and vim.fn.isdirectory(right) == 1 + local is_file = vim.fn.filereadable(left) == 1 and vim.fn.filereadable(right) == 1 + + if is_dir then + diff_directories(left, right) + elseif is_file then + diff_files(left, right) + else + vim.notify('Both arguments must be files or directories', vim.log.levels.ERROR) + end + end, { + nargs = '*', + force = true, + complete = 'file', + }) +end + +return M diff --git a/nvim/.config/nvim/lua/user/git.lua b/nvim/.config/nvim/lua/user/git.lua index a01d126..75c8c14 100644 --- a/nvim/.config/nvim/lua/user/git.lua +++ b/nvim/.config/nvim/lua/user/git.lua @@ -72,12 +72,35 @@ end M.get_branch = function(cb) run_git({ 'branch', '--show-current' }, nil, function(branch) - cb(vim.trim(branch)) + local branch = vim.trim(branch) + if branch == '' then + M.get_short_commit(function(commit_hash) + M.prnt('No branch found, using commit hash: ' .. commit_hash) + cb(commit_hash) + end) + else + cb(vim.trim(branch)) + end end) end M.get_branch_sync = function() - return vim.trim(run_git_sync({ 'branch', '--show-current' }, nil).stdout) + local branch = vim.trim(run_git_sync({ 'branch', '--show-current' }, nil).stdout) + if branch == '' then + return M.get_short_commit_sync() + else + return branch + end +end + +M.get_short_commit = function(cb) + run_git({ 'rev-parse', '--short', 'HEAD' }, nil, function(commit_hash) + cb(vim.trim(commit_hash)) + end) +end + +M.get_short_commit_sync = function() + return vim.trim(run_git_sync({ 'rev-parse', '--short', 'HEAD' }, nil).stdout) end M.get_remotes = function(cb) @@ -119,9 +142,13 @@ M.get_branches_sync = function(remote_name) return branches end +M.checkout = function(branch_name) + run_git({ 'checkout', branch_name }, 'Checking out ' .. branch_name) +end + M.push = function(cb) M.get_branch(function(branch) - run_git({ 'push', 'origin', branch }, 'Pushing to ' .. branch .. '...', cb) + run_git({ 'push', '-u', 'origin', branch }, 'Pushing to ' .. branch .. '...', cb) end) end @@ -157,9 +184,9 @@ M.create_new_branch = function(branch_opts) if branch_opts.args ~= '' then return run_git({ 'checkout', '-b', branch_opts.args }, 'Creating new branch ' .. branch_opts.args) end - vim.ui.input({ prompt = 'Enter new branch name: ' }, function(input) + vim.ui.input({ prompt = 'Enter new branch name❯ ' }, function(input) if not input then - return + return M.prnt 'Canceled.' end if not input:match '^[a-zA-Z0-9_-]+$' then return M.prnt('Invalid branch name', vim.log.levels.ERROR) @@ -207,19 +234,19 @@ end M.ui_select_remotes = function(cb) M.get_remotes(function(remotes) local remote_list = vim.tbl_keys(remotes) - with_ui_select(remote_list, { prompt = 'Select remote: ' }, cb) + with_ui_select(remote_list, { title = 'Remotes', prompt = 'Select remote❯ ' }, cb) end) end M.ui_select_tags = function(cb) M.get_tags(function(tags) - with_ui_select(tags, { prompt = 'Select tag: ' }, cb, false) + with_ui_select(tags, { title = 'Tags', prompt = 'Select tag❯ ' }, cb, false) end) end M.ui_select_branches = function(remote_name, cb) M.get_branches(remote_name, function(branches) - with_ui_select(branches, { prompt = 'Select branch: ' }, cb, false) + with_ui_select(branches, { title = 'Branches', prompt = 'Select branch❯ ' }, cb, false) end) end @@ -240,14 +267,13 @@ M.ui_select_merge_remote_branch = function() end M.ui_select_create_tag = function() - require('fzf-lua.utils').fzf_exit() vim.defer_fn(function() - vim.ui.input({ prompt = 'Enter tag name: ' }, function(tag_name) + vim.ui.input({ prompt = 'Enter tag name❯ ' }, function(tag_name) if not tag_name then return M.prnt 'Canceled.' end run_git({ 'tag', tag_name }, 'Creating tag: ' .. tag_name, function() - with_ui_select({ 'Yes', 'No' }, { prompt = 'Push?' }, function(choice) + with_ui_select({ 'Yes', 'No' }, { prompt = 'Push❯ ' }, function(choice) if choice == 'Yes' then M.prnt('Pushing tag ' .. tag_name .. '...') run_git({ 'push', '--tags' }, nil, function() @@ -262,11 +288,23 @@ M.ui_select_create_tag = function() end, 100) end +M.ui_select_rename_branch = function(branch_name, cb) + -- require('fzf-lua.utils').fzf_exit() + vim.defer_fn(function() + vim.ui.input({ prompt = 'Enter new branch name❯ ', default = branch_name }, function(new_name) + if not new_name then + return M.prnt 'Canceled.' + end + run_git({ 'branch', '-m', branch_name, new_name }, 'Renaming branch: ' .. branch_name .. ' to ' .. new_name, cb) + end) + end, 100) +end + M.ui_select_delete_tag = function() M.ui_select_tags(function(tag) M.prnt('Deleting tag ' .. tag .. ' locally...') run_git({ 'tag', '-d', tag }, nil, function() - with_ui_select({ 'Yes', 'No' }, { prompt = 'Delete tag ' .. tag .. ' from remote?' }, function(choice) + with_ui_select({ 'Yes', 'No' }, { prompt = 'Delete tag ' .. tag .. ' from remote❯ ' }, function(choice) if choice == 'Yes' then M.ui_select_remotes(function(remote) M.prnt('Deleting tag ' .. tag .. ' from remote ' .. remote .. '...') diff --git a/nvim/.config/nvim/lua/user/gitbrowse.lua b/nvim/.config/nvim/lua/user/gitbrowse.lua new file mode 100644 index 0000000..06a5dbc --- /dev/null +++ b/nvim/.config/nvim/lua/user/gitbrowse.lua @@ -0,0 +1,216 @@ +---@class user.gitbrowse +---@overload fun(opts?: user.gitbrowse.Config) +local M = setmetatable({}, { + __call = function(t, ...) + return t.open(...) + end, +}) + +M.meta = { + desc = 'Open the current file, branch, commit, or repo in a browser (e.g. GitHub, GitLab, Bitbucket)', +} + +local uv = vim.uv or vim.loop + +---@class user.gitbrowse.Config +---@field url_patterns? table> +local defaults = { + notify = true, -- show notification on open + -- Handler to open the url in a browser + ---@param url string + open = function(url) + vim.ui.open(url) + end, + ---@type "repo" | "branch" | "file" | "commit" + what = 'file', -- what to open. not all remotes support all types + branch = nil, ---@type string? + commit = nil, ---@type string? + line_start = nil, ---@type number? + line_end = nil, ---@type number? + -- patterns to transform remotes to an actual URL + -- stylua: ignore + remote_patterns = { + { "^(https?://.*)%.git$" , "%1" }, + { "^git@(.+):(.+)%.git$" , "https://%1/%2" }, + { "^git@(.+):(.+)$" , "https://%1/%2" }, + { "^git@(.+)/(.+)$" , "https://%1/%2" }, + { "^ssh://git@(.*)$" , "https://%1" }, + { "^ssh://([^:/]+)(:%d+)/(.*)$" , "https://%1/%3" }, + { "^ssh://([^/]+)/(.*)$" , "https://%1/%2" }, + { "ssh%.dev%.azure%.com/v3/(.*)/(.*)$", "dev.azure.com/%1/_git/%2" }, + { "^https://%w*@(.*)" , "https://%1" }, + { "^git@(.*)" , "https://%1" }, + { ":%d+" , "" }, + { "%.git$" , "" }, + }, + url_patterns = { + ['github%.com'] = { + branch = '/tree/{branch}', + file = '/blob/{branch}/{file}#L{line_start}-L{line_end}', + commit = '/commit/{commit}', + }, + ['gitlab%.com'] = { + branch = '/-/tree/{branch}', + file = '/-/blob/{branch}/{file}#L{line_start}-L{line_end}', + commit = '/-/commit/{commit}', + }, + ['bitbucket%.org'] = { + branch = '/src/{branch}', + file = '/src/{branch}/{file}#lines-{line_start}-L{line_end}', + commit = '/commits/{commit}', + }, + }, +} + +---@class user.gitbrowse.Fields +---@field branch? string +---@field file? string +---@field line_start? number +---@field line_end? number +---@field commit? string +---@field line_count? number + +---@private +---@param remote string +---@param opts? user.gitbrowse.Config +function M.get_repo(remote, opts) + opts = vim.tbl_deep_extend('force', defaults, opts or {}) + local ret = remote + for _, pattern in ipairs(opts.remote_patterns) do + ret = ret:gsub(pattern[1], pattern[2]) --[[@as string]] + end + return ret:find 'https://' == 1 and ret or ('https://%s'):format(ret) +end + +---@param repo string +---@param fields user.gitbrowse.Fields +---@param opts? user.gitbrowse.Config +function M.get_url(repo, fields, opts) + opts = vim.tbl_deep_extend('force', defaults, opts or {}) + for remote, patterns in pairs(opts.url_patterns) do + if repo:find(remote) then + local pattern = patterns[opts.what] + if type(pattern) == 'string' then + return repo .. pattern:gsub('(%b{})', function(key) + return fields[key:sub(2, -2)] or key + end) + elseif type(pattern) == 'function' then + return repo .. pattern(fields) + end + end + end + return repo +end + +---@param cmd string[] +---@param err string +local function system(cmd, err) + local proc = vim.fn.system(cmd) + if vim.v.shell_error ~= 0 then + vim.notify { err, proc } + error '__ignore__' + end + return vim.split(vim.trim(proc), '\n') +end + +---@param hash string +---@param cwd string +---@return boolean +local function is_valid_commit_hash(hash, cwd) + if not (hash:match '^[a-fA-F0-9]+$' and #hash >= 7) then + return false + end + system({ 'git', '-C', cwd, 'rev-parse', '--verify', hash }, 'Invalid commit hash') + return true +end + +---@param opts? user.gitbrowse.Config +function M.open(opts) + opts = vim.tbl_deep_extend('force', defaults, opts or {}) + local ok, err = pcall(M._open, opts) -- errors are handled with notifications + if not ok and err ~= '__ignore__' then + error(err) + end +end + +---@param opts? user.gitbrowse.Config +function M._open(opts) + opts = vim.tbl_deep_extend('force', defaults, opts or {}) + local file = vim.api.nvim_buf_get_name(0) ---@type string? + file = file and (uv.fs_stat(file) or {}).type == 'file' and vim.fs.normalize(file) or nil + local cwd = file and vim.fn.fnamemodify(file, ':h') or vim.fn.getcwd() + local word = vim.fn.expand '' + local commit_to_check = opts.commit or word + local is_commit = commit_to_check and is_valid_commit_hash(commit_to_check, cwd) + + ---@type user.gitbrowse.Fields + local fields = { + branch = opts.branch or system({ 'git', '-C', cwd, 'rev-parse', 'HEAD' }, 'Failed to get current branch')[1], + file = file and system({ 'git', '-C', cwd, 'ls-files', '--full-name', file }, 'Failed to get git file path')[1], + line_start = opts.line_start, + line_end = opts.line_end, + commit = is_commit and commit_to_check or nil, + } + + -- Get visual selection range if in visual mode + if vim.fn.mode():find '[vV]' then + vim.fn.feedkeys(':', 'nx') + local line_start = vim.api.nvim_buf_get_mark(0, '<')[1] + local line_end = vim.api.nvim_buf_get_mark(0, '>')[1] + vim.fn.feedkeys('gv', 'nx') + -- Ensure line_start is always the smaller number + if line_start > line_end then + line_start, line_end = line_end, line_start + end + fields.line_start = line_start + fields.line_end = line_end + else + fields.line_start = fields.line_start or vim.fn.line '.' + fields.line_end = fields.line_end or fields.line_start + end + fields.line_count = fields.line_end - fields.line_start + 1 + + opts.what = is_commit and 'commit' or opts.what == 'commit' and not fields.commit and 'file' or opts.what + opts.what = not is_commit and opts.what == 'file' and not fields.file and 'branch' or opts.what + opts.what = not is_commit and opts.what == 'branch' and not fields.branch and 'repo' or opts.what + + local remotes = {} ---@type {name:string, url:string}[] + + for _, line in ipairs(system({ 'git', '-C', cwd, 'remote', '-v' }, 'Failed to get git remotes')) do + local name, remote = line:match '(%S+)%s+(%S+)%s+%(fetch%)' + if name and remote then + local repo = M.get_repo(remote, opts) + if repo then + table.insert(remotes, { + name = name, + url = M.get_url(repo, fields, opts), + }) + end + end + end + + local function open(remote) + if remote then + if opts.notify ~= false then + vim.notify(('Opening [%s](%s)'):format(remote.name, remote.url)) + end + opts.open(remote.url) + end + end + + if #remotes == 0 then + return vim.notify 'No git remotes found' + elseif #remotes == 1 then + return open(remotes[1]) + end + + vim.ui.select(remotes, { + prompt = 'Select remote to browse', + title = 'Git remotes', + format_item = function(item) + return item.name .. (' '):rep(8 - #item.name) .. ' 🔗 ' .. item.url + end, + }, open) +end + +return M diff --git a/nvim/.config/nvim/lua/user/grep.lua b/nvim/.config/nvim/lua/user/grep.lua new file mode 100644 index 0000000..f713c8c --- /dev/null +++ b/nvim/.config/nvim/lua/user/grep.lua @@ -0,0 +1,87 @@ +local M = {} + +-- Default options +-- Default options +local default_opts = { + keymap = { + search = '', -- The keybinding to trigger search + }, + grep = { + literal_flag = '-F', -- Default literal search flag + format = '%f:%l:%c:%m', -- Default grep format + -- Built-in grep command fallback + cmd = 'grep -n -r --exclude=%s . $*', + }, + -- ripgrep configuration + rg = { + cmd = "rg --vimgrep --no-heading --smart-case --hidden --follow -g '!{%s}' -uu $*", + literal_flag = '-F', + format = '%f:%l:%c:%m,%f:%l:%m', + }, + -- silver searcher configuration + ag = { + cmd = "ag --vimgrep --smart-case --hidden --follow --ignore '!{%s}' $*", + literal_flag = '-Q', + format = '%f:%l:%c:%m', + }, +} + +-- Set grepprg based on available tools +local function setup_grep(opts) + local wildignore = vim.o.wildignore + + if vim.fn.executable 'rg' == 1 then + vim.o.grepprg = string.format(opts.rg.cmd, wildignore) + vim.g.grep_literal_flag = opts.rg.literal_flag + vim.o.grepformat = opts.rg.format + elseif vim.fn.executable 'ag' == 1 then + vim.o.grepprg = string.format(opts.ag.cmd, wildignore) + vim.g.grep_literal_flag = opts.ag.literal_flag + vim.o.grepformat = opts.ag.format + else + vim.o.grepprg = string.format(opts.grep.cmd, vim.fn.shellescape(wildignore)) + vim.g.grep_literal_flag = opts.grep.literal_flag + vim.o.grepformat = opts.grep.format + end +end + +local function rip_grep_cword(bang, visualmode, search_word) + if visualmode then + search_word = require('user.utils').get_visual_selection() + end + + if not search_word or search_word == '' then + search_word = vim.fn.expand '' + end + + local search_message_literally = 'for ' .. search_word + if bang then + search_message_literally = 'literally for ' .. search_word + search_word = (vim.g.grep_literal_flag or '') .. ' -- ' .. vim.fn.shellescape(search_word) + end + + vim.api.nvim_echo({ { ('Searching ' .. search_message_literally), 'None' } }, false, {}) + vim.cmd('silent grep! ' .. search_word) +end + +function M.setup(opts) + opts = vim.tbl_deep_extend('force', default_opts, opts or {}) + + setup_grep(opts) + + -- Create user commands + vim.api.nvim_create_user_command('RipGrepCWORD', function(cmd_opts) + rip_grep_cword(cmd_opts.bang, false, cmd_opts.args) + end, { bang = true, range = true, nargs = '?', complete = 'file_in_path' }) + + vim.api.nvim_create_user_command('RipGrepCWORDVisual', function(cmd_opts) + rip_grep_cword(cmd_opts.bang, true, cmd_opts.args) + end, { bang = true, range = true, nargs = '?', complete = 'file_in_path' }) + + -- Map keys + vim.keymap.set({ 'n', 'v' }, opts.keymap.search, function() + return vim.fn.mode() == 'v' and ':RipGrepCWORDVisual!' or ':RipGrepCWORD!' + end, { remap = false, expr = true }) +end + +return M diff --git a/nvim/.config/nvim/lua/user/keymaps.lua b/nvim/.config/nvim/lua/user/keymaps.lua index 8a2d68f..dbf0ba9 100644 --- a/nvim/.config/nvim/lua/user/keymaps.lua +++ b/nvim/.config/nvim/lua/user/keymaps.lua @@ -6,57 +6,57 @@ map({ 'n', 'x' }, '', "v:count == 0 ? 'gj' : 'j'", { desc = 'Down', expr = map({ 'n', 'x' }, 'k', "v:count == 0 ? 'gk' : 'k'", { desc = 'Up', expr = true, silent = true }) map({ 'n', 'x' }, '', "v:count == 0 ? 'gk' : 'k'", { desc = 'Up', expr = true, silent = true }) --- Select all file visually -map('n', 'sa', 'ggVG', { remap = false, desc = 'Visually select entire buffer' }) - -- Map 0 to first non-blank character -map('n', '0', '^', { remap = false, desc = 'Go to the first non-blank character' }) -map('v', '0', '^', { remap = false, desc = 'Go to the first non-blank character' }) +map({ 'n', 'v' }, '0', '^', { remap = false, desc = 'Go to the first non-blank character' }) --- Move to the end of the line -map('n', 'L', '$ze10zl', { remap = false, desc = 'Go to the end of the line and move view' }) -map('v', 'L', '$', { remap = false }) -map('n', 'H', '0zs10zh', { remap = false }) -map('v', 'H', '0', { remap = false }) +-- Move view left or right +map('n', 'L', '5zl', { remap = false, desc = 'Move view to the right' }) +map('v', 'L', '$', { remap = false, desc = 'Move view to the right' }) +map('n', 'H', '5zh', { remap = false, desc = 'Move view to the left' }) +map('v', 'H', '0', { remap = false, desc = 'Move view to the left' }) -- indent/unindent visual mode selection with tab/shift+tab -map('v', '', '>gv') -map('v', '', '', '>gv', { desc = 'Indent selected text' }) +map('v', '', '', '') -map('c', '', '') -map('c', '', '') -map('c', '', '') +map('c', '', '', { desc = 'Move left in command line' }) +map('c', '', '', { desc = 'Move down in command line' }) +map('c', '', '', { desc = 'Move up in command line' }) +map('c', '', '', { desc = 'Move right in command line' }) -- Add undo break-points -map('i', ',', ',u', { remap = false }) -map('i', '.', '.u', { remap = false }) -map('i', ';', ';u', { remap = false }) +map('i', ',', ',u', { remap = false, desc = 'Add undo break point after comma' }) +map('i', '.', '.u', { remap = false, desc = 'Add undo break point after period' }) +map('i', ';', ';u', { remap = false, desc = 'Add undo break point after semicolon' }) +map('i', '!', '!u', { remap = false, desc = 'Add undo break point after exclamation' }) +map('i', '?', '?u', { remap = false, desc = 'Add undo break point after question mark' }) +map('i', '(', '(u', { remap = false, desc = 'Add undo break point after open paren' }) +map('i', ')', ')u', { remap = false, desc = 'Add undo break point after close paren' }) -map('i', ';;', 'A;', { remap = false }) +map('i', ';;', 'A;', { remap = false, desc = 'Add semicolon at end of line' }) +map('i', ',,', 'A,', { remap = false, desc = 'Add comma at end of line' }) -- delete word on insert mode -map('i', '', 'de', { remap = false }) -map('i', '', 'db', { remap = false }) +map('i', '', 'de', { remap = false, desc = 'Delete word after cursor' }) +map('i', '', 'db', { remap = false, desc = 'Delete word before cursor' }) map('n', 'gx', require('user.open-url').open_url_under_cursor, { remap = false, desc = 'Open url under cursor' }) -- Search for string within the visual selection map('x', '/', '/\\%V', { remap = false }) +-- open github in browser +map({ 'v', 'n' }, 'gh', require('user.gitbrowse').open, { remap = false, desc = 'Open github in browser' }) + -- surround with string interpolation with motion function _G.__surround_with_interpolation(motion) - local start = {} - local finish = {} if motion == nil or motion == 'line' then vim.o.operatorfunc = 'v:lua.__surround_with_interpolation' return vim.fn.feedkeys 'g@' end - if motion == 'char' then - start = vim.api.nvim_buf_get_mark(0, '[') - finish = vim.api.nvim_buf_get_mark(0, ']') - end + local start = vim.api.nvim_buf_get_mark(0, '[') + local finish = vim.api.nvim_buf_get_mark(0, ']') local line = vim.api.nvim_buf_get_text(0, start[1] - 1, start[2], finish[1] - 1, finish[2] + 1, {})[1] local new_text = { '"${' .. line .. '}"' } vim.api.nvim_buf_set_text(0, start[1] - 1, start[2], finish[1] - 1, finish[2] + 1, new_text) @@ -99,7 +99,7 @@ com! BasicGroovyFormat call s:BasicGroovyFormat() ]=] -- Windows mappings -map('n', '', '', { remap = false, silent = true }) +map('n', '', '', { remap = false, silent = true, desc = 'Switch to alternate file' }) map('n', '', 'w', { remap = false, silent = true }) map('n', '', 'c', { remap = false, silent = true }) map('n', 'bn', 'bn', { remap = false, silent = true, desc = 'Next buffer' }) @@ -114,14 +114,12 @@ map('n', '', 'k', { remap = true, desc = 'Go to Upper Window' }) map('n', '', 'l', { remap = true, desc = 'Go to Right Window' }) -- Resize window using arrow keys -map('n', '', 'resize +2', { desc = 'Increase Window Height' }) -map('n', '', 'resize -2', { desc = 'Decrease Window Height' }) -map('n', '', 'vertical resize -2', { desc = 'Decrease Window Width' }) -map('n', '', 'vertical resize +2', { desc = 'Increase Window Width' }) +require('user.winresizer').setup() -- entire file text-object map('o', 'ae', 'normal! ggVG', { remap = false }) map('v', 'ae', 'gg0vG$', { remap = false }) +map('n', 'sa', 'ggVG', { remap = false, desc = 'Visually select entire buffer' }) -- Run and edit macros for _, key in pairs { 'Q', 'X' } do @@ -155,38 +153,25 @@ end map('n', 'dp', function() vim.go.operatorfunc = 'v:lua.__diffput' return 'g@l' -end, { expr = true }) +end, { expr = true, desc = 'Diff put current hunk' }) _G.__diffget = function() vim.cmd [[diffget]] end map('n', 'dg', function() vim.go.operatorfunc = 'v:lua.__diffget' return 'g@l' -end, { expr = true }) -map('n', 'dn', ':windo diffthis', { remap = false, silent = true }) -map('n', 'df', ':windo diffoff', { remap = false, silent = true }) +end, { expr = true, desc = 'Diff get current line' }) +map('n', 'dn', ':windo diffthis', { remap = false, silent = true, desc = 'Start diff mode' }) +map('n', 'df', ':windo diffoff', { remap = false, silent = true, desc = 'End diff mode' }) -- Map enter to no highlight -map('n', '', ':nohlsearch', { remap = false, silent = true }) - --- Set mouse=v mapping -map('n', 'ma', ':set mouse=a', { remap = false, silent = true }) -map('n', 'mv', ':set mouse=v', { remap = false, silent = true }) +map('n', '', ':nohlsearch', { remap = false, silent = true, desc = 'Clear search highlighting' }) -- Exit mappings -map('i', 'jk', '', { remap = false }) -map('n', 'qq', ':qall', { remap = false, silent = true }) - --- Search mappings -map('n', '*', ':execute "normal! *N"', { remap = false, silent = true }) -map('n', '#', ':execute "normal! #n"', { remap = false, silent = true }) --- https://github.com/mhinz/vim-galore#saner-behavior-of-n-and-n -map('n', 'n', "'Nn'[v:searchforward].'zv'", { expr = true, desc = 'Next Search Result' }) -map('x', 'n', "'Nn'[v:searchforward]", { expr = true, desc = 'Next Search Result' }) -map('o', 'n', "'Nn'[v:searchforward]", { expr = true, desc = 'Next Search Result' }) -map('n', 'N', "'nN'[v:searchforward].'zv'", { expr = true, desc = 'Prev Search Result' }) -map('x', 'N', "'nN'[v:searchforward]", { expr = true, desc = 'Prev Search Result' }) -map('o', 'N', "'nN'[v:searchforward]", { expr = true, desc = 'Prev Search Result' }) +map('i', 'jk', '', { remap = false, desc = 'Exit insert mode' }) +map('i', 'kj', '', { remap = false, desc = 'Exit insert mode' }) +map('n', 'qq', ':qall', { remap = false, silent = true, desc = 'Quit all' }) + -- Search visually selected text with // or * or # vim.cmd [[ function! StarSearch(cmdtype) abort @@ -198,44 +183,45 @@ function! StarSearch(cmdtype) abort call setreg('"', old_reg, old_regtype) endfunction ]] - --- Terminal -map('t', '', [[]], { remap = false }) - map('v', '*', ":call StarSearch('/')/=@/", { remap = false, silent = true }) map('v', '#', ":call StarSearch('?')?=@/", { remap = false, silent = true }) +-- Terminal +map('t', '', [[]], { remap = false, desc = 'Exit terminal mode' }) + -- Map - to move a line down and _ a line up -map('n', '-', [["ldd$"lp==]], { remap = false }) -map('n', '_', [["ldd2k"lp==]], { remap = false }) --- Allow clipboard copy paste in neovim -map('', '', '+p', { remap = false, silent = true }) -map('!', '', '+', { remap = false, silent = true }) -map('t', '', '+', { remap = false, silent = true }) -map('v', '', '+', { remap = false }) +map('n', '-', [["ldd$"lp==]], { remap = false, desc = 'Move line down' }) +map('n', '_', [["ldd2k"lp==]], { remap = false, desc = 'Move line up' }) -- Copy entire file to clipboard -map('n', 'Y', ':%y+', { remap = false, silent = true }) +map('n', 'Y', ':%y+', { remap = false, silent = true, desc = 'Copy buffer content to clipboard' }) -- Copy file path to clipboard -map('n', 'cfp', [[:let @+ = expand('%'):echo "Copied relative file path " . expand('%')]], { remap = false, silent = true }) -map('n', 'cfa', [[:let @+ = expand('%:p'):echo "Copied full file path " . expand('%:p')]], { remap = false, silent = true }) -map('n', 'cfd', [[:let @+ = expand('%:p:h'):echo "Copied file directory path " . expand('%:p:h')]], { remap = false, silent = true }) -map('n', 'cfn', [[:let @+ = expand('%:t'):echo "Copied file directory path " . expand('%:t')]], { remap = false, silent = true }) +map('n', 'cfa', function() + local file_path = vim.fn.expand '%:p' + vim.fn.setreg('+', file_path) + print('Copied full file path ' .. file_path) +end, { remap = false, silent = true, desc = 'Copy absolute file path' }) +map('n', 'cfd', function() + local dir_path = vim.fn.expand '%:p:h' + vim.fn.setreg('+', dir_path) + print('Copied file directory path ' .. dir_path) +end, { remap = false, silent = true, desc = 'Copy file directory path' }) +map('n', 'cfn', function() + local file_name = vim.fn.expand '%:t' + vim.fn.setreg('+', file_name) + print('Copied file name ' .. file_name) +end, { remap = false, silent = true, desc = 'Copy file name' }) -- Copy and paste to/from system clipboard -map('v', 'cp', '"+y') -map('n', 'cP', '"+yy') -map('n', 'cp', '"+y') -map('n', 'cv', '"+p') - +map('v', 'cp', '"+y', { desc = 'Copy to system clipboard' }) +map('n', 'cP', '"+yy', { desc = 'Copy line to system clipboard' }) +map('n', 'cp', '"+y', { desc = 'Copy to system clipboard' }) +map('n', 'cv', '"+p', { desc = 'Paste from system clipboard' }) +map('n', '', 'ciw', { desc = 'Change inner word' }) map('n', '', 'ciw') --- Move visually selected block --- map('v', 'J', [[:m '>+1gv=gv]], { remap = false, silent = true }) --- map('v', 'K', [[:m '<-2gv=gv]], { remap = false, silent = true }) - -- Select last inserted text map('n', 'gV', '`[v`]', { remap = false, desc = 'Visually select last insert' }) @@ -243,37 +229,35 @@ map('n', 'gV', '`[v`]', { remap = false, desc = 'Visually select last insert' }) map('n', 'ct', ':retab', { remap = false, silent = true }) -- Enable folding with the leader-f/a -map('n', 'ff', 'za', { remap = false }) -map('n', 'fc', 'zM', { remap = false }) -map('n', 'fo', 'zR', { remap = false }) +map('n', 'ff', 'za', { remap = false, desc = 'Toggle fold' }) +map('n', 'fc', 'zM', { remap = false, desc = 'Close all folds' }) +map('n', 'fo', 'zR', { remap = false, desc = 'Open all folds' }) -- Open level folds -map('n', 'fl', 'zazczA', { remap = false }) +map('n', 'fl', 'zazczA', { remap = false, desc = 'Open fold level' }) -- Change \n to new lines -map('n', '', [[:silent! %s?\\n?\r?gsilent! %s?\\t?\t?gsilent! %s?\\r?\r?g:noh]], { silent = true }) - --- Move vertically by visual line (don't skip wrapped lines) --- nmap('k', "v:count == 0 ? 'gk' : 'k'", opts.expr_silent) --- nmap('j', "v:count == 0 ? 'gj' : 'j'", opts.expr_silent) - +map( + 'n', + '', + [[:silent! %s?\\n?\r?gsilent! %s?\\t?\t?gsilent! %s?\\r?\r?g:noh]], + { silent = true, desc = 'Convert escaped newlines' } +) -- toggle wrap -map('n', 'ww', ':set wrap!', { remap = false, silent = true }) +map('n', 'ww', ':set wrap!', { remap = false, silent = true, desc = 'Toggle line wrap' }) -- Scroll one line -map('n', '', '', { remap = false }) -map('n', '', '', { remap = false }) +map('n', '', '', { remap = false, desc = 'Scroll one line up' }) +map('n', '', '', { remap = false, desc = 'Scroll one line down' }) -- Scrolling centralized -map('n', '', 'zz', { remap = false }) -map('n', '', 'zz', { remap = false }) +map('n', '', 'zz', { remap = false, desc = 'Scroll half page up and center' }) +map('n', '', 'zz', { remap = false, desc = 'Scroll half page down and center' }) -- Change working directory based on open file -map('n', 'cd', ':cd %:p:h:pwd', { remap = false, silent = true }) +map('n', 'cd', ':cd %:p:h:pwd', { remap = false, silent = true, desc = 'Change directory to current file' }) --- Convert all tabs to spaces -map('n', 'ct', ':retab', { silent = true }) -- Change every " -" with " \ -" to break long lines of bash -map('n', [[\]], [[:.s/ -/ \\\r -/g:noh]], { silent = true }) +map('n', [[\]], [[:.s/ -/ \\\r -/g:noh]], { silent = true, desc = 'Break long command line' }) -- global yanks and deletes map('v', 'dab', [["hyqeq:v?\Vh?d E:let @"=@e:noh]], { remap = false, desc = 'Delete all but...', silent = true }) @@ -282,17 +266,43 @@ map('v', 'yab', [["hymmqeq:v?\Vh?yank E:let @"=@e`m:noh map('v', 'yaa', [["hymmqeq:g?\Vh?yank E:let @"=@e`m:noh]], { remap = false, desc = 'Yank all...', silent = true }) -- Base64 dencode -map('v', '64', [[c=substitute(system('base64', @"), '\n$', '', 'g')]], { remap = false, silent = true, desc = 'Base64 encode' }) -map('v', '46', [[c=substitute(system('base64 --decode', @"), '\n$', '', 'g')]], { remap = false, silent = true, desc = 'Base64 decode' }) +local function b64(action) + local start = vim.api.nvim_buf_get_mark(0, '[') + local finish = vim.api.nvim_buf_get_mark(0, ']') + local line = vim.api.nvim_buf_get_text(0, start[1] - 1, start[2], finish[1] - 1, finish[2] + 1, {})[1] + local b64_action = action == 'encode' and vim.base64.encode or vim.base64.decode + local new_text = { b64_action(line) } + vim.api.nvim_buf_set_text(0, start[1] - 1, start[2], finish[1] - 1, finish[2] + 1, new_text) + vim.api.nvim_win_set_cursor(vim.api.nvim_get_current_win(), { finish[1], finish[2] }) +end +function _G.__base64_encode(motion) + if motion == nil or motion == 'line' then + vim.o.operatorfunc = 'v:lua.__base64_encode' + return vim.fn.feedkeys 'g@' + end + b64 'encode' +end +function _G.__base64_decode(motion) + if motion == nil or motion == 'line' then + vim.o.operatorfunc = 'v:lua.__base64_decode' + return vim.fn.feedkeys 'g@' + end + b64 'decode' +end +map('n', '64', _G.__base64_encode) +map('n', '46', _G.__base64_decode) +map('v', '64', _G.__base64_encode) +map('v', '46', _G.__base64_decode) -- Close current buffer map('n', 'bc', ':close', { silent = true, desc = 'Close this buffer' }) -- Duplicate a line and comment out the first line -map('n', 'yc', 'yygccp', { remap = true }) +map('n', 'yc', 'yygccp', { remap = true, desc = 'Duplicate and comment line' }) -- Abbreviations map('!a', 'dont', [[don't]], { remap = false }) +map('!a', 'ill', [[i'll]], { remap = false }) map('!a', 'seperate', 'separate', { remap = false }) map('!a', 'adn', 'and', { remap = false }) map('!a', 'waht', 'what', { remap = false }) @@ -335,7 +345,7 @@ end, {}) -- Change indentation -- ------------------------ map('n', 'cii', function() - vim.ui.input({ prompt = 'Enter new indent: ' }, function(indent_size) + vim.ui.input({ prompt = 'Enter new indent❯ ' }, function(indent_size) local indent_size_normalized = tonumber(indent_size) vim.opt_local.shiftwidth = indent_size_normalized vim.opt_local.softtabstop = indent_size_normalized @@ -393,55 +403,12 @@ vmap :VisualCalculator ---------- -- Grep -- ---------- -vim.cmd [[ -" Set grepprg as RipGrep or ag (the_silver_searcher), fallback to grep -if executable('rg') - let &grepprg="rg --vimgrep --no-heading --smart-case --hidden --follow -g '!{" . &wildignore . "}' -uu $*" - let g:grep_literal_flag="-F" - set grepformat=%f:%l:%c:%m,%f:%l:%m -elseif executable('ag') - let &grepprg='ag --vimgrep --smart-case --hidden --follow --ignore "!{' . &wildignore . '}" $*' - let g:grep_literal_flag="-Q" - set grepformat=%f:%l:%c:%m -else - let &grepprg='grep -n -r --exclude=' . shellescape(&wildignore) . ' . $*' - let g:grep_literal_flag="-F" -endif - -function! RipGrepCWORD(bang, visualmode, ...) abort - let search_word = a:1 - - if a:visualmode - let search_word = GetMotion('gv') - endif - if search_word ==? '' - let search_word = expand('') - endif - - " Set bang command for literal search (no regexp expansion) - let search_message_literally = "for " . search_word - if a:bang == "!" || a:bang == v:true - let search_message_literally = "literally for " . search_word - let search_word = get(g:, 'grep_literal_flag', "") . ' -- ' . shellescape(search_word) - endif - - echom 'Searching ' . search_message_literally +require('user.grep').setup() - " Silent removes the "press enter to continue" prompt - " Bang (!) is for literal search (no regexp expansion) - let grepcmd = 'silent grep! ' . search_word - execute grepcmd -endfunction -]] -vim.api.nvim_create_user_command('RipGrepCWORD', function(f_opts) - vim.fn.RipGrepCWORD(f_opts.bang, false, f_opts.args) -end, { bang = true, range = true, nargs = '?', complete = 'file_in_path' }) -vim.api.nvim_create_user_command('RipGrepCWORDVisual', function(f_opts) - vim.fn.RipGrepCWORD(f_opts.bang, true, f_opts.args) -end, { bang = true, range = true, nargs = '?', complete = 'file_in_path' }) -map({ 'n', 'v' }, '', function() - return vim.fn.mode() == 'v' and ':RipGrepCWORDVisual!' or ':RipGrepCWORD!' -end, { remap = false, expr = true }) +-------------- +-- DiffTool -- +-------------- +require('user.difftool').setup() ------------------------ -- Run current buffer -- @@ -518,3 +485,7 @@ end, { nargs = '?' }) -- Search and Replace -- ------------------------ vim.cmd('source ' .. vim.fn.stdpath 'config' .. '/lua/user/search-replace.vim') + +require('user.tabular-v2').setup {} +require('user.projects').setup() +require 'user.number-separators' diff --git a/nvim/.config/nvim/lua/user/kubectl.lua b/nvim/.config/nvim/lua/user/kubectl.lua new file mode 100644 index 0000000..4d15747 --- /dev/null +++ b/nvim/.config/nvim/lua/user/kubectl.lua @@ -0,0 +1,231 @@ +local commands = require 'kubectl.actions.commands' +local M = { + ingresses = {}, + ['applications.argoproj.io'] = {}, + ['certificaterequests.cert-manager.io'] = {}, + ['certificates.cert-manager.io'] = {}, + ['clusterissuers.cert-manager.io'] = {}, + ['clustersecretstores.external-secrets.io'] = {}, + ['externalsecrets.external-secrets.io'] = {}, + ['scaledobjects.keda.sh'] = {}, + ['targetgroupbindings.elbv2.k8s.aws'] = {}, + ['prometheuses.monitoring.coreos.com'] = {}, + serviceaccounts = {}, +} + +local cluster_to_profile = { + ['spot-prod'] = 'default', + ['spot-prod-bi-billing'] = 'default', + ['spot-dev-us-east-2'] = 'dev', +} + +local profile_to_onelogin = { + default = 'https://spotinst.onelogin.com/client/apps/select/889121819', + dev = 'https://spotinst.onelogin.com/client/apps/select/889121822', +} + +local get_profile_and_region = function() + local cluster_name = require('kubectl.state').context['current-context'] + local aws_profile = os.getenv 'AWS_PROFILE' or cluster_to_profile[cluster_name] + local region = os.getenv 'AWS_REGION' + or vim.trim(vim.system({ 'aws', 'configure', 'get', 'region', '--profile', aws_profile }, { text = true }):wait().stdout) + return aws_profile, region +end + +local prompt_onelogin = function(aws_profile, cb) + local onelogin_url = profile_to_onelogin[aws_profile] + if not onelogin_url then + vim.notify('No OneLogin URL configured for AWS_PROFILE: ' .. aws_profile, vim.log.levels.ERROR) + return + end + vim.schedule(function() + vim.ui.select({ 'Yes', 'No' }, { title = 'Open OneLogin before?' }, function(choice) + if not choice then + return + end + if choice == 'No' then + cb() + return + end + vim.ui.open(profile_to_onelogin[aws_profile]) + vim.defer_fn(function() + cb() + end, 3000) + end) + end) +end + +-- open ALB on AWS console +M.ingresses.select = function(name, ns) + local gvk = require('kubectl.resources.ingresses').definition.gvk + commands.run_async('get_single_async', { + gvk = gvk, + namespace = ns, + name = name, + output = 'Json', + }, function(data_raw) + if not data_raw or data_raw == '' or data_raw == nil then + return + end + local data = vim.json.decode(data_raw) + local ingress_dns = vim.inspect(data.status.loadBalancer.ingress[1].hostname) + vim.schedule(function() + local aws_profile, region = get_profile_and_region() + vim.notify('AWS_PROFILE: ' .. aws_profile .. ' AWS_REGION: ' .. region .. ' Ingress DNS: ' .. ingress_dns) + local aws_cmd = { + 'aws', + 'elbv2', + 'describe-load-balancers', + '--query', + string.format('LoadBalancers[?DNSName==`%s`]', ingress_dns), + '--profile', + aws_profile, + '--output', + 'json', + } + vim.system(aws_cmd, { text = true }, function(aws_output) + local ok + ok, aws_output = pcall(vim.json.decode, aws_output.stdout) + if not ok then + vim.notify('Failed to parse AWS output\n' .. aws_output) + return + end + if vim.tbl_count(aws_output) == 0 then + vim.notify('ALB not found for DNS ' .. ingress_dns) + return + end + local alb_arn = aws_output and aws_output[1].LoadBalancerArn + local lb_url = + string.format('https://%s.console.aws.amazon.com/ec2/home?region=%s#LoadBalancer:loadBalancerArn=%s;tab=listenersb', region, region, alb_arn) + prompt_onelogin(aws_profile, function() + vim.ui.open(lb_url) + end) + end) + end) + end) +end + +-- view Secret of the ServiceAccount +M.serviceaccounts.select = function(name, ns) + local gvk = require('kubectl.resources.serviceaccounts').definition.gvk + local client = require 'kubectl.client' + local sa = client.get_single(vim.json.encode { gvk = gvk, namespace = ns, name = name, output = 'Json' }) + + local sa_decoded = vim.json.decode(sa) + local secret_name = sa_decoded.secrets and sa_decoded.secrets[1] and sa_decoded.secrets[1].name + if secret_name then + require('kubectl.state').filter_key = 'metadata.name=' .. secret_name .. ',metadata.namespace=' .. ns + require('kubectl.state').filter = '' + require('kubectl.resources.secrets').View() + end +end + +-- open ArgoCD application in browser +M['applications.argoproj.io'].select = function(name, ns) + if not (name and ns) then + vim.notify('ArgoCD application name and namespace are required', vim.log.levels.ERROR) + return + end + local ingress_host = vim + .system({ 'kubectl', 'get', 'ingress', '-n', ns, '-l', 'app.kubernetes.io/component=server', '-o', 'jsonpath={.items[].spec.rules[].host}' }, { text = true }) + :wait().stdout + local final_host = string.format('https://%s/applications/argocd/%s', ingress_host, name) + vim.notify('Opening ' .. final_host) + vim.ui.open(final_host) +end + +-- view ExternalSecrets of the ClusterSecretStore +M['clustersecretstores.external-secrets.io'].select = function(name) + require('kubectl.state').filter_key = 'spec.secretStoreRef.name=' .. name .. ',spec.secretStoreRef.kind=ClusterSecretStore' + require('kubectl.state').filter = '' + require('kubectl.resources.fallback').View(nil, 'externalsecrets.external-secrets.io') +end + +-- view Secret of ExternalSecret +M['externalsecrets.external-secrets.io'].select = function(name, ns) + local gvk = require('kubectl.resources.fallback').definition.gvk + local client = require 'kubectl.client' + local es = client.get_single(vim.json.encode { gvk = gvk, namespace = ns, name = name, output = 'Json' }) + local es_decoded = vim.json.decode(es) + local secret_name = es_decoded.status and es_decoded.status.binding and es_decoded.status.binding.name + if secret_name then + require('kubectl.state').filter_key = 'metadata.name=' .. secret_name .. ',metadata.namespace=' .. ns + require('kubectl.state').filter = '' + require('kubectl.resources.secrets').View() + end +end + +-- view CertificateRequests of the Certificate +M['certificates.cert-manager.io'].select = function(name, ns) + require('kubectl.state').filter_key = 'metadata.ownerReferences.name=' .. name .. ',metadata.ownerReferences.kind=Certificate,metadata.namespace=' .. ns + require('kubectl.state').filter = '' + require('kubectl.resources.fallback').View(nil, 'certificaterequests.cert-manager.io') +end + +M['scaledobjects.keda.sh'].select = function(name, ns) + local gvk = require('kubectl.resources.fallback').definition.gvk + local client = require 'kubectl.client' + local so = client.get_single(vim.json.encode { gvk = gvk, namespace = ns, name = name, output = 'Json' }) + local so_decoded = vim.json.decode(so) + local metric_names = so_decoded.status.externalMetricNames or {} + for _, metric_name in ipairs(metric_names) do + vim.system({ + 'kubectl', + 'get', + '--raw', + string.format('/apis/external.metrics.k8s.io/v1beta1/namespaces/%s/%s?labelSelector=scaledobject.keda.sh%%2Fname%%3D%s', ns, metric_name, name), + }, { text = true }, function(result) + if result.code ~= 0 then + vim.notify('Failed to get external metric: ' .. result.stderr, vim.log.levels.ERROR) + return + end + local metrics = vim.json.decode(result.stdout) + if not metrics or not metrics.items or #metrics.items == 0 then + vim.notify('No metrics found for ScaledObject: ' .. name, vim.log.levels.WARN) + return + end + local metric_value = metrics.items[1].value + local num_value = metric_value + --remove the 'm' suffix from the metric value + if metric_value:sub(-1) == 'm' then + num_value = metric_value:sub(1, -2) + end + local real_metric = tonumber(num_value) / 1000 -- Convert from milliseconds to seconds + vim.notify(string.format('Current metric value for %s (%s): %d real metric (%s)', name, metric_name, real_metric, metric_value)) + end) + end +end + +M['targetgroupbindings.elbv2.k8s.aws'].select = function(name, ns) + local gvk = require('kubectl.resources.fallback').definition.gvk + local client = require 'kubectl.client' + local tgb = client.get_single(vim.json.encode { gvk = gvk, namespace = ns, name = name, output = 'Json' }) + local tgb_decoded = vim.json.decode(tgb) + local target_group_arn = tgb_decoded.spec.targetGroupARN + if not target_group_arn then + vim.notify('TargetGroupARN not found for TargetGroupBinding: ' .. name, vim.log.levels.ERROR) + return + end + local aws_profile, region = get_profile_and_region() + local tg_url = string.format('https://%s.console.aws.amazon.com/ec2/home?region=%s#TargetGroup:targetGroupArn=%s', region, region, target_group_arn) + prompt_onelogin(aws_profile, function() + vim.ui.open(tg_url) + end) +end + +M['prometheuses.monitoring.coreos.com'].select = function(name, ns) + local gvk = require('kubectl.resources.fallback').definition.gvk + local client = require 'kubectl.client' + local prometheus = client.get_single(vim.json.encode { gvk = gvk, namespace = ns, name = name, output = 'Json' }) + local prometheus_decoded = vim.json.decode(prometheus) + local pod_selector = prometheus_decoded.status and prometheus_decoded.status.selector + local res = {} + for _, lbl in ipairs(vim.split(pod_selector, ',')) do + table.insert(res, 'metadata.labels.' .. lbl) + end + require('kubectl.state').filter_key = table.concat(res, ',') + require('kubectl.state').filter = '' + require('kubectl.resources.pods').View() +end + +return M diff --git a/nvim/.config/nvim/lua/user/lazy.lua b/nvim/.config/nvim/lua/user/lazy.lua index 03a54ee..37f119d 100644 --- a/nvim/.config/nvim/lua/user/lazy.lua +++ b/nvim/.config/nvim/lua/user/lazy.lua @@ -6,15 +6,18 @@ return { return not vim.tbl_contains(vim.g.disabled_plugins or {}, a.name) end, }, + dev = { + path = '~/Repos', + }, change_detection = { notify = false, }, ui = { border = 'rounded', custom_keys = { - -- open a terminal for the plugin dir ['t'] = function(plugin) - vim.cmd('FloatermNew --cwd=' .. plugin.dir) + vim.fn.setreg('+', plugin.dir) + vim.notify('Copied path to clipboard: ' .. plugin.dir) end, }, }, @@ -33,7 +36,6 @@ return { 'getscriptPlugin', 'gzip', 'logipat', - 'man', 'matchit', 'matchparen', 'netrw', diff --git a/nvim/.config/nvim/lua/user/lsp/actions.lua b/nvim/.config/nvim/lua/user/lsp/actions.lua index 437890a..8900626 100644 --- a/nvim/.config/nvim/lua/user/lsp/actions.lua +++ b/nvim/.config/nvim/lua/user/lsp/actions.lua @@ -11,7 +11,7 @@ M.actions = function() vim.lsp.codelens.run() end, ['Show Definition (gd)'] = function() - vim.cmd 'Lspsaga peek_definition' + vim.lsp.buf.definition() end, ['Show Declaration (gD)'] = function() vim.lsp.buf.declaration() @@ -22,21 +22,26 @@ M.actions = function() ['Show Implementation (gi)'] = function() vim.lsp.buf.implementation() end, - ['Find References - lspsaga (grR)'] = function() - vim.cmd 'Lspsaga finder' - end, - ['Find References (grr)'] = function() + ['Find References (gR)'] = function() vim.lsp.buf.references { includeDeclaration = false } end, + ['Add workspace folder'] = function() + vim.lsp.buf.add_workspace_folder() + end, + ['Remove workspace folder'] = function() + vim.lsp.buf.remove_workspace_folder() + end, + ['List workspace folders'] = function() + print(vim.inspect(vim.lsp.buf.list_workspace_folders())) + end, ['Signature Help (lk)'] = function() vim.lsp.buf.signature_help() end, ['Signature Documentation (K)'] = function() - -- vim.lsp.buf.hover() - vim.cmd 'Lspsaga hover_doc' + vim.lsp.buf.hover() end, - ['Rename symbol (lrn)'] = function() - vim.cmd 'Lspsaga rename ++project' + ['Rename symbol (lr)'] = function() + vim.lsp.buf.rename() end, ['Diagnostics quickfix list (lq)'] = function() vim.diagnostic.setqflist() diff --git a/nvim/.config/nvim/lua/user/lsp/code-actions.lua b/nvim/.config/nvim/lua/user/lsp/code-actions.lua index 0e29337..036de2d 100644 --- a/nvim/.config/nvim/lua/user/lsp/code-actions.lua +++ b/nvim/.config/nvim/lua/user/lsp/code-actions.lua @@ -2,6 +2,12 @@ local git = require 'user.git' local null_ls = require 'null-ls' local M = {} +local api = vim.api + +local function get_indent(line) + return string.match(line, '^%s*') +end + M.revision_branch_comment = { method = null_ls.methods.CODE_ACTION, filetypes = { 'yaml' }, @@ -16,33 +22,31 @@ M.revision_branch_comment = { if is_argo_app then local target_revision_line_number = vim.fn.search('targetRevision: ', 'nw') local target_revision_line = context.content[target_revision_line_number] + local indent = get_indent(target_revision_line) + if string.find(target_revision_line, 'targetRevision: HEAD') then return { { title = 'Change branch to current', action = function() - -- get indentation of current_line - local indent = string.match(target_revision_line, '^%s*') local new_lines = { indent .. 'targetRevision: ' .. git.get_branch_sync() .. ' # TODO: Change to HEAD before merging', } - vim.api.nvim_buf_set_lines(context.bufnr, target_revision_line_number - 1, target_revision_line_number, false, new_lines) - end, - }, - } - else - return { - { - title = 'Change branch to HEAD', - action = function() - -- get indentation of current_line - local indent = string.match(target_revision_line, '^%s*') - local new_lines = { indent .. 'targetRevision: HEAD' } - vim.api.nvim_buf_set_lines(context.bufnr, target_revision_line_number - 1, target_revision_line_number, false, new_lines) + api.nvim_buf_set_lines(context.bufnr, target_revision_line_number - 1, target_revision_line_number, false, new_lines) end, }, } end + + return { + { + title = 'Change branch to HEAD', + action = function() + local new_lines = { indent .. 'targetRevision: HEAD' } + api.nvim_buf_set_lines(context.bufnr, target_revision_line_number - 1, target_revision_line_number, false, new_lines) + end, + }, + } end end, }, @@ -57,7 +61,7 @@ M.toggle_function_params = { local row = context.range.row local current_line = context.content[row] local is_function = vim.regex([=[\v\s*def\s[^\)]+\(]=]):match_str(current_line) - local final = current_line + if is_function then return { { @@ -66,6 +70,8 @@ M.toggle_function_params = { local function_args = current_line:gsub([=[%s*def%s%w+%((.*)%).*]=], '%1') function_args = function_args:gsub('%s?=%s?', '=') function_args = function_args:gsub('%s?:%s?', ':') + local final + if string.find(function_args, 'Map args=') then function_args = function_args:gsub('Map args=%[(.*)%]', '%1') function_args = function_args:gsub('(%w+):([^,]*)', '%1=%2') @@ -74,9 +80,8 @@ M.toggle_function_params = { function_args = function_args:gsub('(%w+)=([^,]*)', '%1:%2') final = current_line:gsub([=[(%s*def%s%w+%().*(%).*)]=], '%1Map args=[' .. function_args .. ']%2') end - -- function_args = string.gsub(function_args, [[\w+:\w+]], '') - -- P(function_args) - vim.api.nvim_buf_set_lines(context.bufnr, row - 1, row, false, { final }) + + api.nvim_buf_set_lines(context.bufnr, row - 1, row, false, { final }) end, }, } @@ -98,22 +103,21 @@ M.library_current_branch = { { title = 'Remove current branch from library', action = function() - vim.api.nvim_buf_set_lines(context.bufnr, 0, 1, false, { "@Library('utils') _" }) - end, - }, - } - else - return { - { - title = 'Change library to current branch', - action = function() - local new_line = string.format("@Library('utils@%s') _ // TODO: remove library before merging", git.get_branch_sync()) - vim.print(vim.inspect(new_line)) - vim.api.nvim_buf_set_lines(context.bufnr, 0, 1, false, { new_line }) + api.nvim_buf_set_lines(context.bufnr, 0, 1, false, { "@Library('utils') _" }) end, }, } end + + return { + { + title = 'Change library to current branch', + action = function() + local new_line = string.format("@Library('utils@%s') _ // TODO: remove library before merging", git.get_branch_sync()) + api.nvim_buf_set_lines(context.bufnr, 0, 1, false, { new_line }) + end, + }, + } end end, }, diff --git a/nvim/.config/nvim/lua/user/lsp/config.lua b/nvim/.config/nvim/lua/user/lsp/config.lua index dcefe63..0c4b01f 100644 --- a/nvim/.config/nvim/lua/user/lsp/config.lua +++ b/nvim/.config/nvim/lua/user/lsp/config.lua @@ -1,4 +1,10 @@ local M = { + diagnostic_signs = { + [vim.diagnostic.severity.ERROR] = '✘', + [vim.diagnostic.severity.WARN] = '', + [vim.diagnostic.severity.HINT] = ' ', + [vim.diagnostic.severity.INFO] = ' ', + }, capabilities = { textDocument = { completion = { @@ -14,48 +20,23 @@ local M = { }, } -M.setup_capabilities = function() - ------------------ - -- Capabilities -- - ------------------ - local cmp_default_capabilities = require('cmp_nvim_lsp').default_capabilities() - -- local cmp_default_capabilities = require('blink.cmp').get_lsp_capabilities() - - M.capabilities = vim.tbl_deep_extend('force', vim.lsp.protocol.make_client_capabilities(), cmp_default_capabilities, M.capabilities or {}, {}) -end - -M.diagnostic_signs = { - [vim.diagnostic.severity.ERROR] = '✘', - [vim.diagnostic.severity.WARN] = '', - [vim.diagnostic.severity.HINT] = ' ', - [vim.diagnostic.severity.INFO] = ' ', -} - -M.diagnostics = function() - ----------------- - -- Diagnostics -- - ----------------- - -- show icons in the sidebar - vim.diagnostic.config { - jump = { float = true }, - signs = { text = M.diagnostic_signs }, - update_in_insert = false, - virtual_text = { - severity = { min = vim.diagnostic.severity.WARN }, - }, - float = { border = 'rounded' }, - } -end - M.init = function() - _G.start_ls = function() - local ft = vim.api.nvim_get_option_value('filetype', { buf = 0 }) - local file_name = _G.tmp_write { should_delete = false, new = false, ft = ft } + _G.start_ls = function(with_file) + local file_name = nil + if with_file == true then + local ft = vim.api.nvim_get_option_value('filetype', { buf = 0 }) + file_name = _G.tmp_write { should_delete = false, new = false, ft = ft } + end -- load lsp require 'lspconfig' return file_name end - vim.keymap.set('n', 'ls', _G.start_ls) + vim.keymap.set('n', 'ls', function() + _G.start_ls(false) + end) + vim.keymap.set('n', 'lS', function() + _G.start_ls(true) + end) require('user.menu').add_actions('LSP', { ['Start LSP (ls)'] = function() _G.start_ls() @@ -65,21 +46,35 @@ end M.setup = function() require('user.lsp.actions').setup() - - -- Set formatting of lsp log require('vim.lsp.log').set_format_func(vim.inspect) + M.capabilities = + vim.tbl_deep_extend('force', vim.lsp.protocol.make_client_capabilities(), require('cmp_nvim_lsp').default_capabilities(), M.capabilities or {}, {}) - -- set up capabilities - M.setup_capabilities() - - -- set up diagnostics configuration - M.diagnostics() - - -- set up mason to install lsp servers - require('mason-lspconfig').setup { automatic_installation = true } + -- Diagnostics + vim.diagnostic.config { + jump = { float = true }, + signs = { text = M.diagnostic_signs }, + virtual_text = { severity = { min = vim.diagnostic.severity.WARN } }, + virtual_lines = { current_line = true }, + float = { border = 'rounded', source = 'if_many' }, + } - -- setup lsp servers + ---@diagnostic disable-next-line: missing-fields require('user.lsp.servers').setup() + + -- on attach + local on_attach_aug = vim.api.nvim_create_augroup('UserLspAttach', { clear = true }) + vim.api.nvim_create_autocmd('LspAttach', { + group = on_attach_aug, + callback = function(ev) + local client = vim.lsp.get_client_by_id(ev.data.client_id) + local bufnr = ev.buf + require 'user.lsp.keymaps'(bufnr) + if client and client.server_capabilities.documentSymbolProvider then + require('nvim-navic').attach(client, bufnr) + end + end, + }) end return M diff --git a/nvim/.config/nvim/lua/user/lsp/formatting.lua b/nvim/.config/nvim/lua/user/lsp/formatting.lua deleted file mode 100644 index f6b994e..0000000 --- a/nvim/.config/nvim/lua/user/lsp/formatting.lua +++ /dev/null @@ -1,97 +0,0 @@ -local M = { - bufnr = 0, -} -M.get_all_clients = function() - local method = 'textDocument/formatting' - local clients = vim.tbl_values(vim.lsp.get_clients { bufnr = M.bufnr }) - local formatting_clients = {} - for _, client in ipairs(clients) do - if client.supports_method(method) then - table.insert(formatting_clients, client) - end - end - return formatting_clients -end - -local function select_client(callback) - local clients = M.get_all_clients() - -- Prompt user for client with vim.ui.input and call callback with response - if #clients == 1 then - return callback(clients[1]) - end - return vim.ui.select(clients, { - prompt = 'Select LSP client', - format_item = function(client) - return client.name - end, - }, function(client) - if client == nil then - return - end - callback(client) - end) -end - -M.format = function(client) - M.format_changedtick = vim.api.nvim_buf_get_changedtick(M.bufnr) - vim.lsp.buf.format { - timeout_ms = 20000, - filter = function(s_client) - return s_client.name == client.name - end, - } - - if M.format_changedtick ~= vim.api.nvim_buf_get_changedtick(M.bufnr) then - vim.notify('Formatted using ' .. client.name) - end -end - -M.format_select = function() - select_client(function(client) - M.format(client) - end) -end - -M.format_on_save = function() - local clients = M.get_all_clients() - if #clients == 0 then - return - end - -- prefer null-ls - for _, client in ipairs(clients) do - if client.name == 'null-ls' then - M.format(client) - return - end - end - M.format(clients[1]) -end - -M.setup = function(client, bufnr) - M.bufnr = bufnr - -- format keymap - vim.keymap.set('n', 'lp', function() - M.format_select() - end, { silent = true, buffer = bufnr, desc = 'Format document' }) - - -- format on save - local group = vim.api.nvim_create_augroup('Format', { clear = false }) - vim.api.nvim_clear_autocmds { - buffer = bufnr, - group = group, - } - local event = 'BufWritePre' - vim.api.nvim_create_autocmd(event, { - group = group, - desc = 'Format on save', - buffer = bufnr, - callback = M.format_on_save, - }) - - -- Formatexpr using LSP - if client.server_capabilities.document_formatting == true then - vim.api.nvim_set_option_value('formatexpr', 'v:lua.vim.lsp.formatexpr()', { buf = bufnr }) - end -end - -return M diff --git a/nvim/.config/nvim/lua/user/lsp/keymaps.lua b/nvim/.config/nvim/lua/user/lsp/keymaps.lua index 0d16201..e9f44db 100644 --- a/nvim/.config/nvim/lua/user/lsp/keymaps.lua +++ b/nvim/.config/nvim/lua/user/lsp/keymaps.lua @@ -8,13 +8,20 @@ return function(bufnr) -- goto definition/declaration vim.keymap.set('n', 'gd', vim.lsp.buf.definition, returnOpts 'Go to definition') vim.keymap.set('n', 'gD', vim.lsp.buf.declaration, returnOpts 'Go to declaration') - -- hover doc - vim.keymap.set('n', 'K', vim.lsp.buf.hover, returnOpts 'Show hover doc') + vim.keymap.set('n', 'lk', vim.lsp.buf.signature_help, returnOpts 'Signature help') -- GoTo code navigation vim.keymap.set('n', 'gy', vim.lsp.buf.type_definition, returnOpts 'Go to type definition') vim.keymap.set('n', 'gi', vim.lsp.buf.implementation, returnOpts 'Go to implementation') - vim.keymap.set('n', 'gR', vim.lsp.buf.references, returnOpts 'Go to references (native)') + vim.keymap.set('n', 'gR', vim.lsp.buf.references, returnOpts 'Go to references') + + -- Hover + vim.keymap.set('n', 'K', function() + vim.lsp.buf.hover { + border = 'rounded', + max_height = 30, + } + end, returnOpts 'Hover documentation') -- Workspace vim.keymap.set('n', 'lwa', vim.lsp.buf.add_workspace_folder, returnOpts 'Add workspace folder') diff --git a/nvim/.config/nvim/lua/user/lsp/on-attach.lua b/nvim/.config/nvim/lua/user/lsp/on-attach.lua deleted file mode 100644 index 6f6cbc4..0000000 --- a/nvim/.config/nvim/lua/user/lsp/on-attach.lua +++ /dev/null @@ -1,71 +0,0 @@ -local user_maps = require 'user.lsp.keymaps' -local utils = require 'user.utils' -local autocmd = vim.api.nvim_create_autocmd -local augroup = utils.augroup - -local on_attach_aug = augroup 'OnAttachAu' -local default_on_attach = function(client, bufnr) - ------------------ - -- Add mappings -- - ------------------ - user_maps(bufnr) - - ----------------------- - -- Plugins on-attach -- - ----------------------- - -- require('user.lsp.formatting').setup(client, bufnr) - if client.server_capabilities.documentSymbolProvider then - require('nvim-navic').attach(client, bufnr) - end - - ------------------ - -- AutoCommands -- - ------------------ - if client.server_capabilities.code_lens then - autocmd({ 'BufEnter', 'InsertLeave', 'InsertEnter' }, { - desc = 'Auto show code lenses', - group = on_attach_aug, - buffer = bufnr, - command = 'silent! lua vim.lsp.codelens.refresh({bufnr=' .. bufnr .. '})', - }) - end - if client.server_capabilities.document_highlight then - -- Highlight text at cursor position - autocmd({ 'CursorHold', 'CursorHoldI' }, { - desc = 'Highlight references to current symbol under cursor', - group = on_attach_aug, - buffer = bufnr, - command = 'silent! lua vim.lsp.buf.document_highlight()', - }) - autocmd({ 'CursorMoved' }, { - desc = 'Clear highlights when cursor is moved', - group = on_attach_aug, - buffer = bufnr, - command = 'silent! lua vim.lsp.buf.clear_references()', - }) - end - - ---------------------------------- - -- Enable tag jump based on LSP -- - ---------------------------------- - if client.server_capabilities.goto_definition then - vim.api.nvim_set_option_value('tagfunc', 'v:lua.vim.lsp.tagfunc', { buf = bufnr }) - end - - ----------------- - -- Inlay Hints -- - ----------------- - if client.server_capabilities.inlayHintProvider then - vim.lsp.inlay_hint.enable(false, { bufnr = bufnr }) - end -end - -local minimal_on_attach = function(_, bufnr) - -- Add mappings - user_maps(bufnr) -end - -return { - default = default_on_attach, - minimal = minimal_on_attach, -} diff --git a/nvim/.config/nvim/lua/user/lsp/servers.lua b/nvim/.config/nvim/lua/user/lsp/servers.lua index 0a92107..5663e11 100644 --- a/nvim/.config/nvim/lua/user/lsp/servers.lua +++ b/nvim/.config/nvim/lua/user/lsp/servers.lua @@ -1,53 +1,30 @@ local M = {} M.setup = function() - local on_attaches = require 'user.lsp.on-attach' - local default_on_attach = on_attaches.default local capabilities = require('user.lsp.config').capabilities - require('lspconfig')['bashls'].setup { - on_attach = default_on_attach, - capabilities = capabilities, - } - - require('lspconfig')['cssls'].setup { - on_attach = default_on_attach, - capabilities = capabilities, - } - - require('lspconfig')['cssmodules_ls'].setup { - on_attach = default_on_attach, - capabilities = capabilities, - } - - require('lspconfig')['dockerls'].setup { - on_attach = default_on_attach, - capabilities = capabilities, - } - - -- typescript - require('lspconfig')['vtsls'].setup { - on_attach = default_on_attach, - capabilities = capabilities, - } - - require('lspconfig')['docker_compose_language_service'].setup { - on_attach = default_on_attach, - capabilities = capabilities, - } - - require('lspconfig')['groovyls'].setup { - on_attach = default_on_attach, - capabilities = capabilities, - } - - require('lspconfig')['html'].setup { - on_attach = default_on_attach, - capabilities = capabilities, - } - - require('lspconfig')['jsonls'].setup { - on_attach = default_on_attach, - capabilities = capabilities, + vim.lsp.config('*', { capabilities = capabilities }) + vim.lsp.enable { + 'bashls', + 'cssls', + 'cssmodules_ls', + 'docker_compose_language_service', + 'dockerls', + 'golangci_lint_ls', + 'groovyls', + 'helm_ls', + 'html', + 'jinja_lsp', + 'jsonls', + 'lua_ls', + 'pyright', + 'taplo', + 'terraformls', + 'vimls', + 'vtsls', + 'yamlls', + } + + vim.lsp.config('jsonls', { settings = { json = { trace = { @@ -57,83 +34,75 @@ M.setup = function() validate = { enable = true }, }, }, - } + }) - require('lspconfig')['pyright'].setup { - on_attach = default_on_attach, - capabilities = capabilities, + vim.lsp.config('pyright', { settings = { organizeimports = { provider = 'isort', }, }, - } + }) - require('lspconfig')['lua_ls'].setup { - on_attach = default_on_attach, - capabilities = capabilities, + vim.lsp.config('lua_ls', { settings = { Lua = { - runtime = { - -- Tell the language server which version of Lua you're using (most likely LuaJIT) - version = 'LuaJIT', - }, - completion = { - callSnippet = 'Replace', - }, - hint = { - enable = true, - }, + runtime = { version = 'LuaJIT' }, + completion = { callSnippet = 'Replace' }, + hint = { enable = true }, diagnostics = { disable = { 'undefined-global' }, globals = { 'vim' }, }, - -- workspace = { - -- -- Make the server aware of Neovim runtime files - -- library = {}, - -- checkThirdParty = false, - -- }, - -- telemetry = { enable = false }, }, }, - } + }) - require('lspconfig')['terraformls'].setup { - on_attach = function(c, b) + vim.lsp.config('terraformls', { + on_attach = function(c) require('treesitter-terraform-doc').setup {} - default_on_attach(c, b) c.server_capabilities.semanticTokensProvider = {} vim.o.commentstring = '# %s' end, - capabilities = capabilities, - } - - require('lspconfig')['vimls'].setup { - on_attach = default_on_attach, - capabilities = capabilities, - } - - require('lspconfig')['taplo'].setup { - on_attach = default_on_attach, - capabilities = capabilities, - } - - require('lspconfig')['helm_ls'].setup { - on_attach = default_on_attach, - capabilities = capabilities, + }) + + -- local yaml_cfg = { + -- yaml = { + -- format = { + -- bracketSpacing = false, + -- }, + -- -- schemas = require('schemastore').yaml.schemas(), + -- -- schemas = vim.tbl_deep_extend('force', { [require('kubernetes').yamlls_schema()] = '*.yaml' }, require('schemastore').yaml.schemas()), + -- schemaStore = { + -- -- Must disable built-in schemaStore support to use + -- -- schemas from SchemaStore.nvim plugin + -- enable = false, + -- -- Avoid TypeError: Cannot read properties of undefined (reading 'length') + -- url = '', + -- }, + -- schemas = {}, + -- }, + -- } + -- vim.lsp.config('yamlls', { + -- capabilities = vim.tbl_deep_extend('force', capabilities, { + -- textDocument = { + -- foldingRange = { + -- dynamicRegistration = true, + -- }, + -- }, + -- }), + -- }) + + local yaml_cfg = require('user.lsp.yaml').setup { capabilities = capabilities } + + vim.lsp.config('helm_ls', { filetypes = { 'helm', 'gotmpl' }, - } - - require('user.lsp.yaml').setup { - capabilities = capabilities, - on_attach = default_on_attach, - } - - -- golang - require('lspconfig').gopls.setup { - capabilities = capabilities, - on_attach = default_on_attach, - } + settings = { + yamlls = { + config = yaml_cfg.settings, + }, + }, + }) end return M diff --git a/nvim/.config/nvim/lua/user/lsp/yaml.lua b/nvim/.config/nvim/lua/user/lsp/yaml.lua index 5099801..d9fcde9 100644 --- a/nvim/.config/nvim/lua/user/lsp/yaml.lua +++ b/nvim/.config/nvim/lua/user/lsp/yaml.lua @@ -6,24 +6,106 @@ local M = { }, }, all_schemas = {}, + yaml_cfg = {}, + core_api_groups = { + [''] = true, -- core group (v1) + ['admissionregistration.k8s.io'] = true, + ['apiextensions.k8s.io'] = true, + ['apps'] = true, + ['autoscaling'] = true, + ['batch'] = true, + ['certificates.k8s.io'] = true, + ['networking.k8s.io'] = true, + ['policy'] = true, + ['rbac.authorization.k8s.io'] = true, + ['storage.k8s.io'] = true, + }, } +local function split_and_add(current, resources) + if current.apiVersion and current.kind then + -- Split apiVersion into group and version + local group, version + if current.apiVersion:find '/' then + group, version = current.apiVersion:match '(.+)/(.+)' + else + group = '' + version = current.apiVersion + end + + table.insert(resources, { + kind = current.kind, + apiVersion = current.apiVersion, + apiGroup = group, + version = version, + line = current.line, + }) + end +end + +M.add_crds = function(bufnr) + local lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false) + if lines[1] == '---' then + table.remove(lines, 1) + end + local resources = {} + local current = { line = 0 } -- Start at line 1 for first resource + for i, line in ipairs(lines) do + if line:match '^kind:' then + current.kind = line:match '^kind:%s*(.+)' + elseif line:match '^apiVersion:' then + current.apiVersion = line:match '^apiVersion:%s*(.+)' + elseif line:match '^%-%-%-' then + split_and_add(current, resources) + current = { line = i + 1 } -- Start at line 1 for first resource + end + end + + -- last section + split_and_add(current, resources) + + -- Add comments before CRD resources + local lines_to_add = {} + local added_kinds = {} + for _, resource in ipairs(resources) do + -- If the API group is not in M.core_api_groups, it's likely a CRD + if resource.apiGroup ~= '' and not M.core_api_groups[resource.apiGroup] then + -- Construct URL based on apiGroup and version + local url = + string.format('https://raw.githubusercontent.com/datreeio/CRDs-catalog/main/%s/%s_%s.json', resource.apiGroup, resource.kind:lower(), resource.version) + local modeline = string.format('# yaml-language-server: $schema=%s', url) + -- Check if the modeline already exists in the previous line + local prev_line = vim.api.nvim_buf_get_lines(bufnr, resource.line, resource.line + 1, false)[1] + if not prev_line or not prev_line:match '# yaml%-language%-server: %$schema=' then + table.insert(lines_to_add, { line = resource.line - 1, comment = modeline }) + if not vim.list_contains(added_kinds, resource.kind) then + table.insert(added_kinds, resource.kind) + end + end + end + end + + -- Insert the comments + local offset = 1 + for _, line in ipairs(lines_to_add) do + local line_num = line.line + vim.api.nvim_buf_set_lines(bufnr, line_num + offset, line_num + offset, false, { line.comment }) + offset = offset + 1 + end + + -- return the kind: of the modelines that were added + return added_kinds +end + M.setup = function(opts) local capabilities = opts.capabilities or require('user.lsp.config').capabilities - local on_attach = opts.on_attach or require('user.lsp.on-attach').default local yaml_lspconfig = { - cmd = { 'yaml-language-server', '--stdio' }, - on_attach = function(c, b) - local filetype = vim.api.nvim_get_option_value('filetype', { buf = b }) - local buftype = vim.api.nvim_get_option_value('buftype', { buf = b }) - local disabled_fts = { 'helm', 'yaml.gotexttmpl', 'gotmpl' } - if buftype ~= '' or filetype == '' or vim.tbl_contains(disabled_fts, filetype) then - vim.diagnostic.enable(false, b) - vim.defer_fn(function() - vim.diagnostic.reset(nil, b) - end, 1000) + on_attach = function(_, bufnr) + local modeline_added = M.add_crds(bufnr) + if not vim.tbl_isempty(modeline_added) then + local crds = table.concat(modeline_added, ', ') + vim.notify('Added YAML modeline for CRDs: ' .. crds, vim.log.levels.INFO, { title = 'YAML LSP' }) end - on_attach(c, b) end, capabilities = vim.tbl_deep_extend('force', capabilities, { textDocument = { @@ -37,7 +119,7 @@ M.setup = function(opts) format = { bracketSpacing = false, }, - schemas = require('schemastore').yaml.schemas(), + -- schemas = require('schemastore').yaml.schemas(), schemaStore = { -- Must disable built-in schemaStore support to use -- schemas from SchemaStore.nvim plugin @@ -53,6 +135,7 @@ M.setup = function(opts) vim.list_extend(M.all_schemas, require('schemastore').json.schemas()) -- vim.list_extend(M.all_schemas, require('user.additional-schemas').crds_as_schemas()) local yaml_cfg = require('yaml-companion').setup { + -- log_level = 'debug', builtin_matchers = { -- Detects Kubernetes files based on content kubernetes = { enabled = true }, @@ -60,7 +143,17 @@ M.setup = function(opts) schemas = M.all_schemas, lspconfig = yaml_lspconfig, } - require('lspconfig')['yamlls'].setup(yaml_cfg) + vim.lsp.config('yamlls', yaml_cfg) + M.yaml_cfg = yaml_cfg + + -- add actions + require('user.menu').add_actions('YAML', { + ['Auto add CRD schema modlines'] = function() + M.add_crds(0) + end, + }) + + return yaml_cfg end return M diff --git a/nvim/.config/nvim/lua/user/menu.lua b/nvim/.config/nvim/lua/user/menu.lua index 788aa9f..1de66e0 100644 --- a/nvim/.config/nvim/lua/user/menu.lua +++ b/nvim/.config/nvim/lua/user/menu.lua @@ -47,7 +47,7 @@ end ---@return nil M.setup = function() vim.keymap.set('n', 'a', function() - vim.ui.select(vim.tbl_keys(M.actions), { prompt = 'Choose action❯ ' }, function(choice) + vim.ui.select(vim.tbl_keys(M.actions), { title = 'Actions', prompt = 'Choose action❯ ' }, function(choice) if choice then M.actions[choice]() end diff --git a/nvim/.config/nvim/lua/user/number-separators.lua b/nvim/.config/nvim/lua/user/number-separators.lua index 7d7d782..232bfb4 100644 --- a/nvim/.config/nvim/lua/user/number-separators.lua +++ b/nvim/.config/nvim/lua/user/number-separators.lua @@ -12,7 +12,7 @@ local function format_number(number_str) local int_part, dec_part = number_str:match '([^.]*)(.?.*)' -- Format integer part with spaces every 3 digits from the right - local formatted = int_part:reverse():gsub('(%d%d%d)', '%1,'):reverse():gsub('^%s+', '') + local formatted = int_part:reverse():gsub('(%d%d%d)', '%1,'):reverse():gsub('^,', ''):gsub('^%s+', '') return sign .. formatted .. dec_part end @@ -72,7 +72,7 @@ end -- enable_number_separators() -- Add command to toggle the feature -vim.api.nvim_create_user_command('ToggleNumberSeparators', function() +vim.api.nvim_create_user_command('NumberSeparatorsToggle', function() if vim.b.number_separators_enabled then disable_number_separators() vim.notify 'Number separators disabled' diff --git a/nvim/.config/nvim/lua/user/open-url.lua b/nvim/.config/nvim/lua/user/open-url.lua index dfd9a39..9a809f3 100644 --- a/nvim/.config/nvim/lua/user/open-url.lua +++ b/nvim/.config/nvim/lua/user/open-url.lua @@ -11,7 +11,7 @@ M.open_url_under_cursor = function() url = string.gsub(url, [[^["'](.*)["']$]], '%1') -- If string starts with https:// - if string.match(url, [[^https://.*]]) then + if string.match(url, [[^https?://.*]]) then return vim.ui.open(url) end @@ -26,6 +26,30 @@ M.open_url_under_cursor = function() return vim.ui.open(M.url_prefix .. '/' .. url .. suffix) end + + -- if not cword, find all http(s) links in the file and use vim.ui.select to choose one + local lines = vim.api.nvim_buf_get_lines(0, 0, -1, false) + local links = {} + for i, line in ipairs(lines) do + for link in string.gmatch(line, [[(https?://[%w%.%-_/]+)]]) do + table.insert(links, { line = i, link = link }) + end + end + + if #links == 0 then + return vim.notify('No links found in the file', vim.log.levels.WARN) + end + + vim.ui.select(links, { + prompt = 'Select a link to open:', + format_item = function(item) + return item.line .. ':🔗 ' .. item.link + end, + }, function(selected) + if selected then + vim.ui.open(selected.link) + end + end) end return M diff --git a/nvim/.config/nvim/lua/user/options.lua b/nvim/.config/nvim/lua/user/options.lua index f47e151..508e519 100644 --- a/nvim/.config/nvim/lua/user/options.lua +++ b/nvim/.config/nvim/lua/user/options.lua @@ -1,11 +1,21 @@ vim.o.compatible = false vim.g.python3_host_prog = 'python3' +vim.g.loaded_perl_provider = 0 +vim.g.loaded_ruby_provider = 0 -- disable legacy vim filetype detection in favor of new lua based from neovim -- vim.g.do_filetype_lua = 1 -- vim.g.did_load_filetypes = 0 -vim.o.titlestring = "nvim: %{substitute(getcwd(), $HOME . '/Repos/', '', '')}" +-- titlestring +vim.cmd [[ + function! CleanTitle() + return "nvim: " . substitute(getcwd(), $HOME . '/\(Repos/\)\?', '', '') + endfunction +]] +vim.o.titlestring = '%{CleanTitle()}' + +vim.o.title = true -- Changes the wezterm title vim.o.cursorcolumn = true vim.o.cursorline = true -- Add highlight behind current line vim.opt.shortmess:append { c = true, l = false, q = false, S = false, C = true, I = true } @@ -29,14 +39,21 @@ vim.opt.fillchars = { diff = ' ', eob = ' ', } -vim.o.shada = [[!,'100,<1000,s100,h]] +vim.o.shada = [[!,'1000,s1000,h]] -- vim.opt.foldcolumn = '1' vim.o.emoji = true -- go to previous/next line with h,l,left arrow and right arrow -- when cursor reaches end/beginning of line -- opt.whichwrap:append '<>[]hl' -vim.opt.diffopt:append { linematch = 50 } -vim.opt.diffopt:append 'vertical' +vim.opt.diffopt = { + 'internal', + 'filler', + 'closeoff', + 'indent-heuristic', + 'linematch:60', + 'vertical', + 'algorithm:histogram', +} vim.o.splitkeep = 'screen' vim.o.number = true -- Show current line number @@ -70,10 +87,8 @@ vim.o.wildmenu = true -- Displays a menu on autocomplete vim.opt.wildmode = { 'longest:full', 'full' } -- Command-line completion mode vim.opt.completeopt = 'menu,menuone,noselect,noinsert,popup' vim.o.previewheight = 15 -vim.o.title = true -- Changes the iterm title vim.o.laststatus = 3 -- Global statusline, only one for all buffers vim.o.showcmd = true -vim.o.guifont = 'Fira Code,Hack Nerd Font' vim.o.mouse = 'a' vim.o.undofile = true -- Enables saving undo history to a file vim.o.undolevels = 10000 @@ -86,9 +101,11 @@ vim.o.conceallevel = 0 vim.o.showmode = false -- Redundant as lighline takes care of that vim.opt.cpoptions:append '>' vim.o.equalalways = true -- When splitting window, make new window same size -vim.o.history = 1000 +vim.o.history = 10000 vim.o.termguicolors = true vim.o.signcolumn = 'yes' +vim.o.virtualedit = 'block' -- Allow cursor to move to the end of the line +vim.opt.nrformats:append 'blank' -- require 'user.winbar' -- opt.winbar = "%{%v:lua.require'user.winbar'.eval()%}" -- vim.o.statuscolumn = '%=%{v:wrap?"":v:relnum?v:relnum:v:lnum} %s%C' @@ -102,13 +119,12 @@ vim.opt.path:append { '**' } -- Folding vim.o.foldenable = true --- vim.o.foldmethod = 'syntax' vim.opt.foldmethod = 'expr' vim.opt.foldexpr = 'nvim_treesitter#foldexpr()' vim.o.foldlevel = 999 vim.o.foldlevelstart = 99 vim.o.foldcolumn = '1' -- '0' is not bad -vim.o.foldenable = true +vim.o.foldtext = 'substitute(getline(v:foldstart),"\t",repeat(" ",&tabstop),"g")."...".trim(getline(v:foldend))' -- Support undercurl vim.cmd [[ @@ -149,16 +165,29 @@ vim.opt.indentkeys:remove '<:>' local kube_config_pattern = [[.*\.kube/config]] vim.filetype.add { extension = { tfvars = 'terraform' }, + filename = { + Brewfile = 'ruby', + }, pattern = { ['.*/templates/.*%.yaml'] = { function() if vim.fn.search('{{.+}}', 'nw') then - return 'gotmpl' + return 'helm' end end, { priority = 200 }, }, ['.*Jenkinsfile.*'] = 'groovy', [kube_config_pattern] = 'yaml', + ['.*'] = function() + -- loop through the first 20 lines of the file and search a line + -- that starts with kind: or apiVersion: to determine the filetype is yaml + for i = 1, 20 do + local line = vim.fn.getline(i) + if line:match '^kind:' or line:match '^apiVersion:' then + return 'yaml' + end + end + end, }, } diff --git a/nvim/.config/nvim/lua/user/projects.lua b/nvim/.config/nvim/lua/user/projects.lua new file mode 100644 index 0000000..90caa4c --- /dev/null +++ b/nvim/.config/nvim/lua/user/projects.lua @@ -0,0 +1,123 @@ +local M = { + icon = '', +} + +-- Helper function to get all directories in the given paths +local function get_directories(paths) + local dirs = {} + for _, path in ipairs(paths) do + local expanded_path = vim.fn.expand(path) + -- If path ends with /, scan subdirectories + if expanded_path:match '/$' then + -- Remove trailing slash for consistent path handling + expanded_path = expanded_path:gsub('/$', '') + local handle = vim.uv.fs_scandir(expanded_path) + if handle then + while true do + local name, type = vim.uv.fs_scandir_next(handle) + if not name then + break + end + if type == 'directory' then + dirs[#dirs + 1] = expanded_path .. '/' .. name + end + end + end + else + -- If path doesn't end with /, just add the directory itself + dirs[#dirs + 1] = expanded_path + end + end + return dirs +end + +-- Get wezterm panes information +local function get_wezterm_panes() + local output = vim.fn.system 'wezterm cli list --format json' + if vim.v.shell_error ~= 0 then + vim.notify('Failed to get wezterm panes', vim.log.levels.ERROR) + return {} + end + return vim.json.decode(output) +end + +-- Find existing nvim project tab +local function find_project_tab(project_name, panes) + for _, pane in ipairs(panes) do + if pane.title == ('nvim: ' .. project_name) then + return pane + end + end + return nil +end + +-- Switch to project +local function switch_to_project(project_path, panes) + local project_name = vim.fn.fnamemodify(project_path, ':t') + local existing_tab = find_project_tab(project_name, panes) + + if existing_tab then + -- Activate existing tab + vim.fn.system('wezterm cli activate-tab --tab-id ' .. existing_tab.tab_id) + vim.fn.system('wezterm cli activate-pane --pane-id ' .. existing_tab.pane_id) + else + -- Create new tab with nvim + vim.system({ 'wezterm', 'cli', 'spawn', '--cwd', vim.fn.expand(project_path) }, { text = true }, function(res) + local id = vim.trim(res.stdout) + vim.schedule(function() + vim.system { 'wezterm', 'cli', 'send-text', '--pane-id', id, 'nvim' .. vim.keycode '' } + end) + end) + end +end + +-- Main function to pick and switch projects +function M.pick_project() + local fzf = require 'fzf-lua' + + -- Get all directories from Repos and dotfiles + local all_dirs = get_directories { '~/Repos/', '~/.dotfiles' } + + -- Get active projects from wezterm + local active_projects = {} + local panes = get_wezterm_panes() + for _, pane in ipairs(panes) do + local project_name = pane.title:match '^nvim: (.+)$' + if project_name then + active_projects[project_name] = pane + end + end + + -- Format directories for display + local formatted_dirs = {} + local display_to_path = {} + for _, dir in ipairs(all_dirs) do + local name = vim.fn.fnamemodify(dir, ':t') + local display_name = active_projects[name] and M.icon .. ' ' .. name or ' ' .. name + formatted_dirs[#formatted_dirs + 1] = display_name + display_to_path[display_name] = dir + end + + -- Show fzf picker + fzf.fzf_exec(formatted_dirs, { + prompt = 'Projects> ', + actions = { + ['default'] = function(selected) + if selected and selected[1] then + local dir = display_to_path[selected[1]] + if dir then + switch_to_project(dir, panes) + end + end + end, + }, + }) +end + +function M.setup() + vim.keymap.set('n', 'pj', function() + M.pick_project() + end, { desc = 'Switch Project' }) +end + +return M diff --git a/nvim/.config/nvim/lua/user/run-buffer.lua b/nvim/.config/nvim/lua/user/run-buffer.lua index f6f5cdb..65d64de 100644 --- a/nvim/.config/nvim/lua/user/run-buffer.lua +++ b/nvim/.config/nvim/lua/user/run-buffer.lua @@ -1,3 +1,63 @@ +--- Given a path, open the file, extract all the Makefile keys, +-- and return them as a list. +---@param path string +---@return table options A telescope options list like +--{ { text: "1 - all", value="all" }, { text: "2 - hello", value="hello" } ...} +local function get_makefile_options(path) + local options = {} + + -- Open the Makefile for reading + local file = io.open(path, 'r') + + if file then + local in_target = false + local count = 0 + + -- Iterate through each line in the Makefile + for line in file:lines() do + -- Check for lines starting with a target rule (e.g., "target: dependencies") + local target = line:match '^(.-):' + if target then + -- Skip .PHONY declarations and empty targets + if not target:match '^%.PHONY' and not target:match '^%s*$' then + in_target = true + count = count + 1 + -- Exclude the ":" and add the option to the list with text and value fields + table.insert(options, { text = count .. ' - ' .. target, value = target }) + end + elseif in_target then + -- If we're inside a target block, stop adding options + in_target = false + end + end + + -- Close the Makefile + file:close() + else + vim.notify('Unable to open a Makefile in the current working dir.', vim.log.levels.ERROR, { + title = 'Makeit.nvim', + }) + end + + return options +end + +local function run_lua(file_name) + local path = file_name:match 'nvim/lua/(.*)%.lua' + if path then + path = path:gsub('/', '.') + if package.loaded[path] then + package.loaded[path] = nil + vim.notify('Unloaded package.path: ' .. path, vim.log.levels.INFO) + end + end + vim.cmd 'luafile %' + vim.notify('Reloading lua file', vim.log.levels.INFO) +end + +---Open a new tab in wezterm and write the command +---@param cmd string command to write +---@param opts table options local function open_tab(cmd, opts) if not opts.cwd then opts.cwd = vim.fn.getcwd() @@ -13,15 +73,74 @@ local function open_tab(cmd, opts) end end -local function execute_file(where) - if vim.bo.buftype == 'terminal' then - return +---Get make command +---@param file_name string file name +---@return string make command +local function get_make(file_name) + local options = get_makefile_options(file_name) + local opts_for_select = vim.tbl_map(function(option) + return option.text + end, options) + + local choice = vim.fn.inputlist { + 'Select a target to run:', + table.concat(opts_for_select, '\n'), + } + if choice < 1 or choice > #options then + return '' end + return 'make ' .. options[choice].value +end + +--- Get cmd or break +---@param ft string filetype +---@param file_name string file name +---@return string|nil cmd +---@return boolean should_break +local function cmd_or_break(ft, file_name) local utils = require 'user.utils' - local ft = vim.bo.filetype ~= '' and vim.bo.filetype or 'sh' + local cmd = utils.filetype_to_command[ft] or 'bash' + local first_line = vim.api.nvim_buf_get_lines(0, 0, 1, false)[1] or '' - -- check if current buffer is a valid file + if first_line:match '^#!' then + cmd = file_name + else + cmd = cmd .. ' ' .. file_name + end + + if vim.startswith(cmd, 'open') then + vim.ui.open(file_name) + return nil, true + end + + if ft == 'lua' then + run_lua(file_name) + return nil, true + end + + if ft == 'groovy' then + require('user.jenkins-validate').validate() + return nil, true + end + + if ft == 'make' then + cmd = get_make(file_name) + if not cmd then + return nil, true + end + end + + if ft == 'terraform' then + cmd = 'terragrunt plan' + end + + return cmd, false +end + +local function filename_and_ft() + local ft = vim.bo.filetype ~= '' and vim.bo.filetype or 'sh' local file_name = vim.fn.expand '%:p' + -- check if current buffer is a valid file if file_name == '' then vim.api.nvim_set_option_value('filetype', ft, { buf = 0 }) file_name = _G.start_ls() @@ -29,40 +148,28 @@ local function execute_file(where) -- check if file has changed and prompt the user if should save if vim.bo.modified then - local save = vim.fn.confirm(('Save changes to %q before running?'):format(vim.fn.bufname()), '&Yes\n&No\n&Cancel') + local save = vim.fn.confirm(('Save changes to %q before running?'):format(file_name), '&Yes\n&No\n&Cancel') if save == 3 then return elseif save == 1 then vim.cmd.write() end end + return file_name, ft +end - -- check if there's a shebang to determine cmd - local cmd = utils.filetype_to_command[ft] or 'bash' - local first_line = vim.api.nvim_buf_get_lines(0, 0, 1, false)[1] or '' - if cmd == 'open' then - return vim.ui.open(file_name) - end - if ft == 'lua' then - local path = vim.fn.expand('%:p'):match('nvim/lua/(.*)%.lua'):gsub('/', '.') - if package.loaded[path] then - package.loaded[path] = nil - end - vim.cmd 'luafile %' - vim.notify('Reloading lua file', vim.log.levels.INFO) +local function execute_file(where) + if vim.bo.buftype == 'terminal' then return end - if ft == 'groovy' then - require('user.jenkins-validate').validate() + local file_name, ft = filename_and_ft() + if not file_name or not ft then return end - if ft == 'terraform' then - cmd = 'terragrunt plan' - ---@diagnostic disable-next-line: undefined-field - elseif first_line:match '^#!' then - cmd = file_name - else - cmd = cmd .. ' ' .. file_name + + local cmd, should_break = cmd_or_break(ft, file_name) + if should_break or not cmd then + return end local opts = { cwd = vim.fn.expand '%:p:h' } @@ -91,9 +198,11 @@ local function execute_file(where) end vim.keymap.set('n', '', execute_file, { remap = false, silent = true }) + vim.api.nvim_create_user_command('RunInTerminal', function() execute_file 'terminal' end, {}) + vim.api.nvim_create_user_command('RunInTab', function() execute_file 'tab' end, {}) diff --git a/nvim/.config/nvim/lua/user/search-replace.vim b/nvim/.config/nvim/lua/user/search-replace.vim index 3c610ad..4221db3 100644 --- a/nvim/.config/nvim/lua/user/search-replace.vim +++ b/nvim/.config/nvim/lua/user/search-replace.vim @@ -4,33 +4,24 @@ endif let g:loaded_search_replace = 1 let s:chars = ['/', '?', '#', ':', '@'] +let s:magic_list = ['\v', '\m', '\M', '\V', ''] +let s:available_flags = ['g', 'c', 'i'] -function! s:should_sar() - if getcmdtype() !=# ':' || get(g:, 'sar_active', v:false) == v:false - return v:false - endif - return v:true +function! s:should_sar() abort + return getcmdtype() ==# ':' && get(g:, 'sar_active', v:false) endfunction -function! s:find_unique_char(chars, str) - " Iterate through each character in the list - for char in a:chars - " Check if the character is not in the string - if stridx(a:str, char) == -1 - return char - endif - endfor - " If all characters are found in the string, return an appropriate value - return '' +function! s:find_unique_char(chars, str) abort + for char in a:chars + if stridx(a:str, char) == -1 + return char + endif + endfor + return '' endfunction -function! SarPopulateSearchline(mode) - if a:mode ==# 'n' - let g:sar_cword = expand('') - else - let g:sar_cword = GetMotion('gv') - endif - " Define a list of characters +function! SarPopulateSearchline(mode) abort + let g:sar_cword = a:mode ==# 'n' ? expand('') : GetMotion('gv') let g:sar_sep = s:find_unique_char(s:chars, g:sar_cword) let g:sar_magic = '\V' let cmd = '.,$s' . g:sar_sep @@ -45,7 +36,7 @@ endfunction nnoremap r :eSarPopulateSearchline('n') vnoremap r :eSarPopulateSearchline('v') -func SarToggleChar(char) +function! SarToggleChar(char) abort let cmd = getcmdline() if !s:should_sar() return cmd @@ -55,10 +46,8 @@ func SarToggleChar(char) let cmd_splitted = split(cmd, sep, 1) let cmd_flags = cmd_splitted[-1] let cmd_pos = getcmdpos() - let available_flags = ['g', 'c', 'i'] if cmd_flags =~ a:char - " remove the flag let new_flags = substitute(cmd_flags, a:char, '', '') else " add the flag @@ -72,107 +61,81 @@ func SarToggleChar(char) let cmd_splitted[-1] = new_flags let cmd = join(cmd_splitted, sep) - " let cmd = cmd[:-len(cmd_flags) - 1] . new_flags - - " place the cursor on the ) call setcmdpos(cmd_pos) return cmd -endfunc +endfunction -func SarToggleReplaceTerm() +function! SarToggleReplaceTerm() abort let cmd = getcmdline() if !s:should_sar() return cmd endif + let sep = get(g:, 'sar_sep', '/') let cmd_splitted = split(cmd, sep, 1) - let sar_cword = get(g:, 'sar_cword') - if empty(sar_cword) - let sar_cword = cmd_splitted[1] - let g:sar_cword = sar_cword - endif - let cmd_pos = getcmdpos() - let replace_term = cmd_splitted[-2] - if replace_term ==# '' - let replace_term = g:sar_cword - else - let replace_term = '' - endif + let g:sar_cword = get(g:, 'sar_cword', cmd_splitted[1]) + let replace_term = cmd_splitted[-2] ==# '' ? g:sar_cword : '' let cmd_splitted[-2] = replace_term let cmd = join(cmd_splitted, sep) call setcmdpos(len(cmd) - len(cmd_splitted[-1])) return cmd -endfunc +endfunction -func SarToggleAllFile() +function! SarToggleAllFile() abort let cmd = getcmdline() if !s:should_sar() return cmd endif + let sep = get(g:, 'sar_sep', '/') let cmd_splitted = split(cmd, sep, 1) - let cmd_pos = getcmdpos() let all_file = cmd_splitted[0] - if all_file ==# '%s' - let all_file = '.,$s' - elseif all_file ==# '.,$s' - let all_file = '0,.s' - else - let all_file = '%s' - endif + let all_file = all_file ==# '%s' ? '.,$s' : + \ all_file ==# '.,$s' ? '0,.s' : '%s' let cmd_splitted[0] = all_file let cmd = join(cmd_splitted, sep) call setcmdpos(len(cmd) - len(cmd_splitted[-1])) return cmd -endfunc +endfunction -func SarToggleSeparator() +function! SarToggleSeparator() abort let cmd = getcmdline() if !s:should_sar() return cmd endif + let sep = get(g:, 'sar_sep', '/') let cmd_splitted = split(cmd, sep, 1) let cmd_pos = getcmdpos() let new_char_idx = index(s:chars, sep) + 1 - let new_sep = new_char_idx >= len(s:chars) ? s:chars[0] : s:chars[new_char_idx] - let g:sar_sep = new_sep - let cmd = join(cmd_splitted, new_sep) + let g:sar_sep = new_char_idx >= len(s:chars) ? s:chars[0] : s:chars[new_char_idx] + + let cmd = join(cmd_splitted, g:sar_sep) call setcmdpos(cmd_pos) return cmd -endfunc +endfunction -func SarToggleMagic() +function! SarToggleMagic() abort let cmd = getcmdline() if !s:should_sar() return cmd endif - let magic_list = ['\v', '\m', '\M', '\V', ''] + let sep = get(g:, 'sar_sep', '/') let cmd_splitted = split(cmd, sep, 1) let cmd_pos = getcmdpos() - - " cword - let sar_cword = get(g:, 'sar_cword') - if empty(sar_cword) - let sar_cword = cmd_splitted[1] - let g:sar_cword = sar_cword - endif - - " magic + let g:sar_cword = get(g:, 'sar_cword', cmd_splitted[1]) let magic = get(g:, 'sar_magic', '\V') - let new_magic_idx = index(magic_list, magic) + 1 - let new_magic = new_magic_idx >= len(magic_list) ? magic_list[0] : magic_list[new_magic_idx] - let g:sar_magic = new_magic + let new_magic_idx = index(s:magic_list, magic) + 1 + let g:sar_magic = new_magic_idx >= len(s:magic_list) ? s:magic_list[0] : s:magic_list[new_magic_idx] - - let cmd_splitted[1] = new_magic . sar_cword + let cmd_splitted[1] = g:sar_magic . g:sar_cword let cmd = join(cmd_splitted, sep) call setcmdpos(cmd_pos) return cmd -endfunc +endfunction cmap eSarToggleChar('g') cmap eSarToggleChar('c') @@ -181,4 +144,3 @@ cmap eSarToggleReplaceTerm() cmap eSarToggleAllFile() cmap eSarToggleSeparator() cmap eSarToggleMagic() - diff --git a/nvim/.config/nvim/lua/user/tabular-v2.lua b/nvim/.config/nvim/lua/user/tabular-v2.lua new file mode 100644 index 0000000..e3f7c07 --- /dev/null +++ b/nvim/.config/nvim/lua/user/tabular-v2.lua @@ -0,0 +1,864 @@ +local M = { + spacing = 4, -- Number of spaces between columns + default_delimiter = '\t', -- Default delimiter fallback + default_interval = 5, -- Default refresh interval in seconds + bufname_suffix = 'Tabular', -- Suffix for buffer names +} + +---@class TabStateV2 +---@field bufnr integer|nil The ID of the buffer associated with the tab state. +---@field delimiter string The delimiter used for parsing data in this tab state. +---@field interval integer The interval (in seconds) for refreshing the data. +---@field command string The command associated with this tab state. +---@field sort_column integer|nil The column index used for sorting. +---@field sort_direction integer The sorting direction: 1 for ascending, -1 for descending. +---@field col_widths integer[] The widths of the columns in the table. +---@field headers string[] The headers of the table. +---@field raw_lines string[] The raw lines of data from the buffer. +---@field lines string[][] The data lines in the table. +---@field ns_headers integer|nil The namespace ID for headers. +---@field ns_sort integer|nil The namespace ID for sorting. +---@field ns_filter integer|nil The namespace ID for filtering. +---@field timer uv.uv_timer_t|nil The timer for periodic updates. +---@field current_filter string|nil The current filter applied to the table. + +---@type table -- Correct and precise annotation reflecting key-value structure +M.tabs_state = {} + +---@type TabStateV2 +M.default_tab_state = { + bufnr = nil, + delimiter = M.default_delimiter, + interval = 5, -- Default refresh interval in seconds + command = '', + sort_column = nil, + sort_direction = 1, -- Default to ascending sort + col_widths = {}, -- Will be calculated dynamically + headers = {}, -- Will be populated with headers + raw_lines = {}, -- Will be populated with raw lines from the buffer + lines = {}, -- Will be populated with data lines + ns_headers = nil, + ns_sort = nil, + ns_filter = nil, + timer = nil, -- Timer for periodic updates +} + +--- Gets or creates a tab state for a given command +--- @param command string: The command to get or create the tab state for +--- @param opts? table: Optional parameters for the tab state +--- @return TabStateV2|nil: The tab state for the command +function M.get_or_create_tab_state(command, opts) + if not command or command == '' then + print 'Invalid command' + return nil + end + + -- Check if the tab state already exists + if M.tabs_state[command] then + if opts then + -- If opts are provided, extend the existing tab state with new options + M.tabs_state[command] = vim.tbl_deep_extend('force', M.tabs_state[command], opts) + end + return M.tabs_state[command] + end + + -- Create a new tab state if it doesn't exist + M.tabs_state[command] = vim.tbl_deep_extend('force', {}, M.default_tab_state, opts or {}) + return M.tabs_state[command] +end + +--- Gets buffer number by name +--- @param bufname string: The name of the buffer +--- @return integer|nil: The buffer number +function M.get_buffer_by_name(bufname) + for _, buf in ipairs(vim.api.nvim_list_bufs()) do + local name = vim.api.nvim_buf_get_name(buf) + local filename = vim.fs.basename(name) + if filename == bufname then + return buf + end + end + return nil +end + +--- Creates or gets existing buffer for tabular display +--- @param title string: The buffer title +--- @return integer: The buffer number +function M.buffer(title) + local bufname = title .. ' - ' .. M.bufname_suffix + local buf = M.get_buffer_by_name(bufname) + + if not buf then + -- Create new buffer for display and open in new tab + buf = vim.api.nvim_create_buf(false, true) + vim.api.nvim_buf_set_name(buf, bufname) + -- create a new tab and set the created buffer in it + vim.cmd 'tabnew' + vim.api.nvim_set_current_buf(buf) + end + + return buf +end + +---@param buf_lines string[] The lines from the buffer to be parsed. +---@param delimiter string The delimiter used to split the lines into columns. +---@return table A table containing parsed headers, lines, and column widths. +function M.raw_parse(buf_lines, delimiter) + local pattern_delimiter + local tab = vim.api.nvim_replace_termcodes('\t', true, true, true) + if delimiter == ' ' then + pattern_delimiter = ' +' + elseif delimiter == tab then + -- check if buf_lines has tab characters + if buf_lines[1]:find(tab) then + pattern_delimiter = tab + else + -- If no tabs, use spaces as fallback + pattern_delimiter = ' +' + end + else + -- Escape special pattern characters in delimiter + pattern_delimiter = delimiter:gsub('[%(%)%.%%%+%-%*%?%[%]%^%$]', '%%%1') + end + + -- clear empty lines + for i = #buf_lines, 1, -1 do + if vim.trim(buf_lines[i]) == '' then + table.remove(buf_lines, i) + end + end + + -- Parse headers + local headers = {} + local header_line = buf_lines[1] + local header_words = vim.split(header_line, pattern_delimiter) + for _, word in ipairs(header_words) do + word = vim.trim(word) + if word ~= '' then + table.insert(headers, word) + end + end + + -- Parse data lines + local lines = {} + for i = 2, #buf_lines do + local line = buf_lines[i] + local row = {} + local words = vim.split(line, pattern_delimiter) + for _, word in ipairs(words) do + word = vim.trim(word) + if word ~= '' then + table.insert(row, word) + end + end + table.insert(lines, row) + end + + -- Calculate column widths + local col_widths = {} + for i, header in ipairs(headers) do + col_widths[i] = #header + for _, row in ipairs(lines) do + col_widths[i] = math.max(col_widths[i], #(row[i] or '')) + end + end + + return { + headers = headers, + lines = lines, + col_widths = col_widths, + } +end + +function M.set_buf_win_options(bufnr) + vim.api.nvim_set_option_value('filetype', 'tabular', { buf = bufnr }) + vim.api.nvim_set_option_value('buftype', 'nofile', { buf = bufnr }) + vim.api.nvim_set_option_value('bufhidden', 'wipe', { buf = bufnr }) + vim.api.nvim_set_option_value('modifiable', true, { buf = bufnr }) + vim.api.nvim_set_option_value('modified', false, { buf = bufnr }) + + -- set no wrap + local winnr = vim.api.nvim_get_current_win() + vim.api.nvim_set_option_value('wrap', false, { win = winnr }) + + -- Set up keymaps + local k_opts = { noremap = true, silent = true, buffer = bufnr } + vim.keymap.set('n', 'gs', function() + M.sort_by_current_column() + end, k_opts) + vim.keymap.set('n', '', function() + M.filter_table() + end, k_opts) + vim.keymap.set('n', 'ge', function() + local tab_state = M.find_tab_state_by_bufnr(bufnr) + if not tab_state then + print 'No tabular state found for current buffer.' + return + end + M.parse_command(tab_state.command) + end, k_opts) + vim.keymap.set('n', '?', function() + M.show_help() + end, k_opts) +end + +function M.set_lines_and_highlight(opts) + -- Validate bufnr and ns_filter before proceeding + if not opts.bufnr or not vim.api.nvim_buf_is_valid(opts.bufnr) then + print('Error: Invalid buffer number: ' .. tostring(opts.bufnr)) + return + end + + vim.api.nvim_buf_set_lines(opts.bufnr, 0, -1, false, opts.display_lines) + + -- Clear any existing filter and sort indicators + vim.api.nvim_buf_clear_namespace(opts.bufnr, opts.ns_filter, 0, -1) + + -- Add filter indicator if there's an active filter + if opts.current_filter and opts.current_filter ~= '' then + vim.api.nvim_buf_set_extmark(opts.bufnr, opts.ns_filter, 1, 0, { + virt_text = { { '🔍 Filter: ' .. opts.current_filter .. ' ', 'Comment' } }, + virt_text_pos = 'overlay', + }) + end + + -- Add header highlighting and sort indicator + local pos = 0 + for i, width in ipairs(opts.col_widths) do + -- Add header highlighting + vim.api.nvim_buf_set_extmark(opts.bufnr, opts.ns_headers, 0, pos, { + end_col = pos + width, + hl_group = 'Statement', + }) + + -- Add sort indicator if this is the sorted column + if opts.sort_column == i then + local sort_indicator = opts.sort_direction == 1 and '▲' or '▼' + local header_length = #opts.headers[i] + vim.api.nvim_buf_set_extmark(opts.bufnr, opts.ns_sort, 0, pos + header_length, { + virt_text = { { ' ' .. sort_indicator, 'Special' } }, + virt_text_pos = 'overlay', + hl_mode = 'combine', + }) + end + + pos = pos + width + M.spacing + end +end + +function M.display_table(tabular_command, delimiter) + if not tabular_command then + print 'Error: tabular_command is nil' + return + end + + -- Maintain state for the opened tabular commands + local tab_state = M.get_or_create_tab_state(tabular_command, { delimiter = delimiter or M.default_delimiter }) + + tab_state.bufnr = M.buffer(tabular_command) + + M.set_buf_win_options(tab_state.bufnr) + -- Add numeric keymaps for column sorting + local k_opts = { noremap = true, silent = true } + for i = 1, #tab_state.headers do + vim.api.nvim_buf_set_keymap(tab_state.bufnr, 'n', tostring(i), string.format([[lua require('user.tabular-v2').sort_by_column(%d)]], i), k_opts) + end + + -- create namespaces for highlighting if they don't exist + tab_state.ns_headers = tab_state.ns_headers or vim.api.nvim_create_namespace('TabularHeaders' .. tab_state.bufnr) + tab_state.ns_sort = tab_state.ns_sort or vim.api.nvim_create_namespace('TabularSort' .. tab_state.bufnr) + tab_state.ns_filter = tab_state.ns_filter or vim.api.nvim_create_namespace('TabularFilter' .. tab_state.bufnr) + + -- Format and insert headers + local formatted_headers = {} + for i, header in ipairs(tab_state.headers) do + -- Pad the header with spaces to match column width + local padded_header = header .. string.rep(' ', tab_state.col_widths[i] - #header) + table.insert(formatted_headers, padded_header) + end + local header_line = table.concat(formatted_headers, string.rep(' ', M.spacing)) + + -- Create separator line with proper spacing + local separator_parts = {} + for _, width in ipairs(tab_state.col_widths) do + table.insert(separator_parts, string.rep('-', width)) + end + local separator_line = table.concat(separator_parts, string.rep('-', M.spacing)) + local display_lines = { header_line, separator_line } + + -- Format and insert data lines + for _, row in ipairs(tab_state.lines) do + local should_display = true + if tab_state.current_filter and tab_state.current_filter ~= '' then + should_display = false + for _, cell in ipairs(row) do + if string.find(string.lower(cell), tab_state.current_filter, 1, true) then + should_display = true + break + end + end + end + + if should_display then + local formatted_row = {} + for i, cell in ipairs(row) do + local value = cell or '' + local padded_cell = value .. string.rep(' ', tab_state.col_widths[i] - #value) + table.insert(formatted_row, padded_cell) + end + table.insert(display_lines, table.concat(formatted_row, string.rep(' ', M.spacing))) + end + end + + M.set_lines_and_highlight { + bufnr = tab_state.bufnr, + display_lines = display_lines, + current_filter = tab_state.current_filter, + col_widths = tab_state.col_widths, + headers = tab_state.headers, + sort_column = tab_state.sort_column, + sort_direction = tab_state.sort_direction, + ns_headers = tab_state.ns_headers, + ns_sort = tab_state.ns_sort, + ns_filter = tab_state.ns_filter, + } +end + +function M.sort_by_column(col_index, direction) + local current_bufnr = vim.api.nvim_get_current_buf() + local tab_state = M.find_tab_state_by_bufnr(current_bufnr) + + if not tab_state then + return + end + + if direction then + tab_state.sort_column = col_index + tab_state.sort_direction = direction + else + if tab_state.sort_column == col_index then + tab_state.sort_direction = tab_state.sort_direction * -1 + else + tab_state.sort_column = col_index + tab_state.sort_direction = 1 + end + end + + table.sort(tab_state.lines, function(a, b) + -- Handle nil values by treating them as empty strings for comparison + local val_a = a[col_index] or '' + local val_b = b[col_index] or '' + + -- check if there are two numbers separated by a comma, if so, remove it + if val_a:find ',' then + val_a = val_a:gsub('(%d+),(%d+)', '%1%2') + end + if val_b:find ',' then + val_b = val_b:gsub('(%d+),(%d+)', '%1%2') + end + + -- Try to convert to numbers if possible + local num_a = tonumber(val_a) + local num_b = tonumber(val_b) + + -- If both values are numbers, compare numerically + if num_a and num_b then + if tab_state.sort_direction == 1 then + return num_a < num_b + else + return num_a > num_b + end + end + + -- Helper function to determine if a string should be sorted numerically + local function should_sort_numerically(str) + local digits = str:gsub('%D', '') + local non_digits = str:gsub('%d', '') + + -- If no digits, definitely not numeric + if #digits == 0 then + return false + end + + -- If mostly digits (>= 50% of string), likely numeric + if #digits >= #str * 0.5 then + return true + end + + -- Check for common numeric patterns: number followed by unit/suffix + -- Examples: "1000 GiB", "5.2xlarge", "100MB", "3.5TB" + if str:match('^%d+%.?%d*%s*%a*$') then + return true + end + + -- If digits are at the beginning and represent a significant portion, likely numeric + if str:match('^%d+') and #digits >= #str * 0.3 then + return true + end + + return false + end + + -- Check if both strings should be sorted numerically + local a_numeric = should_sort_numerically(val_a) + local b_numeric = should_sort_numerically(val_b) + + if a_numeric and b_numeric then + local str_num_a = val_a:match '(%d+%.?%d*)' or val_a:match '(%d+)' + local str_num_b = val_b:match '(%d+%.?%d*)' or val_b:match '(%d+)' + + if str_num_a and str_num_b then + str_num_a = tonumber(str_num_a) + str_num_b = tonumber(str_num_b) + if tab_state.sort_direction == 1 then + return str_num_a < str_num_b + else + return str_num_a > str_num_b + end + end + end + + -- Otherwise compare as strings + if tab_state.sort_direction == 1 then + return val_a < val_b + else + return val_a > val_b + end + end) + + M.display_table(tab_state.command) +end + +function M.filter_table() + local current_bufnr = vim.api.nvim_get_current_buf() + local tab_state = M.find_tab_state_by_bufnr(current_bufnr) + + if not tab_state then + print('No data associated with current buffer. Current buffer ID:', current_bufnr) + return + end + local input_params = { prompt = 'Filter: ' } + if tab_state.current_filter then + input_params.default = tab_state.current_filter + end + vim.ui.input(input_params, function(input) + if input then + tab_state.current_filter = string.lower(input) + M.display_table(tab_state.command) + end + end) +end + +function M.find_tab_state_by_bufnr(bufnr) + for _, state in pairs(M.tabs_state) do + if state.bufnr == bufnr then + return state + end + end + return nil +end + +function M.sort_by_current_column() + local cursor = vim.api.nvim_win_get_cursor(0) + local col = cursor[2] + + -- Retrieve the tab state for the current buffer + local current_bufnr = vim.api.nvim_get_current_buf() + local tab_state = M.find_tab_state_by_bufnr(current_bufnr) + + if not tab_state or not tab_state.col_widths then + print 'Error: col_widths not defined.' + return + end + + -- Find which column the cursor is in based on column widths and spacing + local current_pos = 0 + local current_column = 1 + for i, width in ipairs(tab_state.col_widths) do + local next_pos = current_pos + width + if i < #tab_state.col_widths then + next_pos = next_pos + M.spacing + end + + if col >= current_pos and col < next_pos then + current_column = i + break + end + + current_pos = next_pos + end + + M.sort_by_column(current_column) +end + +--- Show help with available keymaps +function M.show_help() + local help_lines = { + 'Tabular Keymaps Help', + '=====================', + '', + 'Navigation & Sorting:', + ' gs - Toggle sort direction (ascending/descending)', + ' 1-9 - Sort by column number', + " - Position cursor on column and press 'gs' to sort", + '', + 'Filtering & Editing:', + ' - Filter rows (case insensitive)', + ' ge - Edit command (change command, interval, delimiter)', + '', + 'Help:', + ' ? - Show this help', + '', + 'Sort Indicators:', + ' ▲ - Column sorted ascending', + ' ▼ - Column sorted descending', + ' 🔍 - Active filter indicator', + '', + 'Tips:', + ' • Sorting preserves across data refreshes', + ' • Filters are case insensitive and search all columns', + ' • Use numeric keys 1-9 for quick column sorting', + " • Position cursor on any column and press 'gs' to sort", + '', + 'Press any key to close help...', + } + + -- Create a floating window for help + local buf = vim.api.nvim_create_buf(false, true) + local width = 70 + local height = #help_lines + 2 + + -- Calculate center position + local ui = vim.api.nvim_list_uis()[1] + local win_width = ui.width + local win_height = ui.height + local col = math.floor((win_width - width) / 2) + local row = math.floor((win_height - height) / 2) + + -- Create the floating window + local win = vim.api.nvim_open_win(buf, true, { + relative = 'editor', + width = width, + height = height, + col = col, + row = row, + style = 'minimal', + border = 'rounded', + title = ' Tabular Help ', + title_pos = 'center', + }) + + -- Set buffer content + vim.api.nvim_buf_set_lines(buf, 0, -1, false, help_lines) + vim.api.nvim_set_option_value('modifiable', false, { buf = buf }) + vim.api.nvim_set_option_value('buftype', 'nofile', { buf = buf }) + + -- Add some highlighting + local ns = vim.api.nvim_create_namespace 'TabularHelp' + vim.api.nvim_buf_set_extmark(buf, ns, 0, 0, { + end_col = #help_lines[1], + hl_group = 'Title', + }) + vim.api.nvim_buf_set_extmark(buf, ns, 1, 0, { + end_col = #help_lines[2], + hl_group = 'Title', + }) + + -- Highlight section headers + for i, line in ipairs(help_lines) do + if line:match ':$' then + vim.api.nvim_buf_set_extmark(buf, ns, i - 1, 0, { + end_col = #line, + hl_group = 'Special', + }) + elseif line:match '^ [%w<>%-]+' then + -- Highlight keymaps + local keymap = line:match '^ ([%w<>%-]+)' + if keymap then + vim.api.nvim_buf_set_extmark(buf, ns, i - 1, 2, { + end_col = 2 + #keymap, + hl_group = 'Keyword', + }) + end + end + end + + -- Close on any key press + vim.keymap.set('n', '', function() + vim.api.nvim_win_close(win, true) + end, { buffer = buf, nowait = true }) + + -- Close on any other key + vim.keymap.set('n', '', function() + vim.api.nvim_win_close(win, true) + end, { buffer = buf, nowait = true }) + + -- Set up autocommand to close on focus lost + vim.api.nvim_create_autocmd('BufLeave', { + buffer = buf, + callback = function() + if vim.api.nvim_win_is_valid(win) then + vim.api.nvim_win_close(win, true) + end + end, + once = true, + }) +end + +--- Function to parse the current buffer and display the table +function M.parse_command(existing_command) + local tab_state = existing_command and M.get_or_create_tab_state(existing_command) or nil + local inputs = { + command = { prompt = 'Enter command to run: ', default = existing_command or '' }, + interval = { prompt = 'Enter interval (in seconds): ', default = tostring(tab_state and tab_state.interval or M.default_interval) }, + delimiter = { prompt = 'Enter delimiter: ', default = tostring(tab_state and tab_state.delimiter or M.default_delimiter) }, + } + vim.ui.input(inputs.command, function(full_command) + if not full_command or full_command == '' then + print 'Invalid command' + return + end + local command = vim.fs.basename(full_command) + print('Command set: ' .. command) + + vim.ui.input(inputs.interval, function(interval) + interval = tonumber(interval) + if not interval or interval <= 0 then + print 'Invalid interval' + return + end + + vim.ui.input(inputs.delimiter, function(delimiter) + if not delimiter or delimiter == '' or delimiter == nil then + print 'Invalid delimiter' + return + end + + if existing_command and tab_state then + if tab_state.timer and not tab_state.timer:is_closing() and tab_state.bufnr then + tab_state.timer:stop() + tab_state.timer:close() + tab_state.timer = nil + end + if existing_command ~= command then + M.tabs_state[command] = vim.deepcopy(tab_state) + if tab_state.bufnr then + vim.api.nvim_buf_set_name(tab_state.bufnr, command .. ' - ' .. M.bufname_suffix) + end + end + end + tab_state = M.get_or_create_tab_state(command, { command = command }) + + tab_state.delimiter = tostring(delimiter) + tab_state.interval = interval + + -- Start the loop + tab_state.timer = vim.uv.new_timer() + if not tab_state.timer then + print 'Failed to create timer' + return + end + tab_state.timer:start(0, tab_state.interval * 1000, function() + local cmd_args = vim.split(full_command, ' ', { trimempty = true }) + vim.system(cmd_args, { text = true }, function(result) + local output = result.stdout + if not output or output == '' then + print('No output from command: ' .. command) + if result.stderr and result.stderr ~= '' then + print('Error: ' .. result.stderr) + end + return + end + tab_state.raw_lines = vim.split(output, '\n') + vim.schedule(function() + local raw_parse_res = M.raw_parse(tab_state.raw_lines, tab_state.delimiter) + tab_state.headers = raw_parse_res.headers + tab_state.lines = raw_parse_res.lines + tab_state.col_widths = raw_parse_res.col_widths + if tab_state.sort_column then + M.sort_by_column(tab_state.sort_column, tab_state.sort_direction) + else + -- Create or update the buffer + M.display_table(tab_state.command) + end + -- Update winbar with last refresh timestamp only if the buffer is the current one + local last_refresh = os.date '%Y-%m-%d %H:%M:%S' + local current_bufnr = vim.api.nvim_get_current_buf() + if tab_state.bufnr == current_bufnr then + vim.opt_local.winbar = string.format('Tabular: %s (%ss) | Last refresh: %s', command, interval, last_refresh) + end + end) + end) + end) + end) + end) + end) +end + +--- Unload function to clean up resources when buffer is deleted +function M.unload(bufnr) + if not bufnr then + print 'no bufnr' + return + end + + -- Find the tab state associated with this buffer + local tab_state = M.find_tab_state_by_bufnr(bufnr) + if not tab_state then + return + end + + -- Stop and close the timer if it exists + if tab_state.timer then + if not tab_state.timer:is_closing() then + tab_state.timer:stop() + tab_state.timer:close() + end + tab_state.timer = nil + end + + -- Clear namespaces if they exist + if tab_state.ns_headers then + vim.api.nvim_buf_clear_namespace(bufnr, tab_state.ns_headers, 0, -1) + end + if tab_state.ns_sort then + vim.api.nvim_buf_clear_namespace(bufnr, tab_state.ns_sort, 0, -1) + end + if tab_state.ns_filter then + vim.api.nvim_buf_clear_namespace(bufnr, tab_state.ns_filter, 0, -1) + end + + -- Remove the tab state from our tracking table + local command_to_remove = tab_state.command + if command_to_remove then + M.tabs_state[command_to_remove] = nil + end + + print('Cleaned up tabular resources for buffer ' .. bufnr) +end + +function M.ec2_instance_selector_parse(raw_lines) + local first_line = raw_lines[1] or '' + local second_line = raw_lines[2] or '' + local header_dashes = vim.split(second_line, '%s+', { trimempty = true }) + local header_spaces = vim.split(second_line, '-+', { trimempty = true }) + local dashes_lengths = vim.tbl_map(function(str) + return #str + end, header_dashes) + local spaces_lengths = vim.tbl_map(function(str) + return #str + end, header_spaces) + + local headers = {} + local start_pos = 1 + for i, length in ipairs(dashes_lengths) do + local header = first_line:sub(start_pos, start_pos + length - 1) + table.insert(headers, vim.trim(header)) + start_pos = start_pos + length + spaces_lengths[i] + end + + -- Parse data lines + local lines = {} + for i = 3, #raw_lines do + local data_start_pos = 1 + local line = raw_lines[i] + local row = {} + for j, length in ipairs(dashes_lengths) do + local word = line:sub(data_start_pos, data_start_pos + length - 1) + table.insert(row, vim.trim(word)) + data_start_pos = data_start_pos + length + spaces_lengths[j] + end + table.insert(lines, row) + end + + -- Calculate column widths + local col_widths = {} + for i, header in ipairs(headers) do + col_widths[i] = #header + for _, row in ipairs(lines) do + col_widths[i] = math.max(col_widths[i], #(row[i] or '')) + end + end + + return { + headers = headers, + lines = lines, + col_widths = col_widths, + } +end + +function M.parse_buffer() + local tabular_command = 'buffer' + local bufnr = vim.api.nvim_get_current_buf() + local buf_lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false) + local second_line = buf_lines[2] or '' + -- Check if the second line contains dashes seperated by spaces + if second_line:match '[-=]+%s' then + local parse_result = M.ec2_instance_selector_parse(buf_lines) + local tab_state = M.get_or_create_tab_state(tabular_command, { + command = tabular_command, + raw_lines = buf_lines, + headers = parse_result.headers, + lines = parse_result.lines, + col_widths = parse_result.col_widths, + }) + M.display_table(tabular_command) + else + vim.ui.input({ + prompt = 'Enter delimiter: ', + default = M.default_delimiter, + }, function(input) + if not input then + return + end + + local tab_state = M.get_or_create_tab_state(tabular_command, { + command = tabular_command, + raw_lines = buf_lines, + delimiter = input, + }) + tab_state.raw_lines = buf_lines + tab_state.delimiter = input + + local raw_parse_res = M.raw_parse(tab_state.raw_lines, tab_state.delimiter) + tab_state.headers = raw_parse_res.headers + tab_state.lines = raw_parse_res.lines + tab_state.col_widths = raw_parse_res.col_widths + M.display_table(tabular_command) + end) + end +end + +function M.setup(opts) + opts = opts or {} + if opts.delimiter then + M.delimiter = opts.delimiter + end + + -- Create user commands + vim.api.nvim_create_user_command('TabularParse', function() + M.parse_buffer() + end, {}) + vim.api.nvim_create_user_command('TabularParseCmd', function() + M.parse_command() + end, {}) + + -- Set up autocmd to clean up resources when tabular buffers are deleted + local tabular_group = vim.api.nvim_create_augroup('TabularCleanup', { clear = true }) + vim.api.nvim_create_autocmd({ 'BufDelete', 'BufWipeout' }, { + group = tabular_group, + callback = function(args) + local bufnr = args.buf + -- Check if this is a tabular buffer by checking filetype or buffer name + local filetype = vim.api.nvim_get_option_value('filetype', { buf = bufnr }) + local bufname = vim.api.nvim_buf_get_name(bufnr) + + if filetype == 'tabular' or string.match(bufname, '.- ' .. M.bufname_suffix .. '$') then + M.unload(bufnr) + end + end, + desc = 'Clean up tabular resources when buffer is deleted', + }) +end + +return M diff --git a/nvim/.config/nvim/lua/user/tabular.lua b/nvim/.config/nvim/lua/user/tabular.lua new file mode 100644 index 0000000..191e2ef --- /dev/null +++ b/nvim/.config/nvim/lua/user/tabular.lua @@ -0,0 +1,342 @@ +local M = { + raw_lines = {}, + headers = {}, + lines = {}, + delimiter = '\t', + pattern_delimiter = nil, + current_filter = nil, + sort_column = nil, + sort_direction = 1, -- 1 for ascending, -1 for descending + spacing = 4, -- Number of spaces between columns + col_widths = {}, -- Add this line to store column widths + ns_headers = vim.api.nvim_create_namespace 'tabular_headers', + ns_sort = vim.api.nvim_create_namespace 'tabular_sort', + ns_filter = vim.api.nvim_create_namespace 'tabular_filter', +} + +function M.raw_parse() + -- For two spaces, we need a special pattern + if M.delimiter == ' ' then + M.pattern_delimiter = ' +' + else + -- Escape special pattern characters in delimiter + M.pattern_delimiter = M.delimiter:gsub('[%(%)%.%%%+%-%*%?%[%]%^%$]', '%%%1') + end + + -- Parse headers + M.headers = {} + local header_line = M.raw_lines[1] + local header_words = vim.split(header_line, M.pattern_delimiter) + for _, word in ipairs(header_words) do + table.insert(M.headers, vim.trim(word)) + end + + -- Parse data lines + M.lines = {} + for i = 2, #M.raw_lines do + local line = M.raw_lines[i] + local row = {} + local words = vim.split(line, M.pattern_delimiter) + for _, word in ipairs(words) do + table.insert(row, vim.trim(word)) + end + table.insert(M.lines, row) + end +end + +function M.ec2_instance_selector_parse(raw_lines) + local first_line = raw_lines[1] or '' + local second_line = raw_lines[2] or '' + local header_dashes = vim.split(second_line, '%s+', { trimempty = true }) + local header_spaces = vim.split(second_line, '-+', { trimempty = true }) + local dashes_lengths = vim.tbl_map(function(str) + return #str + end, header_dashes) + local spaces_lengths = vim.tbl_map(function(str) + return #str + end, header_spaces) + + M.headers = {} + local start_pos = 1 + for i, length in ipairs(dashes_lengths) do + local header = first_line:sub(start_pos, start_pos + length - 1) + table.insert(M.headers, vim.trim(header)) + start_pos = start_pos + length + spaces_lengths[i] + end + + -- Parse data lines + M.lines = {} + for i = 3, #M.raw_lines do + local data_start_pos = 1 + local line = M.raw_lines[i] + local row = {} + table.insert(M.lines, row) + for j, length in ipairs(dashes_lengths) do + local word = line:sub(data_start_pos, data_start_pos + length - 1) + table.insert(row, vim.trim(word)) + data_start_pos = data_start_pos + length + spaces_lengths[j] + end + table.insert(M.lines, row) + end +end + +function M.parse_buffer() + local bufnr = vim.api.nvim_get_current_buf() + M.raw_lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false) + local second_line = M.raw_lines[2] or '' + -- Check if the second line contains dashes seperated by spaces + if second_line:match '[-=]+%s' then + M.ec2_instance_selector_parse(M.raw_lines) + M.display_table() + else + vim.ui.input({ + prompt = 'Enter delimiter: ', + default = M.delimiter, + }, function(input) + if not input then + return + end + + M.delimiter = input + M.raw_parse() + M.display_table() + end) + end +end + +function M.sort_by_column(col_index) + -- Store current cursor position + local cursor = vim.api.nvim_win_get_cursor(0) + + if M.sort_column == col_index then + M.sort_direction = M.sort_direction * -1 + else + M.sort_column = col_index + M.sort_direction = 1 + end + + table.sort(M.lines, function(a, b) + -- Handle nil values by treating them as empty strings for comparison + local val_a = a[col_index] or '' + local val_b = b[col_index] or '' + + -- check if there are two numbers separated by a comma, if so, remove it + if val_a:find ',' then + val_a = val_a:gsub('(%d+),(%d+)', '%1%2') + end + if val_b:find ',' then + val_b = val_b:gsub('(%d+),(%d+)', '%1%2') + end + + -- Try to convert to numbers if possible + local num_a = tonumber(val_a) + local num_b = tonumber(val_b) + + -- If both values are numbers, compare numerically + if num_a and num_b then + if M.sort_direction == 1 then + return num_a < num_b + else + return num_a > num_b + end + end + + -- check if there is a number in the string + local str_num_a = val_a:match '(%d+)' or false + local str_num_b = val_b:match '(%d+)' or false + if str_num_a and str_num_b then + str_num_a = tonumber(str_num_a) + str_num_b = tonumber(str_num_b) + if M.sort_direction == 1 then + return str_num_a < str_num_b + else + return str_num_a > str_num_b + end + end + + -- Otherwise compare as strings + if M.sort_direction == 1 then + return val_a < val_b + else + return val_a > val_b + end + end) + + M.display_table() + + -- Restore cursor position + vim.api.nvim_win_set_cursor(0, cursor) +end + +function M.filter_table() + local input_params = { prompt = 'Filter: ' } + if M.current_filter then + input_params.default = M.current_filter + end + vim.ui.input(input_params, function(input) + if input then + M.current_filter = string.lower(input) + M.display_table() + end + end) +end + +function M.display_table() + -- Calculate column widths + M.col_widths = {} + for i, header in ipairs(M.headers) do + M.col_widths[i] = #header + for _, row in ipairs(M.lines) do + M.col_widths[i] = math.max(M.col_widths[i], #(row[i] or '')) + end + end + + local bufnr = vim.api.nvim_get_current_buf() + local is_new_display = vim.bo[bufnr].filetype ~= 'tabular' + + if is_new_display then + -- Create new buffer for display and open in new tab + bufnr = vim.api.nvim_create_buf(false, true) + vim.cmd 'tabnew' + vim.api.nvim_set_current_buf(bufnr) + vim.api.nvim_set_option_value('filetype', 'tabular', { buf = bufnr }) + end + + vim.api.nvim_set_option_value('modifiable', true, { buf = bufnr }) + + -- Format and insert headers + local formatted_headers = {} + for i, header in ipairs(M.headers) do + -- Pad the header with spaces to match column width + local padded_header = header .. string.rep(' ', M.col_widths[i] - #header) + table.insert(formatted_headers, padded_header) + end + local header_line = table.concat(formatted_headers, string.rep(' ', M.spacing)) + + -- Create separator line with proper spacing + local separator_parts = {} + for _, width in ipairs(M.col_widths) do + table.insert(separator_parts, string.rep('-', width)) + end + local separator_line = table.concat(separator_parts, string.rep('-', M.spacing)) + local display_lines = { header_line, separator_line } + + -- Format and insert data lines + for _, row in ipairs(M.lines) do + local should_display = true + if M.current_filter and M.current_filter ~= '' then + should_display = false + for _, cell in ipairs(row) do + if string.find(string.lower(cell), M.current_filter) then + should_display = true + break + end + end + end + + if should_display then + local formatted_row = {} + for i, cell in ipairs(row) do + local value = cell or '' + local padded_cell = value .. string.rep(' ', M.col_widths[i] - #value) + table.insert(formatted_row, padded_cell) + end + table.insert(display_lines, table.concat(formatted_row, string.rep(' ', M.spacing))) + end + end + + vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, display_lines) + + -- Clear any existing filter indicators + vim.api.nvim_buf_clear_namespace(bufnr, M.ns_filter, 0, -1) + + -- Add filter indicator if there's an active filter + if M.current_filter and M.current_filter ~= '' then + vim.api.nvim_buf_set_extmark(bufnr, M.ns_filter, 1, 0, { + virt_text = { { '🔍 Filter: ' .. M.current_filter .. ' ', 'Comment' } }, + virt_text_pos = 'overlay', + }) + end + + -- Add header highlighting and sort indicator + local pos = 0 + for i, width in ipairs(M.col_widths) do + -- Add header highlighting + vim.api.nvim_buf_set_extmark(bufnr, M.ns_headers, 0, pos, { + end_col = pos + width, + hl_group = 'Statement', + }) + + -- Add sort indicator if this is the sorted column + if M.sort_column == i then + local sort_indicator = M.sort_direction == 1 and '▲' or '▼' + local header_length = #M.headers[i] + vim.api.nvim_buf_set_extmark(bufnr, M.ns_sort, 0, pos + header_length, { + virt_text = { { ' ' .. sort_indicator, 'Special' } }, + virt_text_pos = 'overlay', + hl_mode = 'combine', + }) + end + + pos = pos + width + M.spacing + end + + -- Set buffer options + vim.api.nvim_set_option_value('buftype', 'nofile', { buf = bufnr }) + vim.api.nvim_set_option_value('bufhidden', 'wipe', { buf = bufnr }) + + -- Switch to the new buffer + vim.api.nvim_set_current_buf(bufnr) + + -- set no wrap + local winnr = vim.api.nvim_get_current_win() + vim.api.nvim_set_option_value('wrap', false, { win = winnr }) + + -- Set up keymaps + local opts = { noremap = true, silent = true } + vim.api.nvim_buf_set_keymap(bufnr, 'n', 'gs', [[lua require('user.tabular').sort_by_current_column()]], opts) + vim.api.nvim_buf_set_keymap(bufnr, 'n', '', [[lua require('user.tabular').filter_table()]], opts) + + -- Add numeric keymaps for column sorting + for i = 1, #M.headers do + vim.api.nvim_buf_set_keymap(bufnr, 'n', tostring(i), string.format([[lua require('user.tabular').sort_by_column(%d)]], i), opts) + end +end + +function M.sort_by_current_column() + local cursor = vim.api.nvim_win_get_cursor(0) + local col = cursor[2] + + -- Find which column the cursor is in based on column widths and spacing + local current_pos = 0 + local current_column = 1 + for i, width in ipairs(M.col_widths) do + local next_pos = current_pos + width + if i < #M.col_widths then + next_pos = next_pos + M.spacing + end + + if col >= current_pos and col < next_pos then + current_column = i + break + end + + current_pos = next_pos + end + + M.sort_by_column(current_column) +end + +function M.setup(opts) + opts = opts or {} + if opts.delimiter then + M.delimiter = opts.delimiter + end + + -- Create user commands + vim.api.nvim_create_user_command('TabularParse', function() + M.parse_buffer() + end, {}) +end + +return M diff --git a/nvim/.config/nvim/lua/user/utils.lua b/nvim/.config/nvim/lua/user/utils.lua index eda8916..acc6d80 100644 --- a/nvim/.config/nvim/lua/user/utils.lua +++ b/nvim/.config/nvim/lua/user/utils.lua @@ -1,9 +1,10 @@ +---@class Utils local M = {} ----Creates an augroup while clearing previous ---- @param name string The name of the augroup. ----@return number The augroup id -M.augroup = function(name) +---Creates an augroup while clearing previous autocmds +---@param name string The name of the augroup +---@return number augroup_id The augroup id +function M.augroup(name) return vim.api.nvim_create_augroup(name, { clear = true }) end @@ -37,98 +38,110 @@ function! ReplaceMotion(motion, text) endfunction ]] ----Execute a shell command and return its output ----@param cmd table The command to execute as a table where the first element is the command and the rest are arguments ----@param cwd? string The working directory to execute the command in (optional) ----@return table stdout The standard output as a table of lines ----@return number? ret The return code of the command ----@return table stderr The standard error as a table of lines -M.get_os_command_output = function(cmd, cwd) - local Job = require 'plenary.job' - if not cwd then - cwd = vim.fn.getcwd() +---Returns the current visual selection without leaving visual mode +---@return string[] lines Array of selected lines +function M.get_visual_selection_stay_in_visual() + local mode = vim.api.nvim_get_mode().mode + local opts = {} + -- \22 is an escaped version of + if mode == 'v' or mode == 'V' or mode == '\22' then + opts.type = mode end + return vim.fn.getregion(vim.fn.getpos 'v', vim.fn.getpos '.', opts) +end + +---Returns the current visual selection and exits visual mode +---@return string text The selected text +function M.get_visual_selection() + local esc = vim.api.nvim_replace_termcodes('', true, false, true) + vim.api.nvim_feedkeys(esc, 'x', false) + local vstart = vim.fn.getpos "'<" + local vend = vim.fn.getpos "'>" + return table.concat(vim.fn.getregion(vstart, vend), '\n') +end + +function M.get_os_command_output(cmd, cwd) if type(cmd) ~= 'table' then - M.pretty_print('cmd has to be a table', vim.log.leger.ERROR, [[🖥️]]) - return {}, -1, {} + M.pretty_print('cmd has to be a table', vim.log.levels.ERROR, '🖥️') + return '', -1, '' end + + local Job = require 'plenary.job' local command = table.remove(cmd, 1) local stderr = {} + + ---@diagnostic disable-next-line: missing-fields local stdout, ret = Job:new({ command = command, args = cmd, - cwd = cwd, + cwd = cwd or vim.fn.getcwd(), on_stderr = function(_, data) table.insert(stderr, data) end, }):sync() + return stdout, ret, stderr end ---- Pretty print using vim.notify ----@param message string: The message to print ----@param title? string: The title of the notification ----@param icon? string: The icon of the notification ----@param level? number: The log level of the notification (vim.log.levels.INFO by default) ----@param timeout? number: The timeout of the notification ----@return nil -M.pretty_print = function(message, title, icon, level, timeout) - if not icon then - icon = '' - end - if not title then - title = 'Neovim' - end - if not level then - level = vim.log.levels.INFO - end - if not timeout then - timeout = 3000 - end - vim.notify(message, level, { title = title, icon = icon, timeout = timeout }) +---Pretty print using vim.notify +---@param message string The message to print +---@param title? string The title of the notification +---@param icon? string The icon of the notification +---@param level? integer The log level (vim.log.levels) +---@param timeout? integer The timeout in milliseconds +function M.pretty_print(message, title, icon, level, timeout) + vim.notify(message, level or vim.log.levels.INFO, { + title = title or 'Neovim', + icon = icon or '', + timeout = timeout or 3000, + }) end +-- Precompute byte offsets for country code conversion +local COUNTRY_CODE_OFFSET = 127397 + ---Converts country code to emoji of the country flag ----@param country_iso string: The country code in 2 uppercase letters ----@return string: emoji of the country flag -M.country_os_to_emoji = function(country_iso) - local flag_icon = '' +---@param country_iso string The country code in 2 uppercase letters (e.g. "US", "GB") +---@return string emoji The country flag emoji +function M.country_os_to_emoji(country_iso) + local flag_icon = {} for i = 1, #country_iso do - local code_point = country_iso:byte(i) + 127397 + local code_point = country_iso:byte(i) + COUNTRY_CODE_OFFSET if code_point <= 0x7F then - flag_icon = flag_icon .. string.char(code_point) + table.insert(flag_icon, string.char(code_point)) elseif code_point <= 0x7FF then - flag_icon = flag_icon .. string.char(0xC0 + math.floor(code_point / 0x40), 0x80 + code_point % 0x40) + table.insert(flag_icon, string.char(0xC0 + math.floor(code_point / 0x40), 0x80 + code_point % 0x40)) elseif code_point <= 0xFFFF then - flag_icon = flag_icon .. string.char(0xE0 + math.floor(code_point / 0x1000), 0x80 + math.floor((code_point % 0x1000) / 0x40), 0x80 + code_point % 0x40) + table.insert(flag_icon, string.char(0xE0 + math.floor(code_point / 0x1000), 0x80 + math.floor((code_point % 0x1000) / 0x40), 0x80 + code_point % 0x40)) elseif code_point <= 0x10FFFF then - flag_icon = flag_icon - .. string.char( + table.insert( + flag_icon, + string.char( 0xF0 + math.floor(code_point / 0x40000), 0x80 + math.floor((code_point % 0x40000) / 0x1000), 0x80 + math.floor((code_point % 0x1000) / 0x40), 0x80 + code_point % 0x40 ) + ) end end - return flag_icon + return table.concat(flag_icon) end ---- Get the next index in a table after the current element ---- @param tbl table The table to search in ---- @param cur any The current element to find ---- @return number index The next index in the table (loops back to 1) -M.tbl_get_next = function(tbl, cur) - local idx = 1 +---Get the next index in a table after the current element +---@param tbl any[] The table to search in +---@param cur any The current element to find +---@return integer index The next index in the table (loops back to 1) +function M.tbl_get_next(tbl, cur) for i, v in ipairs(tbl) do if v == cur then - idx = i % #tbl + 1 - break + return i % #tbl + 1 end end - return idx + return 1 end +-- Static mappings M.filetype_to_extension = { bash = 'sh', javascript = 'js', @@ -158,28 +171,32 @@ M.filetype_to_command = { json = 'jq', } -M.random_emoji = function() - local emojis = { - '🤩', - '👻', - '😈', - '✨', - '👰', - '👑', - '💯', - '💖', - '🌒', - '🇮🇱', - '★', - '⚓️', - '🙉', - '☘️', - '🌍', - '🥨', - '🔥', - '🚀', - } - return emojis[math.random(#emojis)] +-- Cache emojis table +local EMOJIS = { + '🤩', + '👻', + '😈', + '✨', + '👰', + '👑', + '💯', + '💖', + '🌒', + '🇮🇱', + '★', + '⚓️', + '🙉', + '☘️', + '🌍', + '🥨', + '🔥', + '🚀', +} + +---Returns a random emoji from the predefined list +---@return string emoji The random emoji +function M.random_emoji() + return EMOJIS[math.random(#EMOJIS)] end return M diff --git a/nvim/.config/nvim/lua/user/winresizer.lua b/nvim/.config/nvim/lua/user/winresizer.lua new file mode 100644 index 0000000..80c1563 --- /dev/null +++ b/nvim/.config/nvim/lua/user/winresizer.lua @@ -0,0 +1,35 @@ +local M = { + jump_size = 2, + mappings = { + [''] = 'h', + [''] = 'j', + [''] = 'k', + [''] = 'l', + }, +} + +local function can_move_cursor(dir) + local from = vim.fn.winnr() + vim.cmd('wincmd ' .. dir) + local to = vim.fn.winnr() + vim.cmd(from .. 'wincmd w') + return from ~= to +end + +local function resize_window(dir, amount) + local cmd = dir:match '[hl]' and 'vertical resize' or 'resize' + local sign = can_move_cursor(dir) and '+' or '-' + vim.cmd(string.format('%s %s%d', cmd, sign, amount)) +end + +M.setup = function() + for key, dir in pairs(M.mappings) do + vim.keymap.set('n', key, function() + resize_window(dir, M.jump_size) + end, { desc = 'Resize window ' .. dir }) + end +end + +M.setup() + +return M diff --git a/nvim/.config/nvim/minimal_init.lua b/nvim/.config/nvim/minimal_init.lua index dde59a4..b43e858 100644 --- a/nvim/.config/nvim/minimal_init.lua +++ b/nvim/.config/nvim/minimal_init.lua @@ -1,179 +1,334 @@ -local function join_paths(...) - local path_sep = '/' - local result = table.concat({ ... }, path_sep) - return result -end - -local temp_dir = vim.uv.os_getenv 'TEMP' or '/tmp' -local package_root = join_paths(temp_dir, 'nvim', 'site', 'lazy') -local lazypath = join_paths(temp_dir, 'nvim', 'site') .. '/lazy/lazy.nvim' -if not vim.loop.fs_stat(lazypath) then - vim - .system({ - 'git', - 'clone', - '--filter=blob:none', - '--single-branch', - 'https://github.com/folke/lazy.nvim.git', - lazypath, - }, { text = true }) - :wait() -end -vim.opt.runtimepath:prepend(lazypath) - ----@diagnostic disable-next-line: global_usage -_G.load_config = function() - vim.lsp.set_log_level 'trace' - require('vim.lsp.log').set_format_func(vim.inspect) - local nvim_lsp = require 'lspconfig' - local on_attach = function(client, bufnr) - print('On Attach ' .. client.name) - local function buf_set_keymap(...) - vim.api.nvim_buf_set_keymap(bufnr, ...) +local map = vim.keymap.set +-- options {{{ +vim.cmd [[colorscheme sorbet]] +-- Performance: Defer filetype detection for faster startup (if not set elsewhere) +vim.loader.enable() + +-- UI/UX: Highlight on yank +vim.api.nvim_create_autocmd('TextYankPost', { + callback = function() + vim.highlight.on_yank { timeout = 200 } + end, + desc = 'Highlight selection on yank', +}) + +-- Jump to last edit position when reopening file +vim.api.nvim_create_autocmd('BufReadPost', { + callback = function() + local mark = vim.api.nvim_buf_get_mark(0, '"') + if mark[1] > 0 and mark[1] <= vim.api.nvim_buf_line_count(0) then + pcall(vim.api.nvim_win_set_cursor, 0, mark) end + end, + desc = 'Return to last edit pos after reopen', +}) - vim.api.nvim_set_option_value('omnifunc', 'v:lua.vim.lsp.omnifunc', { buf = bufnr }) - - -- Mappings. - local opts = { noremap = true, silent = true } - buf_set_keymap('n', 'gD', 'lua vim.lsp.buf.declaration()', opts) - buf_set_keymap('n', 'gd', 'lua vim.lsp.buf.definition()', opts) - buf_set_keymap('n', 'K', 'lua vim.lsp.buf.hover()', opts) - buf_set_keymap('n', 'gi', 'lua vim.lsp.buf.implementation()', opts) - buf_set_keymap('n', '', 'lua vim.lsp.buf.signature_help()', opts) - buf_set_keymap('n', 'wa', 'lua vim.lsp.buf.add_workspace_folder()', opts) - buf_set_keymap('n', 'wr', 'lua vim.lsp.buf.remove_workspace_folder()', opts) - buf_set_keymap('n', 'wl', 'lua print(vim.inspect(vim.lsp.buf.list_workspace_folders()))', opts) - buf_set_keymap('n', 'D', 'lua vim.lsp.buf.type_definition()', opts) - buf_set_keymap('n', 'rn', 'lua vim.lsp.buf.rename()', opts) - buf_set_keymap('n', 'gr', 'lua vim.lsp.buf.references()', opts) - buf_set_keymap('n', 'e', 'lua vim.diagnostic.open_float()', opts) - buf_set_keymap('n', '[d', 'lua vim.diagnostic.goto_prev()', opts) - buf_set_keymap('n', ']d', 'lua vim.diagnostic.goto_next()', opts) - buf_set_keymap('n', 'q', 'lua vim.diagnostic.setloclist()', opts) - buf_set_keymap('n', 'p', 'lua vim.lsp.buf.formatting()', opts) - end +vim.g.mapleader = ' ' +vim.o.title = true +vim.o.cursorcolumn = true +vim.o.cursorline = true +vim.opt.shortmess:append { c = true, l = false, q = false, S = false, C = true, I = true } +vim.o.list = true +vim.o.shada = [[!,'1000,s10000,h]] +vim.o.number = true +vim.o.relativenumber = true +vim.o.linebreak = true +vim.o.hlsearch = true +vim.o.incsearch = true +vim.o.inccommand = 'split' +vim.o.ignorecase = true +vim.o.smartcase = true +vim.o.autoread = true +vim.o.scrolloff = 4 +vim.o.sidescrolloff = 8 +vim.o.cmdheight = 1 +vim.o.hidden = true +vim.o.showmatch = true +vim.o.splitbelow = true +vim.o.splitright = true +vim.o.startofline = false +vim.o.confirm = true +vim.o.lazyredraw = false +vim.o.swapfile = false +vim.o.wildmenu = true +vim.opt.wildmode = { 'longest:full', 'full' } +vim.opt.completeopt = 'menu,menuone,noselect,noinsert,popup' +vim.o.previewheight = 15 +vim.o.laststatus = 3 +vim.o.showcmd = true +vim.o.mouse = 'a' +vim.o.undofile = true +vim.o.undolevels = 10000 +vim.o.textwidth = 80 +vim.opt.cpoptions:append '>' +vim.o.equalalways = true +vim.o.history = 10000 +vim.o.signcolumn = 'yes' +vim.o.updatetime = 300 +vim.opt.wildignore:append { '**/node_modules/**', '.hg', '.git', '.svn', '*.DS_Store', '*.pyc' } +vim.opt.path:append { '**' } +-- Folding {{{ +vim.o.foldenable = true +vim.opt.foldmethod = 'expr' +vim.opt.foldexpr = 'nvim_treesitter#foldexpr()' +vim.o.foldlevel = 999 +vim.o.foldlevelstart = 99 +vim.o.foldcolumn = '1' -- '0' is not bad +vim.o.foldtext = 'substitute(getline(v:foldstart),"\t",repeat(" ",&tabstop),"g")."...".trim(getline(v:foldend))' +-- }}} +vim.opt.formatoptions:append { + c = true, + j = true, + l = true, + o = true, + r = true, + t = true, +} +-- Indenting +vim.o.breakindent = true +vim.o.autoindent = true +vim.o.copyindent = true +vim.o.smartindent = true +vim.o.shiftwidth = 2 +vim.o.shiftround = true +vim.o.softtabstop = 2 +vim.o.tabstop = 2 +vim.o.smarttab = true +vim.o.expandtab = true +vim.opt.indentkeys:remove '0#' +vim.opt.indentkeys:remove '<:>' +-- }}} - -- load treesitter - require('nvim-treesitter.configs').setup { - highlight = { enable = true }, - } +-- keymaps {{{ +map('n', '', ':nohlsearch', { remap = false, silent = true, desc = 'Clear search highlighting' }) +map({ 'n', 'v' }, '0', '^', { remap = false, desc = 'Go to the first non-blank character' }) +-- Move view left or right +map('n', 'L', '5zl', { remap = false, desc = 'Move view to the right' }) +map('v', 'L', '$', { remap = false, desc = 'Move view to the right' }) +map('n', 'H', '5zh', { remap = false, desc = 'Move view to the left' }) +map('v', 'H', '0', { remap = false, desc = 'Move view to the left' }) - -- Set up completion - local cmp = require 'cmp' - cmp.setup { - snippet = { - expand = function(args) - require('luasnip').lsp_expand(args.body) -- For `luasnip` users. - end, - }, - window = { - completion = cmp.config.window.bordered(), - documentation = cmp.config.window.bordered(), - }, - mapping = cmp.mapping.preset.insert { - [''] = cmp.mapping.scroll_docs(-4), - [''] = cmp.mapping.scroll_docs(4), - [''] = cmp.mapping.complete(), - [''] = cmp.mapping.abort(), - [''] = cmp.mapping.confirm { select = true }, -- Accept currently selected item. Set `select` to `false` to only confirm explicitly selected items. - }, - sources = cmp.config.sources { - { name = 'nvim_lsp' }, - { name = 'luasnip' }, -- For luasnip users. - { name = 'cmp_tabnine', priority = 80 }, - }, - } - local tabnine = require 'cmp_tabnine.config' - tabnine:setup { - max_lines = 500, - max_num_results = 5, - sort = true, - } +-- indent/unindent visual mode selection with tab/shift+tab +map('v', '', '>gv', { desc = 'Indent selected text' }) +map('v', '', 'v', ':vnew', { remap = false, silent = true, desc = 'New buffer vertically split' }) +map('n', 's', ':new', { remap = false, silent = true, desc = 'New buffer horizontally split' }) +map('n', 'e', ':enew', { remap = false, silent = true, desc = 'New empty buffer' }) +map('n', '', 'h', { remap = true, desc = 'Go to Left Window' }) +map('n', '', 'j', { remap = true, desc = 'Go to Lower Window' }) +map('n', '', 'k', { remap = true, desc = 'Go to Upper Window' }) +map('n', '', 'l', { remap = true, desc = 'Go to Right Window' }) - -- Add the server that troubles you here - local name = 'terraformls' - -- local cmd = { 'node', yaml_install_path .. '/out/server/src/server.js', '--stdio' } - if not name then - print 'You have not defined a server name, please edit minimal_init.lua' - end - if not nvim_lsp[name].document_config.default_config.cmd then - print [[You have not defined a server default cmd for a server - that requires it please edit minimal_init.lua]] +map('n', ']q', ':cnextzz', { remap = false, silent = true }) +map('n', '[q', ':cprevzz', { remap = false, silent = true }) + +-- Terminal +map('t', '', [[]], { remap = false, desc = 'Exit terminal mode' }) + +-- Copy file path to clipboard +map('n', 'cfp', [[:let @+ = expand('%'):echo "Copied relative file path " . expand('%')]], { remap = false, silent = true }) +map('n', 'cfa', [[:let @+ = expand('%:p'):echo "Copied full file path " . expand('%:p')]], { remap = false, silent = true }) +map('n', 'cfd', [[:let @+ = expand('%:p:h'):echo "Copied file directory path " . expand('%:p:h')]], { remap = false, silent = true }) +map('n', 'cfn', [[:let @+ = expand('%:t'):echo "Copied file directory path " . expand('%:t')]], { remap = false, silent = true }) + +-- Copy and paste to/from system clipboard +map('v', 'cp', '"+y', { desc = 'Copy to system clipboard' }) +map('n', 'cP', '"+yy', { desc = 'Copy line to system clipboard' }) +map('n', 'cp', '"+y', { desc = 'Copy to system clipboard' }) +map('n', 'cv', '"+p', { desc = 'Paste from system clipboard' }) +map('n', '', 'ciw', { desc = 'Change inner word' }) +map('n', '', 'ciw') +-- Copy entire file to clipboard +map('n', 'Y', ':%y+', { remap = false, silent = true, desc = 'Copy buffer content to clipboard' }) + +-- Scrolling centralized +map('n', '', 'zz', { remap = false, desc = 'Scroll half page up and center' }) +map('n', '', 'zz', { remap = false, desc = 'Scroll half page down and center' }) + +-- Change working directory based on open file +map('n', 'cd', ':cd %:p:h:pwd', { remap = false, silent = true, desc = 'Change directory to current file' }) + +vim.api.nvim_create_user_command('DiffWithSaved', function() + -- Get start buffer + local start = vim.api.nvim_get_current_buf() + local filetype = vim.api.nvim_get_option_value('filetype', { buf = start }) + vim.cmd 'vnew | set buftype=nofile | read ++edit # | 0d_ | diffthis' + local scratch = vim.api.nvim_get_current_buf() + vim.api.nvim_set_option_value('filetype', filetype, { buf = scratch }) + vim.cmd 'wincmd p | diffthis' + + -- Map `q` for both buffers to exit diff view and delete scratch buffer + for _, buf in ipairs { scratch, start } do + map('n', 'q', function() + vim.cmd 'windo diffoff' + vim.api.nvim_buf_delete(scratch, { force = true }) + vim.keymap.del('n', 'q', { buffer = start }) + end, { buffer = buf }) end +end, {}) - nvim_lsp[name].setup { - cmd = { '/Users/mavni/.local/share/nvim/mason/bin/terraform-ls', 'serve' }, - on_attach = on_attach, - capabilities = capabilities, - -- on_init = function() - -- require('user.select-schema').get_client() - -- end, - -- settings = { - -- redhat = { telemetry = { enabled = false } }, - -- yaml = { - -- validate = true, - -- format = { enable = true }, - -- hover = true, - -- trace = { server = 'debug' }, - -- completion = true, - -- schemaStore = { - -- enable = true, - -- url = 'https://www.schemastore.org/api/json/catalog.json', - -- }, - -- schemas = { - -- kubernetes = { - -- '*role*.y*ml', - -- 'deploy.y*ml', - -- 'deployment.y*ml', - -- 'ingress.y*ml', - -- 'kubectl-edit-*', - -- 'pdb.y*ml', - -- 'pod.y*ml', - -- 'hpa.y*ml', - -- 'rbac.y*ml', - -- 'service.y*ml', - -- 'service*account.y*ml', - -- 'storageclass.y*ml', - -- 'svc.y*ml', - -- }, - -- }, - -- }, - -- }, - } +map('n', 'ds', ':DiffWithSaved', { remap = false, silent = true }) +-- }}} - print [[You can find your log at $HOME/.cache/nvim/lsp.log. Please paste in a github issue under a details tag as described in the issue template.]] -end - -require('lazy').setup({ - 'habamax/vim-habamax', - 'neovim/nvim-lspconfig', - { - 'hrsh7th/nvim-cmp', - dependencies = { - 'hrsh7th/cmp-nvim-lsp', - { 'tzachar/cmp-tabnine', build = './install.sh' }, - 'saadparwaiz1/cmp_luasnip', - }, +-- packages {{{ +vim.pack.add { + 'https://github.com/ibhagwan/fzf-lua', + 'https://github.com/nvim-treesitter/nvim-treesitter', + 'https://github.com/folke/lazydev.nvim', + 'https://github.com/echasnovski/mini.surround', + 'https://github.com/nvim-lualine/lualine.nvim', +} +require('mini.surround').setup { + mappings = { + add = 'ys', + delete = 'ds', + find = '', + find_left = '', + highlight = '', + replace = 'cs', + update_n_lines = '', + + -- Add this only if you don't want to use extended mappings + suffix_last = '', + suffix_next = '', }, - 'L3MON4D3/LuaSnip', - 'nvim-treesitter/nvim-treesitter', - config = function() - local configs = require 'nvim-treesitter.configs' - configs.setup { - ensure_installed = 'all', - highlight = { - enable = true, - additional_vim_regex_highlighting = false, + search_method = 'cover_or_next', +} +require('lualine').setup { + sections = { + lualine_a = { 'mode' }, + lualine_b = { 'branch', 'diff', 'diagnostics' }, + lualine_c = { 'filename' }, + lualine_x = { + { + function() + local clients = vim.lsp.get_clients { bufnr = 0 } + if not next(clients) then + return 'No Active Lsp' + end + return 'LSP: ' + .. table.concat( + vim.tbl_map(function(client) + return client.name + end, clients), + ', ' + ) + end, + icon = { ' ' }, }, - } + 'fileformat', + 'filetype', + }, + lualine_y = { 'progress' }, + lualine_z = { 'location' }, + }, +} +require('lazydev').setup { path = '${3rd}/luv/library', words = { 'vim%.uv' } } +-- }}} + +-- treesitter {{{ +local configs = require 'nvim-treesitter.configs' +---@diagnostic disable: missing-fields +configs.setup { + highlight = { enable = true }, + ensure_installed = { + 'bash', + 'comment', + 'diff', + 'embedded_template', + 'javascript', + 'json', + 'lua', + 'luadoc', + 'markdown', + 'markdown_inline', + 'python', + 'query', + 'regex', + 'terraform', + 'tsx', + 'typescript', + 'vim', + 'vimdoc', + 'xml', + 'yaml', + }, + matchup = { enable = true }, + indent = { enable = true, disable = { 'yaml' } }, +} +-- }}} + +-- fzf {{{ +require('fzf-lua').setup { + opts = { + 'default-title', + files = { git_icons = true }, + oldfiles = { cwd_only = true, include_current_session = true }, + grep = { hidden = true }, + keymap = { fzf = { ['ctrl-q'] = 'select-all+accept' } }, + }, +} +vim.keymap.set('n', '', ':FzfLua files', { desc = 'FzfLua files' }) +vim.keymap.set('n', '', ':FzfLua buffers', { desc = 'FzfLua buffers' }) +vim.keymap.set('n', 'hh', ':FzfLua help_tags', { desc = 'FzfLua help tags' }) +vim.keymap.set('n', 'i', ':FzfLua oldfiles', { desc = 'FzfLua old files' }) +vim.keymap.set('n', '/', function() + require('fzf-lua').live_grep { + multiprocess = true, + rg_opts = [=[--column --line-number --hidden --no-heading --color=always --smart-case --max-columns=4096 -g '!.git' -e]=], + } +end, { desc = 'FzfLua live grep' }) +-- }}} + +-- lsp {{{ +local lsp_au = vim.api.nvim_create_augroup('my.lsp', { clear = true }) +vim.api.nvim_create_autocmd('LspAttach', { -- {{{ + group = lsp_au, + callback = function(args) + vim.lsp.set_log_level 'trace' + require('vim.lsp.log').set_format_func(vim.inspect) + local client = assert(vim.lsp.get_client_by_id(args.data.client_id)) + if client:supports_method 'textDocument/implementation' then + vim.keymap.set('n', 'gi', vim.lsp.buf.implementation, { + buffer = args.buf, + desc = 'Go to implementation', + }) + end + + -- Enable auto-completion. Note: Use CTRL-Y to select an item. |complete_CTRL-Y| + if client:supports_method 'textDocument/completion' then + vim.lsp.completion.enable(true, client.id, args.buf, { autotrigger = true }) + end + + -- Auto-format ("lint") on save. + -- Usually not needed if server supports "textDocument/willSaveWaitUntil". + if not client:supports_method 'textDocument/willSaveWaitUntil' and client:supports_method 'textDocument/formatting' then + vim.api.nvim_create_autocmd('BufWritePre', { + group = lsp_au, + buffer = args.buf, + callback = function() + vim.lsp.buf.format { bufnr = args.buf, id = client.id, timeout_ms = 1000 } + end, + }) + end end, -}, { - root = package_root, -}) -_G.load_config() +}) -- }}} -vim.cmd [[colorscheme habamax]] +vim.lsp.config('lua_ls', { + cmd = { 'lua-language-server' }, + filetypes = { 'lua' }, + root_markers = { + '.luarc.json', + '.luarc.jsonc', + '.luacheckrc', + '.stylua.toml', + 'stylua.toml', + 'selene.toml', + 'selene.yml', + '.git', + }, +}) +vim.lsp.enable { 'lua_ls' } +-- }}} diff --git a/nvim/.config/nvim/scripts/minimal_init.vim b/nvim/.config/nvim/scripts/minimal_init.vim new file mode 100644 index 0000000..305e142 --- /dev/null +++ b/nvim/.config/nvim/scripts/minimal_init.vim @@ -0,0 +1,4 @@ +set rtp+=./nvim/.config/nvim/ +set rtp+=../plenary.nvim/ + +runtime! plugin/plenary.vim diff --git a/nvim/.config/nvim/snippets/package.json b/nvim/.config/nvim/snippets/package.json index 471d076..9d74604 100644 --- a/nvim/.config/nvim/snippets/package.json +++ b/nvim/.config/nvim/snippets/package.json @@ -14,6 +14,12 @@ "Jenkinsfile" ], "path": "./groovy.json" + }, + { + "language": [ + "yaml" + ], + "path": "./yaml.json" } ] } diff --git a/nvim/.config/nvim/snippets/yaml.json b/nvim/.config/nvim/snippets/yaml.json new file mode 100644 index 0000000..1714f26 --- /dev/null +++ b/nvim/.config/nvim/snippets/yaml.json @@ -0,0 +1,7 @@ +{ + "modeline": { + "prefix": "mod", + "body": ["# yaml-language-server: $$schema=${1:file:///}"], + "description": "Create an autocmd" + } +} diff --git a/nvim/.config/nvim/syntax/nginx.vim b/nvim/.config/nvim/syntax/nginx.vim deleted file mode 100644 index a722f96..0000000 --- a/nvim/.config/nvim/syntax/nginx.vim +++ /dev/null @@ -1,664 +0,0 @@ -" Vim syntax file -" Language: nginx.conf - -if exists('b:current_syntax') - finish -end - -setlocal iskeyword+=. -setlocal iskeyword+=/ -setlocal iskeyword+=: - -syn match ngxVariable '\$\w\w*' -syn match ngxVariableBlock '\$\w\w*' contained -syn match ngxVariableString '\$\w\w*' contained -syn region ngxBlock start=+^+ end=+{+ contains=ngxComment,ngxDirectiveBlock,ngxVariableBlock,ngxString oneline -syn region ngxString start=+"+ end=+"+ skip=+\\\\\|\\"+ contains=ngxVariableString oneline -syn region ngxString start=+'+ end=+'+ skip=+\\\\\|\\'+ contains=ngxVariableString oneline -syn match ngxComment '\s*#.*$' - -syn keyword ngxBoolean on -syn keyword ngxBoolean off - -syn keyword ngxDirectiveBlock http contained -syn keyword ngxDirectiveBlock mail contained -syn keyword ngxDirectiveBlock events contained -syn keyword ngxDirectiveBlock server contained -syn keyword ngxDirectiveBlock types contained -syn keyword ngxDirectiveBlock location contained -syn keyword ngxDirectiveBlock upstream contained -syn keyword ngxDirectiveBlock charset_map contained -syn keyword ngxDirectiveBlock limit_except contained -syn keyword ngxDirectiveBlock if contained -syn keyword ngxDirectiveBlock geo contained -syn keyword ngxDirectiveBlock map contained - -syn keyword ngxDirectiveImportant include -syn keyword ngxDirectiveImportant root -syn keyword ngxDirectiveImportant server -syn keyword ngxDirectiveImportant server_name -syn keyword ngxDirectiveImportant listen -syn keyword ngxDirectiveImportant internal -syn keyword ngxDirectiveImportant proxy_pass -syn keyword ngxDirectiveImportant memcached_pass -syn keyword ngxDirectiveImportant fastcgi_pass -syn keyword ngxDirectiveImportant try_files - -syn keyword ngxDirectiveControl break -syn keyword ngxDirectiveControl return -syn keyword ngxDirectiveControl rewrite -syn keyword ngxDirectiveControl set - -syn keyword ngxDirectiveError error_page -syn keyword ngxDirectiveError post_action - -syn keyword ngxDirectiveDeprecated connections -syn keyword ngxDirectiveDeprecated imap -syn keyword ngxDirectiveDeprecated open_file_cache_retest -syn keyword ngxDirectiveDeprecated optimize_server_names -syn keyword ngxDirectiveDeprecated satisfy_any - -syn keyword ngxDirective accept_mutex -syn keyword ngxDirective accept_mutex_delay -syn keyword ngxDirective access_log -syn keyword ngxDirective add_after_body -syn keyword ngxDirective add_before_body -syn keyword ngxDirective add_header -syn keyword ngxDirective addition_types -syn keyword ngxDirective aio -syn keyword ngxDirective alias -syn keyword ngxDirective allow -syn keyword ngxDirective ancient_browser -syn keyword ngxDirective ancient_browser_value -syn keyword ngxDirective auth_basic -syn keyword ngxDirective auth_basic_user_file -syn keyword ngxDirective auth_http -syn keyword ngxDirective auth_http_header -syn keyword ngxDirective auth_http_timeout -syn keyword ngxDirective autoindex -syn keyword ngxDirective autoindex_exact_size -syn keyword ngxDirective autoindex_localtime -syn keyword ngxDirective charset -syn keyword ngxDirective charset_types -syn keyword ngxDirective client_body_buffer_size -syn keyword ngxDirective client_body_in_file_only -syn keyword ngxDirective client_body_in_single_buffer -syn keyword ngxDirective client_body_temp_path -syn keyword ngxDirective client_body_timeout -syn keyword ngxDirective client_header_buffer_size -syn keyword ngxDirective client_header_timeout -syn keyword ngxDirective client_max_body_size -syn keyword ngxDirective connection_pool_size -syn keyword ngxDirective create_full_put_path -syn keyword ngxDirective daemon -syn keyword ngxDirective dav_access -syn keyword ngxDirective dav_methods -syn keyword ngxDirective debug_connection -syn keyword ngxDirective debug_points -syn keyword ngxDirective default_type -syn keyword ngxDirective degradation -syn keyword ngxDirective degrade -syn keyword ngxDirective deny -syn keyword ngxDirective devpoll_changes -syn keyword ngxDirective devpoll_events -syn keyword ngxDirective directio -syn keyword ngxDirective directio_alignment -syn keyword ngxDirective empty_gif -syn keyword ngxDirective env -syn keyword ngxDirective epoll_events -syn keyword ngxDirective error_log -syn keyword ngxDirective eventport_events -syn keyword ngxDirective expires -syn keyword ngxDirective fastcgi_bind -syn keyword ngxDirective fastcgi_buffer_size -syn keyword ngxDirective fastcgi_buffers -syn keyword ngxDirective fastcgi_busy_buffers_size -syn keyword ngxDirective fastcgi_cache -syn keyword ngxDirective fastcgi_cache_key -syn keyword ngxDirective fastcgi_cache_methods -syn keyword ngxDirective fastcgi_cache_min_uses -syn keyword ngxDirective fastcgi_cache_path -syn keyword ngxDirective fastcgi_cache_use_stale -syn keyword ngxDirective fastcgi_cache_valid -syn keyword ngxDirective fastcgi_catch_stderr -syn keyword ngxDirective fastcgi_connect_timeout -syn keyword ngxDirective fastcgi_hide_header -syn keyword ngxDirective fastcgi_ignore_client_abort -syn keyword ngxDirective fastcgi_ignore_headers -syn keyword ngxDirective fastcgi_index -syn keyword ngxDirective fastcgi_intercept_errors -syn keyword ngxDirective fastcgi_max_temp_file_size -syn keyword ngxDirective fastcgi_next_upstream -syn keyword ngxDirective fastcgi_param -syn keyword ngxDirective fastcgi_pass_header -syn keyword ngxDirective fastcgi_pass_request_body -syn keyword ngxDirective fastcgi_pass_request_headers -syn keyword ngxDirective fastcgi_read_timeout -syn keyword ngxDirective fastcgi_send_lowat -syn keyword ngxDirective fastcgi_send_timeout -syn keyword ngxDirective fastcgi_split_path_info -syn keyword ngxDirective fastcgi_store -syn keyword ngxDirective fastcgi_store_access -syn keyword ngxDirective fastcgi_temp_file_write_size -syn keyword ngxDirective fastcgi_temp_path -syn keyword ngxDirective fastcgi_upstream_fail_timeout -syn keyword ngxDirective fastcgi_upstream_max_fails -syn keyword ngxDirective flv -syn keyword ngxDirective geoip_city -syn keyword ngxDirective geoip_country -syn keyword ngxDirective google_perftools_profiles -syn keyword ngxDirective gzip -syn keyword ngxDirective gzip_buffers -syn keyword ngxDirective gzip_comp_level -syn keyword ngxDirective gzip_disable -syn keyword ngxDirective gzip_hash -syn keyword ngxDirective gzip_http_version -syn keyword ngxDirective gzip_min_length -syn keyword ngxDirective gzip_no_buffer -syn keyword ngxDirective gzip_proxied -syn keyword ngxDirective gzip_static -syn keyword ngxDirective gzip_types -syn keyword ngxDirective gzip_vary -syn keyword ngxDirective gzip_window -syn keyword ngxDirective if_modified_since -syn keyword ngxDirective ignore_invalid_headers -syn keyword ngxDirective image_filter -syn keyword ngxDirective image_filter_buffer -syn keyword ngxDirective image_filter_jpeg_quality -syn keyword ngxDirective image_filter_transparency -syn keyword ngxDirective imap_auth -syn keyword ngxDirective imap_capabilities -syn keyword ngxDirective imap_client_buffer -syn keyword ngxDirective index -syn keyword ngxDirective ip_hash -syn keyword ngxDirective keepalive_requests -syn keyword ngxDirective keepalive_timeout -syn keyword ngxDirective kqueue_changes -syn keyword ngxDirective kqueue_events -syn keyword ngxDirective large_client_header_buffers -syn keyword ngxDirective limit_conn -syn keyword ngxDirective limit_conn_log_level -syn keyword ngxDirective limit_rate -syn keyword ngxDirective limit_rate_after -syn keyword ngxDirective limit_req -syn keyword ngxDirective limit_req_log_level -syn keyword ngxDirective limit_req_zone -syn keyword ngxDirective limit_zone -syn keyword ngxDirective lingering_time -syn keyword ngxDirective lingering_timeout -syn keyword ngxDirective lock_file -syn keyword ngxDirective log_format -syn keyword ngxDirective log_not_found -syn keyword ngxDirective log_subrequest -syn keyword ngxDirective map_hash_bucket_size -syn keyword ngxDirective map_hash_max_size -syn keyword ngxDirective master_process -syn keyword ngxDirective memcached_bind -syn keyword ngxDirective memcached_buffer_size -syn keyword ngxDirective memcached_connect_timeout -syn keyword ngxDirective memcached_next_upstream -syn keyword ngxDirective memcached_read_timeout -syn keyword ngxDirective memcached_send_timeout -syn keyword ngxDirective memcached_upstream_fail_timeout -syn keyword ngxDirective memcached_upstream_max_fails -syn keyword ngxDirective merge_slashes -syn keyword ngxDirective min_delete_depth -syn keyword ngxDirective modern_browser -syn keyword ngxDirective modern_browser_value -syn keyword ngxDirective msie_padding -syn keyword ngxDirective msie_refresh -syn keyword ngxDirective multi_accept -syn keyword ngxDirective open_file_cache -syn keyword ngxDirective open_file_cache_errors -syn keyword ngxDirective open_file_cache_events -syn keyword ngxDirective open_file_cache_min_uses -syn keyword ngxDirective open_file_cache_valid -syn keyword ngxDirective open_log_file_cache -syn keyword ngxDirective output_buffers -syn keyword ngxDirective override_charset -syn keyword ngxDirective perl -syn keyword ngxDirective perl_modules -syn keyword ngxDirective perl_require -syn keyword ngxDirective perl_set -syn keyword ngxDirective pid -syn keyword ngxDirective pop3_auth -syn keyword ngxDirective pop3_capabilities -syn keyword ngxDirective port_in_redirect -syn keyword ngxDirective postpone_gzipping -syn keyword ngxDirective postpone_output -syn keyword ngxDirective protocol -syn keyword ngxDirective proxy -syn keyword ngxDirective proxy_bind -syn keyword ngxDirective proxy_buffer -syn keyword ngxDirective proxy_buffer_size -syn keyword ngxDirective proxy_buffering -syn keyword ngxDirective proxy_buffers -syn keyword ngxDirective proxy_busy_buffers_size -syn keyword ngxDirective proxy_cache -syn keyword ngxDirective proxy_cache_key -syn keyword ngxDirective proxy_cache_methods -syn keyword ngxDirective proxy_cache_min_uses -syn keyword ngxDirective proxy_cache_path -syn keyword ngxDirective proxy_cache_use_stale -syn keyword ngxDirective proxy_cache_valid -syn keyword ngxDirective proxy_connect_timeout -syn keyword ngxDirective proxy_headers_hash_bucket_size -syn keyword ngxDirective proxy_headers_hash_max_size -syn keyword ngxDirective proxy_hide_header -syn keyword ngxDirective proxy_ignore_client_abort -syn keyword ngxDirective proxy_ignore_headers -syn keyword ngxDirective proxy_intercept_errors -syn keyword ngxDirective proxy_max_temp_file_size -syn keyword ngxDirective proxy_method -syn keyword ngxDirective proxy_next_upstream -syn keyword ngxDirective proxy_pass_error_message -syn keyword ngxDirective proxy_pass_header -syn keyword ngxDirective proxy_pass_request_body -syn keyword ngxDirective proxy_pass_request_headers -syn keyword ngxDirective proxy_read_timeout -syn keyword ngxDirective proxy_redirect -syn keyword ngxDirective proxy_send_lowat -syn keyword ngxDirective proxy_send_timeout -syn keyword ngxDirective proxy_set_body -syn keyword ngxDirective proxy_set_header -syn keyword ngxDirective proxy_ssl_session_reuse -syn keyword ngxDirective proxy_store -syn keyword ngxDirective proxy_store_access -syn keyword ngxDirective proxy_temp_file_write_size -syn keyword ngxDirective proxy_temp_path -syn keyword ngxDirective proxy_timeout -syn keyword ngxDirective proxy_upstream_fail_timeout -syn keyword ngxDirective proxy_upstream_max_fails -syn keyword ngxDirective random_index -syn keyword ngxDirective read_ahead -syn keyword ngxDirective real_ip_header -syn keyword ngxDirective recursive_error_pages -syn keyword ngxDirective request_pool_size -syn keyword ngxDirective reset_timedout_connection -syn keyword ngxDirective resolver -syn keyword ngxDirective resolver_timeout -syn keyword ngxDirective rewrite_log -syn keyword ngxDirective rtsig_overflow_events -syn keyword ngxDirective rtsig_overflow_test -syn keyword ngxDirective rtsig_overflow_threshold -syn keyword ngxDirective rtsig_signo -syn keyword ngxDirective satisfy -syn keyword ngxDirective secure_link_secret -syn keyword ngxDirective send_lowat -syn keyword ngxDirective send_timeout -syn keyword ngxDirective sendfile -syn keyword ngxDirective sendfile_max_chunk -syn keyword ngxDirective server_name_in_redirect -syn keyword ngxDirective server_names_hash_bucket_size -syn keyword ngxDirective server_names_hash_max_size -syn keyword ngxDirective server_tokens -syn keyword ngxDirective set_real_ip_from -syn keyword ngxDirective smtp_auth -syn keyword ngxDirective smtp_capabilities -syn keyword ngxDirective smtp_client_buffer -syn keyword ngxDirective smtp_greeting_delay -syn keyword ngxDirective so_keepalive -syn keyword ngxDirective source_charset -syn keyword ngxDirective ssi -syn keyword ngxDirective ssi_ignore_recycled_buffers -syn keyword ngxDirective ssi_min_file_chunk -syn keyword ngxDirective ssi_silent_errors -syn keyword ngxDirective ssi_types -syn keyword ngxDirective ssi_value_length -syn keyword ngxDirective ssl -syn keyword ngxDirective ssl_certificate -syn keyword ngxDirective ssl_certificate_key -syn keyword ngxDirective ssl_ciphers -syn keyword ngxDirective ssl_client_certificate -syn keyword ngxDirective ssl_crl -syn keyword ngxDirective ssl_dhparam -syn keyword ngxDirective ssl_engine -syn keyword ngxDirective ssl_prefer_server_ciphers -syn keyword ngxDirective ssl_protocols -syn keyword ngxDirective ssl_session_cache -syn keyword ngxDirective ssl_session_timeout -syn keyword ngxDirective ssl_verify_client -syn keyword ngxDirective ssl_verify_depth -syn keyword ngxDirective starttls -syn keyword ngxDirective stub_status -syn keyword ngxDirective sub_filter -syn keyword ngxDirective sub_filter_once -syn keyword ngxDirective sub_filter_types -syn keyword ngxDirective tcp_nodelay -syn keyword ngxDirective tcp_nopush -syn keyword ngxDirective thread_stack_size -syn keyword ngxDirective timeout -syn keyword ngxDirective timer_resolution -syn keyword ngxDirective types_hash_bucket_size -syn keyword ngxDirective types_hash_max_size -syn keyword ngxDirective underscores_in_headers -syn keyword ngxDirective uninitialized_variable_warn -syn keyword ngxDirective use -syn keyword ngxDirective user -syn keyword ngxDirective userid -syn keyword ngxDirective userid_domain -syn keyword ngxDirective userid_expires -syn keyword ngxDirective userid_mark -syn keyword ngxDirective userid_name -syn keyword ngxDirective userid_p3p -syn keyword ngxDirective userid_path -syn keyword ngxDirective userid_service -syn keyword ngxDirective valid_referers -syn keyword ngxDirective variables_hash_bucket_size -syn keyword ngxDirective variables_hash_max_size -syn keyword ngxDirective worker_connections -syn keyword ngxDirective worker_cpu_affinity -syn keyword ngxDirective worker_priority -syn keyword ngxDirective worker_processes -syn keyword ngxDirective worker_rlimit_core -syn keyword ngxDirective worker_rlimit_nofile -syn keyword ngxDirective worker_rlimit_sigpending -syn keyword ngxDirective worker_threads -syn keyword ngxDirective working_directory -syn keyword ngxDirective xclient -syn keyword ngxDirective xml_entities -syn keyword ngxDirective xslt_stylesheet -syn keyword ngxDirective xslt_types - -" 3rd party module list: -" http://wiki.nginx.org/Nginx3rdPartyModules - -" Accept Language Module -" Parses the Accept-Language header and gives the most suitable locale from a list of supported locales. -syn keyword ngxDirectiveThirdParty set_from_accept_language - -" Access Key Module -" Denies access unless the request URL contains an access key. -syn keyword ngxDirectiveThirdParty accesskey -syn keyword ngxDirectiveThirdParty accesskey_arg -syn keyword ngxDirectiveThirdParty accesskey_hashmethod -syn keyword ngxDirectiveThirdParty accesskey_signature - -" Auth PAM Module -" HTTP Basic Authentication using PAM. -syn keyword ngxDirectiveThirdParty auth_pam -syn keyword ngxDirectiveThirdParty auth_pam_service_name - -" Cache Purge Module -" Module adding ability to purge content from FastCGI and proxy caches. -syn keyword ngxDirectiveThirdParty fastcgi_cache_purge -syn keyword ngxDirectiveThirdParty proxy_cache_purge - -" Chunkin Module -" HTTP 1.1 chunked-encoding request body support for Nginx. -syn keyword ngxDirectiveThirdParty chunkin -syn keyword ngxDirectiveThirdParty chunkin_keepalive -syn keyword ngxDirectiveThirdParty chunkin_max_chunks_per_buf -syn keyword ngxDirectiveThirdParty chunkin_resume - -" Circle GIF Module -" Generates simple circle images with the colors and size specified in the URL. -syn keyword ngxDirectiveThirdParty circle_gif -syn keyword ngxDirectiveThirdParty circle_gif_max_radius -syn keyword ngxDirectiveThirdParty circle_gif_min_radius -syn keyword ngxDirectiveThirdParty circle_gif_step_radius - -" Drizzle Module -" Make nginx talk directly to mysql, drizzle, and sqlite3 by libdrizzle. -syn keyword ngxDirectiveThirdParty drizzle_connect_timeout -syn keyword ngxDirectiveThirdParty drizzle_dbname -syn keyword ngxDirectiveThirdParty drizzle_keepalive -syn keyword ngxDirectiveThirdParty drizzle_module_header -syn keyword ngxDirectiveThirdParty drizzle_pass -syn keyword ngxDirectiveThirdParty drizzle_query -syn keyword ngxDirectiveThirdParty drizzle_recv_cols_timeout -syn keyword ngxDirectiveThirdParty drizzle_recv_rows_timeout -syn keyword ngxDirectiveThirdParty drizzle_send_query_timeout -syn keyword ngxDirectiveThirdParty drizzle_server - -" Echo Module -" Brings 'echo', 'sleep', 'time', 'exec' and more shell-style goodies to Nginx config file. -syn keyword ngxDirectiveThirdParty echo -syn keyword ngxDirectiveThirdParty echo_after_body -syn keyword ngxDirectiveThirdParty echo_before_body -syn keyword ngxDirectiveThirdParty echo_blocking_sleep -syn keyword ngxDirectiveThirdParty echo_duplicate -syn keyword ngxDirectiveThirdParty echo_end -syn keyword ngxDirectiveThirdParty echo_exec -syn keyword ngxDirectiveThirdParty echo_flush -syn keyword ngxDirectiveThirdParty echo_foreach_split -syn keyword ngxDirectiveThirdParty echo_location -syn keyword ngxDirectiveThirdParty echo_location_async -syn keyword ngxDirectiveThirdParty echo_read_request_body -syn keyword ngxDirectiveThirdParty echo_request_body -syn keyword ngxDirectiveThirdParty echo_reset_timer -syn keyword ngxDirectiveThirdParty echo_sleep -syn keyword ngxDirectiveThirdParty echo_subrequest -syn keyword ngxDirectiveThirdParty echo_subrequest_async - -" Events Module -" Privides options for start/stop events. -syn keyword ngxDirectiveThirdParty on_start -syn keyword ngxDirectiveThirdParty on_stop - -" EY Balancer Module -" Adds a request queue to Nginx that allows the limiting of concurrent requests passed to the upstream. -syn keyword ngxDirectiveThirdParty max_connections -syn keyword ngxDirectiveThirdParty max_connections_max_queue_length -syn keyword ngxDirectiveThirdParty max_connections_queue_timeout - -" Fancy Indexes Module -" Like the built-in autoindex module, but fancier. -syn keyword ngxDirectiveThirdParty fancyindex -syn keyword ngxDirectiveThirdParty fancyindex_exact_size -syn keyword ngxDirectiveThirdParty fancyindex_footer -syn keyword ngxDirectiveThirdParty fancyindex_header -syn keyword ngxDirectiveThirdParty fancyindex_localtime -syn keyword ngxDirectiveThirdParty fancyindex_readme -syn keyword ngxDirectiveThirdParty fancyindex_readme_mode - -" GeoIP Module (DEPRECATED) -" Country code lookups via the MaxMind GeoIP API. -syn keyword ngxDirectiveThirdParty geoip_country_file - -" Headers More Module -" Set and clear input and output headers...more than "add"! -syn keyword ngxDirectiveThirdParty more_clear_headers -syn keyword ngxDirectiveThirdParty more_clear_input_headers -syn keyword ngxDirectiveThirdParty more_set_headers -syn keyword ngxDirectiveThirdParty more_set_input_headers - -" HTTP Push Module -" Turn Nginx into an adept long-polling HTTP Push (Comet) server. -syn keyword ngxDirectiveThirdParty push_buffer_size -syn keyword ngxDirectiveThirdParty push_listener -syn keyword ngxDirectiveThirdParty push_message_timeout -syn keyword ngxDirectiveThirdParty push_queue_messages -syn keyword ngxDirectiveThirdParty push_sender - -" HTTP Redis Module > -" Redis support.> -syn keyword ngxDirectiveThirdParty redis_bind -syn keyword ngxDirectiveThirdParty redis_buffer_size -syn keyword ngxDirectiveThirdParty redis_connect_timeout -syn keyword ngxDirectiveThirdParty redis_next_upstream -syn keyword ngxDirectiveThirdParty redis_pass -syn keyword ngxDirectiveThirdParty redis_read_timeout -syn keyword ngxDirectiveThirdParty redis_send_timeout - -" HTTP JavaScript Module -" Embedding SpiderMonkey. Nearly full port on Perl module. -syn keyword ngxDirectiveThirdParty js -syn keyword ngxDirectiveThirdParty js_filter -syn keyword ngxDirectiveThirdParty js_filter_types -syn keyword ngxDirectiveThirdParty js_load -syn keyword ngxDirectiveThirdParty js_maxmem -syn keyword ngxDirectiveThirdParty js_require -syn keyword ngxDirectiveThirdParty js_set -syn keyword ngxDirectiveThirdParty js_utf8 - -" Log Request Speed -" Log the time it took to process each request. -syn keyword ngxDirectiveThirdParty log_request_speed_filter -syn keyword ngxDirectiveThirdParty log_request_speed_filter_timeout - -" Memc Module -" An extended version of the standard memcached module that supports set, add, delete, and many more memcached commands. -syn keyword ngxDirectiveThirdParty memc_buffer_size -syn keyword ngxDirectiveThirdParty memc_cmds_allowed -syn keyword ngxDirectiveThirdParty memc_connect_timeout -syn keyword ngxDirectiveThirdParty memc_flags_to_last_modified -syn keyword ngxDirectiveThirdParty memc_next_upstream -syn keyword ngxDirectiveThirdParty memc_pass -syn keyword ngxDirectiveThirdParty memc_read_timeout -syn keyword ngxDirectiveThirdParty memc_send_timeout -syn keyword ngxDirectiveThirdParty memc_upstream_fail_timeout -syn keyword ngxDirectiveThirdParty memc_upstream_max_fails - -" Mogilefs Module -" Implements a MogileFS client, provides a replace to the Perlbal reverse proxy of the original MogileFS. -syn keyword ngxDirectiveThirdParty mogilefs_connect_timeout -syn keyword ngxDirectiveThirdParty mogilefs_domain -syn keyword ngxDirectiveThirdParty mogilefs_methods -syn keyword ngxDirectiveThirdParty mogilefs_noverify -syn keyword ngxDirectiveThirdParty mogilefs_pass -syn keyword ngxDirectiveThirdParty mogilefs_read_timeout -syn keyword ngxDirectiveThirdParty mogilefs_send_timeout -syn keyword ngxDirectiveThirdParty mogilefs_tracker - -" MP4 Streaming Lite Module -" Will seek to a certain time within H.264/MP4 files when provided with a 'start' parameter in the URL. -syn keyword ngxDirectiveThirdParty mp4 - -" Nginx Notice Module -" Serve static file to POST requests. -syn keyword ngxDirectiveThirdParty notice -syn keyword ngxDirectiveThirdParty notice_type - -" Phusion Passenger -" Easy and robust deployment of Ruby on Rails application on Apache and Nginx webservers. -syn keyword ngxDirectiveThirdParty passenger_base_uri -syn keyword ngxDirectiveThirdParty passenger_default_user -syn keyword ngxDirectiveThirdParty passenger_enabled -syn keyword ngxDirectiveThirdParty passenger_log_level -syn keyword ngxDirectiveThirdParty passenger_max_instances_per_app -syn keyword ngxDirectiveThirdParty passenger_max_pool_size -syn keyword ngxDirectiveThirdParty passenger_pool_idle_time -syn keyword ngxDirectiveThirdParty passenger_root -syn keyword ngxDirectiveThirdParty passenger_ruby -syn keyword ngxDirectiveThirdParty passenger_use_global_queue -syn keyword ngxDirectiveThirdParty passenger_user_switching -syn keyword ngxDirectiveThirdParty rack_env -syn keyword ngxDirectiveThirdParty rails_app_spawner_idle_time -syn keyword ngxDirectiveThirdParty rails_env -syn keyword ngxDirectiveThirdParty rails_framework_spawner_idle_time -syn keyword ngxDirectiveThirdParty rails_spawn_method - -" RDS JSON Module -" Help ngx_drizzle and other DBD modules emit JSON data. -syn keyword ngxDirectiveThirdParty rds_json -syn keyword ngxDirectiveThirdParty rds_json_content_type -syn keyword ngxDirectiveThirdParty rds_json_format -syn keyword ngxDirectiveThirdParty rds_json_ret - -" RRD Graph Module -" This module provides an HTTP interface to RRDtool's graphing facilities. -syn keyword ngxDirectiveThirdParty rrd_graph -syn keyword ngxDirectiveThirdParty rrd_graph_root - -" Secure Download -" Create expiring links. -syn keyword ngxDirectiveThirdParty secure_download -syn keyword ngxDirectiveThirdParty secure_download_fail_location -syn keyword ngxDirectiveThirdParty secure_download_path_mode -syn keyword ngxDirectiveThirdParty secure_download_secret - -" SlowFS Cache Module -" Module adding ability to cache static files. -syn keyword ngxDirectiveThirdParty slowfs_big_file_size -syn keyword ngxDirectiveThirdParty slowfs_cache -syn keyword ngxDirectiveThirdParty slowfs_cache_key -syn keyword ngxDirectiveThirdParty slowfs_cache_min_uses -syn keyword ngxDirectiveThirdParty slowfs_cache_path -syn keyword ngxDirectiveThirdParty slowfs_cache_purge -syn keyword ngxDirectiveThirdParty slowfs_cache_valid -syn keyword ngxDirectiveThirdParty slowfs_temp_path - -" Strip Module -" Whitespace remover. -syn keyword ngxDirectiveThirdParty strip - -" Substitutions Module -" A filter module which can do both regular expression and fixed string substitutions on response bodies. -syn keyword ngxDirectiveThirdParty subs_filter -syn keyword ngxDirectiveThirdParty subs_filter_types - -" Supervisord Module -" Module providing nginx with API to communicate with supervisord and manage (start/stop) backends on-demand. -syn keyword ngxDirectiveThirdParty supervisord -syn keyword ngxDirectiveThirdParty supervisord_inherit_backend_status -syn keyword ngxDirectiveThirdParty supervisord_name -syn keyword ngxDirectiveThirdParty supervisord_start -syn keyword ngxDirectiveThirdParty supervisord_stop - -" Upload Module -" Parses multipart/form-data allowing arbitrary handling of uploaded files. -syn keyword ngxDirectiveThirdParty upload_aggregate_form_field -syn keyword ngxDirectiveThirdParty upload_buffer_size -syn keyword ngxDirectiveThirdParty upload_cleanup -syn keyword ngxDirectiveThirdParty upload_limit_rate -syn keyword ngxDirectiveThirdParty upload_max_file_size -syn keyword ngxDirectiveThirdParty upload_max_output_body_len -syn keyword ngxDirectiveThirdParty upload_max_part_header_len -syn keyword ngxDirectiveThirdParty upload_pass -syn keyword ngxDirectiveThirdParty upload_pass_args -syn keyword ngxDirectiveThirdParty upload_pass_form_field -syn keyword ngxDirectiveThirdParty upload_set_form_field -syn keyword ngxDirectiveThirdParty upload_store -syn keyword ngxDirectiveThirdParty upload_store_access - -" Upload Progress Module -" Tracks and reports upload progress. -syn keyword ngxDirectiveThirdParty report_uploads -syn keyword ngxDirectiveThirdParty track_uploads -syn keyword ngxDirectiveThirdParty upload_progress -syn keyword ngxDirectiveThirdParty upload_progress_content_type -syn keyword ngxDirectiveThirdParty upload_progress_header -syn keyword ngxDirectiveThirdParty upload_progress_json_output -syn keyword ngxDirectiveThirdParty upload_progress_template - -" Upstream Fair Balancer -" Sends an incoming request to the least-busy backend server, rather than distributing requests round-robin. -syn keyword ngxDirectiveThirdParty fair -syn keyword ngxDirectiveThirdParty upstream_fair_shm_size - -" Upstream Consistent Hash -" Select backend based on Consistent hash ring. -syn keyword ngxDirectiveThirdParty consistent_hash - -" Upstream Hash Module -" Provides simple upstream load distribution by hashing a configurable variable. -syn keyword ngxDirectiveThirdParty hash -syn keyword ngxDirectiveThirdParty hash_again - -" XSS Module -" Native support for cross-site scripting (XSS) in an nginx. -syn keyword ngxDirectiveThirdParty xss_callback_arg -syn keyword ngxDirectiveThirdParty xss_get -syn keyword ngxDirectiveThirdParty xss_input_types -syn keyword ngxDirectiveThirdParty xss_output_type - -" highlight - -hi link ngxComment Comment -hi link ngxVariable Identifier -hi link ngxVariableBlock Identifier -hi link ngxVariableString PreProc -hi link ngxBlock Normal -hi link ngxString String - -hi link ngxBoolean Boolean -hi link ngxDirectiveBlock Statement -hi link ngxDirectiveImportant Type -hi link ngxDirectiveControl Keyword -hi link ngxDirectiveError Constant -hi link ngxDirectiveDeprecated Error -hi link ngxDirective Identifier -hi link ngxDirectiveThirdParty Special - -let b:current_syntax = 'nginx' diff --git a/wezterm/.wezterm.lua b/wezterm/.wezterm.lua index cfe7582..6e7e8d0 100644 --- a/wezterm/.wezterm.lua +++ b/wezterm/.wezterm.lua @@ -11,6 +11,7 @@ config.max_fps = 240 config.animation_fps = 240 config.front_end = 'WebGpu' config.webgpu_power_preference = 'HighPerformance' +config.scrollback_lines = 10000 -- font config.harfbuzz_features = { @@ -125,10 +126,10 @@ config.keys = { -- arrow keys keybindings for _, direction in ipairs { 'Left', 'Right', 'Up', 'Down' } do -- move between panes - table.insert(config.keys, { key = direction .. 'Arrow', mods = 'CMD|OPT', action = act.ActivatePaneDirection(direction) }) + -- table.insert(config.keys, { key = direction .. 'Arrow', mods = 'CMD|OPT', action = act.ActivatePaneDirection(direction) }) -- resize panes - table.insert(config.keys, { key = direction .. 'Arrow', mods = 'CTRL|CMD', action = act.AdjustPaneSize { direction, 3 } }) + -- table.insert(config.keys, { key = direction .. 'Arrow', mods = 'CTRL|CMD', action = act.AdjustPaneSize { direction, 3 } }) if direction == 'Left' or direction == 'Right' then -- Sends ESC + b and ESC + f sequence, which is used diff --git a/zsh/.bin/git-branches.zsh b/zsh/.bin/git-branches.zsh new file mode 100755 index 0000000..9ae704c --- /dev/null +++ b/zsh/.bin/git-branches.zsh @@ -0,0 +1,30 @@ +#!/bin/zsh + +setopt LOCAL_OPTIONS PIPE_FAIL ERR_EXIT + +current_branch=$(git symbolic-ref -q --short HEAD || :) + +print_remote_or_local() { + local branch_name=$1 + local branch_name_no_remotes=${branch_name#remotes/origin/} + [[ "$branch_name_no_remotes" == "$current_branch" ]] && return + + if [[ $branch_name == remotes/* ]]; then + if [[ ! ${local_branches[(r)$branch_name_no_remotes]} ]]; then + print -P "%F{red} ${branch_name}%f" + fi + else + print " $branch_name" + fi +} + +all_branches=("${(@f)$(git branch --all --sort=-committerdate | grep -v "^\*\|HEAD" | tr -d " ")}") +local_branches=("${(@f)$(git branch --format="%(refname:short)")}") + +print_remote_or_local $all_branches[1] +[[ -n "$current_branch" ]] && print -P "%F{green}*%f $current_branch" + +# Process remaining branches without parallel +for branch in "${(@)all_branches[@]:1}"; do + print_remote_or_local $branch +done diff --git a/zsh/.bin/kdiff b/zsh/.bin/kdiff index 9c11a65..97b4440 100755 --- a/zsh/.bin/kdiff +++ b/zsh/.bin/kdiff @@ -1,2 +1,2 @@ #!/bin/bash -nvim -c "DirDiff $1 $2" +nvim -c "DiffTool $1 $2" diff --git a/zsh/.ripgreprc b/zsh/.ripgreprc new file mode 100644 index 0000000..69865e3 --- /dev/null +++ b/zsh/.ripgreprc @@ -0,0 +1,8 @@ +# Exclude directories. +--glob=!{.git,.svn,node_modules,tealdeer,Trash,vendor} + +# Exclude file types. +--glob=!*.{lock} + +# Exclude files. +--glob=!{package-lock.json} diff --git a/zsh/.zsh_plugins.txt b/zsh/.zsh_plugins.txt index 241f940..66b3927 100644 --- a/zsh/.zsh_plugins.txt +++ b/zsh/.zsh_plugins.txt @@ -6,9 +6,9 @@ RobSis/zsh-completion-generator kind:defer loiccoyle/zsh-github-copilot ohmyzsh/ohmyzsh path:lib/key-bindings.zsh +ohmyzsh/ohmyzsh path:plugins/git zsh-users/zsh-history-substring-search peterhurford/git-it-on.zsh -ohmyzsh/ohmyzsh path:plugins/git hlissner/zsh-autopair agkozak/zhooks diff --git a/zsh/.zshenv b/zsh/.zshenv index 2e096a2..13c6982 100644 --- a/zsh/.zshenv +++ b/zsh/.zshenv @@ -1,4 +1,2 @@ source ~/zsh.d/aliases.zsh source ~/zsh.d/functions.zsh - -export OP_JENKINS_ID=h3oq6iqnwhj6mu756g5rjqskua diff --git a/zsh/.zshrc b/zsh/.zshrc index 01f168d..c67f3bb 100644 --- a/zsh/.zshrc +++ b/zsh/.zshrc @@ -49,6 +49,7 @@ export ASDF_PYTHON_DEFAULT_PACKAGES_FILE=~/.dotfiles/requirements.txt source $HOME/.antidote/antidote.zsh antidote load +eval "$(zoxide init zsh)" # ================ # # PS1 and Random # @@ -72,6 +73,14 @@ done [[ -f $HOME/corp-aliases.sh ]] && source $HOME/corp-aliases.sh +# ================ # +# AWS PROFILES # +# ================ # +# Load all contexts +alias awsdev='export AWS_PROFILE=dev' +alias awsprod='export AWS_PROFILE=default' +alias awsmonitoring='export AWS_PROFILE=monitoring-inf' + # ================ # # Kubectl Contexts # # ================ # diff --git a/zsh/zsh.d/alias-finder.zsh b/zsh/zsh.d/alias-finder.zsh new file mode 100644 index 0000000..e0a51e5 --- /dev/null +++ b/zsh/zsh.d/alias-finder.zsh @@ -0,0 +1,50 @@ +function _alias_parser() { + parsed_alias=$(alias -- "$1") + if [[ $? == 0 ]]; then + echo $parsed_alias | awk -F\' '{print $2}' + fi +} + +function _alias_finder() { + final_result=() + for s in $(echo $1); do + alias_val=$(_alias_parser "$s") + if [[ -n $alias_val ]]; then + # Handle nested aliases with the same name + if [[ $alias_val == *"$s"* ]]; then + final_result+=($alias_val) + else + final_result+=($(_alias_finder "$alias_val")) + fi + else + final_result+=($s) + fi + done + echo "${final_result[@]}" +} + +# Widget function to expand aliases in the current command line +function expand-aliases-widget() { + # Get the current buffer (command line content) + local current_buffer="$BUFFER" + + # Skip if buffer is empty + if [[ -z "$current_buffer" ]]; then + return + fi + + # Pass the buffer through _alias_finder + local expanded_command=$(_alias_finder "$current_buffer") + + # Replace the buffer with the expanded command + BUFFER="$expanded_command" + + # Move cursor to the end of the line + CURSOR=$#BUFFER +} + +# Create the widget +zle -N expand-aliases-widget + +bindkey '^X^A' expand-aliases-widget + diff --git a/zsh/zsh.d/aliases.zsh b/zsh/zsh.d/aliases.zsh index a6a321e..c29b991 100755 --- a/zsh/zsh.d/aliases.zsh +++ b/zsh/zsh.d/aliases.zsh @@ -14,10 +14,11 @@ alias awk=gawk # File Navigation & Management #------------------------------------------------------------------------------ alias ll='ls -lah' -alias ls='ls --color=auto ' +alias ls='eza ' alias dc='cd ' alias dotfiles='cd ~/.dotfiles' -alias pj='fdf ~/Repos' +alias pj='fdf ~/Repos/ ~/.dotfiles' +alias lazy='fdf ~/.local/share/nvim/lazy/' alias repos="~/Repos" #------------------------------------------------------------------------------ @@ -27,15 +28,16 @@ alias vim="nvim" alias v='nvim' alias vi='nvim' alias sudoedit="nvim" -alias lvim='NVIM_APPNAME=LazyVim nvim' alias zshrc='${=EDITOR} ${ZDOTDIR:-$HOME}/.zshrc' +alias lv='NVIM_APPNAME=lightvim nvim ' +alias lvim='NVIM_APPNAME=lightvim nvim ' +alias news='nvim ~/.asdf/installs/neovim/nightly/share/nvim/runtime/doc/news.txt' #------------------------------------------------------------------------------ # Development Tools #------------------------------------------------------------------------------ alias tf='terraform' alias tg='terragrunt' -alias gb='git for-each-ref --sort=-committerdate --format="%(refname:short)" | grep -n . | sed "s?origin/??g" | sort -t: -k2 -u | sort -n | cut -d: -f2 | fzf | xargs git checkout' alias update-nvim-nightly='asdf uninstall neovim nightly && asdf install neovim nightly' #------------------------------------------------------------------------------ @@ -43,6 +45,7 @@ alias update-nvim-nightly='asdf uninstall neovim nightly && asdf install neovim #------------------------------------------------------------------------------ alias gst='git status' alias git_current_branch='git branch --show-current' +alias gb='git for-each-ref --sort=-committerdate --format="%(refname:short)" | grep -n . | sed "s?origin/??g" | sort -t: -k2 -u | sort -n | cut -d: -f2 | fzf | xargs git checkout' #------------------------------------------------------------------------------ # Network & System Utils @@ -91,11 +94,12 @@ alias -g IP='-ojsonpath="{.spec.nodeName}"' alias -g SRT='+short | sort' # Shell Processing -alias -g dollar_1_line='$(awk "{print \$1}"<<<"${line}")' -alias -g dollar_2_line='$(awk "{print \$2}"<<<"${line}")' +alias -g dollar1='$(awk "{print \$1}"<<<"${line}")' +alias -g dollar2='$(awk "{print \$2}"<<<"${line}")' # env aliases alias awsdev='export AWS_PROFILE=dev' -alias awsprod='export AWS_PROFILE=prod' +alias awsprod='export AWS_PROFILE=default' + export LOADED_ALIASES=true diff --git a/zsh/zsh.d/completions.zsh b/zsh/zsh.d/completions.zsh index d3d3f1f..ac70c4e 100644 --- a/zsh/zsh.d/completions.zsh +++ b/zsh/zsh.d/completions.zsh @@ -20,7 +20,7 @@ GENCOMPL_FPATH="${HOME}/.zsh/complete" # zstyles zstyle ':completion:*:*:*:*:*' menu select zstyle ':completion:*' completer _complete _prefix _match _approximate -zstyle ':completion:*' matcher-list 'r:[[:ascii:]]||[[:ascii:]]=** r:|=* m:{a-z\-}={A-Z\_}' +# zstyle ':completion:*' matcher-list 'r:[[:ascii:]]||[[:ascii:]]=** r:|=* m:{a-z\-}={A-Z\_}' zstyle ':completion:*:approximate:*' max-errors 3 numeric zstyle :plugin:zsh-completion-generator programs ggrep kubedebug docker_copy_between_regions ab @@ -31,7 +31,6 @@ autoload -U +X bashcompinit && bashcompinit fpath+=( "${ZSH_CACHE_DIR}/completions" "/opt/homebrew/share/zsh/site-functions" - "${ASDF_DIR}/completions" "${GENCOMPL_FPATH}" ) @@ -42,9 +41,10 @@ complete -o nospace -C terragrunt terragrunt complete -o nospace -C 'aws_completer' aws # Development tools -# load_completion_from_cmd docker completion zsh +load_completion_from_cmd docker completion zsh load_completion_from_cmd kubectl completion zsh load_completion_from_cmd helm completion zsh +load_completion_from_cmd asdf completion zsh # CLI tools load_completion_from_cmd gh completion --shell zsh diff --git a/zsh/zsh.d/fdf.zsh b/zsh/zsh.d/fdf.zsh new file mode 100755 index 0000000..06d4140 --- /dev/null +++ b/zsh/zsh.d/fdf.zsh @@ -0,0 +1,46 @@ +#!/usr/bin/env zsh + +function fdf() { + (($# < 1)) && { + print "Usage: $0 [ ...]" + exit 1 + } + + # Initialize empty array for directories + dirs=() + + # Process each input directory + for input_dir in "$@"; do + if [[ $input_dir == */ ]]; then + # If ends with /, add all subdirectories + dirs+=("${input_dir%/}"/*(/)) + else + # Otherwise add the directory itself + dirs+=("$input_dir"(/)) + fi + done + + # Get wezterm panes with nvim - using read to avoid subprocess + wezterm_json=$(wezterm cli list --format json) + typeset -A pane_map + while IFS=$'\t' read -r title pane_id; do + pane_map[${title#nvim: }]=$pane_id + done < <(print $wezterm_json | jq -r '.[] | select(.title | startswith("nvim: ")) | "\(.title)\t\(.tab_id)~\(.pane_id)"') + + # Process each directory + for dir in $dirs; do + dir_name=${dir:t} + icon=${pane_map[$dir_name]:+ } + icon=${icon:- } + print -f "%s%s\t%s\t%s\n" $icon $dir_name $dir ${pane_map[$dir_name]:-} + done | fzf -d $'\t' --with-nth 1 | while IFS=$'\t' read -r _ full_dir tab_pane; do + if [[ -n $tab_pane ]]; then + tab_id=$(echo $tab_pane | cut -d'~' -f1) + pane_id=$(echo $tab_pane | cut -d'~' -f2) + wezterm cli activate-tab --tab-id "$tab_id" + wezterm cli activate-pane --pane-id "$pane_id" + else + cd "$full_dir" && nvim + fi + done +} diff --git a/zsh/zsh.d/functions.zsh b/zsh/zsh.d/functions.zsh index fe01f82..7b50a92 100755 --- a/zsh/zsh.d/functions.zsh +++ b/zsh/zsh.d/functions.zsh @@ -13,7 +13,7 @@ function say-hebrew() { if [[ -z $* ]]; then dialog -t "Say in hebrew" -m "Enter a sentence in hebrew" --bannertext Say --textfield message,required 2>/dev/null | awk -F: '{print $2}' | xargs say -v 'Carmit (Enhanced)' else - echo $* | say -v 'Carmit (Enhanced)' + echo $* | say -v 'Carmit' fi } @@ -22,32 +22,6 @@ function set-tab-title() { echo -e "\033]0;${title}\a" } -### Helper functions ### -function _alias_parser() { - parsed_alias=$(alias -- "$1") - if [[ $? == 0 ]]; then - echo $parsed_alias | awk -F\' '{print $2}' - fi -} - -function _alias_finder() { - final_result=() - for s in $(echo $1); do - alias_val=$(_alias_parser "$s") - if [[ -n $alias_val ]]; then - # Handle nested aliases with the same name - if [[ $alias_val == *"$s"* ]]; then - final_result+=($alias_val) - else - final_result+=($(_alias_finder "$alias_val")) - fi - else - final_result+=($s) - fi - done - echo "${final_result[@]}" -} - ### Random functions ### function mwatch() { # log_file=/tmp/moshe_mwatch.log @@ -325,14 +299,6 @@ function asdf-kubectl-version() { asdf global kubectl "${TO_INSTALL}" } -# fzf -function fdf() { - # remove trailing / from $1 - dir_clean=${1%/} - all_files=$(find $dir_clean/* -maxdepth 0 -type d -print 2>/dev/null) - dir_to_enter=$(sed "s?$dir_clean/??g" <<<$all_files | fzf) - cd "$dir_clean/$dir_to_enter" && nvim -} function mkdp() { kubectl get pod --no-headers | fzf | awk '{print $1}' | xargs -n 1 kubectl describe pod diff --git a/zsh/zsh.d/fzf.zsh b/zsh/zsh.d/fzf.zsh index 9673ac0..8c98ebb 100644 --- a/zsh/zsh.d/fzf.zsh +++ b/zsh/zsh.d/fzf.zsh @@ -1,7 +1,13 @@ +source <(fzf --zsh) +export FZF_DEFAULT_OPTS='--height=100% --layout=reverse --border --info=inline' +export FZF_CTRL_T_COMMAND='rg --color=never --files --hidden --follow -g "!.git"' +export FZF_CTRL_T_OPTS="--preview 'bat --color=always --style=numbers,changes {}' --walker-skip .git,node_nodules" +export FZF_CTRL_R_OPTS="--ansi --color=hl:underline,hl+:underline,header:italic --header 'Press CTRL-Y to copy command into clipboard' --preview 'echo {2..} | bat --color=always -pl bash' --preview-window 'down:4:wrap' --bind 'ctrl-/:toggle-preview' --bind 'ctrl-y:execute-silent(echo -n {2..} | pbcopy)+abort' --prompt='History> '" + function fzf-rm() { if [[ "$#" -eq 0 ]]; then local files - files=$(find . -maxdepth 1 -type f | fzf --multi) + files=$(find . -maxdepth 1 -type f | fzf --multi --prompt="Remove> ") echo $files | xargs -I '{}' rm {} #we use xargs to capture filenames with spaces in them properly else command rm "$@" @@ -15,13 +21,13 @@ function fzf-man() { $MAN "$@" return $? else - $MAN -k . | fzf --reverse --preview="echo {1,2} | sed 's/ (/./' | sed -E 's/\)\s*$//' | xargs $MAN" | awk '{print $1 "." $2}' | tr -d '()' | xargs -r $MAN + $MAN -k . | fzf --preview="echo {1,2} | sed 's/ (/./' | sed -E 's/\)\s*$//' | xargs $MAN" --prompt="Man Pages> " | awk '{print $1 "." $2}' | tr -d '()' | xargs -r $MAN return $? fi } function fzf-eval() { - echo | fzf -q "$*" --preview-window=up:99% --preview="eval {q}" + echo | fzf -q "$*" --preview-window=up:99% --preview="eval {q}" --prompt="Eval> " } function fzf-aliases-functions() { @@ -29,20 +35,21 @@ function fzf-aliases-functions() { ( (alias) (functions | grep "()" | cut -d ' ' -f1 | grep -v "^_") - ) | fzf | cut -d '=' -f1 + ) | fzf --prompt="Commands> " | cut -d '=' -f1 ) eval $CMD } -fzf-git-status() { +function fzf-git-status() { git rev-parse --git-dir >/dev/null 2>&1 || { echo "You are not in a git repository" && return } local selected selected=$(git -c color.status=always status --short | - fzf --height 50% "$@" --border -m --ansi --nth 2..,.. \ - --preview '(git diff --color=always -- {-1} | sed 1,4d; cat {-1}) | head -500' | + fzf "$@" -m --ansi --nth 2..,.. \ + --preview '(git diff --color=always -- {-1} | sed 1,4d; cat {-1}) | head -500' \ + --prompt="Git Status> " | cut -c4- | sed 's/.* -> //') if [[ $selected ]]; then for prog in $(echo $selected); do diff --git a/zsh/zsh.d/term-support.zsh b/zsh/zsh.d/term-support.zsh index a53df0e..fe1269d 100644 --- a/zsh/zsh.d/term-support.zsh +++ b/zsh/zsh.d/term-support.zsh @@ -1,16 +1,20 @@ +export TERM=xterm-256color +export WEZTERM_SHELL_SKIP_CWD=1 [[ -f /Applications/WezTerm.app/Contents/Resources/wezterm.sh ]] && \ source /Applications/WezTerm.app/Contents/Resources/wezterm.sh -export TERM=xterm-256color -export WEZTERM_SHELL_INTEGRATION=1 -# Emits the control sequence to notify many terminal emulators -# of the cwd +ZSH_TAB_TITLE="%15<..<%~%<<" #15 char left truncated PWD function termsupport_cwd { - print -Pn "\e]0;zsh: $(basename $(pwd))\a" + emulate -L zsh + print -Pn "\e]0;zsh: ${ZSH_TAB_TITLE}\a" # set tab name +} + +function termsupport_cwd_preexec { + emulate -L zsh + local CMD="${2%% *}" + print -Pn "\e]0;${CMD}: ${ZSH_TAB_TITLE}\a" # set tab name } -# Use a precmd hook instead of a chpwd hook to avoid contaminating output -# i.e. when a script or function changes directory without `cd -q`, chpwd -# will be called the output may be swallowed by the script or function. autoload -Uz add-zsh-hook add-zsh-hook precmd termsupport_cwd +add-zsh-hook preexec termsupport_cwd_preexec