Vim IDE Options

Here’s a comparison of the different Vim IDE options I’ve tested for PS development.

To summarize there are four options, and each have their faults:

  1. VSCode + vim plugin
  2. VSCode + neovim plugin
  3. Vim + CoC
  4. Vim + LanguageClient_NeoVim

If you have any suggestions on how to get things running smoothly or have a favorite working setup, please share, and I’ll update this post as a wiki.

VS Code

This is the easiest to setup. Just grab these PS extensions:

And then choose either of these Vim extensions. Unfortunately both of them have irritating issues compared to native vim:

Common Setup for Vim and Neovim

Plugin management:

The following instructions use vim-plug, but there are lots of other options.

Syntax highlighting:

Install purescript-vim by adding this line to the Plug section of ~/.vimrc:

Plug 'purescript-contrib/purescript-vim'

Then relaunch vim and run :PlugInstall

Language server:

npm install -g purescript-language-server

CoC.nvim

(with Neovim)

Installation

Install plugin by adding this line to the Plug section of ~/.vimrc:

Plug 'neoclide/coc.nvim', {'branch': 'release'}

Then relaunch vim and run :PlugInstall

There’s no PS coc extension, so we’re just reusing the VSCode extension. Set that up by launching vim and running:

:CocConfig

Then paste this PS setup config:

{  
  "languageserver": {
    "purescript": {                                
      "command": "purescript-language-server", 
      "args": ["--stdio", "--config {}"], 
      "filetypes": ["purescript"], 
      "rootPatterns": ["bower.json", "psc-package.json", "spago.dhall"] 
    } 
  }
} 

Then add your bindings. None are included by default. You can just start with coping the contents of this example to your ~/.vimrc.

Issues

  • gd “go to definition” usually only works after the second attempt
  • K to show documentation doesn’t render example snippets correctly:

LanguageClient_NeoVim + vimmer-ps + deoplete

  • LanguageClient_NeoVim lets you run queries on the symbol under your cursor such as:
    • :call LanguageClient#textDocument_definition to jump to definition
    • :call LanguageClient#textDocument_hover to show documentation
    • None of these commands have default keybindings, so you need to setup these mappings yourself.
  • vimmer-ps handles launching purescript-language-server and connecting it to LanguageClient_NeoVim. It also provides some basic bindings such as:
    • \ g to go to definition
    • \ h show documentation
    • These assume the default \ (backslash) for <leader>. You can confirm by checking if :let mapleader returns undefined.
  • deoplete provides the auto-completion popup
    • Cycle through suggestions with ctrl+n and ctrl+p. You can also use the arrow keys, but must then use ctrl+y to select the suggestion. These are the default binding and may be changed.

Installation

Add these to your plugin section in ~/.vimrc:

Plug 'autozimu/LanguageClient-neovim', {      
  \ 'branch': 'next',      
  \ 'do': 'bash install.sh',      
  \ }      
Plug 'sriharshachilakapati/vimmer-ps'      
Plug 'Shougo/deoplete.nvim', { 'do': ':UpdateRemotePlugins' }

Configure deoplete to launch at startup:

let g:deoplete#enable_at_startup = 1

Relaunch vim to reload ~/.vimrc (to detect newly listed plugins) and install these plugins by running:

:PlugInstall

Issues

  1. Does not work for spago projects. Only supports bower and psc-package projects. Leads to these issues with spago projects:

    • Only provides matches based on the other symbols in the current file. For example there’s clearly a lot of missing HTML tags here:
      image
    • Running :Pbuild produces this error:

      [LC] [Error] Problem running build: didn't find JSON output

    • Jump to definition doesn’t work for library functions. For example with cursor on the const symbol running :call LanguageClient#textDocument_definition() or using the mapped binding \ g doesn’t take me to the definition in prelude, but instead produces this error:

      [LC] Not found!

  2. The documentation popup is missing lots of valuable info. It would ideally show the same content as the pursuit entry, which is what VSCode does and what CoC attempts.
    image
    VSCode output:
    image

2 Likes

My setup, no idea if it’s better or worse:

Neovim configuration (~/.config/nvim/init.vim)

let g:ale_completion_enabled = 1

call plug#begin(stdpath('data') . '/plugged')
Plug 'purescript-contrib/purescript-vim'
Plug 'dense-analysis/ale'
call plug#end()

Purescript filetype plugin to be able to use purty: (~/.config/nvim/ftplugin/purescript.vim)

let b:ale_fixers = ['purty']

Globally installed node packages, most importantly purescript-language-server and purty:

$ npm list -g --depth=0
/home/joseph/.nvm/versions/node/v12.10.0/lib
├── npm@6.10.3
├── purescript@0.13.6
├── purescript-language-server@0.12.9
├── purty@6.2.0
└── spago@0.14.0

I originally used ALE to integrate with typescript, however it works well with purescript language server. On save the file is typechecked etc, and auto completion will show up automatically and hitting enter on the selected choice will insert the text. I just use :ALEDetail, :ALEGoToDefinitionInSplit and :ALEFix, although ALE can probably do more.

