As text editors go, Vim is the GOAT

As text editors go, Vim is the GOAT

My first encounter with Vim was in 2000 when my then employer decided to port our main product (Bandwidth Control System) from Windows NT to Linux.

Linux was a fresh experience for me, and it opened the door to diving into Emacs and Vim for text editing. I recall reading an Emacs vs. Vim article in a magazine or trade-rag a while back that really piqued my interest. Even now, Emacs still feels a bit out of reach for my simple mind. Vim, however, felt intuitive and almost magical after some learning and experimenting.

Primarily due to discovering and appreciating John Grubber’s Markdown for my professional writing requirements, Vim remained dormant for nearly 25 years, although it was never forgotten.

Fast-forward to 2026 where I am now retired and self-hosting several services for fun and practical purposes. As a macOS user who operates a Proxmox lab and has a preference for running Debian LXC or virtual machines over Docker containers, the need for a fast and powerful multi-platform text editor became evident. So, was Vim still the clear choice for me?

In 25 Years the Vim-adjacent landscape changed a lot. Enter Neovim, the newer shinier and more extensible Vim. For roughly two months, I experimented with Neovim, Kickstart.nvim, LazyVim, LunarVim, NvChad and endless variations of plugins. This time-sink forced me to re-evaluate what my actual needs are. I’m not a programmer, the target audience for most of the above Neovim derivitives. Instead my needs are closer to that of a systems or network operator (Sysops or Netops).

In the end, I reverted to plain Vim with minimal plugins to easily replicate my setup across Macs and Linux systems. Sure, I could also use it on MS-Windows (WSL), but avoid Windows like the plague that it is.

Vim is a truly magical tool that keeps giving, and it’s incredibly challenging in a good way. So, dive in, become proficient, and thank me later!

A custom Ansible playbook simplifies the replication of my Vim configuration. What follows are my Ansible configuration files that install and configure Vim on target host(s) in the inventory.ini file.

Ansible files

inventory.ini

Defines the target host(s) executed by the Ansible Playbook (install_vim.yaml)

# Ansible Inventory

[all]
192.168.5.12

install_vim.yaml (Ansible playbook)

Runs the vim Ansible role

---
- name: Deploy my vim configuration
  hosts: all
  roles:
      - role: vim
  become: yes

vim (Ansible role) directory structure

.
├── defaults
│   └── main.yml
├── files
│   ├── config
│   │   └── vim
│   │       ├── colors.vim
│   │       ├── fzf.vim
│   │       ├── keybinds.vim
│   │       ├── lightline.vim
│   │       ├── options.vim
│   │       ├── plugins.vim
│   │       └── vimrc
│   └── vim-env.bash
└── tasks
    └── main.yml

defaults/main.yml

---
# defaults file for vim role

# Vim configuration directory
vim_config_dir: "~/.config/vim"

# Vim configuration file location
vim_config_file: "~/.config/vim/vimrc"

# Viminfo file location
vim_viminfo_file: "~/.config/vim/viminfo"

# Undo directory
vim_undo_dir: "~/.config/vim/undo"

files/config/vim/colors.vim

set termguicolors
let g:tokyonight_enable_italic = 1
colorscheme tokyonight

files/config/vim/fzf.vim

" FZF keymaps (requires Plug 'junegunn/fzf.vim')

" Files
nnoremap <leader>ff :Files<CR>
nnoremap <leader>fo :History<CR>
nnoremap <leader>fb :Buffers<CR>
nnoremap <leader>fq :clist<CR>    " For quickfix list
nnoremap <leader>fh :Helptags<CR>

" Grep current string
nnoremap <leader>fs :Rg <C-r><C-w><CR>

" Grep input string (fzf prompt)
nnoremap <leader>fg :Rg<Space>

" Grep for current file name (without extension)
nnoremap <leader>fc :execute 'Rg ' . expand('%:t:r')<CR>

" Find files in your Vim config
nnoremap <leader>fi :Files ~/.config/vim<CR>

files/config/vim/keybindings.vim

imap jj <Esc>

" Set leader key
let mapleader = " "

" Open netrw with <leader>cd
nnoremap <leader>e :Ex<CR>

files/config/vim/lightline.vim

set laststatus=2
let g:lightline = {
      \ 'colorscheme' : 'tokyonight',
      \ 'active': {
      \   'left': [ [ 'mode', 'paste' ],
      \             [ 'gitbranch', 'readonly', 'filename', 'modified' ] ],
      \   'right': [ [ 'lineinfo' ], [ 'fileformat', 'fileencoding', 'filetype' ] ]
      \ },
      \ 'component_function': {
      \   'gitbranch': 'FugitiveHead',
      \   'filename': 'LightlineFilename'
      \ }
      \ }

