Skip to content

GustavEikaas/easy-dotnet.nvim

Repository files navigation

Typing SVG

Join easy-dotnet on Discord

Simplifying .NET development in Neovim

easy-dotnet.nvim brings common .NET IDE workflows into Neovim: run, debug, test, manage packages, work with solutions, use Roslyn LSP, and handle project files without leaving the editor.

Important

This plugin now uses easy-dotnet-server to enable more advanced functionality. As a result, the server may require more frequent updates. Run :Dotnet _server update or dotnet tool install -g EasyDotnet to update it. The plugin will attempt to detect when the server is outdated and notify you. If you encounter any issues, please don't hesitate to file an issue.

📰 Stay updated: Major updates and new features are announced in news.md.
Give it a read periodically to stay in sync with the latest capabilities!

Motivation

As a developer transitioning from Rider to Neovim, I found myself missing the simplicity of running projects with just a single button click. Tired of typing out lengthy terminal commands for common tasks like running, testing, and managing user secrets, I decided to create easy-dotnet.nvim. This plugin aims to bridge the gap between the convenience of IDEs like Rider and the flexibility of Neovim.

Table of Contents

  1. Simplifying .NET development in Neovim
  2. Motivation
  3. Features
  4. Requirements
  5. Setup
  6. Advanced Patterns
  7. Commands
  8. Roslyn LSP
  9. Test runner
  10. Neotest
  1. Workspace Diagnostics
  2. Outdated
  3. Add
  4. Project mappings
  5. .NET Framework
  6. New
  7. EntityFramework
  8. Language injections
  9. Nvim-dap configuration
  10. Troubleshooting
  11. Highlight groups
  12. Local Development
  13. Star History
  14. Contributors

Features

  • Roslyn LSP support out of the box — powered by the official .NET Roslyn language server (see LSP details)
  • Debugger configured out of the box- powered by netcoredbg
  • Solution, slnx, csproj and fsproj support: Whether its a single project or a solution containing multiple projects easy-dotnet has you covered.
  • Action Commands: Execute common tasks like building, running, testing, cleaning and restoring with ease.
  • User Secrets Management: Edit, create, and preview .NET user secrets directly within Neovim.
  • Test runner: Test runner similiar to the one you find in Rider.
  • Workspace diagnostics: Get diagnostic errors and warnings from your entire solution or individual projects
  • Outdated command: Makes checking outdated packages a breeze using virtual text
  • (csproj/fsproj) mappings: Keymappings for .csproj and .fsproj files are automatically available
  • Auto bootstrap namespace: Automatically inserts namespace and class/interface when opening a newly created .cs file. (also checks clipboard for json to create class from)
  • Create dotnet templates like with dotnet new, automatically adding them to the current solution
  • Package autocomplete inside .csproj and .fsproj files Check it out
  • Rider-like syntax highlighting for injected languages (sql, json and xml) based on comments

Requirements

  • Neovim 0.11+ built with LuaJIT
  • EasyDotnet dotnet tool install -g EasyDotnet

Although not required by the plugin, it is highly recommended to install one of:

Setup

Without options

-- lazy.nvim
{
  "GustavEikaas/easy-dotnet.nvim",
  dependencies = { "nvim-lua/plenary.nvim", 'folke/snacks.nvim', },
  config = function()
    require("easy-dotnet").setup()
  end
}

With options

