Emacs voor NvChads

Dankwoord

Voordat ik begin met het artikel, wil ik vermelden dat ik zelf pas net begin met alles wat in dit artikel wordt beschreven. Als je suggesties hebt, stuur me dan gerust een e-mail. Ik ben zeer dankbaar aan deci (codeberg / telegram / disroot) voor de revisies en redactie. Ik ben van plan dit onderwerp in de toekomst verder uit te breiden. Veel leesplezier!

Dit artikel is oorspronkelijk geschreven in Org Mode. Je kunt het hier vinden: qlain.online/emacs.org Er is geen AI gebruikt bij het schrijven van deze tekst.

***

Een programma is vrije software als de gebruikers van het programma de vier essentiële vrijheden hebben:
1. De vrijheid om het programma te draaien zoals je wilt, voor elk doel (vrijheid 0).
2. De vrijheid om te bestuderen hoe het programma werkt, en het aan te passen zodat het jouw computerwerk doet zoals jij dat wilt (vrijheid 1). Toegang tot de broncode is hiervoor een voorwaarde.
3. De vrijheid om kopieën te verspreiden zodat je je naaste kunt helpen (vrijheid 2).
4. De vrijheid om kopieën van je gewijzigde versies aan anderen te distribueren (vrijheid 3).
Door dit te doen, geef je de hele gemeenschap de kans om te profiteren van jouw wijzigingen. Toegang tot de broncode is hiervoor een voorwaarde.
Free Software, Free Society: The Selected Essays of Richard M. Stallman

Hoofdstuk 1: Motivatie

Hallo internet. Ik moet toegeven dat ik een NVim-gebruiker ben en al meer dan twee en een half jaar op deze teksteditor vertrouw. Mijn oude configuratie was volledig door mijzelf vanaf nul geschreven; ik schreef zelfs mijn eigen Lua-plugins. Ik deed alles zelf.

Tegelijkertijd kijk ik bijna dagelijks YouTube. Bijna elke tech-blogger die ik volg gebruikt Emacs: Valentin Ignatev, Tsoding, DistroTube, Jonathan Blow. Hoewel ik ook veel contentmakers volg die over Vim/NVim praten, verschijnen ze minder vaak in mijn feed dan Mister Zozin met zijn streams van goddelijke kwaliteit.

Bloggers zijn leuk, maar ik was niet klaar voor een verandering. Ik was gewend aan vim-motions, een lichtgewicht TUI-teksteditor, de programmeertaal Lua en de fijne community. Ik hield erg van de FOSS-filosofie en de kracht die dat geeft. Men zou het communisme kunnen noemen, maar dan goed ontworpen communisme :).

In maart van dit jaar ontdekte ik Free Software, Free Society: The Selected Essays of Richard M. Stallman. Het lezen hiervan heeft mijn kijk op de softwarewereld enorm veranderd. Ik kan dit boek persoonlijk aanbevelen. Toch was ik nog niet klaar voor de overstap en bleef ik zwaar leunen op NVim voor dagelijkse taken en projecten. Totdat ik mijn Arch Linux-systeem moest herinstalleren.

Mijn allereerste Linux-distro was Arch, die ik in 2021 installeerde. Ik maakte toen de fout om een ext4-bestandssysteem te gebruiken, waarbij mijn root slechts 40 GB ruimte had, terwijl mijn home-partitie meer dan 400 GB was. Ik moest constant pakketten verwijderen om zelfs kleine updates te kunnen uitvoeren. Niet ideaal, toch? Ik besloot dat ik klaar was met dit gedoe en installeerde mijn systeem opnieuw, nadat ik alles had gebackupt.

Echter, ik vergat mijn ~/.config/nvim directory... Stel je mijn horror voor toen ik ontdekte dat al mijn oude werk was gewist. Verdrietig, maar wat kon ik doen? Ik schreef deze kleine configuratie om zaken in te stellen terwijl ik nog in de tty zat:

-- ~/.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."})

Het is basis, maar voldoende voor een tty.

Toen, na het installeren en configureren van alles op het systeem, stond ik voor de keuze om alles in NVim vanaf nul opnieuw te configureren. Maar eerlijk gezegd wilde ik niet meer dealen met installatiesystemen zoals lazy.nvim. Dat was voor mij een pijnlijke ervaring, en ik hield er niet van dat een C++ LSP meer dan 4 GB RAM verbruikte. Dat was het moment dat ik besloot dat het tijd was voor verandering. Ik installeerde Emacs.

Hoofdstuk 2: ELisp

