From 3f0cd7546940beb1283ef1442a944d10c48a8431 Mon Sep 17 00:00:00 2001 From: Umut Sahin Onder Date: Wed, 16 Apr 2025 19:32:25 +0200 Subject: [PATCH 01/42] feat: add support for new LSP config API in Neovim 0.11+ --- init.lua | 42 ++++++++++++++++++++++++++++-------------- 1 file changed, 28 insertions(+), 14 deletions(-) diff --git a/init.lua b/init.lua index 776c6873ff6..946ab698b3c 100644 --- a/init.lua +++ b/init.lua @@ -711,20 +711,34 @@ require('lazy').setup({ }) require('mason-tool-installer').setup { ensure_installed = ensure_installed } - require('mason-lspconfig').setup { - ensure_installed = {}, -- explicitly set to an empty table (Kickstart populates installs via mason-tool-installer) - automatic_installation = false, - handlers = { - function(server_name) - local server = servers[server_name] or {} - -- This handles overriding only values explicitly passed - -- by the server configuration above. Useful when disabling - -- certain features of an LSP (for example, turning off formatting for ts_ls) - server.capabilities = vim.tbl_deep_extend('force', {}, capabilities, server.capabilities or {}) - require('lspconfig')[server_name].setup(server) - end, - }, - } + -- Handle lsp setups differently based on Neovim version + -- See :help vim.lsp.config and :help vim.lsp.enable for Neovim 0.11+ + if vim.fn.has 'nvim-0.11' == 1 then + for server, config in pairs(servers) do + -- This handles overriding only values explicitly passed + -- by the server configuration above. Useful when disabling + -- certain features of an LSP (for example, turning off formatting for ts_ls) + config.capabilities = vim.tbl_deep_extend('force', {}, capabilities, server.capabilities or {}) + vim.lsp.config(name, config) + vim.lsp.enable(name) + end + else + -- For Neovim 0.10 and below, use mason-lspconfig for setup + require('mason-lspconfig').setup { + ensure_installed = {}, -- explicitly set to an empty table (Kickstart populates installs via mason-tool-installer) + automatic_installation = false, + handlers = { + function(server_name) + local server = servers[server_name] or {} + -- This handles overriding only values explicitly passed + -- by the server configuration above. Useful when disabling + -- certain features of an LSP (for example, turning off formatting for ts_ls) + server.capabilities = vim.tbl_deep_extend('force', {}, capabilities, server.capabilities or {}) + require('lspconfig')[server_name].setup(server) + end, + }, + } + end end, }, From f70dcb337399831a93ec7434dcc23e18491337e3 Mon Sep 17 00:00:00 2001 From: Umut Sahin Onder Date: Wed, 16 Apr 2025 19:42:03 +0200 Subject: [PATCH 02/42] Typo fix --- init.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/init.lua b/init.lua index 946ab698b3c..69d3834ac1e 100644 --- a/init.lua +++ b/init.lua @@ -718,7 +718,7 @@ require('lazy').setup({ -- This handles overriding only values explicitly passed -- by the server configuration above. Useful when disabling -- certain features of an LSP (for example, turning off formatting for ts_ls) - config.capabilities = vim.tbl_deep_extend('force', {}, capabilities, server.capabilities or {}) + config.capabilities = vim.tbl_deep_extend('force', {}, capabilities, config.capabilities or {}) vim.lsp.config(name, config) vim.lsp.enable(name) end From 4b819009392786f5c0f76a5846c18964aa0aa967 Mon Sep 17 00:00:00 2001 From: Umut Sahin Onder Date: Thu, 17 Apr 2025 17:17:26 +0200 Subject: [PATCH 03/42] fix(lsp): correct variable name in lsp config loop Signed-off-by: Umut Sahin Onder --- init.lua | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/init.lua b/init.lua index 69d3834ac1e..bdcb9805ecc 100644 --- a/init.lua +++ b/init.lua @@ -290,7 +290,7 @@ require('lazy').setup({ -- Then, because we use the `opts` key (recommended), the configuration runs -- after the plugin has been loaded as `require(MODULE).setup(opts)`. - { -- Useful plugin to show you pending keybinds. + { -- Useful plugin to show you pending keybinds. 'folke/which-key.nvim', event = 'VimEnter', -- Sets the loading event to 'VimEnter' opts = { @@ -371,7 +371,7 @@ require('lazy').setup({ { 'nvim-telescope/telescope-ui-select.nvim' }, -- Useful for getting pretty icons, but requires a Nerd Font. - { 'nvim-tree/nvim-web-devicons', enabled = vim.g.have_nerd_font }, + { 'nvim-tree/nvim-web-devicons', enabled = vim.g.have_nerd_font }, }, config = function() -- Telescope is a fuzzy finder that comes with a lot of different things that @@ -479,7 +479,7 @@ require('lazy').setup({ 'WhoIsSethDaniel/mason-tool-installer.nvim', -- Useful status updates for LSP. - { 'j-hui/fidget.nvim', opts = {} }, + { 'j-hui/fidget.nvim', opts = {} }, -- Allows extra capabilities provided by blink.cmp 'saghen/blink.cmp', @@ -719,8 +719,8 @@ require('lazy').setup({ -- by the server configuration above. Useful when disabling -- certain features of an LSP (for example, turning off formatting for ts_ls) config.capabilities = vim.tbl_deep_extend('force', {}, capabilities, config.capabilities or {}) - vim.lsp.config(name, config) - vim.lsp.enable(name) + vim.lsp.config(server, config) + vim.lsp.enable(server) end else -- For Neovim 0.10 and below, use mason-lspconfig for setup From 5502cea3f66d7acd58233e653916cc76b8bde47f Mon Sep 17 00:00:00 2001 From: Umut Sahin Onder Date: Thu, 17 Apr 2025 17:22:08 +0200 Subject: [PATCH 04/42] style: format with stylua --- init.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/init.lua b/init.lua index bdcb9805ecc..398396174d4 100644 --- a/init.lua +++ b/init.lua @@ -290,7 +290,7 @@ require('lazy').setup({ -- Then, because we use the `opts` key (recommended), the configuration runs -- after the plugin has been loaded as `require(MODULE).setup(opts)`. - { -- Useful plugin to show you pending keybinds. + { -- Useful plugin to show you pending keybinds. 'folke/which-key.nvim', event = 'VimEnter', -- Sets the loading event to 'VimEnter' opts = { @@ -371,7 +371,7 @@ require('lazy').setup({ { 'nvim-telescope/telescope-ui-select.nvim' }, -- Useful for getting pretty icons, but requires a Nerd Font. - { 'nvim-tree/nvim-web-devicons', enabled = vim.g.have_nerd_font }, + { 'nvim-tree/nvim-web-devicons', enabled = vim.g.have_nerd_font }, }, config = function() -- Telescope is a fuzzy finder that comes with a lot of different things that @@ -479,7 +479,7 @@ require('lazy').setup({ 'WhoIsSethDaniel/mason-tool-installer.nvim', -- Useful status updates for LSP. - { 'j-hui/fidget.nvim', opts = {} }, + { 'j-hui/fidget.nvim', opts = {} }, -- Allows extra capabilities provided by blink.cmp 'saghen/blink.cmp', From a590ab6c8812ade054cf0f444ee53c1b0daa98a3 Mon Sep 17 00:00:00 2001 From: Umut Sahin Onder Date: Sun, 20 Apr 2025 12:58:24 +0200 Subject: [PATCH 05/42] fix(lsp): Remove backwards compatibility and add comments for lsp setup --- init.lua | 57 ++++++++++++++++++++++++-------------------------------- 1 file changed, 24 insertions(+), 33 deletions(-) diff --git a/init.lua b/init.lua index 398396174d4..bde5d4ff93b 100644 --- a/init.lua +++ b/init.lua @@ -647,12 +647,6 @@ require('lazy').setup({ }, } - -- LSP servers and clients are able to communicate to each other what features they support. - -- By default, Neovim doesn't support everything that is in the LSP specification. - -- When you add blink.cmp, luasnip, etc. Neovim now has *more* capabilities. - -- So, we create new capabilities with blink.cmp, and then broadcast that to the servers. - local capabilities = require('blink.cmp').get_lsp_capabilities() - -- Enable the following language servers -- Feel free to add/remove any LSPs that you want here. They will automatically be installed. -- @@ -711,34 +705,31 @@ require('lazy').setup({ }) require('mason-tool-installer').setup { ensure_installed = ensure_installed } - -- Handle lsp setups differently based on Neovim version - -- See :help vim.lsp.config and :help vim.lsp.enable for Neovim 0.11+ - if vim.fn.has 'nvim-0.11' == 1 then - for server, config in pairs(servers) do - -- This handles overriding only values explicitly passed - -- by the server configuration above. Useful when disabling - -- certain features of an LSP (for example, turning off formatting for ts_ls) - config.capabilities = vim.tbl_deep_extend('force', {}, capabilities, config.capabilities or {}) - vim.lsp.config(server, config) - vim.lsp.enable(server) - end - else - -- For Neovim 0.10 and below, use mason-lspconfig for setup - require('mason-lspconfig').setup { - ensure_installed = {}, -- explicitly set to an empty table (Kickstart populates installs via mason-tool-installer) - automatic_installation = false, - handlers = { - function(server_name) - local server = servers[server_name] or {} - -- This handles overriding only values explicitly passed - -- by the server configuration above. Useful when disabling - -- certain features of an LSP (for example, turning off formatting for ts_ls) - server.capabilities = vim.tbl_deep_extend('force', {}, capabilities, server.capabilities or {}) - require('lspconfig')[server_name].setup(server) - end, - }, - } + -- Handle LSP setups + -- See :help vim.lsp.enable + for server, config in pairs(servers) do + vim.lsp.config(server, config) + vim.lsp.enable(server) end + -- NOTE: Some servers still require the nvim-lspconfig setup until they are updated + -- Use this template inside the for loop if you encounter issues with an lsp + -- + -- if server == 'example_server' or server == 'example_server2' then + -- -- This handles overriding only values explicitly passed + -- -- by the server configuration above. Useful when disabling + -- -- certain features of an LSP (for example, turning off formatting for ts_ls) + -- local capabilities = require('blink.cmp').get_lsp_capabilities() + -- config.capabilities = vim.tbl_deep_extend('force', {}, capabilities, config.capabilities or {}) + -- require('mason-lspconfig')[server_name].setup(config) + -- else + -- vim.lsp.config(server, config) + -- vim.lsp.enable(server) + -- end + -- + -- LSP servers and clients are able to communicate to each other what features they support. + -- With nvim-lspconfig setup, Neovim doesn't support everything that is in the LSP specification. + -- When you add blink.cmp, luasnip, etc. Neovim now has *more* capabilities. + -- So, we create new capabilities with blink.cmp, and then broadcast that to the servers. end, }, From dac395dac6ae1612c77a61811b41fa95d123fe42 Mon Sep 17 00:00:00 2001 From: Umut Sahin Onder Date: Sun, 20 Apr 2025 13:12:00 +0200 Subject: [PATCH 06/42] fix: Typo in comment --- init.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/init.lua b/init.lua index bde5d4ff93b..3f09b9cedb7 100644 --- a/init.lua +++ b/init.lua @@ -720,7 +720,7 @@ require('lazy').setup({ -- -- certain features of an LSP (for example, turning off formatting for ts_ls) -- local capabilities = require('blink.cmp').get_lsp_capabilities() -- config.capabilities = vim.tbl_deep_extend('force', {}, capabilities, config.capabilities or {}) - -- require('mason-lspconfig')[server_name].setup(config) + -- require('mason-lspconfig')[server].setup(config) -- else -- vim.lsp.config(server, config) -- vim.lsp.enable(server) From 531073d8b258774ed6f05a549ea1846d24451de9 Mon Sep 17 00:00:00 2001 From: Umut Sahin Onder Date: Mon, 21 Apr 2025 20:02:43 +0200 Subject: [PATCH 07/42] fix: revert back to original handler logic --- init.lua | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/init.lua b/init.lua index 3f09b9cedb7..81bc4975f2c 100644 --- a/init.lua +++ b/init.lua @@ -705,14 +705,19 @@ require('lazy').setup({ }) require('mason-tool-installer').setup { ensure_installed = ensure_installed } - -- Handle LSP setups - -- See :help vim.lsp.enable - for server, config in pairs(servers) do - vim.lsp.config(server, config) - vim.lsp.enable(server) - end + require('mason-lspconfig').setup { + ensure_installed = {}, -- explicitly set to an empty table (Kickstart populates installs via mason-tool-installer) + automatic_installation = false, + handlers = { + function(server_name) + local config = servers[server_name] or {} + vim.lsp.config(server_name, config) + vim.lsp.enable(server_name) + end, + }, + } -- NOTE: Some servers still require the nvim-lspconfig setup until they are updated - -- Use this template inside the for loop if you encounter issues with an lsp + -- Add this template inside the handler function after initializing config if you encounter issues with any lsp -- -- if server == 'example_server' or server == 'example_server2' then -- -- This handles overriding only values explicitly passed @@ -721,12 +726,10 @@ require('lazy').setup({ -- local capabilities = require('blink.cmp').get_lsp_capabilities() -- config.capabilities = vim.tbl_deep_extend('force', {}, capabilities, config.capabilities or {}) -- require('mason-lspconfig')[server].setup(config) - -- else - -- vim.lsp.config(server, config) - -- vim.lsp.enable(server) + -- return -- end -- - -- LSP servers and clients are able to communicate to each other what features they support. + -- LSP servers and clients are able to communicate to each other what features they support. -- With nvim-lspconfig setup, Neovim doesn't support everything that is in the LSP specification. -- When you add blink.cmp, luasnip, etc. Neovim now has *more* capabilities. -- So, we create new capabilities with blink.cmp, and then broadcast that to the servers. From 81c5210cb57f2127f323d5f6b2dae44bc74374b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Umut=20=C3=96nder?= <93402298+umutondersu@users.noreply.github.com> Date: Wed, 30 Apr 2025 17:01:57 +0200 Subject: [PATCH 08/42] Fix: correct variables inside comments Co-authored-by: Rory Hendrickson <35480205+roryhen@users.noreply.github.com> --- init.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/init.lua b/init.lua index 81bc4975f2c..44199aeb0e4 100644 --- a/init.lua +++ b/init.lua @@ -719,13 +719,13 @@ require('lazy').setup({ -- NOTE: Some servers still require the nvim-lspconfig setup until they are updated -- Add this template inside the handler function after initializing config if you encounter issues with any lsp -- - -- if server == 'example_server' or server == 'example_server2' then + -- if server_name == 'example_server' or server_name == 'example_server2' then -- -- This handles overriding only values explicitly passed -- -- by the server configuration above. Useful when disabling -- -- certain features of an LSP (for example, turning off formatting for ts_ls) -- local capabilities = require('blink.cmp').get_lsp_capabilities() -- config.capabilities = vim.tbl_deep_extend('force', {}, capabilities, config.capabilities or {}) - -- require('mason-lspconfig')[server].setup(config) + -- require('mason-lspconfig')[server_name].setup(config) -- return -- end -- From 9e0c7b634181070aeffc250c0a4852e37732e5a4 Mon Sep 17 00:00:00 2001 From: Umut Sahin Onder Date: Sat, 10 May 2025 17:20:20 +0200 Subject: [PATCH 09/42] fix(deps): pin mason plugin versions to v1.* --- init.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/init.lua b/init.lua index 44199aeb0e4..2c1a01c4ab4 100644 --- a/init.lua +++ b/init.lua @@ -474,8 +474,8 @@ require('lazy').setup({ -- Automatically install LSPs and related tools to stdpath for Neovim -- Mason must be loaded before its dependents so we need to set it up here. -- NOTE: `opts = {}` is the same as calling `require('mason').setup({})` - { 'williamboman/mason.nvim', opts = {} }, - 'williamboman/mason-lspconfig.nvim', + { 'williamboman/mason.nvim', version = 'v1.*', opts = {} }, + { 'williamboman/mason-lspconfig.nvim', version = 'v1.*' }, 'WhoIsSethDaniel/mason-tool-installer.nvim', -- Useful status updates for LSP. From 0018683a23d1d8256b590730a46727523fceb03e Mon Sep 17 00:00:00 2001 From: Umut Sahin Onder Date: Sun, 11 May 2025 15:18:53 +0200 Subject: [PATCH 10/42] feat(lsp): Update Mason to v2 --- init.lua | 40 ++++++++++------------------------------ 1 file changed, 10 insertions(+), 30 deletions(-) diff --git a/init.lua b/init.lua index 2c1a01c4ab4..867c6bc9a78 100644 --- a/init.lua +++ b/init.lua @@ -474,8 +474,8 @@ require('lazy').setup({ -- Automatically install LSPs and related tools to stdpath for Neovim -- Mason must be loaded before its dependents so we need to set it up here. -- NOTE: `opts = {}` is the same as calling `require('mason').setup({})` - { 'williamboman/mason.nvim', version = 'v1.*', opts = {} }, - { 'williamboman/mason-lspconfig.nvim', version = 'v1.*' }, + { 'williamboman/mason.nvim', opts = {} }, + { 'williamboman/mason-lspconfig.nvim', opts = {} }, 'WhoIsSethDaniel/mason-tool-installer.nvim', -- Useful status updates for LSP. @@ -705,34 +705,14 @@ require('lazy').setup({ }) require('mason-tool-installer').setup { ensure_installed = ensure_installed } - require('mason-lspconfig').setup { - ensure_installed = {}, -- explicitly set to an empty table (Kickstart populates installs via mason-tool-installer) - automatic_installation = false, - handlers = { - function(server_name) - local config = servers[server_name] or {} - vim.lsp.config(server_name, config) - vim.lsp.enable(server_name) - end, - }, - } - -- NOTE: Some servers still require the nvim-lspconfig setup until they are updated - -- Add this template inside the handler function after initializing config if you encounter issues with any lsp - -- - -- if server_name == 'example_server' or server_name == 'example_server2' then - -- -- This handles overriding only values explicitly passed - -- -- by the server configuration above. Useful when disabling - -- -- certain features of an LSP (for example, turning off formatting for ts_ls) - -- local capabilities = require('blink.cmp').get_lsp_capabilities() - -- config.capabilities = vim.tbl_deep_extend('force', {}, capabilities, config.capabilities or {}) - -- require('mason-lspconfig')[server_name].setup(config) - -- return - -- end - -- - -- LSP servers and clients are able to communicate to each other what features they support. - -- With nvim-lspconfig setup, Neovim doesn't support everything that is in the LSP specification. - -- When you add blink.cmp, luasnip, etc. Neovim now has *more* capabilities. - -- So, we create new capabilities with blink.cmp, and then broadcast that to the servers. + -- Installed LSPs are configured and enabled automatically with mason-lspconfig + -- The loop below is for overriding the default configuration of LSPs with the ones in the servers table + for server_name, config in pairs(servers) do + vim.lsp.config(server_name, config) + end + + -- NOTE: Some servers may require an old setup until they are updated. For the full list refer here: https://github.com/neovim/nvim-lspconfig/issues/3705 + -- These servers will have to be manually set up with require("lspconfig").server_name.setup{} end, }, From 718c90d2248cbf94060b2991c656942a22acd32a Mon Sep 17 00:00:00 2001 From: Umut Sahin Onder Date: Sun, 11 May 2025 16:16:14 +0200 Subject: [PATCH 11/42] fix: mason server name --- init.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/init.lua b/init.lua index 6cb25dd5163..6ca67b50ce8 100644 --- a/init.lua +++ b/init.lua @@ -482,7 +482,7 @@ require('lazy').setup({ -- Automatically install LSPs and related tools to stdpath for Neovim -- Mason must be loaded before its dependents so we need to set it up here. -- NOTE: `opts = {}` is the same as calling `require('mason').setup({})` - { 'mason-orgmason.nvim', opts = {} }, + { 'mason-org/mason.nvim', opts = {} }, { 'mason-org/mason-lspconfig.nvim', opts = {} }, 'WhoIsSethDaniel/mason-tool-installer.nvim', From 82949b8a263631fd2884a9f57e01a3390e8084f2 Mon Sep 17 00:00:00 2001 From: Umut Sahin Onder Date: Sat, 17 May 2025 13:56:36 +0200 Subject: [PATCH 12/42] Refactor(lsp): Ensure automatic server enabling --- init.lua | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/init.lua b/init.lua index 6ca67b50ce8..c214f9c2aa2 100644 --- a/init.lua +++ b/init.lua @@ -483,7 +483,7 @@ require('lazy').setup({ -- Mason must be loaded before its dependents so we need to set it up here. -- NOTE: `opts = {}` is the same as calling `require('mason').setup({})` { 'mason-org/mason.nvim', opts = {} }, - { 'mason-org/mason-lspconfig.nvim', opts = {} }, + 'mason-org/mason-lspconfig.nvim', 'WhoIsSethDaniel/mason-tool-installer.nvim', -- Useful status updates for LSP. @@ -693,6 +693,11 @@ require('lazy').setup({ }, }, } + ---@type MasonLspconfigSettings + ---@diagnostic disable-next-line: missing-fields + require('mason-lspconfig').setup { + automatic_enable = vim.tbl_keys(servers or {}), + } -- Ensure the servers and tools above are installed -- From 0ecd67991d1d28d0d25c73b3f173a87ff267e379 Mon Sep 17 00:00:00 2001 From: "Paul B. Kim" Date: Sun, 8 Feb 2026 19:57:17 +0900 Subject: [PATCH 13/42] chore: base --- .nvimlog | 12 ++ init.lua | 195 ++++++++++++++---- lua/{kickstart => custom}/health.lua | 0 .../plugins/autopairs.lua | 0 lua/{kickstart => custom}/plugins/debug.lua | 0 .../plugins/gitsigns.lua | 0 lua/custom/plugins/harpoon.lua | 72 +++++++ .../plugins/indent_line.lua | 0 lua/{kickstart => custom}/plugins/lint.lua | 9 + lua/custom/plugins/markdown.lua | 27 +++ .../plugins/neo-tree.lua | 0 lua/custom/plugins/neogit.lua | 18 ++ lua/custom/plugins/opencode.lua | 83 ++++++++ lua/custom/plugins/supermaven.lua | 21 ++ lua/custom/plugins/tmux_navigator.lua | 18 ++ lua/custom/plugins/typescript_tools.lua | 5 + lua/custom/wrapping.lua | 81 ++++++++ 17 files changed, 496 insertions(+), 45 deletions(-) create mode 100644 .nvimlog rename lua/{kickstart => custom}/health.lua (100%) rename lua/{kickstart => custom}/plugins/autopairs.lua (100%) rename lua/{kickstart => custom}/plugins/debug.lua (100%) rename lua/{kickstart => custom}/plugins/gitsigns.lua (100%) create mode 100644 lua/custom/plugins/harpoon.lua rename lua/{kickstart => custom}/plugins/indent_line.lua (100%) rename lua/{kickstart => custom}/plugins/lint.lua (88%) create mode 100644 lua/custom/plugins/markdown.lua rename lua/{kickstart => custom}/plugins/neo-tree.lua (100%) create mode 100644 lua/custom/plugins/neogit.lua create mode 100644 lua/custom/plugins/opencode.lua create mode 100644 lua/custom/plugins/supermaven.lua create mode 100644 lua/custom/plugins/tmux_navigator.lua create mode 100644 lua/custom/plugins/typescript_tools.lua create mode 100644 lua/custom/wrapping.lua diff --git a/.nvimlog b/.nvimlog new file mode 100644 index 00000000000..167af11748b --- /dev/null +++ b/.nvimlog @@ -0,0 +1,12 @@ +WRN 2026-02-08T10:26:22.500 ?.251198 server_start:199: Failed to start server: operation not permitted: /run/user/1000/nvim.251198.0 +WRN 2026-02-08T10:27:17.510 ?.252159 server_start:199: Failed to start server: operation not permitted: /run/user/1000/nvim.252159.0 +WRN 2026-02-08T10:27:27.437 ?.252367 server_start:199: Failed to start server: operation not permitted: /run/user/1000/nvim.252367.0 +WRN 2026-02-08T12:08:26.579 ?.359867 server_start:199: Failed to start server: operation not permitted: /run/user/1000/nvim.359867.0 +WRN 2026-02-08T12:10:44.180 ?.364737 server_start:199: Failed to start server: operation not permitted: /run/user/1000/nvim.364737.0 +ERR 2026-02-08T12:10:44.218 ?.364737 pty_proc_spawn:186: forkpty failed: Permission denied +WRN 2026-02-08T12:18:41.628 ?.381522 server_start:199: Failed to start server: operation not permitted: /run/user/1000/nvim.381522.0 +WRN 2026-02-08T16:12:49.348 ?.515879 server_start:199: Failed to start server: operation not permitted: /run/user/1000/nvim.515879.0 +WRN 2026-02-08T16:33:59.951 ?.543624 server_start:199: Failed to start server: operation not permitted: /run/user/1000/nvim.543624.0 +WRN 2026-02-08T16:57:38.954 ?.571806 server_start:199: Failed to start server: operation not permitted: /run/user/1000/nvim.571806.0 +WRN 2026-02-08T19:51:48.962 ?.702813 server_start:199: Failed to start server: operation not permitted: /run/user/1000/nvim.702813.0 +WRN 2026-02-08T19:51:48.968 ?.702844 server_start:199: Failed to start server: operation not permitted: /run/user/1000/nvim.702844.0 diff --git a/init.lua b/init.lua index c214f9c2aa2..d98e3d51e5c 100644 --- a/init.lua +++ b/init.lua @@ -101,8 +101,7 @@ vim.g.have_nerd_font = false -- Make line numbers default vim.o.number = true -- You can also add relative line numbers, to help with jumping. --- Experiment for yourself to see if you like it! --- vim.o.relativenumber = true +vim.o.relativenumber = true -- Enable mouse mode, can be useful for resizing splits for example! vim.o.mouse = 'a' @@ -118,8 +117,13 @@ vim.schedule(function() vim.o.clipboard = 'unnamedplus' end) --- Enable break indent -vim.o.breakindent = true +require('custom.wrapping').setup() + +-- Default to 4-space indentation unless overridden by filetype/plugins +vim.o.tabstop = 4 +vim.o.shiftwidth = 4 +vim.o.softtabstop = 4 +vim.o.expandtab = true -- Save undo history vim.o.undofile = true @@ -191,13 +195,13 @@ vim.keymap.set('t', '', '', { desc = 'Exit terminal mode' } -- vim.keymap.set('n', '', 'echo "Use j to move!!"') -- Keybinds to make split navigation easier. --- Use CTRL+ to switch between windows +-- NOTE: are managed by vim-tmux-navigator in this config. -- -- See `:help wincmd` for a list of all window commands -vim.keymap.set('n', '', '', { desc = 'Move focus to the left window' }) -vim.keymap.set('n', '', '', { desc = 'Move focus to the right window' }) -vim.keymap.set('n', '', '', { desc = 'Move focus to the lower window' }) -vim.keymap.set('n', '', '', { desc = 'Move focus to the upper window' }) +vim.keymap.set('n', 'wh', '', { desc = 'Move focus to the left window' }) +vim.keymap.set('n', 'wl', '', { desc = 'Move focus to the right window' }) +vim.keymap.set('n', 'wj', '', { desc = 'Move focus to the lower window' }) +vim.keymap.set('n', 'wk', '', { desc = 'Move focus to the upper window' }) -- NOTE: Some terminals have colliding keymaps or are not able to send distinct keycodes -- vim.keymap.set("n", "", "H", { desc = "Move window to the left" }) @@ -219,6 +223,60 @@ vim.api.nvim_create_autocmd('TextYankPost', { end, }) +local path_on_save_group = vim.api.nvim_create_augroup('kickstart-create-path-on-save', { clear = true }) + +-- Only create paths inside these roots (defaults to current working directory). +-- Add more roots as needed, e.g. vim.fn.expand '~/notes' +local path_create_roots = { + vim.fn.getcwd(), +} + +local is_in_allowed_root = function(path) + local abs_path = vim.fn.fnamemodify(path, ':p') + for _, root in ipairs(path_create_roots) do + local abs_root = vim.fn.fnamemodify(root, ':p') + if abs_path:sub(1, #abs_root) == abs_root then + return true + end + end + return false +end + +local ensure_owner_rwx = function(path) + local perms = vim.fn.getfperm(path) + if perms ~= '' and perms:sub(1, 3) ~= 'rwx' then + vim.fn.setfperm(path, 'rwx' .. perms:sub(4)) + end +end + +vim.api.nvim_create_autocmd('BufWritePre', { + desc = 'Create missing parent directories on save', + group = path_on_save_group, + callback = function(args) + if vim.bo[args.buf].buftype ~= '' then + return + end + + local file_path = vim.api.nvim_buf_get_name(args.buf) + if file_path == '' or file_path:match '^%w+://' then + return + end + + local uv = vim.uv or vim.loop + local abs_path = vim.fn.fnamemodify(file_path, ':p') + if not is_in_allowed_root(abs_path) then + return + end + + local parent = vim.fn.fnamemodify(abs_path, ':h') + + if uv.fs_stat(parent) == nil then + vim.fn.mkdir(parent, 'p', '0700') + end + ensure_owner_rwx(parent) + 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' @@ -665,19 +723,44 @@ require('lazy').setup({ -- - settings (table): Override the default settings passed when initializing the server. -- For example, to see the options for `lua_ls`, you could go to: https://luals.github.io/wiki/settings/ local servers = { - -- clangd = {}, - -- gopls = {}, - -- pyright = {}, - -- rust_analyzer = {}, - -- ... etc. See `:help lspconfig-all` for a list of all the pre-configured LSPs - -- - -- Some languages (like typescript) have entire language plugins that can be useful: - -- https://github.com/pmizio/typescript-tools.nvim - -- - -- But for many setups, the LSP (`ts_ls`) will work just fine - -- ts_ls = {}, - -- - + -- Languages + clangd = {}, + gopls = { + settings = { + gopls = { + analyses = { + unusedparams = true, + shadow = true, + }, + staticcheck = true, + gofumpt = true, + }, + }, + }, + pyright = { + settings = { + python = { + analysis = { + typeCheckingMode = 'basic', + autoSearchPaths = true, + useLibraryCodeForTypes = true, + }, + }, + }, + }, + ruff = {}, + rust_analyzer = {}, + bashls = {}, + awk_ls = {}, + cssls = {}, + htmx = {}, + html = {}, + jsonls = {}, + yamlls = {}, + taplo = {}, + elixirls = {}, + gh_actions_ls = {}, + jqls = {}, lua_ls = { -- cmd = { ... }, -- filetypes = { ... }, @@ -692,6 +775,25 @@ require('lazy').setup({ }, }, }, + -- Tools + eslint = {}, + astro = {}, + tailwindcss = {}, + docker_language_server = {}, + docker_compose_language_service = {}, + marksman = {}, + postgres_lsp = {}, + neocmake = {}, + buf_ls = {}, + + -- ... etc. See `:help lspconfig-all` for a list of all the pre-configured LSPs + -- + -- Some languages (like typescript) have entire language plugins that can be useful: + -- https://github.com/pmizio/typescript-tools.nvim + -- + -- But for many setups, the LSP (`ts_ls`) will work just fine + -- ts_ls = {}, + -- } ---@type MasonLspconfigSettings ---@diagnostic disable-next-line: missing-fields @@ -715,6 +817,7 @@ require('lazy').setup({ local ensure_installed = vim.tbl_keys(servers or {}) vim.list_extend(ensure_installed, { 'stylua', -- Used to format Lua code + 'markdownlint', -- Used by nvim-lint for Markdown buffers }) require('mason-tool-installer').setup { ensure_installed = ensure_installed } @@ -933,22 +1036,36 @@ require('lazy').setup({ }, { -- Highlight, edit, and navigate code 'nvim-treesitter/nvim-treesitter', + lazy = false, build = ':TSUpdate', - main = 'nvim-treesitter.configs', -- Sets main module to use for opts -- [[ Configure Treesitter ]] See `:help nvim-treesitter` opts = { ensure_installed = { 'bash', 'c', 'diff', 'html', 'lua', 'luadoc', 'markdown', 'markdown_inline', 'query', 'vim', 'vimdoc' }, - -- Autoinstall languages that are not installed auto_install = true, - highlight = { - enable = true, - -- Some languages depend on vim's regex highlighting system (such as Ruby) for indent rules. - -- If you are experiencing weird indenting issues, add the language to - -- the list of additional_vim_regex_highlighting and disabled languages for indent. - additional_vim_regex_highlighting = { 'ruby' }, - }, - indent = { enable = true, disable = { 'ruby' } }, }, + config = function(_, opts) + local ts = require 'nvim-treesitter' + ts.setup() + + -- Install only parsers that are not already present to avoid repeated startup spam. + if opts.auto_install and opts.ensure_installed and #opts.ensure_installed > 0 then + local installed = ts.get_installed 'parsers' + local missing = vim.tbl_filter(function(lang) + return not vim.list_contains(installed, lang) + end, opts.ensure_installed) + + if #missing > 0 then + ts.install(missing) + end + end + + vim.api.nvim_create_autocmd('FileType', { + group = vim.api.nvim_create_augroup('kickstart-treesitter', { clear = true }), + callback = function() + pcall(vim.treesitter.start) + end, + }) + end, -- There are additional nvim-treesitter modules that you can use to interact -- with nvim-treesitter. You should go explore a few and see what interests you: -- @@ -961,23 +1078,11 @@ require('lazy').setup({ -- init.lua. If you want these files, they are in the repository, so you can just download them and -- place them in the correct locations. - -- NOTE: Next step on your Neovim journey: Add/Configure additional plugins for Kickstart - -- - -- Here are some example plugins that I've included in the Kickstart repository. - -- Uncomment any of the lines below to enable them (you will need to restart nvim). - -- - -- require 'kickstart.plugins.debug', - -- require 'kickstart.plugins.indent_line', - -- require 'kickstart.plugins.lint', - -- require 'kickstart.plugins.autopairs', - -- require 'kickstart.plugins.neo-tree', - -- require 'kickstart.plugins.gitsigns', -- adds gitsigns recommend keymaps - -- NOTE: The import below can automatically add your own plugins, configuration, etc from `lua/custom/plugins/*.lua` -- This is the easiest way to modularize your config. -- -- Uncomment the following line and add your plugins to `lua/custom/plugins/*.lua` to get going. - -- { import = 'custom.plugins' }, + { import = 'custom.plugins' }, -- -- For additional information with loading, sourcing and examples see `:help lazy.nvim-🔌-plugin-spec` -- Or use telescope! diff --git a/lua/kickstart/health.lua b/lua/custom/health.lua similarity index 100% rename from lua/kickstart/health.lua rename to lua/custom/health.lua diff --git a/lua/kickstart/plugins/autopairs.lua b/lua/custom/plugins/autopairs.lua similarity index 100% rename from lua/kickstart/plugins/autopairs.lua rename to lua/custom/plugins/autopairs.lua diff --git a/lua/kickstart/plugins/debug.lua b/lua/custom/plugins/debug.lua similarity index 100% rename from lua/kickstart/plugins/debug.lua rename to lua/custom/plugins/debug.lua diff --git a/lua/kickstart/plugins/gitsigns.lua b/lua/custom/plugins/gitsigns.lua similarity index 100% rename from lua/kickstart/plugins/gitsigns.lua rename to lua/custom/plugins/gitsigns.lua diff --git a/lua/custom/plugins/harpoon.lua b/lua/custom/plugins/harpoon.lua new file mode 100644 index 00000000000..21ec13d6482 --- /dev/null +++ b/lua/custom/plugins/harpoon.lua @@ -0,0 +1,72 @@ +return { + 'ThePrimeagen/harpoon', + branch = 'harpoon2', + dependencies = { 'nvim-lua/plenary.nvim' }, + config = function() + local harpoon = require 'harpoon' + harpoon:setup { + default = { + select = function(list_item, _, options) + if not list_item or not list_item.value or list_item.value == '' then + return + end + + options = options or {} + local open_cmd = 'edit' + if options.vsplit then + open_cmd = 'vsplit' + elseif options.split then + open_cmd = 'split' + elseif options.tabedit then + open_cmd = 'tabedit' + end + + vim.cmd(open_cmd .. ' ' .. vim.fn.fnameescape(list_item.value)) + + local context = list_item.context or {} + local row = math.max(1, tonumber(context.row) or 1) + local col = math.max(0, tonumber(context.col) or 0) + local line_count = vim.api.nvim_buf_line_count(0) + if line_count < 1 then + return + end + + if row > line_count then + row = line_count + end + + local row_text = vim.api.nvim_buf_get_lines(0, row - 1, row, false)[1] or '' + if col > #row_text then + col = #row_text + end + + pcall(vim.api.nvim_win_set_cursor, 0, { row, col }) + end, + }, + } + + vim.keymap.set('n', 'a', function() + if vim.bo.buftype ~= '' then + vim.notify('Harpoon: current buffer is not a file', vim.log.levels.INFO) + return + end + harpoon:list():add() + end, { desc = 'Harpoon add' }) + vim.keymap.set('n', 'hm', function() + harpoon.ui:toggle_quick_menu(harpoon:list()) + end, { desc = 'Harpoon menu' }) + vim.keymap.set('n', '', function() + harpoon.ui:toggle_quick_menu(harpoon:list()) + end, { desc = 'Harpoon menu' }) + vim.api.nvim_create_user_command('HarpoonMenu', function() + harpoon.ui:toggle_quick_menu(harpoon:list()) + end, { desc = 'Toggle Harpoon quick menu' }) + + vim.keymap.set('n', '', function() + harpoon:list():prev() + end) + vim.keymap.set('n', '', function() + harpoon:list():next() + end) + end, +} diff --git a/lua/kickstart/plugins/indent_line.lua b/lua/custom/plugins/indent_line.lua similarity index 100% rename from lua/kickstart/plugins/indent_line.lua rename to lua/custom/plugins/indent_line.lua diff --git a/lua/kickstart/plugins/lint.lua b/lua/custom/plugins/lint.lua similarity index 88% rename from lua/kickstart/plugins/lint.lua rename to lua/custom/plugins/lint.lua index dec42f097c6..61ec2d278f5 100644 --- a/lua/kickstart/plugins/lint.lua +++ b/lua/custom/plugins/lint.lua @@ -8,6 +8,15 @@ return { lint.linters_by_ft = { markdown = { 'markdownlint' }, } + lint.linters.markdownlint = vim.tbl_deep_extend('force', lint.linters.markdownlint or {}, { + args = { + '--stdin', + '--disable', + 'MD013', -- line length + 'MD033', -- inline HTML + 'MD041', -- first line heading + }, + }) -- To allow other plugins to add linters to require('lint').linters_by_ft, -- instead set linters_by_ft like this: diff --git a/lua/custom/plugins/markdown.lua b/lua/custom/plugins/markdown.lua new file mode 100644 index 00000000000..647ab76a1e2 --- /dev/null +++ b/lua/custom/plugins/markdown.lua @@ -0,0 +1,27 @@ +return { + { + 'MeanderingProgrammer/render-markdown.nvim', + ft = { 'markdown' }, + dependencies = { 'nvim-treesitter/nvim-treesitter', 'nvim-tree/nvim-web-devicons' }, + opts = { + file_types = { 'markdown' }, + }, + }, + { + 'iamcco/markdown-preview.nvim', + ft = { 'markdown' }, + cmd = { 'MarkdownPreviewToggle', 'MarkdownPreview', 'MarkdownPreviewStop' }, + build = function() + vim.fn['mkdp#util#install']() + end, + init = function() + vim.g.mkdp_auto_start = 0 + vim.g.mkdp_auto_close = 1 + vim.g.mkdp_refresh_slow = 0 + vim.g.mkdp_filetypes = { 'markdown' } + end, + keys = { + { 'mp', 'MarkdownPreviewToggle', desc = '[M]arkdown [P]review' }, + }, + }, +} diff --git a/lua/kickstart/plugins/neo-tree.lua b/lua/custom/plugins/neo-tree.lua similarity index 100% rename from lua/kickstart/plugins/neo-tree.lua rename to lua/custom/plugins/neo-tree.lua diff --git a/lua/custom/plugins/neogit.lua b/lua/custom/plugins/neogit.lua new file mode 100644 index 00000000000..8a4fb9d6504 --- /dev/null +++ b/lua/custom/plugins/neogit.lua @@ -0,0 +1,18 @@ +return { + 'NeogitOrg/neogit', + lazy = true, + dependencies = { + 'nvim-lua/plenary.nvim', -- required + 'sindrets/diffview.nvim', -- optional - Diff integration + + -- Only one of these is needed. + 'nvim-telescope/telescope.nvim', -- optional + 'ibhagwan/fzf-lua', -- optional + 'nvim-mini/mini.pick', -- optional + 'folke/snacks.nvim', -- optional + }, + cmd = 'Neogit', + keys = { + { 'gg', 'Neogit', desc = 'Show Neogit UI' }, + }, +} diff --git a/lua/custom/plugins/opencode.lua b/lua/custom/plugins/opencode.lua new file mode 100644 index 00000000000..60bd25002f2 --- /dev/null +++ b/lua/custom/plugins/opencode.lua @@ -0,0 +1,83 @@ +return { + 'NickvanDyke/opencode.nvim', + dependencies = { + -- Recommended for `ask()` and `select()`. + -- Required for `snacks` provider. + ---@module 'snacks' <- Loads `snacks.nvim` types for configuration intellisense. + { 'folke/snacks.nvim', opts = { input = {}, picker = {}, terminal = {} } }, + }, + config = function() + ---@type opencode.Opts + vim.g.opencode_opts = { + provider = { + enabled = 'snacks', + snacks = { + auto_close = false, + win = { + position = 'right', + border = 'rounded', + width = math.floor(vim.o.columns * 0.35), + enter = false, + }, + }, + }, + } + + -- Required for `opts.events.reload`. + vim.o.autoread = true + + -- Recommended/example keymaps. + vim.keymap.set({ 'n', 'x' }, '', function() require('opencode').ask('@this: ', { submit = true }) end, { desc = 'Ask opencode…' }) + vim.keymap.set({ 'n', 'x' }, '', function() require('opencode').select() end, { desc = 'Execute opencode action…' }) + local function opencode_toggle() + local ok, err = pcall(function() + require('opencode').toggle() + end) + if not ok then + vim.notify('opencode toggle failed: ' .. tostring(err), vim.log.levels.ERROR) + end + end + + vim.keymap.set({ 'n', 't' }, '', opencode_toggle, { desc = 'Toggle opencode' }) + vim.keymap.set('n', 'oc', opencode_toggle, { desc = 'Toggle opencode chat' }) + + local opencode_term_nav_group = vim.api.nvim_create_augroup('opencode-term-nav', { clear = true }) + vim.api.nvim_create_autocmd('FileType', { + group = opencode_term_nav_group, + pattern = 'opencode_terminal', + callback = function(ev) + local function tmux_nav(cmd) + local esc = vim.api.nvim_replace_termcodes('', true, false, true) + vim.api.nvim_feedkeys(esc, 'n', false) + vim.cmd(cmd) + end + + vim.keymap.set('t', '', function() + tmux_nav 'TmuxNavigateLeft' + end, { buffer = ev.buf, silent = true, desc = 'Tmux navigate left from opencode' }) + + vim.keymap.set('t', '', function() + tmux_nav 'TmuxNavigateDown' + end, { buffer = ev.buf, silent = true, desc = 'Tmux navigate down from opencode' }) + + vim.keymap.set('t', '', function() + tmux_nav 'TmuxNavigateUp' + end, { buffer = ev.buf, silent = true, desc = 'Tmux navigate up from opencode' }) + + vim.keymap.set('t', '', function() + tmux_nav 'TmuxNavigateRight' + end, { buffer = ev.buf, silent = true, desc = 'Tmux navigate right from opencode' }) + end, + }) + + vim.keymap.set({ 'n', 'x' }, 'go', function() return require('opencode').operator '@this ' end, { desc = 'Add range to opencode', expr = true }) + vim.keymap.set('n', 'goo', function() return require('opencode').operator '@this ' .. '_' end, { desc = 'Add line to opencode', expr = true }) + + vim.keymap.set('n', '', function() require('opencode').command 'session.half.page.up' end, { desc = 'Scroll opencode up' }) + vim.keymap.set('n', '', function() require('opencode').command 'session.half.page.down' end, { desc = 'Scroll opencode down' }) + + -- You may want these if you stick with the opinionated "" and "" above — otherwise consider "o…". + vim.keymap.set('n', '+', '', { desc = 'Increment under cursor', noremap = true }) + vim.keymap.set('n', '-', '', { desc = 'Decrement under cursor', noremap = true }) + end, +} diff --git a/lua/custom/plugins/supermaven.lua b/lua/custom/plugins/supermaven.lua new file mode 100644 index 00000000000..b16e65adc0d --- /dev/null +++ b/lua/custom/plugins/supermaven.lua @@ -0,0 +1,21 @@ +return { + 'supermaven-inc/supermaven-nvim', + config = function() + require('supermaven-nvim').setup { + keymaps = { + accept_suggestion = '', + clear_suggestion = '', + accept_word = '', + }, + ignore_filetypes = { cpp = true }, -- or { "cpp", } + color = { + suggestion_color = '#ffffff', + cterm = 244, + }, + log_level = 'info', -- set to "off" to disable logging completely + disable_inline_completion = false, -- disables inline completion for use with cmp + disable_keymaps = false, -- disables built in keymaps for more manual control + condition = function() return false end, -- condition to check for stopping supermaven, `true` means to stop supermaven when the condition is true. + } + end, +} diff --git a/lua/custom/plugins/tmux_navigator.lua b/lua/custom/plugins/tmux_navigator.lua new file mode 100644 index 00000000000..26fb72ba14c --- /dev/null +++ b/lua/custom/plugins/tmux_navigator.lua @@ -0,0 +1,18 @@ +return { + 'christoomey/vim-tmux-navigator', + cmd = { + 'TmuxNavigateLeft', + 'TmuxNavigateDown', + 'TmuxNavigateUp', + 'TmuxNavigateRight', + 'TmuxNavigatePrevious', + 'TmuxNavigatorProcessList', + }, + keys = { + { '', 'TmuxNavigateLeft' }, + { '', 'TmuxNavigateDown' }, + { '', 'TmuxNavigateUp' }, + { '', 'TmuxNavigateRight' }, + { '', 'TmuxNavigatePrevious' }, + }, +} diff --git a/lua/custom/plugins/typescript_tools.lua b/lua/custom/plugins/typescript_tools.lua new file mode 100644 index 00000000000..ba7fb2046e7 --- /dev/null +++ b/lua/custom/plugins/typescript_tools.lua @@ -0,0 +1,5 @@ +return { + 'pmizio/typescript-tools.nvim', + dependencies = { 'nvim-lua/plenary.nvim', 'neovim/nvim-lspconfig' }, + opts = {}, +} diff --git a/lua/custom/wrapping.lua b/lua/custom/wrapping.lua new file mode 100644 index 00000000000..3732a8c09d3 --- /dev/null +++ b/lua/custom/wrapping.lua @@ -0,0 +1,81 @@ +local M = {} + +local defaults = { + width = 100, + patterns = { '*.md', '*.markdown' }, +} + +local function apply_wrap_options(local_opts, width) + local_opts.wrap = true + local_opts.linebreak = true + local_opts.breakindent = true + local_opts.textwidth = width + local_opts.colorcolumn = '' + local_opts.formatoptions:append 't' + local_opts.formatoptions:append 'a' + local_opts.formatoptions:remove 'l' +end + +local function can_reflow(bufnr) + return vim.api.nvim_buf_is_valid(bufnr) and vim.bo[bufnr].modifiable and vim.bo[bufnr].buftype == '' +end + +local function reflow_whole_buffer(bufnr, preserve_view) + if not can_reflow(bufnr) then + return + end + + vim.api.nvim_buf_call(bufnr, function() + local view = preserve_view and vim.fn.winsaveview() or nil + vim.cmd 'silent keepjumps normal! gggqG' + if view then + vim.fn.winrestview(view) + end + end) +end + +function M.setup(opts) + opts = vim.tbl_deep_extend('force', defaults, opts or {}) + + apply_wrap_options(vim.opt, opts.width) + + local group = vim.api.nvim_create_augroup('custom_wrapping_rules', { clear = true }) + + vim.api.nvim_create_autocmd('FileType', { + group = group, + callback = function() + -- Apply after ftplugins so local overrides don't drop wrap options. + vim.schedule(function() + apply_wrap_options(vim.opt_local, opts.width) + end) + end, + }) + + vim.api.nvim_create_autocmd('BufReadPost', { + group = group, + pattern = opts.patterns, + callback = function(args) + -- Reflow existing prose on open so the hard wrap rule is applied immediately. + if vim.b[args.buf].did_initial_wrap then + return + end + vim.b[args.buf].did_initial_wrap = true + vim.schedule(function() + reflow_whole_buffer(args.buf, false) + end) + end, + }) + + vim.api.nvim_create_autocmd('BufWritePre', { + group = group, + pattern = opts.patterns, + callback = function(args) + if not vim.bo[args.buf].modified then + return + end + reflow_whole_buffer(args.buf, true) + end, + }) +end + +return M From 1b65e4683442bdd6c11576a99e6aeeb7e53d14ad Mon Sep 17 00:00:00 2001 From: "Paul B. Kim" Date: Fri, 6 Mar 2026 11:14:27 +0900 Subject: [PATCH 14/42] chore: add AGENTS.md development context --- AGENTS.md | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 AGENTS.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000000..73e1b28f7db --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,48 @@ +# Repository Guidelines + +## Project Structure & Module Organization +- Root entrypoint: `init.lua` (Kickstart-based primary config). +- Custom Lua modules live under `lua/custom/`. +- Plugin specs are split into `lua/custom/plugins/*.lua` (one concern per file, e.g. `gitsigns.lua`, `lint.lua`, `persistence.lua`). +- Utility modules (non-plugin config) live in `lua/custom/*.lua` (for example `wrapping.lua`, `health.lua`). +- Reference docs live in `README.md` and `doc/kickstart.txt`. +- Plugin versions are pinned in `lazy-lock.json`. + +## Build, Test, and Development Commands +- `nvim` + Starts Neovim and triggers lazy.nvim plugin loading/install. +- `nvim --headless "+qa"` + Fast startup sanity check (useful in CI-style validation). +- `nvim --headless "+checkhealth" "+qa"` + Runs health checks for Neovim, plugins, and external tools. +- `nvim --headless "+Lazy! sync" "+qa"` + Syncs plugin set to current specs. +- `luac -p init.lua lua/custom/**/*.lua` + Lua syntax validation for config files. + +## Coding Style & Naming Conventions +- Language: Lua (Neovim API style). +- Formatting: `stylua` using `.stylua.toml` settings (2-space indentation, no tabs). +- Prefer small, focused plugin spec files named by feature (`neo-tree.lua`, `markdown.lua`). +- Use descriptive keymap `desc` fields and group prefixes via which-key. +- Keep comments concise and practical; avoid repeating obvious code behavior. + +## Testing Guidelines +- No formal unit-test framework is configured in this repo. +- Required checks before PR: + - Lua parse check (`luac -p ...`) + - Headless startup (`nvim --headless "+qa"`) + - Health check (`:checkhealth`) for affected tooling (LSP, formatters, linters). +- For plugin/config changes, include manual verification steps in PR notes (keymaps, commands, expected behavior). + +## Commit & Pull Request Guidelines +- Follow existing history style: Conventional Commit-like prefixes such as: + - `feat(scope): ...` + - `fix(scope): ...` + - `chore: ...` +- Keep commits scoped to one logical change (plugin, keymap group, diagnostics, etc.). +- PRs should include: + - Summary of behavior changes + - Files touched (e.g. `init.lua`, `lua/custom/plugins/...`) + - Validation performed (commands run) + - Screenshots/GIFs only when UI behavior is materially changed. From 357dab6e1c6aac24bd68a26da8aa5b7a98e00033 Mon Sep 17 00:00:00 2001 From: "Paul B. Kim" Date: Fri, 6 Mar 2026 11:14:30 +0900 Subject: [PATCH 15/42] fix(plugins): initial plugin configuration fixes --- init.lua | 41 ++++++++++++------------- lua/custom/plugins/gitsigns.lua | 11 +++++-- lua/custom/plugins/persistence.lua | 48 ++++++++++++++++++++++++++++++ lua/custom/plugins/supermaven.lua | 3 +- lua/custom/wrapping.lua | 33 +++++++++++++++----- 5 files changed, 105 insertions(+), 31 deletions(-) create mode 100644 lua/custom/plugins/persistence.lua diff --git a/init.lua b/init.lua index d98e3d51e5c..d74b4f564a3 100644 --- a/init.lua +++ b/init.lua @@ -179,6 +179,13 @@ vim.keymap.set('n', '', 'nohlsearch') -- Diagnostic keymaps vim.keymap.set('n', 'q', vim.diagnostic.setloclist, { desc = 'Open diagnostic [Q]uickfix list' }) +vim.keymap.set('n', '[d', function() + vim.diagnostic.jump { count = -1, float = true } +end, { desc = 'Go to previous [D]iagnostic' }) +vim.keymap.set('n', ']d', function() + vim.diagnostic.jump { count = 1, float = true } +end, { desc = 'Go to next [D]iagnostic' }) +vim.keymap.set('n', 'de', vim.diagnostic.open_float, { desc = 'Show [D]iagnostic [E]rror details' }) -- 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 @@ -329,19 +336,6 @@ require('lazy').setup({ -- options to `gitsigns.nvim`. -- -- See `:help gitsigns` to understand what the configuration keys do - { -- Adds git related signs to the gutter, as well as utilities for managing changes - 'lewis6991/gitsigns.nvim', - opts = { - signs = { - add = { text = '+' }, - change = { text = '~' }, - delete = { text = '_' }, - topdelete = { text = '‾' }, - changedelete = { text = '~' }, - }, - }, - }, - -- NOTE: Plugins can also be configured to run Lua code when they are loaded. -- -- This is often very useful to both group configuration, as well as handle @@ -402,8 +396,14 @@ require('lazy').setup({ -- Document existing key chains spec = { + { 'a', group = 'Harpoon [A]dd' }, + { 'd', group = '[D]iagnostics' }, + { 'g', group = '[G]it' }, + { 'm', group = '[M]arkdown' }, + { 'o', group = '[O]pencode' }, { 's', group = '[S]earch' }, { 't', group = '[T]oggle' }, + { 'w', group = '[W]indow' }, { 'h', group = 'Git [H]unk', mode = { 'n', 'v' } }, }, }, @@ -701,14 +701,15 @@ require('lazy').setup({ virtual_text = { source = 'if_many', spacing = 2, + severity = { min = vim.diagnostic.severity.WARN }, format = function(diagnostic) - local diagnostic_message = { - [vim.diagnostic.severity.ERROR] = diagnostic.message, - [vim.diagnostic.severity.WARN] = diagnostic.message, - [vim.diagnostic.severity.INFO] = diagnostic.message, - [vim.diagnostic.severity.HINT] = diagnostic.message, - } - return diagnostic_message[diagnostic.severity] + if diagnostic.severity == vim.diagnostic.severity.WARN then + return 'W: ' .. diagnostic.message + end + if diagnostic.severity == vim.diagnostic.severity.ERROR then + return 'E: ' .. diagnostic.message + end + return nil end, }, } diff --git a/lua/custom/plugins/gitsigns.lua b/lua/custom/plugins/gitsigns.lua index cbbd22d24fc..cf3d48931a0 100644 --- a/lua/custom/plugins/gitsigns.lua +++ b/lua/custom/plugins/gitsigns.lua @@ -6,6 +6,13 @@ return { { 'lewis6991/gitsigns.nvim', opts = { + signs = { + add = { text = '+' }, + change = { text = '~' }, + delete = { text = '_' }, + topdelete = { text = '‾' }, + changedelete = { text = '~' }, + }, on_attach = function(bufnr) local gitsigns = require 'gitsigns' @@ -44,7 +51,7 @@ return { map('n', 'hs', gitsigns.stage_hunk, { desc = 'git [s]tage hunk' }) map('n', 'hr', gitsigns.reset_hunk, { desc = 'git [r]eset hunk' }) map('n', 'hS', gitsigns.stage_buffer, { desc = 'git [S]tage buffer' }) - map('n', 'hu', gitsigns.stage_hunk, { desc = 'git [u]ndo stage hunk' }) + map('n', 'hu', gitsigns.undo_stage_hunk, { desc = 'git [u]ndo stage hunk' }) map('n', 'hR', gitsigns.reset_buffer, { desc = 'git [R]eset buffer' }) map('n', 'hp', gitsigns.preview_hunk, { desc = 'git [p]review hunk' }) map('n', 'hb', gitsigns.blame_line, { desc = 'git [b]lame line' }) @@ -54,7 +61,7 @@ return { end, { desc = 'git [D]iff against last commit' }) -- Toggles map('n', 'tb', gitsigns.toggle_current_line_blame, { desc = '[T]oggle git show [b]lame line' }) - map('n', 'tD', gitsigns.preview_hunk_inline, { desc = '[T]oggle git show [D]eleted' }) + map('n', 'tD', gitsigns.toggle_deleted, { desc = '[T]oggle git show [D]eleted' }) end, }, }, diff --git a/lua/custom/plugins/persistence.lua b/lua/custom/plugins/persistence.lua new file mode 100644 index 00000000000..83fa606a8f2 --- /dev/null +++ b/lua/custom/plugins/persistence.lua @@ -0,0 +1,48 @@ +return { + 'folke/persistence.nvim', + event = 'BufReadPre', + opts = { + options = { 'buffers', 'curdir', 'tabpages', 'winsize', 'help', 'globals' }, + }, + keys = { + { + 'wr', + function() + require('persistence').load() + end, + desc = '[W]orkspace [R]estore session', + }, + { + 'wl', + function() + require('persistence').load { last = true } + end, + desc = '[W]orkspace [L]ast session', + }, + { + 'wd', + function() + require('persistence').stop() + end, + desc = '[W]orkspace [D]isable session save', + }, + }, + init = function() + vim.api.nvim_create_autocmd('VimEnter', { + group = vim.api.nvim_create_augroup('persistence-auto-restore', { clear = true }), + callback = function() + if vim.fn.argc(-1) > 0 then + return + end + + local ignored = { gitcommit = true, gitrebase = true } + if ignored[vim.bo.filetype] then + return + end + + require('persistence').load() + end, + nested = true, + }) + end, +} diff --git a/lua/custom/plugins/supermaven.lua b/lua/custom/plugins/supermaven.lua index b16e65adc0d..29be06968cc 100644 --- a/lua/custom/plugins/supermaven.lua +++ b/lua/custom/plugins/supermaven.lua @@ -1,5 +1,6 @@ return { 'supermaven-inc/supermaven-nvim', + event = 'InsertEnter', config = function() require('supermaven-nvim').setup { keymaps = { @@ -12,7 +13,7 @@ return { suggestion_color = '#ffffff', cterm = 244, }, - log_level = 'info', -- set to "off" to disable logging completely + log_level = 'off', disable_inline_completion = false, -- disables inline completion for use with cmp disable_keymaps = false, -- disables built in keymaps for more manual control condition = function() return false end, -- condition to check for stopping supermaven, `true` means to stop supermaven when the condition is true. diff --git a/lua/custom/wrapping.lua b/lua/custom/wrapping.lua index 3732a8c09d3..e7720140f97 100644 --- a/lua/custom/wrapping.lua +++ b/lua/custom/wrapping.lua @@ -3,6 +3,8 @@ local M = {} local defaults = { width = 100, patterns = { '*.md', '*.markdown' }, + max_lines = 5000, + max_bytes = 1024 * 1024, } local function apply_wrap_options(local_opts, width) @@ -16,12 +18,28 @@ local function apply_wrap_options(local_opts, width) local_opts.formatoptions:remove 'l' end -local function can_reflow(bufnr) - return vim.api.nvim_buf_is_valid(bufnr) and vim.bo[bufnr].modifiable and vim.bo[bufnr].buftype == '' +local function can_reflow(bufnr, opts) + if not vim.api.nvim_buf_is_valid(bufnr) or not vim.bo[bufnr].modifiable or vim.bo[bufnr].buftype ~= '' then + return false + end + + if opts.max_lines and vim.api.nvim_buf_line_count(bufnr) > opts.max_lines then + return false + end + + local name = vim.api.nvim_buf_get_name(bufnr) + if name ~= '' and opts.max_bytes then + local stat = (vim.uv or vim.loop).fs_stat(name) + if stat and stat.size and stat.size > opts.max_bytes then + return false + end + end + + return true end -local function reflow_whole_buffer(bufnr, preserve_view) - if not can_reflow(bufnr) then +local function reflow_whole_buffer(bufnr, preserve_view, opts) + if not can_reflow(bufnr, opts) then return end @@ -37,12 +55,11 @@ end function M.setup(opts) opts = vim.tbl_deep_extend('force', defaults, opts or {}) - apply_wrap_options(vim.opt, opts.width) - local group = vim.api.nvim_create_augroup('custom_wrapping_rules', { clear = true }) vim.api.nvim_create_autocmd('FileType', { group = group, + pattern = 'markdown', callback = function() -- Apply after ftplugins so local overrides don't drop wrap options. vim.schedule(function() @@ -61,7 +78,7 @@ function M.setup(opts) end vim.b[args.buf].did_initial_wrap = true vim.schedule(function() - reflow_whole_buffer(args.buf, false) + reflow_whole_buffer(args.buf, false, opts) end) end, }) @@ -73,7 +90,7 @@ function M.setup(opts) if not vim.bo[args.buf].modified then return end - reflow_whole_buffer(args.buf, true) + reflow_whole_buffer(args.buf, true, opts) end, }) end From ba5ecb2e1fffedc5d3cadf7d3f9f94d321a44f80 Mon Sep 17 00:00:00 2001 From: "Paul B. Kim" <50508394+paulbkim01@users.noreply.github.com> Date: Tue, 10 Feb 2026 06:31:35 +0900 Subject: [PATCH 16/42] chore: clear README.md Removed extensive installation and configuration instructions for kickstart.nvim. --- README.md | 242 +----------------------------------------------------- 1 file changed, 1 insertion(+), 241 deletions(-) diff --git a/README.md b/README.md index 4113950550d..a3e67b10460 100644 --- a/README.md +++ b/README.md @@ -1,241 +1 @@ -# kickstart.nvim - -## Introduction - -A starting point for Neovim that is: - -* Small -* Single-file -* Completely Documented - -**NOT** a Neovim distribution, but instead a starting point for your configuration. - -## Installation - -### Install Neovim - -Kickstart.nvim targets *only* the latest -['stable'](https://github.com/neovim/neovim/releases/tag/stable) and latest -['nightly'](https://github.com/neovim/neovim/releases/tag/nightly) of Neovim. -If you are experiencing issues, please make sure you have the latest versions. - -### Install External Dependencies - -External Requirements: -- Basic utils: `git`, `make`, `unzip`, C Compiler (`gcc`) -- [ripgrep](https://github.com/BurntSushi/ripgrep#installation), - [fd-find](https://github.com/sharkdp/fd#installation) -- Clipboard tool (xclip/xsel/win32yank or other depending on the platform) -- A [Nerd Font](https://www.nerdfonts.com/): optional, provides various icons - - if you have it set `vim.g.have_nerd_font` in `init.lua` to true -- Emoji fonts (Ubuntu only, and only if you want emoji!) `sudo apt install fonts-noto-color-emoji` -- Language Setup: - - If you want to write Typescript, you need `npm` - - If you want to write Golang, you will need `go` - - etc. - -> [!NOTE] -> See [Install Recipes](#Install-Recipes) for additional Windows and Linux specific notes -> and quick install snippets - -### Install Kickstart - -> [!NOTE] -> [Backup](#FAQ) your previous configuration (if any exists) - -Neovim's configurations are located under the following paths, depending on your OS: - -| OS | PATH | -| :- | :--- | -| Linux, MacOS | `$XDG_CONFIG_HOME/nvim`, `~/.config/nvim` | -| Windows (cmd)| `%localappdata%\nvim\` | -| Windows (powershell)| `$env:LOCALAPPDATA\nvim\` | - -#### Recommended Step - -[Fork](https://docs.github.com/en/get-started/quickstart/fork-a-repo) this repo -so that you have your own copy that you can modify, then install by cloning the -fork to your machine using one of the commands below, depending on your OS. - -> [!NOTE] -> Your fork's URL will be something like this: -> `https://github.com//kickstart.nvim.git` - -You likely want to remove `lazy-lock.json` from your fork's `.gitignore` file -too - it's ignored in the kickstart repo to make maintenance easier, but it's -[recommended to track it in version control](https://lazy.folke.io/usage/lockfile). - -#### Clone kickstart.nvim - -> [!NOTE] -> If following the recommended step above (i.e., forking the repo), replace -> `nvim-lua` with `` in the commands below - -
Linux and Mac - -```sh -git clone https://github.com/nvim-lua/kickstart.nvim.git "${XDG_CONFIG_HOME:-$HOME/.config}"/nvim -``` - -
- -
Windows - -If you're using `cmd.exe`: - -``` -git clone https://github.com/nvim-lua/kickstart.nvim.git "%localappdata%\nvim" -``` - -If you're using `powershell.exe` - -``` -git clone https://github.com/nvim-lua/kickstart.nvim.git "${env:LOCALAPPDATA}\nvim" -``` - -
- -### Post Installation - -Start Neovim - -```sh -nvim -``` - -That's it! Lazy will install all the plugins you have. Use `:Lazy` to view -the current plugin status. Hit `q` to close the window. - -#### Read The Friendly Documentation - -Read through the `init.lua` file in your configuration folder for more -information about extending and exploring Neovim. That also includes -examples of adding popularly requested plugins. - -> [!NOTE] -> For more information about a particular plugin check its repository's documentation. - - -### Getting Started - -[The Only Video You Need to Get Started with Neovim](https://youtu.be/m8C0Cq9Uv9o) - -### FAQ - -* What should I do if I already have a pre-existing Neovim configuration? - * You should back it up and then delete all associated files. - * This includes your existing init.lua and the Neovim files in `~/.local` - which can be deleted with `rm -rf ~/.local/share/nvim/` -* Can I keep my existing configuration in parallel to kickstart? - * Yes! You can use [NVIM_APPNAME](https://neovim.io/doc/user/starting.html#%24NVIM_APPNAME)`=nvim-NAME` - to maintain multiple configurations. For example, you can install the kickstart - configuration in `~/.config/nvim-kickstart` and create an alias: - ``` - alias nvim-kickstart='NVIM_APPNAME="nvim-kickstart" nvim' - ``` - When you run Neovim using `nvim-kickstart` alias it will use the alternative - config directory and the matching local directory - `~/.local/share/nvim-kickstart`. You can apply this approach to any Neovim - distribution that you would like to try out. -* What if I want to "uninstall" this configuration: - * See [lazy.nvim uninstall](https://lazy.folke.io/usage#-uninstalling) information -* Why is the kickstart `init.lua` a single file? Wouldn't it make sense to split it into multiple files? - * The main purpose of kickstart is to serve as a teaching tool and a reference - configuration that someone can easily use to `git clone` as a basis for their own. - As you progress in learning Neovim and Lua, you might consider splitting `init.lua` - into smaller parts. A fork of kickstart that does this while maintaining the - same functionality is available here: - * [kickstart-modular.nvim](https://github.com/dam9000/kickstart-modular.nvim) - * Discussions on this topic can be found here: - * [Restructure the configuration](https://github.com/nvim-lua/kickstart.nvim/issues/218) - * [Reorganize init.lua into a multi-file setup](https://github.com/nvim-lua/kickstart.nvim/pull/473) - -### Install Recipes - -Below you can find OS specific install instructions for Neovim and dependencies. - -After installing all the dependencies continue with the [Install Kickstart](#Install-Kickstart) step. - -#### Windows Installation - -
Windows with Microsoft C++ Build Tools and CMake -Installation may require installing build tools and updating the run command for `telescope-fzf-native` - -See `telescope-fzf-native` documentation for [more details](https://github.com/nvim-telescope/telescope-fzf-native.nvim#installation) - -This requires: - -- Install CMake and the Microsoft C++ Build Tools on Windows - -```lua -{'nvim-telescope/telescope-fzf-native.nvim', build = 'cmake -S. -Bbuild -DCMAKE_BUILD_TYPE=Release && cmake --build build --config Release && cmake --install build --prefix build' } -``` -
-
Windows with gcc/make using chocolatey -Alternatively, one can install gcc and make which don't require changing the config, -the easiest way is to use choco: - -1. install [chocolatey](https://chocolatey.org/install) -either follow the instructions on the page or use winget, -run in cmd as **admin**: -``` -winget install --accept-source-agreements chocolatey.chocolatey -``` - -2. install all requirements using choco, exit the previous cmd and -open a new one so that choco path is set, and run in cmd as **admin**: -``` -choco install -y neovim git ripgrep wget fd unzip gzip mingw make -``` -
-
WSL (Windows Subsystem for Linux) - -``` -wsl --install -wsl -sudo add-apt-repository ppa:neovim-ppa/unstable -y -sudo apt update -sudo apt install make gcc ripgrep unzip git xclip neovim -``` -
- -#### Linux Install -
Ubuntu Install Steps - -``` -sudo add-apt-repository ppa:neovim-ppa/unstable -y -sudo apt update -sudo apt install make gcc ripgrep unzip git xclip neovim -``` -
-
Debian Install Steps - -``` -sudo apt update -sudo apt install make gcc ripgrep unzip git xclip curl - -# Now we install nvim -curl -LO https://github.com/neovim/neovim/releases/latest/download/nvim-linux-x86_64.tar.gz -sudo rm -rf /opt/nvim-linux-x86_64 -sudo mkdir -p /opt/nvim-linux-x86_64 -sudo chmod a+rX /opt/nvim-linux-x86_64 -sudo tar -C /opt -xzf nvim-linux-x86_64.tar.gz - -# make it available in /usr/local/bin, distro installs to /usr/bin -sudo ln -sf /opt/nvim-linux-x86_64/bin/nvim /usr/local/bin/ -``` -
-
Fedora Install Steps - -``` -sudo dnf install -y gcc make git ripgrep fd-find unzip neovim -``` -
- -
Arch Install Steps - -``` -sudo pacman -S --noconfirm --needed gcc make git ripgrep fd unzip neovim -``` -
- +# nvim From 1c56f4cdc0c53967ed9dfa5779db49b9a5411ed5 Mon Sep 17 00:00:00 2001 From: Test Date: Wed, 11 Feb 2026 05:25:24 +0900 Subject: [PATCH 17/42] fix(treesitter): use configs.setup and drop extra install logic --- init.lua | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/init.lua b/init.lua index d74b4f564a3..604c859e820 100644 --- a/init.lua +++ b/init.lua @@ -754,7 +754,6 @@ require('lazy').setup({ bashls = {}, awk_ls = {}, cssls = {}, - htmx = {}, html = {}, jsonls = {}, yamlls = {}, @@ -1045,20 +1044,7 @@ require('lazy').setup({ auto_install = true, }, config = function(_, opts) - local ts = require 'nvim-treesitter' - ts.setup() - - -- Install only parsers that are not already present to avoid repeated startup spam. - if opts.auto_install and opts.ensure_installed and #opts.ensure_installed > 0 then - local installed = ts.get_installed 'parsers' - local missing = vim.tbl_filter(function(lang) - return not vim.list_contains(installed, lang) - end, opts.ensure_installed) - - if #missing > 0 then - ts.install(missing) - end - end + require('nvim-treesitter.configs').setup(opts) vim.api.nvim_create_autocmd('FileType', { group = vim.api.nvim_create_augroup('kickstart-treesitter', { clear = true }), From 7748db4117db7b92980dd59e2681d438ab3f75f7 Mon Sep 17 00:00:00 2001 From: "Paul B. Kim" Date: Tue, 17 Feb 2026 08:14:43 +0900 Subject: [PATCH 18/42] fix(config): wire Vue TS backend and remove stale wrapping module --- init.lua | 18 ++++++-- lua/custom/wrapping.lua | 98 ----------------------------------------- 2 files changed, 15 insertions(+), 101 deletions(-) delete mode 100644 lua/custom/wrapping.lua diff --git a/init.lua b/init.lua index 604c859e820..1f714f0e701 100644 --- a/init.lua +++ b/init.lua @@ -117,8 +117,6 @@ vim.schedule(function() vim.o.clipboard = 'unnamedplus' end) -require('custom.wrapping').setup() - -- Default to 4-space indentation unless overridden by filetype/plugins vim.o.tabstop = 4 vim.o.shiftwidth = 4 @@ -723,6 +721,14 @@ require('lazy').setup({ -- - capabilities (table): Override fields in capabilities. Can be used to disable certain LSP features. -- - settings (table): Override the default settings passed when initializing the server. -- For example, to see the options for `lua_ls`, you could go to: https://luals.github.io/wiki/settings/ + local vue_language_server_path = vim.fn.stdpath 'data' .. '/mason/packages/vue-language-server/node_modules/@vue/language-server' + local vue_typescript_plugin = { + name = '@vue/typescript-plugin', + location = vue_language_server_path, + languages = { 'vue' }, + configNamespace = 'typescript', + } + local servers = { -- Languages clangd = {}, @@ -778,6 +784,13 @@ require('lazy').setup({ -- Tools eslint = {}, astro = {}, + vue_ls = {}, + ts_ls = { + filetypes = { 'vue' }, + init_options = { + plugins = { vue_typescript_plugin }, + }, + }, tailwindcss = {}, docker_language_server = {}, docker_compose_language_service = {}, @@ -792,7 +805,6 @@ require('lazy').setup({ -- https://github.com/pmizio/typescript-tools.nvim -- -- But for many setups, the LSP (`ts_ls`) will work just fine - -- ts_ls = {}, -- } ---@type MasonLspconfigSettings diff --git a/lua/custom/wrapping.lua b/lua/custom/wrapping.lua deleted file mode 100644 index e7720140f97..00000000000 --- a/lua/custom/wrapping.lua +++ /dev/null @@ -1,98 +0,0 @@ -local M = {} - -local defaults = { - width = 100, - patterns = { '*.md', '*.markdown' }, - max_lines = 5000, - max_bytes = 1024 * 1024, -} - -local function apply_wrap_options(local_opts, width) - local_opts.wrap = true - local_opts.linebreak = true - local_opts.breakindent = true - local_opts.textwidth = width - local_opts.colorcolumn = '' - local_opts.formatoptions:append 't' - local_opts.formatoptions:append 'a' - local_opts.formatoptions:remove 'l' -end - -local function can_reflow(bufnr, opts) - if not vim.api.nvim_buf_is_valid(bufnr) or not vim.bo[bufnr].modifiable or vim.bo[bufnr].buftype ~= '' then - return false - end - - if opts.max_lines and vim.api.nvim_buf_line_count(bufnr) > opts.max_lines then - return false - end - - local name = vim.api.nvim_buf_get_name(bufnr) - if name ~= '' and opts.max_bytes then - local stat = (vim.uv or vim.loop).fs_stat(name) - if stat and stat.size and stat.size > opts.max_bytes then - return false - end - end - - return true -end - -local function reflow_whole_buffer(bufnr, preserve_view, opts) - if not can_reflow(bufnr, opts) then - return - end - - vim.api.nvim_buf_call(bufnr, function() - local view = preserve_view and vim.fn.winsaveview() or nil - vim.cmd 'silent keepjumps normal! gggqG' - if view then - vim.fn.winrestview(view) - end - end) -end - -function M.setup(opts) - opts = vim.tbl_deep_extend('force', defaults, opts or {}) - - local group = vim.api.nvim_create_augroup('custom_wrapping_rules', { clear = true }) - - vim.api.nvim_create_autocmd('FileType', { - group = group, - pattern = 'markdown', - callback = function() - -- Apply after ftplugins so local overrides don't drop wrap options. - vim.schedule(function() - apply_wrap_options(vim.opt_local, opts.width) - end) - end, - }) - - vim.api.nvim_create_autocmd('BufReadPost', { - group = group, - pattern = opts.patterns, - callback = function(args) - -- Reflow existing prose on open so the hard wrap rule is applied immediately. - if vim.b[args.buf].did_initial_wrap then - return - end - vim.b[args.buf].did_initial_wrap = true - vim.schedule(function() - reflow_whole_buffer(args.buf, false, opts) - end) - end, - }) - - vim.api.nvim_create_autocmd('BufWritePre', { - group = group, - pattern = opts.patterns, - callback = function(args) - if not vim.bo[args.buf].modified then - return - end - reflow_whole_buffer(args.buf, true, opts) - end, - }) -end - -return M From 8894b26dbcd15a1ed61f0d2cd5dce8f0c2fc99d2 Mon Sep 17 00:00:00 2001 From: "Paul B. Kim" Date: Tue, 17 Feb 2026 08:59:59 +0900 Subject: [PATCH 19/42] feat(git): add diffview shortcuts --- lua/custom/plugins/diffview.lua | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 lua/custom/plugins/diffview.lua diff --git a/lua/custom/plugins/diffview.lua b/lua/custom/plugins/diffview.lua new file mode 100644 index 00000000000..4e94670c0fc --- /dev/null +++ b/lua/custom/plugins/diffview.lua @@ -0,0 +1,19 @@ +return { + 'sindrets/diffview.nvim', + dependencies = { 'nvim-lua/plenary.nvim' }, + cmd = { + 'DiffviewOpen', + 'DiffviewClose', + 'DiffviewFileHistory', + 'DiffviewFocusFiles', + 'DiffviewToggleFiles', + 'DiffviewRefresh', + }, + keys = { + { 'gd', 'DiffviewOpen', desc = '[G]it [D]iff view' }, + { 'gD', 'DiffviewClose', desc = '[G]it [D]iff close' }, + { 'gf', 'DiffviewFileHistory %', desc = '[G]it [F]ile history' }, + { 'gF', 'DiffviewFileHistory', desc = '[G]it [F]ull history' }, + }, + opts = {}, +} From 1ce6776b49eddddcdfd8bfec686acd0f9fd2531e Mon Sep 17 00:00:00 2001 From: "Paul B. Kim" Date: Tue, 17 Feb 2026 09:38:58 +0900 Subject: [PATCH 20/42] fix(treesitter): use new nvim-treesitter setup API --- init.lua | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/init.lua b/init.lua index 1f714f0e701..0194b1ff896 100644 --- a/init.lua +++ b/init.lua @@ -1056,7 +1056,12 @@ require('lazy').setup({ auto_install = true, }, config = function(_, opts) - require('nvim-treesitter.configs').setup(opts) + local treesitter = require 'nvim-treesitter' + treesitter.setup() + + if opts.auto_install and opts.ensure_installed and #opts.ensure_installed > 0 then + treesitter.install(opts.ensure_installed) + end vim.api.nvim_create_autocmd('FileType', { group = vim.api.nvim_create_augroup('kickstart-treesitter', { clear = true }), From ef1e7af98ee52d7ae297d6b1f4fcb9941768b11b Mon Sep 17 00:00:00 2001 From: "Paul B. Kim" Date: Tue, 17 Feb 2026 09:38:58 +0900 Subject: [PATCH 21/42] chore(gitignore): ignore local Serena metadata --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 005b535b606..e4b332903af 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ nvim spell/ lazy-lock.json +.serena/ From e5a8662c919927aaaef82d4afe84f42adf780ac9 Mon Sep 17 00:00:00 2001 From: "Paul B. Kim" Date: Tue, 17 Feb 2026 12:31:50 +0900 Subject: [PATCH 22/42] docs(nvim): add README and :help reference guide --- README.md | 40 ++++++++++++++++++++++++++++++++++++++++ doc/nvim.txt | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ doc/tags | 6 ++++++ 3 files changed, 96 insertions(+) create mode 100644 doc/nvim.txt diff --git a/README.md b/README.md index a3e67b10460..78a198b8d2e 100644 --- a/README.md +++ b/README.md @@ -1 +1,41 @@ # nvim + +Personal Neovim config based on `kickstart.nvim`, with custom plugins and +workflows for LSP, Git, and terminal-first editing. + +## Quick checks + +Use these commands after config changes: + +```sh +nvim --headless "+qa" +nvim --headless "+checkhealth" "+qa" +luac -p init.lua lua/custom/**/*.lua +``` + +## Git workflow keymaps + +- `gg`: open Neogit UI +- `gd`: open Diffview +- `gD`: close Diffview +- `gf`: file history in Diffview (current file) +- `gF`: repository history in Diffview +- `h...`: hunk actions from Gitsigns (`:which-key h`) + +## LSP notes + +- Vue support uses `vue_ls` plus `ts_ls` scoped to `vue` filetypes. +- `ts_ls` is wired with `@vue/typescript-plugin`, so `vue_ls` can forward + TypeScript requests in `.vue` buffers. +- `typescript-tools.nvim` remains available for TypeScript/JavaScript workflows. + +## Treesitter notes + +- Uses the current API: `require('nvim-treesitter').setup()`. +- Config installs parsers from `ensure_installed` automatically when needed. + +## Help docs + +This repo ships a Vim help doc. After opening Neovim, run: + +- `:help nvim-config` diff --git a/doc/nvim.txt b/doc/nvim.txt new file mode 100644 index 00000000000..9efabeb6ef3 --- /dev/null +++ b/doc/nvim.txt @@ -0,0 +1,50 @@ +============================================================================== +NVIM CONFIG *nvim-config* + *nvimn-config* + +This is a custom Neovim configuration based on kickstart.nvim. + +QUICK CHECKS *nvim-config-quick-checks* + +Run these commands after config changes: + +> + nvim --headless "+qa" + nvim --headless "+checkhealth" "+qa" + luac -p init.lua lua/custom/**/*.lua +< + +GIT KEYMAPS *nvim-config-git* + +Core git mappings in normal mode: + +- gg: open Neogit +- gd: open Diffview +- gD: close Diffview +- gf: Diffview file history for current file +- gF: Diffview repository history +- h...: Gitsigns hunk actions + +Use |which-key| with g and h to discover more actions. + +LSP: VUE + TYPESCRIPT *nvim-config-vue* + +Vue buffers rely on both: + +- `vue_ls` for Vue language features +- `ts_ls` (filetype-scoped to `vue`) with `@vue/typescript-plugin` + +This pairing allows `vue_ls` to forward TypeScript requests for `.vue` files. + +TREESITTER *nvim-config-treesitter* + +This config uses the current nvim-treesitter API: + +> + require('nvim-treesitter').setup() +< + +Configured parsers are installed from `ensure_installed` as needed. + +============================================================================== + vim:tw=78:ts=8:ft=help:norl: diff --git a/doc/tags b/doc/tags index 687ae7721d9..857a702e9a4 100644 --- a/doc/tags +++ b/doc/tags @@ -1,3 +1,9 @@ kickstart-is kickstart.txt /*kickstart-is* kickstart-is-not kickstart.txt /*kickstart-is-not* kickstart.nvim kickstart.txt /*kickstart.nvim* +nvim-config nvim.txt /*nvim-config* +nvim-config-git nvim.txt /*nvim-config-git* +nvim-config-quick-checks nvim.txt /*nvim-config-quick-checks* +nvim-config-treesitter nvim.txt /*nvim-config-treesitter* +nvim-config-vue nvim.txt /*nvim-config-vue* +nvimn-config nvim.txt /*nvimn-config* From 6fb17303bc79b7d68338d190703ca736adc32d4e Mon Sep 17 00:00:00 2001 From: "Paul B. Kim" Date: Tue, 17 Feb 2026 15:09:13 +0900 Subject: [PATCH 23/42] feat(workflow): add backend engineering plugin stack --- init.lua | 40 +++++++- lua/custom/plugins/cmake_tools.lua | 29 ++++++ lua/custom/plugins/dap_js_ts.lua | 40 ++++++++ lua/custom/plugins/debug.lua | 1 + lua/custom/plugins/grug_far.lua | 27 +++++ lua/custom/plugins/helm.lua | 3 + lua/custom/plugins/lint.lua | 35 ++++++- lua/custom/plugins/neotest.lua | 98 +++++++++++++++++++ lua/custom/plugins/oil.lua | 14 +++ lua/custom/plugins/treesitter_context.lua | 17 ++++ lua/custom/plugins/treesitter_textobjects.lua | 38 +++++++ lua/custom/plugins/trouble.lua | 45 +++++++++ 12 files changed, 385 insertions(+), 2 deletions(-) create mode 100644 lua/custom/plugins/cmake_tools.lua create mode 100644 lua/custom/plugins/dap_js_ts.lua create mode 100644 lua/custom/plugins/grug_far.lua create mode 100644 lua/custom/plugins/helm.lua create mode 100644 lua/custom/plugins/neotest.lua create mode 100644 lua/custom/plugins/oil.lua create mode 100644 lua/custom/plugins/treesitter_context.lua create mode 100644 lua/custom/plugins/treesitter_textobjects.lua create mode 100644 lua/custom/plugins/trouble.lua diff --git a/init.lua b/init.lua index 0194b1ff896..71c0867e4f6 100644 --- a/init.lua +++ b/init.lua @@ -395,13 +395,18 @@ require('lazy').setup({ -- Document existing key chains spec = { { 'a', group = 'Harpoon [A]dd' }, + { 'c', group = '[C]Make' }, { 'd', group = '[D]iagnostics' }, + { 'e', group = '[E]xplorer' }, { 'g', group = '[G]it' }, { 'm', group = '[M]arkdown' }, + { 'n', group = '[N]eotest' }, { 'o', group = '[O]pencode' }, { 's', group = '[S]earch' }, { 't', group = '[T]oggle' }, { 'w', group = '[W]indow' }, + { 'x', group = 'Trouble' }, + { 'j', group = '[J]ump' }, { 'h', group = 'Git [H]unk', mode = { 'n', 'v' } }, }, }, @@ -762,7 +767,23 @@ require('lazy').setup({ cssls = {}, html = {}, jsonls = {}, - yamlls = {}, + yamlls = { + settings = { + yaml = { + schemas = { + ['https://raw.githubusercontent.com/yannh/kubernetes-json-schema/refs/heads/master/v1.32.1-standalone-strict/all.json'] = { + '*.k8s.yaml', + 'k8s/**/*.yaml', + 'manifests/**/*.yaml', + 'kubernetes/**/*.yaml', + }, + ['https://json.schemastore.org/chart'] = 'Chart.yaml', + ['https://json.schemastore.org/helmfile.json'] = 'helmfile.yaml', + ['https://json.schemastore.org/kustomization.json'] = 'kustomization.yaml', + }, + }, + }, + }, taplo = {}, elixirls = {}, gh_actions_ls = {}, @@ -785,6 +806,7 @@ require('lazy').setup({ eslint = {}, astro = {}, vue_ls = {}, + helm_ls = {}, ts_ls = { filetypes = { 'vue' }, init_options = { @@ -830,6 +852,13 @@ require('lazy').setup({ vim.list_extend(ensure_installed, { 'stylua', -- Used to format Lua code 'markdownlint', -- Used by nvim-lint for Markdown buffers + 'prettierd', + 'prettier', + 'clang-format', + 'hadolint', + 'yamllint', + 'js-debug-adapter', + 'codelldb', }) require('mason-tool-installer').setup { ensure_installed = ensure_installed } @@ -876,6 +905,15 @@ require('lazy').setup({ end, formatters_by_ft = { lua = { 'stylua' }, + javascript = { 'prettierd', 'prettier', stop_after_first = true }, + javascriptreact = { 'prettierd', 'prettier', stop_after_first = true }, + typescript = { 'prettierd', 'prettier', stop_after_first = true }, + typescriptreact = { 'prettierd', 'prettier', stop_after_first = true }, + json = { 'prettierd', 'prettier', stop_after_first = true }, + jsonc = { 'prettierd', 'prettier', stop_after_first = true }, + yaml = { 'prettierd', 'prettier', stop_after_first = true }, + c = { 'clang_format' }, + cpp = { 'clang_format' }, -- Conform can also run multiple formatters sequentially -- python = { "isort", "black" }, -- diff --git a/lua/custom/plugins/cmake_tools.lua b/lua/custom/plugins/cmake_tools.lua new file mode 100644 index 00000000000..b532dc2dd37 --- /dev/null +++ b/lua/custom/plugins/cmake_tools.lua @@ -0,0 +1,29 @@ +return { + 'Civitasv/cmake-tools.nvim', + dependencies = { 'nvim-lua/plenary.nvim' }, + cmd = { + 'CMakeGenerate', + 'CMakeBuild', + 'CMakeRun', + 'CMakeTest', + 'CMakeSelectBuildType', + 'CMakeSelectBuildTarget', + }, + ft = { 'cmake' }, + keys = { + { 'cg', 'CMakeGenerate', desc = '[C]Make [G]enerate' }, + { 'cb', 'CMakeBuild', desc = '[C]Make [B]uild' }, + { 'cr', 'CMakeRun', desc = '[C]Make [R]un' }, + { 'ct', 'CMakeTest', desc = '[C]Make [T]est' }, + { 'cc', 'CMakeSelectBuildType', desc = '[C]Make [C]onfiguration' }, + }, + opts = { + cmake_generate_options = { '-DCMAKE_EXPORT_COMPILE_COMMANDS=1' }, + cmake_executor = { + name = 'quickfix', + }, + cmake_runner = { + name = 'terminal', + }, + }, +} diff --git a/lua/custom/plugins/dap_js_ts.lua b/lua/custom/plugins/dap_js_ts.lua new file mode 100644 index 00000000000..8dc13a726ad --- /dev/null +++ b/lua/custom/plugins/dap_js_ts.lua @@ -0,0 +1,40 @@ +return { + 'mxsdev/nvim-dap-vscode-js', + dependencies = { 'mfussenegger/nvim-dap' }, + ft = { 'javascript', 'javascriptreact', 'typescript', 'typescriptreact', 'vue' }, + config = function() + require('dap-vscode-js').setup { + debugger_cmd = { 'js-debug-adapter' }, + adapters = { + 'pwa-node', + 'pwa-chrome', + 'pwa-msedge', + 'node-terminal', + 'pwa-extensionHost', + }, + } + + local dap = require 'dap' + local configs = { + { + type = 'pwa-node', + request = 'launch', + name = 'Launch current file (Node)', + program = '${file}', + cwd = '${workspaceFolder}', + sourceMaps = true, + }, + { + type = 'pwa-node', + request = 'attach', + name = 'Attach to process', + processId = require('dap.utils').pick_process, + cwd = '${workspaceFolder}', + }, + } + + for _, language in ipairs { 'javascript', 'javascriptreact', 'typescript', 'typescriptreact', 'vue' } do + dap.configurations[language] = vim.list_extend(dap.configurations[language] or {}, configs) + end + end, +} diff --git a/lua/custom/plugins/debug.lua b/lua/custom/plugins/debug.lua index 753cb0cedd3..ab98923693d 100644 --- a/lua/custom/plugins/debug.lua +++ b/lua/custom/plugins/debug.lua @@ -95,6 +95,7 @@ return { ensure_installed = { -- Update this to ensure that you have the debuggers for the langs you want 'delve', + 'codelldb', }, } diff --git a/lua/custom/plugins/grug_far.lua b/lua/custom/plugins/grug_far.lua new file mode 100644 index 00000000000..7023842f7a0 --- /dev/null +++ b/lua/custom/plugins/grug_far.lua @@ -0,0 +1,27 @@ +return { + 'MagicDuck/grug-far.nvim', + cmd = { 'GrugFar', 'GrugFarWithin' }, + opts = {}, + keys = { + { + 'sR', + function() + require('grug-far').open { + prefills = { + search = vim.fn.expand '', + }, + } + end, + mode = 'n', + desc = '[S]earch project [R]eplace', + }, + { + 'sR', + function() + require('grug-far').with_visual_selection() + end, + mode = 'x', + desc = '[S]earch project [R]eplace selection', + }, + }, +} diff --git a/lua/custom/plugins/helm.lua b/lua/custom/plugins/helm.lua new file mode 100644 index 00000000000..d4b5e1b4bf4 --- /dev/null +++ b/lua/custom/plugins/helm.lua @@ -0,0 +1,3 @@ +return { + 'towolf/vim-helm', +} diff --git a/lua/custom/plugins/lint.lua b/lua/custom/plugins/lint.lua index 61ec2d278f5..2c194f59436 100644 --- a/lua/custom/plugins/lint.lua +++ b/lua/custom/plugins/lint.lua @@ -7,6 +7,9 @@ return { local lint = require 'lint' lint.linters_by_ft = { markdown = { 'markdownlint' }, + dockerfile = { 'hadolint' }, + yaml = { 'yamllint' }, + ['yaml.helm-values'] = { 'yamllint' }, } lint.linters.markdownlint = vim.tbl_deep_extend('force', lint.linters.markdownlint or {}, { args = { @@ -56,11 +59,41 @@ return { vim.api.nvim_create_autocmd({ 'BufEnter', 'BufWritePost', 'InsertLeave' }, { group = lint_augroup, callback = function() + local function executable_cmd(cmd) + if type(cmd) == 'function' then + local ok, value = pcall(cmd) + if not ok then + return nil + end + return value + end + return cmd + end + + local function available_linters_for(ft) + local configured = lint.linters_by_ft[ft] or {} + local available = {} + + for _, linter_name in ipairs(configured) do + local linter = lint.linters[linter_name] + local cmd = linter and executable_cmd(linter.cmd) + if cmd == nil or cmd == '' or vim.fn.executable(cmd) == 1 then + table.insert(available, linter_name) + end + end + + return available + end + -- Only run the linter in buffers that you can modify in order to -- avoid superfluous noise, notably within the handy LSP pop-ups that -- describe the hovered symbol using Markdown. if vim.bo.modifiable then - lint.try_lint() + local filetype = vim.bo.filetype + local linters = available_linters_for(filetype) + if #linters > 0 then + lint.try_lint(linters) + end end end, }) diff --git a/lua/custom/plugins/neotest.lua b/lua/custom/plugins/neotest.lua new file mode 100644 index 00000000000..1b0d2937b6f --- /dev/null +++ b/lua/custom/plugins/neotest.lua @@ -0,0 +1,98 @@ +return { + 'nvim-neotest/neotest', + dependencies = { + 'nvim-neotest/nvim-nio', + 'nvim-lua/plenary.nvim', + 'nvim-treesitter/nvim-treesitter', + 'nvim-neotest/neotest-jest', + 'marilari88/neotest-vitest', + 'alfaix/neotest-gtest', + }, + keys = { + { + 'nr', + function() + require('neotest').run.run() + end, + desc = '[N]eotest run nea[r]est', + }, + { + 'nf', + function() + require('neotest').run.run(vim.fn.expand '%') + end, + desc = '[N]eotest run [F]ile', + }, + { + 'ns', + function() + require('neotest').run.run(vim.fn.getcwd()) + end, + desc = '[N]eotest run [S]uite', + }, + { + 'nd', + function() + require('neotest').run.run { strategy = 'dap' } + end, + desc = '[N]eotest [D]ebug nearest', + }, + { + 'nn', + function() + require('neotest').summary.toggle() + end, + desc = '[N]eotest summary', + }, + { + 'no', + function() + require('neotest').output.open { enter = true, auto_close = true } + end, + desc = '[N]eotest [O]utput', + }, + { + 'nO', + function() + require('neotest').output_panel.toggle() + end, + desc = '[N]eotest [O]utput panel', + }, + { + 'na', + function() + require('neotest').run.attach() + end, + desc = '[N]eotest [A]ttach', + }, + { + 'nS', + function() + require('neotest').run.stop() + end, + desc = '[N]eotest [S]top', + }, + }, + config = function() + local adapters = {} + + local ok_jest, jest = pcall(require, 'neotest-jest') + if ok_jest then + table.insert(adapters, jest {}) + end + + local ok_vitest, vitest = pcall(require, 'neotest-vitest') + if ok_vitest then + table.insert(adapters, vitest {}) + end + + local ok_gtest, gtest = pcall(require, 'neotest-gtest') + if ok_gtest then + table.insert(adapters, gtest.setup {}) + end + + require('neotest').setup { + adapters = adapters, + } + end, +} diff --git a/lua/custom/plugins/oil.lua b/lua/custom/plugins/oil.lua new file mode 100644 index 00000000000..8a6d676717a --- /dev/null +++ b/lua/custom/plugins/oil.lua @@ -0,0 +1,14 @@ +return { + 'stevearc/oil.nvim', + cmd = { 'Oil' }, + keys = { + { 'eo', 'Oil', desc = '[E]xplorer [O]il' }, + }, + opts = { + default_file_explorer = false, + columns = { 'icon' }, + view_options = { + show_hidden = true, + }, + }, +} diff --git a/lua/custom/plugins/treesitter_context.lua b/lua/custom/plugins/treesitter_context.lua new file mode 100644 index 00000000000..2904e80767c --- /dev/null +++ b/lua/custom/plugins/treesitter_context.lua @@ -0,0 +1,17 @@ +return { + 'nvim-treesitter/nvim-treesitter-context', + opts = { + max_lines = 4, + multiline_threshold = 20, + trim_scope = 'outer', + }, + keys = { + { + 'tc', + function() + require('treesitter-context').toggle() + end, + desc = '[T]oggle code [C]ontext', + }, + }, +} diff --git a/lua/custom/plugins/treesitter_textobjects.lua b/lua/custom/plugins/treesitter_textobjects.lua new file mode 100644 index 00000000000..417daad429a --- /dev/null +++ b/lua/custom/plugins/treesitter_textobjects.lua @@ -0,0 +1,38 @@ +return { + 'nvim-treesitter/nvim-treesitter-textobjects', + branch = 'main', + dependencies = { 'nvim-treesitter/nvim-treesitter' }, + config = function() + require('nvim-treesitter-textobjects').setup { + move = { + set_jumps = true, + }, + } + + local move = require 'nvim-treesitter-textobjects.move' + + vim.keymap.set('n', 'jm', function() + move.goto_next_start '@function.outer' + end, { desc = '[J]ump next [M]ethod start' }) + + vim.keymap.set('n', 'jM', function() + move.goto_next_end '@function.outer' + end, { desc = '[J]ump next [M]ethod end' }) + + vim.keymap.set('n', 'jk', function() + move.goto_previous_start '@function.outer' + end, { desc = '[J]ump previous method start' }) + + vim.keymap.set('n', 'jK', function() + move.goto_previous_end '@function.outer' + end, { desc = '[J]ump previous method end' }) + + vim.keymap.set('n', 'jc', function() + move.goto_next_start '@class.outer' + end, { desc = '[J]ump next [C]lass start' }) + + vim.keymap.set('n', 'jC', function() + move.goto_previous_start '@class.outer' + end, { desc = '[J]ump previous class start' }) + end, +} diff --git a/lua/custom/plugins/trouble.lua b/lua/custom/plugins/trouble.lua new file mode 100644 index 00000000000..d47f80a791b --- /dev/null +++ b/lua/custom/plugins/trouble.lua @@ -0,0 +1,45 @@ +return { + 'folke/trouble.nvim', + cmd = { 'Trouble' }, + opts = {}, + keys = { + { + 'xx', + function() + require('trouble').toggle 'diagnostics' + end, + desc = 'Trouble: all diagnostics', + }, + { + 'xw', + function() + require('trouble').toggle 'diagnostics' + end, + desc = 'Trouble: [W]orkspace diagnostics', + }, + { + 'xd', + function() + require('trouble').toggle { + mode = 'diagnostics', + filter = { buf = 0 }, + } + end, + desc = 'Trouble: [D]ocument diagnostics', + }, + { + 'xq', + function() + require('trouble').toggle 'qflist' + end, + desc = 'Trouble: [Q]uickfix list', + }, + { + 'xl', + function() + require('trouble').toggle 'loclist' + end, + desc = 'Trouble: [L]ocation list', + }, + }, +} From a06b051e6d535c34668a659e0fb32c24b8733035 Mon Sep 17 00:00:00 2001 From: "Paul B. Kim" Date: Tue, 17 Feb 2026 15:09:21 +0900 Subject: [PATCH 24/42] docs(readme): document backend workflow and keymaps --- README.md | 201 +++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 184 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 78a198b8d2e..1bd1f779c51 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,31 @@ # nvim -Personal Neovim config based on `kickstart.nvim`, with custom plugins and -workflows for LSP, Git, and terminal-first editing. +Personal Neovim config based on `kickstart.nvim`, tuned for backend-heavy work +in JavaScript/TypeScript, C/C++, Kubernetes, and server development. -## Quick checks +This README is the working manual for what is configured, why it exists, and +how to use it quickly. -Use these commands after config changes: +## Design principles + +- Additive, not disruptive: new plugins and mappings are added without replacing + existing behavior. +- Modular plugin specs: each concern lives in `lua/custom/plugins/*.lua`. +- Terminal-friendly workflow: most actions map to short leader sequences and + preserve CLI-first habits. +- Keep startup stable: major features are lazy-loaded by command, filetype, or + explicit keymaps where possible. + +## Repository layout + +- `init.lua`: base options, core plugin setup, LSP, formatting, treesitter. +- `lua/custom/plugins/*.lua`: modular plugin specs and custom behavior. +- `doc/nvim.txt`: Vim help document (`:help nvim-config`). +- `lazy-lock.json`: plugin lockfile managed by lazy.nvim. + +## Quick validation commands + +Run these after config changes: ```sh nvim --headless "+qa" @@ -13,29 +33,176 @@ nvim --headless "+checkhealth" "+qa" luac -p init.lua lua/custom/**/*.lua ``` -## Git workflow keymaps +Useful maintenance commands: + +```sh +nvim --headless "+Lazy! sync" "+qa" +nvim --headless "+MasonToolsInstallSync" "+qa" +``` + +## Keymap manual + +### Git workflow - `gg`: open Neogit UI - `gd`: open Diffview - `gD`: close Diffview -- `gf`: file history in Diffview (current file) -- `gF`: repository history in Diffview -- `h...`: hunk actions from Gitsigns (`:which-key h`) +- `gf`: Diffview file history (current file) +- `gF`: Diffview repo history +- `h...`: Gitsigns hunk actions (`:which-key h`) + +### Diagnostics and code navigation + +- `xx`: Trouble diagnostics +- `xw`: Trouble workspace diagnostics +- `xd`: Trouble current buffer diagnostics +- `xq`: Trouble quickfix list +- `xl`: Trouble location list +- `tc`: toggle treesitter context header +- `jm` / `jk`: next/previous function start +- `jM` / `jK`: next/previous function end +- `jc` / `jC`: next/previous class start + +### Tests and debug + +- `nr`: neotest run nearest +- `nf`: neotest run current file +- `ns`: neotest run suite (cwd) +- `nd`: neotest debug nearest via DAP +- `nn`: neotest summary toggle +- `no`: neotest output for nearest test +- `nO`: neotest output panel toggle +- `na`: attach to running neotest process +- `nS`: stop neotest run + +Existing DAP keys are unchanged: + +- `` continue/start, `` step into, `` step over, `` step out +- `` toggle dap-ui, `b` toggle breakpoint, `B` conditional bp + +### Search, replace, and explorer + +- `sR`: project search/replace with grug-far +- `eo`: open Oil explorer view (optional, non-default explorer) + +### CMake workflow (optional) + +- `cg`: CMake generate +- `cb`: CMake build +- `cr`: CMake run +- `ct`: CMake test +- `cc`: CMake select build type + +## Plugin stack by workflow + +### LSP and language intelligence + +- `nvim-lspconfig` + `mason-lspconfig` + `mason-tool-installer` +- Vue integration: + - `vue_ls` enabled + - `ts_ls` scoped to `vue` with `@vue/typescript-plugin` + - keeps `typescript-tools.nvim` available for TS/JS workflows +- Kubernetes/Helm integration: + - `yamlls` with schema mappings for Kubernetes, Helm chart, Helmfile, + and Kustomize + - `helm_ls` enabled + - `vim-helm` added for Helm syntax support + +### Treesitter and structural editing + +- `nvim-treesitter` uses current API (`require('nvim-treesitter').setup()`). +- `nvim-treesitter-context` provides sticky scope context. +- `nvim-treesitter-textobjects` adds structure-aware function/class jumps. + +### Debugging + +- Core: `nvim-dap`, `nvim-dap-ui`, `mason-nvim-dap`, `nvim-dap-go`. +- JS/TS: `nvim-dap-vscode-js` configured with `js-debug-adapter` and + `pwa-node` launch/attach defaults. +- C/C++: `codelldb` installation added through Mason DAP setup. + +### Testing + +- `neotest` core with adapters: + - `neotest-jest` + - `neotest-vitest` + - `neotest-gtest` + +Notes for C++ tests: + +- `neotest-gtest` needs executable mapping per project (use `:ConfigureGtest` + from the neotest summary window). + +### Formatting and linting + +- Formatting via `conform.nvim`: + - JS/TS/JSON/YAML: `prettierd` -> `prettier` + - C/C++: `clang_format` + - Lua: `stylua` +- Linting via `nvim-lint`: + - markdown: `markdownlint` + - dockerfile: `hadolint` + - yaml / yaml.helm-values: `yamllint` + +Linting is executable-aware for configured linters to avoid noisy diagnostics +when a linter binary is unavailable. + +### Project workflow plugins + +- `trouble.nvim`: focused diagnostics/issues panel +- `grug-far.nvim`: project-wide search/replace +- `oil.nvim`: optional file editing explorer (does not replace default explorer) +- `cmake-tools.nvim`: CMake build/run/test helpers (lazy and optional) + +## Mason-managed tools and servers + +This config ensures installation for key tools used by the workflows above, +including: + +- `prettierd`, `prettier`, `clang-format` +- `hadolint`, `yamllint`, `markdownlint`, `stylua` +- `js-debug-adapter`, `codelldb` +- configured LSP servers from `servers` table (including `helm_ls`) + +Check with `:Mason` and install manually if needed. + +## Typical workflows + +### JS/TS service workflow + +1. Edit with LSP + treesitter context. +2. Run nearest test with `nr` or file with `nf`. +3. Debug test or code path with `nd` / ``. +4. Use `sR` for safe project refactors. + +### C/C++ workflow + +1. Navigate symbols with `jm/jk/jc/jC`. +2. Build/test with CMake mappings if project uses CMake. +3. Debug using existing DAP keys with `codelldb` installed. +4. Run gtest via neotest after `:ConfigureGtest` setup. + +### Kubernetes/Helm workflow -## LSP notes +1. Edit manifests with `yamlls` schema-backed completion/validation. +2. Edit charts/templates with Helm support (`helm_ls`, `vim-helm`). +3. Use `sR` for scoped multi-file YAML refactors. -- Vue support uses `vue_ls` plus `ts_ls` scoped to `vue` filetypes. -- `ts_ls` is wired with `@vue/typescript-plugin`, so `vue_ls` can forward - TypeScript requests in `.vue` buffers. -- `typescript-tools.nvim` remains available for TypeScript/JavaScript workflows. +## Troubleshooting -## Treesitter notes +- Verify startup: `nvim --headless "+qa"` +- Verify health: `nvim --headless "+checkhealth" "+qa"` +- Verify LSP clients in current buffer: `:LspInfo` +- Verify formatter mapping: `:ConformInfo` +- Verify Mason state: `:Mason` +- Re-sync plugins: `:Lazy sync` -- Uses the current API: `require('nvim-treesitter').setup()`. -- Config installs parsers from `ensure_installed` automatically when needed. +If a new feature appears missing, first confirm lazy-loading trigger +(keymap/filetype/command) was actually used. ## Help docs -This repo ships a Vim help doc. After opening Neovim, run: +This repo ships a Vim help file: - `:help nvim-config` +- `:help nvimn-config` From 1da7fbeaf3d3422a14059a13ef99928d595d0e6d Mon Sep 17 00:00:00 2001 From: "Paul B. Kim" Date: Tue, 17 Feb 2026 16:01:32 +0900 Subject: [PATCH 25/42] feat(debug): add C/C++ codelldb profile and textobject selects Add additive C/C++ DAP launch defaults and treesitter select textobjects to speed structural editing while preserving existing keymaps and DAP controls. --- README.md | 6 ++- lua/custom/dap_cpp.lua | 43 +++++++++++++++++++ lua/custom/plugins/debug.lua | 2 + lua/custom/plugins/treesitter_textobjects.lua | 9 ++++ 4 files changed, 58 insertions(+), 2 deletions(-) create mode 100644 lua/custom/dap_cpp.lua diff --git a/README.md b/README.md index 1bd1f779c51..cb53f9c957f 100644 --- a/README.md +++ b/README.md @@ -62,6 +62,7 @@ nvim --headless "+MasonToolsInstallSync" "+qa" - `jm` / `jk`: next/previous function start - `jM` / `jK`: next/previous function end - `jc` / `jC`: next/previous class start +- Textobject select (operator-pending/visual): `af`/`if` for function, `ac`/`ic` for class ### Tests and debug @@ -119,7 +120,8 @@ Existing DAP keys are unchanged: - Core: `nvim-dap`, `nvim-dap-ui`, `mason-nvim-dap`, `nvim-dap-go`. - JS/TS: `nvim-dap-vscode-js` configured with `js-debug-adapter` and `pwa-node` launch/attach defaults. -- C/C++: `codelldb` installation added through Mason DAP setup. +- C/C++: `codelldb` installation via Mason and baseline launch profile + (`Launch current file (codelldb)`). ### Testing @@ -179,7 +181,7 @@ Check with `:Mason` and install manually if needed. 1. Navigate symbols with `jm/jk/jc/jC`. 2. Build/test with CMake mappings if project uses CMake. -3. Debug using existing DAP keys with `codelldb` installed. +3. Debug using existing DAP keys and select `Launch current file (codelldb)`. 4. Run gtest via neotest after `:ConfigureGtest` setup. ### Kubernetes/Helm workflow diff --git a/lua/custom/dap_cpp.lua b/lua/custom/dap_cpp.lua new file mode 100644 index 00000000000..483fc41e9a0 --- /dev/null +++ b/lua/custom/dap_cpp.lua @@ -0,0 +1,43 @@ +local M = {} + +local function has_configuration(configurations, name, adapter) + for _, configuration in ipairs(configurations or {}) do + if configuration.name == name and configuration.type == adapter then + return true + end + end + + return false +end + +function M.setup(dap) + dap = dap or require 'dap' + + dap.adapters.codelldb = dap.adapters.codelldb or { + type = 'executable', + command = 'codelldb', + } + + local launch_name = 'Launch current file (codelldb)' + local launch_configuration = { + name = launch_name, + type = 'codelldb', + request = 'launch', + program = function() + return vim.fn.input('Path to executable: ', vim.fn.getcwd() .. '/', 'file') + end, + cwd = '${workspaceFolder}', + stopOnEntry = false, + args = {}, + } + + for _, language in ipairs { 'c', 'cpp' } do + dap.configurations[language] = dap.configurations[language] or {} + + if not has_configuration(dap.configurations[language], launch_name, 'codelldb') then + table.insert(dap.configurations[language], vim.deepcopy(launch_configuration)) + end + end +end + +return M diff --git a/lua/custom/plugins/debug.lua b/lua/custom/plugins/debug.lua index ab98923693d..ff9c8cb0535 100644 --- a/lua/custom/plugins/debug.lua +++ b/lua/custom/plugins/debug.lua @@ -145,5 +145,7 @@ return { detached = vim.fn.has 'win32' == 0, }, } + + require('custom.dap_cpp').setup(dap) end, } diff --git a/lua/custom/plugins/treesitter_textobjects.lua b/lua/custom/plugins/treesitter_textobjects.lua index 417daad429a..f904c5b70b2 100644 --- a/lua/custom/plugins/treesitter_textobjects.lua +++ b/lua/custom/plugins/treesitter_textobjects.lua @@ -4,6 +4,15 @@ return { dependencies = { 'nvim-treesitter/nvim-treesitter' }, config = function() require('nvim-treesitter-textobjects').setup { + select = { + lookahead = true, + keymaps = { + ['af'] = '@function.outer', + ['if'] = '@function.inner', + ['ac'] = '@class.outer', + ['ic'] = '@class.inner', + }, + }, move = { set_jumps = true, }, From 3b78c4b65d933bead3e4984a708c0743d66dffc9 Mon Sep 17 00:00:00 2001 From: "Paul B. Kim" Date: Tue, 17 Feb 2026 21:57:17 +0900 Subject: [PATCH 26/42] fix(opencode): sanitize notified errors Avoid exposing full upstream error payloads in notifications by redacting token-like fields and showing bounded, safe messages while keeping ask/select/command promise handling resilient. --- lua/custom/plugins/opencode.lua | 101 ++++++++++++++++++++++++++++++-- 1 file changed, 95 insertions(+), 6 deletions(-) diff --git a/lua/custom/plugins/opencode.lua b/lua/custom/plugins/opencode.lua index 60bd25002f2..90f5141438a 100644 --- a/lua/custom/plugins/opencode.lua +++ b/lua/custom/plugins/opencode.lua @@ -7,6 +7,83 @@ return { { 'folke/snacks.nvim', opts = { input = {}, picker = {}, terminal = {} } }, }, config = function() + local function redact_sensitive(value) + if type(value) ~= 'string' then + return value + end + + local redacted = value + redacted = redacted:gsub('([Aa]uthorization%s*:%s*[Bb]earer%s+)[^%s,;]+', '%1[REDACTED]') + redacted = redacted:gsub('([Aa][Pp][Ii][_%-%s]?[Kk][Ee][Yy]%s*[:=]%s*)[^%s,;]+', '%1[REDACTED]') + redacted = redacted:gsub('([Tt][Oo][Kk][Ee][Nn]%s*[:=]%s*)[^%s,;]+', '%1[REDACTED]') + return redacted + end + + local function truncate(value, max_len) + if #value <= max_len then + return value + end + + return value:sub(1, max_len) .. '...' + end + + local function format_opencode_error(err) + if type(err) == 'string' then + return truncate(redact_sensitive(err), 300) + end + + if type(err) == 'table' then + local message = err.message or err.msg or err.error or err.reason + if type(message) == 'string' and message ~= '' then + return truncate(redact_sensitive(message), 300) + end + + local status = err.status or err.code + if status ~= nil then + return 'opencode request failed (' .. tostring(status) .. ')' + end + + return 'opencode request failed' + end + + return truncate(redact_sensitive(tostring(err)), 300) + end + + local function notify_opencode_error(err) + if not err then + return + end + + vim.notify(format_opencode_error(err), vim.log.levels.ERROR, { title = 'opencode' }) + end + + local function opencode_ask(default, opts) + opts = opts or {} + opts.context = opts.context or require('opencode.context').new() + + return require('opencode.ui.ask') + .ask(default, opts.context) + :next(function(input) + if input:sub(-2) == '\\n' then + input = input:sub(1, -3) .. '\n' + opts.clear = false + opts.submit = false + end + + opts.context:clear() + return require('opencode.api.prompt').prompt(input, opts) + end) + :catch(notify_opencode_error) + end + + local function opencode_select(opts) + return require('opencode.ui.select').select(opts):catch(notify_opencode_error) + end + + local function opencode_command(command) + return require('opencode.api.command').command(command):catch(notify_opencode_error) + end + ---@type opencode.Opts vim.g.opencode_opts = { provider = { @@ -27,8 +104,12 @@ return { vim.o.autoread = true -- Recommended/example keymaps. - vim.keymap.set({ 'n', 'x' }, '', function() require('opencode').ask('@this: ', { submit = true }) end, { desc = 'Ask opencode…' }) - vim.keymap.set({ 'n', 'x' }, '', function() require('opencode').select() end, { desc = 'Execute opencode action…' }) + vim.keymap.set({ 'n', 'x' }, '', function() + opencode_ask('@this: ', { submit = true }) + end, { desc = 'Ask opencode…' }) + vim.keymap.set({ 'n', 'x' }, '', function() + opencode_select() + end, { desc = 'Execute opencode action…' }) local function opencode_toggle() local ok, err = pcall(function() require('opencode').toggle() @@ -70,11 +151,19 @@ return { end, }) - vim.keymap.set({ 'n', 'x' }, 'go', function() return require('opencode').operator '@this ' end, { desc = 'Add range to opencode', expr = true }) - vim.keymap.set('n', 'goo', function() return require('opencode').operator '@this ' .. '_' end, { desc = 'Add line to opencode', expr = true }) + vim.keymap.set({ 'n', 'x' }, 'go', function() + return require('opencode').operator '@this ' + end, { desc = 'Add range to opencode', expr = true }) + vim.keymap.set('n', 'goo', function() + return require('opencode').operator '@this ' .. '_' + end, { desc = 'Add line to opencode', expr = true }) - vim.keymap.set('n', '', function() require('opencode').command 'session.half.page.up' end, { desc = 'Scroll opencode up' }) - vim.keymap.set('n', '', function() require('opencode').command 'session.half.page.down' end, { desc = 'Scroll opencode down' }) + vim.keymap.set('n', '', function() + opencode_command 'session.half.page.up' + end, { desc = 'Scroll opencode up' }) + vim.keymap.set('n', '', function() + opencode_command 'session.half.page.down' + end, { desc = 'Scroll opencode down' }) -- You may want these if you stick with the opinionated "" and "" above — otherwise consider "o…". vim.keymap.set('n', '+', '', { desc = 'Increment under cursor', noremap = true }) From e6be670294dd8f38dec6983f8836248514868d50 Mon Sep 17 00:00:00 2001 From: "Paul B. Kim" Date: Fri, 6 Mar 2026 11:15:08 +0900 Subject: [PATCH 27/42] fix(startup): fix multi-splash, remove stale session manager --- .nvimlog | 12 ------------ init.lua | 8 ++++---- lua/custom/plugins/markdown.lua | 1 + lua/custom/plugins/persistence.lua | 18 ------------------ 4 files changed, 5 insertions(+), 34 deletions(-) delete mode 100644 .nvimlog diff --git a/.nvimlog b/.nvimlog deleted file mode 100644 index 167af11748b..00000000000 --- a/.nvimlog +++ /dev/null @@ -1,12 +0,0 @@ -WRN 2026-02-08T10:26:22.500 ?.251198 server_start:199: Failed to start server: operation not permitted: /run/user/1000/nvim.251198.0 -WRN 2026-02-08T10:27:17.510 ?.252159 server_start:199: Failed to start server: operation not permitted: /run/user/1000/nvim.252159.0 -WRN 2026-02-08T10:27:27.437 ?.252367 server_start:199: Failed to start server: operation not permitted: /run/user/1000/nvim.252367.0 -WRN 2026-02-08T12:08:26.579 ?.359867 server_start:199: Failed to start server: operation not permitted: /run/user/1000/nvim.359867.0 -WRN 2026-02-08T12:10:44.180 ?.364737 server_start:199: Failed to start server: operation not permitted: /run/user/1000/nvim.364737.0 -ERR 2026-02-08T12:10:44.218 ?.364737 pty_proc_spawn:186: forkpty failed: Permission denied -WRN 2026-02-08T12:18:41.628 ?.381522 server_start:199: Failed to start server: operation not permitted: /run/user/1000/nvim.381522.0 -WRN 2026-02-08T16:12:49.348 ?.515879 server_start:199: Failed to start server: operation not permitted: /run/user/1000/nvim.515879.0 -WRN 2026-02-08T16:33:59.951 ?.543624 server_start:199: Failed to start server: operation not permitted: /run/user/1000/nvim.543624.0 -WRN 2026-02-08T16:57:38.954 ?.571806 server_start:199: Failed to start server: operation not permitted: /run/user/1000/nvim.571806.0 -WRN 2026-02-08T19:51:48.962 ?.702813 server_start:199: Failed to start server: operation not permitted: /run/user/1000/nvim.702813.0 -WRN 2026-02-08T19:51:48.968 ?.702844 server_start:199: Failed to start server: operation not permitted: /run/user/1000/nvim.702844.0 diff --git a/init.lua b/init.lua index 71c0867e4f6..f99a98950aa 100644 --- a/init.lua +++ b/init.lua @@ -117,10 +117,10 @@ vim.schedule(function() vim.o.clipboard = 'unnamedplus' end) --- Default to 4-space indentation unless overridden by filetype/plugins -vim.o.tabstop = 4 -vim.o.shiftwidth = 4 -vim.o.softtabstop = 4 +-- Default to 2-space indentation unless overridden by filetype/plugins +vim.o.tabstop = 2 +vim.o.shiftwidth = 2 +vim.o.softtabstop = 2 vim.o.expandtab = true -- Save undo history diff --git a/lua/custom/plugins/markdown.lua b/lua/custom/plugins/markdown.lua index 647ab76a1e2..26cc99297f6 100644 --- a/lua/custom/plugins/markdown.lua +++ b/lua/custom/plugins/markdown.lua @@ -18,6 +18,7 @@ return { vim.g.mkdp_auto_start = 0 vim.g.mkdp_auto_close = 1 vim.g.mkdp_refresh_slow = 0 + vim.g.mkdp_browser = 'google-chrome-stable' vim.g.mkdp_filetypes = { 'markdown' } end, keys = { diff --git a/lua/custom/plugins/persistence.lua b/lua/custom/plugins/persistence.lua index 83fa606a8f2..5c0763d3117 100644 --- a/lua/custom/plugins/persistence.lua +++ b/lua/custom/plugins/persistence.lua @@ -27,22 +27,4 @@ return { desc = '[W]orkspace [D]isable session save', }, }, - init = function() - vim.api.nvim_create_autocmd('VimEnter', { - group = vim.api.nvim_create_augroup('persistence-auto-restore', { clear = true }), - callback = function() - if vim.fn.argc(-1) > 0 then - return - end - - local ignored = { gitcommit = true, gitrebase = true } - if ignored[vim.bo.filetype] then - return - end - - require('persistence').load() - end, - nested = true, - }) - end, } From cfa2d19f59bd247b75a531520df4ad98175480b2 Mon Sep 17 00:00:00 2001 From: "Paul B. Kim" Date: Sun, 22 Feb 2026 20:46:09 +0900 Subject: [PATCH 28/42] added folding and lint --- lua/custom/plugins/folding.lua | 27 ++++++++++ lua/custom/plugins/lint.lua | 92 +++++++++++++++++++++------------- 2 files changed, 85 insertions(+), 34 deletions(-) create mode 100644 lua/custom/plugins/folding.lua diff --git a/lua/custom/plugins/folding.lua b/lua/custom/plugins/folding.lua new file mode 100644 index 00000000000..c6cf7c21908 --- /dev/null +++ b/lua/custom/plugins/folding.lua @@ -0,0 +1,27 @@ +return { + { + 'kevinhwang91/nvim-ufo', + dependencies = { + 'kevinhwang91/promise-async', + }, + event = 'VeryLazy', + init = function() + vim.o.foldcolumn = '1' + vim.o.foldlevel = 99 + vim.o.foldlevelstart = 99 + vim.o.foldenable = true + end, + opts = { + provider_selector = function(_, _, _) + return { 'treesitter', 'indent' } + end, + }, + config = function(_, opts) + local ufo = require 'ufo' + ufo.setup(opts) + + vim.keymap.set('n', 'zR', ufo.openAllFolds, { desc = 'Open all folds' }) + vim.keymap.set('n', 'zM', ufo.closeAllFolds, { desc = 'Close all folds' }) + end, + }, +} diff --git a/lua/custom/plugins/lint.lua b/lua/custom/plugins/lint.lua index 2c194f59436..08494844582 100644 --- a/lua/custom/plugins/lint.lua +++ b/lua/custom/plugins/lint.lua @@ -53,47 +53,71 @@ return { -- lint.linters_by_ft['terraform'] = nil -- lint.linters_by_ft['text'] = nil - -- Create autocommand which carries out the actual linting - -- on the specified events. - local lint_augroup = vim.api.nvim_create_augroup('lint', { clear = true }) - vim.api.nvim_create_autocmd({ 'BufEnter', 'BufWritePost', 'InsertLeave' }, { - group = lint_augroup, - callback = function() - local function executable_cmd(cmd) - if type(cmd) == 'function' then - local ok, value = pcall(cmd) - if not ok then - return nil - end - return value - end - return cmd + local function executable_cmd(cmd) + if type(cmd) == 'function' then + local ok, value = pcall(cmd) + if not ok then + return nil end + return value + end + return cmd + end - local function available_linters_for(ft) - local configured = lint.linters_by_ft[ft] or {} - local available = {} + local function available_linters_for(ft) + local configured = lint.linters_by_ft[ft] or {} + local available = {} + for _, linter_name in ipairs(configured) do + local linter = lint.linters[linter_name] + local cmd = linter and executable_cmd(linter.cmd) + if cmd == nil or cmd == '' or vim.fn.executable(cmd) == 1 then + table.insert(available, linter_name) + end + end + return available + end - for _, linter_name in ipairs(configured) do - local linter = lint.linters[linter_name] - local cmd = linter and executable_cmd(linter.cmd) - if cmd == nil or cmd == '' or vim.fn.executable(cmd) == 1 then - table.insert(available, linter_name) + local function trigger_lint() + if vim.bo.modifiable then + local linters = available_linters_for(vim.bo.filetype) + if #linters > 0 then + lint.try_lint(linters) + end + end + end + + -- Toggle lint on/off + local lint_enabled = true + vim.keymap.set('n', 'tl', function() + lint_enabled = not lint_enabled + if not lint_enabled then + local cleared = {} + for _, linters in pairs(lint.linters_by_ft) do + for _, linter_name in ipairs(linters) do + local ns = lint.get_namespace(linter_name) + if not cleared[ns] then + vim.diagnostic.reset(ns) + cleared[ns] = true end end - - return available end + else + trigger_lint() + end + vim.notify('Lint ' .. (lint_enabled and 'enabled' or 'disabled')) + end, { desc = '[T]oggle [L]int' }) - -- Only run the linter in buffers that you can modify in order to - -- avoid superfluous noise, notably within the handy LSP pop-ups that - -- describe the hovered symbol using Markdown. - if vim.bo.modifiable then - local filetype = vim.bo.filetype - local linters = available_linters_for(filetype) - if #linters > 0 then - lint.try_lint(linters) - end + -- Create autocommand which carries out the actual linting + -- on the specified events. + local lint_augroup = vim.api.nvim_create_augroup('lint', { clear = true }) + vim.api.nvim_create_autocmd({ 'BufEnter', 'BufWritePost', 'InsertLeave' }, { + group = lint_augroup, + -- Only run the linter in buffers that you can modify in order to + -- avoid superfluous noise, notably within the handy LSP pop-ups that + -- describe the hovered symbol using Markdown. + callback = function() + if lint_enabled then + trigger_lint() end end, }) From 953e5b7d5cf0ca68a648ebef0d42fbaf80c6d80b Mon Sep 17 00:00:00 2001 From: "Paul B. Kim" Date: Tue, 3 Mar 2026 07:39:44 +0900 Subject: [PATCH 29/42] =?UTF-8?q?feat(lsp):=20add=20file=20rename=20?= =?UTF-8?q?=E2=86=92=20import=20update=20support?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Switch pyright to basedpyright (willRenameFiles capability) - Add nvim-lsp-file-operations for neo-tree rename → LSP updates - Add Snacks.rename with cR for current-buffer file renames - Harden oil.nvim with lsp_file_methods (2s timeout, autosave unmodified) --- init.lua | 9 ++++----- lua/custom/plugins/lsp_file_operations.lua | 13 +++++++++++++ lua/custom/plugins/oil.lua | 5 +++++ lua/custom/plugins/opencode.lua | 8 +++++++- 4 files changed, 29 insertions(+), 6 deletions(-) create mode 100644 lua/custom/plugins/lsp_file_operations.lua diff --git a/init.lua b/init.lua index f99a98950aa..47fd96b3b0a 100644 --- a/init.lua +++ b/init.lua @@ -749,13 +749,12 @@ require('lazy').setup({ }, }, }, - pyright = { + basedpyright = { settings = { - python = { + basedpyright = { analysis = { - typeCheckingMode = 'basic', - autoSearchPaths = true, - useLibraryCodeForTypes = true, + typeCheckingMode = 'standard', + diagnosticMode = 'openFilesOnly', }, }, }, diff --git a/lua/custom/plugins/lsp_file_operations.lua b/lua/custom/plugins/lsp_file_operations.lua new file mode 100644 index 00000000000..3f27b8eefe9 --- /dev/null +++ b/lua/custom/plugins/lsp_file_operations.lua @@ -0,0 +1,13 @@ +return { + 'antosha417/nvim-lsp-file-operations', + dependencies = { 'nvim-neo-tree/neo-tree.nvim' }, + config = function() + require('lsp-file-operations').setup() + + -- Advertise file operation capabilities to all LSP servers + local file_ops_caps = require('lsp-file-operations').default_capabilities() + vim.lsp.config('*', { + capabilities = file_ops_caps, + }) + end, +} diff --git a/lua/custom/plugins/oil.lua b/lua/custom/plugins/oil.lua index 8a6d676717a..c4fb1abd068 100644 --- a/lua/custom/plugins/oil.lua +++ b/lua/custom/plugins/oil.lua @@ -10,5 +10,10 @@ return { view_options = { show_hidden = true, }, + lsp_file_methods = { + enabled = true, + timeout_ms = 2000, + autosave_changes = 'unmodified', + }, }, } diff --git a/lua/custom/plugins/opencode.lua b/lua/custom/plugins/opencode.lua index 90f5141438a..56c1346db5a 100644 --- a/lua/custom/plugins/opencode.lua +++ b/lua/custom/plugins/opencode.lua @@ -4,7 +4,13 @@ return { -- Recommended for `ask()` and `select()`. -- Required for `snacks` provider. ---@module 'snacks' <- Loads `snacks.nvim` types for configuration intellisense. - { 'folke/snacks.nvim', opts = { input = {}, picker = {}, terminal = {} } }, + { + 'folke/snacks.nvim', + opts = { input = {}, picker = {}, terminal = {}, rename = {} }, + keys = { + { 'cR', function() Snacks.rename.rename_file() end, desc = 'Rename File (LSP)' }, + }, + }, }, config = function() local function redact_sensitive(value) From 99d232e9027e8c84006ebf94045f42bdcc847167 Mon Sep 17 00:00:00 2001 From: "Paul B. Kim" Date: Fri, 6 Mar 2026 11:15:20 +0900 Subject: [PATCH 30/42] feat(keymap): reorganize g prefix, add LSP goto keymaps --- init.lua | 39 ++++++++++++++++++++++++--------- lua/custom/plugins/diffview.lua | 8 +++---- lua/custom/plugins/neogit.lua | 2 +- 3 files changed, 34 insertions(+), 15 deletions(-) diff --git a/init.lua b/init.lua index 47fd96b3b0a..386a854032f 100644 --- a/init.lua +++ b/init.lua @@ -398,7 +398,8 @@ require('lazy').setup({ { 'c', group = '[C]Make' }, { 'd', group = '[D]iagnostics' }, { 'e', group = '[E]xplorer' }, - { 'g', group = '[G]it' }, + { 'g', group = '[G]oto' }, + { 'G', group = '[G]it' }, { 'm', group = '[M]arkdown' }, { 'n', group = '[N]eotest' }, { 'o', group = '[O]pencode' }, @@ -606,19 +607,23 @@ require('lazy').setup({ -- Find references for the word under your cursor. map('grr', require('telescope.builtin').lsp_references, '[G]oto [R]eferences') + map('gr', require('telescope.builtin').lsp_references, '[G]oto [R]eferences') -- Jump to the implementation of the word under your cursor. -- Useful when your language has ways of declaring types without an actual implementation. map('gri', require('telescope.builtin').lsp_implementations, '[G]oto [I]mplementation') + map('gi', require('telescope.builtin').lsp_implementations, '[G]oto [I]mplementation') -- Jump to the definition of the word under your cursor. -- This is where a variable was first declared, or where a function is defined, etc. -- To jump back, press . map('grd', require('telescope.builtin').lsp_definitions, '[G]oto [D]efinition') + map('gd', require('telescope.builtin').lsp_definitions, '[G]oto [D]efinition') -- WARN: This is not Goto Definition, this is Goto Declaration. -- For example, in C this would take you to the header. map('grD', vim.lsp.buf.declaration, '[G]oto [D]eclaration') + map('gD', vim.lsp.buf.declaration, '[G]oto [D]eclaration') -- Fuzzy find all the symbols in your current document. -- Symbols are things like variables, functions, types, etc. @@ -632,6 +637,7 @@ require('lazy').setup({ -- Useful when you're not sure what type a variable is and you want to see -- the definition of its *type*, not where it was *defined*. map('grt', require('telescope.builtin').lsp_type_definitions, '[G]oto [T]ype Definition') + map('gt', require('telescope.builtin').lsp_type_definitions, '[G]oto [T]ype Definition') -- This function resolves a difference between neovim nightly (version 0.11) and stable (version 0.10) ---@param client vim.lsp.Client @@ -1064,6 +1070,13 @@ require('lazy').setup({ -- - sr)' - [S]urround [R]eplace [)] ['] require('mini.surround').setup() + -- Delete buffers while preserving window layout. + local bufremove = require 'mini.bufremove' + bufremove.setup() + vim.keymap.set('n', 'bd', function() + bufremove.delete(0, false) + end, { desc = '[B]uffer [D]elete' }) + -- Simple and easy statusline. -- You could remove this setup call if you don't like it, -- and try some other statusline plugin @@ -1085,20 +1098,26 @@ require('lazy').setup({ }, { -- Highlight, edit, and navigate code 'nvim-treesitter/nvim-treesitter', + branch = 'main', lazy = false, build = ':TSUpdate', -- [[ Configure Treesitter ]] See `:help nvim-treesitter` - opts = { - ensure_installed = { 'bash', 'c', 'diff', 'html', 'lua', 'luadoc', 'markdown', 'markdown_inline', 'query', 'vim', 'vimdoc' }, - auto_install = true, - }, - config = function(_, opts) + config = function() local treesitter = require 'nvim-treesitter' treesitter.setup() - - if opts.auto_install and opts.ensure_installed and #opts.ensure_installed > 0 then - treesitter.install(opts.ensure_installed) - end + require('nvim-treesitter.install').ensure_installed { + 'bash', + 'c', + 'diff', + 'html', + 'lua', + 'luadoc', + 'markdown', + 'markdown_inline', + 'query', + 'vim', + 'vimdoc', + } vim.api.nvim_create_autocmd('FileType', { group = vim.api.nvim_create_augroup('kickstart-treesitter', { clear = true }), diff --git a/lua/custom/plugins/diffview.lua b/lua/custom/plugins/diffview.lua index 4e94670c0fc..b893a0c8898 100644 --- a/lua/custom/plugins/diffview.lua +++ b/lua/custom/plugins/diffview.lua @@ -10,10 +10,10 @@ return { 'DiffviewRefresh', }, keys = { - { 'gd', 'DiffviewOpen', desc = '[G]it [D]iff view' }, - { 'gD', 'DiffviewClose', desc = '[G]it [D]iff close' }, - { 'gf', 'DiffviewFileHistory %', desc = '[G]it [F]ile history' }, - { 'gF', 'DiffviewFileHistory', desc = '[G]it [F]ull history' }, + { 'Gd', 'DiffviewOpen', desc = '[G]it [D]iff view' }, + { 'GD', 'DiffviewClose', desc = '[G]it [D]iff close' }, + { 'Gf', 'DiffviewFileHistory %', desc = '[G]it [F]ile history' }, + { 'GF', 'DiffviewFileHistory', desc = '[G]it [F]ull history' }, }, opts = {}, } diff --git a/lua/custom/plugins/neogit.lua b/lua/custom/plugins/neogit.lua index 8a4fb9d6504..c91dfedc141 100644 --- a/lua/custom/plugins/neogit.lua +++ b/lua/custom/plugins/neogit.lua @@ -13,6 +13,6 @@ return { }, cmd = 'Neogit', keys = { - { 'gg', 'Neogit', desc = 'Show Neogit UI' }, + { 'Gg', 'Neogit', desc = 'Show Neogit UI' }, }, } From da30b1441952c859e152ff3c0a0b5af262891cba Mon Sep 17 00:00:00 2001 From: "Paul B. Kim" Date: Fri, 6 Mar 2026 11:15:24 +0900 Subject: [PATCH 31/42] feat(yank): add location reference clipboard utilities --- lua/custom/location_reference.lua | 244 ++++++++++++++++++++++ lua/custom/plugins/location_reference.lua | 41 ++++ 2 files changed, 285 insertions(+) create mode 100644 lua/custom/location_reference.lua create mode 100644 lua/custom/plugins/location_reference.lua diff --git a/lua/custom/location_reference.lua b/lua/custom/location_reference.lua new file mode 100644 index 00000000000..3908bda2f2c --- /dev/null +++ b/lua/custom/location_reference.lua @@ -0,0 +1,244 @@ +local M = {} + +local sk = vim.lsp.protocol.SymbolKind +local symbol_kinds = { + [sk.Class] = true, + [sk.Method] = true, + [sk.Constructor] = true, + [sk.Enum] = true, + [sk.Interface] = true, + [sk.Function] = true, + [sk.Struct] = true, +} + +local function range_contains(range, line0, col0) + if not range then + return false + end + + local start_line = range.start.line + local start_col = range.start.character + local end_line = range['end'].line + local end_col = range['end'].character + + if line0 < start_line or line0 > end_line then + return false + end + if line0 == start_line and col0 < start_col then + return false + end + if line0 == end_line and col0 > end_col then + return false + end + + return true +end + +local function range_size(range) + -- Weight lines heavily so a single-line range is always smaller than a multi-line one + return (range['end'].line - range.start.line) * 100000 + (range['end'].character - range.start.character) +end + +local function append_document_symbols(symbols, out, parent_name) + for _, symbol in ipairs(symbols or {}) do + local symbol_name = symbol.name + local full_name = parent_name and (parent_name .. '.' .. symbol_name) or symbol_name + table.insert(out, { + name = full_name, + kind = symbol.kind, + range = symbol.range, + }) + append_document_symbols(symbol.children, out, full_name) + end +end + +local function append_symbol_information(symbols, out) + for _, symbol in ipairs(symbols or {}) do + local name = symbol.name + if symbol.containerName and symbol.containerName ~= '' then + name = symbol.containerName .. '.' .. name + end + table.insert(out, { + name = name, + kind = symbol.kind, + range = symbol.location and symbol.location.range, + }) + end +end + +local function get_symbol_at_cursor() + if #vim.lsp.get_clients { bufnr = 0 } == 0 then + return nil + end + + local params = { textDocument = vim.lsp.util.make_text_document_params() } + local responses = vim.lsp.buf_request_sync(0, 'textDocument/documentSymbol', params, 500) + if not responses then + return nil + end + + local cursor = vim.api.nvim_win_get_cursor(0) + local line0 = cursor[1] - 1 + -- LSP ranges use UTF-16 offsets; convert from byte offset for correct matching + -- on files with non-ASCII characters (e.g. CJK, emoji). + local line_text = vim.api.nvim_buf_get_lines(0, line0, line0 + 1, false)[1] or '' + local col0 = vim.str_utfindex(line_text, 'utf-16', cursor[2]) + local symbols = {} + + for _, response in pairs(responses) do + if not response.error then + local result = response.result + if type(result) == 'table' and result[1] then + if result[1].selectionRange or result[1].children then + append_document_symbols(result, symbols, nil) + else + append_symbol_information(result, symbols) + end + end + end + end + + local best_symbol, best_size + for _, symbol in ipairs(symbols) do + if symbol_kinds[symbol.kind] and range_contains(symbol.range, line0, col0) then + local size = range_size(symbol.range) + if not best_size or size < best_size then + best_size = size + best_symbol = symbol.name + end + end + end + + return best_symbol +end + +local function get_relative_path_from_root(file_path) + local root = vim.fs.root(file_path, { '.git' }) or vim.fn.getcwd() + + if vim.fs.relpath then + local rel = vim.fs.relpath(root, file_path) + if rel then + return rel + end + end + + local prefix = root + if not prefix:match '/$' then + prefix = prefix .. '/' + end + if file_path:sub(1, #prefix) == prefix then + return file_path:sub(#prefix + 1) + end + + return file_path +end + +local function get_file_parts() + local file_path = vim.api.nvim_buf_get_name(0) + if file_path == '' then + vim.notify('No file path for current buffer', vim.log.levels.WARN) + return nil + end + + return { + file_path = file_path, + file_rel = get_relative_path_from_root(file_path), + } +end + +local function get_location_parts() + local parts = get_file_parts() + if not parts then + return nil + end + + local mode = vim.api.nvim_get_mode().mode + local loc + + if mode == 'v' or mode == 'V' or mode == '\22' then + -- Use 'v' (visual anchor) and '.' (cursor) for the live selection. + -- ' marks are only reliable after exiting visual mode (e.g. ':' mappings), + -- but mappings stay in visual mode so those marks are stale. + local start_pos = vim.fn.getpos 'v' + local end_pos = vim.fn.getpos '.' + local s_line, s_col = start_pos[2], start_pos[3] + local e_line, e_col = end_pos[2], end_pos[3] + + -- getpos('v') returns zeros when called outside a real visual context; fall back. + if s_line == 0 then + local cur = vim.api.nvim_win_get_cursor(0) + s_line, s_col = cur[1], cur[2] + 1 + e_line, e_col = s_line, s_col + elseif s_line > e_line or (s_line == e_line and s_col > e_col) then + s_line, e_line = e_line, s_line + s_col, e_col = e_col, s_col + end + + loc = string.format('%d:%d-%d:%d', s_line, s_col, e_line, e_col) + else + local cursor = vim.api.nvim_win_get_cursor(0) + local line_num = cursor[1] + local line_text = vim.api.nvim_buf_get_lines(0, line_num - 1, line_num, false)[1] or '' + local end_col = math.max(vim.fn.strchars(line_text), 1) + loc = string.format('%d:%d-%d:%d', line_num, 1, line_num, end_col) + end + + return { + file_path = parts.file_path, + file_rel = parts.file_rel, + loc = loc, + symbol = get_symbol_at_cursor(), + } +end + +local function yank(label, value) + vim.fn.setreg('"', value) + pcall(vim.fn.setreg, '+', value) + vim.notify('Copied ' .. label .. ': ' .. value, vim.log.levels.INFO) +end + +function M.copy_absolute_location_reference() + local parts = get_location_parts() + if not parts then + return + end + + local location = string.format('%s:%s', parts.file_path, parts.loc) + if parts.symbol and parts.symbol ~= '' then + location = location .. ' symbol:' .. parts.symbol + end + + yank('absolute location', location) +end + +function M.copy_relative_location_reference() + local parts = get_location_parts() + if not parts then + return + end + + local symbol = parts.symbol and parts.symbol ~= '' and parts.symbol or 'none' + yank('relative location', string.format('@%s loc:%s symbol:%s', parts.file_rel, parts.loc, symbol)) +end + +function M.copy_buffer_file_reference() + local parts = get_file_parts() + if not parts then + return + end + + yank('buffer file', string.format('@%s', parts.file_rel)) +end + +function M.copy_buffer_directory_reference() + local parts = get_file_parts() + if not parts then + return + end + + local abs_dir = vim.fn.fnamemodify(parts.file_path, ':h') + local rel_dir = get_relative_path_from_root(abs_dir) + yank('buffer directory', string.format('@%s', rel_dir)) +end + +return M diff --git a/lua/custom/plugins/location_reference.lua b/lua/custom/plugins/location_reference.lua new file mode 100644 index 00000000000..7cdee361ccc --- /dev/null +++ b/lua/custom/plugins/location_reference.lua @@ -0,0 +1,41 @@ +return { + 'folke/which-key.nvim', + keys = { + { + 'ya', + function() + require('custom.location_reference').copy_absolute_location_reference() + end, + mode = { 'n', 'x' }, + desc = '[Y]ank [A]bsolute ref', + }, + { + 'yr', + function() + require('custom.location_reference').copy_relative_location_reference() + end, + mode = { 'n', 'x' }, + desc = '[Y]ank [R]elative ref', + }, + { + 'yf', + function() + require('custom.location_reference').copy_buffer_file_reference() + end, + mode = { 'n', 'x' }, + desc = '[Y]ank Buffer [F]ile', + }, + { + 'yd', + function() + require('custom.location_reference').copy_buffer_directory_reference() + end, + mode = { 'n', 'x' }, + desc = '[Y]ank Buffer [D]irectory', + }, + }, + opts = function(_, opts) + opts.spec = opts.spec or {} + table.insert(opts.spec, { 'y', group = '[Y]ank' }) + end, +} From ef32c150ff3af1969aa541419b156996b1e9a422 Mon Sep 17 00:00:00 2001 From: "Paul B. Kim" Date: Fri, 6 Mar 2026 11:08:25 +0900 Subject: [PATCH 32/42] feat(ui): add bufferline plugin config --- lua/custom/plugins/bufferline.lua | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 lua/custom/plugins/bufferline.lua diff --git a/lua/custom/plugins/bufferline.lua b/lua/custom/plugins/bufferline.lua new file mode 100644 index 00000000000..ba8a12f0f65 --- /dev/null +++ b/lua/custom/plugins/bufferline.lua @@ -0,0 +1,18 @@ +return { + 'akinsho/bufferline.nvim', + event = 'VeryLazy', + dependencies = { 'nvim-tree/nvim-web-devicons' }, + keys = { + { '', 'BufferLineCyclePrev', desc = 'Previous buffer' }, + { '', 'BufferLineCycleNext', desc = 'Next buffer' }, + { 'bp', 'BufferLinePick', desc = '[B]uffer [P]ick' }, + }, + opts = { + options = { + mode = 'buffers', + always_show_bufferline = true, + show_buffer_close_icons = false, + show_close_icon = false, + }, + }, +} From cbfd797e56f204c0f9564c216bc78c1f10e39c8b Mon Sep 17 00:00:00 2001 From: "Paul B. Kim" Date: Fri, 6 Mar 2026 12:27:42 +0900 Subject: [PATCH 33/42] refactor(config): inline location reference plugin and refresh docs --- AGENTS.md | 2 +- README.md | 22 +- lua/custom/health.lua | 52 ----- lua/custom/location_reference.lua | 244 -------------------- lua/custom/plugins/location_reference.lua | 257 +++++++++++++++++++++- 5 files changed, 263 insertions(+), 314 deletions(-) delete mode 100644 lua/custom/health.lua delete mode 100644 lua/custom/location_reference.lua diff --git a/AGENTS.md b/AGENTS.md index 73e1b28f7db..f329f55b6c0 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -4,7 +4,7 @@ - Root entrypoint: `init.lua` (Kickstart-based primary config). - Custom Lua modules live under `lua/custom/`. - Plugin specs are split into `lua/custom/plugins/*.lua` (one concern per file, e.g. `gitsigns.lua`, `lint.lua`, `persistence.lua`). -- Utility modules (non-plugin config) live in `lua/custom/*.lua` (for example `wrapping.lua`, `health.lua`). +- Utility modules (non-plugin config) live in `lua/custom/*.lua` (for example `wrapping.lua`). - Reference docs live in `README.md` and `doc/kickstart.txt`. - Plugin versions are pinned in `lazy-lock.json`. diff --git a/README.md b/README.md index cb53f9c957f..fa13a5afd1e 100644 --- a/README.md +++ b/README.md @@ -44,15 +44,20 @@ nvim --headless "+MasonToolsInstallSync" "+qa" ### Git workflow -- `gg`: open Neogit UI -- `gd`: open Diffview -- `gD`: close Diffview -- `gf`: Diffview file history (current file) -- `gF`: Diffview repo history +- `Gg`: open Neogit UI +- `Gd`: open Diffview +- `GD`: close Diffview +- `Gf`: Diffview file history (current file) +- `GF`: Diffview repo history - `h...`: Gitsigns hunk actions (`:which-key h`) ### Diagnostics and code navigation +- `gd`: goto definition +- `gD`: goto declaration +- `gr`: goto references +- `gi`: goto implementation +- `gt`: goto type definition - `xx`: Trouble diagnostics - `xw`: Trouble workspace diagnostics - `xd`: Trouble current buffer diagnostics @@ -64,6 +69,13 @@ nvim --headless "+MasonToolsInstallSync" "+qa" - `jc` / `jC`: next/previous class start - Textobject select (operator-pending/visual): `af`/`if` for function, `ac`/`ic` for class +### Yank references (AI-friendly) + +- `ya`: yank absolute reference with location and symbol +- `yr`: yank relative reference with location and symbol +- `yf`: yank current buffer file as `@relative/path` +- `yd`: yank current buffer parent directory as `@relative/dir` + ### Tests and debug - `nr`: neotest run nearest diff --git a/lua/custom/health.lua b/lua/custom/health.lua deleted file mode 100644 index b59d08649af..00000000000 --- a/lua/custom/health.lua +++ /dev/null @@ -1,52 +0,0 @@ ---[[ --- --- This file is not required for your own configuration, --- but helps people determine if their system is setup correctly. --- ---]] - -local check_version = function() - local verstr = tostring(vim.version()) - if not vim.version.ge then - vim.health.error(string.format("Neovim out of date: '%s'. Upgrade to latest stable or nightly", verstr)) - return - end - - if vim.version.ge(vim.version(), '0.10-dev') then - vim.health.ok(string.format("Neovim version is: '%s'", verstr)) - else - vim.health.error(string.format("Neovim out of date: '%s'. Upgrade to latest stable or nightly", verstr)) - end -end - -local check_external_reqs = function() - -- Basic utils: `git`, `make`, `unzip` - for _, exe in ipairs { 'git', 'make', 'unzip', 'rg' } do - local is_executable = vim.fn.executable(exe) == 1 - if is_executable then - vim.health.ok(string.format("Found executable: '%s'", exe)) - else - vim.health.warn(string.format("Could not find executable: '%s'", exe)) - end - end - - return true -end - -return { - check = function() - vim.health.start 'kickstart.nvim' - - vim.health.info [[NOTE: Not every warning is a 'must-fix' in `:checkhealth` - - Fix only warnings for plugins and languages you intend to use. - Mason will give warnings for languages that are not installed. - You do not need to install, unless you want to use those languages!]] - - local uv = vim.uv or vim.loop - vim.health.info('System Information: ' .. vim.inspect(uv.os_uname())) - - check_version() - check_external_reqs() - end, -} diff --git a/lua/custom/location_reference.lua b/lua/custom/location_reference.lua deleted file mode 100644 index 3908bda2f2c..00000000000 --- a/lua/custom/location_reference.lua +++ /dev/null @@ -1,244 +0,0 @@ -local M = {} - -local sk = vim.lsp.protocol.SymbolKind -local symbol_kinds = { - [sk.Class] = true, - [sk.Method] = true, - [sk.Constructor] = true, - [sk.Enum] = true, - [sk.Interface] = true, - [sk.Function] = true, - [sk.Struct] = true, -} - -local function range_contains(range, line0, col0) - if not range then - return false - end - - local start_line = range.start.line - local start_col = range.start.character - local end_line = range['end'].line - local end_col = range['end'].character - - if line0 < start_line or line0 > end_line then - return false - end - if line0 == start_line and col0 < start_col then - return false - end - if line0 == end_line and col0 > end_col then - return false - end - - return true -end - -local function range_size(range) - -- Weight lines heavily so a single-line range is always smaller than a multi-line one - return (range['end'].line - range.start.line) * 100000 + (range['end'].character - range.start.character) -end - -local function append_document_symbols(symbols, out, parent_name) - for _, symbol in ipairs(symbols or {}) do - local symbol_name = symbol.name - local full_name = parent_name and (parent_name .. '.' .. symbol_name) or symbol_name - table.insert(out, { - name = full_name, - kind = symbol.kind, - range = symbol.range, - }) - append_document_symbols(symbol.children, out, full_name) - end -end - -local function append_symbol_information(symbols, out) - for _, symbol in ipairs(symbols or {}) do - local name = symbol.name - if symbol.containerName and symbol.containerName ~= '' then - name = symbol.containerName .. '.' .. name - end - table.insert(out, { - name = name, - kind = symbol.kind, - range = symbol.location and symbol.location.range, - }) - end -end - -local function get_symbol_at_cursor() - if #vim.lsp.get_clients { bufnr = 0 } == 0 then - return nil - end - - local params = { textDocument = vim.lsp.util.make_text_document_params() } - local responses = vim.lsp.buf_request_sync(0, 'textDocument/documentSymbol', params, 500) - if not responses then - return nil - end - - local cursor = vim.api.nvim_win_get_cursor(0) - local line0 = cursor[1] - 1 - -- LSP ranges use UTF-16 offsets; convert from byte offset for correct matching - -- on files with non-ASCII characters (e.g. CJK, emoji). - local line_text = vim.api.nvim_buf_get_lines(0, line0, line0 + 1, false)[1] or '' - local col0 = vim.str_utfindex(line_text, 'utf-16', cursor[2]) - local symbols = {} - - for _, response in pairs(responses) do - if not response.error then - local result = response.result - if type(result) == 'table' and result[1] then - if result[1].selectionRange or result[1].children then - append_document_symbols(result, symbols, nil) - else - append_symbol_information(result, symbols) - end - end - end - end - - local best_symbol, best_size - for _, symbol in ipairs(symbols) do - if symbol_kinds[symbol.kind] and range_contains(symbol.range, line0, col0) then - local size = range_size(symbol.range) - if not best_size or size < best_size then - best_size = size - best_symbol = symbol.name - end - end - end - - return best_symbol -end - -local function get_relative_path_from_root(file_path) - local root = vim.fs.root(file_path, { '.git' }) or vim.fn.getcwd() - - if vim.fs.relpath then - local rel = vim.fs.relpath(root, file_path) - if rel then - return rel - end - end - - local prefix = root - if not prefix:match '/$' then - prefix = prefix .. '/' - end - if file_path:sub(1, #prefix) == prefix then - return file_path:sub(#prefix + 1) - end - - return file_path -end - -local function get_file_parts() - local file_path = vim.api.nvim_buf_get_name(0) - if file_path == '' then - vim.notify('No file path for current buffer', vim.log.levels.WARN) - return nil - end - - return { - file_path = file_path, - file_rel = get_relative_path_from_root(file_path), - } -end - -local function get_location_parts() - local parts = get_file_parts() - if not parts then - return nil - end - - local mode = vim.api.nvim_get_mode().mode - local loc - - if mode == 'v' or mode == 'V' or mode == '\22' then - -- Use 'v' (visual anchor) and '.' (cursor) for the live selection. - -- ' marks are only reliable after exiting visual mode (e.g. ':' mappings), - -- but mappings stay in visual mode so those marks are stale. - local start_pos = vim.fn.getpos 'v' - local end_pos = vim.fn.getpos '.' - local s_line, s_col = start_pos[2], start_pos[3] - local e_line, e_col = end_pos[2], end_pos[3] - - -- getpos('v') returns zeros when called outside a real visual context; fall back. - if s_line == 0 then - local cur = vim.api.nvim_win_get_cursor(0) - s_line, s_col = cur[1], cur[2] + 1 - e_line, e_col = s_line, s_col - elseif s_line > e_line or (s_line == e_line and s_col > e_col) then - s_line, e_line = e_line, s_line - s_col, e_col = e_col, s_col - end - - loc = string.format('%d:%d-%d:%d', s_line, s_col, e_line, e_col) - else - local cursor = vim.api.nvim_win_get_cursor(0) - local line_num = cursor[1] - local line_text = vim.api.nvim_buf_get_lines(0, line_num - 1, line_num, false)[1] or '' - local end_col = math.max(vim.fn.strchars(line_text), 1) - loc = string.format('%d:%d-%d:%d', line_num, 1, line_num, end_col) - end - - return { - file_path = parts.file_path, - file_rel = parts.file_rel, - loc = loc, - symbol = get_symbol_at_cursor(), - } -end - -local function yank(label, value) - vim.fn.setreg('"', value) - pcall(vim.fn.setreg, '+', value) - vim.notify('Copied ' .. label .. ': ' .. value, vim.log.levels.INFO) -end - -function M.copy_absolute_location_reference() - local parts = get_location_parts() - if not parts then - return - end - - local location = string.format('%s:%s', parts.file_path, parts.loc) - if parts.symbol and parts.symbol ~= '' then - location = location .. ' symbol:' .. parts.symbol - end - - yank('absolute location', location) -end - -function M.copy_relative_location_reference() - local parts = get_location_parts() - if not parts then - return - end - - local symbol = parts.symbol and parts.symbol ~= '' and parts.symbol or 'none' - yank('relative location', string.format('@%s loc:%s symbol:%s', parts.file_rel, parts.loc, symbol)) -end - -function M.copy_buffer_file_reference() - local parts = get_file_parts() - if not parts then - return - end - - yank('buffer file', string.format('@%s', parts.file_rel)) -end - -function M.copy_buffer_directory_reference() - local parts = get_file_parts() - if not parts then - return - end - - local abs_dir = vim.fn.fnamemodify(parts.file_path, ':h') - local rel_dir = get_relative_path_from_root(abs_dir) - yank('buffer directory', string.format('@%s', rel_dir)) -end - -return M diff --git a/lua/custom/plugins/location_reference.lua b/lua/custom/plugins/location_reference.lua index 7cdee361ccc..1b44725161f 100644 --- a/lua/custom/plugins/location_reference.lua +++ b/lua/custom/plugins/location_reference.lua @@ -1,35 +1,268 @@ +local sk = vim.lsp.protocol.SymbolKind +local symbol_kinds = { + [sk.Class] = true, + [sk.Method] = true, + [sk.Constructor] = true, + [sk.Enum] = true, + [sk.Interface] = true, + [sk.Function] = true, + [sk.Struct] = true, +} + +local function range_contains(range, line0, col0) + if not range then + return false + end + + local start_line = range.start.line + local start_col = range.start.character + local end_line = range['end'].line + local end_col = range['end'].character + + if line0 < start_line or line0 > end_line then + return false + end + if line0 == start_line and col0 < start_col then + return false + end + if line0 == end_line and col0 > end_col then + return false + end + + return true +end + +local function range_size(range) + -- Weight lines heavily so a single-line range is always smaller than a multi-line one + return (range['end'].line - range.start.line) * 100000 + (range['end'].character - range.start.character) +end + +local function append_document_symbols(symbols, out, parent_name) + for _, symbol in ipairs(symbols or {}) do + local symbol_name = symbol.name + local full_name = parent_name and (parent_name .. '.' .. symbol_name) or symbol_name + table.insert(out, { + name = full_name, + kind = symbol.kind, + range = symbol.range, + }) + append_document_symbols(symbol.children, out, full_name) + end +end + +local function append_symbol_information(symbols, out) + for _, symbol in ipairs(symbols or {}) do + local name = symbol.name + if symbol.containerName and symbol.containerName ~= '' then + name = symbol.containerName .. '.' .. name + end + table.insert(out, { + name = name, + kind = symbol.kind, + range = symbol.location and symbol.location.range, + }) + end +end + +local function get_symbol_at_cursor() + if #vim.lsp.get_clients { bufnr = 0 } == 0 then + return nil + end + + local params = { textDocument = vim.lsp.util.make_text_document_params() } + local responses = vim.lsp.buf_request_sync(0, 'textDocument/documentSymbol', params, 500) + if not responses then + return nil + end + + local cursor = vim.api.nvim_win_get_cursor(0) + local line0 = cursor[1] - 1 + -- LSP ranges use UTF-16 offsets; convert from byte offset for correct matching + -- on files with non-ASCII characters (e.g. CJK, emoji). + local line_text = vim.api.nvim_buf_get_lines(0, line0, line0 + 1, false)[1] or '' + local col0 = vim.str_utfindex(line_text, 'utf-16', cursor[2]) + local symbols = {} + + for _, response in pairs(responses) do + if not response.error then + local result = response.result + if type(result) == 'table' and result[1] then + if result[1].selectionRange or result[1].children then + append_document_symbols(result, symbols, nil) + else + append_symbol_information(result, symbols) + end + end + end + end + + local best_symbol, best_size + for _, symbol in ipairs(symbols) do + if symbol_kinds[symbol.kind] and range_contains(symbol.range, line0, col0) then + local size = range_size(symbol.range) + if not best_size or size < best_size then + best_size = size + best_symbol = symbol.name + end + end + end + + return best_symbol +end + +local function get_relative_path_from_root(file_path) + local root = vim.fs.root(file_path, { '.git' }) or vim.fn.getcwd() + + if vim.fs.relpath then + local rel = vim.fs.relpath(root, file_path) + if rel then + return rel + end + end + + local prefix = root + if not prefix:match '/$' then + prefix = prefix .. '/' + end + if file_path:sub(1, #prefix) == prefix then + return file_path:sub(#prefix + 1) + end + + return file_path +end + +local function get_file_parts() + local file_path = vim.api.nvim_buf_get_name(0) + if file_path == '' then + vim.notify('No file path for current buffer', vim.log.levels.WARN) + return nil + end + + return { + file_path = file_path, + file_rel = get_relative_path_from_root(file_path), + } +end + +local function get_location_parts() + local parts = get_file_parts() + if not parts then + return nil + end + + local mode = vim.api.nvim_get_mode().mode + local loc + + if mode == 'v' or mode == 'V' or mode == '\22' then + -- Use 'v' (visual anchor) and '.' (cursor) for the live selection. + -- ' marks are only reliable after exiting visual mode (e.g. ':' mappings), + -- but mappings stay in visual mode so those marks are stale. + local start_pos = vim.fn.getpos 'v' + local end_pos = vim.fn.getpos '.' + local s_line, s_col = start_pos[2], start_pos[3] + local e_line, e_col = end_pos[2], end_pos[3] + + -- getpos('v') returns zeros when called outside a real visual context; fall back. + if s_line == 0 then + local cur = vim.api.nvim_win_get_cursor(0) + s_line, s_col = cur[1], cur[2] + 1 + e_line, e_col = s_line, s_col + elseif s_line > e_line or (s_line == e_line and s_col > e_col) then + s_line, e_line = e_line, s_line + s_col, e_col = e_col, s_col + end + + loc = string.format('%d:%d-%d:%d', s_line, s_col, e_line, e_col) + else + local cursor = vim.api.nvim_win_get_cursor(0) + local line_num = cursor[1] + local line_text = vim.api.nvim_buf_get_lines(0, line_num - 1, line_num, false)[1] or '' + local end_col = math.max(vim.fn.strchars(line_text), 1) + loc = string.format('%d:%d-%d:%d', line_num, 1, line_num, end_col) + end + + return { + file_path = parts.file_path, + file_rel = parts.file_rel, + loc = loc, + symbol = get_symbol_at_cursor(), + } +end + +local function yank(label, value) + vim.fn.setreg('"', value) + pcall(vim.fn.setreg, '+', value) + vim.notify('Copied ' .. label .. ': ' .. value, vim.log.levels.INFO) +end + +local function copy_absolute_location_reference() + local parts = get_location_parts() + if not parts then + return + end + + local location = string.format('%s:%s', parts.file_path, parts.loc) + if parts.symbol and parts.symbol ~= '' then + location = location .. ' symbol:' .. parts.symbol + end + + yank('absolute location', location) +end + +local function copy_relative_location_reference() + local parts = get_location_parts() + if not parts then + return + end + + local symbol = parts.symbol and parts.symbol ~= '' and parts.symbol or 'none' + yank('relative location', string.format('@%s loc:%s symbol:%s', parts.file_rel, parts.loc, symbol)) +end + +local function copy_buffer_file_reference() + local parts = get_file_parts() + if not parts then + return + end + + yank('buffer file', string.format('@%s', parts.file_rel)) +end + +local function copy_buffer_directory_reference() + local parts = get_file_parts() + if not parts then + return + end + + local abs_dir = vim.fn.fnamemodify(parts.file_path, ':h') + local rel_dir = get_relative_path_from_root(abs_dir) + yank('buffer directory', string.format('@%s', rel_dir)) +end + return { 'folke/which-key.nvim', keys = { { 'ya', - function() - require('custom.location_reference').copy_absolute_location_reference() - end, + copy_absolute_location_reference, mode = { 'n', 'x' }, desc = '[Y]ank [A]bsolute ref', }, { 'yr', - function() - require('custom.location_reference').copy_relative_location_reference() - end, + copy_relative_location_reference, mode = { 'n', 'x' }, desc = '[Y]ank [R]elative ref', }, { 'yf', - function() - require('custom.location_reference').copy_buffer_file_reference() - end, + copy_buffer_file_reference, mode = { 'n', 'x' }, desc = '[Y]ank Buffer [F]ile', }, { 'yd', - function() - require('custom.location_reference').copy_buffer_directory_reference() - end, + copy_buffer_directory_reference, mode = { 'n', 'x' }, desc = '[Y]ank Buffer [D]irectory', }, From 0db92a7f4b6b65dd9919349ea03c72c645b9b9b5 Mon Sep 17 00:00:00 2001 From: "Paul B. Kim" Date: Fri, 6 Mar 2026 12:31:23 +0900 Subject: [PATCH 34/42] refactor(debug): inline C/C++ DAP setup into debug plugin spec dap_cpp.lua was a separate module used in exactly one place. Inlined setup_cpp_dap() directly into debug.lua to remove the unnecessary indirection. Co-Authored-By: Claude Sonnet 4.6 --- lua/custom/dap_cpp.lua | 43 ------------------------------------ lua/custom/plugins/debug.lua | 40 ++++++++++++++++++++++++++++++++- 2 files changed, 39 insertions(+), 44 deletions(-) delete mode 100644 lua/custom/dap_cpp.lua diff --git a/lua/custom/dap_cpp.lua b/lua/custom/dap_cpp.lua deleted file mode 100644 index 483fc41e9a0..00000000000 --- a/lua/custom/dap_cpp.lua +++ /dev/null @@ -1,43 +0,0 @@ -local M = {} - -local function has_configuration(configurations, name, adapter) - for _, configuration in ipairs(configurations or {}) do - if configuration.name == name and configuration.type == adapter then - return true - end - end - - return false -end - -function M.setup(dap) - dap = dap or require 'dap' - - dap.adapters.codelldb = dap.adapters.codelldb or { - type = 'executable', - command = 'codelldb', - } - - local launch_name = 'Launch current file (codelldb)' - local launch_configuration = { - name = launch_name, - type = 'codelldb', - request = 'launch', - program = function() - return vim.fn.input('Path to executable: ', vim.fn.getcwd() .. '/', 'file') - end, - cwd = '${workspaceFolder}', - stopOnEntry = false, - args = {}, - } - - for _, language in ipairs { 'c', 'cpp' } do - dap.configurations[language] = dap.configurations[language] or {} - - if not has_configuration(dap.configurations[language], launch_name, 'codelldb') then - table.insert(dap.configurations[language], vim.deepcopy(launch_configuration)) - end - end -end - -return M diff --git a/lua/custom/plugins/debug.lua b/lua/custom/plugins/debug.lua index ff9c8cb0535..ba9491db525 100644 --- a/lua/custom/plugins/debug.lua +++ b/lua/custom/plugins/debug.lua @@ -6,6 +6,44 @@ -- be extended to other languages as well. That's why it's called -- kickstart.nvim and not kitchen-sink.nvim ;) +local function has_configuration(configurations, name, adapter) + for _, configuration in ipairs(configurations or {}) do + if configuration.name == name and configuration.type == adapter then + return true + end + end + + return false +end + +local function setup_cpp_dap(dap) + dap.adapters.codelldb = dap.adapters.codelldb or { + type = 'executable', + command = 'codelldb', + } + + local launch_name = 'Launch current file (codelldb)' + local launch_configuration = { + name = launch_name, + type = 'codelldb', + request = 'launch', + program = function() + return vim.fn.input('Path to executable: ', vim.fn.getcwd() .. '/', 'file') + end, + cwd = '${workspaceFolder}', + stopOnEntry = false, + args = {}, + } + + for _, language in ipairs { 'c', 'cpp' } do + dap.configurations[language] = dap.configurations[language] or {} + + if not has_configuration(dap.configurations[language], launch_name, 'codelldb') then + table.insert(dap.configurations[language], vim.deepcopy(launch_configuration)) + end + end +end + return { -- NOTE: Yes, you can install new plugins here! 'mfussenegger/nvim-dap', @@ -146,6 +184,6 @@ return { }, } - require('custom.dap_cpp').setup(dap) + setup_cpp_dap(dap) end, } From 6d2fd09ce0a47f55edaec38b2822c58b885a58ef Mon Sep 17 00:00:00 2001 From: "Paul B. Kim" Date: Fri, 6 Mar 2026 22:41:52 +0900 Subject: [PATCH 35/42] fix(bufferline): preserve neo-tree on buffer cleanup --- lua/custom/plugins/bufferline.lua | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/lua/custom/plugins/bufferline.lua b/lua/custom/plugins/bufferline.lua index ba8a12f0f65..cdb68e06886 100644 --- a/lua/custom/plugins/bufferline.lua +++ b/lua/custom/plugins/bufferline.lua @@ -6,6 +6,19 @@ return { { '', 'BufferLineCyclePrev', desc = 'Previous buffer' }, { '', 'BufferLineCycleNext', desc = 'Next buffer' }, { 'bp', 'BufferLinePick', desc = '[B]uffer [P]ick' }, + { + 'bD', + function() + local bufremove = require 'mini.bufremove' + + for _, bufnr in ipairs(vim.api.nvim_list_bufs()) do + if vim.api.nvim_buf_is_valid(bufnr) and vim.bo[bufnr].buflisted and vim.bo[bufnr].filetype ~= 'neo-tree' then + bufremove.delete(bufnr, false) + end + end + end, + desc = '[B]uffer [D]elete all', + }, }, opts = { options = { From 448f3de38940af6b438f1d09e1755845e2fc99af Mon Sep 17 00:00:00 2001 From: "Paul B. Kim" Date: Sat, 7 Mar 2026 22:33:45 +0900 Subject: [PATCH 36/42] fix(markdown): conditionally open preview browser --- lua/custom/plugins/markdown.lua | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/lua/custom/plugins/markdown.lua b/lua/custom/plugins/markdown.lua index 26cc99297f6..82a8729caca 100644 --- a/lua/custom/plugins/markdown.lua +++ b/lua/custom/plugins/markdown.lua @@ -15,10 +15,28 @@ return { vim.fn['mkdp#util#install']() end, init = function() + local browser = '' + local is_macos = vim.fn.has('macunix') == 1 + + if is_macos then + vim.cmd([[ + function! OpenMarkdownPreview(url) abort + execute 'silent !open ' . shellescape(a:url) + endfunction + ]]) + elseif vim.fn.executable('google-chrome-stable') == 1 then + browser = 'google-chrome-stable' + elseif vim.fn.executable('google-chrome') == 1 then + browser = 'google-chrome' + elseif vim.fn.executable('chromium') == 1 then + browser = 'chromium' + end + vim.g.mkdp_auto_start = 0 vim.g.mkdp_auto_close = 1 vim.g.mkdp_refresh_slow = 0 - vim.g.mkdp_browser = 'google-chrome-stable' + vim.g.mkdp_browser = browser + vim.g.mkdp_browserfunc = is_macos and 'OpenMarkdownPreview' or '' vim.g.mkdp_filetypes = { 'markdown' } end, keys = { From f9b4a07158b9260875440697a5d967edc33ca7c5 Mon Sep 17 00:00:00 2001 From: "Paul B. Kim" Date: Sun, 5 Apr 2026 15:12:18 +0900 Subject: [PATCH 37/42] add claude base --- .claude/settings.json | 5 ++++ .claude/settings.local.json | 8 ++++++ .githooks/post-commit | 8 ++++++ .gitignore | 2 +- AGENTS.md | 48 ----------------------------------- init.lua | 50 +++++++++++++++++++++++++++---------- 6 files changed, 59 insertions(+), 62 deletions(-) create mode 100644 .claude/settings.json create mode 100644 .claude/settings.local.json create mode 100755 .githooks/post-commit delete mode 100644 AGENTS.md diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 00000000000..be47d4a5761 --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,5 @@ +{ + "enabledPlugins": { + "lua-lsp@claude-plugins-official": true + } +} diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 00000000000..a8c9ff87232 --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,8 @@ +{ + "permissions": { + "allow": [ + "Bash(wc:*)", + "Bash(git:*)" + ] + } +} diff --git a/.githooks/post-commit b/.githooks/post-commit new file mode 100755 index 00000000000..fb8fa665cee --- /dev/null +++ b/.githooks/post-commit @@ -0,0 +1,8 @@ +#!/bin/sh +echo "→ Pushing to remote..." +if git push; then + echo "✓ Push successful." +else + echo "✗ Push failed. Run 'git push' manually to retry." >&2 + exit 1 +fi diff --git a/.gitignore b/.gitignore index e4b332903af..e00a4f59297 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,4 @@ nvim spell/ lazy-lock.json -.serena/ +.local diff --git a/AGENTS.md b/AGENTS.md deleted file mode 100644 index f329f55b6c0..00000000000 --- a/AGENTS.md +++ /dev/null @@ -1,48 +0,0 @@ -# Repository Guidelines - -## Project Structure & Module Organization -- Root entrypoint: `init.lua` (Kickstart-based primary config). -- Custom Lua modules live under `lua/custom/`. -- Plugin specs are split into `lua/custom/plugins/*.lua` (one concern per file, e.g. `gitsigns.lua`, `lint.lua`, `persistence.lua`). -- Utility modules (non-plugin config) live in `lua/custom/*.lua` (for example `wrapping.lua`). -- Reference docs live in `README.md` and `doc/kickstart.txt`. -- Plugin versions are pinned in `lazy-lock.json`. - -## Build, Test, and Development Commands -- `nvim` - Starts Neovim and triggers lazy.nvim plugin loading/install. -- `nvim --headless "+qa"` - Fast startup sanity check (useful in CI-style validation). -- `nvim --headless "+checkhealth" "+qa"` - Runs health checks for Neovim, plugins, and external tools. -- `nvim --headless "+Lazy! sync" "+qa"` - Syncs plugin set to current specs. -- `luac -p init.lua lua/custom/**/*.lua` - Lua syntax validation for config files. - -## Coding Style & Naming Conventions -- Language: Lua (Neovim API style). -- Formatting: `stylua` using `.stylua.toml` settings (2-space indentation, no tabs). -- Prefer small, focused plugin spec files named by feature (`neo-tree.lua`, `markdown.lua`). -- Use descriptive keymap `desc` fields and group prefixes via which-key. -- Keep comments concise and practical; avoid repeating obvious code behavior. - -## Testing Guidelines -- No formal unit-test framework is configured in this repo. -- Required checks before PR: - - Lua parse check (`luac -p ...`) - - Headless startup (`nvim --headless "+qa"`) - - Health check (`:checkhealth`) for affected tooling (LSP, formatters, linters). -- For plugin/config changes, include manual verification steps in PR notes (keymaps, commands, expected behavior). - -## Commit & Pull Request Guidelines -- Follow existing history style: Conventional Commit-like prefixes such as: - - `feat(scope): ...` - - `fix(scope): ...` - - `chore: ...` -- Keep commits scoped to one logical change (plugin, keymap group, diagnostics, etc.). -- PRs should include: - - Summary of behavior changes - - Files touched (e.g. `init.lua`, `lua/custom/plugins/...`) - - Validation performed (commands run) - - Screenshots/GIFs only when UI behavior is materially changed. diff --git a/init.lua b/init.lua index 386a854032f..5014167722f 100644 --- a/init.lua +++ b/init.lua @@ -84,6 +84,9 @@ I hope you enjoy your Neovim journey, P.S. You can delete this when you're done too. It's your config now! :) --]] +-- Prepend mise shims to PATH so Mason/LSP can find node, go, etc. +vim.env.PATH = vim.env.HOME .. '/.local/share/mise/shims:' .. vim.env.PATH + -- Set as the leader key -- See `:help mapleader` -- NOTE: Must happen before plugins are loaded (otherwise wrong leader will be used) @@ -184,6 +187,26 @@ vim.keymap.set('n', ']d', function() vim.diagnostic.jump { count = 1, float = true } end, { desc = 'Go to next [D]iagnostic' }) vim.keymap.set('n', 'de', vim.diagnostic.open_float, { desc = 'Show [D]iagnostic [E]rror details' }) +vim.keymap.set('n', 'dy', function() + local line = vim.api.nvim_win_get_cursor(0)[1] + local diagnostics = vim.diagnostic.get(0, { lnum = line - 1 }) + if #diagnostics == 0 then + vim.notify('No diagnostics on this line', vim.log.levels.INFO) + return + end + local file_path = vim.api.nvim_buf_get_name(0) + local root = vim.fs.root(file_path, { '.git' }) or vim.fn.getcwd() + local prefix = root:match '/$' and root or (root .. '/') + local rel = file_path:sub(1, #prefix) == prefix and file_path:sub(#prefix + 1) or file_path + local parts = {} + for _, d in ipairs(diagnostics) do + table.insert(parts, string.format('@%s:%d: %s', rel, d.lnum + 1, d.message)) + end + local result = table.concat(parts, '\n') + vim.fn.setreg('"', result) + pcall(vim.fn.setreg, '+', result) + vim.notify('Yanked ' .. #diagnostics .. ' diagnostic(s)', vim.log.levels.INFO) +end, { desc = '[D]iagnostic [Y]ank to clipboard' }) -- 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 @@ -1104,19 +1127,20 @@ require('lazy').setup({ -- [[ Configure Treesitter ]] See `:help nvim-treesitter` config = function() local treesitter = require 'nvim-treesitter' - treesitter.setup() - require('nvim-treesitter.install').ensure_installed { - 'bash', - 'c', - 'diff', - 'html', - 'lua', - 'luadoc', - 'markdown', - 'markdown_inline', - 'query', - 'vim', - 'vimdoc', + treesitter.setup { + ensure_installed = { + 'bash', + 'c', + 'diff', + 'html', + 'lua', + 'luadoc', + 'markdown', + 'markdown_inline', + 'query', + 'vim', + 'vimdoc', + }, } vim.api.nvim_create_autocmd('FileType', { From 5e80798015cd7abfbabaa8be1b5a311364471a65 Mon Sep 17 00:00:00 2001 From: "Paul B. Kim" Date: Fri, 8 May 2026 23:44:07 +0900 Subject: [PATCH 38/42] remove opencode plugin config --- lua/custom/plugins/opencode.lua | 178 -------------------------------- 1 file changed, 178 deletions(-) delete mode 100644 lua/custom/plugins/opencode.lua diff --git a/lua/custom/plugins/opencode.lua b/lua/custom/plugins/opencode.lua deleted file mode 100644 index 56c1346db5a..00000000000 --- a/lua/custom/plugins/opencode.lua +++ /dev/null @@ -1,178 +0,0 @@ -return { - 'NickvanDyke/opencode.nvim', - dependencies = { - -- Recommended for `ask()` and `select()`. - -- Required for `snacks` provider. - ---@module 'snacks' <- Loads `snacks.nvim` types for configuration intellisense. - { - 'folke/snacks.nvim', - opts = { input = {}, picker = {}, terminal = {}, rename = {} }, - keys = { - { 'cR', function() Snacks.rename.rename_file() end, desc = 'Rename File (LSP)' }, - }, - }, - }, - config = function() - local function redact_sensitive(value) - if type(value) ~= 'string' then - return value - end - - local redacted = value - redacted = redacted:gsub('([Aa]uthorization%s*:%s*[Bb]earer%s+)[^%s,;]+', '%1[REDACTED]') - redacted = redacted:gsub('([Aa][Pp][Ii][_%-%s]?[Kk][Ee][Yy]%s*[:=]%s*)[^%s,;]+', '%1[REDACTED]') - redacted = redacted:gsub('([Tt][Oo][Kk][Ee][Nn]%s*[:=]%s*)[^%s,;]+', '%1[REDACTED]') - return redacted - end - - local function truncate(value, max_len) - if #value <= max_len then - return value - end - - return value:sub(1, max_len) .. '...' - end - - local function format_opencode_error(err) - if type(err) == 'string' then - return truncate(redact_sensitive(err), 300) - end - - if type(err) == 'table' then - local message = err.message or err.msg or err.error or err.reason - if type(message) == 'string' and message ~= '' then - return truncate(redact_sensitive(message), 300) - end - - local status = err.status or err.code - if status ~= nil then - return 'opencode request failed (' .. tostring(status) .. ')' - end - - return 'opencode request failed' - end - - return truncate(redact_sensitive(tostring(err)), 300) - end - - local function notify_opencode_error(err) - if not err then - return - end - - vim.notify(format_opencode_error(err), vim.log.levels.ERROR, { title = 'opencode' }) - end - - local function opencode_ask(default, opts) - opts = opts or {} - opts.context = opts.context or require('opencode.context').new() - - return require('opencode.ui.ask') - .ask(default, opts.context) - :next(function(input) - if input:sub(-2) == '\\n' then - input = input:sub(1, -3) .. '\n' - opts.clear = false - opts.submit = false - end - - opts.context:clear() - return require('opencode.api.prompt').prompt(input, opts) - end) - :catch(notify_opencode_error) - end - - local function opencode_select(opts) - return require('opencode.ui.select').select(opts):catch(notify_opencode_error) - end - - local function opencode_command(command) - return require('opencode.api.command').command(command):catch(notify_opencode_error) - end - - ---@type opencode.Opts - vim.g.opencode_opts = { - provider = { - enabled = 'snacks', - snacks = { - auto_close = false, - win = { - position = 'right', - border = 'rounded', - width = math.floor(vim.o.columns * 0.35), - enter = false, - }, - }, - }, - } - - -- Required for `opts.events.reload`. - vim.o.autoread = true - - -- Recommended/example keymaps. - vim.keymap.set({ 'n', 'x' }, '', function() - opencode_ask('@this: ', { submit = true }) - end, { desc = 'Ask opencode…' }) - vim.keymap.set({ 'n', 'x' }, '', function() - opencode_select() - end, { desc = 'Execute opencode action…' }) - local function opencode_toggle() - local ok, err = pcall(function() - require('opencode').toggle() - end) - if not ok then - vim.notify('opencode toggle failed: ' .. tostring(err), vim.log.levels.ERROR) - end - end - - vim.keymap.set({ 'n', 't' }, '', opencode_toggle, { desc = 'Toggle opencode' }) - vim.keymap.set('n', 'oc', opencode_toggle, { desc = 'Toggle opencode chat' }) - - local opencode_term_nav_group = vim.api.nvim_create_augroup('opencode-term-nav', { clear = true }) - vim.api.nvim_create_autocmd('FileType', { - group = opencode_term_nav_group, - pattern = 'opencode_terminal', - callback = function(ev) - local function tmux_nav(cmd) - local esc = vim.api.nvim_replace_termcodes('', true, false, true) - vim.api.nvim_feedkeys(esc, 'n', false) - vim.cmd(cmd) - end - - vim.keymap.set('t', '', function() - tmux_nav 'TmuxNavigateLeft' - end, { buffer = ev.buf, silent = true, desc = 'Tmux navigate left from opencode' }) - - vim.keymap.set('t', '', function() - tmux_nav 'TmuxNavigateDown' - end, { buffer = ev.buf, silent = true, desc = 'Tmux navigate down from opencode' }) - - vim.keymap.set('t', '', function() - tmux_nav 'TmuxNavigateUp' - end, { buffer = ev.buf, silent = true, desc = 'Tmux navigate up from opencode' }) - - vim.keymap.set('t', '', function() - tmux_nav 'TmuxNavigateRight' - end, { buffer = ev.buf, silent = true, desc = 'Tmux navigate right from opencode' }) - end, - }) - - vim.keymap.set({ 'n', 'x' }, 'go', function() - return require('opencode').operator '@this ' - end, { desc = 'Add range to opencode', expr = true }) - vim.keymap.set('n', 'goo', function() - return require('opencode').operator '@this ' .. '_' - end, { desc = 'Add line to opencode', expr = true }) - - vim.keymap.set('n', '', function() - opencode_command 'session.half.page.up' - end, { desc = 'Scroll opencode up' }) - vim.keymap.set('n', '', function() - opencode_command 'session.half.page.down' - end, { desc = 'Scroll opencode down' }) - - -- You may want these if you stick with the opinionated "" and "" above — otherwise consider "o…". - vim.keymap.set('n', '+', '', { desc = 'Increment under cursor', noremap = true }) - vim.keymap.set('n', '-', '', { desc = 'Decrement under cursor', noremap = true }) - end, -} From 48ba60ab707f293398cd645f043955daf79436cd Mon Sep 17 00:00:00 2001 From: "Paul B. Kim" Date: Fri, 8 May 2026 23:44:37 +0900 Subject: [PATCH 39/42] clean up gitsigns keymaps --- lua/custom/plugins/gitsigns.lua | 29 +++-------------------------- 1 file changed, 3 insertions(+), 26 deletions(-) diff --git a/lua/custom/plugins/gitsigns.lua b/lua/custom/plugins/gitsigns.lua index 8b298093507..bf037d7207d 100644 --- a/lua/custom/plugins/gitsigns.lua +++ b/lua/custom/plugins/gitsigns.lua @@ -44,6 +44,7 @@ return { map('n', 'hs', gitsigns.stage_hunk, { desc = 'git [s]tage hunk' }) map('n', 'hr', gitsigns.reset_hunk, { desc = 'git [r]eset hunk' }) map('n', 'hS', gitsigns.stage_buffer, { desc = 'git [S]tage buffer' }) + map('n', 'hu', gitsigns.undo_stage_hunk, { desc = 'git [u]ndo stage hunk' }) map('n', 'hR', gitsigns.reset_buffer, { desc = 'git [R]eset buffer' }) map('n', 'hp', gitsigns.preview_hunk, { desc = 'git [p]review hunk' }) map('n', 'hi', gitsigns.preview_hunk_inline, { desc = 'git preview hunk [i]nline' }) @@ -55,31 +56,7 @@ return { -- Toggles map('n', 'tb', gitsigns.toggle_current_line_blame, { desc = '[T]oggle git show [b]lame line' }) map('n', 'tw', gitsigns.toggle_word_diff) - - -- Actions - -- visual mode - map('v', 'hs', function() - gitsigns.stage_hunk { vim.fn.line '.', vim.fn.line 'v' } - end, { desc = 'git [s]tage hunk' }) - map('v', 'hr', function() - gitsigns.reset_hunk { vim.fn.line '.', vim.fn.line 'v' } - end, { desc = 'git [r]eset hunk' }) - -- normal mode - map('n', 'hs', gitsigns.stage_hunk, { desc = 'git [s]tage hunk' }) - map('n', 'hr', gitsigns.reset_hunk, { desc = 'git [r]eset hunk' }) - map('n', 'hS', gitsigns.stage_buffer, { desc = 'git [S]tage buffer' }) - map('n', 'hu', gitsigns.undo_stage_hunk, { desc = 'git [u]ndo stage hunk' }) - map('n', 'hR', gitsigns.reset_buffer, { desc = 'git [R]eset buffer' }) - map('n', 'hp', gitsigns.preview_hunk, { desc = 'git [p]review hunk' }) - map('n', 'hb', gitsigns.blame_line, { desc = 'git [b]lame line' }) - map('n', 'hd', gitsigns.diffthis, { desc = 'git [d]iff against index' }) - map('n', 'hD', function() - gitsigns.diffthis '@' - end, { desc = 'git [D]iff against last commit' }) - -- Toggles - map('n', 'tb', gitsigns.toggle_current_line_blame, { desc = '[T]oggle git show [b]lame line' }) - map('n', 'tD', gitsigns.toggle_deleted, { desc = '[T]oggle git show [D]eleted' }) - end, - }, + map('n', 'tD', gitsigns.toggle_deleted, { desc = '[T]oggle git show [D]eleted' }) + end, }, } From 20fd850084b0f4fe8dcbfdc6281939f93a760c8d Mon Sep 17 00:00:00 2001 From: "Paul B. Kim" Date: Fri, 8 May 2026 23:44:54 +0900 Subject: [PATCH 40/42] harden language tooling setup --- init.lua | 130 ++++++++++++++++++------ lua/custom/plugins/debug.lua | 24 ++--- lua/custom/plugins/typescript_tools.lua | 9 +- 3 files changed, 117 insertions(+), 46 deletions(-) diff --git a/init.lua b/init.lua index 7bcfaafd5b1..c4a2c9db2ee 100644 --- a/init.lua +++ b/init.lua @@ -124,6 +124,26 @@ vim.o.shiftwidth = 2 vim.o.softtabstop = 2 vim.o.expandtab = true +vim.filetype.add { + extension = { + mdx = 'mdx', + }, + filename = { + ['compose.yaml'] = 'yaml.docker-compose', + ['compose.yml'] = 'yaml.docker-compose', + ['docker-compose.yaml'] = 'yaml.docker-compose', + ['docker-compose.yml'] = 'yaml.docker-compose', + ['.gitlab-ci.yaml'] = 'yaml.gitlab', + ['.gitlab-ci.yml'] = 'yaml.gitlab', + }, + pattern = { + ['.*/templates/.*%.yaml'] = 'helm', + ['.*/templates/.*%.yml'] = 'helm', + ['.*/values.*%.yaml'] = 'yaml.helm-values', + ['.*/values.*%.yml'] = 'yaml.helm-values', + }, +} + -- Enable undo/redo changes even after closing and reopening a file vim.o.undofile = true @@ -331,6 +351,20 @@ end local rtp = vim.opt.rtp rtp:prepend(lazypath) +local treesitter_parsers = { + 'bash', + 'c', + 'diff', + 'html', + 'lua', + 'luadoc', + 'markdown', + 'markdown_inline', + 'query', + 'vim', + 'vimdoc', +} + -- [[ Configure and install plugins ]] -- -- To check the current status of your plugins, run @@ -396,7 +430,6 @@ require('lazy').setup({ { 'G', group = '[G]it' }, { 'm', group = '[M]arkdown' }, { 'n', group = '[N]eotest' }, - { 'o', group = '[O]pencode' }, { 's', group = '[S]earch' }, { 't', group = '[T]oggle' }, { 'w', group = '[W]indow' }, @@ -768,22 +801,11 @@ require('lazy').setup({ languages = { 'vue' }, configNamespace = 'typescript', } + local has_go = vim.fn.executable 'go' == 1 local servers = { -- Languages clangd = {}, - gopls = { - settings = { - gopls = { - analyses = { - unusedparams = true, - shadow = true, - }, - staticcheck = true, - gofumpt = true, - }, - }, - }, basedpyright = { settings = { basedpyright = { @@ -821,7 +843,6 @@ require('lazy').setup({ taplo = {}, elixirls = {}, gh_actions_ls = {}, - jqls = {}, lua_ls = { -- cmd = { ... }, -- filetypes = { ... }, @@ -863,6 +884,44 @@ require('lazy').setup({ -- But for many setups, the LSP (`ts_ls`) will work just fine -- } + + if has_go then + servers.gopls = { + settings = { + gopls = { + analyses = { + unusedparams = true, + shadow = true, + }, + staticcheck = true, + gofumpt = true, + }, + }, + } + end + + if vim.fn.executable 'jq-lsp' == 1 then + servers.jqls = {} + end + + servers.marksman.filetypes = { 'markdown', 'mdx' } + servers.tailwindcss.filetypes = { + 'astro', + 'css', + 'html', + 'javascript', + 'javascriptreact', + 'less', + 'markdown', + 'mdx', + 'sass', + 'scss', + 'svelte', + 'typescript', + 'typescriptreact', + 'vue', + } + ---@type MasonLspconfigSettings ---@diagnostic disable-next-line: missing-fields require('mason-lspconfig').setup { @@ -876,7 +935,9 @@ require('lazy').setup({ -- :Mason -- -- You can press `g?` for help in this menu. - local ensure_installed = vim.tbl_keys(servers or {}) + local ensure_installed = vim.tbl_filter(function(server_name) + return server_name ~= 'gopls' and server_name ~= 'jqls' + end, vim.tbl_keys(servers or {})) vim.list_extend(ensure_installed, { 'stylua', -- Used to format Lua code 'markdownlint', -- Used by nvim-lint for Markdown buffers @@ -1125,26 +1186,30 @@ require('lazy').setup({ 'nvim-treesitter/nvim-treesitter', branch = 'main', lazy = false, - lazy = false, - build = ':TSUpdate', + build = function() + if vim.fn.executable 'tree-sitter' ~= 1 then + vim.notify('nvim-treesitter: tree-sitter CLI is required to install parsers', vim.log.levels.WARN) + return + end + + local ok, treesitter = pcall(require, 'nvim-treesitter') + if ok then + treesitter.install(treesitter_parsers, { summary = true }):wait(300000) + end + end, -- [[ Configure Treesitter ]] See `:help nvim-treesitter` config = function() local treesitter = require 'nvim-treesitter' - treesitter.setup { - ensure_installed = { - 'bash', - 'c', - 'diff', - 'html', - 'lua', - 'luadoc', - 'markdown', - 'markdown_inline', - 'query', - 'vim', - 'vimdoc', - }, - } + treesitter.setup() + + vim.api.nvim_create_user_command('TSInstallConfigured', function() + if vim.fn.executable 'tree-sitter' ~= 1 then + vim.notify('nvim-treesitter: install tree-sitter CLI first', vim.log.levels.ERROR) + return + end + + treesitter.install(treesitter_parsers, { summary = true }):raise_on_error() + end, { desc = 'Install configured Treesitter parsers' }) vim.api.nvim_create_autocmd('FileType', { group = vim.api.nvim_create_augroup('kickstart-treesitter', { clear = true }), @@ -1176,6 +1241,7 @@ require('lazy').setup({ -- In normal mode type `sh` then write `lazy.nvim-plugin` -- you can continue same window with `sr` which resumes last telescope search }, { ---@diagnostic disable-line: missing-fields + rocks = { enabled = false }, ui = { -- If you are using a Nerd Font: set icons to an empty table which will use the -- default lazy.nvim defined Nerd Font icons, otherwise define a unicode icons table diff --git a/lua/custom/plugins/debug.lua b/lua/custom/plugins/debug.lua index 765294008ea..84feb62f2d3 100644 --- a/lua/custom/plugins/debug.lua +++ b/lua/custom/plugins/debug.lua @@ -78,6 +78,7 @@ return { config = function() local dap = require 'dap' local dapui = require 'dapui' + local has_delve = vim.fn.executable 'dlv' == 1 require('mason-nvim-dap').setup { -- Makes a best effort to setup the various debuggers with @@ -90,11 +91,7 @@ return { -- You'll need to check that you have the required things installed -- online, please don't ask me how to install them :) - ensure_installed = { - -- Update this to ensure that you have the debuggers for the langs you want - 'delve', - 'codelldb', - }, + ensure_installed = { 'codelldb' }, } -- Dap UI setup @@ -137,14 +134,15 @@ return { dap.listeners.before.event_terminated['dapui_config'] = dapui.close dap.listeners.before.event_exited['dapui_config'] = dapui.close - -- Install golang specific config - require('dap-go').setup { - delve = { - -- On Windows delve must be run attached or it crashes. - -- See https://github.com/leoluz/nvim-dap-go/blob/main/README.md#configuring - detached = vim.fn.has 'win32' == 0, - }, - } + if has_delve then + require('dap-go').setup { + delve = { + -- On Windows delve must be run attached or it crashes. + -- See https://github.com/leoluz/nvim-dap-go/blob/main/README.md#configuring + detached = vim.fn.has 'win32' == 0, + }, + } + end setup_cpp_dap(dap) end, diff --git a/lua/custom/plugins/typescript_tools.lua b/lua/custom/plugins/typescript_tools.lua index ba7fb2046e7..fb036b6aeef 100644 --- a/lua/custom/plugins/typescript_tools.lua +++ b/lua/custom/plugins/typescript_tools.lua @@ -1,5 +1,12 @@ return { 'pmizio/typescript-tools.nvim', dependencies = { 'nvim-lua/plenary.nvim', 'neovim/nvim-lspconfig' }, - opts = {}, + opts = { + filetypes = { + 'javascript', + 'javascriptreact', + 'typescript', + 'typescriptreact', + }, + }, } From 37a75addf9976f1441d7b6b529c2402045f13256 Mon Sep 17 00:00:00 2001 From: "Paul B. Kim" Date: Sat, 9 May 2026 00:05:20 +0900 Subject: [PATCH 41/42] tidy and harden neovim config --- init.lua | 649 ++++-------------------------- lua/custom/plugins/bufferline.lua | 8 +- lua/custom/plugins/debug.lua | 65 +-- lua/custom/plugins/gitsigns.lua | 2 - lua/custom/plugins/init.lua | 5 - lua/custom/plugins/lint.lua | 48 +-- lua/custom/plugins/markdown.lua | 21 +- lua/custom/plugins/neo-tree.lua | 2 +- lua/custom/plugins/neogit.lua | 11 +- 9 files changed, 114 insertions(+), 697 deletions(-) diff --git a/init.lua b/init.lua index c4a2c9db2ee..e85335cabca 100644 --- a/init.lua +++ b/init.lua @@ -1,95 +1,9 @@ ---[[ - -===================================================================== -==================== READ THIS BEFORE CONTINUING ==================== -===================================================================== -======== .-----. ======== -======== .----------------------. | === | ======== -======== |.-""""""""""""""""""-.| |-----| ======== -======== || || | === | ======== -======== || KICKSTART.NVIM || |-----| ======== -======== || || | === | ======== -======== || || |-----| ======== -======== ||:Tutor || |:::::| ======== -======== |'-..................-'| |____o| ======== -======== `"")----------------(""` ___________ ======== -======== /::::::::::| |::::::::::\ \ no mouse \ ======== -======== /:::========| |==hjkl==:::\ \ required \ ======== -======== '""""""""""""' '""""""""""""' '""""""""""' ======== -======== ======== -===================================================================== -===================================================================== - -What is Kickstart? - - Kickstart.nvim is *not* a distribution. - - Kickstart.nvim is a starting point for your own configuration. - The goal is that you can read every line of code, top-to-bottom, understand - what your configuration is doing, and modify it to suit your needs. - - Once you've done that, you can start exploring, configuring and tinkering to - make Neovim your own! That might mean leaving Kickstart just the way it is for a while - or immediately breaking it into modular pieces. It's up to you! - - If you don't know anything about Lua, I recommend taking some time to read through - a guide. One possible example which will only take 10-15 minutes: - - https://learnxinyminutes.com/docs/lua/ - - After understanding a bit more about Lua, you can use `:help lua-guide` as a - reference for how Neovim integrates Lua. - - :help lua-guide - - (or HTML version): https://neovim.io/doc/user/lua-guide.html - -Kickstart Guide: - - TODO: The very first thing you should do is to run the command `:Tutor` in Neovim. - - If you don't know what this means, type the following: - - - - : - - Tutor - - - - (If you already know the Neovim basics, you can skip this step.) - - Once you've completed that, you can continue working through **AND READING** the rest - of the kickstart init.lua. - - Next, run AND READ `:help`. - This will open up a help window with some basic information - about reading, navigating and searching the builtin help documentation. - - This should be the first place you go to look when you're stuck or confused - with something. It's one of my favorite Neovim features. - - MOST IMPORTANTLY, we provide a keymap "sh" to [s]earch the [h]elp documentation, - which is very useful when you're not exactly sure of what you're looking for. - - I have left several `:help X` comments throughout the init.lua - These are hints about where to find more information about the relevant settings, - plugins or Neovim features used in Kickstart. - - NOTE: Look for lines like this - - Throughout the file. These are for you, the reader, to help you understand what is happening. - Feel free to delete them once you know what you're doing, but they should serve as a guide - for when you are first encountering a few different constructs in your Neovim config. - -If you experience any errors while trying to install kickstart, run `:checkhealth` for more info. - -I hope you enjoy your Neovim journey, -- TJ - -P.S. You can delete this when you're done too. It's your config now! :) ---]] - -- Prepend mise shims to PATH so Mason/LSP can find node, go, etc. vim.env.PATH = vim.env.HOME .. '/.local/share/mise/shims:' .. vim.env.PATH -- Set as the leader key -- See `:help mapleader` --- NOTE: Must happen before plugins are loaded (otherwise wrong leader will be used) +-- Must happen before plugins are loaded. vim.g.mapleader = ' ' vim.g.maplocalleader = ' ' @@ -98,12 +12,9 @@ vim.g.have_nerd_font = false -- [[ Setting options ]] -- See `:help vim.o` --- NOTE: You can change these options as you wish! --- For more options, you can see `:help option-list` -- Make line numbers default vim.o.number = true --- You can also add relative line numbers, to help with jumping. vim.o.relativenumber = true -- Enable mouse mode, can be useful for resizing splits for example! @@ -189,6 +100,14 @@ vim.o.scrolloff = 10 -- See `:help 'confirm'` vim.o.confirm = true +-- Modern UI defaults and safer project-local behavior. +vim.o.winborder = 'rounded' +vim.o.pumborder = 'rounded' +vim.o.pummaxwidth = 80 +vim.o.smoothscroll = true +vim.o.modeline = false +vim.o.jumpoptions = 'clean,view' + -- [[ Basic Keymaps ]] -- See `:help vim.keymap.set()` @@ -198,27 +117,40 @@ vim.keymap.set('n', '', 'nohlsearch') -- Diagnostic Config & Keymaps -- See :help vim.diagnostic.Opts +local function diagnostic_on_jump(diagnostic) + if diagnostic then vim.diagnostic.open_float { scope = 'cursor', focus = false } end +end + vim.diagnostic.config { update_in_insert = false, severity_sort = true, float = { border = 'rounded', source = 'if_many' }, - underline = { severity = { min = vim.diagnostic.severity.WARN } }, - - -- Can switch between these as you prefer - virtual_text = true, -- Text shows up at the end of the line - virtual_lines = false, -- Text shows up underneath the line, with virtual lines - - -- Auto open the float, so you can easily read the errors when jumping with `[d` and `]d` - jump = { float = true }, + underline = { severity = vim.diagnostic.severity.ERROR }, + signs = vim.g.have_nerd_font and { + text = { + [vim.diagnostic.severity.ERROR] = '󰅚 ', + [vim.diagnostic.severity.WARN] = '󰀪 ', + [vim.diagnostic.severity.INFO] = '󰋽 ', + [vim.diagnostic.severity.HINT] = '󰌶 ', + }, + } or {}, + virtual_text = { + source = 'if_many', + spacing = 2, + severity = { min = vim.diagnostic.severity.WARN }, + format = function(diagnostic) + if diagnostic.severity == vim.diagnostic.severity.WARN then return 'W: ' .. diagnostic.message end + if diagnostic.severity == vim.diagnostic.severity.ERROR then return 'E: ' .. diagnostic.message end + return nil + end, + }, + virtual_lines = false, + jump = { on_jump = diagnostic_on_jump }, } vim.keymap.set('n', 'q', vim.diagnostic.setloclist, { desc = 'Open diagnostic [Q]uickfix list' }) -vim.keymap.set('n', '[d', function() - vim.diagnostic.jump { count = -1, float = true } -end, { desc = 'Go to previous [D]iagnostic' }) -vim.keymap.set('n', ']d', function() - vim.diagnostic.jump { count = 1, float = true } -end, { desc = 'Go to next [D]iagnostic' }) +vim.keymap.set('n', '[d', function() vim.diagnostic.jump { count = -1 } end, { desc = 'Go to previous [D]iagnostic' }) +vim.keymap.set('n', ']d', function() vim.diagnostic.jump { count = 1 } end, { desc = 'Go to next [D]iagnostic' }) vim.keymap.set('n', 'de', vim.diagnostic.open_float, { desc = 'Show [D]iagnostic [E]rror details' }) vim.keymap.set('n', 'dy', function() local line = vim.api.nvim_win_get_cursor(0)[1] @@ -279,9 +211,7 @@ vim.keymap.set('n', 'wk', '', { desc = 'Move focus to the uppe 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.hl.on_yank() - end, + callback = function() vim.hl.on_yank() end, }) local path_on_save_group = vim.api.nvim_create_augroup('kickstart-create-path-on-save', { clear = true }) @@ -296,44 +226,32 @@ local is_in_allowed_root = function(path) local abs_path = vim.fn.fnamemodify(path, ':p') for _, root in ipairs(path_create_roots) do local abs_root = vim.fn.fnamemodify(root, ':p') - if abs_path:sub(1, #abs_root) == abs_root then - return true - end + if abs_path:sub(1, #abs_root) == abs_root then return true end end return false end local ensure_owner_rwx = function(path) local perms = vim.fn.getfperm(path) - if perms ~= '' and perms:sub(1, 3) ~= 'rwx' then - vim.fn.setfperm(path, 'rwx' .. perms:sub(4)) - end + if perms ~= '' and perms:sub(1, 3) ~= 'rwx' then vim.fn.setfperm(path, 'rwx' .. perms:sub(4)) end end vim.api.nvim_create_autocmd('BufWritePre', { desc = 'Create missing parent directories on save', group = path_on_save_group, callback = function(args) - if vim.bo[args.buf].buftype ~= '' then - return - end + if vim.bo[args.buf].buftype ~= '' then return end local file_path = vim.api.nvim_buf_get_name(args.buf) - if file_path == '' or file_path:match '^%w+://' then - return - end + if file_path == '' or file_path:match '^%w+://' then return end local uv = vim.uv or vim.loop local abs_path = vim.fn.fnamemodify(file_path, ':p') - if not is_in_allowed_root(abs_path) then - return - end + if not is_in_allowed_root(abs_path) then return end local parent = vim.fn.fnamemodify(abs_path, ':h') - if uv.fs_stat(parent) == nil then - vim.fn.mkdir(parent, 'p', '0700') - end + if uv.fs_stat(parent) == nil then vim.fn.mkdir(parent, 'p', '0700') end ensure_owner_rwx(parent) end, }) @@ -366,61 +284,19 @@ local treesitter_parsers = { } -- [[ Configure and install plugins ]] --- --- To check the current status of your plugins, run --- :Lazy --- --- You can press `?` in this menu for help. Use `:q` to close the window --- --- To update plugins you can run --- :Lazy update --- --- NOTE: Here is where you install your plugins. require('lazy').setup({ - -- NOTE: Plugins can be added via a link or github org/name. To run setup automatically, use `opts = {}` { 'NMAC427/guess-indent.nvim', opts = {} }, - -- Alternatively, use `config = function() ... end` for full control over the configuration. - -- If you prefer to call `setup` explicitly, use: - -- { - -- 'lewis6991/gitsigns.nvim', - -- config = function() - -- require('gitsigns').setup({ - -- -- Your gitsigns configuration here - -- }) - -- end, - -- } - -- - -- Here is a more advanced example where we pass configuration - -- options to `gitsigns.nvim`. - -- - -- See `:help gitsigns` to understand what the configuration keys do - -- NOTE: Plugins can also be configured to run Lua code when they are loaded. - -- - -- This is often very useful to both group configuration, as well as handle - -- lazy loading plugins that don't need to be loaded immediately at startup. - -- - -- For example, in the following configuration, we use: - -- event = 'VimEnter' - -- - -- which loads which-key before all the UI elements are loaded. Events can be - -- normal autocommands events (`:help autocmd-events`). - -- - -- Then, because we use the `opts` key (recommended), the configuration runs - -- after the plugin has been loaded as `require(MODULE).setup(opts)`. - - { -- Useful plugin to show you pending keybinds. + { 'folke/which-key.nvim', event = 'VimEnter', ---@module 'which-key' ---@type wk.Opts ---@diagnostic disable-next-line: missing-fields opts = { - -- delay between pressing a key and opening which-key (milliseconds) delay = 0, icons = { mappings = vim.g.have_nerd_font }, - -- Document existing key chains spec = { { 'a', group = 'Harpoon [A]dd' }, { 'c', group = '[C]Make' }, @@ -435,92 +311,36 @@ require('lazy').setup({ { 'w', group = '[W]indow' }, { 'x', group = 'Trouble' }, { 'j', group = '[J]ump' }, - { 'h', group = 'Git [H]unk', mode = { 'n', 'v' } }, -- Enable gitsigns recommended keymaps first + { 'h', group = 'Git [H]unk', mode = { 'n', 'v' } }, { 'gr', group = 'LSP Actions', mode = { 'n' } }, }, }, }, - -- NOTE: Plugins can specify dependencies. - -- - -- The dependencies are proper plugin specifications as well - anything - -- you do for a plugin at the top level, you can do for a dependency. - -- - -- Use the `dependencies` key to specify the dependencies of a particular plugin - { -- Fuzzy Finder (files, lsp, etc) 'nvim-telescope/telescope.nvim', - -- By default, Telescope is included and acts as your picker for everything. - - -- If you would like to switch to a different picker (like snacks, or fzf-lua) - -- you can disable the Telescope plugin by setting enabled to false and enable - -- your replacement picker by requiring it explicitly (e.g. 'custom.plugins.snacks') - - -- Note: If you customize your config for yourself, - -- it’s best to remove the Telescope plugin config entirely - -- instead of just disabling it here, to keep your config clean. enabled = true, event = 'VimEnter', dependencies = { 'nvim-lua/plenary.nvim', - { -- If encountering errors, see telescope-fzf-native README for installation instructions + { 'nvim-telescope/telescope-fzf-native.nvim', - - -- `build` is used to run some command when the plugin is installed/updated. - -- This is only run then, not every time Neovim starts up. build = 'make', - - -- `cond` is a condition used to determine whether this plugin should be - -- installed and loaded. cond = function() return vim.fn.executable 'make' == 1 end, }, { 'nvim-telescope/telescope-ui-select.nvim' }, - - -- Useful for getting pretty icons, but requires a Nerd Font. { 'nvim-tree/nvim-web-devicons', enabled = vim.g.have_nerd_font }, }, config = function() - -- Telescope is a fuzzy finder that comes with a lot of different things that - -- it can fuzzy find! It's more than just a "file finder", it can search - -- many different aspects of Neovim, your workspace, LSP, and more! - -- - -- The easiest way to use Telescope, is to start by doing something like: - -- :Telescope help_tags - -- - -- After running this command, a window will open up and you're able to - -- type in the prompt window. You'll see a list of `help_tags` options and - -- a corresponding preview of the help. - -- - -- Two important keymaps to use while in Telescope are: - -- - Insert mode: - -- - Normal mode: ? - -- - -- This opens a window that shows you all of the keymaps for the current - -- Telescope picker. This is really useful to discover what Telescope can - -- do as well as how to actually do it! - - -- [[ Configure Telescope ]] - -- See `:help telescope` and `:help telescope.setup()` require('telescope').setup { - -- You can put your default mappings / updates / etc. in here - -- All the info you're looking for is in `:help telescope.setup()` - -- - -- defaults = { - -- mappings = { - -- i = { [''] = 'to_fuzzy_refine' }, - -- }, - -- }, - -- pickers = {} extensions = { ['ui-select'] = { require('telescope.themes').get_dropdown() }, }, } - -- Enable Telescope extensions if they are installed pcall(require('telescope').load_extension, 'fzf') pcall(require('telescope').load_extension, 'ui-select') - -- See `:help telescope.builtin` local builtin = require 'telescope.builtin' vim.keymap.set('n', 'sh', builtin.help_tags, { desc = '[S]earch [H]elp' }) vim.keymap.set('n', 'sk', builtin.keymaps, { desc = '[S]earch [K]eymaps' }) @@ -534,51 +354,18 @@ require('lazy').setup({ vim.keymap.set('n', 'sc', builtin.commands, { desc = '[S]earch [C]ommands' }) vim.keymap.set('n', '', builtin.buffers, { desc = '[ ] Find existing buffers' }) - -- This runs on LSP attach per buffer (see main LSP attach function in 'neovim/nvim-lspconfig' config for more info, - -- it is better explained there). This allows easily switching between pickers if you prefer using something else! - vim.api.nvim_create_autocmd('LspAttach', { - group = vim.api.nvim_create_augroup('telescope-lsp-attach', { clear = true }), - callback = function(event) - local buf = event.buf - - -- Find references for the word under your cursor. - vim.keymap.set('n', 'grr', builtin.lsp_references, { buffer = buf, desc = '[G]oto [R]eferences' }) - - -- Jump to the implementation of the word under your cursor. - -- Useful when your language has ways of declaring types without an actual implementation. - vim.keymap.set('n', 'gri', builtin.lsp_implementations, { buffer = buf, desc = '[G]oto [I]mplementation' }) - - -- Jump to the definition of the word under your cursor. - -- This is where a variable was first declared, or where a function is defined, etc. - -- To jump back, press . - vim.keymap.set('n', 'grd', builtin.lsp_definitions, { buffer = buf, desc = '[G]oto [D]efinition' }) - - -- Fuzzy find all the symbols in your current document. - -- Symbols are things like variables, functions, types, etc. - vim.keymap.set('n', 'gO', builtin.lsp_document_symbols, { buffer = buf, desc = 'Open Document Symbols' }) - - -- Fuzzy find all the symbols in your current workspace. - -- Similar to document symbols, except searches over your entire project. - vim.keymap.set('n', 'gW', builtin.lsp_dynamic_workspace_symbols, { buffer = buf, desc = 'Open Workspace Symbols' }) - - -- Jump to the type of the word under your cursor. - -- Useful when you're not sure what type a variable is and you want to see - -- the definition of its *type*, not where it was *defined*. - vim.keymap.set('n', 'grt', builtin.lsp_type_definitions, { buffer = buf, desc = '[G]oto [T]ype Definition' }) + vim.keymap.set( + 'n', + '/', + function() + builtin.current_buffer_fuzzy_find(require('telescope.themes').get_dropdown { + winblend = 10, + previewer = false, + }) end, - }) + { desc = '[/] Fuzzily search in current buffer' } + ) - -- Override default behavior and theme when searching - vim.keymap.set('n', '/', function() - -- You can pass additional configuration to Telescope to change the theme, layout, etc. - builtin.current_buffer_fuzzy_find(require('telescope.themes').get_dropdown { - winblend = 10, - previewer = false, - }) - end, { desc = '[/] Fuzzily search in current buffer' }) - - -- It's also possible to pass additional configuration options. - -- See `:help telescope.builtin.live_grep()` for information about particular keys vim.keymap.set( 'n', 's/', @@ -591,19 +378,13 @@ require('lazy').setup({ { desc = '[S]earch [/] in Open Files' } ) - -- Shortcut for searching your Neovim configuration files vim.keymap.set('n', 'sn', function() builtin.find_files { cwd = vim.fn.stdpath 'config' } end, { desc = '[S]earch [N]eovim files' }) end, }, - -- LSP Plugins { - -- Main LSP Configuration 'neovim/nvim-lspconfig', dependencies = { - -- Automatically install LSPs and related tools to stdpath for Neovim - -- Mason must be loaded before its dependents so we need to set it up here. - -- NOTE: `opts = {}` is the same as calling `require('mason').setup({})` { 'mason-org/mason.nvim', ---@module 'mason.settings' @@ -611,117 +392,37 @@ require('lazy').setup({ ---@diagnostic disable-next-line: missing-fields opts = {}, }, - -- Maps LSP server names between nvim-lspconfig and Mason package names. 'mason-org/mason-lspconfig.nvim', 'WhoIsSethDaniel/mason-tool-installer.nvim', - - -- Useful status updates for LSP. { 'j-hui/fidget.nvim', opts = {} }, }, config = function() - -- Brief aside: **What is LSP?** - -- - -- LSP is an initialism you've probably heard, but might not understand what it is. - -- - -- LSP stands for Language Server Protocol. It's a protocol that helps editors - -- and language tooling communicate in a standardized fashion. - -- - -- In general, you have a "server" which is some tool built to understand a particular - -- language (such as `gopls`, `lua_ls`, `rust_analyzer`, etc.). These Language Servers - -- (sometimes called LSP servers, but that's kind of like ATM Machine) are standalone - -- processes that communicate with some "client" - in this case, Neovim! - -- - -- LSP provides Neovim with features like: - -- - Go to definition - -- - Find references - -- - Autocompletion - -- - Symbol Search - -- - and more! - -- - -- Thus, Language Servers are external tools that must be installed separately from - -- Neovim. This is where `mason` and related plugins come into play. - -- - -- If you're wondering about lsp vs treesitter, you can check out the wonderfully - -- and elegantly composed help section, `:help lsp-vs-treesitter` - - -- This function gets run when an LSP attaches to a particular buffer. - -- That is to say, every time a new file is opened that is associated with - -- an lsp (for example, opening `main.rs` is associated with `rust_analyzer`) this - -- function will be executed to configure the current buffer vim.api.nvim_create_autocmd('LspAttach', { group = vim.api.nvim_create_augroup('kickstart-lsp-attach', { clear = true }), callback = function(event) - -- NOTE: Remember that Lua is a real programming language, and as such it is possible - -- to define small helper and utility functions so you don't have to repeat yourself. - -- - -- In this case, we create a function that lets us more easily define mappings specific - -- for LSP related items. It sets the mode, buffer and description for us each time. local map = function(keys, func, desc, mode) mode = mode or 'n' vim.keymap.set(mode, keys, func, { buffer = event.buf, desc = 'LSP: ' .. desc }) end - -- Rename the variable under your cursor. - -- Most Language Servers support renaming across files, etc. - map('grn', vim.lsp.buf.rename, '[R]e[n]ame') + local builtin = require 'telescope.builtin' + local client = vim.lsp.get_client_by_id(event.data.client_id) - -- Execute a code action, usually your cursor needs to be on top of an error - -- or a suggestion from your LSP for this to activate. + map('grn', vim.lsp.buf.rename, '[R]e[n]ame') map('gra', vim.lsp.buf.code_action, '[G]oto Code [A]ction', { 'n', 'x' }) - - -- Find references for the word under your cursor. - map('grr', require('telescope.builtin').lsp_references, '[G]oto [R]eferences') - map('gr', require('telescope.builtin').lsp_references, '[G]oto [R]eferences') - - -- Jump to the implementation of the word under your cursor. - -- Useful when your language has ways of declaring types without an actual implementation. - map('gri', require('telescope.builtin').lsp_implementations, '[G]oto [I]mplementation') - map('gi', require('telescope.builtin').lsp_implementations, '[G]oto [I]mplementation') - - -- Jump to the definition of the word under your cursor. - -- This is where a variable was first declared, or where a function is defined, etc. - -- To jump back, press . - map('grd', require('telescope.builtin').lsp_definitions, '[G]oto [D]efinition') - map('gd', require('telescope.builtin').lsp_definitions, '[G]oto [D]efinition') - - -- WARN: This is not Goto Definition, this is Goto Declaration. - -- For example, in C this would take you to the header. + map('grr', builtin.lsp_references, '[G]oto [R]eferences') + map('gr', builtin.lsp_references, '[G]oto [R]eferences') + map('gri', builtin.lsp_implementations, '[G]oto [I]mplementation') + map('gi', builtin.lsp_implementations, '[G]oto [I]mplementation') + map('grd', builtin.lsp_definitions, '[G]oto [D]efinition') + map('gd', builtin.lsp_definitions, '[G]oto [D]efinition') map('grD', vim.lsp.buf.declaration, '[G]oto [D]eclaration') map('gD', vim.lsp.buf.declaration, '[G]oto [D]eclaration') + map('gO', builtin.lsp_document_symbols, 'Open Document Symbols') + map('gW', builtin.lsp_dynamic_workspace_symbols, 'Open Workspace Symbols') + map('grt', builtin.lsp_type_definitions, '[G]oto [T]ype Definition') + map('gt', builtin.lsp_type_definitions, '[G]oto [T]ype Definition') - -- Fuzzy find all the symbols in your current document. - -- Symbols are things like variables, functions, types, etc. - map('gO', require('telescope.builtin').lsp_document_symbols, 'Open Document Symbols') - - -- Fuzzy find all the symbols in your current workspace. - -- Similar to document symbols, except searches over your entire project. - map('gW', require('telescope.builtin').lsp_dynamic_workspace_symbols, 'Open Workspace Symbols') - - -- Jump to the type of the word under your cursor. - -- Useful when you're not sure what type a variable is and you want to see - -- the definition of its *type*, not where it was *defined*. - map('grt', require('telescope.builtin').lsp_type_definitions, '[G]oto [T]ype Definition') - map('gt', require('telescope.builtin').lsp_type_definitions, '[G]oto [T]ype Definition') - - -- This function resolves a difference between neovim nightly (version 0.11) and stable (version 0.10) - ---@param client vim.lsp.Client - ---@param method vim.lsp.protocol.Method - ---@param bufnr? integer some lsp support methods only in specific files - ---@return boolean - local function client_supports_method(client, method, bufnr) - if vim.fn.has 'nvim-0.11' == 1 then - return client:supports_method(method, bufnr) - else - return client.supports_method(method, { bufnr = bufnr }) - end - end - - -- The following two autocommands are used to highlight references of the - -- word under your cursor when your cursor rests there for a little while. - -- See `:help CursorHold` for information about when this is executed - -- - -- When you move your cursor, the highlights will be cleared (the second autocommand). - local client = vim.lsp.get_client_by_id(event.data.client_id) if client and client:supports_method('textDocument/documentHighlight', event.buf) then local highlight_augroup = vim.api.nvim_create_augroup('kickstart-lsp-highlight', { clear = false }) vim.api.nvim_create_autocmd({ 'CursorHold', 'CursorHoldI' }, { @@ -745,55 +446,18 @@ require('lazy').setup({ }) end - -- The following code creates a keymap to toggle inlay hints in your - -- code, if the language server you are using supports them - -- - -- This may be unwanted, since they displace some of your code if client and client:supports_method('textDocument/inlayHint', event.buf) then map('th', function() vim.lsp.inlay_hint.enable(not vim.lsp.inlay_hint.is_enabled { bufnr = event.buf }) end, '[T]oggle Inlay [H]ints') end + + if client and client:supports_method('textDocument/codeLens', event.buf) then vim.lsp.codelens.enable(true, { bufnr = event.buf }) end + + if client and client:supports_method('textDocument/linkedEditingRange', event.buf) then + vim.lsp.linked_editing_range.enable(true, { client_id = client.id }) + end end, }) - -- Diagnostic Config - -- See :help vim.diagnostic.Opts - vim.diagnostic.config { - severity_sort = true, - float = { border = 'rounded', source = 'if_many' }, - underline = { severity = vim.diagnostic.severity.ERROR }, - signs = vim.g.have_nerd_font and { - text = { - [vim.diagnostic.severity.ERROR] = '󰅚 ', - [vim.diagnostic.severity.WARN] = '󰀪 ', - [vim.diagnostic.severity.INFO] = '󰋽 ', - [vim.diagnostic.severity.HINT] = '󰌶 ', - }, - } or {}, - virtual_text = { - source = 'if_many', - spacing = 2, - severity = { min = vim.diagnostic.severity.WARN }, - format = function(diagnostic) - if diagnostic.severity == vim.diagnostic.severity.WARN then - return 'W: ' .. diagnostic.message - end - if diagnostic.severity == vim.diagnostic.severity.ERROR then - return 'E: ' .. diagnostic.message - end - return nil - end, - }, - } - - -- Enable the following language servers - -- Feel free to add/remove any LSPs that you want here. They will automatically be installed. - -- - -- Add any additional override configuration in the following tables. Available keys are: - -- - cmd (table): Override the default command used to start the server - -- - filetypes (table): Override the default list of associated filetypes for the server - -- - capabilities (table): Override fields in capabilities. Can be used to disable certain LSP features. - -- - settings (table): Override the default settings passed when initializing the server. - -- For example, to see the options for `lua_ls`, you could go to: https://luals.github.io/wiki/settings/ local vue_language_server_path = vim.fn.stdpath 'data' .. '/mason/packages/vue-language-server/node_modules/@vue/language-server' local vue_typescript_plugin = { name = '@vue/typescript-plugin', @@ -844,20 +508,14 @@ require('lazy').setup({ elixirls = {}, gh_actions_ls = {}, lua_ls = { - -- cmd = { ... }, - -- filetypes = { ... }, - -- capabilities = {}, settings = { Lua = { completion = { callSnippet = 'Replace', }, - -- You can toggle below to ignore Lua_LS's noisy `missing-fields` warnings - -- diagnostics = { disable = { 'missing-fields' } }, }, }, }, - -- Tools eslint = {}, astro = {}, vue_ls = {}, @@ -875,14 +533,6 @@ require('lazy').setup({ postgres_lsp = {}, neocmake = {}, buf_ls = {}, - - -- ... etc. See `:help lspconfig-all` for a list of all the pre-configured LSPs - -- - -- Some languages (like typescript) have entire language plugins that can be useful: - -- https://github.com/pmizio/typescript-tools.nvim - -- - -- But for many setups, the LSP (`ts_ls`) will work just fine - -- } if has_go then @@ -900,9 +550,7 @@ require('lazy').setup({ } end - if vim.fn.executable 'jq-lsp' == 1 then - servers.jqls = {} - end + if vim.fn.executable 'jq-lsp' == 1 then servers.jqls = {} end servers.marksman.filetypes = { 'markdown', 'mdx' } servers.tailwindcss.filetypes = { @@ -928,19 +576,10 @@ require('lazy').setup({ automatic_enable = vim.tbl_keys(servers or {}), } - -- Ensure the servers and tools above are installed - -- - -- To check the current status of installed tools and/or manually install - -- other tools, you can run - -- :Mason - -- - -- You can press `g?` for help in this menu. - local ensure_installed = vim.tbl_filter(function(server_name) - return server_name ~= 'gopls' and server_name ~= 'jqls' - end, vim.tbl_keys(servers or {})) + local ensure_installed = vim.tbl_filter(function(server_name) return server_name ~= 'gopls' and server_name ~= 'jqls' end, vim.tbl_keys(servers or {})) vim.list_extend(ensure_installed, { - 'stylua', -- Used to format Lua code - 'markdownlint', -- Used by nvim-lint for Markdown buffers + 'stylua', + 'markdownlint', 'prettierd', 'prettier', 'clang-format', @@ -952,14 +591,9 @@ require('lazy').setup({ require('mason-tool-installer').setup { ensure_installed = ensure_installed } - -- Installed LSPs are configured and enabled automatically with mason-lspconfig - -- The loop below is for overriding the default configuration of LSPs with the ones in the servers table for server_name, config in pairs(servers) do vim.lsp.config(server_name, config) end - - -- NOTE: Some servers may require an old setup until they are updated. For the full list refer here: https://github.com/neovim/nvim-lspconfig/issues/3705 - -- These servers will have to be manually set up with require("lspconfig").server_name.setup{} end, }, @@ -980,9 +614,6 @@ require('lazy').setup({ opts = { notify_on_error = false, format_on_save = function(bufnr) - -- Disable "format_on_save lsp_fallback" for languages that don't - -- have a well standardized coding style. You can add additional - -- languages here or re-enable it for the disabled ones. local disable_filetypes = { c = true, cpp = true } if disable_filetypes[vim.bo[bufnr].filetype] then return nil @@ -1004,11 +635,6 @@ require('lazy').setup({ yaml = { 'prettierd', 'prettier', stop_after_first = true }, c = { 'clang_format' }, cpp = { 'clang_format' }, - -- Conform can also run multiple formatters sequentially - -- python = { "isort", "black" }, - -- - -- You can use 'stop_after_first' to run the first available formatter from the list - -- javascript = { "prettierd", "prettier", stop_after_first = true }, }, }, }, @@ -1018,28 +644,13 @@ require('lazy').setup({ event = 'VimEnter', version = '1.*', dependencies = { - -- Snippet Engine { 'L3MON4D3/LuaSnip', version = '2.*', build = (function() - -- Build Step is needed for regex support in snippets. - -- This step is not supported in many windows environments. - -- Remove the below condition to re-enable on windows. if vim.fn.has 'win32' == 1 or vim.fn.executable 'make' == 0 then return end return 'make install_jsregexp' end)(), - dependencies = { - -- `friendly-snippets` contains a variety of premade snippets. - -- See the README about individual language/framework/plugin snippets: - -- https://github.com/rafamadriz/friendly-snippets - -- { - -- 'rafamadriz/friendly-snippets', - -- config = function() - -- require('luasnip.loaders.from_vscode').lazy_load() - -- end, - -- }, - }, opts = {}, }, }, @@ -1047,42 +658,14 @@ require('lazy').setup({ ---@type blink.cmp.Config opts = { keymap = { - -- 'default' (recommended) for mappings similar to built-in completions - -- to accept ([y]es) the completion. - -- This will auto-import if your LSP supports it. - -- This will expand snippets if the LSP sent a snippet. - -- 'super-tab' for tab to accept - -- 'enter' for enter to accept - -- 'none' for no mappings - -- - -- For an understanding of why the 'default' preset is recommended, - -- you will need to read `:help ins-completion` - -- - -- No, but seriously. Please read `:help ins-completion`, it is really good! - -- - -- All presets have the following mappings: - -- /: move to right/left of your snippet expansion - -- : Open menu or open docs if already open - -- / or /: Select next/previous item - -- : Hide menu - -- : Toggle signature help - -- - -- See :h blink-cmp-config-keymap for defining your own keymap preset = 'default', - - -- For more advanced Luasnip keymaps (e.g. selecting choice nodes, expansion) see: - -- https://github.com/L3MON4D3/LuaSnip?tab=readme-ov-file#keymaps }, appearance = { - -- 'mono' (default) for 'Nerd Font Mono' or 'normal' for 'Nerd Font' - -- Adjusts spacing to ensure icons are aligned nerd_font_variant = 'mono', }, completion = { - -- By default, you may press `` to show the documentation. - -- Optionally, set `auto_show = true` to show the documentation after a delay. documentation = { auto_show = false, auto_show_delay_ms = 500 }, }, @@ -1092,27 +675,15 @@ require('lazy').setup({ snippets = { preset = 'luasnip' }, - -- Blink.cmp includes an optional, recommended rust fuzzy matcher, - -- which automatically downloads a prebuilt binary when enabled. - -- - -- By default, we use the Lua implementation instead, but you may enable - -- the rust implementation via `'prefer_rust_with_warning'` - -- - -- See :h blink-cmp-config-fuzzy for more information fuzzy = { implementation = 'lua' }, - -- Shows a signature help window while you type arguments for a function signature = { enabled = true }, }, }, - { -- You can easily change to a different colorscheme. - -- Change the name of the colorscheme plugin below, and then - -- change the command in the config to whatever the name of that colorscheme is. - -- - -- If you want to see what colorschemes are already installed, you can use `:Telescope colorscheme`. + { 'folke/tokyonight.nvim', - priority = 1000, -- Make sure to load this before all the other start plugins. + priority = 1000, config = function() ---@diagnostic disable-next-line: missing-fields require('tokyonight').setup { @@ -1121,9 +692,6 @@ require('lazy').setup({ }, } - -- Load the colorscheme here. - -- Like many other themes, this one has different styles, and you could load - -- any other, such as 'tokyonight-storm', 'tokyonight-moon', or 'tokyonight-day'. vim.cmd.colorscheme 'tokyonight-night' end, }, @@ -1142,43 +710,18 @@ require('lazy').setup({ { -- Collection of various small independent plugins/modules 'nvim-mini/mini.nvim', config = function() - -- Better Around/Inside textobjects - -- - -- Examples: - -- - va) - [V]isually select [A]round [)]paren - -- - yinq - [Y]ank [I]nside [N]ext [Q]uote - -- - ci' - [C]hange [I]nside [']quote require('mini.ai').setup { n_lines = 500 } - - -- Add/delete/replace surroundings (brackets, quotes, etc.) - -- - -- - saiw) - [S]urround [A]dd [I]nner [W]ord [)]Paren - -- - sd' - [S]urround [D]elete [']quotes - -- - sr)' - [S]urround [R]eplace [)] ['] require('mini.surround').setup() - -- Delete buffers while preserving window layout. local bufremove = require 'mini.bufremove' bufremove.setup() - vim.keymap.set('n', 'bd', function() - bufremove.delete(0, false) - end, { desc = '[B]uffer [D]elete' }) + vim.keymap.set('n', 'bd', function() bufremove.delete(0, false) end, { desc = '[B]uffer [D]elete' }) - -- Simple and easy statusline. - -- You could remove this setup call if you don't like it, - -- and try some other statusline plugin local statusline = require 'mini.statusline' - -- set use_icons to true if you have a Nerd Font statusline.setup { use_icons = vim.g.have_nerd_font } - -- You can configure sections in the statusline by overriding their - -- default behavior. For example, here we set the section for - -- cursor location to LINE:COLUMN ---@diagnostic disable-next-line: duplicate-set-field statusline.section_location = function() return '%2l:%-2v' end - - -- ... and there is more! - -- Check out: https://github.com/nvim-mini/mini.nvim end, }, @@ -1193,11 +736,8 @@ require('lazy').setup({ end local ok, treesitter = pcall(require, 'nvim-treesitter') - if ok then - treesitter.install(treesitter_parsers, { summary = true }):wait(300000) - end + if ok then treesitter.install(treesitter_parsers, { summary = true }):wait(300000) end end, - -- [[ Configure Treesitter ]] See `:help nvim-treesitter` config = function() local treesitter = require 'nvim-treesitter' treesitter.setup() @@ -1213,33 +753,12 @@ require('lazy').setup({ vim.api.nvim_create_autocmd('FileType', { group = vim.api.nvim_create_augroup('kickstart-treesitter', { clear = true }), - callback = function() - pcall(vim.treesitter.start) - end, + callback = function() pcall(vim.treesitter.start) end, }) end, - -- There are additional nvim-treesitter modules that you can use to interact - -- with nvim-treesitter. You should go explore a few and see what interests you: - -- - -- - Incremental selection: Included, see `:help nvim-treesitter-incremental-selection-mod` - -- - Show your current context: https://github.com/nvim-treesitter/nvim-treesitter-context - -- - Treesitter + textobjects: https://github.com/nvim-treesitter/nvim-treesitter-textobjects }, - -- The following comments only work if you have downloaded the kickstart repo, not just copy pasted the - -- init.lua. If you want these files, they are in the repository, so you can just download them and - -- place them in the correct locations. - - -- NOTE: The import below can automatically add your own plugins, configuration, etc from `lua/custom/plugins/*.lua` - -- This is the easiest way to modularize your config. - -- - -- Uncomment the following line and add your plugins to `lua/custom/plugins/*.lua` to get going. { import = 'custom.plugins' }, - -- - -- For additional information with loading, sourcing and examples see `:help lazy.nvim-🔌-plugin-spec` - -- Or use telescope! - -- In normal mode type `sh` then write `lazy.nvim-plugin` - -- you can continue same window with `sr` which resumes last telescope search }, { ---@diagnostic disable-line: missing-fields rocks = { enabled = false }, ui = { diff --git a/lua/custom/plugins/bufferline.lua b/lua/custom/plugins/bufferline.lua index cdb68e06886..ded3d53cbdb 100644 --- a/lua/custom/plugins/bufferline.lua +++ b/lua/custom/plugins/bufferline.lua @@ -1,7 +1,9 @@ return { 'akinsho/bufferline.nvim', event = 'VeryLazy', - dependencies = { 'nvim-tree/nvim-web-devicons' }, + dependencies = { + { 'nvim-tree/nvim-web-devicons', enabled = vim.g.have_nerd_font }, + }, keys = { { '', 'BufferLineCyclePrev', desc = 'Previous buffer' }, { '', 'BufferLineCycleNext', desc = 'Next buffer' }, @@ -12,9 +14,7 @@ return { local bufremove = require 'mini.bufremove' for _, bufnr in ipairs(vim.api.nvim_list_bufs()) do - if vim.api.nvim_buf_is_valid(bufnr) and vim.bo[bufnr].buflisted and vim.bo[bufnr].filetype ~= 'neo-tree' then - bufremove.delete(bufnr, false) - end + if vim.api.nvim_buf_is_valid(bufnr) and vim.bo[bufnr].buflisted and vim.bo[bufnr].filetype ~= 'neo-tree' then bufremove.delete(bufnr, false) end end end, desc = '[B]uffer [D]elete all', diff --git a/lua/custom/plugins/debug.lua b/lua/custom/plugins/debug.lua index 84feb62f2d3..e762660b7a6 100644 --- a/lua/custom/plugins/debug.lua +++ b/lua/custom/plugins/debug.lua @@ -1,16 +1,6 @@ --- debug.lua --- --- Shows how to use the DAP plugin to debug your code. --- --- Primarily focused on configuring the debugger for Go, but can --- be extended to other languages as well. That's why it's called --- kickstart.nvim and not kitchen-sink.nvim ;) - local function has_configuration(configurations, name, adapter) for _, configuration in ipairs(configurations or {}) do - if configuration.name == name and configuration.type == adapter then - return true - end + if configuration.name == name and configuration.type == adapter then return true end end return false @@ -27,9 +17,7 @@ local function setup_cpp_dap(dap) name = launch_name, type = 'codelldb', request = 'launch', - program = function() - return vim.fn.input('Path to executable: ', vim.fn.getcwd() .. '/', 'file') - end, + program = function() return vim.fn.input('Path to executable: ', vim.fn.getcwd() .. '/', 'file') end, cwd = '${workspaceFolder}', stopOnEntry = false, args = {}, @@ -47,25 +35,15 @@ end ---@module 'lazy' ---@type LazySpec return { - -- NOTE: Yes, you can install new plugins here! 'mfussenegger/nvim-dap', - -- NOTE: And you can specify dependencies as well dependencies = { - -- Creates a beautiful debugger UI 'rcarriga/nvim-dap-ui', - - -- Required dependency for nvim-dap-ui 'nvim-neotest/nvim-nio', - - -- Installs the debug adapters for you 'mason-org/mason.nvim', 'jay-babu/mason-nvim-dap.nvim', - - -- Add your own debuggers here 'leoluz/nvim-dap-go', }, keys = { - -- Basic debugging keymaps, feel free to change to your liking! { '', function() require('dap').continue() end, desc = 'Debug: Start/Continue' }, { '', function() require('dap').step_into() end, desc = 'Debug: Step Into' }, { '', function() require('dap').step_over() end, desc = 'Debug: Step Over' }, @@ -81,26 +59,13 @@ return { local has_delve = vim.fn.executable 'dlv' == 1 require('mason-nvim-dap').setup { - -- Makes a best effort to setup the various debuggers with - -- reasonable debug configurations automatic_installation = true, - - -- You can provide additional configuration to the handlers, - -- see mason-nvim-dap README for more information handlers = {}, - - -- You'll need to check that you have the required things installed - -- online, please don't ask me how to install them :) ensure_installed = { 'codelldb' }, } - -- Dap UI setup - -- For more information, see |:help nvim-dap-ui| ---@diagnostic disable-next-line: missing-fields dapui.setup { - -- Set icons to characters that are more likely to work in every terminal. - -- Feel free to remove or use ones that you like more! :) - -- Don't feel like these are good choices. icons = { expanded = '▾', collapsed = '▸', current_frame = '*' }, ---@diagnostic disable-next-line: missing-fields controls = { @@ -118,31 +83,15 @@ return { }, } - -- Change breakpoint icons - -- vim.api.nvim_set_hl(0, 'DapBreak', { fg = '#e51400' }) - -- vim.api.nvim_set_hl(0, 'DapStop', { fg = '#ffcc00' }) - -- local breakpoint_icons = vim.g.have_nerd_font - -- and { Breakpoint = '', BreakpointCondition = '', BreakpointRejected = '', LogPoint = '', Stopped = '' } - -- or { Breakpoint = '●', BreakpointCondition = '⊜', BreakpointRejected = '⊘', LogPoint = '◆', Stopped = '⭔' } - -- for type, icon in pairs(breakpoint_icons) do - -- local tp = 'Dap' .. type - -- local hl = (type == 'Stopped') and 'DapStop' or 'DapBreak' - -- vim.fn.sign_define(tp, { text = icon, texthl = hl, numhl = hl }) - -- end - dap.listeners.after.event_initialized['dapui_config'] = dapui.open dap.listeners.before.event_terminated['dapui_config'] = dapui.close dap.listeners.before.event_exited['dapui_config'] = dapui.close - if has_delve then - require('dap-go').setup { - delve = { - -- On Windows delve must be run attached or it crashes. - -- See https://github.com/leoluz/nvim-dap-go/blob/main/README.md#configuring - detached = vim.fn.has 'win32' == 0, - }, - } - end + if has_delve then require('dap-go').setup { + delve = { + detached = vim.fn.has 'win32' == 0, + }, + } end setup_cpp_dap(dap) end, diff --git a/lua/custom/plugins/gitsigns.lua b/lua/custom/plugins/gitsigns.lua index bf037d7207d..99d64a3437a 100644 --- a/lua/custom/plugins/gitsigns.lua +++ b/lua/custom/plugins/gitsigns.lua @@ -1,6 +1,4 @@ -- Adds git related signs to the gutter, as well as utilities for managing changes --- NOTE: gitsigns is already included in init.lua but contains only the base --- config. This will add also the recommended keymaps. ---@module 'lazy' ---@type LazySpec diff --git a/lua/custom/plugins/init.lua b/lua/custom/plugins/init.lua index b3ddcfdd3aa..a2ded374463 100644 --- a/lua/custom/plugins/init.lua +++ b/lua/custom/plugins/init.lua @@ -1,8 +1,3 @@ --- You can add your own plugins here or in other files in this directory! --- I promise not to create any merge conflicts in this directory :) --- --- See the kickstart.nvim README for more information - ---@module 'lazy' ---@type LazySpec return {} diff --git a/lua/custom/plugins/lint.lua b/lua/custom/plugins/lint.lua index 08494844582..3a355d854a7 100644 --- a/lua/custom/plugins/lint.lua +++ b/lua/custom/plugins/lint.lua @@ -21,44 +21,10 @@ return { }, }) - -- To allow other plugins to add linters to require('lint').linters_by_ft, - -- instead set linters_by_ft like this: - -- lint.linters_by_ft = lint.linters_by_ft or {} - -- lint.linters_by_ft['markdown'] = { 'markdownlint' } - -- - -- However, note that this will enable a set of default linters, - -- which will cause errors unless these tools are available: - -- { - -- clojure = { "clj-kondo" }, - -- dockerfile = { "hadolint" }, - -- inko = { "inko" }, - -- janet = { "janet" }, - -- json = { "jsonlint" }, - -- markdown = { "vale" }, - -- rst = { "vale" }, - -- ruby = { "ruby" }, - -- terraform = { "tflint" }, - -- text = { "vale" } - -- } - -- - -- You can disable the default linters by setting their filetypes to nil: - -- lint.linters_by_ft['clojure'] = nil - -- lint.linters_by_ft['dockerfile'] = nil - -- lint.linters_by_ft['inko'] = nil - -- lint.linters_by_ft['janet'] = nil - -- lint.linters_by_ft['json'] = nil - -- lint.linters_by_ft['markdown'] = nil - -- lint.linters_by_ft['rst'] = nil - -- lint.linters_by_ft['ruby'] = nil - -- lint.linters_by_ft['terraform'] = nil - -- lint.linters_by_ft['text'] = nil - local function executable_cmd(cmd) if type(cmd) == 'function' then local ok, value = pcall(cmd) - if not ok then - return nil - end + if not ok then return nil end return value end return cmd @@ -70,9 +36,7 @@ return { for _, linter_name in ipairs(configured) do local linter = lint.linters[linter_name] local cmd = linter and executable_cmd(linter.cmd) - if cmd == nil or cmd == '' or vim.fn.executable(cmd) == 1 then - table.insert(available, linter_name) - end + if cmd == nil or cmd == '' or vim.fn.executable(cmd) == 1 then table.insert(available, linter_name) end end return available end @@ -80,9 +44,7 @@ return { local function trigger_lint() if vim.bo.modifiable then local linters = available_linters_for(vim.bo.filetype) - if #linters > 0 then - lint.try_lint(linters) - end + if #linters > 0 then lint.try_lint(linters) end end end @@ -116,9 +78,7 @@ return { -- avoid superfluous noise, notably within the handy LSP pop-ups that -- describe the hovered symbol using Markdown. callback = function() - if lint_enabled then - trigger_lint() - end + if lint_enabled then trigger_lint() end end, }) end, diff --git a/lua/custom/plugins/markdown.lua b/lua/custom/plugins/markdown.lua index 82a8729caca..f5beefa0b94 100644 --- a/lua/custom/plugins/markdown.lua +++ b/lua/custom/plugins/markdown.lua @@ -2,7 +2,10 @@ return { { 'MeanderingProgrammer/render-markdown.nvim', ft = { 'markdown' }, - dependencies = { 'nvim-treesitter/nvim-treesitter', 'nvim-tree/nvim-web-devicons' }, + dependencies = { + 'nvim-treesitter/nvim-treesitter', + { 'nvim-tree/nvim-web-devicons', enabled = vim.g.have_nerd_font }, + }, opts = { file_types = { 'markdown' }, }, @@ -11,24 +14,22 @@ return { 'iamcco/markdown-preview.nvim', ft = { 'markdown' }, cmd = { 'MarkdownPreviewToggle', 'MarkdownPreview', 'MarkdownPreviewStop' }, - build = function() - vim.fn['mkdp#util#install']() - end, + build = function() vim.fn['mkdp#util#install']() end, init = function() local browser = '' - local is_macos = vim.fn.has('macunix') == 1 + local is_macos = vim.fn.has 'macunix' == 1 if is_macos then - vim.cmd([[ + vim.cmd [[ function! OpenMarkdownPreview(url) abort execute 'silent !open ' . shellescape(a:url) endfunction - ]]) - elseif vim.fn.executable('google-chrome-stable') == 1 then + ]] + elseif vim.fn.executable 'google-chrome-stable' == 1 then browser = 'google-chrome-stable' - elseif vim.fn.executable('google-chrome') == 1 then + elseif vim.fn.executable 'google-chrome' == 1 then browser = 'google-chrome' - elseif vim.fn.executable('chromium') == 1 then + elseif vim.fn.executable 'chromium' == 1 then browser = 'chromium' end diff --git a/lua/custom/plugins/neo-tree.lua b/lua/custom/plugins/neo-tree.lua index af8d4495650..0ac053b979e 100644 --- a/lua/custom/plugins/neo-tree.lua +++ b/lua/custom/plugins/neo-tree.lua @@ -8,7 +8,7 @@ return { version = '*', dependencies = { 'nvim-lua/plenary.nvim', - 'nvim-tree/nvim-web-devicons', -- not strictly required, but recommended + { 'nvim-tree/nvim-web-devicons', enabled = vim.g.have_nerd_font }, 'MunifTanjim/nui.nvim', }, lazy = false, diff --git a/lua/custom/plugins/neogit.lua b/lua/custom/plugins/neogit.lua index c91dfedc141..784b71be1e2 100644 --- a/lua/custom/plugins/neogit.lua +++ b/lua/custom/plugins/neogit.lua @@ -2,14 +2,9 @@ return { 'NeogitOrg/neogit', lazy = true, dependencies = { - 'nvim-lua/plenary.nvim', -- required - 'sindrets/diffview.nvim', -- optional - Diff integration - - -- Only one of these is needed. - 'nvim-telescope/telescope.nvim', -- optional - 'ibhagwan/fzf-lua', -- optional - 'nvim-mini/mini.pick', -- optional - 'folke/snacks.nvim', -- optional + 'nvim-lua/plenary.nvim', + 'sindrets/diffview.nvim', + 'nvim-telescope/telescope.nvim', }, cmd = 'Neogit', keys = { From 7b571fee2efea1e6316b5949118a6625c71233bf Mon Sep 17 00:00:00 2001 From: "Paul B. Kim" Date: Sat, 9 May 2026 00:14:57 +0900 Subject: [PATCH 42/42] add neovim field manual --- MANUAL.md | 341 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 2 + init.lua | 9 +- 3 files changed, 351 insertions(+), 1 deletion(-) create mode 100644 MANUAL.md diff --git a/MANUAL.md b/MANUAL.md new file mode 100644 index 00000000000..cc0b3992e72 --- /dev/null +++ b/MANUAL.md @@ -0,0 +1,341 @@ +# Neovim Field Manual + +This is a hands-on guide for this config. It focuses on the shortcuts, motions, +and working habits that are most useful day to day. + +Leader is ``. + +## First Moves + +| Goal | Keys | Notes | +| --- | --- | --- | +| See available leader groups | `` | `which-key` opens immediately. | +| Find files | `sf` | Telescope file picker. | +| Search text in project | `sg` | Uses ripgrep through Telescope. | +| Search current buffer | `/` | Compact in-buffer fuzzy search. | +| Switch buffers | `` | Telescope buffer picker. | +| Previous / next buffer | `` / `` | Bufferline navigation. | +| Reveal file tree | `\` | Neo-tree reveal. | +| Open Oil explorer | `eo` | Editable directory buffer. | +| Format buffer | `f` | Conform, with LSP fallback. | +| Exit terminal mode | `` | Easier than ``. | + +## Motions Worth Practicing + +### Cursor and file movement + +| Motion | Meaning | Practice | +| --- | --- | --- | +| `h j k l` | left, down, up, right | Use these before arrow keys. | +| `w` / `b` | next / previous word start | Move through identifiers quickly. | +| `e` / `ge` | next / previous word end | Useful before `c` and `d`. | +| `0` / `^` / `$` | line start, first nonblank, line end | Combine with `d`, `y`, `c`. | +| `gg` / `G` | file top / bottom | Prefix `G` with a line number. | +| `%` | matching pair | Parentheses, braces, brackets. | +| `{` / `}` | paragraph or block-ish movement | Good in Markdown and code blocks. | +| `` / `` | half-page down / up | Your `scrolloff=10` keeps context visible. | +| `` / `` | jump back / forward | `jumpoptions=clean,view` restores view better. | + +### Search movement + +| Motion | Meaning | +| --- | --- | +| `/text` | search forward | +| `?text` | search backward | +| `n` / `N` | next / previous match | +| `*` / `#` | search word under cursor forward / backward | +| `` | clear search highlight | + +Search is smart-case: lowercase searches are case-insensitive, mixed-case +searches become case-sensitive. + +### One-character targeting + +| Motion | Meaning | Example | +| --- | --- | --- | +| `f` | move to next char | `f)` | +| `F` | move to previous char | `F(` | +| `t` | move before next char | `dt,` deletes until comma. | +| `T` | move after previous char | `cT.` changes back to after dot. | +| `;` / `,` | repeat char search forward / backward | Works after `f`, `F`, `t`, `T`. | + +## Editing Grammar + +Vim editing is usually: + +```text +operator + motion +``` + +| Operator | Meaning | Example | +| --- | --- | --- | +| `d` | delete | `dw`, `d$`, `di"` | +| `c` | change | `ciw`, `cib`, `ct,` | +| `y` | yank | `yiw`, `yap`, `y$` | +| `>` / `<` | indent / unindent | `>ip`, `sh` | +| Keymaps | `sk` | +| Files | `sf` | +| Builtin pickers | `ss` | +| Word under cursor | `sw` | +| Live grep | `sg` | +| Diagnostics | `sd` | +| Resume last picker | `sr` | +| Recent files | `s.` | +| Commands | `sc` | +| Open-file grep | `s/` | +| Neovim config files | `sn` | +| Project search and replace | `sR` | + +Hands-on flow: + +1. Use `sg` to find a symbol or string. +2. Use `` in Telescope when you want a quickfix list. +3. Use `sR` for real project edits with preview. +4. Use `f` after edits to format. + +## LSP and Code Intelligence + +| Goal | Keys | +| --- | --- | +| Rename symbol | `grn` | +| Code action | `gra` | +| References | `grr` or `gr` | +| Implementation | `gri` or `gi` | +| Definition | `grd` or `gd` | +| Declaration | `grD` or `gD` | +| Document symbols | `gO` | +| Workspace symbols | `gW` | +| Type definition | `grt` or `gt` | +| Toggle inlay hints | `th` | + +This config also enables LSP CodeLens when the server supports it and linked +editing for servers that expose `textDocument/linkedEditingRange`. + +## Diagnostics + +| Goal | Keys | +| --- | --- | +| Previous diagnostic | `[d` | +| Next diagnostic | `]d` | +| Diagnostic details | `de` | +| Diagnostics location list | `q` | +| Yank diagnostic context | `dy` | +| Trouble all diagnostics | `xx` | +| Trouble workspace diagnostics | `xw` | +| Trouble current document | `xd` | +| Trouble quickfix | `xq` | +| Trouble loclist | `xl` | + +Diagnostic display is intentionally quieter: + +- Virtual text shows warnings and errors. +- Underlines are limited to errors. +- Diagnostic floats use rounded borders. +- Jumping diagnostics opens a focused-on-cursor float through `jump.on_jump`. + +## Git + +### Repository-level UI + +| Goal | Keys | +| --- | --- | +| Open Neogit | `Gg` | +| Open Diffview | `Gd` | +| Close Diffview | `GD` | +| Current file history | `Gf` | +| Repository history | `GF` | + +### Hunk-level work + +| Goal | Keys | +| --- | --- | +| Next / previous hunk | `]c` / `[c` | +| Stage hunk | `hs` | +| Reset hunk | `hr` | +| Stage buffer | `hS` | +| Undo staged hunk | `hu` | +| Reset buffer | `hR` | +| Preview hunk | `hp` | +| Preview hunk inline | `hi` | +| Blame current line | `hb` | +| Diff against index | `hd` | +| Diff against last commit | `hD` | +| All hunks to quickfix | `hQ` | +| Current buffer hunks to quickfix | `hq` | +| Toggle current-line blame | `tb` | +| Toggle word diff | `tw` | +| Toggle deleted lines | `tD` | + +In visual mode, `hs` and `hr` stage or reset the selected hunk +range. + +## Buffers, Windows, and Sessions + +| Goal | Keys | +| --- | --- | +| Previous / next buffer | `` / `` | +| Pick buffer | `bp` | +| Delete current buffer | `bd` | +| Delete all listed buffers | `bD` | +| Move left / right window | `wh` / `wl` | +| Move lower / upper window | `wj` / `wk` | +| Tmux-aware navigation | `` | +| Restore session | `wr` | +| Restore last session | `wl` | +| Stop session save | `wd` | + +Splits open to the right and below by default. + +## Files and AI-Friendly References + +| Goal | Keys | +| --- | --- | +| Neo-tree reveal | `\` | +| Oil explorer | `eo` | +| Harpoon add file | `a` | +| Harpoon menu | `hm` or `` | +| Harpoon previous / next | `` / `` | +| Yank absolute file location | `ya` | +| Yank relative file location | `yr` | +| Yank current file as `@path` | `yf` | +| Yank current directory as `@dir` | `yd` | + +The reference yanks include file, range, and symbol context when available. +They are useful for prompts, review comments, and issue descriptions. + +## Tests and Debugging + +### Neotest + +| Goal | Keys | +| --- | --- | +| Run nearest test | `nr` | +| Run current file | `nf` | +| Run suite from cwd | `ns` | +| Debug nearest test | `nd` | +| Toggle summary | `nn` | +| Open output | `no` | +| Toggle output panel | `nO` | +| Attach | `na` | +| Stop run | `nS` | + +### DAP + +| Goal | Keys | +| --- | --- | +| Continue / start | `` | +| Step into | `` | +| Step over | `` | +| Step out | `` | +| Toggle breakpoint | `b` | +| Conditional breakpoint | `B` | +| Toggle DAP UI | `` | + +Configured adapters include Go through `dap-go` when `dlv` exists, JS/TS through +`js-debug-adapter`, and C/C++ through `codelldb`. + +## CMake, Markdown, and Code Context + +| Goal | Keys | +| --- | --- | +| CMake generate | `cg` | +| CMake build | `cb` | +| CMake run | `cr` | +| CMake test | `ct` | +| CMake config | `cc` | +| Markdown preview | `mp` | +| Toggle Treesitter context | `tc` | +| Next function start | `jm` | +| Next function end | `jM` | +| Previous function start | `jk` | +| Previous function end | `jK` | +| Next class start | `jc` | +| Previous class start | `jC` | +| Open all folds | `zR` | +| Close all folds | `zM` | + +## Completion and Snippets + +Completion uses `blink.cmp` with LSP, paths, and snippets. + +| Goal | Keys | +| --- | --- | +| Open completion/docs | `` | +| Next / previous item | `` / `` | +| Accept completion | `` | +| Hide menu | `` | +| Toggle signature help | `` | + +Supermaven is active on insert: + +| Goal | Keys | +| --- | --- | +| Accept suggestion | `` | +| Clear suggestion | `` | +| Accept word | `` | + +## Config-Specific Habits + +- Use `` groups by memory shape: `s` for search, `g` for goto, `G` for + git UI, `h` for git hunks, `x` for Trouble, `n` for tests, `w` for windows + and sessions, `y` for reference yanks. +- Keep relative line numbers on. For example, `d5j` deletes five lines down, + and `y3k` yanks three lines up. +- Use `.` after focused edits. Example: `ciwnew_name` then jump and press + `.` to repeat. +- Prefer text objects over visual selection when possible: `ci"`, `di)`, + `yaf`, `dac`. +- Use `` after LSP jumps. Your jump list preserves view, so returning to + the previous context is less disorienting. +- Use `dy`, `yr`, and `yf` when copying context for AI + agents or code reviews. +- Do not rely on modelines for project settings. This config disables modelines + for safer project-local behavior. + +## Five-Minute Drill + +1. Open a project with `nvim .`. +2. Press `sf`, open a file. +3. Use `/`, `n`, `N`, `w`, `b`, `%`, ``, and `` to move without the mouse. +4. Change a word with `ciw`, repeat the edit elsewhere with `.`. +5. Select a function with `vaf`, then try `yaf` and `daf` in a scratch file. +6. Jump to definition with `gd`, return with ``. +7. Open diagnostics with `xx`, then jump with `[d` and `]d`. +8. Preview a git hunk with `hp`, then stage it with `hs`. +9. Run the nearest test with `nr`. +10. Copy an AI-friendly reference with `yr`. diff --git a/README.md b/README.md index 69cfdb925d4..19f022d51d1 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,8 @@ Personal Neovim config based on `kickstart.nvim`, tuned for backend-heavy work in JavaScript/TypeScript, C/C++, Kubernetes, and server development. +For the hands-on shortcut and motion guide, start with [MANUAL.md](MANUAL.md). + This README is the working manual for what is configured, why it exists, and how to use it quickly. diff --git a/init.lua b/init.lua index e85335cabca..d1fb928a97c 100644 --- a/init.lua +++ b/init.lua @@ -8,7 +8,7 @@ vim.g.mapleader = ' ' vim.g.maplocalleader = ' ' -- Set to true if you have a Nerd Font installed and selected in the terminal -vim.g.have_nerd_font = false +vim.g.have_nerd_font = true -- [[ Setting options ]] -- See `:help vim.o` @@ -513,6 +513,13 @@ require('lazy').setup({ completion = { callSnippet = 'Replace', }, + diagnostics = { + globals = { 'vim' }, + }, + workspace = { + checkThirdParty = false, + library = vim.api.nvim_get_runtime_file('', true), + }, }, }, },