Neovim for the Frontend

I've had the pleasure of working with different text editors as a developer for about 7 years now, ranging from lesser known editors such as BBEdit, to TextWranger to the once-popular Sublime Text, until currently the most popular VSCode.

Previously my experience with using a terminal based editor was limited to just making on-the-fly edits on a server that I would ssh into and that would be the extent of it. Probably the longest time spent in vanilla vim (or its variants) would be to be editing an nginx file, which was ofcourse an overwhelmingly difficult experience for a fledgling vimmer.

Hacker Man I've always imagined that hardcore haxx0rs were the only kind of people that used vim. You know, the kind always diving into mainframes and stealing highly classified information from some government database, while everybody else was still trying to figure how to quit.

Previously my aversion towards taking on vim as a full on text editor was mainly because of the lack of an efficient file navigator, and a solid project search/replace solution. My interest in living in the terminal as a developer pushed me little by little out of my comfort zone to seek out information to get me on my path towards my journey as a full on vimmer. Through this journey I've learned that while vim obviously doesn't make you a l337 hacker, with practice makes it a lot more of an efficient developer as you start editing code with whole lot less keystrokes.

With a little big of digging around, I've had a lot good Youtube Tutors, ChrisAtMachine and ThePrimeagen, a lot of good web resources such as amix's vim configuration, and even more fully opinionated dark configurations to help out ease into the building applications as a js/ts developer. Plugins such as NVim Telescope and CoCSearch has solved for my problems for switching over to becoming my primary text editor.

With a little bit of copy-pasting, here and there, and having tweaked my .vimrc over the past year, I've landed on a pretty solid configuration that works for me. The plugin manager used is Vim Plug

A few features that are worth noting in this configuration are

  • autocompletion
  • search and replace
  • find in project
  • fast text navigation to file
  • file tree visualization (using Nerd Tree)
  • Typescript, React, SCSS syntax highlighting
  • Prettier formatting on save
  • ESLint error/warning prompting

Below is what my current vim config looks like on iTerm. The window on the left is nvim, and the stacked two windows on the right are shell instances running.

Neovim Config

To be able to use this fully, you'll need to use Neovim 5.0 and above.

"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
" => DEFAULTS
"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
let mapleader=" "
set smartcase
set incsearch 
set ignorecase
set lazyredraw 
set magic
set showmatch 
set hlsearch