Emacs is een zeer aanpasbare teksteditor die gebruikmaakt van een eigen geïnterpreteerde configuratietaal geschreven in C, genaamd Emacs Lisp (Elisp). Het is een goed ontworpen taal met een van de beste documentaties die ik tot nu toe heb gezien (op Rust na :3). De taal wordt primair gebruikt voor het configureren van Emacs en het wijzigen of uitbreiden van de functionaliteit, maar kan ook als algemene programmeertaal worden gebruikt.

Bijvoorbeeld voor scripts: #!/usr/bin/env -S emacs --script

Of door Emacs direct aan te roepen met -Q, --batch en/of --eval om Elisp-code vanuit de shell uit te voeren.

Voordat ik begin met de configuratie, wil ik de lezer eerst de basis van Elisp en het gebruik ervan bijbrengen. Er wordt ervan uitgegaan dat je de basis van programmeren al beheerst.

Onthoud: alles is een expressie van een bepaalde soort. Je kunt een expressie in Emacs evalueren door je cursor op de expressie te plaatsen en C-x C-e in te drukken.

Basistypes in ELisp

Primitieve types:

  • integers :: 69
  • floats :: 4.2
  • strings :: "i hate myself"
  • symbols :: 'symbol
  • conses (paren) :: (a . b)
  • vectors :: [1 2 3]
  • hash tables :: #s(hash-table data (key1 val1 key2 300))
  • byte-code functies :: worden geëvalueerd door de byte-code interpreter wanneer ze worden aangeroepen
  • functies
  • macros
  • primitive functions :: functies gedefinieerd in C. Ze worden ook 'subr' genoemd, afgeleid van "subroutine"
  • records :: custom types

Types die niet strikt genomen 'real types' zijn maar wel in de editor worden gebruikt:

  • characters :: ?a (eigenlijk integers)
  • lists :: '(a b c) (eigenlijk conses)

Types gerelateerd aan de teksteditor:

  • buffers
  • markers
  • windows
  • frames
  • mutexes
  • processeses
  • threads

Belangrijke vermeldingen:

  • t :: een logische "true" waarde.
  • nil :: kan drie dingen betekenen: een logische "false" waarde, een symbool met de naam 'nil', of een lege lijst.

Globale variabelen

(setq name "eto")
;; of
(defvar name "eto"
"Author's name.")

Deze kunnen buiten de huidige scope worden gebruikt.

  • setq :: Zie eintr/Using-setq
  • defvar :: Zie elisp/Defining-Variables

Lokale variabelen

(let ((x 10)
      (y 10))
  (+ x y))

Deze kunnen alleen worden aangeroepen vanuit het (let) blok.

  • let :: Zie eintr/let

Functies

(defun italian (name)
  "Greet in italian."
  (message "Ciao, %s.", name))

(italian "eto")

Functies retourneren de laatste expressie. De eerste string wordt een docstring genoemd en dient om het doel van de functie te definiëren.

  • defun :: Zie elisp/Defining-Functions

Lijsten (Lists)

Lijsten zijn essentieel in de Lisp-familie. Het is een niet-primitief type dat wordt gebruikt voor het representeren van een collectie elementen, en is geconstrueerd uit een reeks cons-cellen die aan elkaar gekoppeld zijn in een geordende lijst.

Functie: (consp 'object') Kan worden gebruikt om te controleren of een waarde een cons-cel is. Het retourneert t als ein object een cons-cel is en nil anders.

Een pair (paar) is een operatie die wordt gebruikt om twee willekeurige waarden te koppelen. Atoms (historisch afgeleid van "ondeelbaar") worden gebruikt om objecten te representeren die geen cons-cellen zijn.

Functie: (atom 'object') Wordt gebruikt om te controleren of een object een atom is. Retourneert t in het geval van een atom en nil anders.

Lisp-lijsten zijn standaard linked lists, omdat pointers impliciet zijn en er geen verschil is tussen cons-cellen die een waarde bevatten en cellen die naar een waarde wijzen. In Elisp zijn er verschillende soorten lijsten:

  • association lists
  • property lists

Er zijn veel manieren om een lijst te definiëren of ermee te werken. Hier de basis:

