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:
  1. The freedom to run the program as you wish, for any purpose (freedom 0).
  2. 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.
  3. The freedom to redistribute copies so you can help your neighbor (freedom 2).
  4. 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
~/.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

(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

(let ((x 10)
     (y 10))
(+ x y))
Can only be called from the inside of the *(let)* block.

+ let :: eintr/let



Functions

(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:
  1. association lists
  2. property lists

There are many ways to define, or interact with a list. I'll go over the basics:
(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:
(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
(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
(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.

  1. lambda calculus :: wiki/Lambda_calculus
  2. lambda expression :: elisp/Lambda-Expressions



If statements

(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.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.
(setq backup-directory-alist `(("." . ,(concat user-emacs-directory "backups"))))


Default identation

(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.
(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.
  1. melpa :: repository built from upstream sources and managed by package.el
  2. elpa.nongnu :: archive of packages written in Emacs Lisp by NonGNU people for GNU Emacs
  3. 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.
(require 'voidlight-theme)


Rust

(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.
(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.
(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.
(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.
(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:
: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

(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


  1. Lambda Calculus
  2. Jonathan Blow
  3. Tsoding
  4. Valentin Ignatiev
  5. ThePrimeagen
  6. DistroTube
  7. Arch Linux
  8. Free Software, Free Society
  9. Emacs Lisp Tutorial
  10. Practical Emacs Lisp
  11. Tsoding: Configuring Emacs on My New Laptop
  12. tenketokt: MIT Mad-Prophet Predicts the Future in 1980
  13. ELisp Manual
  14. Zathura
  15. MPV
  16. VC-Mode Meets Magit - or Why I Finally Gave In!
  17. vimpulse
  18. Viper
  19. The Rust Programming Language
  20. lazyvim
  21. Scheme. Pairs and Lists