-- lazy.nvim
{
  "GustavEikaas/easy-dotnet.nvim",
  -- 'nvim-telescope/telescope.nvim' or 'ibhagwan/fzf-lua' or 'folke/snacks.nvim'
  -- are highly recommended for a better experience
  dependencies = { "nvim-lua/plenary.nvim", 'mfussenegger/nvim-dap', 'folke/snacks.nvim', },
  config = function()
    local dotnet = require("easy-dotnet")
    -- Options are not required
    dotnet.setup({
     managed_terminal = {
       auto_hide = true, -- auto hides terminal if exit code is 0
       auto_hide_delay = 1000, -- delay before auto hiding, 0 = instant
       mappings = {
         next_tab       = { lhs = "<Tab>",   desc = "Next terminal tab" },
         prev_tab       = { lhs = "<S-Tab>", desc = "Previous terminal tab" },
         new_terminal   = { lhs = "+",       desc = "New user terminal" },
         close_terminal = { lhs = "X",       desc = "Close current terminal tab" },
         hide_panel     = { lhs = "q",       desc = "Hide terminal panel" },
       },
     },
      -- Optional configuration for external terminals (matches nvim-dap structure)
      external_terminal = nil,
      projx_lsp = {
        enabled = true,
      },
      lsp = {
        enabled = true, -- Enable builtin roslyn lsp
        set_fold_expr = false,
        preload_roslyn = true, -- Start loading roslyn before any buffer is opened
        roslynator_enabled = true, -- Automatically enable roslynator analyzer
        easy_dotnet_analyzer_enabled = true, -- Enable roslyn analyzer from easy-dotnet-server
        easy_dotnet_extension_enabled = false, -- Needs to be true for enhanced_rename and create_type_from_usage
        enhanced_rename = false, -- auto rename file when renaming class
        create_type_from_usage = false, -- code action for creating class from unresolved symbol in a separate file
        restart_roslyn_on_branch_change = false, -- Restart Roslyn when Git HEAD changes
        auto_refresh_codelens = true,
        suggest_updates = true, -- Periodically suggest roslyn-language-server updates
        analyzer_assemblies = {}, -- Any additional roslyn analyzers you might use like SonarAnalyzer.CSharp
        razor = {
          enabled = true,
          html = {
            enabled = true,
            cmd = nil, -- Auto-detect project node_modules/.bin/vscode-html-language-server, then PATH
            request_timeout = 5000,
          },
        },
        config = {},
      },
      debugger = {
        -- Path to custom coreclr DAP adapter
        -- When set, this fully overrides `engine`; easy-dotnet-server uses this binary as-is.
        -- When nil, easy-dotnet-server falls back to its own bundled debugger selected by `engine`.
        bin_path = nil,
        -- Which bundled debugger to use when `bin_path` is nil.
        --   "netcoredbg" (default) — Samsung netcoredbg
        --   "dncdbg"               — viewizard/dncdbg (a fork of netcoredbg with a richer set of features)
        engine = "netcoredbg",
        console = "integratedTerminal", -- Controls where the target app runs: "integratedTerminal" (Neovim buffer) or "externalTerminal" (OS window)
        apply_value_converters = true,
        auto_register_dap = true,
        mappings = {
          open_variable_viewer = { lhs = "T", desc = "open variable viewer" },
        },
      },
      ---@type TestRunnerOptions
      test_runner = {
        auto_start_testrunner = true,
        hide_legend = false,
        -- Set to true when using neotest to avoid duplicate signs and conflicting buffer keymaps. 
        neotest_integration = false,
        ---@type "split" | "vsplit" | "float" | "buf"
        viewmode = "float",
        ---@type number|nil
        vsplit_width = nil,
        ---@type string|nil "topleft" | "topright" 
        vsplit_pos = nil,
        icons = {
          passed = "",
          skipped = "",
          failed = "",
          success = "",
          reload = "",
          test = "",
          sln = "󰘐",
          project = "󰘐",
          dir = "",
          package = "",
          class = "",
          build_failed = "󰒡",
        },
        mappings = {
          run_test_from_buffer = { lhs = "<leader>r", desc = "run test from buffer" },
          run_all_tests_from_buffer = { lhs = "<leader>t", desc = "Run all tests in file" },
          get_build_errors = { lhs = "<leader>e", desc = "get build errors" },
          peek_stack_trace_from_buffer = { lhs = "<leader>p", desc = "peek stack trace from buffer" },
          debug_test_from_buffer = { lhs = "<leader>d", desc = "run test from buffer" },
          debug_test = { lhs = "<leader>d", desc = "debug test" },
          go_to_file = { lhs = "<leader>g", desc = "go to file" },
          run_all = { lhs = "<leader>R", desc = "run all tests" },
          run = { lhs = "<leader>r", desc = "run test" },
          peek_stacktrace = { lhs = "<leader>p", desc = "peek stacktrace of failed test" },
          expand = { lhs = "o", desc = "expand" },
          expand_node = { lhs = "E", desc = "expand node" },
          collapse_all = { lhs = "W", desc = "collapse all" },
          close = { lhs = "q", desc = "close testrunner" },
          refresh_testrunner = { lhs = "<C-r>", desc = "refresh testrunner" },
          cancel = { lhs = "<C-c>", desc = "cancel in-flight operation" },
          next_failure = { lhs = "]f", desc = "jump to next failing test" },
          prev_failure = { lhs = "[f", desc = "jump to previous failing test" },
        }
      },
      new = {
        project = {
          prefix = "sln" -- "sln" | "none"
        }
      },
      csproj_mappings = true,
      fsproj_mappings = true,
      auto_bootstrap_namespace = {
          --block_scoped, file_scoped
          type = "block_scoped",
          enabled = true,
          use_clipboard_json = {
            behavior = "prompt", --'auto' | 'prompt' | 'never',
            register = "+", -- which register to check
          },
      },
      server = {
          use_visual_studio = false, -- Set true for .NET Framework support on Windows
          ---@type nil | "Off" | "Critical" | "Error" | "Warning" | "Information" | "Verbose" | "All"
          log_level = nil,
      },
      -- choose which picker to use with the plugin
      -- possible values are "telescope" | "fzf" | "snacks" | "basic"
      -- if no picker is specified, the plugin will determine
      -- the available one automatically with this priority:
      --  snacks -> fzf -> telescope ->  basic
      picker = "snacks",
      notifications = {
        --Set this to false if you have configured lualine to avoid double logging
        handler = function(start_event)
          local spinner = require("easy-dotnet.ui-modules.spinner").new()
          spinner:start_spinner(function() return start_event.job.name end)
          ---@param finished_event JobEvent
          return function(finished_event)
            spinner:stop_spinner(finished_event.result.msg, finished_event.result.level)
          end
        end,
      },
      diagnostics = {
        default_severity = "error",
        setqflist = false,
      },
      outdated = {
        mappings = {
          upgrade = { lhs = "<leader>pu", desc = "upgrade package under cursor" },
          upgrade_all = { lhs = "<leader>pa", desc = "upgrade all outdated packages" },
        },
      },
    })

    -- Example command
    vim.api.nvim_create_user_command('Secrets', function()
      dotnet.secrets()
    end, {})

    -- Example keybinding
    vim.keymap.set("n", "<C-p>", function()
      vim.cmd("Dotnet run profile default")
    end)
  end
}