" Clear highlighting on escape in normal mode
nnoremap <esc> :noh<return><esc>
nnoremap <esc>^[ <esc>^[

set mat=2
set history=500
filetype plugin on
filetype indent on
set nobackup
set nowb
set noswapfile
set expandtab
set smarttab
set shiftwidth=2
set tabstop=2
syntax enable 
syntax on
set number

au FocusGained,BufEnter * checktime

" (useful for handling the permission-denied error) only for vim not neovim
command! W execute 'w !sudo tee % > /dev/null' <bar> edit!

let $LANG='en'
set ruler
set cmdheight=1
set hid

" Configure backspace so it acts as it should act
set backspace=eol,start,indent
set whichwrap+=<,>,h,l
set foldcolumn=1

" Enable 256 colors palette in Gnome Terminal
if $COLORTERM == 'gnome-terminal'
  set t_Co=256
endif

set background=dark

" Set extra options when running in GUI mode
if has("gui_running")
  set guioptions-=T
  set guioptions-=e
  set t_Co=256
  set guitablabel=%M\ %t
endif

" Set utf8 as standard encoding and en_US as the standard language
set encoding=utf8

" Use Unix as the standard file type
set ffs=unix,dos,mac

"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
" => PLUGINS
"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""

call plug#begin('~/.vim/plugged')
Plug 'preservim/nerdtree'
Plug 'Xuyuanp/nerdtree-git-plugin'
Plug 'PhilRunninger/nerdtree-visual-selection'
Plug 'ryanoasis/vim-devicons'

Plug 'gitgutter/vim'
Plug 'itchyny/lightline.vim'
Plug 'tpope/vim-fugitive'
Plug 'apzelos/blamer.nvim'
Plug 'pseewald/vim-anyfold'
Plug 'Yggdroot/indentLine'

Plug 'dense-analysis/ale'
Plug 'tpope/vim-commentary'
Plug 'sheerun/vim-polyglot'
Plug 'elzr/vim-json'
Plug 'SirVer/ultisnips'
Plug 'vim-markdown'
" Plug 'frazrepo/vim-rainbow'
Plug 'prettier/vim-prettier', {
    \ 'do': 'yarn install',
    \ 'for': ['javascript', 'typescript', 'css', 'less', 'scss', 'json', 'graphql', 'markdown', 'vue', 'yaml', 'html'] }

Plug 'HerringtonDarkholme/yats.vim'
Plug 'yuezk/vim-js'
Plug 'maxmellon/vim-jsx-pretty'

Plug 'tpope/vim-surround'
Plug 'neoclide/coc.nvim', {'branch': 'release'}

Plug 'morhetz/gruvbox'
Plug 'kyoz/purify', { 'rtp': 'vim' }

Plug 'nvim-lua/popup.nvim'
Plug 'nvim-lua/plenary.nvim'
Plug 'nvim-telescope/telescope.nvim'

Plug 'voldikss/vim-floaterm', {'branch': 'master'}

call plug#end()

""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
" => GENERAL TEXT FORMATTING
""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
" Remap VIM 0 to first non-blank character
map 0 ^

" Delete trailing white space on save, useful for some filetypes ;)
fun! CleanExtraSpaces()
let save_cursor = getpos(".")
let old_query = getreg('/')
silent! %s/\s\+$//e
call setpos('.', save_cursor)
call setreg('/', old_query)
endfun

if has("autocmd")
autocmd BufWritePre *.txt,*.js,*.py,*.wiki,*.sh,*.coffee :call CleanExtraSpaces()
endif 


""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
" => COLORSCHEME
""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
" colorscheme gruvbox
colorscheme purify

""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
" => VISUAL MODE
""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
" Visual mode pressing * or # searches for the current selection
" Super useful! From an idea by Michael Naumann
vnoremap <silent> * :<C-u>call VisualSelection('', '')<CR>/<C-R>=@/<CR><CR>
vnoremap <silent> # :<C-u>call VisualSelection('', '')<CR>?<C-R>=@/<CR><CR>

function! VisualSelection(direction, extra_filter) range
let l:saved_reg = @"
execute "normal! vgvy"

let l:pattern = escape(@", "\\/.*'$^~[]")
let l:pattern = substitute(l:pattern, "\n$", "", "")

if a:direction == 'gv'
  call CmdLine("Ack '" . l:pattern . "' " )
elseif a:direction == 'replace'
  call CmdLine("%s" . '/'. l:pattern . '/')
endif

let @/ = l:pattern
let @" = l:saved_reg
endfunction

""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
" => WINDOW NAVIGATION
""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
map <C-j> <C-W>j
map <C-k> <C-W>k
map <C-h> <C-W>h
map <C-l> <C-W>l

""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
" => SPLITS
""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
map <leader>v :vsplit<cr>
map <leader>x :split<cr>


""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
" => BUFFERS
""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
" Close the current buffer
map <leader>bd :Bclose<cr>:tabclose<cr>gT
" Close all the buffers
map <leader>ba :bufdo bd<cr>

""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
" => TABS
""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
" Useful mappings for managing tabs
map <leader>tn :tabnew<cr>

" Go to tab by <leader>{number 1 to 0}
noremap <leader>1 1gt
noremap <leader>2 2gt
noremap <leader>3 3gt
noremap <leader>4 4gt
noremap <leader>5 5gt
noremap <leader>6 6gt
noremap <leader>7 7gt
noremap <leader>8 8gt
noremap <leader>9 9gt
noremap <leader>0 :tablast<cr>

""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
" => VIM PRETTIER 
""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
let g:prettier#autoformat_config_present = 1
let g:prettier#autoformat = 1
let g:prettier#autoformat_config_files = ['.prettierrc','package.json']
autocmd BufWritePre *.js,*.jsx,*.mjs,*.ts,*.tsx,*.css,*.less,*.scss,*.json,*.graphql,*.md,*.vue PrettierAsync

command! -nargs=0 Prettier :call CocAction('runCommand', 'prettier.formatFile')
map <leader>p :Prettier<cr>

""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
" => VIM RAINBOW
""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
let g:rainbow_active = 1
let g:ctrlp_custom_ignore = 'node_modules\|DS_Store\|git'
let g:rainbow_load_separately = [
      \ [ '*' , [['(', ')'], ['\[', '\]'], ['{', '}']] ],
      \ [ '*.tex' , [['(', ')'], ['\[', '\]']] ],
      \ [ '*.cpp' , [['(', ')'], ['\[', '\]'], ['{', '}']] ],
      \ [ '*.{html,htm}' , [['(', ')'], ['\[', '\]'], ['{', '}'], ['<\a[^>]*>', '</[^>]*>']] ],
      \ ]

let g:rainbow_guifgs = ['RoyalBlue3', 'DarkOrange3', 'DarkOrchid3', 'FireBrick']
let g:rainbow_ctermfgs = ['lightblue', 'lightgreen', 'yellow', 'red', 'magenta']
set textwidth=79

""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
" => Copy to Clipboard 
""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
noremap <Leader>y "*y
noremap <Leader>y "+y

""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
" => Nerd Tree
""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
let NERDTreeShowHidden=1
let NERDTreeIgnore = ['\.pyc$', '__pycache__']
let NERDTreeAutoDeleteBuffer = 1
let NERDTreeQuitOnOpen=1
" let NERDTreeCustomOpenArgs={'file':{'where': 't'}}
let g:NERDTreeWinSize=35
" map <leader>nn :NERDTreeToggle<cr>
map <leader>nb :NERDTreeFromBookmark<Space>
" map <leader>nf :NERDTreeFind<cr>

map <leader>nn :NERDTreeToggle<cr>
map <leader>nf :NERDTreeFind<cr>

" Exit Vim if NERDTree is the only window left.
autocmd BufEnter * if tabpagenr('$') == 1 && winnr('$') == 1 && exists('b:NERDTree') && b:NERDTree.isTabTree() |
    \ quit | endif

" Open the existing NERDTree on each new tab.
autocmd BufWinEnter * silent NERDTreeMirror


""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
" => Floaterm
""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
map <leader>` :FloatermNew--height=0.9 --width=0.9 <cr> 
let g:floaterm_autoclose=1
let g:floaterm_rootmarkers=['.project', '.git', '.hg', '.svn', '.root', '.gitignore']
let g:floaterm_gitcommit='floaterm'

""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
" MOUSE SCROLLING FOR MAC 
""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
set mouse=a
map <ScrollWheelUp> <C-Y>
map <ScrollWheelDown> <C-E>

""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
" TELESCOPE
""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
" Emulate Control P with telescope
nnoremap <leader>ff <cmd>Telescope find_files<cr>
nnoremap <silent> <C-p> <cmd>Telescope find_files<cr>

" Emulate FlyGrep with telescope
nnoremap <leader>fg <cmd>Telescope live_grep<cr>
nnoremap <leader>/ <cmd>Telescope live_grep<cr>

" Emulate BufferList with telescope
nnoremap <leader>fb <cmd>Telescope buffers<cr>
nnoremap <silent> <Leader><Enter> <cmd>Telescope buffers<cr>

nnoremap <leader>fh <cmd>Telescope help_tags<cr>

""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
" INDENTATION (remapping in visual mode as well) 
""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
let g:indentLine_setColors = 0
let g:indentLine_char_list = ['|', '¦', '┆', '┊']
map <leader>i :IndentLinesToggle<cr>
:noremap > >>
:noremap < <<
:vnoremap < <gv
:vnoremap > >gv

""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
" COC - CONQUER OF COMPLETION 
""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
map <leader>f :CocSearch  **/*<Left><Left><Left><Left><Left>
let g:coc_global_extensions = [
  \ 'coc-snippets',
  \ 'coc-pairs',
  \ 'coc-tsserver',
  \ 'coc-eslint', 
  \ 'coc-prettier', 
  \ 'coc-json', 
  \ 'coc-rls',
  \ 'coc-lists',
  \ 'coc-html',
  \ 'coc-prisma'
  \ ]
" from readme
" if hidden is not set, TextEdit might fail.
set hidden " Some servers have issues with backup files, see #649 set nobackup set nowritebackup " Better display for messages set cmdheight=2 " You will have bad experience for diagnostic messages when it's default 4000.
set updatetime=300

" don't give |ins-completion-menu| messages.
set shortmess+=c

" always show signcolumns
set signcolumn=yes

" Use tab for trigger completion with characters ahead and navigate.
" Use command ':verbose imap <tab>' to make sure tab is not mapped by other plugin.
inoremap <silent><expr> <TAB>
      \ pumvisible() ? "\<C-n>" :
      \ <SID>check_back_space() ? "\<TAB>" :
      \ coc#refresh()
inoremap <expr><S-TAB> pumvisible() ? "\<C-p>" : "\<C-h>"

function! s:check_back_space() abort
  let col = col('.') - 1
  return !col || getline('.')[col - 1]  =~# '\s'
endfunction

" Use <c-space> to trigger completion.
if has('nvim')
  inoremap <silent><expr> <c-space> coc#refresh()
else
  inoremap <silent><expr> <c-@> coc#refresh()
endif

" Use <cr> to confirm completion, `<C-g>u` means break undo chain at current position.
" Coc only does snippet and additional edit on confirm.
inoremap <expr> <cr> pumvisible() ? "\<C-y>" : "\<C-g>u\<CR>"
" Or use `complete_info` if your vim support it, like:
" inoremap <expr> <cr> complete_info()["selected"] != "-1" ? "\<C-y>" : "\<C-g>u\<CR>"

" Use `[g` and `]g` to navigate diagnostics
nmap <silent> [g <Plug>(coc-diagnostic-prev)
nmap <silent> ]g <Plug>(coc-diagnostic-next)

" Remap keys for gotos
nmap <silent> gd <Plug>(coc-definition)
nmap <silent> gy <Plug>(coc-type-definition)
nmap <silent> gi <Plug>(coc-implementation)
nmap <silent> gr <Plug>(coc-references)

" Use K to show documentation in preview window
nnoremap <silent> K :call <SID>show_documentation()<CR>

function! s:show_documentation()
  if (index(['vim','help'], &filetype) >= 0)
    execute 'h '.expand('<cword>')
  else
    call CocAction('doHover')
  endif
endfunction

" Highlight symbol under cursor on CursorHold
autocmd CursorHold * silent call CocActionAsync('highlight')

" Remap for rename current word
nmap <F2> <Plug>(coc-rename)

""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
" => GIT GUTTER
""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
let g:gitgutter_enabled = 1

""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
" => LIGHT LINE
""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
set laststatus=2
if !has('gui_running')
  set t_Co=256
endif

" Replace filename component of Lightline statusline
let g:lightline = {
  \ 'component_function': {
  \   'filename': 'FilenameForLightline'
  \ }
\ }
 
" Show full path of filename
function! FilenameForLightline()
    return expand('%')
endfunction


" Git Fugitive Bindings
map <leader>g :Git<cr>
map <leader>gd :Gdiffsplit<cr>
map <leader>gl :Git log<cr>
map <leader>gc :Commits<cr>
map <leader>gl :FloatermNew --height=0.9 --width=0.9 lazygit<cr>

"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
" => VIM SURROUND
"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
vmap Si S(i_<esc>f)
au FileType mako vmap Si S"i${ _(<esc>2f"a) }<esc>

"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
" => Ale (syntax checker and linter)
"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
let g:ale_linters = {
      \   'javascript': ['eslint'],
      \   'python': ['flake8'],
      \   'go': ['go', 'golint', 'errcheck']
      \}
let g:ale_sign_error = '✘'
let g:ale_sign_warning = '⚠'
" let g:ale_fix_on_save = 1
"
highlight ALEError ctermbg=none cterm=underline
highlight ALEWarning ctermbg=none cterm=underline
highlight ALEErrorSign ctermbg=NONE ctermfg=red
highlight ALEWarningSign ctermbg=NONE ctermfg=yellow

"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
" => VIM JSX PRETTY ( FOR REACT )
"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
let g:vim_jsx_pretty_colorful_config = 1 " default 0
let g:vim_jsx_pretty_template_tags = ['jsx', 'js', 'html']

""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
" Blamer 
""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
let g:blamer_enabled = 1

""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
" FOLDING 
""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
autocmd Filetype * AnyFoldActivate
let g:anyfold_fold_comments=1
set foldlevel=0  " close all folds
set foldlevel=99 " Open all folds

""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
" => FAST EDIT AND RELOAD VIM FILES
""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
map <leader>e :e! ~/dot-files/init.vim<cr>
autocmd! bufwritepost ~/dot-files/init.vim source ~/dot-files/init.vim

Zigmund Sun Oo®