10 Jan 2021 |
neovim
plugin
lsp
What is language server protocol?
Development becomes a lot easier if all languages support features like autocomplete, linting, go-to-definition etc. within the editor of your choice. But this is a difficult task for editor devs. Each editor has to build support or integrate with tools that provide language support. Each of these tools behave in different ways making integration difficult. Language server protocol was defined by microsoft to put and end to this. LSP defines a standard protocol for tools(called language servers) to communicate with your editor.
Image source: Visual Studio Code
What is a language server client?
A language server client is the part of your editor that interacts with a language server - Sometimes implemented as plugins, sometimes built-in.
What language clients are available for vim?
- vim-lsp
- LanguageClient-neovim
- coc.nvim
- ale
- Built-in language server in neovim
We’ll see how to setup the built-in language server in neovim in this blog post.
First, setup a decent looking theme
Onebuddy is an atom one inspired theme. Install the theme with vim-plug
.
Add this to your vimrc:
Plug 'tjdevries/colorbuddy.vim'
Plug 'Th3Whit3Wolf/onebuddy', { 'branch': 'main' }
Enable the theme by adding this to vimrc:
set termguicolors
colorscheme onebuddy
What features should we expect?
- Show errors in line
- Show warnings in line
- Show errors/warnings in a list
- Format file
- Jump to definition
- See documentation
- Code actions - Actions like “Extract to function”, “Import this module” - Yes, you can get that in vim.
- Autocomplete
The first 7 are really easy to achieve. Let us handle autocomplete in a separate post.
How to setup built-in lsp client for neovim?
neovim’s lsp client is built in lua. The configuration is also in lua. Usable configurations for common language servers are collected in neovim/nvim-lspconfig.
Install the plugin with:
Plug 'neovim/nvim-lspconfig'
Let us create a new file in .vim
directory with all the lsp configurations. Name it lsp_config.lua
. Now in your vimrc
, add the following:
luafile ~/.vim/lsp_config.lua
In this file, add some configuration for common language servers
lspconfig = require'lspconfig'
lspconfig.pyls.setup{}
lspconfig.tsserver.setup{}
lspconfig.rust_analyzer.setup{}
That is it! You should start seeing diagnostics now. Ensure you have the right language servers installed.
For example, for python language server, do
pip3 install 'python-language-server[all]'
Add some signs to the gutter
It would be nice to see some symbols to the left of the code when there are errors.
Add these lines to your vimrc.
sign define LspDiagnosticsSignError text=🔴
sign define LspDiagnosticsSignWarning text=🟠
sign define LspDiagnosticsSignInformation text=🔵
sign define LspDiagnosticsSignHint text=🟢
Add some keybindings
We need some keybindings for actions like jump to definition. Add the following to your vimrc.
nnoremap <silent> gd <cmd>lua vim.lsp.buf.definition()<CR>
nnoremap <silent> gi <cmd>lua vim.lsp.buf.implementation()<CR>
nnoremap <silent> gr <cmd>lua vim.lsp.buf.references()<CR>
nnoremap <silent> gD <cmd>lua vim.lsp.buf.declaration()<CR>
nnoremap <silent> ge <cmd>lua vim.lsp.diagnostic.set_loclist()<CR>
nnoremap <silent> K <cmd>lua vim.lsp.buf.hover()<CR>
nnoremap <silent> <leader>f <cmd>lua vim.lsp.buf.formatting()<CR>
nnoremap <silent> <leader>rn <cmd>lua vim.lsp.buf.rename()<CR>
nnoremap <silent> <leader>a <cmd>lua vim.lsp.buf.code_action()<CR>
xmap <silent> <leader>a <cmd>lua vim.lsp.buf.range_code_action()<CR>
This will create the following mappings
Keybindings |
Action |
gd |
Go to definition |
gi |
Go to implementation |
gr |
See references |
gD |
Go to declaration |
ge |
Show errors in loclist |
gd |
Go to definition |
space f |
FOrmat file |
space r n |
Rename variable under cursor |
space a |
Code actions - On normal mode and visual selections |
Your end result should look like this:
27 Dec 2020 |
contemplation
I woke up today and was welcomed by a mountain of dishes in the sink. My first reaction was - “Screw that - I’ll just go back to bed”. So, after a couple more hours of sleep, I went back to find that the mountain hadn’t magically disappeared.
Now I had to tackle this problem. This is the strategy I followed
- Move everything out of the sink back into the kitchen.
- Take 5 utensils into the sink.
- Wash them.
- If there are more dishes left, goto 2.
That was it! The mountain of dishes were done.
This is how a typical Monday looks like too. You wake up - look at the mountin of tasks - try to do them all at once - fail miserably - wonder at the end of the week, what went wrong.
All you need to do is this - split the tasks into “5-utensil-size” tasks. Tackling smaller tasks is easy and far, far less intimidating.
I’ve started doing that now, and not digitally. Grabbed a notebook and started adding tasks in there and splitting them. The inspiration for this came from the Bullet journal method designed by Ryder Carroll.
13 Nov 2020 |
vim
neovim
plugin
FZF is the center-piece of my vim configuration. It is another excellent piece of software by Junegunn Choi.
fzf
is a command line fuzzy finder.
Now, that doesn’t sound like much. But the way it can integrate with your other tools will blow your mind. The repository for FZF is located here and has extensive documentation on how to use it. Follow these instructions to install it.
To see how it works, run this command:
find .
will list all the files in the directory recursively. You pipe that output into fzf
. Fuzzy search for the file of your choice and press enter. fzf
will output that filename and that is passed as an argument to vim
. Pretty neat, huh? fzf
also has a multiselect mode which can be activated with fzf -m
. You can select multiple entries by pressing TAB
.
fzf
has a plugin interface for using it in vim. Before installing and jumping into it, we need to lay some groundwork.
RipGrep
RipGrep is a faster replacement for grep written in rust. Output of some ripgrep commands will be piped into fzf to get our desired setup.
Now, go install ripgrep. The binary is called rg
.
Vim leader key
Vim can map a sequence of keys to an action. Leader is just another key in that sequence. For example,
map <leader>q :q<CR>
will map the sequence <leader>
+ q
to :q <enter>
. The default leader key is \
. You can change the value of leader with :mapleader
. I find space
to be the best key for leader. It is accessible with both fingers and it isn’t really doing anything else in the normal mode. So, go ahead and add this line in your vimrc
.
let mapleader = "\<Space>"
Install fzf.vim
We saw how to install plugins using vim-plug
in another post.
Add these 2 lines to your vimrc(Between plug#begin
and plug#end
).
Plug 'junegunn/fzf', { 'do': { -> fzf#install() } }
Plug 'junegunn/fzf.vim'
Now, let us setup some config and keybindings for useful commands.
" use ripgrep to search for files
let $FZF_DEFAULT_COMMAND = 'rg -l ""'
map <C-p> :Files<CR>
map <C-b> :Buffers<CR>
map <Leader><Leader> :Commands<CR>
map <Leader>/ :execute 'Rg ' . input('Rg/')<CR>
map <Leader>l :BLines<CR>
map <Leader>gf :GF?<CR>
This will create the following mappings
Keybindings |
Action |
Ctrl + p |
Fuzzy find files in the current working directory |
Ctrl + b |
Fuzzy find open buffers |
space space |
Fuzzy find from a list of all available commands |
space / |
Search for some text within the working directory. Enter the text after pressing space / |
space l |
Fuzzy search lines in the open file |
space g f |
Fuzzy search for files that got added/changed after the last commit |
You can find a list of commands here. See if anything tickles your fancy.
Searching for word under cursor
Something that I do pretty regularly is to search for the word under my cursor. This can be done by defining a vim function. I bind it to space G
(That is an uppercase G).
command! -bang -nargs=* RgExact
\ call fzf#vim#grep(
\ 'rg -F --column --line-number --no-heading --color=always --smart-case -- '.shellescape(<q-args>), 1,
\ fzf#vim#with_preview(), <bang>0)
nmap <Leader>G :execute 'RgExact ' . expand('<cword>') <Cr>
Example
07 Nov 2020 |
vim
neovim
plugin
If you use vim, you can use h,j,k,l
keys to navigate instead of the arrow keys. You won’t have to move your hands away from the home row, and you’ll end up saving a lot of time.
One of my seniors in college told me this about vim. It sounded so incredibly ridiculous at that time, that I decided to give it a shot.
For the first couple of years, my .vimrc
looked like this.
set nu
set cindent
syntax on
This enabled line numbers, indentation and added some syntax highligting to the code.
My real journey into vim started with this post titled “How I boosted my vim” by Vincent Driessen.
It introduced me to vim’s plugin ecosystem and I found out there is more to vim than h,j,k,l
.
10 years later, my (neo)vim looks like this:
Basic setup
Depending on if you are using vim or neovim, startup file is different. For vim, it is ~/.vimrc
.
For neovim, it is ~/.config/nvim/init.vim
. Add the following block into your startup file.
filetype plugin indent on
syntax on
set modeline
set encoding=utf-8
set termencoding=utf-8
set tabstop=4 " a tab is four spaces
set expandtab " Use spaces to insert a <TAB>
set backspace=indent,eol,start " allow backspacing over everything
" in insert mode
set autoindent " always set autoindenting on
set copyindent " copy the previous indentation
" on autoindenting
set number " always show line numbers
set shiftwidth=4 " number of spaces to use
" for autoindenting
set shiftround " use multiple of shiftwidth when
" indenting with '<' and '>'
set smarttab " insert tabs on the start
" of a line according to
" shiftwidth, not tabstop
set hlsearch " highlight search terms
set incsearch " show search matches as you type
set ignorecase " ignore case when searching
set smartcase " ignore case if search pattern is all
" lowercase, case-sensitive otherwise
set scrolloff=8 " Start scrolling at the last 8 lines
set history=1000 " remember more commands and search history
set undolevels=1000 " 1000 levels of undo
set title " change the terminal's title
set noerrorbells " don't beep
set wildmenu " Better command line completion
set wildmode=list:longest,full
set nobackup " no backup files
set nowritebackup
set noswapfile " no swap files
set nolazyredraw
set hidden
set cmdheight=2 " Better display for messages
set updatetime=300
set inccommand=nosplit " Preview for search and replace
" (neovim only)
set pastetoggle=<F2> " F2 Toggles set paste
au InsertLeave * set nopaste " Exit paste mode when leaving insert mode
" No shift for :
nnoremap ; :
" Navigate wrapped lines easily
nnoremap j gj
nnoremap k gk
" Remove highlight on ,/
nmap <silent> ,/ :nohlsearch<CR>
" Correct typos
aug FixTypos
:command! WQ wq
:command! Wq wq
:command! QA qa
:command! Qa qa
:command! W w
:command! Q q
aug end
If you would like to know more details about any of these settings, you can do a :h <command>
. For eg:- :h autoindent
Plugins
vim-plug
by Junegunn Choi is a good candidate for a plugin manager.
Install vim-plug
.
For vim in osx, linux
curl -fLo ~/.vim/autoload/plug.vim --create-dirs \
https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim
For neovim in osx, linux
sh -c 'curl -fLo "${XDG_DATA_HOME:-$HOME/.local/share}"/nvim/site/autoload/plug.vim --create-dirs \
https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim'
For others, see here. When you start vim next, the vim-plug
will be installed.
Now, let us install a plugin called lightline
. The URL for lightline
on github is https://github.com/itchyny/lightline.vim. You need to use the url slug for the plugin to install it; like shown below.
call plug#begin('~/.vim/plugged')
Plug 'itchyny/lightline.vim'
call plug#end()
To install the plugin, quit vim and run vim +PlugInstall
. When you restart vim, you can see a better status line(the line the bottom of vim).
vim-plug
provides some commands to manage plugins. Here are some I use often.
PlugInstall
- Installs all the listed plugins in vimrc
.
PlugUpdate
- Updates all the listed plugins in vimrc
.
PlugClean
- Removes unlisted plugins.
31 Oct 2020 |
python
functional
Say, you have to open and process a file in python. You can do that using a function like this
def open_and_process(filename):
file = open(filename)
process(file)
file.close() # This is important
If an exception occurs in the process
function, file.close()
won’t be executed.
This pattern is the same for other types of resources too:
- Acquire resource
- Use resource
- Release resource
If something fails during the use phase, the resource won’t be released cleanly.
There are a couple of ways to solve this.
- Use
try...finally
def open_and_process(filename):
try:
file = open(filename)
process(file)
finally:
file.close()
- Python Context Managers.
You would have seen code in python that looks like this.
with open(filename) as word_file:
words = word_file.readlines()
A Context Manager is a class which implements __enter__
and __exit__
functions
class context:
def __init__(self):
print("initializing")
def __enter__(self):
print("entering block")
return(10)
def __exit__(self, _type, _value, _trace):
print("exiting block")
with context() as val:
print("block")
print(val) # This is the return value of __enter__ function
# Output
> initializing
> entering block
> block
> 10
> exiting block
When entering the block, the __enter__
method is called. When exiting the block, the __exit__
method is called. The cleanup(like closing the file) can be done in the __exit__
method.
Luckily, python provides a contextlib class so that you won’t have to write a class with __enter__
and __exit__
methods. In the example below, code before yield
will be executed before entering the block. Code after yield
will be executed on exiting the block.
from contextlib import contextmanager
@contextmanager
def context():
print("entering")
try:
yield 10
finally:
print("exiting")
with context() as var:
print(var) # this is the yielded value
print("inside block")
# output
> entering
> 10
> inside block
> exiting
An interesting usecase for this is in mocking classes in tests.
Here, we are building a calculator which calls an AdderService
to perform addition. It calls the AdderService
using an AdderServiceClient
class.
class AdderServiceClient:
def add(self, a, b):
# This is an external API call. Throwing an exception to
# indicate failure when called during tests
raise Exception("This calls an external service "
"and should not be used in tests")
class Calculator():
def __init__(self, adder):
self.adder = adder
def add(self, a, b):
return self.adder.add(a, b)
def test_calculator_adds():
calc = Calculator(AdderServiceClient())
assert calc.add(1, 2) == 3
# This test fails with
> Exception: This calls an external service and should not be used in tests
Now, bring in some contextmanager magic here.
from contextlib import contextmanager
@contextmanager
def mocked_adder_service_client():
# Define a function that can add without
# calling an external service.
# The _ here is a placeholder for self
def mocked_add(_, a, b):
return a + b
# Take a backup of the add in AdderServiceClient
real_add = AdderServiceClient.add
# Replace the add in AdderServiceClient with our mock function
AdderServiceClient.add = mocked_add
yield
# Cleanup. Put back the real add
AdderServiceClient.add = real_add
def test_calculator_adds():
calc = Calculator(AdderServiceClient())
with mocked_adder_service_client():
assert calc.add(1, 2) == 3
We have a passing test case now.