(setq mlist '(1 2 3)) ;; definieer een lijst van drie elementen
(B 3 4 "A")           ;; een lijst van vier elementen
()                    ;; lege lijst
nil                   ;; lege lijst
((A B nil))           ;; een lijst van één element - een lijst van twee elementen en één lege lijst

Interactie met een lijst:

(car mlist)            ;; het eerste element (index)
(cdr mlist)            ;; de rest van de lijst na het eerste element
(nth 'number mlist)    ;; retourneert het n-de element
(nthcdr 'number mlist) ;; retourneert de n-de cdr van de lijst
(take 'number mlist)   ;; retourneert de eerste n elementen van de lijst
(cons 'number mlist)   ;; voegt een element toe aan de kop (head)
(pop mlist)            ;; bekijkt de car van een lijst en verwijdert deze
(append mlist (list 'a 'b)) ;; combineert lijsten
(mapcar #'sqrt '(1 3))      ;; past een functie toe op elk element van een lijst

Er is ook de Dotted pair notatie die wordt gebruikt om CAR en CDR expliciet weer te geven. In deze notatie wordt een standaardlijst geschreven als (1 . (2 . (3 . nil))), wat equivalent is aan (1 2 3). Dotted pair notatie maakt het mogelijk om expliciet te beschrijven wie de CAR is en wie de rest van de CDR. In ons voorbeeld was 1 de CAR, terwijl 2 en 3 de CDR waren, waarbij nil als terminator van de lijst werd gebruikt. De lijstnotatie ((1 2 3)) maakt het echter makkelijker leesbaar.

Visueel kan dit worden weergegeven als: [ 1 | ] -> [ 2 | ] -> [ 3 | nil ]

  • lists :: Zie elisp/Lists

Lambda

Referentie: Sine approximation formula van Bhāskara I. \[ \sin \: x \approx \frac{16x(\pi - x)}{5\pi^2 - 4x(\pi -x)} \]

(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 een anoniem functie-object dat een waarde neemt voor evaluatie. We kunnen hier dieper op ingaan, maar dit is voor nu voldoende.

  • lambda calculus :: Zie wiki/Lambda_calculus
  • lambda expression :: Zie elisp/Lambda-Expressions

If statements (Voorwaardelijke instructies)

(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 de default case

;; 'when wordt gebruikt als een /if/ zonder else
(when (> age 22)
  (message "It's so over.")
  'over)

;; 'unless is het tegenovergestelde van when
(unless (< age 22)
  'so-back)

;; condities met 'and' en 'or' logische operatoren
(and (> 5 3) (< 10 20)) ;; beide moeten true zijn
(or (> 5 10) (< 10 20)) ;; minstens één moet true zijn
  • control structures :: Zie elisp/Control-Structures

Nu de basis is behandeld, kunnen we overgaan naar de configuratie.

Hoofdstuk 3: Configuratie

Natuurlijk worden eenvoudige zaken als syntax highlighting verwacht van een moderne editor, maar standaard Emacs biedt niet veel. Je kunt je eigen thema schrijven via M-x customize-create-theme (zie ook emacs/Custom-theme).

Ik vertrouw dagelijks op een aantal tools die mijn leven vereenvoudigen:

  • git :: versiebeheer
  • obsidian :: graph view en markdown rendering
  • nvim :: voor de vim motions

Emacs heeft voor elk van deze equivalenten:

  • magit :: magit/magit
  • org mode :: orgmode.org
  • org-roam :: orgroam.com
  • org-roam-ui :: github.com/org-roam-ui
  • evil :: emacs-evil/evil

Dit zijn de plugins die mijn productiviteit bij dagelijkse taken aanzienlijk hebben verbeterd. Daarnaast loop ik door de installatie van andere plugins zoals lang-mode en de algemene configuratie.

Initiële configuratie

Emacs kan worden geconfigureerd door het bestand ~/.emacs te bewerken. Het kijkt echter ook in ~/.emacs.d/init.el en ~/.config/emacs/init.el. Standaard wordt ~/.emacs gebruikt. Als je je configuratie in ~/.emacs.d of ~/.config/emacs wilt plaatsen, zorg er dan voor dat de standaard ~/.emacs niet bestaat.

De beste initiële configuratie:

;; ~/.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)

Laten we de regels doorlopen:

  • (setq custom-file "~/.emacs.d/custom.el"): Wanneer je pakketten installeert of wijzigingen doet buiten het configuratiebestand, neigt Emacs alles in het configuratiebestand te schrijven. Het is beter om je config te scheiden van de editor-wijzigingen.
  • (load-file "~/.emacs.d/custom.el"): Hier laden we het zojuist gedefinieerde custom bestand.
  • (setq inhibit-startup-screen t): We willen het standaard startscherm niet zien.
  • (global-visual-line-mode t): Schakelt visual-mode in voor alle buffers.
  • (global-display-line-numbers-mode): Toont regelnummers.
  • (show-paren-mode t): Visuele weergave van waar haakjes eindigen.
  • (tool-bar-mode -1), (menu-bar-mode -1), (scroll-bar-mode -1): Schakelt onhandige interface-elementen uit.
  • (column-number-mode 1): Toont regelnummer en kolompositie in de Mode line.
  • (electric-pair-mode 1): Sluit automatisch haakjes, brackets en andere delimiters.
  • (recentf-mode 1): Emacs houdt hiermee bij welke bestanden onlangs zijn geopend.

Backup directory

Vervolgens kunnen we een map aanmaken voor backups van bewerkte bestanden. Zorg dat de map bestaat.

(setq backup-directory-alist `(("." . ,(concat user-emacs-directory "backups"))))

Standaard inspringing (Default indentation)

(setq-default tab-width 4)
(setq-default indent-tabs-mode 4)
(setq-default c-basic-offset 4)

Package manager

Nu het belangrijkste deel: de configuratie van de 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 vereenvoudigt het beheer van plugins door alles simpel en op één plek geconfigureerd te houden. Je kunt het zien als de lazy.nvim van Emacs.

  • melpa: repository gebouwd uit upstream bronnen en beheerd door package.el.
  • elpa.nongnu: archief van pakketten geschreven in Emacs Lisp door NonGNU-personen voor GNU Emacs.
  • elpa.gnu: pakketten geschreven in ELisp door GNU voor GNU Emacs.

Onthoud: om een pakket handmatig te installeren kun je altijd M-x package-install "naam" uitvoeren.

Thema

Ik ben zelf een minimalist, dus ik gebruik voidlight-theme met 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 een brug voor Emacs die VIM-motions in de editor brengt. Er zijn ook Viper-mode en Vimpulse, maar deze zijn verouderd of missen functionaliteit. Viper-mode is een ingebouwde VI-emulatie, terwijl Vimpulse geavanceerde functies toevoegt aan Viper. Evil mode is een complete emulatie van de 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 een git versiebeheer-plugin voor Emacs. Het biedt een mooie GUI, en mijn belangrijkste reden om voor deze interface te kiezen was de goede documentatie en het feit dat er altijd een lijst met controls zichtbaar is. Er zijn andere goede alternatieven zoals vc-mode, maar de kracht van Magit zit in de uitgebreide functionaliteit.

(use-package magit
  :ensure t)

Een repository kan altijd worden geïnitialiseerd met C-x g. Alle configuraties zijn te vinden bij: magit/Variable-Index.

Org mode

In de moderne wereld gebruiken veel mensen programma's zoals Obsidian om hun gedachten op één plek te verzamelen. Dat is begrijpelijk, aangezien deze programma's goede out-of-the-box functionaliteit bieden. Echter, als we alles samen willen voegen, moeten we overwegen om niet miljoenen programma's te gebruiken om simpelweg tekstbestanden te schrijven.

Org mode is een ingebouwde outline-mode die een manier biedt om bestanden voor diverse onderwerpen te schrijven en te organiseren. Het is sinds Emacs 23 pre-installed, dus je hoeft het alleen correct te configureren.

Je kunt overschakelen naar Org mode via M-x org-mode.

Org Roam

Dit is een feature-rijk kennismanagementsysteem in platte tekst dat de functionaliteit van Org mode aanvult.

(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

Een Obsidian-achtige graph-view voor Org Roam. Dit is een frontend voor Org Roam en Org mode die afhankelijk is van websockets.

(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))

Je kunt ook deze hook aanpassen: :hook (after-init . org-roam-ui-mode)

Om dit te verkennen, voer je M-x org-mode-ui-mode uit en bezoek je localhost:35901 in je browser.

Persoonlijke voorkeuren

(use-package dired-open
  :config
  (setq dired-open-extensions '(("pdf" . "zathura")
                               ("mkv" . "mpv"))))

Slotwoord

Emacs is niet zomaar een teksteditor. Emacs is een OS binnen een OS. Je kunt er alles mee doen zodra je onder de oppervlakte begint te graven. Ik ben begonnen met het leren van functionele talen nog voordat ik Emacs probeerde, maar het was Clojure en een verzadiging van Lua die me deed overwegen over te stappen.

Ik raad de lezer aan om talen te leren zoals Haskell, Common Lisp, Scheme of iets daartussenin. Voor those die geïnteresseerd zijn in de wiskundige kant, is er ook de tak Lambda Calculus. Emacs houdt je nergens tegen. Word de god van de editor.

Droom groter, dat zal je goed doen. - tenketokt

Referenties

  • 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