a practical guide on the different features
Created: 2026-10-15 Thu 16:59
Emacs is the main software I use since 2002. You never feel you understand it enough and every time I tried something different I really couldn't perceive the advantage and went back to emacs. I even asked claude/cursor what would be the best tool for writing software with interactive sessions, organize your workflow as per org mode, coordinate the agents and act on the system at the same time and the bot responded: keep using emacs. LLMs helped me to improve the way I use it and now it's hard to exit it since all the agents, programs and news are integrated here and the different outputs get piped together.
My current configuration is .emacs.el. emacs.el is a thin core (STYLE, environment, PACKAGES, INTERFACE, language-modes, BACKUP,
WELCOME, SPELLING); the per-topic config lives in sibling modules in
~/.emacs.d/ loaded after the bootstrap:
org-mode.el (org-roam/md-roam/org-capture), coding.el (corfu/eglot/minuet,
python, node, platformio), llm.el (ollama-buddy/gptel/ellama),
multimedia.el (lilypond).
#+BEGIN: columnview :id global :link t :indent t :for#+BEGIN: columnview :id global :link t :indent t :for|||||<10>|
Emacs grows with you. IDEs have many configuration options but they don't give you the freedom to adapt to your workflow and many options are limited. Especially the UI doesn't let you create a really readable interface were you just focus on what information is needed. Some useful examples.
Emacs is the most versatile text editor, it has an overwhelming option of integration and configuration. It takes time to configure it but the productivity speed is unbeatable. I tried IDEs but I find the UI too confusing to concentrate and they force you to work in a single manner. Emacs is the best option for .org too:
More on
There are multiple configurations to include LLMs in emacs:
More on and emacs and LLMs.
variables and functions don't share the same namespace (they can have the same name) You can evaluate the lisp code by hitting [C-x C-e] at the end of the last parenthesis.
(defun sing-fun () ;;(interactive) (insert "ciccia")
(run-hooks 'fun-hook))
(add-hook 'fun-hook (lambda () (insert "3")))
I can set variables with (setq ciccia "ciccia") or groups (customize-group org-export-hugo)
define (defun insert-numbers () "insert a sequence of numbers" (interactive) (insert "\n 1 2 3")) and execute function (insert-numbers)
(insert "\nciccia " "ciccio") ciccia ciccio
(eq :foo ':foo)
(defun test-fun (arg) (+ arg 2))
(defun make-odd (arg) (if (% arg 2) (+ arg 1) arg))
(advice-add 'test-fun :filter-return #'make-odd)
(test-fun 4)
(defun insert-code-block ()
(insert "\n#+begin_src :exports both :results output replace \n\n#+end_src"))
(keymap-set c-mode-map "C-M-b" 'insert-code-block)
import re
;;; pandoc-mode-autoloads.el --- automatically extracted autoloads (do not edit) -*- lexical-binding: t -*-;; Generated by the `loaddefs-generate' function.;; This file is part of GNU Emacs.;;; Code:(provide 'pandoc-mode-autoloads);; Local Variables:;; version-control: never;; no-byte-compile: t;; no-update-autoloads: t;; no-native-compile: t;; coding: utf-8-emacs-unix;; End:;;; pandoc-mode-autoloads.el ends here
find keymap associated to lisp command: [C-h w] (command-name)
(describe-key (kbd "M-:"))
(global-set-key (kbd "C-Alt-d") 'delete-region)
(keymap-global-set "C-Alt-d" 'delete-region) ;; newer version
Check all key mapping in org-mode
(describe-keymap 'org-mode-map)
(describe-keymap magit-mode-map)
KEYMAP\ OBJECT\ \(no\ variable\)\ 42
Edit variable definition [C-h v]
(describe-function 'package-install)
(custom-set-variables
'(ansi-color-faces-vector
[default default default italic underline success warning error])
'(ansi-color-names-vector
["#000044" "#d55e00" "#009e73" "#f8ec59" "#0072b2" "#cc79a7" "#56b4e9" "white"])
'(custom-enabled-themes '(misterioso))
'(custom-safe-themes
'("43b0db785fc313b52a42f8e5e88d12e6bd6ff9cee5ffb3591acf51bbd465b3f4" "47aaf1021bdd742a2f91448f089ad6fe95028c9557638d4333452ce85da980de" default)))
You can change the theme by hitting [C-x C-e] after the last parenthesis
(load-theme 'misterioso) (load-theme 'tango-dark) (load-theme 'wheatgrass) (load-theme 'whiteboard) (load-theme 'light-blue) (load-theme 'deeper-blue) (load-theme 'modus-vivendi) (load-theme 'leuven-dark)
(org-modern-mode 1)
(set-frame-parameter (selected-frame) 'alpha 100) (set-frame-parameter (selected-frame) 'alpha 80)
full screen [f11]
We can use icons as we write (all-the-icons-wicon "tornado" :face 'all-the-icons-blue)
(use-package all-the-icons
:if (display-graphic-p))
(all-the-icons-install-fonts)
(all-the-icons-insert-material "account-balance")
(use-package all-the-icons-dired
:hook (dired-mode . all-the-icons-dired-mode))
Prettify symbols
(setq prettify-symbol-alist
(mapcan (lambda (x) (list x (cons (upcase (car x)) (cdr x))))
'(("bug" . ?)
("lambda" . ?λ)
("software" . ?)
))
)
(defconst lisp--prettify-symbols-alist
'(("lambda" . ?λ)))
(add-hook 'emacs-lisp-mode-hook
(lambda ()
(push '(">=" . ?≥) prettify-symbols-alist)))
(prettify-symbols-mode 1)
A bug is a when a software fails.
To use the packages listed here open this org file in emacs and set straight.el to test the experimental packages
;; If you're using straight.el, keep package.el from auto-loading.
(add-to-list 'load-path (expand-file-name "~/.emacs.d/lisp/"))
(setq package-enable-at-startup nil)
;; Bootstrap straight.el
(defvar bootstrap-version)
(let ((bootstrap-file
(expand-file-name "straight/repos/straight.el/bootstrap.el"
user-emacs-directory))
(bootstrap-version 7))
(unless (file-exists-p bootstrap-file)
(with-current-buffer
(url-retrieve-synchronously
"https://raw.githubusercontent.com/radian-software/straight.el/develop/install.el"
'silent 'inhibit-cookies)
(goto-char (point-max))
(eval-print-last-sexp)))
(load bootstrap-file nil 'nomessage))
(straight-use-package 'use-package)
(setq straight-use-package-by-default t)
(use-package package)
(setq package-archives
'(("melpa" . "https://melpa.org/packages/")
("melpa-stable" . "https://stable.melpa.org/packages/")
("gnu" . "https://elpa.gnu.org/packages/")
("nongnu" . "https://elpa.nongnu.org/nongnu/")))
(setq package-archive-priorities
'(("melpa-stable" . 10)
("gnu" . 7)
("melpa" . 5)))
(setq package-check-signature 'allow-unsigned)
(setq package-archive-contents nil)
(package-initialize)
(advice-add 'package-refresh-contents :override
(lambda (&rest _)
(message "Package refresh skipped (run M-x list-packages to refresh archives)"))
'((name . no-refresh-on-init)))
(setq byte-compile-warnings '(cl-functions))
(use-package cl-lib)
(let ((default-directory "~/.emacs.d/"))
(normal-top-level-add-subdirs-to-load-path))
(add-to-list 'load-path "~/.emacs.d/lisp/")
There is a rich collections of software which can be added to emacs.
We can use flashcards for learning
(use-package anki-editor
:defer t
:straight (:host github :type git :repo "anki-editor/anki-editor"))
We can then use anki properties
* card:PROPERTIES::ANKI_DECK: English:ANKI_NOTE_TYPE: Basic (and reversed card):ANKI_TAGS: vocab idioms:END:** Front(it's) raining cats and dogs** Backit's raining very hard* basic:PROPERTIES::ANKI_NOTE_TYPE: Basic:END:** sect1* extract from field value:PROPERTIES::ANKI_NOTE_TYPE: Basic:ANKI_FIELD_FRONT: Can one define an anki-field inside an org-mode property?:ANKI_PREPEND_HEADING: nil:END:** FrontNotice that property fields will override subheading fields.This block will be skipped
(setq W 250
H 250
BG #xFF8B00FF)
(setq ball-canvas-vec (make-vector (* W H) BG))
(setq ball-canvas `(image :type canvas :id rect :data-width ,W :data-height ,H :data ,ball-canvas-vec))
(insert (propertize "#" 'display ball-canvas))
(defun draw-ball (cx cy color)
(let ((r2 (* ball-radius ball-radius))) ; Radius squared
(dotimes (y-off (1+ (* 2 ball-radius)))
(dotimes (x-off (1+ (* 2 ball-radius)))
(let* ((px (+ cx (- x-off ball-radius)))
(py (+ cy (- y-off ball-radius)))
(dx-local (- px cx)) ; Horizontal distance from center
(dy-local (- py cy)) ; Vertical distance from center
(dist2 (+ (* dx-local dx-local) (* dy-local dy-local)))) ; squared distance using pythagorean theorem
;; check if pixel is inside the circle AND inside the canvas bounds
(when (and (>= px 0) (< px W) (>= py 0) (< py H) (<= dist2 r2))
(aset ball-canvas-vec (+ (* py W) px) color)))))))
(defun ball-bounce ()
(setq bx (+ bx dx) by (+ by dy))
;; compare with ball-radius so the *edge* of the circle bounces, not the center.
(when (<= bx ball-radius) (setq dx (abs dx)) (setq bx ball-radius))
(when (>= bx (- W ball-radius)) (setq dx (- (abs dx))) (setq bx (- W ball-radius)))
(when (<= by ball-radius) (setq dy (abs dy)) (setq by ball-radius))
(when (>= by (- H ball-radius)) (setq dy (- (abs dy))) (setq by (- H ball-radius))))
(setq ball-frame 0)
(setq max-ball-frames 2000)
(defun ball-loop ()
(if (>= ball-frame max-ball-frames)
(cancel-timer ball-timer)
(setq ball-frame (1+ ball-frame))
(draw-ball bx by BG) ; make old ball invisible (turn it into background color)
(ball-bounce)
(draw-ball bx by ball-color)
(canvas-refresh ball-canvas t)))
(setq ball-timer (run-with-timer 0 0.02 'ball-loop))
(defvar ob-shader-start (float-time))
(defvar ob-shader-canvas '(image :type canvas :id ob-shader :data-width 300 :data-height 300))
(defvar ob-shader-timer (run-with-timer
nil (/ 1 60.0)
(lambda ()
(ob-shader-render ob-shader-canvas (- (float-time) ob-shader-start))
(canvas-refresh ob-shader-canvas))))
(setq backup-inhibited t
create-lockfiles nil
auto-save-default nil
auto-save-no-message t
auto-save-list-file-prefix nil
org-confirm-babel-evaluate nil)
(defun org-babel-execute:shader (body _params)
(ob-shader-load (format "
precision mediump float;
varying vec2 vUV;
uniform float t;
void main() {
vec2 p = vUV * 2.0 - 1.0;
float x = p.x, y = p.y, a = atan(y, x), r = length(p);
float R, G, B;
%s
gl_FragColor = vec4(B, G, R, 1.0);
}
" body)))
(font-lock-add-keywords 'org-mode `((": {SHADER}" 0 '(face nil display ,ob-shader-canvas))) t)
(shell-command (format "gcc -O2 -I%ssrc ob-shader.c -o /tmp/ob-shader.so -lGL -lEGL -fPIC -shared"
(shell-quote-argument source-directory)))
(module-load "/tmp/ob-shader.so")
(add-hook 'org-mode-hook
(lambda ()
(when (string-search "ob-shader.org" (buffer-file-name))
(with-silent-modifications
(org-babel-execute-buffer)))))
R = (cos(210.0*x*y + 5.0*t) + 1.0) / 2.0;
G = (cos(200.0*x*y + 5.0*t) + 1.0) / 2.0;
B = (cos(10.0*x*y + 5.0*t) + 1.0) / 2.0;
{SHADER}
float z = sin(a * 10.0 + r * 15.0 - t * 5.0);
R = G = 0.5 + 0.5 * sin(z + t * 0.1);
B = 0.5 + 0.4 * sin(10.0*z + t * 0.2);
{SHADER}
;; sudo apt-get install -y autoconf automake gcc libpng-dev libpoppler-dev libpoppler-glib-dev libz-dev make pkg-config
(use-package pdf-tools
:defer t
:commands (pdf-loader-install)
:mode "\\.pdf\\'"
:bind (:map pdf-view-mode-map
("j" . pdf-view-next-line-or-next-page)
("k" . pdf-view-previous-line-or-previous-page)
("C-=" . pdf-view-enlarge)
("C--" . pdf-view-shrink))
:init (pdf-loader-install)
:config (add-to-list 'revert-without-query ".pdf"))
(add-hook 'pdf-view-mode-hook #'(lambda () (interactive) (display-line-number-mode -1 )))
Multiple named workspaces
(use-package perspective)
Document reader for emacs
(use-package reader
:straight '(reader :type git :host codeberg :repo "MonadicSheep/emacs-reader"))
Set a splash screen (dashboard-open)
(use-package dashboard
:ensure t
:config
(dashboard-setup-startup-hook))
;; (setq initial-buffer-choice 'dashboard-open)
(setq dashboard-banner-logo-title "intertino dell'anticolo")
(setq dashboard-startup-banner "logo-ansi-truecolor")
(setq dashboard-center-content t)
(setq dashboard-vertically-center-content t)
(setq dashboard-show-shortcuts nil)
(setq dashboard-items '((recents . 5) (bookmarks . 5) (projects . 5) (agenda . 5) (registers . 5)))
(setq dashboard-item-shortcuts '((recents . "r") (bookmarks . "m") (projects . "p") (agenda . "a") (registers . "e")))
(setq dashboard-startupify-list '(dashboard-insert-banner dashboard-insert-newline dashboard-insert-banner-title
dashboard-insert-newline dashboard-insert-navigator dashboard-insert-newline
dashboard-insert-init-info dashboard-insert-items dashboard-insert-newline
dashboard-insert-footer))
(setq dashboard-navigation-cycle t)
(setq dashboard-display-icons-p t)
(setq dashboard-icon-type 'all-the-icons)
(add-to-list 'dashboard-items '(agenda) t)
(setq dashboard-week-agenda t)
(add-to-list 'load-path "~/.emacs.d/lisp/econky")
(require 'econky)
(defun my/setup-econky-frame (frame)
"Configura o layout 80/20 ao abrir um novo frame via emacsclient."
(with-selected-frame frame
(let ((econky-buffer (get-buffer-create "*econky*")))
(econky-start)
(let ((window-side (split-window-right (- (window-total-width)
(/ (window-total-width) 5)))))
(switch-to-buffer "*scratch*")
(set-window-buffer window-side econky-buffer)
(set-window-dedicated-p window-side t)))))
(add-hook 'after-make-frame-functions #'my/setup-econky-frame)
We can call ddgs for web search
# search results
pip install -U ddgs
ddgs text -k "ffmpeg interpolate between frames"
deft is a package for browsing plain test notes. It is useful to rename variables
(use-package deft ;;for quickly browsing, filtering, and editing directories of plain text notes
:after org
:bind
("C-c n d" . deft)
:custom
(deft-recursive t)
(deft-use-filter-string-for-filename t)
(deft-default-extension "org")
(deft-directory org-roam-directory))
denote is a note taking package easing the naming of files and linked sources doc.
(use-package denote ;;namespace and metadata for files
:ensure t
:hook (dired-mode . denote-dired-mode)
:bind
(("C-c n n" . denote)
("C-c n r" . denote-rename-file)
("C-c n l" . denote-link)
("C-c n b" . denote-backlinks)
("C-c n d" . denote-dired)
("C-c n g" . denote-grep))
:config
(setq denote-directory (expand-file-name "~/lav/src/spiega/project/"))
(denote-rename-buffer-mode 1))
We install some spell checkers and the integration with them
sudo apt install aspell-fr aspell-de aspell-it hunspell hunspell-it
(cond
((executable-find "hunspell")
(setq ispell-program-name "hunspell")
(ispell-set-spellchecker-params)
(setq ispell-local-dictionary "en_US,it_IT")
(ispell-hunspell-add-multi-dic "en_GB,it_IT")
(setq ispell-local-dictionary-alist
'(("en_US" "[[:alpha:]]" "[^[:alpha:]]" "[']" nil ("-d" "en_US") nil utf-8))))
((executable-find "aspell")
(setq ispell-program-name "aspell")
(setq ispell-extra-args '("--sug-mode=ultra" "--lang=en_US"))))
(add-hook 'text-mode-hook 'flyspell-mode)
;; For markdown, use flyspell but skip code blocks and URLs
(add-hook 'markdown-mode-hook
(lambda ()
(setq flyspell-generic-check-word-predicate
(lambda ()
(not (or (markdown-code-block-at-point-p)
(markdown-inline-code-at-point-p)
(markdown-link-p)))))
(flyspell-mode 1)))
[M-x] opens minibuffer [M-|] shell command on region [M-:] eval eval-region
Registers and bookmarks in emacs are temporary information you can save in your session.
The useful commands for registers and bookmarks are:
A bookmark is persistent
(set-register ?h '(file . "~/lav/src/spiega/template/header_org.org"))
(file . ~/lav/src/spiega/template/header_org.org)
#+name : keybindings
(describe-key "C-a")
C - a is undefined
How to use dired
(use-package emms)
Shell in a buffer
Insert a shell command elisp:(org-insert-structure-template%20%22s%22) [C-c C-x s s]
(("n" . "notes") ("a" . "export ascii") ("c" . "center")("C" . "comment") ("e" . "example") ("E" . "export")("h" . "export html") ("l" . "export latex") ("q" . "quote")("s" . "src") ("v" . "verse"))
You can start a web browser elisp:(eww%20'%22intertino.it%22)
We can use a media player by launching elisp:(emms)
(use-package emms
:config (setq emms-add-directory-tree "~/Music/")
)
Retrieve the news feed elisp:(elfeed)
(use-package elfeed)
(setq elfeed-feeds
'(("https://nullprogram.com/feed/" blog )
("https://planet.emacslife.com/atom.xml" )
("https://www.rainews.it/rss" rai)
("https://yhetil.org/emacs-devel/new.atom" emacs lists devel)
("https://yhetil.org/emacs-bugs/new.atom" emacs lists bugs)
("https://sachachua.com/blog/category/emacs-news/feed/" emacs news)))
(setf url-queue-timeout 30)
30
We can use mu4e package.
(use-package mu4e)
Voice assistant and dictation
;; https://git.sr.ht/~lepisma/emacs-speech-input
(use-package esi-dictate
:straight (:host github :type git :repo "lepisma/emacs-speech-input")
:custom
(esi-dictate-dg-api-key "<DEEPGRAM-API-KEY>")
(esi-dictate-llm-provider <llm-provider-using-llm.el>)
; (esi-dictate-llm-prompt )
; Here is an example configuration for using OpenAI's
; (esi-dictate-llm-provider (make-llm-openai :key "<OPENAI-API-KEY>" :chat-model "gpt-4o-mini"))
:bind (:map esi-dictate-mode-map
("C-g" . esi-dictate-stop))
:config
(setq llm-warn-on-nonfree nil)
:hook (esi-dictate-speech-final . esi-dictate-fix-context))
Could not install
(use-package voicemacs
:straight (:host github :type git :repo "jcaw/voicemacs" )
)
t
Collection of tools for multimedia files
We launch
edraw:file=../../f/f_twin/edraw_res.svg
by hitting [C-c C-o] on the link and finish with [C-c C-c]
or (edraw)
[[*Example][edraw:file=../../f/f_twin/edraw_res.svg]]
<edraw:file=../../f/f_twin/edraw_res.svg>
(use-package edraw-org
:straight (:host github :type git :repo "misohena/el-easydraw")
:config (edraw-org-setup-default)
(edraw-org-setup-exporter))
(when (use-package 'dbus nil t)
(defun inkscape-test ()
"Test Inkscape D-Bus integration - creates a new desktop with a rectangle."
(interactive)
(let* ((desktop (dbus-call-method
:session "org.inkscape" "/org/inkscape/application"
"org.inkscape.application" "desktop_new"))
(rect (dbus-call-method
:session "org.inkscape" desktop
"org.inkscape.document" "rectangle"
:int32 100 :int32 100 :int32 100 :int32 100)))
(message "Created desktop: %s, rectangle: %s" desktop rect))))
Dependencies
sudo apt install lilypond timidity gv xpdf
Some useful commands (LilyPond-electric-bar) counts within bars
LilyPond-autocompletion (S-<iso-lefttab>)
LilyPond-command-all-midi (C-c C-<return>) Play midi corresponding to the current
LilyPond-command-buffer (C-c C-b) Run LilyPond on buffer.
LilyPond-command-current-midi (C-c <return>) Play midi corresponding to the current document.
LilyPond-command-formatmidi (C-c m) Format the midi output of the current document.
LilyPond-command-lilypond (C-c C-l) Run lilypond for the current document.
LilyPond-command-master (C-c C-c) Run command on the current document.
LilyPond-command-view (C-c C-s) View the output of current document.
LilyPond-music-glossary-info Launch Info for music-glossary.
LilyPond-quick-insert-mode (C-c q) Insert notes with fewer key strokes by using a simple keyboard piano.
LilyPond-what-beat (C-c b) Returns how much of a measure lies between last measaure ’|’ and point.
(require 'lilypond-mode)
(autoload 'LilyPond-mode "lilypond-mode" "LilyPond Editing Mode" t)
(add-to-list 'auto-mode-alist '("\\.ly$" . LilyPond-mode))
(add-to-list 'auto-mode-alist '("\\.ily$" . LilyPond-mode))
(add-hook 'LilyPond-mode-hook (lambda () (turn-on-font-lock)))
(add-hook 'LilyPond-mode-hook (function (lambda () (add-to-list 'LilyPond-command-alist '("OpenPDF" "open '%f'")))))
(defvar ac-lilypond-identifiers
'((candidates . (lambda () (all-completions ac-target LilyPond-identifiers)))))
(defvar ni-LilyPond-keywords
(mapcar (lambda (x) (concat "\\" x)) LilyPond-keywords))
(defvar ac-lilypond-keywords
'((candidates . (lambda () (all-completions ac-target ni-LilyPond-keywords)))))
(defvar ac-lilypond-Creserved-words
'((candidates . (lambda () (all-completions ac-target LilyPond-Capitalized-Reserved-Words)))))
(defvar ac-lilypond-ncreserved-words
'((candidates . (lambda () (all-completions ac-target LilyPond-non-capitalized-reserved-words)))))
(provide 'init-lilypond)
We can connect a midi device to interact with lilypond
cd ~/lav/viudi/synth/rasp/soundfont
bash ./fluidsynth.sh
aconnect -o
aconnect 28 128
client 14: 'Midi Through' [type=kernel]
0 'Midi Through Port-0'
client 28: 'nanoKEY Fold' [type=kernel,card=3]
0 'nanoKEY Fold _ CTRL'
client 128: 'fluid0' [type=user,pid=173475]
0 'fluid0 '
(use-package org-media-note
:straight (:host github :type git :repo "yuchen-lea/org-media-note")
:init (setq org-media-note-use-cite t)
:hook (org-mode . org-media-note-mode)
:bind (
("H-v" . org-media-note-show-interface)) ;; Main entrance
:config
(setq org-media-note-screenshot-image-dir "~/Downloads/imgs/") ;; Folder to save screenshot
)
(org-media-note-show-interface)
(use-package mpv
:straight (:host github :type git :repo "kljohann/mpv.el")
)
(mpv-play "~/VideoProd/publicati/albio.mp4")
(start-process "midi-player" nil "timidity" "-Ow" "~/lav/viudi/mus/bolla/Corsa.midi")
;;(use-package midi-emacs)
(defun play-midi-fluidsynth (file soundfont)
"Play MIDI file with fluidsynth and soundfont."
(start-process "fluidsynth" nil "fluidsynth" soundfont file))
(defun play-midi-file (file)
"Play MIDI file using timidity."
(start-process "timidity" nil "timidity" file))
(play-midi-fluidsynth "~/lav/viudi/mus/bolla/Corsa.midi" "~/lav/Audio/sf2/Sonatina.sf2") (play-midi-file "~/lav/viudi/mus/bolla/Corsa.midi")
We can configure gnome to support using emacs.
gsettings list-recursively org.gnome.desktop.input-sources
gsettings set org.gnome.desktop.input-sources xkb-options "['hyper:menu']"
gsettings set org.gnome.desktop.input-sources xkb-options "['caps:hyper', 'insert:hyper']"
#xkeyboard-config
#sudo apt-get install gnome-tweak-tool
gnome-tweak-tools
How can we configure emacs for coding.
C-c UP or C-c DOWN - Move the current row up or down. C-c LEFT or C-c RIGHT - Move the current column left or right. C-c S-UP - Kill the current row. C-c S-DOWN - Insert a row above the current row. With a prefix argument, row line is created below the current one. C-c S-LEFT - Kill the current column. C-c S-RIGHT - Insert a new column to the left of the current one. C-c C-d - Re-align the current table (markdown-do). C-c C-c ^ - Sort the rows of a table by a specified column. This command prompts you for the column number and a sort method (alphabetical or numerical, optionally in reverse). C-c C-c | - Convert the region to a table. This function attempts to recognize comma, tab, and space separated data and then splits the data into cells accordingly. C-c C-c t - Transpose table at point.
Not working
(use-package markdown-export
:straight (markdown-export :type git :host github :repo "robert-zaremba/markdown-export")
:after markdown-mode)
A bit too heavy. Menu [C-c /]
(use-package pandoc-mode)
(add-hook 'markdown-mode-hook 'pandoc-mode)
We can use web-mode for css, js, json, php, html, node:
(use-package web-mode
:ensure t
:mode
(("\\.html\\'" . web-mode)
("\\.php\\'" . web-mode)
("\\.ts\\'" . web-mode)
("\\.tsx\\'" . web-mode)
("\\.css\\'" . web-mode)
("\\.js\\'" . web-mode)))
(setq web-mode-engines-alist
'(("php" . "\\.phtml\\'")
("blade" . "\\.blade\\."))
)
pnpm install typescript-language-server typescript
We can connect emacs to a language server (LSP) which runs externally checks on the code
(use-package lsp-mode
:commands (lsp lsp-deferred)
:hook ((prog-mode . lsp-deferred)
(python-mode . lsp)
(lsp-mode . lsp-enable-which-key-integration))
:custom (read-process-output-max (* 1024 1024))
:init
(setq lsp-completion-provider :none)
(setq lsp-keymap-prefix "C-c l")
(setq lsp-diagostic-provider :flycheck)
)
(use-package lsp-ui
:hook (lsp-mode . flycheck-mode)
;; :bind (:map flycheck-mode-map
;; ("M-n" flycheck-previous-error)
;; ("M-p" flycheck-next-error))
:custom (flycheck-display-error-delay .3))
| flycheck-mode | lsp-enable-which-key-integration |
Projectile helps with managing files in your projects, build, run, search, substitute…
We can:
(projectile-dispatch) (setq projectile-indexing-method 'hybrid) (setq projectile-enable-caching t)
(projectile-index-project-async) -> [Projectile] Async indexing has no effect while caching is disabled (define-key projectile-mode-map (kbd "s-,") 'projectile-command-map)
The live emacs.el builds coding assistance from three layers that do not collide because each owns a different moment of the interaction (see the autocompletion guide below):
The legacy Python completer stack (company, company-jedi, elpy, flymake+pylint) has been removed from emacs.el; only eglot + corfu + minuet remain active. Flycheck is still used for graphviz (dot) files.
Few references: gavinok
minuet talks to whatever LLM serving backend is up. Because the backend
can change over time (unsloth, LM Studio, llama.cpp, vLLM, ollama…), the
relevant knobs in emacs.el are the three minuet-openai-fim-compatible-options
plists and the model name:
:end-point:api-key"UNSLOTH_TOKEN" (minuet
resolves the env var at request time), or a placeholder like "TERM" for
backends that ignore auth.:model| Backend | Typical endpoint | Auth | Notes |
|---|---|---|---|
| unsloth | http://localhost:8889/api/inference/completions |
Bearer UNSLOTH_TOKEN |
GGUF served via llama.cpp proxy; chat models give weak FIM output |
| LM Studio | http://localhost:1234/v1/completions |
none (placeholder) | OpenAI-compatible server UI |
| llama.cpp | http://localhost:8080/v1/completions |
none (placeholder) | llama-server / llama-cli --server |
| vLLM | http://localhost:8000/v1/completions |
depends on config | needs a server image with FIM support |
| ollama | http://localhost:11434/v1/completions |
none (placeholder) | OpenAI-compat layer; models like qwen2.5-coder |
| lemonade | http://localhost:13305/v1/completions |
none (placeholder) | OpenAI-compat layer; models like qwen2.5-coder |
Any switch is a one-line :end-point change (plus re-pulling/reloading the
model on the serving side) — the rest of minuet's config stays identical.
This is the same "bring your own backend" idea that ellama and ollama-buddy
use (local LLM clients).
This config is meant to run on laptops of very different power (a gaming laptop with a GPU, an old thin client, …). emacs.el detects the machine and reports the recommended stack, but never applies it automatically — the same emacs.el runs everywhere.
packages-recommended-for-hardware(:tier full|lite :packages (...) :auto-suggest t|nil :eglot-auto t|nil
:reason string).packages-report-hardwareM-x to re-show).packages-apply-recommendedM-x to apply the recommended tier.
Heuristic: >= 8 CPUs and >= 16 GB RAM => full, else lite. Override per
machine with machine/ai-stack (below) or the env var EMACS_AI_STACK=full|lite.
| tier | auto-suggest | eglot | packages | when |
|---|---|---|---|---|
| full | yes | auto | eglot corfu minuet | gaming/workstation or any box with a local LLM running |
| lite | no (manual) | manual | corfu dabbrev | old/thin laptop, visible UI lag, or no LLM available |
emacs.el stays identical on every laptop. A git-ignored
~/.emacs.d/machine.el (sibling of ellama_setup.el / gptel_tools.el, in
the repo under emacs/.emacs.d/) is loaded if present and overrides the
fallbacks. Copy the shipped template and set only what differs on this host:
machine/ai-stack"full" or "lite" (nil = auto from hardware).machine/minuet-host / machine/minuet-port / machine/minuet-model /
machine/minuet-api-key / machine/minuet-endpoint :: the minuet LLM
backend — host, port, model, auth, and the actual OpenAI-compatible FIM
route (different servers expose it at different paths).machine/ollama-url / machine/unsloth-urlellama_setup.el model
discovery.Because the end-point host/port/model now come from machine.el, switching backends on a given machine is a one-line edit in that file.
minuet never blocks Emacs or spams errors when the LLM is absent:
my/minuet-probe-backend TCP-probes the minuet endpoint (1s
timeout, asynchronous). Only if the server answers and the tier is full
does auto-suggest turn on.M-i stays quick and harmless); a
dead backend just yields a one-line message instead of a freeze.org-mode-hook and text-mode-hook (same gate: backend up and tier
full), so ghost-text works when writing notes/markdown too, and stays off
on the lite tier.M-x packages-apply-recommended.To keep a cold boot snappy the heavy / asynchronous work is deferred:
org-roamorg-roam-db-autosync-mode and md-roam-mode are
enabled on the first actual roam command (or M-x org-roam-db-sync), so the
DB is (re)built on demand instead of blocking cold boot.minuetellamastraight*straight-process* buffer is killed once
straight.el finishes (my/kill-straight-process-buffer on a 1-second start
timer), so it no longer lingers in the buffer list.
Instead of the default disabled splash screen you get a small custom
welcome buffer (*welcome*, M-x my/welcome-buffer) with the date, quick
commands, and clickable recent files (recentf, now enabled).
The previous session's buffers return lazily via desktop mode:
desktop-restore-eager is 0 so heavy modes load without blocking — the welcome
splash stays as the landing screen while buffers restore in the background.
Built-in:
Extensions (active):
(company and company-jedi were the older Python completer; removed from emacs.el.)
Minibuffer completion
(use-package vertico ;;minibuffer completion
:ensure t
:init (vertico-mode)
:bind (:map minibuffer-local-map
("<next>" . vertico-next-group)
("<prior>" . vertico-previous-group)))
vertico-previous-group
This is the autocompletion stack configured in the live emacs.el. It layers three tools that do not collide because each activates in a different context:
The key rule that keeps them from colliding: TAB means one thing at a time. When a minuet ghost is on screen, minuet-active-mode-map owns TAB (accept the AI suggestion); otherwise TAB goes to corfu; with no candidate at all, TAB just indents (tab-always-indent set to 'complete).
| key | context | action |
|---|---|---|
| TAB | corfu popup visible | corfu-insert (accept completion) |
| TAB | minuet ghost visible | minuet-accept-suggestion-line (accept first AI line) |
| M-a | minuet ghost visible | accept AI suggestion line |
| M-A | minuet ghost visible | accept whole AI suggestion |
| M-e | minuet ghost visible | dismiss AI suggestion |
| M-n / M-p | minuet ghost visible | next / previous AI suggestion |
| M-i | anywhere | minuet-show-suggestion (request AI completion now) |
| M-y | anywhere | minuet-complete-with-minibuffer (pick a completion) |
| M-x eglot | python / ts / js buffer | (re)start the language server manually |
The mouse also works: middle-click / mouse-2 on a corfu candidate inserts it. eglot adds its own context menu (mouse-3) with "Go to definition", "Rename", etc.
pyright (real LSP completion -> corfu)typescript-language-server for .ts/.tsx/.js/.jsx and web-modepython-mode-hook eglot-ensure) and offers candidates.corfu pops up the candidates. TAB or middle-click accepts one.minuet-active-mode-map takes over and TAB accepts the AI line instead.corfu-auto is deliberately OFF so corfu does not pop up competing suggestions while minuet is generating. corfu appears on demand (TAB), minuet appears automatically.
# Python LSPs (see requirements.txt)
pip install -r requirements.txt
# TypeScript LSP (global pnpm so node_modules stays out of this repo)
pnpm add -g typescript typescript-language-server
The binaries must be on exec-path; emacs.el already adds the pnpm global bin dir.
Legacy minibuffer-completion framework. Kept as an optional heavy fallback; currently commented out in emacs.el (see preferred setup).
(use-package helm ;;display completion
:straight t
:config
)
Lighter minibuffer-completion framework (companion of helm). Optional alternate; commented out in emacs.el.
Org-babel kernel support: run Jupyter notebooks / languages from Org source
blocks (where the jupyter executable is present).
(when (executable-find "jupyter")
(org-babel-do-load-languages
'org-babel-load-languages
'((jupyter . t)))
(define-key jupyter-org-interaction-mode-map (kbd "C-s-c h") #'jupyter-org-hydra/body)
(define-key jupyter-org-interaction-mode-map (kbd "C-c h") nil))
eglot snippet ement
Flexible, order-independent minibuffer/completion matching (space-separated substrings match in any order). Complements vertico/consult.
(use-package orderless)
C language formatter
(use-package clang-format
:commands (clang-format-buffer clang-format-on-save-mode))
(add-hook 'c-mode-hook 'clang-format-on-save-mode)
(add-hook 'c++-mode-hook 'clang-format-on-save-mode)
(add-hook 'glsl-mode-hook 'clang-format-on-save-mode)
Corfu basic
(use-package corfu ;; autocompletion
:hook ((prog-mode . (lambda () (setq-local corfu-auto))
(shell-mode . corfu-mode)
(eshell-mode . corfu-mode))
:init
(global-corfu-mode)
)
| corfu-mode |
More advanced options
(use-package corfu
;; :disabled
:ensure t
;; Optional customization
:custom
(corfu-cycle t) ; Allows cycling through candidates
(corfu-auto t) ; Enable auto completion
(corfu-auto-prefix 10)
(corfu-auto-trigger ".")
(corfu-auto-delay 0.1)
(corfu-popupinfo-delay '(0.5 . 0.2))
(corfu-preview-current 'insert) ; insert previewed candidate
(corfu-preselect 'prompt)
(corfu-on-exact-match nil) ; Don't auto expand tempel snippets
;; Optionally use TAB for cycling, default is `corfu-complete'.
:bind (:map corfu-map
("M-SPC" . corfu-insert-separator)
("TAB" . corfu-next)
([tab] . corfu-next)
("S-TAB" . corfu-previous)
([backtab] . corfu-previous)
("S-<return>" . corfu-insert)
("RET" . nil))
:init
(global-corfu-mode)
(corfu-history-mode)
(corfu-popupinfo-mode)) ; Popup completion info
Enable key bindings
(global-set-key (kbd "M-/") 'dabbrev-expand)
(global-set-key (kbd "M-C-/") 'dabbrev-completion)
dabbrev-completion
Completion-at-point functions (capf) that compose and enrich the completion sources corfu displays (file/history/pcomplete wrappers, eglot wiring).
(use-package cape
:ensure t
:defer 10
:bind ("C-c f" . cape-file)
:init
;; Add `completion-at-point-functions', used by `completion-at-point'.
(defun my/add-shell-completion ()
(interactive)
(add-to-list 'completion-at-point-functions 'cape-history)
(add-to-list 'completion-at-point-functions 'pcomplete-completions-at-point))
(add-hook 'shell-mode-hook #'my/add-shell-completion nil t)
:config
;; Make capfs composable
(advice-add #'eglot-completion-at-point :around #'cape-wrap-nonexclusive)
(advice-add #'comint-completion-at-point :around #'cape-wrap-nonexclusive)
;; Silence then pcomplete capf, no errors or messages!
(advice-add 'pcomplete-completions-at-point :around #'cape-wrap-silent))
cape-file
Heuristic dabbrev-style expansion (tries many sources: dabbrev, kill-ring, file names, Lisp symbols…). Fast, offline, always available.
(use-package hippie-exp
:bind ([remap dabbrev-expand] . hippie-expand)
:commands (hippie-expand)
:custom
(dabbrev-ignored-buffer-regexps '("\\.\\(?:pdf\\|jpe?g\\|png\\)\\'"))
(dabbrev-upcase-means-case-search t)
:config
(setopt hippie-expand-try-functions-list
'(tempel-expand
try-expand-all-abbrevs
try-expand-dabbrev
try-expand-dabbrev-all-buffers
try-expand-dabbrev-from-kill
try-complete-lisp-symbol-partially
try-complete-lisp-symbol
try-complete-file-name-partially
try-complete-file-name)))
The alternates above (helm, ivy, orderless, the extra corfu options) are historical or optional. The preferred configuration for the machine you are on is decided by packages-recommended-for-hardware and applied manually with
packages-apply-recommended (see the subsections under code assistant):
full → eglot + corfu + minuet auto-suggest.lite → corfu + dabbrev, auto-suggest off (manual M-i only).Pick one tier and forget the rest; the others exist so the same emacs.el keeps working on every machine without edits.
Language models without IDE capabilities have limited added value. We can in principle build a coding assistant within emacs but I prefer by now to use different coding assistant with different capabilities. My workflow:
Well configured coding assistants don't need extreme performant models
More on
;; Ollama client helpers
(defun my/ollama-chat (prompt)
"Send PROMPT to Ollama and show the response in a temp buffer."
(interactive "sPrompt: ")
(let* ((url-request-method "POST")
(url-request-extra-headers
'(("Content-Type" . "application/json")))
(url-request-data
(encode-coding-string
(json-encode
`(("model" . "llama3.2")
("stream" . :json-false)
("prompt" . ,prompt)))
'utf-8))
(buf (url-retrieve-synchronously "http://localhost:11434/api/generate")))
(with-current-buffer buf
(goto-char (point-min))
(re-search-forward "^$")
(forward-char 1)
(let ((json-object-type 'alist))
(message "%s" (alist-get 'response (json-read)))))
(kill-buffer buf)))
;; Example command for Blender prompts
(defun my/blender-prompt (task)
"Prepare a Blender instruction from TASK."
(interactive "sBlender task: ")
(my/ollama-chat
(concat
"Rewrite this as a concise Blender instruction: "
task)))
(global-set-key (kbd "C-c o") #'my/ollama-chat)
(global-set-key (kbd "C-c b") #'my/blender-prompt)
;; Meta bindings for convenience
(global-set-key (kbd "M-a") #'save-buffer)
(global-set-key (kbd "M-s") #'save-buffer)
;; (unless (package-installed-p 'blender-mode)
;; (package-install 'blender-mode))
;; (use-package 'blender-mode)
Three Emacs clients talk to LLM backends; they are configured in
~/.emacs.d/llm.el (the LLM module) and ~/.emacs.d/.
gptel (code)~/.emacs.d/gptel_tools.el. Lazy-loaded.ellama (reasoning/docs)~/.emacs.d/ellama_setup.el, lazy-loaded by ellama-load-and-call.ollama-buddy (Unsloth)Key bindings (llm.el):
| Key | Command | Description |
|---|---|---|
C-c g |
gptel-load-and-call |
Load gptel+tools then run gptel |
C-c C-g |
gptel-send |
Send region/buffer to gptel |
C-c C-b |
my/gptel-blender-prompt |
Blender prompt helper (org-mode) |
gptel-load-and-call (llm.el) adds user-emacs-directory to the
load path, loads gptel_tools, binds C-c C-g, then invokes gptel. Tools
live in ~/.emacs.d/gptel_tools.el and allowed shell commands in
~/.emacs.d/gptel-allowed-commands.txt.
Lazy-loaded by ellama-load-and-call (llm.el) from
~/.emacs.d/ellama_setup.el. Supports dynamic provider discovery (defaults
to Unsloth's /api/inference/models, needs UNSLOTH_TOKEN) and a C-c e
prefix keymap. C-x e is left for macro-execute. See agent_call for
the full provider/command breakdown.
Configured in llm.el (the LLM module).
unsloth/gemma-4-E2B-it-GGUFlocalhost:~8889~ (the local Unsloth server)UNSLOTH_TOKEN (API key)t), streaming (t), auto-scroll (t),
max file size 10MB.| Key | Command | Description |
|---|---|---|
C-c o |
ollama-buddy-role-transient-menu |
Role-based chat menu |
C-c O |
ollama-buddy-transient-menu |
Full ollama-buddy menu |
Note: a very old ollama chat helper above historically bound C-c o to a
hardcoded llama3.2 call; that binding is superseded by ollama-buddy.
(use-package aidermacs
:ensure t
:bind (("C-c f x" . aidermacs-transient-menu))
:config (setenv "OLLAMA_API_BASE" "http://127.0.0.1:11434")
:custom
(aidermacs-default-chat-model 'code)
(aidermacs-default-model "ollama_chat/qwen3.5:9b"))
(defun my-aider-add-related (query)
(interactive "sSearch context: ")
(let* ((cmd (format "rg -l \"%s\"" query))
(files (split-string (shell-command-to-string cmd) "\n" t))
(aider-cmd
(concat "aider " (mapconcat #'identity files " "))))
(vterm)
(vterm-send-string aider-cmd)
(vterm-send-return)))
(defun elmo-python-code ()
"Function to integrate ELMo-like functionality using Python."
(interactive)
;; Assuming you have a Python script that interacts with ELMo and
returns results.
(let ((python-eldoc-buffer (make-temp-file "elmocode" nil t)))
(with-current-buffer python-eldoc-buffer
(insert "#!/usr/bin/env python3\n"
"import json\n"
"\n"
"def elmo_functionality(text):\n"
f" # Dummy ELMo function that returns a JSON
object. Replace with actual ELMo call.\n"
f" return {json.dumps({'result': 'processed
text'})}\n"
"\n"
"result = elmo_functionality('"'"'Your input text
here'"'"')\n")
(write-region nil nil python-eldoc-buffer)
(shell-command (concat "chmod +x " python-eldoc-buffer))
(call-process-shell-command
python-eldoc-buffer
:stderr nil
:read-only t
:treat-piped-output-as-errors nil))
;; Now you can access the result in your current buffer.
(message "ELMo function call completed. Result is accessible via `M-x elmo-python-code`")
))
(global-set-key (kbd "<f12>") 'elmo-python-code)
Emacs has many code REPL features were you can send lines of code and test the execution and the data transformation. I mainly use REPL with:
Source code into syntax tree
(use-package treesit-auto)
(use-package tree-sitter)
(use-package tree-sitter-org
:straight (:host github :type git :repo "Idorobots/tree-sitter-org" )
)
t
The provided Emacs configuration file demonstrates a comprehensive setup tailored for efficient coding and development. The work encompasses various features that enhance productivity, code management, and collaboration within the software lifecycle.
Overall, this configuration represents a robust setup for modern software development using Emacs. It integrates various features that cater to different aspects of the software lifecycle, from initial planning and coding to deployment and documentation, making it an essential tool for developers seeking an efficient and flexible development environment.
Entered on [2026-07-24 Fri 14:09]
file:///home/sabeiro/lav/src/spiega/emacs/emacs.el#MissingReference
Entered on [2026-07-24 Fri 14:18]
file:///home/sabeiro/lav/src/spiega/emacs/emacs.el#MissingReference
Entered on [2026-07-24 Fri 14:18]
file:///home/sabeiro/lav/src/spiega/emacs/emacs.el#MissingReference
Entered on [2026-07-24 Fri 14:18]
file:///home/sabeiro/lav/src/spiega/emacs/emacs.el#MissingReference
Entered on [2026-07-24 Fri 14:18]
Entered on [2026-07-24 Fri 14:19]
Entered on [2026-07-24 Fri 14:19]
Entered on [2026-07-24 Fri 14:21]
Entered on [2026-07-24 Fri 14:23]
Entered on [2026-07-24 Fri 14:23]
Entered on [2026-07-24 Fri 14:25]
Entered on [2026-07-24 Fri 14:26]
The provided Emacs configuration file demonstrates a comprehensive setup tailored for efficient coding and development. The work encompasses various features that enhance productivity, code management, and collaboration within the software lifecycle.
### Key Features:
Overall, this configuration represents a robust setup for modern software development using Emacs. It integrates various features that cater to different aspects of the software lifecycle, from initial planning and coding to deployment and documentation, making it an essential tool for developers seeking an efficient and flexible development environment.