Lualine config

Simple components

local dotnet = require("easy-dotnet")

require("lualine").setup {
  sections = {
    -- ...
    lualine_a = { "mode", dotnet.lualine.jobs },
    -- Shows the default startup project and its launch profile (if any),
    -- pushed by the server whenever it changes.
    lualine_x = { dotnet.lualine.active_project },
    -- ...
  },
}

Dynamic run/stop component

A single component that adapts to running state:

  • Idle: ProjectName — left-click runs, right-click debugs
  • Running: ProjectName (green) — process alive, no debugger attached
  • Debugging: ProjectName (orange) — debugger attached

Uses three Codicons (requires Nerd Font): nf-cod-debug_start, nf-cod-debug_stop, nf-cod-debug.

local dotnet = require("easy-dotnet")

require("lualine").setup {
  sections = {
    lualine_x = {
      dotnet.lualine.jobs,
      {
        dotnet.lualine.run_status,
        color    = dotnet.lualine.run_status_color,
        on_click = dotnet.lualine.run_status_click,
      },
    },
  },
}

Advanced Patterns

For a more complete Visual Studio-style setup with external run/debug terminals, AppWrapper window reuse, the built-in terminal panel, lualine status, and ProjX project-file features, see Advanced Patterns.

Commands

Lua functions

Legend

  • <Picker> -> The configured picker (snacks, fzf, telescope, or the basic fallback)
  • <DArgs> -> Dotnet args (e.g --no-build, --configuration release). Always optional
  • <Picker Default> -> Picker selection that persists for future use
  • <sln> -> Solution file (in some cases .csproj or .fsproj is used as fallback if no .sln file exists)
