Rust, Vim and friends.


Or "old tools" for modern development.

Rust is a systems programming language commonly utilized for tasks such as systems programming, embedded systems, low-level applications, web browser development, and creating low-latency, high-performance distributed applications.

While it may not seem like a language that would attract widespread interest, Rust's popularity has been growing for years now. This trend can be compared to the rise of Java over 30 years ago when it revolutionized the industry by offering a more effective and powerful solution for tackling complex tasks. One of the superpowers of Rust comes from the development environment: the tools and applications supporting the developers are great and constantly improving; plus, they are not so difficult to configure and tailor to some specific needs.

This situation, plus my constant curiosity, sparked a simple question: can I do it differently? Can I use something different than the usual VsCode (great tool, don't get me wrong)? What about creating an environment that is very Rust-oriented, if it even exists that word, trying to use fewer resources, more keyboard-centric, and maybe even using my old beloved tools like terminal and Vim? The answer is yes. Even better, we can use all those "classic" (a polite way of saying "old") tools in a modern way. But first...

Why?

In summary, a modern terminal-based environment offers me speed and resource efficiency, using less memory and CPU. It is keyboard-centric, which enhances my focus and speeds up my workflow by minimizing my reliance on the mouse. Additionally, it reduces my dependency on the latest shiny IDEs or tools, whilst being able to leverage powerful integrations to things like LLM and AI

Plus, it is cheap and green (just a couple of buzzwords ;-P ). I started years ago, and now I always buy refurbished hardware, helping the environment by giving a second chance to perfectly still good working hardware, and saving thousands of bucks, avoiding the marketing trap of needing to buy the latest machine.

It is all Open Source

Let's be clear from the beginning. It is all Open Source, and freely available online.

First of all, it runs on Linux. Nowadays, I mostly use Linux Mint. After years of using/battling with distros like Slackware and then even Arch, or Debian, I switched to a modern, stable and easy distro that still allows me to tweak and change almost everything, but without getting mad whenever I want to integrate a new kernel or a new version is available.

But it doesn't really matter the distro: today, the Linux environment is so incredibly powerful and stable that a distro choice is mostly a case of personal taste.

Window manager: i3.

A bit out of the scope of this post since it is not particularly focused only on development or Rust, but pertinent to the effort of minimizing resource usage and having a more keyboard-focused workflow, i3 (https://i3wm.org/) is a small, simple, yet powerful and feature-rich tilling (not overlapping frames) windows manager. It allows me to easily manage multi-monitor and different modes (similar to Vim) from my keyboard.

Removing some services and using i3 on my old laptop, I achieved a starting memory footprint of less than 300Mb (yes, it is correct MegaBytes!!).

Terminal Emulator: Alacritty

Let's dive into the "rewrite it in Rust" World using the less-expected new tool: a terminal emulator completely rewritten in Rust.

Alacritty (https://alacritty.org/) is a modern terminal emulator that leverages GPU acceleration for rendering, resulting in exceptional performance. It's cross-platform, minimalist by design, and configured entirely through a YAML file.

Installation and Configuration

Or if you have Rust installed and you prefer more control, you have two options:

The beauty of Alacritty is its simplicity - it does one thing (terminal emulation) extremely well while leaving other functionality to dedicated tools that integrate pretty well. On top of that, some unique features like Vi mode, Search, and Multi-Window allow me to do most of the actions from my keyboard and save memory and resources.

Configuring Alacritty could become a full-time job tweaking all the available settings, but sticking to the default options already provides an enjoyable and productive setup. A nice addition is the theme repository (https://github.com/alacritty/alacritty-theme), providing palettes and colour combinations for all tastes. A simple example from my config file using the config docs as a guide:

[general]
## Reload config on changes
live_config_reload = true
## Import themes.
import = ["~/.config/alacritty/themes/tokyo_night_enhanced.toml"]
#[env]
#TERM = "xterm-256color"
[window]
## Set CWD as window title
dynamic_title = true
## Ignored when dynamic_title = true
title = "Alacritty"
## Fullscreen, Maximized, Windowed, SimpleFullscreen
startup_mode = "SimpleFullscreen"
## How transparent the window is; 0.0 to 1.0
opacity = 0.85
## Add additional padding evenly around terminal content
dynamic_padding = true
padding.x = 4
padding.y = 0
...

Terminal Multiplexer: Tmux

Tmux is an evergreen tool; so simple and yet so powerful, especially with small monitors and reduced resources but still dealing with complex projects. This is where tmux shines - it allows you to create multiple terminal sessions within a single window, detach from and reattach to sessions, and maintain your development environment even if your connection drops.

Installation and Basic Configuration

Create a basic configuration at ~/.tmux.conf:

# Change prefix from 'Ctrl+b' to 'Ctrl+a' (optional)
unbind C-b
set-option -g prefix C-a
bind-key C-a send-prefix

# Split panes using | and -
bind | split-window -h
bind - split-window -v
unbind '"'
unbind %

# Set vi-mode for navigating buffers
setw -g mode-keys vi

# Increase history limit
set-option -g history-limit 5000

# Theme and status bar customization
set -g status-style 'bg=#333333 fg=#5eacd3'

Integrating Tmux with Vim

If you're using Vim, the vim-tmux-navigator plugin allows for seamless navigation between Vim splits and tmux panes:

In your .vimrc:

Plugin 'christoomey/vim-tmux-navigator'

And in your .tmux.conf:

# Smart pane switching with awareness of Vim splits

is_vim="ps -o state= -o comm= -t '#{pane_tty}' \
    | grep -iqE '^[^TXZ ]+ +(\\S+\\/)?g?(view|n?vim?x?)(diff)?$'"
    
bind-key -n 'C-h' if-shell "$is_vim" 'send-keys C-h'  'select-pane -L'
bind-key -n 'C-j' if-shell "$is_vim" 'send-keys C-j'  'select-pane -D'
bind-key -n 'C-k' if-shell "$is_vim" 'send-keys C-k'  'select-pane -U'
bind-key -n 'C-l' if-shell "$is_vim" 'send-keys C-l'  'select-pane -R'

Fully Rust(y)? Meet Zellij

Even more into terminal development and in the "use all Rust"? A tool worth mentioning is zellij (https://zellij.dev/), that works pretty well with Helix. My muscle memory is pretty tied up with Vim and Tmux, but it is definitely a tool worth checking.

Editor options

Now for the core of the development environment - the text editor. Let's explore two similar options: Vim (with plugins) and Helix.

Option 1: Vim for Rust Development

vIM is a highly configurable text editor built to make creating and changing any kind of text very efficient. For Rust development, we'll need several plugins to create an IDE-like experience.

Installation

Essential Configuration for Rust

First, we'll set up Vim with a plugin manager. I use Vundle but there are many others.

My .vimrc configuration (with some omissions):

set nocompatible              " be iMproved, required
filetype off                  " required

" set the runtime path to include Vundle and initialize
set rtp+=~/.vim/bundle/Vundle.vim

call vundle#begin()
" let Vundle manage Vundle, required
Plugin 'VundleVim/Vundle.vim'

" File navigation and search
Plugin 'junegunn/fzf'
Plugin 'junegunn/fzf.vim'
Plugin 'preservim/nerdtree'        " File explorer
Plugin 'jistr/vim-nerdtree-tabs'

Plugin 'xolox/vim-misc'
" Requires CTAGS - https://github.com/universal-ctags/ctags
" Plugin 'majutsushi/tagbar'  NEW NAME
Plugin 'preservim/tagbar'

Plugin 'nanotech/jellybeans.vim'
Plugin 'vim-airline/vim-airline'
Plugin 'vim-airline/vim-airline-themes'
"
" ----- Plugin Rust dev
Plugin 'rust-lang/rust.vim'
Plugin 'shougo/echodoc.vim'  " doc echo, params list while typing
Plugin 'dense-analysis/ale'  " LSP Engine

" Haskell dev
...
" Scala dev
...
" Code completion
Plugin 'prabirshrestha/async.vim'
Plugin 'prabirshrestha/vim-lsp'    " Language Server Protocol
Plugin 'prabirshrestha/asyncomplete.vim'
Plugin 'prabirshrestha/asyncomplete-lsp.vim'
Plugin 'mattn/vim-lsp-settings'    " Auto-configure LSP
" --- Working with Git ---
Plugin 'tpope/vim-fugitive'
Plugin 'airblade/vim-gitgutter'
Plugin 'gregsexton/gitv'

" Plugin 'ErichDonGubler/vim-sublime-monokai'
Plugin 'morhetz/gruvbox'
" All of your Plugins must be added before the following line
call vundle#end()            " required

filetype plugin indent on    " required
set omnifunc=syntaxcomplete#Complete

" NERDTree settings START  -----------------------------------
" open NERDTree automatically when Vim starts
autocmd vimenter * NERDTree
autocmd StdinReadPre * let s:std_in=1
autocmd VimEnter * if argc() == 1 && isdirectory(argv()[0]) && !exists("s:std_in") | exe 'NERDTree' argv()[0] | wincmd p | ene | endif
" use Ctrl+n to toggle NERDTree
map <C-n> :NERDTreeToggle<CR>
"close Vim if the only window left open is NERDTree
autocmd bufenter * if (winnr("$") == 1 && exists("b:NERDTree") && b:NERDTree.isTabTree()) | q | endif

let g:NERDTreeDirArrowExpandable = '▸'
let g:NERDTreeDirArrowCollapsible = '▾'
let g:nerdtree_tabs_open_on_console_startup=1
let g:airline#extensions#tabline#enabled = 1
"
" NERDTree settings END -------------------------------------------
"
" Move between buffers
map <leader>n :bnext<cr>
map <leader>p :bprevious<cr>
map <leader>d :bdelete<cr>

" OPEN TERMINAL inside Vim ----------------------------------
" Open a terminal in another window
" bel term = below, open the terminal in the below window
map <silent> <leader>t :bel term<cr>
" Open a terminal in the current window
map <silent><leader><C-t> :term ++curwin<cr>
" ---------------------
" Enable auto completion menu after pressing TAB.
set wildmenu
" Make wildmenu behave like similar to Bash completion.
set wildmode=list:longest
" There are certain files that we would never want to edit with Vim.
" Wildmenu will ignore files with these extensions.
set wildignore=*.docx,*.jpg,*.png,*.gif,*.pdf,*.pyc,*.exe,*.flv,*.img,*.xlsx

" ALE Config start --------------------------------
let g:ale_linters = { 'rust' : ['analyzer'],}
let g:ale_fixers = {'rust': ['rustfmt']}
set completeopt=menu,menuone,preview,noselect,noinsert
let g:ale_completion_enabled = 0  " Use asyncomplete instead
let g:ale_set_highlights = 1

let g:ale_fix_on_save = 1
" linter NOT running while writing
let g:ale_lint_on_text_changed = 'never'
" linter NOT running just after opening a file
let g:ale_lint_on_enter = 0
let g:ale_lint_on_save = 1
let g:ale_virtualtext_cursor = 2
let g:ale_hover_to_floating_preview = 1
let g:ale_hove_cursor = 1
" ALE signs
let g:ale_sign_error = '✘'
let g:ale_sign_warning = '⚠'

" LSP configuration for Rust
if executable('rust-analyzer')
    au User lsp_setup call lsp#register_server({
        \ 'name': 'rust-analyzer',
        \ 'cmd': {server_info->['rust-analyzer']},
        \ 'allowlist': ['rust'],
        \ 'initialization_options': {
        \   'cargo': {
        \     'allFeatures': v:true,
        \   },
        \   'checkOnSave': {
        \     'command': 'clippy',
        \   },
        \   'procMacro': {
        \     'enable': v:true,
        \   },
        \ },
        \ })
endif

" LSP mappings
function! s:on_lsp_buffer_enabled() abort
    setlocal omnifunc=lsp#complete
    setlocal signcolumn=yes
    nmap <buffer> gd <plug>(lsp-definition)
    nmap <buffer> gr <plug>(lsp-references)
    nmap <buffer> gi <plug>(lsp-implementation)
    nmap <buffer> <leader>rn <plug>(lsp-rename)
    nmap <buffer> [g <plug>(lsp-previous-diagnostic)
    nmap <buffer> ]g <plug>(lsp-next-diagnostic)
    nmap <buffer> K <plug>(lsp-hover)
endfunction

augroup lsp_install
    au!
    autocmd User lsp_buffer_enabled call s:on_lsp_buffer_enabled()
augroup END

" Hover - show info about the keyword at the cursor
nmap <silent> <leader>h :ALEHover<CR>
" GOTO Definiotn, Show References - Keys binding
nmap <silent> <F12> :ALEGoToDefinition<CR>
" Ctrl + F12
nmap <silent> <C-F12> :ALEFindReferences<CR>
" Previous and next Error - Keys binding
" Shift + F8
nmap <silent> <S-F8> <Plug>(ale_previous_wrap)
nmap <silent> <F8> <Plug>(ale_next_wrap)
" ALE Config End -------------------------------

" echodoc  Config Start -------------------------------
let g:echodoc#enable_at_startup = 1
let g:echodoc#type = 'popup'
" To use a custom highlight for the popup window,
" change Pmenu to your highlight group
highlight link EchoDocPopup Pmenu
" echodoc  Config End -------------------------------

" ----- majutsushi/tagbar settings -----
" Open/close tagbar with \b
nmap <silent> <leader>b :TagbarToggle<CR>
" Uncomment to open tagbar automatically whenever possible
"autocmd BufEnter * nested :call tagbar#autoopen(0)

" ----- airblade/vim-gitgutter settings -----
" Required after having changed the colorscheme
" hi clear SignColumn
" In vim-airline, only display "hunks" if the diff is non-zero
"let g:airline#extensions#hunks#non_zero_only = 1
" Airline settings
let g:airline_powerline_fonts = 1
let g:airline#extensions#tabline#enabled = 1
let g:airline#extensions#ale#enabled = 1

" --- jellybeans color and font settings ---
let g:jellybeans_use_lowcolor_black = 0
set guifont=Monaco:h10 noanti
" jellybeans color and font settings END

set tabstop=2                   "A tab is 4 spaces
set expandtab                   "Always uses spaces instead of tabs
set softtabstop=2               "Insert 2 spaces when tab is pressed
set shiftwidth=2                "An indent is 2 spaces
set shiftround                  "Round indent to nearest shiftwidth multiple

""""""""""""""""""""""""""""""""""""
" Appearance
"""""""""""""""""""""""""""""""""""""
set t_Co=256
let g:gruvbox_italic=1
colorscheme gruvbox
" colorscheme relaxedgreen
" colorscheme sublimemonokai
set background=dark
set cursorline
set number
set laststatus=2
set showtabline=1

set number relativenumber

set ruler
set showcmd
set incsearch
set hlsearch
set ignorecase
set smartcase
set encoding=utf-8

...

syntax on
syntax enable

hi! Normal ctermbg=NONE guibg=NONE
hi! NonText ctermbg=NONE guibg=NONE

set clipboard=unnamed

This configuration provides:

Installing and Configuring Rust-Analyzer For the LSP support to work correctly, you need to have rust-analyzer installed and available in your PATH:

rustup component add rust-analyzer

If that doesn't work, you can download the binary directly Check the rust-analyzer GitHub page for the latest release URL

Option 2: Helix Editor

Helix is a post-modern terminal-based editor inspired by Kakoune, designed with a modal editing philosophy but with a more intuitive key scheme. It has excellent out-of-the-box support for Rust.

Installation and basic configuration

Configuration

Create ~/.config/helix/config.toml:

theme = "mytheme"

[editor]
line-number = "relative"
mouse = false
bufferline = "multiple"
auto-format = false
text-width = 130

[editor.cursor-shape]
insert = "bar"
normal = "block"
select = "underline"

[editor.file-picker]
hidden = false

[editor.indent-guides]
render = true
 
[editor.lsp]
display-messages = true
auto-signature-help = true
display-inlay-hints = true

[editor.statusline]
left = ["mode", "spinner"]
center = ["file-name"]
right = ["diagnostics", "selections", "position", "file-encoding", "file-line-ending", "file-type"]
separator = "│"
mode.normal = "NORMAL"
mode.insert = "INSERT"
mode.select = "SELECT"

[keys.normal]
# Add custom keybindings here
g = { a = "code_action" }  # Quick access to code actions
space = { w = ":w", q = ":q" }
C-p = "file_picker"
C-f = "search_selection"

Creating a dedicated ~/.config/helix/languages.toml for Rust:

[[language]]
name = "rust"
comment-token = "//"
auto-format = true
indent = { tab-width = 4, unit = "    " }

[language.auto-pairs]
'(' = ')'
'{' = '}'
'[' = ']'
'"' = '"'
'`' = '`'

[language.config]
# Customize Rust Analyzer settings
checkOnSave = { command = "clippy" }
cargo = { allFeatures = true }
procMacro = { enable = true }
inlayHints = { bindingModeHints = true, chainingHints = true, typeHints = true }

The beauty of Helix is that it requires minimal configuration to get powerful Rust support. The core features include:

Fuzzy Finding with FZF

FZF (Fuzzy Finder) is a versatile command-line fuzzy finder that can be integrated with various tools in your development environment.

Installation

Basic Configuration

Add to your .bashrc or .zshrc:

# fzf configuration
export FZF_DEFAULT_COMMAND='fd --type f --hidden --follow --exclude .git'
export FZF_DEFAULT_OPTS='--height 40% --layout=reverse --border'

# Key bindings for bash
$ source /usr/share/doc/fzf/examples/key-bindings.bash
$ source /usr/share/doc/fzf/examples/completion.bash

# For zsh users
$ source /usr/share/fzf/key-bindings.zsh
$ source /usr/share/fzf/completion.zsh

FZF can be used directly in the terminal:

CTRL-T to paste the selected files/directories onto the command line CTRL-R to paste the selected command from history

ALT-C to cd into the selected directory

It also integrates beautifully with Vim through the fzf.vim plugin, allowing you to search files, buffers, and even grep through code content.

Setting Up Rust Tools

To get the most out of your Rust development environment, you'll need several core Rust tools:

# Install Rust via rustup
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
# Add Rust to your path
source $HOME/.cargo/env
# Install core components
rustup component add rust-analyzer
rustup component add rustfmt
rustup component add clippy

Install useful cargo extensions

cargo install cargo-edit    # Add, remove, upgrade dependencies
cargo install cargo-watch   # Watch for changes and run commands
cargo install cargo-expand  # Show result of macro expansion
cargo install cargo-audit   # Audit dependencies for vulnerabilities
cargo install cargo-update  # Update installed binaries

Putting It All Together

Let's create a workflow example that combines all these tools for Rust development:

#Start a new terminal session:
alacritty
#Create a new tmux session:
$ tmux new -s rust-dev
#Split the window into panes:
# Use your tmux prefix (Ctrl+a in our config) followed by:
<prefix> |    # Split vertically
<prefix> -    # Split horizontally
#Navigate and set up your workspace:
#Left pane: Vim for code editing
#Upper right: Cargo watch for continuous testing
#Lower right: Command line for Git operations and cargo commands
#Create a new project:
$ cargo new my-project
$ cd my-project
#Start coding with Vim:
$ vim src/lib.rs
#Set up continuous testing in the upper right pane:
$ cargo watch -x "test -- --nocapture"

What about AI?

Nowadays, avoiding talking about AI and LLM in tech-related discussions is pretty impossible. I don't use them too much. Languages like Rust or Haskell are still not very popular, so the data available for the bot is limited, resulting usually in poor suggestions. Also, very popular languages like Python or Javascript are updated often, so sometimes the LLM suggestions are a bit... stale.

I do use bots and LLMs, more like a smart advisor or helper, with their powerful capability to produce deep and interesting search results.

Nevertheless, both Vim and Helix have plugins supporting the integration of Copilot-like tools. I'm testing some of them these days, but not ready to use in my daily routine.

Conclusion

Building a terminal-based Rust development environment gives you tremendous power and flexibility. Whether you choose Vim with its rich ecosystem of plugins or Helix with its streamlined, Rust-friendly design, you'll have access to modern IDE features while maintaining the speed and efficiency that terminal tools offer.

The configuration for original Vim maintains its classic simplicity while adding the modern capabilities needed for productive Rust programming.

As you grow more comfortable with this environment, you can further customize it to match your exact workflow. The plugin ecosystem is always growing and maturing.

The principles-first approach is well-served by these tools, as they encourage thoughtful architecture and clear separation of concerns in your code. With these tools properly configured, you'll have a development environment that's both powerful and enjoyable to use.

Happy hacking!