PowerShell Development in Neovim
Tags: powershell, neovim, wsl
Published: 2024-06-27 07:54:51

## 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](https://github.com/neovim/neovim/blob/master/INSTALL.md#linux), remember to do the Path command too.)
- [Lua](https://www.lua.org/download.html)
- [LuaRocks](https://github.com/luarocks/luarocks/wiki/Installation-instructions-for-Unix)
- [PowerShell](https://learn.microsoft.com/en-us/powershell/scripting/install/install-ubuntu?view=powershell-7.4)
- 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](https://docs.npmjs.com/downloading-and-installing-node-js-and-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.](http://www.lazyvim.org/configuration/lazy.nvim)

### 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](https://github.com/airbus-cert/tree-sitter-powershell/tree/main)

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:
```lua
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
}
```
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](https://github.com/ThePrimeagen/init.lua/blob/master/lua/theprimeagen/lazy/lsp.lua) 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](https://github.com/PowerShell/PowerShellEditorServices/blob/main/docs/guide/getting_started.md) 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:
```lua
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
}
```
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.](https://github.com/rafamadriz/friendly-snippets/blob/main/snippets/PowerShell.json)

### Additional Plugins
- Git integration: [fugitive](https://github.com/tpope/vim-fugitive)
- Quick file nav: [telescope](https://github.com/nvim-telescope/telescope.nvim)
- Tree-based undo history: [undotree](https://github.com/mbbill/undotree)

---
TLDR: Read the docs. Always read the docs.