Function Description
dotnet.run_profile() dotnet run --project <Picker> --launch-profile <Picker>
dotnet.run() dotnet run --project <Picker> <DArgs>
dotnet.run_default() dotnet run --project <Picker Default> <DArgs>
dotnet.run_profile_default() dotnet run --project <Picker Default> --launch-profile <Picker> <DArgs>
dotnet.debug_profile() Debug selected project with selected launch profile
dotnet.debug_attach() Attach the debugger to a running .NET process
dotnet.debug() Debug selected project
dotnet.debug_default() Debug the persisted default project
dotnet.debug_profile_default() Debug the persisted default project with selected launch profile
dotnet.build() dotnet build <Picker> <DArgs>
dotnet.build_solution() dotnet build <sln> <DArgs>
dotnet.build_solution_quickfix() dotnet build <sln> <DArgs> and opens build errors in the quickfix list
dotnet.build_quickfix() dotnet build <Picker> <DArgs> and opens build errors in the quickfix list
dotnet.build_default() dotnet build <Picker Default> <DArgs>
dotnet.build_default_quickfix() dotnet build <Picker Default> <DArgs> and opens build errors in the quickfix list
dotnet.pack() dotnet pack -c release
dotnet.push() dotnet pack and push
dotnet.test() dotnet test <Picker> <DArgs>
dotnet.test_solution() dotnet test <sln> <DArgs>
dotnet.test_default() dotnet test <Picker Default> <DArgs>
dotnet.watch() dotnet watch --project <Picker> <DArgs>
dotnet.watch_default() dotnet watch --project <Picker Default> <DArgs>
dotnet.restore() dotnet restore <sln> <Dargs>
dotnet.clean() dotnet clean <pick target>
dotnet.remove_package()
dotnet.add_package()
dotnet.testrunner() Shows or hides the testrunner
dotnet.is_dotnet_project() Returns true if a .csproj or .sln file is present in the current working directory or subfolders
dotnet.try_get_selected_solution() If a solution is selected, returns { basename: string, path: string }, otherwise nil
dotnet.new() Picker for creating a new template based on Dotnet new
dotnet.outdated() Runs Dotnet outdated in supported file types (.csproj, .fsproj, Directory.Packages.props, Packages.props, Directory.Build.props) and displays virtual text with the latest package versions.
dotnet.solution_select(path: string) Manually set a solution file for the current working directory. Useful for non-standard layouts where the solution file is outside the normal search depth or in a different location.
dotnet.solution_add() dotnet sln <sln> add <Picker>.
dotnet.solution_remove() dotnet sln <sln> remove <Picker>.
dotnet.ef_migrations_remove() Removes the last applied Entity Framework migration
dotnet.ef_migrations_add(name: string) Adds a new Entity Framework migration with the specified name.
dotnet.ef_migrations_list() Lists all applied Entity Framework migrations.
dotnet.ef_database_drop() Drops the database for the selected project.
dotnet.ef_database_update() Updates the database to the latest migration.
dotnet.ef_database_update_pick() Opens a picker to update the database to a selected migration.
dotnet.createfile(path) Spawns a picker for creating a new file based on a .NET new template
dotnet.secrets() Opens a picker for .NET user-secrets
dotnet.get_debug_dll() Returns the DLL from the bin/debug folder
dotnet.get_environment_variables(project_name, project_path, use_default_launch_profile: boolean) Returns the environment variables from the launchSetting.json file
dotnet.reset() Deletes all files persisted by easy-dotnet.nvim. Use this if unable to pick a different solution or project
dotnet.stop() Stops running workspace sessions
diagnostics.get_workspace_diagnostics() Get workspace diagnostics using configured default severity
diagnostics.get_workspace_diagnostics("error") Get workspace diagnostics for errors only
diagnostics.get_workspace_diagnostics("warning") Get workspace diagnostics for errors and warnings
local dotnet = require("easy-dotnet")
dotnet.lsp_start()
dotnet.lsp_restart()
dotnet.lsp_stop()
dotnet.get_environment_variables(project_name, project_path, use_default_launch_profile: boolean)
dotnet.is_dotnet_project()                                 
dotnet.try_get_selected_solution()                         
dotnet.get_debug_dll()                                     
dotnet.reset()                                             
dotnet.test()
dotnet.test_solution()
dotnet.test_default()
dotnet.testrunner()
dotnet.new()
dotnet.outdated()
dotnet.add_package()
dotnet.remove_package()
dotnet.solution_select(path: string)
dotnet.ef_migrations_remove()
dotnet.ef_migrations_add(name: string)
dotnet.ef_migrations_list()
dotnet.ef_database_drop()
dotnet.ef_database_update()
dotnet.ef_database_update_pick()
dotnet.createfile(path: string)                                    
dotnet.build()                           
dotnet.build_solution()
dotnet.build_solution_quickfix()
dotnet.build_quickfix()                 
dotnet.build_default()                 
dotnet.build_default_quickfix()       
dotnet.pack()                           
dotnet.push()                           
dotnet.run()
dotnet.run_profile_default()
dotnet.run_default()
dotnet.watch()
dotnet.watch_default()
dotnet.secrets()                                                          
dotnet.clean()                                                           
dotnet.restore()
dotnet.stop()

