From 16250fd7ea5692c3bf26358700cb59f99597da89 Mon Sep 17 00:00:00 2001 From: Jesper Liljegren Date: Fri, 6 Feb 2026 15:05:37 +0100 Subject: [PATCH 1/7] feat(plugins): add mason-lspconfig and catppuccin --- init.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/init.lua b/init.lua index d5ae6dc9b2a..520d99f0965 100644 --- a/init.lua +++ b/init.lua @@ -481,6 +481,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 = {} }, 'WhoIsSethDaniel/mason-tool-installer.nvim', -- Useful status updates for LSP. @@ -809,6 +810,7 @@ require('lazy').setup({ vim.cmd.colorscheme 'tokyonight-night' end, }, + { 'catppuccin/nvim', name = 'catppuccin', priority = 1000 }, -- Highlight todo, notes, etc in comments { 'folke/todo-comments.nvim', event = 'VimEnter', dependencies = { 'nvim-lua/plenary.nvim' }, opts = { signs = false } }, From 2d753707a40c4e32593b06f4473e4b4012e35e5f Mon Sep 17 00:00:00 2001 From: Jesper Liljegren Date: Mon, 16 Feb 2026 13:45:38 +0100 Subject: [PATCH 2/7] feat: enable nerd font support --- init.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/init.lua b/init.lua index 520d99f0965..8da2a65e855 100644 --- a/init.lua +++ b/init.lua @@ -91,7 +91,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` From a336884e856be910ec7a9612eea01ae27b01c36d Mon Sep 17 00:00:00 2001 From: Jesper Liljegren Date: Mon, 23 Mar 2026 10:57:51 +0100 Subject: [PATCH 3/7] feat: add dankcolors base16 theme plugin with auto-reload support --- lua/plugins/dankcolors.lua | 91 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 lua/plugins/dankcolors.lua diff --git a/lua/plugins/dankcolors.lua b/lua/plugins/dankcolors.lua new file mode 100644 index 00000000000..f819a8f5ae2 --- /dev/null +++ b/lua/plugins/dankcolors.lua @@ -0,0 +1,91 @@ +return { + { + "RRethy/base16-nvim", + priority = 1000, + config = function() + require('base16-colorscheme').setup({ + base00 = '#141218', + base01 = '#141218', + base02 = '#9d99a5', + base03 = '#9d99a5', + base04 = '#f4efff', + base05 = '#faf8ff', + base06 = '#faf8ff', + base07 = '#faf8ff', + base08 = '#ff9fb2', + base09 = '#ff9fb2', + base0A = '#d7c6ff', + base0B = '#a5ffb8', + base0C = '#e9e0ff', + base0D = '#d7c6ff', + base0E = '#ded0ff', + base0F = '#ded0ff', + }) + + vim.api.nvim_set_hl(0, 'Visual', { + bg = '#9d99a5', + fg = '#faf8ff', + bold = true + }) + vim.api.nvim_set_hl(0, 'Statusline', { + bg = '#d7c6ff', + fg = '#141218', + }) + vim.api.nvim_set_hl(0, 'LineNr', { fg = '#9d99a5' }) + vim.api.nvim_set_hl(0, 'CursorLineNr', { fg = '#e9e0ff', bold = true }) + + vim.api.nvim_set_hl(0, 'Statement', { + fg = '#ded0ff', + bold = true + }) + vim.api.nvim_set_hl(0, 'Keyword', { link = 'Statement' }) + vim.api.nvim_set_hl(0, 'Repeat', { link = 'Statement' }) + vim.api.nvim_set_hl(0, 'Conditional', { link = 'Statement' }) + + vim.api.nvim_set_hl(0, 'Function', { + fg = '#d7c6ff', + bold = true + }) + vim.api.nvim_set_hl(0, 'Macro', { + fg = '#d7c6ff', + italic = true + }) + vim.api.nvim_set_hl(0, '@function.macro', { link = 'Macro' }) + + vim.api.nvim_set_hl(0, 'Type', { + fg = '#e9e0ff', + bold = true, + italic = true + }) + vim.api.nvim_set_hl(0, 'Structure', { link = 'Type' }) + + vim.api.nvim_set_hl(0, 'String', { + fg = '#a5ffb8', + italic = true + }) + + vim.api.nvim_set_hl(0, 'Operator', { fg = '#f4efff' }) + vim.api.nvim_set_hl(0, 'Delimiter', { fg = '#f4efff' }) + vim.api.nvim_set_hl(0, '@punctuation.bracket', { link = 'Delimiter' }) + vim.api.nvim_set_hl(0, '@punctuation.delimiter', { link = 'Delimiter' }) + + vim.api.nvim_set_hl(0, 'Comment', { + fg = '#9d99a5', + italic = true + }) + + local current_file_path = vim.fn.stdpath("config") .. "/lua/plugins/dankcolors.lua" + if not _G._matugen_theme_watcher then + local uv = vim.uv or vim.loop + _G._matugen_theme_watcher = uv.new_fs_event() + _G._matugen_theme_watcher:start(current_file_path, {}, vim.schedule_wrap(function() + local new_spec = dofile(current_file_path) + if new_spec and new_spec[1] and new_spec[1].config then + new_spec[1].config() + print("Theme reload") + end + end)) + end + end + } +} From 20b41844decb19a399b35a9461046c3a64c066fc Mon Sep 17 00:00:00 2001 From: Jesper Liljegren Date: Thu, 26 Mar 2026 08:40:08 +0100 Subject: [PATCH 4/7] feat: add .NET development plugins and improve LSP navigation fallbacks --- init.lua | 58 +++- lua/custom/plugins/dotnet.lua | 486 ++++++++++++++++++++++++++++++++++ 2 files changed, 536 insertions(+), 8 deletions(-) create mode 100644 lua/custom/plugins/dotnet.lua diff --git a/init.lua b/init.lua index 8da2a65e855..4e81bfc1760 100644 --- a/init.lua +++ b/init.lua @@ -416,6 +416,15 @@ require('lazy').setup({ group = vim.api.nvim_create_augroup('telescope-lsp-attach', { clear = true }), callback = function(event) local buf = event.buf + local client = vim.lsp.get_client_by_id(event.data.client_id) + + local goto_declaration = function() + if client and client:supports_method('textDocument/declaration', buf) then + vim.lsp.buf.declaration() + else + builtin.lsp_definitions() + end + end -- Find references for the word under your cursor. vim.keymap.set('n', 'grr', builtin.lsp_references, { buffer = buf, desc = '[G]oto [R]eferences' }) @@ -427,7 +436,9 @@ require('lazy').setup({ -- 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', 'gd', builtin.lsp_definitions, { buffer = buf, desc = '[G]oto [D]efinition' }) vim.keymap.set('n', 'grd', builtin.lsp_definitions, { buffer = buf, desc = '[G]oto [D]efinition' }) + vim.keymap.set('n', 'gD', goto_declaration, { buffer = buf, desc = '[G]oto [D]eclaration' }) -- Fuzzy find all the symbols in your current document. -- Symbols are things like variables, functions, types, etc. @@ -480,7 +491,15 @@ 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-org/mason.nvim', opts = {} }, + { + 'mason-org/mason.nvim', + opts = { + registries = { + 'github:mason-org/mason-registry', + 'github:Crashdummyy/mason-registry', + }, + }, + }, { 'mason-org/mason-lspconfig.nvim', opts = {} }, 'WhoIsSethDaniel/mason-tool-installer.nvim', @@ -523,6 +542,8 @@ require('lazy').setup({ vim.api.nvim_create_autocmd('LspAttach', { group = vim.api.nvim_create_augroup('kickstart-lsp-attach', { clear = true }), callback = function(event) + local client = vim.lsp.get_client_by_id(event.data.client_id) + -- 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. -- @@ -537,20 +558,28 @@ require('lazy').setup({ -- Most Language Servers support renaming across files, etc. map('grn', vim.lsp.buf.rename, '[R]e[n]ame') + -- Show documentation for the symbol under your cursor. + map('K', vim.lsp.buf.hover, 'Hover Documentation') + -- 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('gra', vim.lsp.buf.code_action, '[G]oto Code [A]ction', { 'n', 'x' }) -- 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('grD', function() + if client and client:supports_method('textDocument/declaration', event.buf) then + vim.lsp.buf.declaration() + else + vim.lsp.buf.definition() + end + end, '[G]oto [D]eclaration') -- 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' }, { @@ -853,12 +882,25 @@ require('lazy').setup({ { -- Highlight, edit, and navigate code 'nvim-treesitter/nvim-treesitter', + build = ':TSUpdate', config = function() - local filetypes = { 'bash', 'c', 'diff', 'html', 'lua', 'luadoc', 'markdown', 'markdown_inline', 'query', 'vim', 'vimdoc' } - require('nvim-treesitter').install(filetypes) vim.api.nvim_create_autocmd('FileType', { - pattern = filetypes, - callback = function() vim.treesitter.start() end, + pattern = { + 'bash', + 'c', + 'cs', + 'diff', + 'html', + 'lua', + 'markdown', + 'query', + 'vim', + 'help', + }, + callback = function(args) + local lang = vim.treesitter.language.get_lang(args.match) or args.match + pcall(vim.treesitter.start, args.buf, lang) + end, }) end, }, @@ -883,7 +925,7 @@ require('lazy').setup({ -- 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/custom/plugins/dotnet.lua b/lua/custom/plugins/dotnet.lua new file mode 100644 index 00000000000..1b3d46f591d --- /dev/null +++ b/lua/custom/plugins/dotnet.lua @@ -0,0 +1,486 @@ +-- ============================================================================= +-- .NET / C# Development Setup +-- ============================================================================= +-- This file wires up everything needed for a productive .NET experience in +-- Neovim: Roslyn LSP, GitHub Copilot (via blink.cmp), DAP debugging with +-- netcoredbg, and Neotest for running xUnit/NUnit/MSTest tests. +-- +-- PREREQUISITES (install once on your system): +-- - .NET SDK: https://dotnet.microsoft.com/download +-- - Node.js > 20 (for Copilot): https://nodejs.org +-- - On first launch run: :Copilot auth (one-time GitHub login) +-- - Mason will auto-install: netcoredbg, csharpier +-- - roslyn.nvim downloads its own language server on first use +-- +-- QUICK REFERENCE โ€” KEY BINDINGS +-- (language-agnostic binds work everywhere; .NET-specific are noted) +-- ============================================================================= +-- +-- โ”€โ”€ LSP NAVIGATION โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +-- grd Go to Definition (jump to where a symbol is defined) +-- grD Go to Declaration (e.g. interface declaration) +-- gri Go to Implementation (jump to concrete implementation) +-- grr Find All References (list all usages in Telescope) +-- grt Go to Type Definition (jump to the type of a variable) +-- gO Document Symbols (fuzzy search symbols in this file) +-- gW Workspace Symbols (fuzzy search symbols in whole project) +-- K Hover Documentation (show docs / type info under cursor) +-- grn Rename Symbol (rename across the whole project) +-- gra Code Actions (fix suggestions, extract method, etc.) +-- f Format Buffer (run csharpier on the current file) +-- th Toggle Inlay Hints (show/hide parameter name hints) +-- [d / ]d Previous / Next Diagnostic +-- q Diagnostic Quickfix List +-- sd Search Diagnostics (Telescope) +-- +-- โ”€โ”€ COMPLETION (blink.cmp) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +-- Open completion menu (or open docs if menu is open) +-- / Next / Previous item (also / ) +-- Accept completion (auto-imports if LSP supports it) +-- Dismiss menu +-- Toggle signature help (see function parameter hints) +-- / Move through snippet expansions +-- +-- โ”€โ”€ GITHUB COPILOT โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +-- Copilot suggestions appear in the blink.cmp completion menu automatically. +-- They are ranked alongside LSP completions โ€” just navigate with /. +-- :Copilot auth Re-authenticate with GitHub (run once on first setup) +-- :Copilot status Check if Copilot is active +-- +-- โ”€โ”€ DEBUGGING (nvim-dap + netcoredbg) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +-- dc Continue / Start debug session +-- db Toggle Breakpoint (set/unset on current line) +-- dB Conditional Breakpoint (prompt for a condition expression) +-- ds Step Over (F10 equivalent) +-- di Step Into (F11 equivalent) +-- do Step Out (Shift-F11 equivalent) +-- dr Open REPL (interactive debug console) +-- dl Re-run Last Session (repeat last debug config) +-- du Toggle Debug UI (show/hide the dap-ui panels) +-- de Evaluate Expression (hover eval under cursor) +-- dE Evaluate (input prompt) (type an expression to evaluate) +-- +-- The debug UI opens automatically when a session starts and shows: +-- Left panel: Scopes ยท Breakpoints ยท Call Stack ยท Watches +-- Bottom panel: REPL ยท Console output +-- +-- โ”€โ”€ TESTING (neotest + neotest-dotnet) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +-- nn Run Nearest Test (test method under cursor) +-- nf Run File Tests (all tests in current file) +-- na Run All Tests (entire project/solution) +-- ns Toggle Test Summary (tree view of all tests + status) +-- no Show Test Output (output of last test run) +-- np Show Short Output (condensed output panel) +-- nd Debug Nearest Test (run test under cursor with DAP) +-- nw Watch Nearest Test (re-run on file save) +-- nx Stop Running Tests +-- +-- ============================================================================= + +return { + + -- =========================================================================== + -- 1. ROSLYN LSP (seblj/roslyn.nvim) + -- =========================================================================== + -- The same Roslyn language server that powers Visual Studio and Rider. + -- Downloads Microsoft.CodeAnalysis.LanguageServer automatically on first use. + -- Provides: IntelliSense, go-to-definition, find-references, rename, + -- code actions (extract method, add using, etc.), inlay hints, + -- and full project/solution awareness. + { + 'seblj/roslyn.nvim', + ft = { 'cs', 'vb' }, + dependencies = { + 'mason-org/mason.nvim', -- ensures mason is loaded first + }, + opts = { + -- roslyn.nvim finds and downloads the language server into stdpath('data'). + -- Set exe to override with a custom path if you manage the server yourself. + config = { + -- Pass extra capabilities from blink.cmp so the LSP knows we support + -- snippet completions, resolve, etc. + capabilities = vim.tbl_deep_extend( + 'force', + vim.lsp.protocol.make_client_capabilities(), + -- blink.cmp may not be loaded yet at spec parse time; we wrap in a + -- function via on_attach / capabilities below instead. + {} + ), + }, + -- In larger repos / monoliths this helps Roslyn search a bit wider for + -- the correct solution or project root. + broad_search = true, + -- Allow target switching when multiple solutions exist in the repo. + lock_target = false, + }, + config = function(_, opts) + -- Inject blink.cmp capabilities now that blink is guaranteed loaded. + local ok, blink = pcall(require, 'blink.cmp') + if ok then + opts.config = opts.config or {} + opts.config.capabilities = blink.get_lsp_capabilities(opts.config.capabilities) + end + + require('roslyn').setup(opts) + end, + }, + + -- =========================================================================== + -- 2. GITHUB COPILOT (zbirenbaum/copilot.lua + blink-cmp-copilot) + -- =========================================================================== + -- copilot.lua runs the Copilot LSP in the background. + -- We disable the built-in ghost-text / panel UI and instead surface + -- completions through blink.cmp so everything comes from one menu. + { + 'zbirenbaum/copilot.lua', + event = 'InsertEnter', + opts = { + -- Disable copilot.lua's own UI โ€” blink.cmp owns the completion menu. + suggestion = { enabled = false }, + panel = { enabled = false }, + -- Explicitly enable C# (and a few other useful filetypes). + filetypes = { + cs = true, + lua = true, + python = true, + javascript = true, + typescript = true, + -- Disable noisy filetypes. + markdown = false, + help = false, + gitcommit = false, + }, + -- Node.js must be > 20. + copilot_node_command = 'node', + }, + }, + + -- blink-cmp-copilot bridges copilot.lua into blink.cmp as a source provider. + -- Copilot completions appear in the menu with a icon and "Copilot" label. + { + 'giuxtaposition/blink-cmp-copilot', + dependencies = { 'zbirenbaum/copilot.lua' }, + -- blink.cmp is already configured in init.lua; we patch its sources table + -- here so we don't have to touch the existing config. + specs = { + { + 'saghen/blink.cmp', + optional = true, + opts = { + sources = { + default = { 'lsp', 'path', 'snippets', 'copilot' }, + providers = { + copilot = { + name = 'copilot', + module = 'blink-cmp-copilot', + -- Show Copilot items after LSP items in the menu. + score_offset = -1, + async = true, + -- Give it a recognisable icon in the completion menu. + transform_items = function(_, items) + for _, item in ipairs(items) do + item.kind_icon = '' + item.kind_name = 'Copilot' + end + return items + end, + }, + }, + }, + }, + }, + }, + }, + + -- =========================================================================== + -- 3. DEBUGGING (nvim-dap + nvim-dap-ui + mason-nvim-dap) + -- =========================================================================== + -- netcoredbg is the open-source .NET debugger that understands the DAP + -- protocol. mason-nvim-dap installs it automatically. + { + 'mfussenegger/nvim-dap', + dependencies = { + -- Automatic installer / bridge between mason and dap adapters. + { + 'jay-babu/mason-nvim-dap.nvim', + dependencies = { 'mason-org/mason.nvim' }, + opts = { + -- Let mason-nvim-dap install and configure netcoredbg. + ensure_installed = { 'netcoredbg' }, + automatic_installation = true, + handlers = {}, -- use default handlers for installed adapters + }, + }, + + -- A full debug UI: scopes, locals, call stack, breakpoints, REPL. + { + 'rcarriga/nvim-dap-ui', + dependencies = { 'nvim-neotest/nvim-nio' }, + config = function() + local dap = require 'dap' + local dapui = require 'dapui' + + dapui.setup { + -- Layout: left sidebar + bottom panel โ€” mirrors a typical IDE layout. + layouts = { + { + -- Left sidebar: variables, call stack, breakpoints, watches. + position = 'left', + size = 40, + elements = { + { id = 'scopes', size = 0.35 }, + { id = 'stacks', size = 0.35 }, + { id = 'breakpoints', size = 0.15 }, + { id = 'watches', size = 0.15 }, + }, + }, + { + -- Bottom tray: REPL for live evaluation + program console output. + position = 'bottom', + size = 12, + elements = { + { id = 'repl', size = 0.5 }, + { id = 'console', size = 0.5 }, + }, + }, + }, + } + + -- Auto-open the UI when a debug session starts. + dap.listeners.after.event_initialized['dapui_config'] = function() + dapui.open() + end + -- Auto-close the UI when the session ends or terminates. + dap.listeners.before.event_terminated['dapui_config'] = function() + dapui.close() + end + dap.listeners.before.event_exited['dapui_config'] = function() + dapui.close() + end + end, + }, + + -- Virtual text shows variable values inline while stepping through code. + { + 'theHamsta/nvim-dap-virtual-text', + opts = { + -- Show the value of the variable at the current scope inline. + display_callback = function(variable, _, _, _, options) + if #variable.value > 60 then + return ' ' .. string.sub(variable.value, 1, 57) .. '...' + end + return ' ' .. variable.value + end, + }, + }, + }, + + config = function() + local dap = require 'dap' + + -- โ”€โ”€ Adapter โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + -- mason-nvim-dap installs netcoredbg into mason's bin directory. + -- We resolve the path at runtime so it works on any machine. + local mason_bin = vim.fn.stdpath 'data' .. '/mason/bin' + local netcoredbg_cmd = mason_bin .. '/netcoredbg' + -- On Windows mason wraps executables in .cmd files. + if vim.fn.has 'win32' == 1 then + netcoredbg_cmd = mason_bin .. '/netcoredbg.cmd' + end + + dap.adapters.coreclr = { + type = 'executable', + command = netcoredbg_cmd, + args = { '--interpreter=vscode' }, + } + + -- โ”€โ”€ Helper: prompt for / remember DLL path โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + -- On first launch you'll be asked for the path to the compiled DLL. + -- The answer is cached for the rest of the session; you're only re-asked + -- if you confirm you want to change it. + local function get_dll_path() + if vim.g.dotnet_last_dll_path == nil then + vim.g.dotnet_last_dll_path = vim.fn.input( + 'Path to DLL: ', + vim.fn.getcwd() .. '/bin/Debug/', + 'file' + ) + else + local change = vim.fn.confirm( + 'Reuse last DLL?\n' .. vim.g.dotnet_last_dll_path, + '&Yes\n&No', + 1 + ) + if change == 2 then + vim.g.dotnet_last_dll_path = vim.fn.input( + 'Path to DLL: ', + vim.g.dotnet_last_dll_path, + 'file' + ) + end + end + return vim.g.dotnet_last_dll_path + end + + -- โ”€โ”€ Helper: optional pre-build โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + local function maybe_build() + local build = vim.fn.confirm('Build project first?', '&Yes\n&No', 2) + if build == 1 then + local proj = vim.fn.input( + 'Path to .csproj/.sln: ', + vim.g.dotnet_last_proj_path or (vim.fn.getcwd() .. '/'), + 'file' + ) + vim.g.dotnet_last_proj_path = proj + vim.notify('Building ' .. proj .. ' โ€ฆ', vim.log.levels.INFO) + local result = os.execute('dotnet build -c Debug "' .. proj .. '" > /dev/null 2>&1') + if result == 0 then + vim.notify('Build succeeded', vim.log.levels.INFO) + else + vim.notify('Build FAILED โ€” check :messages', vim.log.levels.ERROR) + end + end + end + + -- โ”€โ”€ Launch configurations โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + -- Shared across cs and fsharp. + local dotnet_launch = { + { + type = 'coreclr', + name = 'Launch .NET (netcoredbg)', + request = 'launch', + program = function() + maybe_build() + return get_dll_path() + end, + }, + { + -- Attach to a running process (e.g. an already-running web server). + type = 'coreclr', + name = 'Attach to process', + request = 'attach', + processId = require('dap.utils').pick_process, + }, + } + + dap.configurations.cs = dotnet_launch + dap.configurations.fsharp = dotnet_launch + + -- โ”€โ”€ Keybinds โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + -- See the header comment at the top of this file for full descriptions. + local map = function(keys, func, desc) + vim.keymap.set('n', keys, func, { desc = 'Debug: ' .. desc }) + end + + map('dc', dap.continue, '[C]ontinue / Start') + map('db', dap.toggle_breakpoint, 'Toggle [B]reakpoint') + map('dB', function() + dap.set_breakpoint(vim.fn.input 'Breakpoint condition: ') + end, 'Conditional [B]reakpoint') + map('ds', dap.step_over, '[S]tep Over') + map('di', dap.step_into, 'Step [I]nto') + map('do', dap.step_out, 'Step [O]ut') + map('dr', dap.repl.open, 'Open [R]EPL') + map('dl', dap.run_last, 'Run [L]ast') + map('du', function() require('dapui').toggle() end, 'Toggle [U]I') + map('de', function() require('dapui').eval() end, '[E]val under cursor') + map('dE', function() + require('dapui').eval(vim.fn.input 'Expression: ') + end, '[E]val expression') + + -- Register the d group name with which-key (if loaded). + local ok, wk = pcall(require, 'which-key') + if ok then + wk.add { { 'd', group = '[D]ebug' } } + end + end, + }, + + -- =========================================================================== + -- 4. TESTING (nvim-neotest + neotest-dotnet) + -- =========================================================================== + -- neotest-dotnet discovers xUnit, NUnit, and MSTest tests automatically by + -- scanning for [Fact], [Theory], [Test], [TestCase], etc. attributes. + { + 'nvim-neotest/neotest', + dependencies = { + 'nvim-lua/plenary.nvim', + 'antoinemadec/FixCursorHold.nvim', -- required by neotest + 'nvim-treesitter/nvim-treesitter', + -- .NET adapter โ€” supports xUnit, NUnit, MSTest. + 'Issafalcon/neotest-dotnet', + }, + config = function() + require('neotest').setup { + adapters = { + require('neotest-dotnet') { + -- dap integration: nd will attach the debugger to the test. + dap = { justMyCode = false }, + -- Discover tests using the dotnet CLI runner. + -- Set to 'omnisharp' if you prefer the OmniSharp test runner. + dotnet_additional_args = {}, + }, + }, + -- Show pass/fail icons in the gutter next to each test. + status = { enabled = true, signs = true, virtual_text = false }, + -- Open the output panel automatically for short failures. + output = { enabled = true, open_on_run = 'short' }, + -- Use a bottom split for the output panel. + output_panel = { + enabled = true, + open = 'botright split | resize 15', + }, + } + + -- โ”€โ”€ Keybinds โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + -- See the header comment at the top of this file for full descriptions. + local nt = require 'neotest' + local map = function(keys, func, desc) + vim.keymap.set('n', keys, func, { desc = 'Test: ' .. desc }) + end + + map('nn', function() nt.run.run() end, 'Run [N]earest test') + map('nf', function() nt.run.run(vim.fn.expand '%') end, 'Run [F]ile tests') + map('na', function() nt.run.run(vim.fn.getcwd()) end, 'Run [A]ll tests') + map('ns', function() nt.summary.toggle() end, 'Toggle [S]ummary panel') + map('no', function() nt.output.open { enter = true } end, 'Show [O]utput') + map('np', function() nt.output_panel.toggle() end, 'Toggle output [P]anel') + map('nd', function() nt.run.run { strategy = 'dap' } end, '[D]ebug nearest test') + map('nw', function() nt.watch.toggle(vim.fn.expand '%') end, '[W]atch file tests') + map('nx', function() nt.run.stop() end, 'Stop running tests') + + -- Register the n group name with which-key (if loaded). + local ok, wk = pcall(require, 'which-key') + if ok then + wk.add { { 'n', group = '[N]eotest' } } + end + end, + }, + + -- =========================================================================== + -- 5. CSHARPIER (formatter, installed via Mason) + -- =========================================================================== + -- csharpier is the opinionated C# formatter (like gofmt/prettier for C#). + -- It is installed by mason-tool-installer below and wired into conform.nvim. + { + -- Re-use mason-tool-installer (already set up in init.lua) to ensure + -- csharpier is installed. We declare it as a separate spec here so this + -- file is self-contained; lazy.nvim merges duplicate plugin specs. + 'WhoIsSethDaniel/mason-tool-installer.nvim', + optional = true, + opts = function(_, opts) + opts.ensure_installed = opts.ensure_installed or {} + vim.list_extend(opts.ensure_installed, { 'csharpier', 'roslyn' }) + end, + }, + { + -- Patch conform.nvim to format C# files with csharpier. + -- This merges cleanly with the existing conform setup in init.lua. + 'stevearc/conform.nvim', + optional = true, + opts = function(_, opts) + opts.formatters_by_ft = opts.formatters_by_ft or {} + opts.formatters_by_ft.cs = { 'csharpier' } + end, + }, +} From a18d7270b6174acc4b6455e480c292c88b7932c5 Mon Sep 17 00:00:00 2001 From: Jesper Liljegren Date: Thu, 26 Mar 2026 09:22:56 +0100 Subject: [PATCH 5/7] Revert "feat: add .NET development plugins and improve LSP navigation fallbacks" This reverts commit 20b41844decb19a399b35a9461046c3a64c066fc. --- init.lua | 58 +--- lua/custom/plugins/dotnet.lua | 486 ---------------------------------- 2 files changed, 8 insertions(+), 536 deletions(-) delete mode 100644 lua/custom/plugins/dotnet.lua diff --git a/init.lua b/init.lua index 4e81bfc1760..8da2a65e855 100644 --- a/init.lua +++ b/init.lua @@ -416,15 +416,6 @@ require('lazy').setup({ group = vim.api.nvim_create_augroup('telescope-lsp-attach', { clear = true }), callback = function(event) local buf = event.buf - local client = vim.lsp.get_client_by_id(event.data.client_id) - - local goto_declaration = function() - if client and client:supports_method('textDocument/declaration', buf) then - vim.lsp.buf.declaration() - else - builtin.lsp_definitions() - end - end -- Find references for the word under your cursor. vim.keymap.set('n', 'grr', builtin.lsp_references, { buffer = buf, desc = '[G]oto [R]eferences' }) @@ -436,9 +427,7 @@ require('lazy').setup({ -- 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', 'gd', builtin.lsp_definitions, { buffer = buf, desc = '[G]oto [D]efinition' }) vim.keymap.set('n', 'grd', builtin.lsp_definitions, { buffer = buf, desc = '[G]oto [D]efinition' }) - vim.keymap.set('n', 'gD', goto_declaration, { buffer = buf, desc = '[G]oto [D]eclaration' }) -- Fuzzy find all the symbols in your current document. -- Symbols are things like variables, functions, types, etc. @@ -491,15 +480,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-org/mason.nvim', - opts = { - registries = { - 'github:mason-org/mason-registry', - 'github:Crashdummyy/mason-registry', - }, - }, - }, + { 'mason-org/mason.nvim', opts = {} }, { 'mason-org/mason-lspconfig.nvim', opts = {} }, 'WhoIsSethDaniel/mason-tool-installer.nvim', @@ -542,8 +523,6 @@ require('lazy').setup({ vim.api.nvim_create_autocmd('LspAttach', { group = vim.api.nvim_create_augroup('kickstart-lsp-attach', { clear = true }), callback = function(event) - local client = vim.lsp.get_client_by_id(event.data.client_id) - -- 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. -- @@ -558,28 +537,20 @@ require('lazy').setup({ -- Most Language Servers support renaming across files, etc. map('grn', vim.lsp.buf.rename, '[R]e[n]ame') - -- Show documentation for the symbol under your cursor. - map('K', vim.lsp.buf.hover, 'Hover Documentation') - -- 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('gra', vim.lsp.buf.code_action, '[G]oto Code [A]ction', { 'n', 'x' }) -- WARN: This is not Goto Definition, this is Goto Declaration. -- For example, in C this would take you to the header. - map('grD', function() - if client and client:supports_method('textDocument/declaration', event.buf) then - vim.lsp.buf.declaration() - else - vim.lsp.buf.definition() - end - end, '[G]oto [D]eclaration') + map('grD', vim.lsp.buf.declaration, '[G]oto [D]eclaration') -- 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' }, { @@ -882,25 +853,12 @@ require('lazy').setup({ { -- Highlight, edit, and navigate code 'nvim-treesitter/nvim-treesitter', - build = ':TSUpdate', config = function() + local filetypes = { 'bash', 'c', 'diff', 'html', 'lua', 'luadoc', 'markdown', 'markdown_inline', 'query', 'vim', 'vimdoc' } + require('nvim-treesitter').install(filetypes) vim.api.nvim_create_autocmd('FileType', { - pattern = { - 'bash', - 'c', - 'cs', - 'diff', - 'html', - 'lua', - 'markdown', - 'query', - 'vim', - 'help', - }, - callback = function(args) - local lang = vim.treesitter.language.get_lang(args.match) or args.match - pcall(vim.treesitter.start, args.buf, lang) - end, + pattern = filetypes, + callback = function() vim.treesitter.start() end, }) end, }, @@ -925,7 +883,7 @@ require('lazy').setup({ -- 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/custom/plugins/dotnet.lua b/lua/custom/plugins/dotnet.lua deleted file mode 100644 index 1b3d46f591d..00000000000 --- a/lua/custom/plugins/dotnet.lua +++ /dev/null @@ -1,486 +0,0 @@ --- ============================================================================= --- .NET / C# Development Setup --- ============================================================================= --- This file wires up everything needed for a productive .NET experience in --- Neovim: Roslyn LSP, GitHub Copilot (via blink.cmp), DAP debugging with --- netcoredbg, and Neotest for running xUnit/NUnit/MSTest tests. --- --- PREREQUISITES (install once on your system): --- - .NET SDK: https://dotnet.microsoft.com/download --- - Node.js > 20 (for Copilot): https://nodejs.org --- - On first launch run: :Copilot auth (one-time GitHub login) --- - Mason will auto-install: netcoredbg, csharpier --- - roslyn.nvim downloads its own language server on first use --- --- QUICK REFERENCE โ€” KEY BINDINGS --- (language-agnostic binds work everywhere; .NET-specific are noted) --- ============================================================================= --- --- โ”€โ”€ LSP NAVIGATION โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ --- grd Go to Definition (jump to where a symbol is defined) --- grD Go to Declaration (e.g. interface declaration) --- gri Go to Implementation (jump to concrete implementation) --- grr Find All References (list all usages in Telescope) --- grt Go to Type Definition (jump to the type of a variable) --- gO Document Symbols (fuzzy search symbols in this file) --- gW Workspace Symbols (fuzzy search symbols in whole project) --- K Hover Documentation (show docs / type info under cursor) --- grn Rename Symbol (rename across the whole project) --- gra Code Actions (fix suggestions, extract method, etc.) --- f Format Buffer (run csharpier on the current file) --- th Toggle Inlay Hints (show/hide parameter name hints) --- [d / ]d Previous / Next Diagnostic --- q Diagnostic Quickfix List --- sd Search Diagnostics (Telescope) --- --- โ”€โ”€ COMPLETION (blink.cmp) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ --- Open completion menu (or open docs if menu is open) --- / Next / Previous item (also / ) --- Accept completion (auto-imports if LSP supports it) --- Dismiss menu --- Toggle signature help (see function parameter hints) --- / Move through snippet expansions --- --- โ”€โ”€ GITHUB COPILOT โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ --- Copilot suggestions appear in the blink.cmp completion menu automatically. --- They are ranked alongside LSP completions โ€” just navigate with /. --- :Copilot auth Re-authenticate with GitHub (run once on first setup) --- :Copilot status Check if Copilot is active --- --- โ”€โ”€ DEBUGGING (nvim-dap + netcoredbg) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ --- dc Continue / Start debug session --- db Toggle Breakpoint (set/unset on current line) --- dB Conditional Breakpoint (prompt for a condition expression) --- ds Step Over (F10 equivalent) --- di Step Into (F11 equivalent) --- do Step Out (Shift-F11 equivalent) --- dr Open REPL (interactive debug console) --- dl Re-run Last Session (repeat last debug config) --- du Toggle Debug UI (show/hide the dap-ui panels) --- de Evaluate Expression (hover eval under cursor) --- dE Evaluate (input prompt) (type an expression to evaluate) --- --- The debug UI opens automatically when a session starts and shows: --- Left panel: Scopes ยท Breakpoints ยท Call Stack ยท Watches --- Bottom panel: REPL ยท Console output --- --- โ”€โ”€ TESTING (neotest + neotest-dotnet) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ --- nn Run Nearest Test (test method under cursor) --- nf Run File Tests (all tests in current file) --- na Run All Tests (entire project/solution) --- ns Toggle Test Summary (tree view of all tests + status) --- no Show Test Output (output of last test run) --- np Show Short Output (condensed output panel) --- nd Debug Nearest Test (run test under cursor with DAP) --- nw Watch Nearest Test (re-run on file save) --- nx Stop Running Tests --- --- ============================================================================= - -return { - - -- =========================================================================== - -- 1. ROSLYN LSP (seblj/roslyn.nvim) - -- =========================================================================== - -- The same Roslyn language server that powers Visual Studio and Rider. - -- Downloads Microsoft.CodeAnalysis.LanguageServer automatically on first use. - -- Provides: IntelliSense, go-to-definition, find-references, rename, - -- code actions (extract method, add using, etc.), inlay hints, - -- and full project/solution awareness. - { - 'seblj/roslyn.nvim', - ft = { 'cs', 'vb' }, - dependencies = { - 'mason-org/mason.nvim', -- ensures mason is loaded first - }, - opts = { - -- roslyn.nvim finds and downloads the language server into stdpath('data'). - -- Set exe to override with a custom path if you manage the server yourself. - config = { - -- Pass extra capabilities from blink.cmp so the LSP knows we support - -- snippet completions, resolve, etc. - capabilities = vim.tbl_deep_extend( - 'force', - vim.lsp.protocol.make_client_capabilities(), - -- blink.cmp may not be loaded yet at spec parse time; we wrap in a - -- function via on_attach / capabilities below instead. - {} - ), - }, - -- In larger repos / monoliths this helps Roslyn search a bit wider for - -- the correct solution or project root. - broad_search = true, - -- Allow target switching when multiple solutions exist in the repo. - lock_target = false, - }, - config = function(_, opts) - -- Inject blink.cmp capabilities now that blink is guaranteed loaded. - local ok, blink = pcall(require, 'blink.cmp') - if ok then - opts.config = opts.config or {} - opts.config.capabilities = blink.get_lsp_capabilities(opts.config.capabilities) - end - - require('roslyn').setup(opts) - end, - }, - - -- =========================================================================== - -- 2. GITHUB COPILOT (zbirenbaum/copilot.lua + blink-cmp-copilot) - -- =========================================================================== - -- copilot.lua runs the Copilot LSP in the background. - -- We disable the built-in ghost-text / panel UI and instead surface - -- completions through blink.cmp so everything comes from one menu. - { - 'zbirenbaum/copilot.lua', - event = 'InsertEnter', - opts = { - -- Disable copilot.lua's own UI โ€” blink.cmp owns the completion menu. - suggestion = { enabled = false }, - panel = { enabled = false }, - -- Explicitly enable C# (and a few other useful filetypes). - filetypes = { - cs = true, - lua = true, - python = true, - javascript = true, - typescript = true, - -- Disable noisy filetypes. - markdown = false, - help = false, - gitcommit = false, - }, - -- Node.js must be > 20. - copilot_node_command = 'node', - }, - }, - - -- blink-cmp-copilot bridges copilot.lua into blink.cmp as a source provider. - -- Copilot completions appear in the menu with a icon and "Copilot" label. - { - 'giuxtaposition/blink-cmp-copilot', - dependencies = { 'zbirenbaum/copilot.lua' }, - -- blink.cmp is already configured in init.lua; we patch its sources table - -- here so we don't have to touch the existing config. - specs = { - { - 'saghen/blink.cmp', - optional = true, - opts = { - sources = { - default = { 'lsp', 'path', 'snippets', 'copilot' }, - providers = { - copilot = { - name = 'copilot', - module = 'blink-cmp-copilot', - -- Show Copilot items after LSP items in the menu. - score_offset = -1, - async = true, - -- Give it a recognisable icon in the completion menu. - transform_items = function(_, items) - for _, item in ipairs(items) do - item.kind_icon = '' - item.kind_name = 'Copilot' - end - return items - end, - }, - }, - }, - }, - }, - }, - }, - - -- =========================================================================== - -- 3. DEBUGGING (nvim-dap + nvim-dap-ui + mason-nvim-dap) - -- =========================================================================== - -- netcoredbg is the open-source .NET debugger that understands the DAP - -- protocol. mason-nvim-dap installs it automatically. - { - 'mfussenegger/nvim-dap', - dependencies = { - -- Automatic installer / bridge between mason and dap adapters. - { - 'jay-babu/mason-nvim-dap.nvim', - dependencies = { 'mason-org/mason.nvim' }, - opts = { - -- Let mason-nvim-dap install and configure netcoredbg. - ensure_installed = { 'netcoredbg' }, - automatic_installation = true, - handlers = {}, -- use default handlers for installed adapters - }, - }, - - -- A full debug UI: scopes, locals, call stack, breakpoints, REPL. - { - 'rcarriga/nvim-dap-ui', - dependencies = { 'nvim-neotest/nvim-nio' }, - config = function() - local dap = require 'dap' - local dapui = require 'dapui' - - dapui.setup { - -- Layout: left sidebar + bottom panel โ€” mirrors a typical IDE layout. - layouts = { - { - -- Left sidebar: variables, call stack, breakpoints, watches. - position = 'left', - size = 40, - elements = { - { id = 'scopes', size = 0.35 }, - { id = 'stacks', size = 0.35 }, - { id = 'breakpoints', size = 0.15 }, - { id = 'watches', size = 0.15 }, - }, - }, - { - -- Bottom tray: REPL for live evaluation + program console output. - position = 'bottom', - size = 12, - elements = { - { id = 'repl', size = 0.5 }, - { id = 'console', size = 0.5 }, - }, - }, - }, - } - - -- Auto-open the UI when a debug session starts. - dap.listeners.after.event_initialized['dapui_config'] = function() - dapui.open() - end - -- Auto-close the UI when the session ends or terminates. - dap.listeners.before.event_terminated['dapui_config'] = function() - dapui.close() - end - dap.listeners.before.event_exited['dapui_config'] = function() - dapui.close() - end - end, - }, - - -- Virtual text shows variable values inline while stepping through code. - { - 'theHamsta/nvim-dap-virtual-text', - opts = { - -- Show the value of the variable at the current scope inline. - display_callback = function(variable, _, _, _, options) - if #variable.value > 60 then - return ' ' .. string.sub(variable.value, 1, 57) .. '...' - end - return ' ' .. variable.value - end, - }, - }, - }, - - config = function() - local dap = require 'dap' - - -- โ”€โ”€ Adapter โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ - -- mason-nvim-dap installs netcoredbg into mason's bin directory. - -- We resolve the path at runtime so it works on any machine. - local mason_bin = vim.fn.stdpath 'data' .. '/mason/bin' - local netcoredbg_cmd = mason_bin .. '/netcoredbg' - -- On Windows mason wraps executables in .cmd files. - if vim.fn.has 'win32' == 1 then - netcoredbg_cmd = mason_bin .. '/netcoredbg.cmd' - end - - dap.adapters.coreclr = { - type = 'executable', - command = netcoredbg_cmd, - args = { '--interpreter=vscode' }, - } - - -- โ”€โ”€ Helper: prompt for / remember DLL path โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ - -- On first launch you'll be asked for the path to the compiled DLL. - -- The answer is cached for the rest of the session; you're only re-asked - -- if you confirm you want to change it. - local function get_dll_path() - if vim.g.dotnet_last_dll_path == nil then - vim.g.dotnet_last_dll_path = vim.fn.input( - 'Path to DLL: ', - vim.fn.getcwd() .. '/bin/Debug/', - 'file' - ) - else - local change = vim.fn.confirm( - 'Reuse last DLL?\n' .. vim.g.dotnet_last_dll_path, - '&Yes\n&No', - 1 - ) - if change == 2 then - vim.g.dotnet_last_dll_path = vim.fn.input( - 'Path to DLL: ', - vim.g.dotnet_last_dll_path, - 'file' - ) - end - end - return vim.g.dotnet_last_dll_path - end - - -- โ”€โ”€ Helper: optional pre-build โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ - local function maybe_build() - local build = vim.fn.confirm('Build project first?', '&Yes\n&No', 2) - if build == 1 then - local proj = vim.fn.input( - 'Path to .csproj/.sln: ', - vim.g.dotnet_last_proj_path or (vim.fn.getcwd() .. '/'), - 'file' - ) - vim.g.dotnet_last_proj_path = proj - vim.notify('Building ' .. proj .. ' โ€ฆ', vim.log.levels.INFO) - local result = os.execute('dotnet build -c Debug "' .. proj .. '" > /dev/null 2>&1') - if result == 0 then - vim.notify('Build succeeded', vim.log.levels.INFO) - else - vim.notify('Build FAILED โ€” check :messages', vim.log.levels.ERROR) - end - end - end - - -- โ”€โ”€ Launch configurations โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ - -- Shared across cs and fsharp. - local dotnet_launch = { - { - type = 'coreclr', - name = 'Launch .NET (netcoredbg)', - request = 'launch', - program = function() - maybe_build() - return get_dll_path() - end, - }, - { - -- Attach to a running process (e.g. an already-running web server). - type = 'coreclr', - name = 'Attach to process', - request = 'attach', - processId = require('dap.utils').pick_process, - }, - } - - dap.configurations.cs = dotnet_launch - dap.configurations.fsharp = dotnet_launch - - -- โ”€โ”€ Keybinds โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ - -- See the header comment at the top of this file for full descriptions. - local map = function(keys, func, desc) - vim.keymap.set('n', keys, func, { desc = 'Debug: ' .. desc }) - end - - map('dc', dap.continue, '[C]ontinue / Start') - map('db', dap.toggle_breakpoint, 'Toggle [B]reakpoint') - map('dB', function() - dap.set_breakpoint(vim.fn.input 'Breakpoint condition: ') - end, 'Conditional [B]reakpoint') - map('ds', dap.step_over, '[S]tep Over') - map('di', dap.step_into, 'Step [I]nto') - map('do', dap.step_out, 'Step [O]ut') - map('dr', dap.repl.open, 'Open [R]EPL') - map('dl', dap.run_last, 'Run [L]ast') - map('du', function() require('dapui').toggle() end, 'Toggle [U]I') - map('de', function() require('dapui').eval() end, '[E]val under cursor') - map('dE', function() - require('dapui').eval(vim.fn.input 'Expression: ') - end, '[E]val expression') - - -- Register the d group name with which-key (if loaded). - local ok, wk = pcall(require, 'which-key') - if ok then - wk.add { { 'd', group = '[D]ebug' } } - end - end, - }, - - -- =========================================================================== - -- 4. TESTING (nvim-neotest + neotest-dotnet) - -- =========================================================================== - -- neotest-dotnet discovers xUnit, NUnit, and MSTest tests automatically by - -- scanning for [Fact], [Theory], [Test], [TestCase], etc. attributes. - { - 'nvim-neotest/neotest', - dependencies = { - 'nvim-lua/plenary.nvim', - 'antoinemadec/FixCursorHold.nvim', -- required by neotest - 'nvim-treesitter/nvim-treesitter', - -- .NET adapter โ€” supports xUnit, NUnit, MSTest. - 'Issafalcon/neotest-dotnet', - }, - config = function() - require('neotest').setup { - adapters = { - require('neotest-dotnet') { - -- dap integration: nd will attach the debugger to the test. - dap = { justMyCode = false }, - -- Discover tests using the dotnet CLI runner. - -- Set to 'omnisharp' if you prefer the OmniSharp test runner. - dotnet_additional_args = {}, - }, - }, - -- Show pass/fail icons in the gutter next to each test. - status = { enabled = true, signs = true, virtual_text = false }, - -- Open the output panel automatically for short failures. - output = { enabled = true, open_on_run = 'short' }, - -- Use a bottom split for the output panel. - output_panel = { - enabled = true, - open = 'botright split | resize 15', - }, - } - - -- โ”€โ”€ Keybinds โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ - -- See the header comment at the top of this file for full descriptions. - local nt = require 'neotest' - local map = function(keys, func, desc) - vim.keymap.set('n', keys, func, { desc = 'Test: ' .. desc }) - end - - map('nn', function() nt.run.run() end, 'Run [N]earest test') - map('nf', function() nt.run.run(vim.fn.expand '%') end, 'Run [F]ile tests') - map('na', function() nt.run.run(vim.fn.getcwd()) end, 'Run [A]ll tests') - map('ns', function() nt.summary.toggle() end, 'Toggle [S]ummary panel') - map('no', function() nt.output.open { enter = true } end, 'Show [O]utput') - map('np', function() nt.output_panel.toggle() end, 'Toggle output [P]anel') - map('nd', function() nt.run.run { strategy = 'dap' } end, '[D]ebug nearest test') - map('nw', function() nt.watch.toggle(vim.fn.expand '%') end, '[W]atch file tests') - map('nx', function() nt.run.stop() end, 'Stop running tests') - - -- Register the n group name with which-key (if loaded). - local ok, wk = pcall(require, 'which-key') - if ok then - wk.add { { 'n', group = '[N]eotest' } } - end - end, - }, - - -- =========================================================================== - -- 5. CSHARPIER (formatter, installed via Mason) - -- =========================================================================== - -- csharpier is the opinionated C# formatter (like gofmt/prettier for C#). - -- It is installed by mason-tool-installer below and wired into conform.nvim. - { - -- Re-use mason-tool-installer (already set up in init.lua) to ensure - -- csharpier is installed. We declare it as a separate spec here so this - -- file is self-contained; lazy.nvim merges duplicate plugin specs. - 'WhoIsSethDaniel/mason-tool-installer.nvim', - optional = true, - opts = function(_, opts) - opts.ensure_installed = opts.ensure_installed or {} - vim.list_extend(opts.ensure_installed, { 'csharpier', 'roslyn' }) - end, - }, - { - -- Patch conform.nvim to format C# files with csharpier. - -- This merges cleanly with the existing conform setup in init.lua. - 'stevearc/conform.nvim', - optional = true, - opts = function(_, opts) - opts.formatters_by_ft = opts.formatters_by_ft or {} - opts.formatters_by_ft.cs = { 'csharpier' } - end, - }, -} From 90f6f2d4d6c47157e966bd39ff2497ac187ea603 Mon Sep 17 00:00:00 2001 From: Jesper Liljegren Date: Fri, 13 Mar 2026 21:23:00 +0100 Subject: [PATCH 6/7] feat: add dynamic DMS/base46 theme integration and switch the default colorscheme to Catppuccin --- colors/dms.lua | 83 ++++++++++++++++++++++++++++++++++++++++++++++++++ init.lua | 61 +++++++++++++++++++++++++++---------- 2 files changed, 128 insertions(+), 16 deletions(-) create mode 100644 colors/dms.lua diff --git a/colors/dms.lua b/colors/dms.lua new file mode 100644 index 00000000000..3a710c19806 --- /dev/null +++ b/colors/dms.lua @@ -0,0 +1,83 @@ +local present, base46 = pcall(require, "base46") +if not present or not base46._DMS_SUPPORT then + vim.notify( + "base46 plugin not found or incorrect, make sure to install AvengeMedia/base46", + vim.log.levels.ERROR, + { title = "dms integration" } + ) + return +end + +local config_home = vim.env.XDG_CONFIG_HOME +if config_home == nil or #config_home == 0 then + config_home = vim.fs.joinpath(vim.env.HOME, ".config") +end +local settings_file_path = vim.fs.joinpath(config_home, "DankMaterialShell", "settings.json") +local settings_file = io.open(settings_file_path, "r") +if settings_file == nil then + vim.notify( + "cannnot read dms settings file at '" .. settings_file_path .. "'", + vim.log.levels.ERROR, + { title = "dms integration" } + ) + return +end +local settings = vim.json.decode(settings_file:read("*a")) +settings_file:close() + +local function deepGet(t, k) + for _, s in ipairs(k) do + if type(t) ~= "table" then + return + end + t = t[s] + end + return t +end + +local current_file_path = debug.getinfo(1, "S").source:sub(2) +local theme_base = deepGet(settings, { "matugenTemplateNeovimSettings", vim.o.background, "baseTheme" }) + or ("github_" .. vim.o.background) +local harmony = deepGet(settings, { "matugenTemplateNeovimSettings", vim.o.background, "harmony" }) or 0.5 +local theme_name = "dms" + +if not _G._matugen_theme_watcher then + local uv = vim.uv or vim.loop + _G._matugen_theme_watcher = { uv.new_fs_event(), uv.new_fs_event(), reload_timer = uv.new_timer() } + + local debounce_time = 100 -- ms + local function handler() + _G._matugen_theme_watcher.reload_timer:stop() + _G._matugen_theme_watcher.reload_timer:start( + debounce_time, + 0, + vim.schedule_wrap(function() + base46.theme_tables[theme_name] = nil + if vim.g.colors_name == theme_name then + vim.cmd.colorscheme(theme_name) + vim.notify("Theme reload", vim.log.levels.INFO, { title = "dms integration" }) + end + -- NOTE: contrary to what the documentation says, uv fs events usually do not manage to react to more than one edit. + -- I understand that this is not intended: some edit processes in a typical system (e.g. the one neovim uses with + -- multiple renames and changes) make things hard to follow for libuv. Therefore, a restart is the best option. + _G._matugen_theme_watcher[1]:stop() + _G._matugen_theme_watcher[2]:stop() + _G._matugen_theme_watcher[1]:start(current_file_path, {}, handler) + _G._matugen_theme_watcher[2]:start(settings_file_path, {}, handler) + end) + ) + end + _G._matugen_theme_watcher[1]:start(current_file_path, {}, handler) + _G._matugen_theme_watcher[2]:start(settings_file_path, {}, handler) +end + +if not base46.theme_tables[theme_name] or base46.theme_tables[theme_name].type ~= vim.o.background then + local builtin = vim.deepcopy(assert(base46.get_builtin_theme(theme_base))) + local harmonized = base46.theme_harmonize(builtin, "#89b4fa", harmony) + harmonized = base46.theme_set_bg(harmonized, "#1e1e2e") + + base46.theme_tables[theme_name] = harmonized +end + +base46.load(theme_name) +vim.g.colors_name = theme_name diff --git a/init.lua b/init.lua index 8da2a65e855..a42d633092b 100644 --- a/init.lua +++ b/init.lua @@ -789,28 +789,57 @@ require('lazy').setup({ }, }, - { -- 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. + { -- Colorscheme + 'catppuccin/nvim', + name = 'catppuccin', + priority = 1000, config = function() - ---@diagnostic disable-next-line: missing-fields - require('tokyonight').setup { - styles = { - comments = { italic = false }, -- Disable italics in comments + require('catppuccin').setup { + flavour = 'mocha', + background = { + light = 'latte', + dark = 'mocha', }, + transparent_background = true, + show_end_of_buffer = false, + term_colors = true, + integrations = { + gitsigns = true, + mason = true, + mini = { + enabled = true, + indentscope_color = '', + }, + native_lsp = { + enabled = true, + inlay_hints = { + background = true, + }, + }, + telescope = { + enabled = true, + }, + treesitter = true, + which_key = true, + }, + custom_highlights = function(colors) + return { + CursorLineNr = { fg = colors.lavender, bold = true }, + FloatBorder = { bg = colors.mantle, fg = colors.surface2 }, + LineNr = { fg = colors.overlay1 }, + NormalFloat = { bg = colors.mantle }, + Pmenu = { bg = colors.mantle, fg = colors.text }, + PmenuSel = { bg = colors.surface0, fg = colors.text, bold = true }, + StatusLine = { bg = colors.surface0, fg = colors.text }, + StatusLineNC = { bg = colors.base, fg = colors.overlay1 }, + Visual = { bg = colors.surface1 }, + } + end, } - -- 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' + vim.cmd.colorscheme 'catppuccin' end, }, - { 'catppuccin/nvim', name = 'catppuccin', priority = 1000 }, -- Highlight todo, notes, etc in comments { 'folke/todo-comments.nvim', event = 'VimEnter', dependencies = { 'nvim-lua/plenary.nvim' }, opts = { signs = false } }, From a3d1a092dbe1694f0ec94acf2db0c3e73cf568f7 Mon Sep 17 00:00:00 2001 From: Jesper Liljegren Date: Mon, 23 Mar 2026 10:08:11 +0100 Subject: [PATCH 7/7] feat: enable relative line numbers in Neovim --- init.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/init.lua b/init.lua index a42d633092b..2c69c429d93 100644 --- a/init.lua +++ b/init.lua @@ -102,7 +102,7 @@ vim.g.have_nerd_font = true 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'