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"