local diagnostics = require("easy-dotnet.actions.diagnostics")
diagnostics.get_workspace_diagnostics()
diagnostics.get_workspace_diagnostics("error") 
diagnostics.get_workspace_diagnostics("warning")

Vim commands

Run :Dotnet in nvim to list all commands
Dotnet lsp start
Dotnet lsp restart
Dotnet lsp stop
Dotnet testrunner
Dotnet run
Dotnet run default
Dotnet run profile
Dotnet run profile default
Dotnet debug
Dotnet debug attach
Dotnet debug default
Dotnet debug profile
Dotnet debug profile default
Dotnet watch
Dotnet watch default
Dotnet test
Dotnet test default
Dotnet test solution
Dotnet test run-settings set
Dotnet build
Dotnet build quickfix
Dotnet build solution
Dotnet build solution quickfix
Dotnet build default
Dotnet build default quickfix
Dotnet add package
Dotnet add package prerelease
Dotnet remove package
Dotnet pack
Dotnet push
Dotnet ef database update
Dotnet ef database update pick
Dotnet ef database drop
Dotnet ef migrations add
Dotnet ef migrations remove
Dotnet ef migrations list
Dotnet secrets
Dotnet restore
Dotnet clean
Dotnet new
Dotnet createfile <path>
Dotnet solution select [path]
Dotnet solution add
Dotnet solution remove
Dotnet outdated
Dotnet terminal toggle
Dotnet terminal show
Dotnet terminal hide
Dotnet diagnostic
Dotnet diagnostic errors
Dotnet diagnostic warnings
checkhealth easy-dotnet

-- Internal 
Dotnet reset -- Deletes all persisted files
Dotnet _server restart
Dotnet _server update
Dotnet _server stop
Dotnet _server start
Dotnet _server logdump
Dotnet _server logdump buildserver
Dotnet _server logdump stdout
Dotnet _server loglevel <level>

Roslyn LSP

Roslyn LSP support is enabled out of the box — no configuration required.
Just open a C#, Razor, or CSHTML file and the official .NET language server starts automatically. If roslyn-language-server is missing, easy-dotnet will install the global tool with dotnet tool install --global roslyn-language-server --prerelease. Existing installs are not updated automatically; run dotnet-easydotnet roslyn update when an update is suggested.

Razor support also uses VS Code's HTML language server for markup completions, hover, formatting, and related requests. easy-dotnet does not bundle or auto-install this Node dependency. Install it globally with npm install -g vscode-langservers-extracted, or per project with npm install --save-dev vscode-langservers-extracted. When vscode-html-language-server is available in project node_modules/.bin or on PATH, easy-dotnet starts and wires it up automatically. Run :checkhealth easy-dotnet for dependency status and install guidance. Set lsp.razor.html.cmd to override the command, or disable Razor with lsp.razor.enabled = false.

Set lsp.restart_roslyn_on_branch_change = true to restart Roslyn automatically after Git branch or detached HEAD changes. This can help when large file watcher bursts leave stale diagnostics, especially on Linux. It is disabled by default because it stops and starts the Roslyn client for the active root.

