LSP in Vim with the LSC Plugin

The emergence of the Language Server Protocol (LSP) and asynchronous job support has given rise to a myriad of code completion frameworks for the Vim and Neovim editors.

So many choices is both a benefit and curse; the benefit being that the savvy Vim user can craft a configuration specific to their need, the curse, especially for a novice, is the classic paradox of choice, where to start and what to choose?

This post will discuss my code completion setup, and more broadly my LSP setup, for Ruby and JavaScript filetypes using the LSC plugin.

Note, my choices may not necessarily suit you, but they do offer a starting point for users wishing to use LSP-based code completion, and other advanced language-aware actions, in Vim.

Feel free to refer to my dotfiles to view my particular LSP configuration.

Prerequisites

A modern version of Vim or Neovim that supports asynchronous job control is required. That means Vim 8 or any modern version of Neovim.

Preferably, a very recent version of Vim, version 8.1.2050 or Neovim 0.4.0 at the time of this writing, is strongly recommended since the LSP hover operation of the LSC plugin can use either Vim’s Popup Window or Neovim’s Floating Window functionality if available.

I also recommend installing and updating Vim, or Neovim, using Homebrew on macOS and Linux.

For example, to install Neovim using Brew:

brew install neovim

And to update Neovim:

brew upgrade neovim

Language Server Protocol (LSP)

Created by Microsoft, LSP was originally developed for the Visual Studio Code editor to decouple code editing and presentation from language-specific actions.

Language-specific actions, such as: auto-completion, hovering and navigation, that used to be the purview of heavyweight IDEs are now available to LSP-capable editors when associated with an appropriate LSP-compliant language server. LSP transfers the responsibility of such language-specific actions out of the editor to a vendor-agnostic language server that runs as a separate background process on the host.

As an open JSON-RPC-based standard, LSP now has multi-vendor support which has led to the development of numerous language clients and servers.

Vim Omni Completion

The 2006 release of Vim 7 saw the introduction a new form of completion, omni-completion. Omni-completion, orthogonal to existing forms of completion such as keyword or dictionary completion, is performed by the defined omnifunc and will usually offer filetype-specific completions.

Invoked by <Control-x><Control-o> , the intelligence of omni-completion depends on the sophistication of the omnifunc in use. Vim ships with set of rudimentary omni completion implementations. Users can install more advanced omni completion plugins, such as: Tern for JavaScript or clang_complete for C or C++, to improve the quality of such completions.

Nonetheless, there are a few issues with omni-completion:

completion is a synchronous operation, invoking omni-completion will block the editor until the operation is concluded

omni-completion plugins need to be coded and maintained specifically for Vim

However, with LSP-based completion, Vim can leverage and use the same language servers used by Visual Studio Code. One can be confident that the major language servers are actively developed and maintained. On the other hand, some omni-completion plugins, such as Tern for Vim, are no longer maintained.

Also, LSP-based solutions can leverage Vim & Neovim’s asynchronous job control to not block the editor whilst editing. Auto-completion, where completion candidates are displayed as one types, should be asynchronous otherwise editing may be painful due to stalls arising from the synchronous invocation of the omnifunc .

LSP offers more than just code completion; a full-featured language server can provide context-aware navigation, code refactoring and hovering tooltips, among other capabilities.

My LSP configuration, documented below, readily allows for both non-blocking auto-completion or manually invoked omni-completion using the same language servers in both cases. You choose which type of completion suits.

Vim Completion Frameworks and LSP-clients

A Vim completion framework is responsible for collating completion candidates and displaying those choices to the user. Advanced completion frameworks often operate, by default, in asynchronous auto-completion mode.

An LSP client on the other-hand is editor tooling that supports communication with a language server employing the Language Server Protocol. As of the time of this post, October 2019, neither Vim nor Neovim provide out-of-the-box support for LSP. However, a future version of Neovim will provide LSP support as noted in this pull request. In the meantime there are multiple Vim plugins that do provide LSP support.

Certain code completions frameworks also include direct LSP support whilst others delegate such duties to a separate LSP-client plugin.

Notable code completion and LSP-client plugins for Vim and Neovim:

YouCompleteMe, a monolithic code completion engine that predates LSP and asynchronous job control in Vim, both of which have now been incorporated. For many years this was the go to code completion plugin for Vim, nowadays there are lighter weight alternatives.