For documentation I use Pursuit, and just checking it looks like K for documentation doesn’t work with this configuration anyway

EDIT: Looks like ALE’s gotodefinition also doesn’t work for things like ‘const’, tbh I’ve mostly used ALE with typescript, only recently with purescript

2 Likes

@milesfrain Please add psc-ide-vim to the list. I’m using it currently and to be honest since the latest purs release I’m quite happy with it (I have to force a full reload sometimes with :Pload but it is not a huge problem now :slight_smile: ).
I’ve tried shortly the two other options for vim but they lack the possibility to import a symbol “on demand” as far as I know (this topic is explored here by @alextes I think in the case of coc) . Please correct me if I’m wrong. With psc-ide-vim I’m able to do this which is quite an important feature to me in practice.
I can share my setup if anyone is interested. Side note: psc-ide-vim is integrated through the syntastic plugin so I’m not sure if this setup works on the neovim.

EDIT:
One additional note about psc-ide-vim is that it communicates directly with purs ide server. This can be seen as a benefit - it has simpler background setup much simpler architecture (the architecture is rather not simpler per se as it implements the whole communication protocol in the vim language I think). It can be seen also as a “cons” because the rest of the plugins rely on purescript-language-server so probably community effort will go in this direction in general.

1 Like

psc-ide-vim seems like the most promising option so far. I still need to spend a bit more time getting omnicomplete configured nicely.

Please do.

Edit: I have some other questions about this plugin. Also looks like the repo could use some additional maintainers.

2 Likes

Please do.

Sure thing. Warning: it seems that discourse has crappy syntax highlighting for vim so I’m using # for comments (it should be ") to make these snippets readable.

In general I use .vimrc per project directory so I can setup different port for the purs-ide in it:

let g:psc_ide_server_port = 38218

Side note: I was able to force vim to use local .vimrc file by including this in my main .vimrc I think:

let b:thisdir=getcwd()
let b:vimrc=b:thisdir."/.vimrc"
if (filereadable(b:vimrc))
    execute "source ".b:vimrc
endif

If you know a better way please let me know.

Given that I have well specified port and I want to see what is going on on the purs ide side I can always start a server before launching vim in the debug mode and observe logs on the console because psc-ide-vim attaches to the existing server if it exists:

purs ide server --editor-mode --log-level all --port 38218

Let me start with basic plugin setup:

# 0 = off
# 1 = single module mode (via psc-ide rebuild)
# 2 = full build mode (via pulp)
autocmd FileType purescript let g:psc_ide_syntastic_mode = 1
# 1 = auto opened and auto closed when non
# 2 = no auto open but auto close
# 3 = auto open but not auto close
autocmd FileType purescript let g:syntastic_auto_loc_list = 2
# 0 = no fill, 1 = fill loc list
autocmd FileType purescript let g:syntastic_always_populate_loc_list = 1
autocmd FileType purescript let g:psc_ide_log_level = 0

Here are my bindings - I’m not sure how valuable they are for you but they list (all?) of possible functions:

# I'm using these on daily basis I think
autocmd FileType purescript nm <buffer> <silent> <leader>hA :<C-U>call PSCIDEaddTypeAnnotation(matchstr(getline(line(".")), '^\s*\zs\k\+\ze'))<CR>
autocmd FileType purescript nm <buffer> <silent> <leader>hs :<C-U>call PSCIDEapplySuggestion("")<CR>
autocmd FileType purescript nm <buffer> <silent> <leader>hi :<C-U>call PSCIDEimportIdentifier(PSCIDEgetKeyword())<CR>
autocmd FileType purescript nm <buffer> <silent> <leader>hg :<C-U>call PSCIDEgoToDefinition("", PSCIDEgetKeyword())<CR>
autocmd FileType purescript nm <buffer> <silent> <leader>hl :<C-U>call PSCIDEload(".", 0)<CR>
autocmd FileType purescript nm <buffer> <silent> <leader>ha :<C-U>call PSCIDEaddTypeAnnotation(getline("."))<CR>

# I'm not sure about this stuff - maybe I should drop this as I have not used this for a long time ;-)
autocmd FileType purescript nm <buffer> <silent> <leader>hC :<C-U>call PSCIDEcaseSplit("!")<CR>
autocmd FileType purescript nm <buffer> <silent> <leader>hf :<C-U>call PSCIDEaddClause("")<CR>
autocmd FileType purescript nm <buffer> <silent> <leader>hp :<C-U>call PSCIDEpursuit(PSCIDEgetKeyword())<CR>
autocmd FileType purescript nm <buffer> <silent> <leader>hq :<C-U>call PSCIDEaddImportQualifications()<CR>
autocmd FileType purescript nm <buffer> <silent> <leader>ht :<C-U>call PSCIDEtype(PSCIDEgetKeyword(), v:true)<CR>

