commit 71b7bd737309576259309bb0faeeaf05c98f9e31 Author: Fergus Molloy Date: Wed Jun 18 14:38:36 2025 +0100 init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..005b535 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +tags +test.sh +.luarc.json +nvim + +spell/ +lazy-lock.json diff --git a/.stylua.toml b/.stylua.toml new file mode 100644 index 0000000..139e939 --- /dev/null +++ b/.stylua.toml @@ -0,0 +1,6 @@ +column_width = 160 +line_endings = "Unix" +indent_type = "Spaces" +indent_width = 2 +quote_style = "AutoPreferSingle" +call_parentheses = "None" diff --git a/init.lua b/init.lua new file mode 100644 index 0000000..b05f2bf --- /dev/null +++ b/init.lua @@ -0,0 +1,31 @@ +-- Set as the leader key +-- See `:help mapleader` +vim.g.mapleader = ' ' +vim.g.maplocalleader = ' ' + +require 'options' +require 'keymaps' +require 'aucmd' + +-- function that's useful for debugging +P = function(v) + print(vim.inspect(v)) + return v +end + +-- [[ Install `lazy.nvim` plugin manager ]] +-- See `:help lazy.nvim.txt` or https://github.com/folke/lazy.nvim for more info +local lazypath = vim.fn.stdpath 'data' .. '/lazy/lazy.nvim' +if not (vim.uv or vim.loop).fs_stat(lazypath) then + local lazyrepo = 'https://github.com/folke/lazy.nvim.git' + local out = vim.fn.system { 'git', 'clone', '--filter=blob:none', '--branch=stable', lazyrepo, lazypath } + if vim.v.shell_error ~= 0 then + error('Error cloning lazy.nvim:\n' .. out) + end +end ---@diagnostic disable-next-line: undefined-field +vim.opt.rtp:prepend(lazypath) + +require('lazy').setup { import = 'plugins' } + +-- The line beneath this is called `modeline`. See `:help modeline` +-- vim: ts=2 sts=2 sw=2 et diff --git a/lua/aucmd.lua b/lua/aucmd.lua new file mode 100644 index 0000000..4a52135 --- /dev/null +++ b/lua/aucmd.lua @@ -0,0 +1,26 @@ +-- Highlight when yanking (copying) text +-- See `:help vim.highlight.on_yank()` +vim.api.nvim_create_autocmd('TextYankPost', { + desc = 'Highlight when yanking (copying) text', + group = vim.api.nvim_create_augroup('kickstart-highlight-yank', { clear = true }), + callback = function() + vim.highlight.on_yank() + end, +}) + +-- disable spell in terminals +vim.api.nvim_create_autocmd('TermOpen', { + desc = 'disable spell in terminal', + pattern = '*', + callback = function(_) + vim.wo[0].spell = false + end, +}) + +vim.api.nvim_create_autocmd('BufRead', { + desc = 'disable spell for certain filetypes', + pattern = { '*.out', '*.log' }, + callback = function(_) + vim.wo[0].spell = false + end, +}) diff --git a/lua/keymaps.lua b/lua/keymaps.lua new file mode 100644 index 0000000..f484b5c --- /dev/null +++ b/lua/keymaps.lua @@ -0,0 +1,48 @@ +-- Clear highlights on search when pressing in normal mode +-- See `:help hlsearch` +vim.keymap.set('n', '', 'nohlsearch') + +-- Diagnostic keymaps +vim.keymap.set('n', 'q', vim.diagnostic.setloclist, { desc = 'Open diagnostic [Q]uickfix list' }) + +-- Exit terminal mode in the builtin terminal with a shortcut that is a bit easier +-- for people to discover. Otherwise, you normally need to press , which +-- is not what someone will guess without a bit more experience. +-- +-- NOTE: This won't work in all terminal emulators/tmux/etc. Try your own mapping +-- or just use to exit terminal mode +vim.keymap.set('t', '', '', { desc = 'Exit terminal mode' }) + +-- disable help button +vim.keymap.set({ 'n', 'v', 't', 'i' }, '', '') +-- disable space +vim.keymap.set({ 'n', 'v' }, '', '') + +-- disable cmd mode (as best we can) +vim.keymap.set('n', 'q:', '') +vim.keymap.set('n', 'Q', '') + +-- center the cursor on big jumps +vim.keymap.set('n', '', 'zz') +vim.keymap.set('n', '', 'zz') +vim.keymap.set('n', 'n', 'nzz') +vim.keymap.set('n', 'N', 'Nzz') + +-- better handling of wrapped lines +vim.keymap.set('n', 'k', "v:count == 0 ? 'gk' : 'k'", { expr = true, silent = true }) +vim.keymap.set('n', 'j', "v:count == 0 ? 'gj' : 'j'", { expr = true, silent = true }) + +vim.keymap.set('n', 'j', 'bprev', { desc = 'Previous buffer' }) +vim.keymap.set('n', 'k', 'bnext', { desc = 'Next buffer' }) +vim.keymap.set('n', 'x', 'bd', { desc = 'Delete buffer' }) +vim.keymap.set('n', 'b', 'b#', { desc = 'Last buffer' }) +vim.keymap.set('n', 'b', 'w%dbe#bd#', { desc = 'Delete all buffers apart from current' }) + +-- better visual mode pasting +vim.keymap.set('v', 'p', 'pgvy', { silent = true }) +vim.keymap.set('v', 'P', 'Pgvy', { silent = true }) + +vim.keymap.set({ 'n', 'v' }, 'x', '"_x', { silent = true }) +vim.keymap.set({ 'n', 'v' }, 'X', '"_X', { silent = true }) + +vim.keymap.set('n', 'v', 'vsplit', { desc = 'Create vertical split' }) diff --git a/lua/lsp/golangci_lint_ls.lua b/lua/lsp/golangci_lint_ls.lua new file mode 100644 index 0000000..8d046e2 --- /dev/null +++ b/lua/lsp/golangci_lint_ls.lua @@ -0,0 +1,31 @@ +local M = { + filetypes = { 'go', 'gomod' }, +} + +-- disable lsp if wrong version installed +function M.enable() + local enable = true + + if + pcall(function() + vim + .system({ 'golangci-lint', '--version' }, { text = true }, function(out) + if out.code ~= 0 then + enable = false + return + end + if out.stdout:match ' 1%.%d+%.%d+' then + enable = false + return + end + end) + :wait() + end) + then + return enable + else + return false -- disable if golangci-lint isn't available + end +end + +return M diff --git a/lua/lsp/init.lua b/lua/lsp/init.lua new file mode 100644 index 0000000..e272195 --- /dev/null +++ b/lua/lsp/init.lua @@ -0,0 +1,53 @@ +local M = {} + +function M.setup() + local servers = { + ts_ls = { filetypes = { 'javascript', 'typescript', 'javascriptreact', 'typescriptreact' } }, + gopls = { filetypes = { 'go', 'gomod' } }, + rust_analyzer = { + filetypes = { 'rust' }, + settings = { + ['rust-analyzer'] = { + check = { + command = 'clippy', + extraArgs = { + '--', + '-Dclippy::correctness', + '-Wclippy::complexity', + '-Wclippy::pedantic', + '-Wclippy::perf', + }, + }, + }, + }, + }, + golangci_lint_ls = require 'lsp.golangci_lint_ls', + erlangls = { cmd = { '/home/fergusmolloy/.local/bin/erlang_ls' }, filetypes = { 'erlang' } }, + elixirls = { cmd = { 'elixir-ls' }, filetypes = { 'elixir' } }, + lua_ls = require 'lsp.lua_ls', + } + + local on_attach = require 'lsp.on_attach' + local lspconfig = require 'lspconfig' + + for server, config in pairs(servers) do + if require('lsp.utils').check_ft(server, config) then + if config['enable'] then + if not config['enable']() then + vim.notify('Disabling language server ' .. server, vim.log.levels.WARN) + goto continue + end + end + else + goto continue + end + config.capabilities = require('blink.cmp').get_lsp_capabilities(config.capabilities) + config.on_attach = require 'lsp.on_attach' + vim.lsp.enable(server) + vim.lsp.config(server, config) + + ::continue:: + end +end + +return M diff --git a/lua/lsp/lua_ls.lua b/lua/lsp/lua_ls.lua new file mode 100644 index 0000000..a848ad9 --- /dev/null +++ b/lua/lsp/lua_ls.lua @@ -0,0 +1,34 @@ +return { + on_init = function(client) + if client.workspace_folders then + local path = client.workspace_folders[1].name + if path ~= vim.fn.stdpath 'config' and (vim.uv.fs_stat(path .. '/.luarc.json') or vim.uv.fs_stat(path .. '/.luarc.jsonc')) then + return + end + end + + client.config.settings.Lua = vim.tbl_deep_extend('force', client.config.settings.Lua, { + runtime = { + -- Tell the language server which version of Lua you're using + -- (most likely LuaJIT in the case of Neovim) + version = 'LuaJIT', + }, + -- Make the server aware of Neovim runtime files + workspace = { + checkThirdParty = false, + library = { + vim.env.VIMRUNTIME, + -- Depending on the usage, you might want to add additional paths here. + -- "${3rd}/luv/library" + -- "${3rd}/busted/library", + }, + -- or pull in all of 'runtimepath'. NOTE: this is a lot slower and will cause issues when working on your own configuration (see https://github.com/neovim/nvim-lspconfig/issues/3189) + -- library = vim.api.nvim_get_runtime_file("", true) + }, + }) + end, + settings = { + Lua = {}, + }, + filetypes = { 'lua' }, +} diff --git a/lua/lsp/on_attach.lua b/lua/lsp/on_attach.lua new file mode 100644 index 0000000..10d85e8 --- /dev/null +++ b/lua/lsp/on_attach.lua @@ -0,0 +1,29 @@ +return function(client, bufnr) + require('nvim-navbuddy').attach(client, bufnr) + + local nmap = function(keys, func, desc) + if desc then + desc = 'LSP: ' .. desc + end + + vim.keymap.set('n', keys, func, { buffer = bufnr, desc = desc }) + end + + nmap('gd', vim.lsp.buf.definition, '[G]oto [D]efinition') + nmap('gi', vim.lsp.buf.implementation, '[G]oto [I]mplementation') + nmap('D', vim.lsp.buf.type_definition, 'Type [D]efinition') + nmap('rn', vim.lsp.buf.rename, '[R]e[N]ame') + nmap('ca', vim.lsp.buf.code_action, '[C]ode [A]ction') + nmap('K', vim.lsp.buf.hover, 'Hover docs') + + -- Lesser used LSP functionality + nmap('gD', vim.lsp.buf.declaration, '[G]oto [D]eclaration') + nmap('wa', vim.lsp.buf.add_workspace_folder, '[W]orkspace [A]dd Folder') + nmap('wr', vim.lsp.buf.remove_workspace_folder, '[W]orkspace [R]emove Folder') + nmap('wl', function() + print(vim.inspect(vim.lsp.buf.list_workspace_folders())) + end, '[W]orkspace [L]ist Folders') + + nmap('e', vim.diagnostic.open_float, 'Open floating diagnostics') + nmap('q', vim.diagnostic.setloclist, 'Open diagnostics list') +end diff --git a/lua/lsp/utils.lua b/lua/lsp/utils.lua new file mode 100644 index 0000000..4925f57 --- /dev/null +++ b/lua/lsp/utils.lua @@ -0,0 +1,15 @@ +local M = {} +function M.check_ft(server, config) + if config.filetypes == nil or next(config.filetypes) == nil then + return true -- if no filetypes specified then enable + else + config.server = server + for _, ft in ipairs(config.filetypes) do + if vim.bo[0].filetype == ft then + return true + end + end + end + return false +end +return M diff --git a/lua/options.lua b/lua/options.lua new file mode 100644 index 0000000..1e6525b --- /dev/null +++ b/lua/options.lua @@ -0,0 +1,67 @@ +-- Show which line your cursor is on +vim.o.cursorline = true + +-- highligt vertical column +vim.o.colorcolumn = '100' + +-- better diff +vim.o.diffopt = 'internal,filler,closeoff,iwhiteall' + +-- Make line numbers default +vim.o.number = true +vim.o.relativenumber = true + +-- Enable mouse mode, can be useful for resizing splits for example! +vim.o.mouse = 'a' + +-- Don't show the mode, since it's already in the status line +vim.o.showmode = false + +-- Enable break indent +vim.o.breakindent = true + +-- Save undo history +vim.o.undofile = true + +-- Case-insensitive searching UNLESS \C or one or more capital letters in the search term +vim.o.ignorecase = true +vim.o.smartcase = true +-- search and replace +vim.o.gdefault = true +vim.o.hlsearch = true + +-- Keep signcolumn on by default +vim.opt.signcolumn = 'yes' + +-- Decrease update time +vim.opt.updatetime = 250 + +-- Decrease mapped sequence wait time +vim.opt.timeoutlen = 300 + +-- Configure how new splits should be opened +vim.opt.splitright = true +vim.opt.splitbelow = true + +-- Sets how neovim will display certain whitespace characters in the editor. +-- See `:help 'list'` +-- and `:help 'listchars'` +vim.opt.list = true +vim.opt.listchars = { tab = '» ', trail = '·', nbsp = '␣' } + +-- Minimal number of screen lines to keep around the cursor. +vim.opt.scrolloff = 8 +vim.o.sidescrolloff = 8 + +vim.o.tabstop = 4 +vim.o.shiftwidth = 4 + +-- better grepping +vim.o.grepprg = 'rg --vimgrep --smart-case' +vim.o.grepformat = '%f:%l:%c:%m' + +vim.o.termguicolors = true + +-- enable spell checking +vim.o.spell = true +vim.o.spelllang = 'en_gb' diff --git a/lua/plugins/ai.lua b/lua/plugins/ai.lua new file mode 100644 index 0000000..fdb524f --- /dev/null +++ b/lua/plugins/ai.lua @@ -0,0 +1,58 @@ +local file = vim.fn.glob '~/projects/cdy-nvim' +if file ~= '' then + return { + { + 'projects/cdy-nvim', + dir = '~/projects/cdy-nvim', + cmd = { 'CodyChat', 'CodyAsk', 'CodySetModel', 'CodyToggleFileContext', 'CodyToggleRepoContext', 'CodyNew' }, + dependencies = { + 'MunifTanjim/nui.nvim', + 'nvim-lua/plenary.nvim', + }, + keys = { + { 'cc', 'CodyChat', desc = 'Open cody chat' }, + { 'cn', 'CodyNew', desc = 'Start new cody chat' }, + }, + dev = true, + opts = { + include_file = true, + include_repo = true, + }, + }, + } +else + return { + { + 'olimorris/codecompanion.nvim', + lazy = false, + opts = { + strategies = { + chat = { + adapter = 'anthropic', + }, + inline = { + adapter = 'anthropic', + }, + }, + }, + dependencies = { + 'nvim-lua/plenary.nvim', + 'nvim-treesitter/nvim-treesitter', + }, + }, + -- { + -- 'pasky/claude.vim', + -- lazy = false, + -- config = function() + -- local api_key = os.getenv 'ANTHROPIC_API_KEY' + -- vim.g.claude_api_key = api_key + -- vim.cmd [[ + -- let g:claude_map_implement = "ci" + -- let g:claude_map_open_chat = "cc" + -- let g:claude_map_send_chat_message = "" + -- let g:claude_map_cancel_response = "cx" + -- ]] + -- end, + -- }, + } +end diff --git a/lua/plugins/blink.lua b/lua/plugins/blink.lua new file mode 100644 index 0000000..1338663 --- /dev/null +++ b/lua/plugins/blink.lua @@ -0,0 +1,55 @@ +return { + { + 'saghen/blink.cmp', + version = 'v0.13.1', + event = 'LspAttach', + dependencies = { + 'Kaiser-Yang/blink-cmp-avante', + 'hrsh7th/nvim-cmp', + 'L3MON4D3/LuaSnip', + 'saadparwaiz1/cmp_luasnip', + 'hrsh7th/cmp-nvim-lsp', + 'hrsh7th/cmp-nvim-lua', + 'hrsh7th/cmp-path', + 'hrsh7th/cmp-buffer', + }, + opts = { + keymap = { + preset = 'enter', + [''] = { 'select_next', 'fallback' }, + [''] = { 'select_prev', 'fallback' }, + [''] = { 'hide', 'fallback' }, + }, + cmdline = { enabled = false }, + term = { enabled = false }, + + completion = { + accept = { + auto_brackets = { enabled = true }, + }, + documentation = { auto_show = true }, + }, + + appearance = { + use_nvim_cmp_as_default = true, + nerd_font_variant = 'mono', + }, + + sources = { + default = { 'avante', 'lsp', 'path', 'snippets', 'buffer' }, + per_filetype = { + codecompanion = { 'codecompanion' }, + }, + providers = { + avante = { + module = 'blink-cmp-avante', + name = 'Avante', + opts = {}, + }, + }, + }, + snippets = { preset = 'luasnip' }, + }, + opts_extend = { 'sources.default' }, + }, +} diff --git a/lua/plugins/codecompanion.lua b/lua/plugins/codecompanion.lua new file mode 100644 index 0000000..b464538 --- /dev/null +++ b/lua/plugins/codecompanion.lua @@ -0,0 +1,56 @@ +return { + { + 'olimorris/codecompanion.nvim', + dependencies = { + { 'nvim-treesitter/nvim-treesitter', build = ':TSUpdate' }, + { 'nvim-lua/plenary.nvim' }, + -- Test with blink.cmp + { + 'saghen/blink.cmp', + lazy = false, + version = '*', + opts = { + keymap = { + preset = 'enter', + [''] = { 'select_prev', 'fallback' }, + [''] = { 'select_next', 'fallback' }, + }, + cmdline = { sources = { 'cmdline' } }, + sources = { + default = { 'lsp', 'path', 'buffer', 'codecompanion' }, + }, + }, + }, + }, + opts = { + adapters = { + ollama = function() + return require('codecompanion.adapters').extend('ollama', { + schema = { + model = { + default = 'hf.co/Qwen/Qwen2.5-Coder-7B-Instruct-GGUF:latest', + }, + num_ctx = { + default = 4096, + }, + }, + }) + end, + allow_insecure = true, + }, + --Refer to: https://github.com/olimorris/codecompanion.nvim/blob/main/lua/codecompanion/config.lua + strategies = { + --NOTE: Change the adapter as required + chat = { + adapter = 'ollama', + completion_provider = 'blink', -- blink|cmp|coc|default + }, + inline = { adapter = 'ollama' }, + cmd = { adapter = 'ollama' }, + }, + opts = { + log_level = 'DEBUG', + }, + }, + }, +} diff --git a/lua/plugins/conform.lua b/lua/plugins/conform.lua new file mode 100644 index 0000000..a36e61c --- /dev/null +++ b/lua/plugins/conform.lua @@ -0,0 +1,60 @@ +return { + { + 'stevearc/conform.nvim', + event = 'LspAttach', + ft = { 'lua', 'elixir', 'go', 'erlang', 'typescriptreact', 'typescript', 'json', 'nix' }, + cmd = 'Format', + keys = { + { + 'fm', + function() + require('conform').format { async = true, lsp_format = 'fallback' } + end, + desc = 'Format buffer', + }, + }, + config = function() + require('conform').setup { + notify_on_error = true, + format_on_save = function(bufnr) + local cwd = vim.fn.getcwd() + if cwd:match '/cashout' or cwd:match '/cbe' then + vim.notify_once('Detected cashout repo, auto format disabled', vim.log.levels.WARN) + return + elseif vim.g.disable_autoformat or vim.b[bufnr].disable_autoformat then + return + end + return { timeout = 500, lsp_fallback = true } + end, + formatters_by_ft = { + lua = { 'stylua' }, + go = { 'gofmt', 'goimports' }, + typescript = { 'prettier' }, + typescriptreact = { 'prettier' }, + json = { 'prettier' }, + nix = { 'nixfmt' }, + }, + } + vim.api.nvim_create_user_command('Format', function(args) + require('conform').format { async = true, lsp_format = 'fallback' } + end, { desc = 'Format buffer' }) + vim.api.nvim_create_user_command('FormatDisable', function(args) + if args.bang then + -- FormatDisable! will disable formatting just for this buffer + vim.b.disable_autoformat = true + else + vim.g.disable_autoformat = true + end + end, { + desc = 'Disable autoformat-on-save', + bang = true, + }) + vim.api.nvim_create_user_command('FormatEnable', function() + vim.b.disable_autoformat = false + vim.g.disable_autoformat = false + end, { + desc = 'Re-enable autoformat-on-save', + }) + end, + }, +} diff --git a/lua/plugins/dap.lua b/lua/plugins/dap.lua new file mode 100644 index 0000000..421125f --- /dev/null +++ b/lua/plugins/dap.lua @@ -0,0 +1,51 @@ +local view = require 'plugins.dap.dap-view' +return { + { + 'mfussenegger/nvim-dap', + dependencies = { + 'leoluz/nvim-dap-go', + view.get_config(), + }, + ft = { 'go' }, + event = 'LspAttach', + keys = { + { '', 'DapStepInto' }, + { '', 'DapContinue' }, + { '', 'DapStepOver' }, + { + 'dt', + function() + require('dap-go').debug_test() + end, + desc = 'Debug closest go test', + }, + }, + config = function() + require('dap-go').setup { + dap_configurations = { + { + type = 'go', + name = 'Debug COSCT', + request = 'launch', + mode = 'test', + program = '${file}', + buildFlags = { '-tags=service_legacy' }, + outputMode = 'remote', + }, + }, + } + end, + }, + { + 'weissle/persistent-breakpoints.nvim', + ft = { 'go' }, + opts = { + load_breakpoints_event = { 'VimEnter' }, + }, + keys = { + { 'db', 'PBToggleBreakpoint', desc = 'Toggle breakpoint' }, + { 'dB', 'PBSetConditionalBreakpoint', desc = 'Set conditional breakpoint' }, + { 'dl', 'PBSetLogPoint', desc = 'Set log point' }, + }, + }, +} diff --git a/lua/plugins/dap/dap-view.lua b/lua/plugins/dap/dap-view.lua new file mode 100644 index 0000000..ec92526 --- /dev/null +++ b/lua/plugins/dap/dap-view.lua @@ -0,0 +1,53 @@ +M = {} + +M.get_config = function() + return { + 'igorlfs/nvim-dap-view', + opts = { + winbar = { + controls = { enabled = true }, + sections = { 'watches', 'scopes', 'console', 'breakpoints', 'threads', 'repl' }, + }, + windows = { + terminal = { + -- `go` is known to not use the terminal. + hide = { 'go' }, + }, + }, + }, + keys = { + { 'dvv', 'DapViewToggle' }, + { 'dvb', 'DapViewShow breakpoints' }, + { 'dvs', 'DapViewShow scopes' }, + { 'dvw', 'DapViewShow watches' }, + { 'dvW', 'DapViewWatch' }, + { 'dvr', 'DapViewShow repl' }, + { 'dvc', 'DapViewShow console' }, + }, + + config = function(_, opts) + require('dap-view').setup(opts) + vim.api.nvim_create_autocmd('QuitPre', { + group = vim.api.nvim_create_augroup('dap-view-extra', {}), + pattern = '*', + callback = function(_) + local wins = vim.api.nvim_list_wins() + local splits = 0 + for _, w in ipairs(wins) do + local buf = vim.api.nvim_win_get_buf(w) + local name = vim.api.nvim_buf_get_name(buf) + if not (name:match 'dap%-view' or name:match 'dap%-repl' or name == '') then + splits = splits + 1 + end + end + + if splits == 1 then + require('dap-view').close() + end + end, + }) + end, + } +end + +return M diff --git a/lua/plugins/fzf.lua b/lua/plugins/fzf.lua new file mode 100644 index 0000000..a19f952 --- /dev/null +++ b/lua/plugins/fzf.lua @@ -0,0 +1,75 @@ +return { + { + 'ibhagwan/fzf-lua', + opts = {}, + keys = { + { + 'ff', + function() + require('fzf-lua').files() + end, + desc = 'Find files', + }, + { + 'fw', + function() + require('fzf-lua').live_grep_glob { rg_glob = true } + end, + desc = 'Find word', + }, + { + 'fb', + function() + require('fzf-lua').buffers() + end, + desc = 'Find buffer', + }, + + -- lsp + { + 'gr', + function() + require('fzf-lua').lsp_references() + end, + desc = 'Find references', + }, + { + 'ds', + function() + require('fzf-lua').lsp_document_symbols() + end, + desc = 'Find document symbols', + }, + { + 'dd', + function() + require('fzf-lua').diagnostics_document() + end, + desc = 'Find document diagnostics', + }, + { + 'dw', + function() + require('fzf-lua').diagnostics_workspace() + end, + desc = 'Find workspace diagnostics', + }, + + -- dap + { + 'dc', + function() + require('fzf-lua').dap_configurations() + end, + desc = 'Run DAP configuration', + }, + { + 'dq', + function() + require('fzf-lua').dap_commands() + end, + desc = 'Send DAP command', + }, + }, + }, +} diff --git a/lua/plugins/gitsigns.lua b/lua/plugins/gitsigns.lua new file mode 100644 index 0000000..4a96462 --- /dev/null +++ b/lua/plugins/gitsigns.lua @@ -0,0 +1,19 @@ +return { + { -- Adds git related signs to the gutter, as well as utilities for managing changes + 'lewis6991/gitsigns.nvim', + lazy = false, + keys = { + { 'gb', 'Gitsigns blame', desc = 'Show blame for current buffer' }, + { 'gp', 'Gitsigns preview_hunk', desc = 'Preview hunk' }, + }, + opts = { + signs = { + add = { text = '+' }, + change = { text = '~' }, + delete = { text = '_' }, + topdelete = { text = '‾' }, + changedelete = { text = '~' }, + }, + }, + }, +} diff --git a/lua/plugins/harpoon.lua b/lua/plugins/harpoon.lua new file mode 100644 index 0000000..92d1020 --- /dev/null +++ b/lua/plugins/harpoon.lua @@ -0,0 +1,102 @@ +return { + { + 'ThePrimeagen/harpoon', + branch = 'harpoon2', + dependencies = { + 'nvim-lua/plenary.nvim', + 'ibhagwan/fzf-lua', + }, + keys = { + { + 'i', + function() + require('harpoon'):list():add() + end, + desc = 'add current buffer to harpoon', + }, + { + 'u', + function() + local harpoon = require 'harpoon' + local harpoon_files = harpoon:list() + local file_paths = {} + + for _, item in ipairs(harpoon_files.items) do + if item.value ~= nil or item.value ~= '' then + table.insert(file_paths, item.value) + end + end + + require('fzf-lua').fzf_exec(file_paths, { + fn_transform = function(x) + return require('fzf-lua').make_entry.file(x, { file_icons = true, color_icons = true }) + end, + actions = { + ['default'] = require('fzf-lua').actions.file_edit, + ['ctrl-x'] = function(selected, _) + for i, item in ipairs(harpoon_files.items) do + if item.value == selected[1] then + harpoon:list():remove_at(i) + return + end + end + vim.notify('Could not find item ' .. selected[1] .. ' in harpoon list', vim.log.levels.WARN) + end, + }, + }) + end, + desc = 'Find in harpoon list', + }, + { + 'fy', + function() + local harpoon = require 'harpoon' + local harpoon_files = harpoon:list 'yeet' + local file_paths = {} + + for _, item in ipairs(harpoon_files.items) do + if item.value ~= nil or item.value ~= '' then + table.insert(file_paths, item.value) + end + end + + require('fzf-lua').fzf_exec(file_paths, { + actions = { + ['default'] = function(selection) + require('yeet').execute(selection[1]) + end, + ['ctrl-x'] = function(selected, _) + for i, item in ipairs(harpoon_files.items) do + if item.value == selected[1] then + harpoon:list('yeet'):remove_at(i) + return + end + end + vim.notify('Could not find item ' .. selected[1] .. ' in harpoon list [yeet]', vim.log.levels.WARN) + end, + }, + }) + end, + desc = 'Find yeet', + }, + { + '', + function() + local harpoon = require 'harpoon' + harpoon.ui:toggle_quick_menu(harpoon:list 'yeet') + end, + desc = 'Open yeet cache', + }, + }, + config = function() + local harpoon = require 'harpoon' + harpoon:setup { + yeet = { + select = function(list_item, _, _) + require('yeet').execute(list_item.value) + end, + }, + } + end, + }, +} diff --git a/lua/plugins/init.lua b/lua/plugins/init.lua new file mode 100644 index 0000000..51c54ba --- /dev/null +++ b/lua/plugins/init.lua @@ -0,0 +1,103 @@ +return { + -- NOTE: [ THEMES ] + -- { + -- 'yorumicolors/yorumi.nvim', + -- }, + -- { + -- 'savq/melange-nvim', + -- }, + -- { + -- 'sainnhe/gruvbox-material', + -- lazy = false, + -- config = function() + -- vim.g.gruvbox_material_background = 'medium' + -- vim.g.gruvbox_material_enable_italic = true + -- vim.cmd 'colorscheme gruvbox-material' + -- end, + -- }, + { + 'rebelot/kanagawa.nvim', + lazy = false, + config = function() + vim.cmd 'colorscheme kanagawa' + end, + }, + + { -- Git integrations + 'tpope/vim-fugitive', + cmd = { 'G', 'Git' }, + keys = { + { 'gg', 'Git', desc = 'Open git fugitive' }, + }, + }, + + { -- traverse undotree + 'mbbill/undotree', + keys = { + { 'fu', 'UndotreeToggle', desc = 'Toggle undotree' }, + }, + }, + + { -- better f motions + 'justinmk/vim-sneak', + keys = { + { 's', 'Sneak_s', desc = 'Sneak forward' }, + { 'S', 'Sneak_S', desc = 'Sneak backwards' }, + }, + }, + + { -- surround motions + 'kylechui/nvim-surround', + opts = {}, + event = 'VeryLazy', + }, + + { -- put cursor where you left it + 'farmergreg/vim-lastplace', + lazy = false, + }, + + { -- Useful plugin to show you pending keybinds. + 'folke/which-key.nvim', + event = 'VeryLazy', + opts = {}, + }, + + { + -- `lazydev` configures Lua LSP for your Neovim config, runtime and plugins + -- used for completion, annotations and signatures of Neovim apis + 'folke/lazydev.nvim', + ft = 'lua', + opts = { + library = { + -- Load luvit types when the `vim.uv` word is found + { path = '${3rd}/luv/library', words = { 'vim%.uv' } }, + }, + }, + }, + + { + 'nmac427/guess-indent.nvim', + Event = 'BufEnter', + opts = {}, + }, + + -- Highlight todo, notes, etc in comments + { + 'folke/todo-comments.nvim', + event = 'VimEnter', + dependencies = { 'nvim-lua/plenary.nvim' }, + opts = { signs = false }, + }, + + { + 'j-hui/fidget.nvim', + version = '*', + opts = { + notification = { + override_vim_notify = true, + }, + }, + lazy = false, + }, +} diff --git a/lua/plugins/lsp.lua b/lua/plugins/lsp.lua new file mode 100644 index 0000000..6f04359 --- /dev/null +++ b/lua/plugins/lsp.lua @@ -0,0 +1,36 @@ +return { + { + 'neovim/nvim-lspconfig', + dependencies = { + 'hrsh7th/nvim-cmp', + 'saghen/blink.cmp', + { + 'SmiteshP/nvim-navbuddy', + dependencies = { + 'SmiteshP/nvim-navic', + 'MunifTanjim/nui.nvim', + }, + opts = { lsp = { auto_attach = true } }, + keys = { + { 'n', 'Navbuddy', desc = 'Open Navbuddy' }, + }, + }, + }, + ft = { 'lua', 'elixir', 'go', 'erlang', 'typescript', 'typescriptreact', 'rust' }, + config = function() + require('lsp').setup() + end, + }, + { + 'windwp/nvim-ts-autotag', + ft = { 'javascriptreact', 'typescriptreact', 'html' }, + opts = { + opts = { + -- Defaults + enable_close = true, -- Auto close tags + enable_rename = true, -- Auto rename pairs of tags + enable_close_on_slash = false, -- Auto close on trailing ', + }, + config = function() + require('luasnip').add_snippets('go', require 'snippets.go') + require('luasnip').add_snippets('erlang', require 'snippets.erlang') + require('luasnip').add_snippets('elixir', require 'snippets.elixir') + end, + }, +} diff --git a/lua/plugins/noice.lua b/lua/plugins/noice.lua new file mode 100644 index 0000000..a9e29c6 --- /dev/null +++ b/lua/plugins/noice.lua @@ -0,0 +1,49 @@ +return {} +-- { +-- 'folke/noice.nvim', +-- dependencies = { +-- 'MunifTanjim/nui.nvim', +-- }, +-- Event = 'VeryLazy', +-- opts = { +-- lsp = { +-- -- override markdown rendering so that **cmp** and other plugins use **Treesitter** +-- override = { +-- ['vim.lsp.util.convert_input_to_markdown_lines'] = true, +-- ['vim.lsp.util.stylize_markdown'] = true, +-- ['cmp.entry.get_documentation'] = true, -- requires hrsh7th/nvim-cmp +-- }, +-- }, +-- -- you can enable a preset for easier configuration +-- presets = { +-- bottom_search = true, -- use a classic bottom cmdline for search +-- command_palette = true, -- position the cmdline and popupmenu together +-- long_message_to_split = true, -- long messages will be sent to a split +-- inc_rename = false, -- enables an input dialog for inc-rename.nvim +-- lsp_doc_border = false, -- add a border to hover docs and signature help +-- }, +-- messages = { +-- enabled = true, +-- view_search = false, +-- }, +-- popupmenu = { +-- enabled = true, +-- }, +-- -- filter out annoying erlangls messages +-- routes = { +-- { +-- filter = { +-- event = 'lsp', +-- kind = 'progress', +-- find = 'Indexing', +-- cond = function(message) +-- local client = vim.tbl_get(message.opts, 'progress', 'client') +-- return client == 'erlangls' +-- end, +-- }, +-- opts = { skip = true }, +-- }, +-- }, +-- }, +-- }, +-- } diff --git a/lua/plugins/notes.lua b/lua/plugins/notes.lua new file mode 100644 index 0000000..6908dd3 --- /dev/null +++ b/lua/plugins/notes.lua @@ -0,0 +1,28 @@ +return { + { + 'epwalsh/obsidian.nvim', + version = '*', + ft = 'markdown', + dependencies = { + 'nvim-lua/plenary.nvim', + }, + config = function(_, opts) + require('obsidian').setup(opts) + vim.api.nvim_create_autocmd('FileType', { + group = vim.api.nvim_create_augroup('MarkdownConceal', {}), + pattern = 'markdown', + callback = function(ev) + vim.opt_local.conceallevel = 2 + end, + }) + end, + opts = { + workspaces = { + { + name = 'notes', + path = '~/notes', + }, + }, + }, + }, +} diff --git a/lua/plugins/oil.lua b/lua/plugins/oil.lua new file mode 100644 index 0000000..9a7fe9c --- /dev/null +++ b/lua/plugins/oil.lua @@ -0,0 +1,11 @@ +return { + { + 'stevearc/oil.nvim', + opts = { + skip_confirm_for_simple_edits = true, + }, + keys = { + { 'fo', 'Oil --float', desc = 'Open oil' }, + }, + }, +} diff --git a/lua/plugins/treesitter.lua b/lua/plugins/treesitter.lua new file mode 100644 index 0000000..9432e7f --- /dev/null +++ b/lua/plugins/treesitter.lua @@ -0,0 +1,77 @@ +return { + { -- Highlight, edit, and navigate code + 'nvim-treesitter/nvim-treesitter', + dependencies = { + 'nvim-treesitter/nvim-treesitter-textobjects', -- maybe try mini.ai instead? + }, + build = ':TSUpdate', + main = 'nvim-treesitter.configs', -- Sets main module to use for opts + opts = { + auto_install = false, + highlight = { enable = true }, + indent = { enable = true }, + incremental_selection = { + enable = true, + keymaps = { + node_incremental = 'v', + node_decremental = 'V', + scope_incremental = '', + }, + }, + textobjects = { + swap = { + enable = true, + swap_next = { + ['a'] = '@parameter.inner', + }, + swap_previous = { + ['A'] = '@parameter.inner', + }, + }, + select = { + enable = true, + + -- Automatically jump forward to textobj, similar to targets.vim + lookahead = true, + + keymaps = { + -- You can use the capture groups defined in textobjects.scm + ['af'] = '@function.outer', + ['if'] = '@function.inner', + ['ac'] = '@class.outer', + -- You can optionally set descriptions to the mappings (used in the desc parameter of + -- nvim_buf_set_keymap) which plugins like which-key display + ['ic'] = { query = '@class.inner', desc = 'Select inner part of a class region' }, + -- You can also use captures from other query groups like `locals.scm` + ['as'] = { query = '@local.scope', query_group = 'locals', desc = 'Select language scope' }, + + -- custom + ['ip'] = { query = '@parameter.inner', desc = 'Select inner parameter' }, + }, + -- You can choose the select mode (default is charwise 'v') + -- + -- Can also be a function which gets passed a table with the keys + -- * query_string: eg '@function.inner' + -- * method: eg 'v' or 'o' + -- and should return the mode ('v', 'V', or '') or a table + -- mapping query_strings to modes. + selection_modes = { + ['@parameter.outer'] = 'v', -- charwise + ['@function.outer'] = 'V', -- linewise + ['@class.outer'] = '', -- blockwise + }, + -- If you set this to `true` (default is `false`) then any textobject is + -- extended to include preceding or succeeding whitespace. Succeeding + -- whitespace has priority in order to act similarly to eg the built-in + -- `ap`. + -- + -- Can also be a function which gets passed a table with the keys + -- * query_string: eg '@function.inner' + -- * selection_mode: eg 'v' + -- and should return true or false + include_surrounding_whitespace = true, + }, + }, + }, + }, +} diff --git a/lua/plugins/yeet.lua b/lua/plugins/yeet.lua new file mode 100644 index 0000000..c043545 --- /dev/null +++ b/lua/plugins/yeet.lua @@ -0,0 +1,80 @@ +return { + { + 'samharju/yeet.nvim', + opts = { + clear_before_yeet = false, + interrupt_before_yeet = true, + yeet_and_run = true, + custom_eval = function(cmd_string) + if cmd_string:match '#test' then + -- get current win's filetype + local bufnr = vim.api.nvim_win_get_buf(0) + local ft = vim.bo[bufnr].filetype + + if ft == 'go' then + local dap_go = require 'dap-go-ts' + local test = dap_go.closest_test() + + cmd_string = cmd_string:gsub('#test', test.name) + end + end + + if cmd_string:match '#cwd' then + local cwd = vim.fn.getcwd() + + cmd_string = cmd_string:gsub('#cwd', cwd) + end + + if cmd_string:match '#file' then + local file = vim.api.nvim_buf_get_name(0) + + cmd_string = cmd_string:gsub('#file', file) + end + + return cmd_string + end, + }, + keys = { + { + -- Open target selection + 'yt', + function() + require('yeet').select_target() + end, + desc = 'Open yeet target select', + }, + { + -- Douple tap \ to yeet at something + '\\\\', + function() + require('yeet').execute() + end, + desc = 'Yeet', + }, + { + -- Toggle autocommand for yeeting after write + 'yo', + function() + require('yeet').toggle_post_write() + if vim.g.yeetaucmd then + vim.g.yeetaucmd = false + vim.notify 'disabled yeet aucmd' + else + vim.g.yeetaucmd = true + vim.notify 'enabled yeet aucmd' + end + end, + desc = 'Toggle post write yeet aucmd', + }, + { + -- Yeet visual selection. Useful sending core to a repl or running multiple commands. + 'yv', + function() + require('yeet').execute_selection { clear_before_yeet = false } + end, + mode = { 'n', 'v' }, + desc = 'Yeet selected text', + }, + }, + }, +} diff --git a/lua/snippets/elixir.lua b/lua/snippets/elixir.lua new file mode 100644 index 0000000..a564707 --- /dev/null +++ b/lua/snippets/elixir.lua @@ -0,0 +1 @@ +return {} diff --git a/lua/snippets/erlang.lua b/lua/snippets/erlang.lua new file mode 100644 index 0000000..d48b752 --- /dev/null +++ b/lua/snippets/erlang.lua @@ -0,0 +1,116 @@ +local ls = require 'luasnip' +local s = ls.snippet +local sn = ls.snippet_node +local isn = ls.indent_snippet_node +local t = ls.text_node +local i = ls.insert_node +local f = ls.function_node +local c = ls.choice_node +local d = ls.dynamic_node +local r = ls.restore_node +local events = require 'luasnip.util.events' +local ai = require 'luasnip.nodes.absolute_indexer' +local extras = require 'luasnip.extras' +local l = extras.lambda +local rep = extras.rep +local p = extras.partial +local m = extras.match +local n = extras.nonempty +local dl = extras.dynamic_lambda +local fmt = require('luasnip.extras.fmt').fmt +local fmta = require('luasnip.extras.fmt').fmta +local conds = require 'luasnip.extras.expand_conditions' +local postfix = require('luasnip.extras.postfix').postfix +local types = require 'luasnip.util.types' +local parse = require('luasnip.util.parser').parse_snippet +local ms = ls.multi_snippet +local k = require('luasnip.nodes.key_indexer').new_key + +return { + s({ trig = 'mod', name = 'Module', description = 'insert module with filename populated' }, { + t '-module(', + f(function(args, snip) + local filename = snip.env.TM_FILENAME + local rev = string.reverse(filename) + local dot = string.find(rev, '.', 1, true) + if dot == nil then + return filename + else + return filename:sub(1, -(dot + 1)) + end + end, {}), + t ').', + }), + s({ trig = 'fn', name = 'function', description = 'new function' }, { + i(1, 'function_name'), + t '(', + i(2), + t { ') ->', '' }, + i(0), + t '.', + }), + s({ trig = 'case', name = 'case statement', description = 'new case statement' }, { + t 'case ', + i(1), + t { ' of', '' }, + i(2), + t 'end', + }), + s({ trig = 'HT', name = 'list pattern', description = 'list pattern' }, { + t '[ ', + i(1, 'Head'), + t ' | ', + i(2, 'Tail'), + t ' ]', + }), + s({ trig = 'tup', name = 'tuple pattern', description = 'tuple pattern' }, { + t '{ ', + i(1, 'Value'), + t ' }', + }), + s({ trig = 'DOWN', name = 'Down message', description = 'down message for matching' }, { + t "{'DOWN', Ref, process, Pid, Reason}", + }), + s({ trig = 'mexp', name = 'merge exports', description = 'merge multiple lines of exports into one' }, { + t '-export([', + f(function(_, snip) + local all_exports = '' + local is_first = true + for _, line in ipairs(snip.env.LS_SELECT_RAW) do + local exports = line:gsub('-export%(%[(.*)%]%).', '%1') + if is_first then + all_exports = all_exports .. exports + is_first = false + else + all_exports = all_exports .. ',' .. exports + end + end + return all_exports + end, {}), + t ']).', + }), + s({ trig = 'server', name = 'gen server template', description = 'create gen server outline' }, { + t { + '-behavoir(gen_server).', + '-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).', + '', + 'init(State) ->', + '\t{ok, State}.', + '', + 'handle_call(_Event, _From, State) ->', + '\t{noreply, State}.', + '', + 'handle_cast(_Event, State) ->', + '\t{noreply, State}.', + '', + 'handle_info(_Event, State) ->', + '\t{noreply, State}.', + '', + 'terminate(_Reason, _State) ->', + '\tok.', + '', + 'code_change(_OldSvn, State, _Extra) ->', + '\t{ok, State}.', + }, + }), +} diff --git a/lua/snippets/go.lua b/lua/snippets/go.lua new file mode 100644 index 0000000..fa2d826 --- /dev/null +++ b/lua/snippets/go.lua @@ -0,0 +1,73 @@ +local ls = require 'luasnip' +local s = ls.snippet +local sn = ls.snippet_node +local isn = ls.indent_snippet_node +local t = ls.text_node +local i = ls.insert_node +local f = ls.function_node +local c = ls.choice_node +local d = ls.dynamic_node +local r = ls.restore_node +local events = require 'luasnip.util.events' +local ai = require 'luasnip.nodes.absolute_indexer' +local extras = require 'luasnip.extras' +local l = extras.lambda +local rep = extras.rep +local p = extras.partial +local m = extras.match +local n = extras.nonempty +local dl = extras.dynamic_lambda +local fmt = require('luasnip.extras.fmt').fmt +local fmta = require('luasnip.extras.fmt').fmta +local conds = require 'luasnip.extras.expand_conditions' +local postfix = require('luasnip.extras.postfix').postfix +local types = require 'luasnip.util.types' +local parse = require('luasnip.util.parser').parse_snippet +local ms = ls.multi_snippet +local k = require('luasnip.nodes.key_indexer').new_key + +return { + s({ trig = 'struct', name = 'struct', description = 'Create a new struct' }, { + t 'type ', + i(1, 'StructName'), + t { ' struct {', '\t' }, + i(0), + t { '', '}' }, + }), + s({ trig = 'enil', name = 'err is nil', description = 'check if err is nil' }, { + t 'if ', + i(1, 'err'), + t { ' != nil {', '\t' }, + i(2), + t { '', '}' }, + }), + s({ trig = 'snil', name = 'err is nil (surround)', description = 'check if err is nil (surround)' }, { + t 'if ', + i(1, 'err'), + t { ' != nil {', '' }, + f(function(_, snip) + local res, env = {}, snip.env + for _, ele in ipairs(env.LS_SELECT_RAW) do + table.insert(res, ele) + end + return res + end, {}), + t { '', '}' }, + }), + s({ trig = 'erris', name = 'errors.Is', description = 'repalce err == with errors.Is' }, { + t 'error.Is(', + f(function(_, snip) + local line = snip.env.LS_SELECT_RAW[1] + return line:gsub(' ?== ?', ', ') + end, {}), + t ')', + }), + s({ trig = 'errnis', name = 'not errors.Is', description = 'repalce err != with !errors.Is' }, { + t '!error.Is(', + f(function(_, snip) + local line = snip.env.LS_SELECT_RAW[1] + return line:gsub(' ?!= ?', ', ') + end, {}), + t ')', + }), +}