Emacs for NvChads
2026.08.07
Acknowledgments
Before I get started with the article I'd like to mention that I am just getting
started with everything written in this article. If you have any suggesting consider
emailing me. I am very grateful to deci (
codeberg /
telegram /
disroot) for the revisions and editing. I plan to expand this topic with some time. Good time reading!
This article was originally written in Org Mode. Get it here if you want to:
qlain.online/emacs.org
No AI was used here.
A program is free software if the program’s users have the four essential
freedoms:
-
The freedom to run the program as you wish, for any purpose (freedom 0).
-
The freedom to study how the program works, and change it so it does your
computing as you wish (freedom 1). Access to the source code is a precondition
for this.
-
The freedom to redistribute copies so you can help your neighbor (freedom 2).
-
The freedom to distribute copies of your modified versions to others (freedom
3).
By doing this you can give the whole community a chance to
benefit from your changes. Access to the source code is a precondition for
this.
--- Free Software, Free Society: The Selected Essays of Richard M. Stallman
Chapter 1: Motivation
Hello internet. I have to admit, I am a NVim user, and relied on this text
editor for more than two and a half years. My old config was written from
scratch by me. I was writing lua plugins by myself. Did everything.
However, almost on a daily basis I watch YouTube. And almost every tech blogger
that I follow uses Emacs:
Valentin Ignatev,
Tsoding,
DistroTube,
Jonathan Blow. Still I follow a pretty big amount of Vim/NVim content creators, yet they
are not as often on my feed as Mister Zozin with his godly quality streams.
Bloggers are nice, but I wasn't ready for a change. I was quite used to vim
motions, lightweight tui text editor, lua programming language, and nice
community. I was quite fond of the FOSS philosophy and what power it gives. One
would say communism, but well designed communism :). It was in the March of this
year when I discovered
Free Software, Free Society: The Selected Essays of Richard M. Stallman. Reading through it changed my views on software world so
much. This read would be my personal recommendation. Still, I wasn't ready and
continued to heavily rely on NVim for everyday tasks and projects. Until I had a
need to reinstall my Arch Linux system.
You see, my first ever Linux distro was Arch that I installed back in 2021. And
I had a mistake of using an ext4 filesystem, and with my root being only 40gb of
space, while the home was over 400gb. I constantly needed to uninstall some
packages just to do small updates. Bad, right? I then decided that I am tired of
this bullshit, and reinstalled my system with backing up everything
beforehand. However, I forgot about my
~/.config/nvim directory... Imagine my
horror when I discovered that all of my old work was wiped. Sad, but what do I
do? I then wrote this small config for configuring stuff while still in tty
Lua
~/.config/nvim/init.lua
local opt = vim.opt
opt.tabstop = 4
opt.number = true
opt.relativenumber = true
local keymap = vim.keymap.set
keymap("n", "", ":edit ./")
keymap("n", 'sv', 'v', {desc = "Split window vertically."})
keymap("n", 'sh', 'e', {desc = "Split window horizontally."})
keymap("n", 'se', '=', {desc = "Make splits equal size."})
keymap("n", 'sx', 'close', {desc = "Close current split."})
keymap("n", 'th', 'tabnew', {desc = "Open a new tab."})
keymap("n", 'tx', 'tabclose', {desc = "Close current tab."})
keymap("n", 'tn', 'tabn', {desc = "Go to next tab."})
keymap("n", 'tp', 'tabp', {desc = "Go to previous tab."})
keymap("n", 'tf', 'tabnew %', {desc = "Open current buffer in a new tab."})
It is basic but enough for a tty.
Then, after installing and configuring everything on the system, I had this
choice of reconfiguring everything in NVim from scratch. But, hell, I
didn't want to deal with lazy.nvim installation systems. It was a pain for me,
and I didn't like having a C++ LSP eating over 4gb of RAM. That was when I
finally decided that it is time to change. I installed Emacs.
Chapter 2: ELisp
Emacs is a highly customizable text editor that uses a custom interpreted config
language written in C called Emacs Lisp (Elisp). It is a well made language
with one of the best documentations I have seen so far (beside Rust :3). The
language is primarly used for configuring Emacs and changing/extending its
functionality, but can also be used as a general-purpose language.
For example for scripts:
#!/usr/bin/env -S emacs --script
Or to call Emacs directly with either
-Q,
--batch and/or
--eval to evaluate
Elisp code from shell.
Before starting with the configuration I wanted to firstly teach the reader
basics of Elisp and its usage. Of course you are expected to know the basics of
programming.
Remember: everything is an expression of some sort. You can evaluate an
expression in Emacs, if your cursor is placed on the expression, and you press
C-x C-e.
Basic types present in ELisp
Primitive types:
- integers :: 69
- floats :: 4.2
- strings :: "i hate myself"
- symbols :: 'symbol
- conses (pairs) :: (a . b)
- vectors :: [1 2 3]
- hash tables :: #s(hash-table data (key1 val1 key2 300))
- byte-code functions :: gets evaluated by byte-code interpreter when called
functions
- macros
- primitive functions :: functions defined in C. they are
also called 'subr', which derives from "subroutine"
- records :: custom types
read here: elisp/Lisp-Data-Types
Not the real types but used in the editor:
- characters :: ?a (actually integers)
- lists :: '(a b c) (actually conses)
Types related to the text editor:
- buffers
- markers
- windows
- frames
- mutexes
- processeses
- threads
read here: elisp/Editing-Types
Mentions:
- t :: a logical true value
- nil :: could be: logical false value, symbol with the name 'nil, and an empty list.
Global variables
Emacs Lisp
(setq name "eto")
;; or
(defvar name "eto"
"Author's name.")
They can be used outside of the current scope.
+ setq ::
eintr/Using-setq
+ defvar ::
elisp/Defining-Variables
Local variables
Emacs Lisp
(let ((x 10)
(y 10))
(+ x y))
Can only be called from the inside of the *(let)* block.
+ let ::
eintr/let
Functions
Emacs Lisp
(defun italian (name)
"Greet in italian."
(message "Ciao, %s.", name))
(italian "eto")
Functions return the last expression.
The first string is called a
docstring, which can be used to define function's
purpose.
+ defun ::
elisp/Defining-Functions
Lists
Lists are essential in the Lisp family. They are a non primitive type used for
repsenting a collection of elements, and is constructed from a series of cons
cells chained / linked together into an ordered list.
Function:
(consp 'object')
Can be used to check if a value is cons cell. It returns t if an object is a cons cell and nil otherwise.
A pair is an operation used for joining two arbitrary values.
Atoms (historically derives from "indivisible") are used to represent objects that are not
cons cells.
Function:
(atom 'object')
It is used to check an object of being an atom. Returns t in case of an object being an atom and nil otherwise.
Lisp lists are linked lists by default, because of the pointers being implicit and there is
no difference between cons cells holding a value and those pointing to a value.
In Elisp there are different kinds of lists:
- association lists
- property lists
There are many ways to define, or interact with a list. I'll go over the basics:
Emacs Lisp
(setq mlist '(1 2 3)) ;; define a list of three elements
(B 3 4 "A") ;; a list of four elements
() ;; empty list
nil ;; empty list
((A B nil)) ;; a list of one element - a list of two elements and one empty list
Interacting with a list:
Emacs Lisp
(car mlist) ;; the index element
(cdr mlist) ;; rest of the list after index
(nth 'number mlist) ;; retuns the nth element
(nthcdr 'number mlist) ;; returns the nth cdr of the list
(take 'number mlist) ;; to return the n first elements of the list
(cons 'number mlist) ;; add an element to the head
(pop mlist) ;; a way to examine the car of a list and to take it off
(append mlist (list 'a 'b)) ;; combine lists
(mapcar #'sqrt '(1 3)) ;; applies a function to each element of a list in a sequence of turns
There is also the Dotted pair notation that is used to represent CAR and CDR explicitly.
In this notation a standard list is written as
(1 . (2 . (3 . nil))),
which is equivalent to
(1 2 3).
Dotted pair notation allows to explicitly describe who is the CAR and who is rest of the CDR.
In the case of our example the
1 was the CAR, while
2 and
3 are the CDR, and nil was used
as a terminator of the list. Which is not required, but the list notation makes it easier to read.
They can also be represented in such manner:
--- --- --- --- --- ---
| | |--> | | |--> | | |--> nil
--- --- --- --- --- ---
| | |
| | |
--> 1 --> 2 --> 3
Emacs Lisp
(1 . (2 . (3 . nil)))
+ lists ::
elisp/Lists
Lambda
\[
sin \: x \approx \frac{16x(\pi - x)}{5\pi^2 - 4x(\pi -x)}
\]
Reference:
Bhāskara I's sine approximation formula
Emacs Lisp
(lambda (x)
"Calculate the sin of x using Bhaskara I’s Sine Approximation Formula."
(/
(* (* 16 x) (- 3.14 x))
(- (* 5 (* 3.14 3.14)) (* (* 4 x) (- 3.14 x)))))
Lambda is an anonymous function object that takes a value for evaluation. We can dig deeper with this topic, but it is enough for now.
- lambda calculus :: wiki/Lambda_calculus
- lambda expression :: elisp/Lambda-Expressions
If statements
Emacs Lisp
(if (> 6 7)
"6 is greater"
"7 is greater")
(setq mark 9)
(cond ((< mark 8) "Acceptable!")
((< mark 10) "Almost max.")
(t "Try harder!")) ;; t is default case
;; 'when is used as an /if/ with no else
(when (> age 22)
(message "It's so over.")
'over)
;; 'unless would be the opposite of when
(unless (< age 22)
'so-back)
;; conditions with 'and' and 'or' logical operators
(and (> 5 3) (< 10 20)) ;; both have to be true
(or (> 5 10) (< 10 20)) ;; at least one should be true
+ control structures ::
elisp/Control-Structures
Looks like the basics are covered. Let's proceed with the configuration.
Chapter 3: Configuration
Of course some simple things like syntax highlightning are expected of a modern
editor, but plain Emacs doesn't give you much. Write your own theme if you want
to:
emacs/Custom-theme. Simplest way would be
M-x customize-create-theme.
I rely on a number of tools everyday that simplify my life:
- git :: version control
- obsidian :: graph view and markdown rendering
- nvim :: for the vim motions
Emacs has many equivalents to each of these.
These are the plugins that have improved my productivity with everyday tasks
significantly. Besides them I'll walk you over the installation of some other
plugins such as
lang-mode, and general configuration.
Initial configuration
Let's get started.
Emacs can be configured by editing the
~/.emacs file. However, it also looks
inside of the
~/.emacs.d/init.el and
~/.config/emacs/init.el.
~/.emacs is
used by default. Therefore, if you want to have your configuration placed inside
~/.emacs.d or
~/.config/emacs, ensure that the default
~/.emacs doesn't
exists.
The best initial configuration:
Emacs Lisp
~/.emacs.d/init.el
(setq custom-file "~/.emacs.d/custom.el")
(load-file "~/.emacs.d/custom.el")
(setq inhibit-startup-screen t)
(global-visual-line-mode t)
(global-display-line-numbers-mode)
(show-paren-mode t)
(tool-bar-mode -1)
(menu-bar-mode -1)
(scroll-bar-mode -1)
(column-number-mode 1)
(electric-pair-mode 1)
(recentf-mode 1)
That's a lot of stuff already. Let's go through each line.
Command:
(setq custom-file "~/.emacs.d/custom.el")
When installing some packages, or doing any changes outside of the config
file, Emacs still tends to write everything that was somehow touched into the
config file. It is better to separate your config from the editor changes.
Command:
(load-file "~/.emacs.d/custom.el")
Here we're loading the custom file defined previously.
Command:
(setq inhibit-startup-screen t)
We don't want to see the default screen, right?
Command:
(global-visual-line-mode t)
To toggle visual-mode in all buffers.
Command:
(global-display-line-numbers-mode)
Provides a line number.
Command:
(show-paren-mode t)
Custom visualization of where the parentheses end.
Commands:
- (tool-bar-mode -1)
- (menu-bar-mode -1)
- (scroll-bar-mode -1)
Turns off bad stuff.
Command:
(column-number-mode 1)
Line number and column position in the Mode line.
Command:
(electric-pair-mode 1)
Automatically close the parentheses, brackets and other delimiters.
Command:
(recentf-mode 1)
With this enabled Emacs will keep track of previously opened files.
This is enough to make your Emacs look like a decent editor.
Backup directory
Next we could create a directory for backups of edited files. Ensure the directory exists.
Emacs Lisp
(setq backup-directory-alist `(("." . ,(concat user-emacs-directory "backups"))))
Default identation
Emacs Lisp
(setq-default tab-width 4)
(setq-default indent-tabs-mode 4)
(setq-default c-basic-offset 4)
Package manager
And now the most important part, configuration of the package manager.
Emacs Lisp
(require 'package)
(setq package-archives
'(("melpa" . "https://melpa.org/packages/")
("nongnu" . "https://elpa.nongnu.org/nongnu/")
("elpa" . "https://elpa.gnu.org/packages/")))
(package-initialize)
(unless package-archive-contents
(package-refresh-contents))
(unless (package-installed-p 'use-package)
(package-install 'use-package))
(require 'use-package
(setq use-package-always-ensure t)
use-package simplifies management of the plugins by keeping everything simple and configured in one place.
Without having it configured one would need to have a lot of configurations done just to have a couple of packages
present in the configuration directory.
You can think of it as
lazy.nvim of Emacs.
- melpa :: repository built from upstream sources and managed by package.el
- elpa.nongnu :: archive of packages written in Emacs Lisp by NonGNU people for GNU Emacs
- elpa.gnu :: packages written in ELisp by GNU for GNU Emacs
Now that the databases are all set up we can proceed with the plugins.
Remember: to install a package you can always run
M-x package-install "name"
Theme
I myself am a minimalistic person, so I did stick to
voidlight-theme with
JetBrains Mono.
Emacs Lisp
(require 'voidlight-theme)
Rust
Emacs Lisp
(require 'rust-mode)
(defun sp1ff/rust/mode-hook ()
"rust-mode hook."
;; Style per the Rust Style Guide:
;; "https://github.com/rust-lang-nursery/fmt-rfcs/blob/master/guide/guide.md"
(setq indent-tabs-mode nil
tab-width 4
c-basic-offset 4
fill-column 100))
(use-package rust-mode
:ensure t
:hook (rust-mode . sp1ff/rust/mode-hook)
:config
(let ((dot-cargo-bin (expand-file-name "~/.cargo/bin")))
(setq rust-rustfmt-bin (concat dot-cargo-bin "rustfmt")
rust-cargo-bin (concat dot-cargo-bin "cargo")
rust-format-on-save t)))
Evil mode
Evil mode is a bridge for Emacs that brings the VIM-motions into the editor.
There is also
Viper-mode and
Vimpulse. However, both are either outdated or lack functionality.
Viper-mode is a built-in VI emulation mode, while
Vimpulse adds some advanced features to the Viper-mode.
Evil mode is a complete emulation of the VIM motions.
Emacs Lisp
(setq evil-want-keybinding nil)
(require 'evil)
(evil-mode 1)
(use-package evil-collection
:ensure t
:after evil
:init
(evil-collection-init))
(evil-define-key 'normal dired-mode-map "gu" 'revert-buffer)
(evil-set-undo-system 'undo-redo)
Magit
Magit is a git version control plugin for Emacs. It provides a nice GUI, and my key reason to choose this particular git interface
was a good documenatation and always having a list of controls present before eyes.
There are some other quite good alternatives, such as vc-mode. However, Magit's power comes with its feature-rich functionality. I don't
like to have 100 plugins that do the exact same thing differently (only if there is a reason behind it).
Magit has it all.
Emacs Lisp
(use-package magit
:ensure t)
A repository can always be initialized using
C-x g.
All of its configuration can be found here:
magit/Variable-Index
Org mode
In a modern world many people tend to use programs such as
Obsidian to have their thoughts placed in one place.
And it is understandable, since these programs provide quite nice out-of-box functionality. However, if we want
to have everything together then we should also consider not using millions of programs to just write text files, right?
Org mode is a built-in outline mode that provides a way to write and organize files for various topics.
It is pre-installed since the Emacs 23, and so you should just configure it correctly.
After setting it all up I would like to proceed to another plugin that is usually presented like holy-feature of Obsidian, but can be used in Emacs -
Org-roam-ui, which is a frontend for
Org Roam and Org mode and it depends on
websocket.
Switching to Org mode can be done by
M-x org-mode.
Org Roam
It is a feature-rich plain-text knowledge management system that completes Org mode functionality.
Emacs Lisp
(setq org-agenda-files '("~/org/"))
(setq org-log-done 'time)
(add-to-list 'auto-mode-alist '("\\.org\\'" . org-mode))
(add-hook 'org-mode-hook 'org-indent-mode)
(org-roam-directory "~/org")
Org Roam UI
Obsidian-like graph-view for Org Roam.
Emacs Lisp
(use-package org-roam-ui
(:host github :repo "org-roam/org-roam-ui" :branch "main" :files ("*.el" "out"))
:after org-roam
:config
(setq org-roam-ui-sync-theme t
org-roam-ui-follow t
org-roam-ui-update-on-save t
org-roam-ui-open-on-start t))
You can also adjust this hook:
Emacs Lisp
:hook (after-init . org-roam-ui-mode)
Then to explore, run
M-x org-mode-ui-mode, and visit
localhost:35901 in your
browser.
Personal preferences
Emacs Lisp
(use-package dired-open
:config
(setq dired-open-extensions '(("pdf" . "zathura")
("mkv" . "mpv"))))
Afterword
Emacs is not just a text editor. Emacs is an OS inside of an OS. You can do
everything with it once you start digging below the surface. I have started
learning functional languages way before trying out Emacs. However, it was
Clojure and tiredness of Lua that made me consider switching.
I suggest the reader to start learning languages such as
Haskell,
Common Lisp,
Scheme or anything in between. There is also the branch of mathematics called
Lambda Calculus for those who are interested in the math side of things.
Emacs doesn't stop you from anything. Become the god of the editor.
Dream bigger, it would do you well.
- tenketokt
References
-
Lambda Calculus
-
Jonathan Blow
-
Tsoding
-
Valentin Ignatiev
-
ThePrimeagen
-
DistroTube
-
Arch Linux
-
Free Software, Free Society
-
Emacs Lisp Tutorial
-
Practical Emacs Lisp
-
Tsoding: Configuring Emacs on My New Laptop
-
tenketokt: MIT Mad-Prophet Predicts the Future in 1980
-
ELisp Manual
-
Zathura
-
MPV
-
VC-Mode Meets Magit - or Why I Finally Gave In!
-
vimpulse
-
Viper
-
The Rust Programming Language
-
lazyvim
-
Scheme. Pairs and Lists