DEV Community

Kacper Michta
Kacper Michta

Posted on • Updated on

PowerShell Development in Neovim

Overview

I have been developing several PowerShell projects over the last year, solely in Neovim. Just recently I rebuilt my neovim config, and found a reliable way to get it configured.

The configuration uses Lazy package manager, I use several plugins, but for brevity I'll include only the configuration required to get the LSP and treesitter functionality. I use WSL with the default ubuntu distro, and use tmux for all my terminal needs.

The Configuration

Pre-Requisites

During my configuration process I ran into several errors that pointed me to missing packages in my fresh WSL install. I remember having to install all of these iteratively until my neovim was working smoothly.

  • Neovim (do not use ap-get, the stable repo contains a fairly old version. I just used the pre-built archive method from the repo, remember to do the Path command too.)
  • Lua
  • LuaRocks
  • PowerShell
  • A C Compiler (I installed gcc)
  • Python 3 (also sudo apt install python-is-python3 this made python3 run when using the python command, one of my plugins needed it apparently)
  • npm

These are all that I can remember running into, however there may be others, if during installation and configuration you see errors, please read them carefully as that’s how I found the above dependencies. A lot of the time I saw ‘x’ command not found, or ‘x’ not installed.

Set up Lazy

Set up whatever package manager you prefer, the configuration below can be modified to fit, I went with Lazy. The documentation has a starter example for how you can set it up.

Treesitter

Great News! I found a treesitter parser for PowerShell that was updated and functional and not over 5 years old! This is the repo: tree-sitter-powershell

I have a folder for my custom parsers in ~/.config/nvim/tsparsers, this is where I cloned the repo. Then I have a file in my ~/.config/nvim/lua/<pathToMyPluginFiles> called treesitter.lua. Here is how I have it set up:

return {
    'nvim-treesitter/nvim-treesitter',
    built = ':TSUpdate',
    config = function()
        require('nvim-treesitter.configs').setup({

            ensure_installed = {
                "vimdoc", "lua", "bash"
            },

            sync_install = false,

            auto_install = true,

            indent = {
                enable = true
            },

            highlight = {
                enable = true,

                additional_vim_regex_highlighting = false,
            },
        })

        local treesitter_parser_config = require('nvim-treesitter.parsers').get_parser_configs()
        treesitter_parser_config.powershell = {
            install_info = {
                url = "~/.config/nvim/tsparsers/tree-sitter-powershell",
                files = { "src/parser.c", "src/scanner.c" },
                branch = "main",
                generate_requires_npm = false,
                requires_generate_from_grammar = false,
            },
            filetype = "ps1",
        }

    end
}
Enter fullscreen mode Exit fullscreen mode

After sourcing the file, and restarting nvim, I had my syntax highlighting. Please note the path in the ‘url’ value and make sure it matches the path where you have the cloned tree-sitter-powershell repo.

LSP

For the LSP, my configuration is almost the same as ThePrimeagen’s with some changes to the auto-complete behaviour, and the added custom server for PowerShell. PowerShell Editor Services actually have some documentation in their repo that is very helpful, it’s not very detailed but gives enough to get neovim configured. So download a package from the Releases (NOT a clone of the repo, made that mistake instantly before I read the docs). I extracted the Zip into a folder ~/.config/nvim/customLsp, it doesn’t really matter what the folder is as long as you have a record of the path. Then in my lazy plugins I have the lsp.lua file set up as below:

return {
    'neovim/nvim-lspconfig',
    dependencies = {
        "williamboman/mason.nvim",
        "williamboman/mason-lspconfig.nvim",
        "hrsh7th/cmp-nvim-lsp",
        "hrsh7th/cmp-buffer",
        "hrsh7th/cmp-path",
        "hrsh7th/cmp-cmdline",
        "hrsh7th/nvim-cmp",
        "L3MON4D3/LuaSnip",
        "saadparwaiz1/cmp_luasnip",
        "j-hui/fidget.nvim",
        -- "rafamadriz/friendly-snippets",
    },

    config = function()
        local cmp = require('cmp')
        local cmp_lsp = require('cmp_nvim_lsp')
        local capabilities = vim.tbl_deep_extend(
            'force',
            {},
            vim.lsp.protocol.make_client_capabilities(),
            cmp_lsp.default_capabilities())

        require('fidget').setup({})
        require('mason').setup()
        require('mason-lspconfig').setup({
            ensure_installed = {},
            handlers = {
                function(server_name)
                    require('lspconfig')[server_name].setup {
                        capabilities = capabilities
                    }
                end,

                powershell_es = function()
                    local lspconfig = require('lspconfig')
                    lspconfig.powershell_es.setup{
                        bundle_path = '~/.config/nvim/customLsp',
                        on_attach = function(client, bufnr)
                            vim.api.nvim_buf_set_option(bufnr, 'omnifunc', 'v:lua.vim.lsp.omnifunc')
                        end,
                        settings = {powershell = { codeFormatting = { Preset = 'OTBS'} } }
                    }
                end
            }
        })

        -- require("luasnip.loaders.from_vscode").lazy_load()

        local cmp_select = { behavior = cmp.SelectBehavior.Replace }

        cmp.setup({
            snippet = {
                expand = function(args)
                    require('luasnip').lsp_expand(args.body)
                end,
            },
            mapping = cmp.mapping.preset.insert({
                ['<C-p>'] = cmp.mapping(cmp.mapping.select_prev_item(cmp_select), {'i'}),
                ['<C-n>'] = cmp.mapping(cmp.mapping.select_next_item(cmp_select), {'i'}),
                ['<C-y>'] = cmp.mapping.confirm({ select = true }),
                ['<C-Space>'] = cmp.mapping.complete(),
            }),
            sources = cmp.config.sources({
                { name = 'nvim_lsp' },
                { name = 'luasnip' },
            }, {
                { name = 'buffer' },
            })
        })

        vim.diagnostic.config({
            float = {
                focusable = false,
                style = 'minimal',
                border = 'rounded',
                source = 'always',
                header = '',
                prefix = '',
            },
        })
    end
}
Enter fullscreen mode Exit fullscreen mode

Main thing to edit here is the ‘bundle_path’ which contains the path to the extracted PowerShell Editor Services. Give nvim a restart, note that it may take a little while to kick in, but after the first load I find it to be pretty much instant every time I go straight into a PS file.

Snippets

If you would like code snippets (like in VS Code), simply uncomment two lines:

  • "rafamadriz/friendly-snippets" in the dependencies
  • require("luasnip.loaders.from_vscode").lazy_load() under the mason-lspconfig setup.

This will give you the snippets for PowerShell which you can find in the repository for the friendly-snippets repo.

Additional Plugins


TLDR: Read the docs. Always read the docs.

Top comments (0)