“What is a computer, Dad?” I asked this question 25 years ago as a 10-year-old boy. “It’s something like your FX-3600P CASIO calculator, but a bit more advanced!” my dad replied.

It was a well-defined answer, especially for a theoretical physicist working with a Commodore 64 at the university to solve complex quantum physics problems. At that time, the scientific calculator that I had inherited from my dad was the most advanced technology I could program.

This definition, coupled with the DOS-based PC I bought five years later, changed my perspective on computing systems forever. Even now, after 25 years — when GUI-based operating systems have become the dominant technology — any computing system still feels to me like a more advanced calculator.

I believe this is a familiar story for many of us as embedded system engineers. We navigate through Bash, synchronize with the kernel’s heartbeat, and connect via the serial port. Notifications from emails and social media, and colorful UIs send us into a panic. All we need is a black screen with lines of code to do our jobs.

This is a short tutorial for such engineers. For those who still think a terminal is enough to do their job. For those who believe a computer with 16KB of RAM and 70KB of disk is enough to launch a project and still be operational after 48 years of continuous working while escaping from our “Pale Blue Dot” with the speed of 17 km/sec (see Voyager program). For those who don’t require wide 4K UHD double monitors with a Core-i9 and 32GB RAM stack and an AI-based IDE to do their tasks.

Don’t get me wrong, I’m not a Luddite or a Technophobe. I just think that less is usually better — until we need more, As Exupéry once said:

Perfection is achieved, not when there is nothing more to add, but when there is nothing left to take away.

As we do in our profession as embedded engineers!

I therefore want to use a development environment that runs in a terminal (thus vi), but still offers enough features to work efficiently (thus vi-improved, i.e. vim).

Enough storytelling, let’s get started!

Installing prerequisites

No matter what distro you are using, these packages should be available from your distro.

For Ubuntu/Debian users:

$ sudo  apt  install  vim ctags

and Fedora:

$ sudo dnf install vim ctags

Installing Plugin manager

Most of the features that make vim improved are available with plugins. There are so many that downloading and installing them manually becomes cumbersome. So, we need a plugin manager to handle all stuff related to plugins. There are a wide variety of plugin managers that you may use. Here we will use vim-plug to install required plugins.

You can download and install vim-plug with curl as follows:

curl -fLo ~/.vim/autoload/plug.vim --create-dirs \
    https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim

Install required plugins

There are a lot of different plugins that can be used, but as embedded engineers let’s not forget our nature and be minimalistic as much as possible by using the plugins that are required. We will use the following plugins:

  • coc.nvim for code completion and language server integration
  • tagbar for code structure navigation
  • nerdtree for file system navigation
  • vim-cpp-enhanced-highlight for better syntax highlighting in C/C++

Edit your ~/.vimrc (with vim, obviously!) to include these plugins:

call plug#begin('~/.vim/plugged')

" Code completion and navigation
Plug 'neoclide/coc.nvim', {'branch': 'release'}
Plug 'preservim/tagbar'
Plug 'scrooloose/nerdtree'

" Syntax highlighting for C and C++
Plug 'octol/vim-cpp-enhanced-highlight'

call plug#end()

Install the plugins by running:PlugInstall in Vim.

Configure Kernel Coding Style

The Linux kernel uses a strict coding style defined in Documentation/process/coding-style.rst. Now we are going to configure Vim to adhere to it.

To do that, add the following lines to ~/.vimrc:

" Kernel coding style
set tabstop=8       " Display tab as 8 spaces
set shiftwidth=8    " Indent with 8 spaces
set expandtab       " Convert tabs to spaces
set cindent         " Use C-style indentation
set autoindent      " Auto-indent new lines
set smartindent     " Enable smart indentation

" Show line numbers and ruler
set number
set ruler

" Highlight trailing whitespace

highlight ExtraWhitespace ctermbg=red guibg=red
match ExtraWhitespace /\s\+$/

" Enable mouse support (optional)
set mouse=a

" Folding based on syntax
set foldmethod=syntax
set foldlevel=99

" For device tree files, we need tabs
autocmd FileType dts,dtsi setlocal noexpandtab tabstop=8 shiftwidth=8

If each of the above settings does not suit your needs, remove them from the ~/.vimrc file.

Enable Ctags for Easy Navigation

By calling make tags in the kernel directory, the tags would be made automatically. However, for other sources, you can use ctags. Ctags generates an index of all identifiers in your codebase, allowing you to jump between functions, variables, and headers.

Generate tags for the any other source:

Go to the source directory and then run:

ctags -R --languages=C --c-kinds=+p --fields=+iaS --extras=+q .

This command will generate a huge tags file that includes all used tags in the source code with their full path.

Add tags to vim

To navigate through the generated tags in vim, we should inform vim that such file is available. To to that add this to ~/.vimrc:

set tags+=/path/to/source/tags

