Boost Your Productivity with Terraform Autocompletion in Vim
RoseSecurity
Posted on January 2, 2024
Pure Vim
Every so often, when the struggle of jumping between IDE windows, tabs, and extensions catches up to me, I step back and settle into a terminal window with a beautiful, yet simple Vim configuration for my daily DevOps development. In this guide, I will illustrate the process of configuring autocompletion for Terraform code in Vim. This walkthrough uses Vim Plug for plugin installations, so ensure this is downloaded before continuing (or use another plugin manager).
Configuring Terraform's Language Server
What is an LSP?
For us to take advantage of Terraform's auto-completion features, we need to make use of a Language Server Protocol (LSP), which defines the protocol used between an editor or IDE and a language server. This provides language features like auto-complete, go to the definition, find all references, etc. By harnessing a few plugins, we can streamline our Terraform development efforts in pure Vim.
Installing the Terraform LSP
To install the Terraform LSP, we can either utilize Homebrew to download the releases or reference Hashicorp's Official Packaging Guide for further installation instructions. If you choose to use Homebrew, here is the command!
brew install hashicorp/tap/terraform-ls
Installing Vim Plugins
Now that we have the Terraform LSP installed, we need another LSP for language-specific autocompletion. This is where Conquer of Completion (CoC) shines. Before installing CoC, you will need to make sure that you have the necessary dependencies on your system, namely Vim 8 and above and node version 14.14 and above.
To install CoC, simply include the following line in your vimrc and give the command :PlugInstall
Plug 'neoclide/coc.nvim'
For CoC's autocompletion to work, there are numerous configurations. While discussing all of them is beyond this guide's scope, my preferred vimrc is provided below:
"CoC Settings
" Use tab for trigger completion with characters ahead and navigate.
" NOTE: Use command ':verbose imap <tab>' to make sure tab is not mapped by
" other plugin before putting this into your config.
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
" Make <CR> to accept selected completion item or notify coc.nvim to format
" <C-g>u breaks current undo, please make your own choice.
inoremap <silent><expr> <CR> coc#pum#visible() ? coc#pum#confirm()\:"\<C-g>u\<CR>\<c-r>=coc#on_enter()\<CR>""Ultisnips Settings
let g:UltiSnipsExpandTrigger="<tab>"
let g:UltiSnipsJumpForwardTrigger="<c-b>"
let g:UltiSnipsJumpBackwardTrigger="<c-z>"
" If you want :UltiSnipsEdit to split your window.
let g:UltiSnipsEditSplit="vertical""coc-snippets Settings
"inoremap <silent><expr> <TAB>
" \ coc#pum#visible() ? coc#_select_confirm() :
"\ coc#expandableOrJumpable() ? "\<C-r>=coc#rpc#request('doKeymap', ['snippets-expand-jump',''])\<CR>" :
" \ CheckBackspace() ? "\<TAB>" :
"\ coc#refresh()"
"function! CheckBackspace() abort
" let col = col('.') - 1
"return!col || getline('.')[col - 1] =~# '\s'"endfunction
""let g:coc_snippet_next = '<tab>'
To edit CoC's settings after installation, enter Vim and type the command :CoCConfig or configure ~/.vim/coc-settings.json as follows:
Now that our plugins are installed, let's add the following to create our final vimrc:
""""""""""""""""""""""""""""""" => Terraform
""""""""""""""""""""""""""""""
" Set filetype to HCL for files with .hcl extension
autocmd BufRead,BufNewFile *.hcl set filetype=hcl
" Set filetype to Terraform for files with .tf and .tfvars extensions
autocmd BufRead,BufNewFile *.tf,*.tfvars set filetype=terraform
" Set filetype to JSON for files with .tfstate and .tfstate.backup extensions
autocmd BufRead,BufNewFile *.tfstate,*.tfstate.backup set filetype=json
" Allow vim-terraform to automatically format *.tf and *.tfvars files with terraform fmt
let g:terraform_fmt_on_save=1
" Allow vim-terraform to align settings automatically with Tabularize
let g:terraform_align=1
" Install plugins
call plug#begin('~/.vim/plugged')
Plug 'neoclide/coc.nvim', {'branch': 'release'}
Plug 'hashivim/vim-terraform'
Plug 'vim-syntastic/syntastic'
Plug 'juliosueiras/vim-terraform-completion'
call plug#end()
" Syntastic Config
set statusline+=%#warningmsg#
set statusline+=%{SyntasticStatuslineFlag()}set statusline+=%*let g:syntastic_always_populate_loc_list = 1
let g:syntastic_auto_loc_list = 1
let g:syntastic_check_on_open = 1
let g:syntastic_check_on_wq = 0
" (Optional)Remove Info(Preview) window
set completeopt-=preview
"(Optional)Hide Info(Preview) window after completions
autocmd CursorMovedI *if pumvisible()== 0|pclose|endif
autocmd InsertLeave *if pumvisible()== 0|pclose|endif
" (Optional) Enable terraform plan to be include in filter
let g:syntastic_terraform_tffilter_plan = 1
"(Optional) Default: 0, enable(1)/disable(0) plugin's keymapping
let g:terraform_completion_keys = 1
" (Optional) Default: 1, enable(1)/disable(0) terraform module registry completion
let g:terraform_registry_module_completion = 0
"CoC Settings
" Use tab for trigger completion with characters ahead and navigate.
" NOTE: Use command ':verbose imap <tab>' to make sure tab is not mapped by
" other plugin before putting this into your config.
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
" Make <CR> to accept selected completion item or notify coc.nvim to format
" <C-g>u breaks current undo, please make your own choice.
inoremap <silent><expr> <CR> coc#pum#visible() ? coc#pum#confirm()
\: "\<C-g>u\<CR>\<c-r>=coc#on_enter()\<CR>"
"Ultisnips Settings
let g:UltiSnipsExpandTrigger="<tab>"
let g:UltiSnipsJumpForwardTrigger="<c-b>"
let g:UltiSnipsJumpBackwardTrigger="<c-z>"
" If you want :UltiSnipsEdit to split your window.
let g:UltiSnipsEditSplit="vertical"
"coc-snippets Settings
"inoremap <silent><expr> <TAB>
" \ coc#pum#visible() ? coc#_select_confirm() :
" \ coc#expandableOrJumpable() ? "\<C-r>=coc#rpc#request('doKeymap', ['snippets-expand-jump',''])\<CR>" :
" \ CheckBackspace() ? "\<TAB>" :
" \ coc#refresh()
"
"function! CheckBackspace() abort
" let col = col('.') - 1
" return !col || getline('.')[col - 1] =~# '\s'
"endfunction
"
"let g:coc_snippet_next = '<tab>'
Test it Out
Conclusion
I hope that this guide was an informative journey to improving your DevOps development workflow. If you have any questions, enjoyed the content, or would like to check out more of my work, feel free to drop a comment below and visit my GitHub.
I write Infrastructure-as-Code and automation to help startups and established companies quickly and securely build their cloud environments, but when I'm not building cloud networks, you can catch me conducting security research to identify modern tactics, techniques, and procedures used by malicious actors. If you enjoy my code, blogs, or tools, feel free to reach out and connect!