deoplete by Shogo, the first asynchronous code completion framework for Neovim, but which now also supports Vim. Completion candidates are gathered from deoplete-compatible sources; note, LSP-based results require integration with the LanguageClient-neovim plugin.

ncm2 by roxma, is another asynchronous code completion framework for Vim and Neovim. Conceptually similar to deoplete, completion candidates are gathered from ncm2-compatible sources; however, LSP-based candidates require integration with either the LanguageClient-neovim or ncm2-vim-lsp plugins.

Coc, a contemporary code completion framework for Neovim and Vim with inbuilt LSP support. Being Typescript-based allows Coc to leverage existing plugins used by Visual Studio Code. Somewhat against the norm, Coc operates its own configuration and extension system.

Completor, an asynchronous completion framework for Vim, and now also Neovim-compatible, with inbuilt LSP support. Leaner and more accessible than the plugins mentioned above.

LanguageClient-neovim, an LSP client commonly used in combination with an asynchronous completion framework such as deoplete or ncm2. The name implies Neovim-only support, but nowadays it also supports Vim.

vim-lsp, an LSP client written in Vimscript; unlike some Python-based clients listed above. This plugin is frequently used with the asyncomplete.vim plugin by the same author.

asyncomplete.vim, an ayschronous auto-completion framework written in Vimscript that supports both Vim and Neovim’s asynchronous job control APIs. Like deoplete and ncm2, LSP-based candidates require integration with an LSP-client plugin, this time with the vim-lsp plugin.

ALE, primarily an asynchronous linting and fixing plugin, but now extended to support LSP. Language servers can provide linting, hence the reason why ALE integrated LSP. ALE now includes LSP-based code completion in addition to other LSP functionality.

LSC by Nate Bosch, a performant LSP client, written in Vimscript, that supports LSP-based asynchronous auto-completion in both Vim and Neovim.

MUcomplete, a minimalist auto-completion plugin that leverages Vim’s existing completion infrastructure. This plugin does not support asynchronous operation.

Supertab, a tab completion plugin for Vim. This plugin simply maps the TAB key to Vim’s existing completion kinds.

VimCompletesMe, another tab completion plugin for Vim, similar to Supertab but simpler.

The LSC Plugin

After much trialling I chose LSC due to the following characteristics of the plugin:

LSC is a combination LSP-client and completion plugin that stands alone, there is no multi-plugin dance required

Implemented in pure Vimscript, this eases installation and avoids certain kinds of upgrade pain that may be experienced by Python-based alternatives

Compatible with both Vim and Neovim’s differing asynchronous job control APIs

Light in weight, less than three thousand lines of Vimscript; LSC augments Vim, it does not take over unlike certain other plugins

Excellent performance due to: asynchronous operation and use of performance optimizations such as incremental updates and debouncing

Pristine completions, candidates will only be sourced from the language server, there will be no mixing of candidates from other completion sources

Straightforward and concise ~/.vimrc -based configuration

Referenced language servers are installed and maintained externally, similar to how the ALE plugin uses external linters and fixers

Simple configuration option to select either asynchronous auto-completion or synchronous manual completion, once the omnifunc option is appropriately set

Auto-completion, if enabled, will only apply to filetypes that are associated with a language server

The find all references operation will populate the quickfix list; no custom UIs in contrast to certain other LSP plugins

The symbol hover operation, often mapped to K , can use either Vim’s popup window or Neovim’s floating window APIs if available, whilst double K will convert a popup/floating window into split-preview window if hover persistence is desired

Easy to use code actions operation, just select the listed number to carry out a specific language/framework action, for example wrapping a widget with another widget in Flutter

The simplicity LSC may not suit you, especially if you wish to combine completion candidates from multiple sources, say LSP-based candidates with keyword-in-file candidates. In my case I already have ctrl -based mappings that allow easy switching from one completion kind to another.

I augment LSC with the VimCompletesMe plugin since I like using the TAB key to scroll through completion candidates, among other benefits of that plugin.

LSC Installation & Configuration

If using the vim-plug plugin manager, add the following to your ~/.vimrc file:

Plug 'natebosch/vim-lsc' Plug 'ajh17/VimCompletesMe'

Then run :PlugInstall to install the plugins. Please change the notation appropriately if using an alternate plugin manager for Vim.