For more information check out

Test runner

Integrated test runner inspired by Rider IDE. Powered by easy-dotnet-server. The server supports both VSTest and Microsoft Testing Platform adapters.

testrunner float testrunner mid-run

If you experience issues with a test adapter please open an issue.

The test runner starts automatically when the server starts and runs discovery silently in the background, so the tree is ready before you open the window.

  • Test runner window
    • Float, split and vsplit view modes
    • Grouped by solution, project, namespace and class
    • Jump to next / previous failing test (]f / [f, dot-repeatable)
    • Passed, skipped, failed with live counts in header
    • Configurable icons and highlights
    • Collapsible hierarchy
    • Peek stacktrace with parsed highlighting (your code in yellow, framework code in grey)
    • Run and debug from solution, project, namespace, class or test
    • Cancel in-flight runs
    • Go to file
    • Aggregate test results across projects
  • Buffer integration
    • Gutter signs on test methods and classes
    • Run test or class from buffer
    • Debug test or class from buffer
    • Peek stacktrace from buffer
    • Flash on run and result

Keymaps

Key Action
o Expand / collapse node under cursor
E Expand all
W Collapse all
]f / [f Jump to next / previous failing test (dot-repeatable with .)
<leader>r Run test under cursor
<leader>R Run all tests
<leader>d Debug test under cursor
<leader>p Peek stacktrace of failed test
<leader>g Go to file
<leader>e Show build errors (failed build)
<C-r> Refresh test runner node
<C-c> Cancel in-flight operation
q Close window

Debugging tests

Use <leader>d on any node in the runner to start a debug session. Breakpoints must be set manually before starting the session.

Run settings

Use :Dotnet test run-settings set to pick a .runsettings file for a project. The server searches the workspace, stores the selection per project, and applies it when running tests through the VSTest adapter.

Running tests from buffer

Gutter signs appear automatically on test methods and classes once discovery has completed.

Key Action
<leader>r Run test or class under cursor
<leader>d Debug test or class under cursor
<leader>p Peek stacktrace of failed test

When a run is triggered from the buffer the method or class flashes to confirm it was picked up. When the run finishes it flashes again in the colour of the result.

test signs test flash confirm floating stacktrace from buffer

Neotest

easy-dotnet ships a built-in neotest adapter. It reuses the same test runner state that powers the built-in test runner window, so no extra discovery step is needed.

Requirements

Setup

1. Enable neotest_integration in easy-dotnet

This disables the built-in buffer signs and keymaps so neotest can manage them instead:

require("easy-dotnet").setup({
  test_runner = {
    neotest_integration = true,
  },
})

2. Register the adapter with neotest

require("neotest").setup({
  adapters = {
    require("easy-dotnet.neotest"),
  },
})

lazy.nvim example

{
  "nvim-neotest/neotest",
  dependencies = {
    "nvim-lua/nvim-nio",
    "nvim-lua/plenary.nvim",
    "antoinemadec/FixCursorHold.nvim",
    "nvim-treesitter/nvim-treesitter",
    "GustavEikaas/easy-dotnet.nvim",
  },
  config = function()
    require("neotest").setup({
      adapters = {
        require("easy-dotnet.neotest"),
      },
    })
  end,
},
-- Make sure easy-dotnet is configured with neotest_integration = true
{
  "GustavEikaas/easy-dotnet.nvim",
  config = function()
    require("easy-dotnet").setup({
      test_runner = {
        neotest_integration = true,
      },
    })
  end,
}

Workspace Diagnostics

Analyze your entire solution or individual projects for compilation errors and warnings using Roslyn diagnostics.

Commands

  • Dotnet diagnostic - Uses the configured default severity (errors by default)
  • Dotnet diagnostic errors - Shows only compilation errors
  • Dotnet diagnostic warnings - Shows both errors and warnings

Configuration

require("easy-dotnet").setup({
  diagnostics = {
    default_severity = "error",  -- "error" or "warning" (default: "error")
    setqflist = false,           -- Populate quickfix list automatically (default: false)
  },
})

Features

  • Solution/Project Selection: When multiple projects or solutions are available, you'll be prompted to select which one to analyze
  • Roslyn Integration: Uses the Roslyn Language Server Protocol for accurate diagnostics
  • Neovim Diagnostics Integration: Results are populated into Neovim's built-in diagnostic system, allowing you to:
    • Navigate between diagnostics using :lua vim.diagnostic.goto_next() and :lua vim.diagnostic.goto_prev()
    • View diagnostics in the quickfix list using :lua vim.diagnostic.setqflist() (or automatically if configured)
    • See inline diagnostic messages
    • View with trouble (requires trouble.nvim)
    • View with snacks diagnostic picker (requires snacks.nvim)

The diagnostics will appear in Neovim's diagnostic system, allowing you to navigate through them using your standard diagnostic keymaps. If you have trouble.nvim or snacks.nvim configured, the diagnostics will automatically be available in their respective interfaces.

Outdated

Run the command Dotnet outdated in one of the supported filetypes, virtual text with packages latest version will appear

Supports the following filetypes

  • *.csproj
  • *.fsproj
  • Directory.Packages.props
  • Packages.props
  • Directory.Build.props

After running Dotnet outdated, buffer-local keymaps are registered to upgrade packages in place (replaces the Version="..." attribute with the latest version and removes the virtual text). Both keymaps are configurable via outdated.mappings:

Default Action
<leader>pu Upgrade the outdated package under the cursor
<leader>pa Upgrade all outdated packages in the buffer

image

Add

Add package

Adding nuget packages are available using the :Dotnet add package command. This will allow you to browse for nuget packages.

image

Project mappings

Key mappings are available automatically within .csproj and .fsproj files

Add reference

<leader>ar -> Opens a picker for selecting which project reference to add

image

Package autocomplete

Package autocomplete in .csproj files is provided by the ProjX LSP, which is enabled by default. This triggers autocomplete for <PackageReference Include="<lsp-trigger>" Version="<lsp-trigger>" />.

Older configurations that manually register the easy-dotnet source with nvim-cmp or blink.cmp should be removed.

image

Note

Latest is added as a snippet to make it easier to select the latest version

image

.NET Framework

Basic support for .NET framework has been achieved. This means basic functionality like build/run/test/test-runner should work. If you find something not working feel free to file an issue.

Requirements

  • choco install nuget.commandline
  • Visual studio installation
  • options.server.use_visual_studio == true

New

Create dotnet templates as with dotnet new <templatename> Try it out by running Dotnet new

Project

Dotnet.new.showcase.mp4

Configuration file

If a configuration file is selected it will

  1. Create the configuration file and place it next to your solution file. (solution files and gitignore files are placed in cwd)

Integrating with nvim-tree

Adding the following configuration to your nvim-tree will allow for quickly creating contextual items. Today this creates C# enums, records, interfaces, and classes using Roslyn.

    require("nvim-tree").setup({
      on_attach = function(bufnr)
        local api = require('nvim-tree.api')

        local function opts(desc)
          return { desc = 'nvim-tree: ' .. desc, buffer = bufnr, noremap = true, silent = true, nowait = true }
        end

        vim.keymap.set('n', 'A', function()
          local node = api.tree.get_node_under_cursor()
          local path = node.type == "directory" and node.absolute_path or vim.fs.dirname(node.absolute_path)
          require("easy-dotnet").create_item(path)
        end, opts('Create item'))
      end
    })

Integrating with neo-tree

Adding the following configuration to your neo-tree will allow for quickly creating contextual items. Today this creates C# enums, records, interfaces, and classes using Roslyn.

      require("neo-tree").setup({
      ---...other options
        filesystem = {
          window = {
            mappings = {
              -- Make the mapping anything you want
              ["R"] = "easy",
            },
          },
          commands = {
            ["easy"] = function(state)
              local node = state.tree:get_node()
              local path = node.type == "directory" and node.path or vim.fs.dirname(node.path)
              require("easy-dotnet").create_item(path)
            end
          }
        },
      })

Integrating with mini files

Adding the following autocmd to your config will allow for quickly creating contextual items.

    vim.api.nvim_create_autocmd("User", {
      pattern = "MiniFilesBufferCreate",
      callback = function(args)
        local buf_id = args.data.buf_id
        vim.keymap.set("n", "<leader>a", function()
          local entry = require("mini.files").get_fs_entry()
          if entry == nil then
            vim.notify("No fd entry in mini files", vim.log.levels.WARN)
            return
          end
          local target_dir = entry.path
          if entry.fs_type == "file" then
            target_dir = vim.fn.fnamemodify(entry.path, ":h")
          end
          require("easy-dotnet").create_item(target_dir)
        end, { buffer = buf_id, desc = "Create item" })
      end,
    })

Integrating with snacks explorer

  {
    "folke/snacks.nvim",
    ---@type snacks.Config
    opts = {
      picker = {
        sources = {
          explorer = {
            win = {
              list = {
                keys = {
                  ["A"] = "explorer_add_dotnet",
                },
              },
            },
            actions = {
              explorer_add_dotnet = function(picker)
                local dir = picker:dir()
                local easydotnet = require("easy-dotnet")

                easydotnet.create_item(dir)
              end,
            },
          },
        },
      },
    },
  },

EntityFramework

Common EntityFramework commands have been added mainly to reduce the overhead of writing --project .. --startup-project ...

Requirements

This functionality relies on dotnet-ef tool, install using dotnet tool install --global dotnet-ef

Database

  • Dotnet ef database update
  • Dotnet ef database update pick --allows to pick which migration to apply
  • Dotnet ef database drop

Migrations

  • Dotnet ef migrations add <name>
  • Dotnet ef migrations remove
  • Dotnet ef migrations list

Language injections

Rider-like syntax highlighting for injected languages (sql, json and xml) based on comments.

Just add single-line comment like //language=json before string to start using this.

Showcase

Language injection with raw json string as an example.

image

Requirements

This functionality is based on Treesitter and parsers for sql, json and xml, so make sure you have these parsers installed: :TSInstall sql json xml.

Support matrix

Strings

string sql json xml
quoted
verbatim
raw
regexp quoted
regexp verbatim
regexp raw

Interpolated strings

interpolated string json xml
quoted
verbatim
raw
regexp quoted
regexp verbatim
regexp raw

Nvim-dap configuration

Check out debugging-setup for a full walkthrough of debugging setup

Troubleshooting

  • Update the plugin to latest version
  • Run :checkhealth easy-dotnet

Highlight groups

Click to see all highlight groups
Highlight group Default
EasyDotnetTestRunnerSolution Question
EasyDotnetTestRunnerProject Character
EasyDotnetTestRunnerTest Normal
EasyDotnetTestRunnerSubcase Conceal
EasyDotnetTestRunnerDir Directory
EasyDotnetTestRunnerPackage Include
EasyDotnetTestRunnerPassed DiagnosticOk
EasyDotnetTestRunnerFailed DiagnosticError
EasyDotnetTestRunnerInconclusive DiagnosticHint
EasyDotnetTestRunnerRunning DiagnosticWarn
EasyDotnetTestRunnerQueued Comment
EasyDotnetTestRunnerProbable Comment
EasyDotnetDebuggerFloatVariable Question
EasyDotnetDebuggerVirtualVariable Question
EasyDotnetDebuggerVirtualException DiagnosticError
EasyDotnetPeekTitle Custom (Accent)
EasyDotnetPeekBorder Custom (Accent)
EasyDotnetPeekMain Custom (Dark)
EasyDotnetPeekSideBar Custom (Darker)
EasyDotnetPeekWinBar Custom (Dark)
EasyDotnetPeekHint Custom (Italic Gray)
EasyDotnetPeekListActive Custom (Blue)

Local Development

Check out setup guide

Star History

Star History Chart

Contributors

About

A fully self-contained Neovim plugin for .NET development. Ships with integrated Roslyn LSP and netcoredbg debugging, offering fast, dependency-free setup and smooth build/test workflows for C# across .NET Core and .NET Framework.

Topics

Resources

License

Contributing

Security policy

Stars

Watchers

Forks

Contributors