I’ve been a Vim user for many years. And JavaScript support has never really worked for me. In 2020 I’ve decided to give it another try.

I’m going to use Vim 8.2, released in December 2019, which includes popup support and TypeScript syntax highlighting.

Language support

So, what do we want from Vim in terms of language support? This is what IDE provides:

Syntax highlighting (*.ts and *.tsx files)

Go to definition (open another file where a symbol is defined)

Refactoring (context-aware find and replace)

Code completion (open list of methods after access operator)

Method signature documentation (popup with arguments types)

Compile errors and ESLint errors (instant coding style and type checking)

Some of it comes with Vim OOTB, most can be added with plugins.

Plugin system

Vim has various plugin/package systems: Pathogen, Vundle, Plug, Dein. I think in 2020 we should use the native plugin/package introduced in Vim8.

To use it, we create ~/.vim/pack/anything/start folder, where anything could be any string that describes a set of plugins. I’ll be using typescript: ~/.vim/pack/typescript/start.

Inside this folder we unpack/clone vim plugins. If we use cloning, we wouldn’t be able to use plugin’s help, so we need to generate help tags for each plugin:

:helptags start/my-plugin/doc

Syntax highlighting

Many plugins provide syntax highlighting for TypeScript. Here’s a list of plugins:

But in Vim 8.2 no plugins are necessary, TS is supported OOTB. It is implemented by including the yats.vim plugin into Vim distribution.

Unfortunately, typescriptreact support is not perfect. It works in simple cases:

While if you provide function’s return type, it suddenly breaks:

The issue is known but there’s no solution. I’ve tried typescript-vim and vim-jsx-typescript plugins but those not very good either. So I went ahead and fixed this specific case. But there are many other issues. Regex syntax highlighting can only get you so far. Semantic highlighting is on the way.

Advanced language support: LSP

Vim script is powerful, but it’s a waste of time to reimplement all the advanced language features for each IDE or editor. In 2016 some smart people at Microsoft (of course, driven by a hidden desire to increase their market share) introduced a new idea: Language Server Protocol. It is a specification that allows decoupling editor UI from language semantics. So that you can implement a language server once for all the editors/IDEs and each editor/IDE will implement LSP support just once:

Any improvement in VSCode language support now benefits Vim users too (or, which is more likely, vice versa).

Unfortunately (or not), as it always happens, there’s no single language server, even for a single editor/language pair. In the Vim world, there are at least two contenders: coc.nvim and vim-lsp. Conquer of Completion looks like a powerful all-in-one solution, while vim-lsp looks more modular and simpler. I’ve decided to start with vim-lsp.

TypeScript language server

Before installing any vim lsp plugins, we need to install the language server itself.

The idea of LSP (a protocol which“embraces” open standards) most likely stems from tsserver, a typescript server implementation used in VSCode. Tsserver doesn’t use LSP to communicate with VSCode. So, as pointed in the relevant GitHub issue, tsserver is the “extend” part in the MS e̶v̶i̶l EEE strategy.

So all the TypeScript LSP solutions out there are based on tsserver:

I’m going to use the last one, which conveniently provides the npm package.

yarn global add typescript-language-server

We also need to add the yarn bin folder to $PATH. Alternatively, we can provide a full path when starting it.

yarn global bin # ~/.yarn/bin

echo ‘PATH=”$HOME/.yarn/bin:$PATH”’ > ~/.profile

Don’t forget to restart the user session (logout+login), so that the changes are applied.

vim-lsp and friends

To set up a solution based on vim-lsp we’d need several plugins. First, vim-lsp itself. It requires another plugin as a dependency: async.vim. It is needed so that vim doesn’t freeze while waiting for the LSP server to start.

cd ~/.vim/pack/typescript/start

git clone https://github.com/prabirshrestha/async.vim.git

git clone https://github.com/prabirshrestha/vim-lsp.git

Additional plugin needed for asynchronous completion:

git clone https://github.com/prabirshrestha/asyncomplete.vim.git

The asyncomplete.vim plugin doesn’t know where to get completion data from, so an additional plugin is required as a glue between asyncomplete.vim and vim-lsp:

git clone https://github.com/prabirshrestha/asyncomplete-lsp.vim

This plugin doesn’t connect vim-lsp with any specific language servers, it just provides utilities that we can call from our ~/.vimrc file. For gluing typescript server with vim plugin we can use this snippet:

Or we can just clone it as a plugin and modify to our needs:

git clone https://github.com/ryanolsonx/vim-lsp-typescript.git

Testing

After all the plugins are installed, we can test it out. Let’s clone a sample repo:

https://github.com/ryota-murakami/react-typescript-todo-example-2019

Install dependencies:

yarn install

Open some files:

vi src/App/index.tsx

highlighting working

Let’s test if lint issues are shown. Define an unused variable and type “:LspDocumentDiagnostics” command.

I’ve defined these keyboard shortcuts for this:

nmap <C-F8> :LspDocumentDiagnostics <CR>

autocmd FileType qf nnoremap <silent> <buffer> <CR> <CR> :cclose<CR>:lclose<CR>

It also works for compile errors:

Let’s test go to the definition: move the cursor to a symbol and press “gd”:

go to definition opens a new buffer

We can also have some file-scoped refactoring. Go to symbol and press F2:

Code completion is quite powerful:

Also, method signature documentation is available:

That’s basically it. Happy 2020!