You can now navigate with:

  • Ctrl-]: Jump to a tag definition.
  • Ctrl-t: Return to the previous location.

Enable Language Server for Advanced Features

For features like autocomplete, linting, and code navigation, coc.nvim should be configured with the ccls language server.

ccls is not available in the official repositories of the Fedora project. You can either directly download a binary executable from Releases · MaskRay/ccls . As an alternative, you can build it from source on your host with the following command:

git clone --depth=1 --recursive https://github.com/MaskRay/ccls
cd ccls
cmake -H. -Bbuild -DCMAKE_BUILD_TYPE=Release
cmake --build build --target install

Then we should let coc.nvim to use it: Open ~/.vimrc and add:

let g:coc_global_extensions = ['coc-clangd']

tagbar provides an outline of your code structure, such as functions, variables, and macros. To use it in vim you can invoke it via the following command:

:TagbarToggle

You could assign a shortcut key to the TagbarToggle, e.g. nmap <F4> :TagbarToggle<CR>. Now, by pressing F4 you can toggle it.

Generate compile_commands.json for code completion and syntax highlighting

cclc or any language server require an input file that contains detailed information about how each source file in a project is compiled (compiler that is used, include paths, macro definitions, etc.). We generate this file by running the normal build steps within bear command. The generated file is compile_commands.json. For example, to generate the environment for the kernel:

make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- defconfig
bear -- make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- -j$(nproc)

Place the compile_commands.json in the kernel source directory or configure ccls to locate it.

Enable Syntax Highlighting

Enable syntax highlighting for kernel source files:

:syntax on

Enable Error Checking with gcc

Integrate gcc or clang for real-time syntax and error checking.

Add the following to your ~/.vimrc:

set makeprg=make\ ARCH=arm64\ CROSS_COMPILE=aarch64-linux-gnu-
set errorformat=%f:%l:%c:\ %m

Run:make to compile the current file and display errors.

Optimize for Large Files

Kernel files can be large, so optimize Vim’s performance:

set lazyredraw
set nocompatible
set nohidden
set nowrap

Optional: Enable NERDTree for File Navigation

To navigate the source files, NERDTree can be used. You can launch it with:

:NERDTreeToggle

Save and Reload Vim Configuration

Save your changes to ~/.vimrc and reload Vim to apply the settings:

source ~/.vimrc

Full vimrc file

Here you can find the full .vimrc file from my system setup. You can modify it based on your needs:

call plug#begin('~/.vim/plugged')

" Code completion and navigation
Plug 'neoclide/coc.nvim', {'branch': 'release'}
Plug 'preservim/tagbar'
Plug 'scrooloose/nerdtree'

" Syntax highlighting for C and C++
Plug 'octol/vim-cpp-enhanced-highlight'

call plug#end()

" Kernel coding style
set tabstop=8       " Display tab as 8 spaces
set shiftwidth=8    " Indent with 8 spaces
set expandtab       " Convert tabs to spaces
set cindent         " Use C-style indentation
set autoindent      " Auto-indent new lines
set smartindent     " Enable smart indentation

" Show line numbers and ruler
"set number
set ruler

" Highlight trailing whitespace
highlight ExtraWhitespace ctermbg=red guibg=red
function! Preserve(command)
  let l:search=@/
  let l:line = line(".")
  let l:col = col(".")
  execute a:command
  let @/=l:search
  call cursor(l:line, l:col)
endfunction
match ExtraWhitespace /\s\+$/

" Enable mouse support (optional)
"set mouse=a

" Folding based on syntax
set foldmethod=syntax
set foldlevel=99
" Enable ctags for linux kernel
set tags+=/home/javad/workspace/embedded/linux-kernel/tags
let g:coc_global_extensions = ['coc-clangd']
"set makeprg=aarch64-linux-gnu-gcc\ -Wall\ -Wextra\ -c\ %
set makeprg=make\ ARCH=arm64\ CROSS_COMPILE=aarch64-linux-gnu-
set errorformat=%f:%l:%c:\ %m

set lazyredraw
set nocompatible
set nohidden
set nowrap

" Use <Tab> and <Shift-Tab> to navigate completion menu
inoremap <expr> <Tab> pumvisible() ? "\<C-n>" : "\<Tab>"
inoremap <expr> <S-Tab> pumvisible() ? "\<C-p>" : "\<S-Tab>"

" Use <Enter> to confirm selection or insert a newline
inoremap <expr> <CR> pumvisible() ? coc#_select_confirm() : "\<CR>"
"
highlight Pmenu      ctermbg=black ctermfg=green
highlight PmenuSel   ctermbg=blue ctermfg=white
highlight PmenuSbar  ctermbg=gray
highlight PmenuThumb ctermbg=blue
" For dts
autocmd FileType dts,dtsi setlocal noexpandtab tabstop=8 shiftwidth=8
" For toggle bar
nmap <F4> :TagbarToggle<CR>