[ANN] ocaml-lsp preview

Dear OCaml Hackers,

I’m excited to announce ocaml-lsp. This project contains an implementation of an LSP server for the OCaml language. The current implementation piggy backs on the widely successful merlin tool to provide completion & type inference. In the future, we’d like to use all other essential tools such as ocamlformat, odoc, dune to provide more functionality in your editors.

For now, the project isn’t yet available on opam as we’re still polishing some rough edges in the release process. Nevertheless, I invite all brave souls who are ready to experiment to give this lsp server a try. Your feedback & contributions are most welcome :slight_smile:

31 Likes

Hello,

This project looks nice.

If I am an Emacs or Vi user, can I take advantage of an LSP server?

Or, is this only for some new editors like Atom or VScode?

Thanks,
F.

@UnixJunkie of course! That’s the whole point of this tooling.

For Vim you can choose between:

I am not an Emacs expert, but there is amazing LSP integration too:

3 Likes

Neovim 0.5.0 (now pre-released) has native LSP support as well: https://github.com/neovim/neovim/pull/11336

Not sure how well integrated is it going to be with various plugins (example)

5 Likes

What about ocp-indent ?

It’s not on the roadmap but I don’t see why support can’t be added. Feel free to add support for it if this is a feature you’d like.

I imagine the current plugins for ocp-indent would still work, and personally that should be enough for me.

Is there any relation with that:

Does it need it?

1 Like

Great news. I’m a little concerned though that ocaml-lsp vendors merlin. If they are indeed tightly coupled, shouldn’t they still be developed and released together? Or is there plan to de-couple them in the future?

We might decouple them further in the future but it’s not a huge concern at the moment. The issue with keeping the server inside merlin was that merlin is one of the few tools that we’d like to integrate with.

I understand the concern the concern that it’s more convenient the develop them together, but really that’s a wider concern at the level of the whole OCaml platform. This deserves it’s own discussion, but I personally think that solving this will require new package management tooling.

NeoVim 0.5.0 will also include the tree-sitter parser for syntax highlighting, which will allow way better coloring. And tree-sitter already has OCaml grammar, so implementing semantics-aware syntax highlighter will be easier. But I expect the support more or less ready for external contributions only in 0.6.0, sadly. Integrating the tool with something like GitHub Semantic (Haskell alert) will greatly improve OCaml experience on GitHub too, see the corresponding issue.

3 Likes

Wow I didn’t realize that’s what tree-sitter was. That’s awesome.

The next step for Semantic support is documented here, but I’m working on some improvements of the tree-sitter parser first.

4 Likes

For Emacs there is also eglot: https://github.com/joaotavora/eglot – As the README says, it’s quite minimalist compared to lsp-mode.

Here is an example with ALE and Neovim (tested with v0.3.8):

  • Install the Ale plugin. If your Vim has support for packages (Vim 8+ or Neovim) you can simply clone it in the correct subdir, no need for a plugin manager:
    git clone https://github.com/w0rp/ale.git .vim/pack/my-plugins/start/ale
  • Add this to your .vimrc:
" only invoke merlin to check for errors when                                                                                                                               
" exiting insert mode, not on each keystroke.                                                                                                                               
let g:ale_lint_on_text_changed="never"                                                                                                                                      
let g:ale_lint_on_insert_leave=1                                                                                                                                            
                                                                                                                                                                            
" enable ALE's internal completion if deoplete is not used                                                                                                                  
let g:ale_completion_enabled=1                                                                                                                                              
                                                                                                                                                                            
" only pop up completion when stopped typing for ~0.5s,                                                                                                                     
" to avoid distracting when completion is not needed                                                                                                                        
let g:ale_completion_delay=500                                                                                                                                              
                                                                                                                                                                            
" see ale-completion-completeopt-bug                                                                                                                                        
set completeopt=menu,menuone,preview,noselect,noinsert                                                                                                                      
                                                                                                                                                                            
if has('packages')                                                                                                                                                          
    packloadall                                                                                                                                                             
                                                                                                                                                                            
    " This should be part of ALE itself, like ols.vim                                                                                                                       
    call ale#linter#Define('ocaml',{                                                                                                                                        
                \ 'name':'ocaml-lsp',                                                                                                                                       
                \ 'lsp': 'stdio',                                                                                                                                           
                \ 'executable': 'ocamllsp',                                                                                                                                 
                \ 'command': '%e',                                                                                                                                          
                \ 'project_root': function('ale#handlers#ols#GetProjectRoot')                                                                                               
                \})                                                                                                                                                         
                                                                                                                                                                            
    " remap 'gd' like Merlin would                                                                                                                                          
    nmap <silent><buffer> gd  <Plug>(ale_go_to_definition_in_split)<CR>                                                                                                     
                                                                                                                                                                            
    " go back                                                                                                                                                               
    nnoremap <silent> <LocalLeader>gb <C-O>                                                                                                                                 
                                                                                                                                                                            
    " show list of file:line:col of references for symbol under cursor                                                                                                      
    nmap <silent><buffer> <LocalLeader>go :ALEFindReferences -relative<CR>                                                                                                  
                                                                                                                                                                            
    " Show documentation if available, and type                                                                                                                             
    nmap <silent><buffer> <LocalLeader>hh <Plug>(ale_hover)<CR>                                                                                                             
                                                                                                                                                                            
    " So I can type ,hh. More convenient than \hh.                                                                                                                          
    nmap , <LocalLeader>                                                                                                                                                    
    vmap , <LocalLeader>                                                                                                                                                    
endif 
6 Likes

Hi, Did you get this working when using Jane Street’s Base module.
I’m seeing warnings here. I’m using ALE in neovim. ocam 4.10, all new setup

open Base <<Unbound Module Base

let downcase_extension filename =
match String.rsplit2 filename ~on:’.’ with << Unbound value String.rsplit2
| None -> filename
| Some (base,ext) ->
base ^ “.” ^ String.lowercase ext

(call like this map downcase_extension [“ABC.TXT”;“abc.Txt”];;)

let map f l =
List.map ~f:f l << does not like ~f

type point2d = {x:float;y:float}

let magnitude {x=x_pos; y=y_pos} =
Float.sqrt(x_pos **. 2. +. y_pos **. 2.) << does not like **