My Ruby and JavaScript LSC configuration:

let g:lsc_server_commands = { \ 'ruby' : { \ 'command' : 'solargraph stdio' , \ 'log_level' : -1 , \ 'suppress_stderr' : v : true , \ }, \ 'javascript' : { \ 'command' : 'typescript-language-server --stdio' , \ 'log_level' : -1 , \ 'suppress_stderr' : v : true , \ } \ } let g:lsc_auto_map = { \ 'GoToDefinition' : 'gd' , \ 'FindReferences' : 'gr' , \ 'Rename' : 'gR' , \ 'ShowHover' : 'K' , \ 'FindCodeActions' : 'ga' , \ 'Completion' : 'omnifunc' , \ } let g:lsc_enable_autocomplete = v : true let g:lsc_enable_diagnostics = v : false let g:lsc_reference_highlights = v : false let g:lsc_trace_level = 'off'

For a given filetype the LSC plugin will take care of launching, communicating and shutting down the named language server command . The specified language servers, listed above, will be discussed in greater detail in the following Ruby and JavaScript sections.

In addition to LSP-based auto-completion, I define and use the following four LSP actions:

LSP Action LSC Command Vim Mapping Go to definition for the symbol under the cursor GoToDefinition gd Find all references for the symbol under the cursor FindReferences gr Rename the symbol under the cursor and all references Rename gR Show hover tooltip for the symbol under the cursor ShowHover K Find code actions at the cursor location FindCodeActions ga

I strongly recommend the following completeopt setting when using auto-completion:

set completeopt = menu , menuone , noinsert , noselect

If you do not care for auto-completion but do wish to use LSP-based omni-completion, via <Control-x><Control-o> , then add the following to your ~/.vimrc :

let g:lsc_enable_autocomplete = v : false

I use the ALE plugin for linting and fixing, specifically StandardRB for Ruby and StandardJS for JavaScript, hence, I disable LSC diagnostics. However, if you wish to use LSP-based real-time linting, and your language server supports it, then specify let g:lsc_enable_diagnostics = v:true .

Lastly, I configure LSC to suppress all client/server messages; by default the LSC plugin is a little too chatty with regard to displaying all messages, even when they are not that useful.

Whilst debugging a recalcitrant language server please do enable LSC diagnostics.

Screenshot

The LSC plugin auto-completing JavaScript.

Ruby Language Server

Solargraph is the prime LSP-compliant language server for Ruby.

Install Solargraph with the following command:

gem install solargraph

For LSC to function please ensure solargraph is available in your $PATH .

The intelligence of Solargraph, when operating in a Gemfile -managed projects, can be improved by running following command in the project’s base directory:

solargraph bundle

Solargraph will now use the documentation from the project’s Gems for improved completions.

Similarly, the quality of Solargraph completions will be further enhanced for Rails projects by also copying this Gist into your project, I copied that file into the initializers directory.

Lastly, Solargraph is a still maturing technology, so please install updates when they become available.

JavaScript Language Server

Sourcegraph’s language server, javascript-typescript-langserver , is often noted as the language server to use for JavaScript and TypeScript. However, I have found it to be a little slow and somewhat buggy; for example the find all references action regularly failed with my React projects.

Interestingly, Visual Studio Code actually uses editor-provided TypeScript technologies, instead of Sourcegraph’s language server, for JavaScript and TypeScript language actions. Unfortunately, Microsoft’s stand-alone TypeScript server, tsserver , is not LSP-compliant, but it does provide the core capabilities needed by an LSP client.

Thankfully TypeFox does provide a shim between tsserver and the Language Server Protocol with the TypeScript Language Server package. This is the language server I recommend for JavaScript and TypeScript filetypes.

Install the TypeScript Language Server with the following command:

npm install -g typescript-language-server

For LSC to function please ensure typescript-language-server is available in your $PATH .

Language Servers

Available language servers for certain prevalent programming languages:

Note, I have not tested these language servers personally.

Conclusion

Which plugin(s) one ultimately uses is not that interesting, what is genuinely game-changing are the advanced editing capabilities that LSP provides.

This Language Server Protocol Vim screencast, by Greg Hurrell, is pertinent with respect to that point:

Hopefully this post provides enough detail to start your LSP journey in Vim.

Please enable JavaScript to view the comments powered by Disqus.