function! LightlineFilename()
  return expand('%:t') !=# '' ? expand('%:t') : '[No Name]'
endfunction

files/config/vim/options.vim

" Add line numbers
set number
set nrformats= "treat all numerals as decimals

set clipboard=unnamedplus

set relativenumber
set tabstop=4
set shiftwidth=4
set autoindent
set mouse=a

set guifont=Monaco:h16

syntax on

set rtp+=/opt/homebrew/opt/fzf

" remove startup screen
set shortmess+=I

" Set all vim directories to ~/.config/vim
set viminfofile=~/.config/vim/viminfo

" Enable persistent undo
set undofile
set undodir=~/.config/vim/undo//

files/config/vim/plugins.vim

let s:plugin_dir = expand('~/.config/vim/plugged')

function! s:ensure(repo)
  let name = split(a:repo, '/')[-1]
  let path = s:plugin_dir . '/' . name

  if !isdirectory(path)
    if !isdirectory(s:plugin_dir)
      call mkdir(s:plugin_dir, 'p')
    endif
    execute '!git clone --depth=1 https://github.com/' . a:repo . ' ' . shellescape(path)
  endif

  execute 'set runtimepath+=' . fnameescape(path)
endfunction

call s:ensure('ghifarit53/tokyonight-vim')
call s:ensure('junegunn/fzf')
call s:ensure('junegunn/fzf.vim')
call s:ensure('itchyny/lightline.vim')

files/config/vim/vimrc

" Vim Configuration File
" Managed by Ansible - vim role

source ~/.config/vim/options.vim
source ~/.config/vim/keybinds.vim
source ~/.config/vim/plugins.vim
source ~/.config/vim/fzf.vim
source ~/.config/vim/lightline.vim
source ~/.config/vim/colors.vim

files/vim-env.bash

# ===========================================
# VIM CONFIGURATION
# Managed by Ansible - vim role
# ===========================================

# Set VIMINIT to use custom vim configuration location
export VIMINIT="source ~/.config/vim/vimrc"

# FZF integration for vim
export FZF_DEFAULT_COMMAND='fd --type f --hidden --follow'

tasks/main.yml

---
# tasks file for vim role

- name: Check if vim is installed
  ansible.builtin.command: which vim
  register: vim_check
  changed_when: false
  failed_when: false
  become: no

- name: Install vim (macOS)
  ansible.builtin.shell: brew install vim
  when:
      - vim_check.rc != 0
      - ansible_os_family == 'Darwin'
  become: no

- name: Install vim (Debian/Ubuntu)
  ansible.builtin.apt:
      name: vim
      state: present
      update_cache: yes
  when:
      - vim_check.rc != 0
      - ansible_os_family == 'Debian'
  become: yes

- name: Install vim (RedHat/CentOS/Fedora)
  ansible.builtin.yum:
      name: vim
      state: present
  when:
      - vim_check.rc != 0
      - ansible_os_family == 'RedHat'
  become: yes

- name: Install vim (Arch Linux)
  ansible.builtin.pacman:
      name: vim
      state: present
  when:
      - vim_check.rc != 0
      - ansible_os_family == 'Archlinux'
  become: yes

- name: Ensure vim config directory exists
  ansible.builtin.file:
      path: "{{ vim_config_dir }}"
      state: directory
      mode: "0755"
  become: no

- name: Ensure vim subdirectories exist
  ansible.builtin.file:
      path: "{{ item }}"
      state: directory
      mode: "0755"
  become: no
  loop:
      - "{{ vim_undo_dir }}"

- name: Deploy vim configuration files
  ansible.builtin.copy:
      src: "config/vim/"
      dest: "{{ vim_config_dir }}/"
      mode: "0644"
  become: no

- name: Deploy .fdignore to home directory
  ansible.builtin.copy:
      src: fdignore
      dest: "~/.fdignore"
      mode: "0644"
  become: no

- name: Ensure ~/.config/bash directory exists
  ansible.builtin.file:
      path: "~/.config/bash"
      state: directory
      mode: "0755"
  become: no

- name: Deploy vim environment configuration to modular bash
  ansible.builtin.copy:
      src: vim-env.bash
      dest: "~/.config/bash/vim-env.bash"
      mode: "0644"
  become: no

- name: Display vim configuration status
  ansible.builtin.debug:
      msg:
          - "Vim configuration completed!"
          - "Config directory: {{ vim_config_dir }}"
          - "Config file: {{ vim_config_file }}"
          - "Viminfo location: {{ vim_viminfo_file }}"
          - "Environment variables configured in ~/.config/bash/vim-env.bash"
          - "Note: Ensure your .bashrc sources ~/.config/bash/*.bash files"

Recommended Vim Resources

Leave a Reply

Your email address will not be published. Required fields are marked *