EDIT: Please ignore these two suggestions below. @milesfrain provies better answers below - I mean :Papply! for suggestions and <C-x><C-o> for completion :slight_smile:

I’m finding this macro really practical:

let @q = ":lfirst\<CR>:Papply\<CR>:w\<CR>"

If for example I have like 20 import cleanup suggestions (like “unused import”) I can hit Escape then 25@q and psc-ide-vim repeats jumping and applying cleanup suggestion for me (this trick sometimes doesn’t work when I have signature suggestion as this fix could bring “unkown” symbols to the scope and break compilation…).

I still need to spend a bit more time getting omnicomplete configured nicely.

I’m not able to find any config related to omnicompetion and I’m not sure if it is default behavior of vim but I use <CTRL-N> and <CTRL-P> in general (like after imported module .) and it seems that it somewhat works…

I have additional macros related to PS but here we are talking about ide integration so I’m not including them for clarity… or should I?

3 Likes

Would Papply! ( added ! to apply all suggestions) accomplish the same thing?

It looks like Ctrl-n (:help i_ctrl-n note the i_ prefix for insert mode) does simple completion, where it just checks against all the strings it finds after your cursor position. It will search in a few additional locations, but doesn’t use any knowledge of the language.

This is an excellent talk that describes how all this stuff works in more detail (video and slides).

What you want is Ctrl-x Ctrl-o for “Omni completion”, which is built into vim and utilizes the language server. The results are a lot more useful.

Remaining issues for me are trying to tweak it to get behavior closer to what I’ve been spoiled with in VSCode. Rebinding to launch with Ctrl-Space is a start:

inoremap <C-Space> <C-x><C-o>

But I’d really like to setup fuzzy case-insensitive matching. For example, matching H.liftEffect by just typing H.leff.

1 Like

Would Papply! ( added ! to apply all suggestions) accomplish the same thing?

Holy Cow, it works like a charm! Thanks a lot!

This is an excellent talk that describes how all this stuff works in more detail (video 2 and slides).

Thanks for the material.

But I’d really like to setup fuzzy case-insensitive matching. For example, matching H.liftEffect by just typing H.leff.

Wow. This possibility sounds amazing.

@jy14898 thanks for sharing ale based solution. I’m curious about formatting integration - does purty work well in practice? Does it cover all language constructs? We have possibly quite a large codebase which we want to finally migrate to purty but I’m not sure if we can start doing this now :wink:

Anyways I want to start testing some way of auto formatting in my workflow but I don’t want mix ale with sytastic. @milesfrain would like to share any suggestions / solutions in this area?

1 Like

I only play around in purescript so can’t really comment on how well purty works for complicated real work, the main reason why I installed it is because ale suggested it as a ‘fixer’ when using :ALEFixSuggest.

From what I can tell it does a reasonable job, although I find it weird that it adds newlines between pattern matching (when not using case _ of, which here one should be)

data ADT = One | Two | Three

eval :: ADT -> Unit
eval One = unit
eval Two = unit
eval Three = unit

becomes

data ADT
  = One
  | Two
  | Three

eval :: ADT -> Unit
eval One = unit

eval Two = unit

eval Three = unit

It might be configurable, I haven’t looked. Once again my configuration was really something I just stumbled upon after using ale for other work and was little effort to get up and running for purescript

2 Likes

It’s not configurable sadly:( The pattern matching formatting annoys me as well, but I’m pretty sure if it was the other way around there would be another camp being annoyed:)

I’m currently using purty to format-on-save with vscode, and it works well overall, although there are a few bugs that can break your build or format things in undesirable ways. That pattern matching issue with the eval example is tracked here.

Another todo that would improve usability is to preserve newlines. Preserving intentional alignment spaces would be great too (in my opinion).

1 Like

Thanks a lot Guys for responses!

@jy14898 It seems that it is not intentional and @joneshf created a ticket for this himself :slight_smile:

@Adrielus, yeah I think that this is by design and I think this is a good choice :slight_smile:

@milesfrain Good to know, thanks.

Sounds cool.

I strongly agree with the Author here - I think that it is better to keep simple, constant indentation style :stuck_out_tongue:
Beside better diffing constant indentation also minimizes maintenance burden and entry point to the projects for the people without purty integration.
I want to migrate all my projects and our libs to drop unicode symbols for the same reason. It is unnecessary additional formatting problem for the new collaborators.

@milesfrain :Papply! is just instant operation and saves me a lot of time! Thanks once again!

This talk is really cool. What I’ve found watching it is that purs provides a way to generate tag files (thanks to @matthewleon):

$  purs docs --format ctags "src/**/*.purs" ".spago/**/src/**/*.purs"

which also can be done using spago directly:

$ spago docs -f ctags

Of course this is probably redundant with functions provided by psc-ide-vim but maybe there are some additional use cases of tags file. For sure they provide a common jumps interface (C-], C-i, C-o).

1 Like