This is my personal Emacs config. It is unfinished, experimental and always a work in progress. A lot of this config is inspired by – and, in many places, directly copied from – the work of others, and I am grateful to all the authors of the packages and Emacs masters who provided these wonderful tools.

emacs.dz is fantastic collection of public configs – a great place to discover different styles, workflows, and inspiration from the community.


Initializing Emacs Package Management

This is the basic boilerpate to get the package system in order.

(require 'package)

(add-to-list 'package-archives '("melpa" . "http://melpa.org/packages/") t)

(package-initialize)

;; If there are no archived package contents, refresh them
(when (not package-archive-contents)
  (package-refresh-contents))

Why I use use-package

use-package is a macro that allows you to write cleaner and more efficient Emacs configurations. It provides a way to manage package loading, configuration, and dependencies in a declarative manner. It makes your Emacs config cleaner, faster and easier to manage. quelpa builds and installs packages straight from Git URLs, which is useful for bleeding-edge packages and custom forks that are not available on MELPA. Here is how I bootstrap use-package and quelpa:

;; Bootstrap `use-package'
(unless (package-installed-p 'use-package)
  (package-refresh-contents)
  (package-install 'use-package))
(eval-when-compile (require 'use-package))
(setq use-package-always-ensure t)

;; setup quelpa
(use-package quelpa
  :ensure)

(quelpa '(quelpa-use-package :fetcher git
                             :url "https://github.com/quelpa/quelpa-use-package.git"))
(require 'quelpa-use-package)

Silencing Compilation Warnings

By default, Emacs loves to open a buffer to tell you about every little warning, especially during byte compilation. But unless I’m debugging, I don’t need those messages in my face. The following snippet will suppress those buffers. They are still available in the buffer list, if needed.

(add-to-list 'display-buffer-alist
             '("\\`\\*\\(Warnings\\|Compile-Log\\)\\*\\'"
               (display-buffer-no-window)
               (allow-no-window . t)))

Quitting and Restarting Emacs

Add a confirmation prompt to prevent accidental exits. I also install restart-emacs which gives a clean, 1-command way to fully reboot emacs. And yes, why type yes/no when you can just type y/n?

(setq confirm-kill-emacs 'y-or-n-p)
(fset 'yes-or-no-p 'y-or-n-p)

(use-package restart-emacs
  :ensure t)

Running Emacs as a Server

Emacs can run as a server, allowing you to connect to it from the terminal using emacsclient. This helps keep the same state across sessions.

(server-start)

Apple Keyboard Key Remapping

When I use Emacs on my Mac, I want the Command key to be the Meta key. This is a personal preference, but it makes sense to me. I also want to use the Option key as the Super key. This is a common setup for Mac users, and it makes Emacs feel more natural on macOS.

(if (eq system-type 'darwin)
    (setq mac-command-modifier 'meta mac-option-modifier 'super mac-control-modifier 'control))

Keeping Backups and Custom Settings Separately

I like to keep my custom.el separate so Emacs’ autogenerated settings don’t clutter my init file. I also like to keep my backups in a separate directory, so they don’t clutter my working directory.

;; move custom.el
(setq custom-file "~/.emacs.d/custom.el")
(unless (file-exists-p custom-file)
  (shell-command (concat "touch " custom-file)))
(load custom-file)

;; backups
(defvar backup-dir)
(setq backup-dir "~/.emacs.d/backup")
(unless (file-exists-p backup-dir)
  (make-directory backup-dir))
(setq backup-directory-alist '(("." . "~/.emacs.d/backup"))
      backup-by-copying t
      delete-old-versions t
      version-control t
      kept-new-versions 5
      kept-old-versions 2)

Inheriting Environment Variables from Shell

exec-path-from-shell is a package that allows Emacs to inherit environment variables from the user shell. How Emacs is started can affect the env variables it has access to, and this happens a lot on macOS, where GUI applications may not have the same environment as the terminal. There are many usecases for this, but here I specifically want to load variables that change names or paths depending on the computer I am using.

(use-package exec-path-from-shell
  :if (memq window-system '(mac ns x))
  :ensure t
  :init (exec-path-from-shell-initialize)
  (exec-path-from-shell-copy-env "DROPBOX_HOME")
  (exec-path-from-shell-copy-env "ORG_HOME"))

Loading Custom Emacs Lisp Files

;; load custom elisp files
(add-to-list 'custom-theme-load-path "~/.emacs.d/themes/")
(add-to-list 'load-path "~/.emacs.d/lisp/")

;;; define variables early in the init
(setq vsr/org-agenda-directory (substitute-in-file-name "$ORG_HOME/kb/gtd/"))
(setq vsr/kb_files (file-expand-wildcards (substitute-in-file-name "$ORG_HOME/kb/*.org")))

Appearance, Visual Tweaks and UI Polish

Basic UI Tweaks

Use UTF-8 encoding by default.

(prefer-coding-system 'utf-8)
(setq coding-system-for-read 'utf-8
      coding-system-for-write 'utf-8)

I like a clean, minimal interface. Strip away bars and splash screens.

(scroll-bar-mode -1)
(tool-bar-mode -1)
(menu-bar-mode -1)
(setq visible-bell nil
      inhibit-splash-screen t
      initial-scratch-message nil)

Disable the blinking cursor. I find it distracting. But I do still want to know where my cursor is, especially after scrolling or jumping around the buffer. That’s where beacon comes in. It highlights the cursor position when it moves across buffers, so I can see where I am without the blinking.

;; cursors
(blink-cursor-mode 0)

(use-package beacon
  :ensure t
  :init (beacon-mode 1))

When working with multiple buffers and windows, it’s easy to lose track of which one is actually active. That’s where dimmer comes in — it dims inactive windows so your focus naturally stays where you’re working. To prevent weird visuals in Magit buffers, I exclude them entirely.

(use-package dimmer
  :ensure t
  :config (setq dimmer-fraction 0.20)
  (dimmer-configure-helm)
  (dimmer-configure-magit)
  (dimmer-configure-org)
  ;; (setq dimmer-debug-messages 3)
  (dimmer-mode t))

(add-to-list 'dimmer-buffer-exclusion-regexps "^magit.*")

Vertical indentation guides in all programming buffers.

(use-package highlight-indent-guides
  :ensure t
  :init (add-hook 'prog-mode-hook 'highlight-indent-guides-mode))

Theme

I use doom-themes for a nice, clean look. My custom theme is mostly just “tomorrow-night” but with some small modifications. While I could have made most of those changes with custom-set-faces, doing so has some unintended side effects, especially when I flip themes.

(use-package doom-themes
  :ensure t
  :config
  ;; Global settings (defaults)
  (setq doom-themes-enable-bold t    ; if nil, bold is universally disabled
        doom-themes-enable-italic t) ; if nil, italics is universally disabled

  ;; custom theme
  ;; theme file location is already in load-path
  (load-theme 'my-doom-tomorrow-night-black t)

  ;; Enable flashing mode-line on errors
  (doom-themes-visual-bell-config)
  (setq doom-themes-treemacs-theme "doom-colors") ; use "doom-colors" for less minimal icon theme
  (doom-themes-treemacs-config)
  ;; Corrects (and improves) org-mode's native fontification.
  (doom-themes-org-config))

(set-frame-font "Source Code Pro-11" nil t)

Depending on whether I am coding or writing, I prefer different themes. I wrote a little toggle function to switch between themes.

(defvar my/themes '(doom-acario-light my-doom-tomorrow-night-black)
  "List of themes to toggle between.")

(defvar my/current-theme-index 0
  "Current index in `my/themes`.")

(defun my/apply-theme (theme)
  "Load THEME."
  (load-theme theme t)
  )

(defun my/toggle-theme ()
  "Toggle between themes in `my/themes`."
  (interactive)
  (setq my/current-theme-index (% (1+ my/current-theme-index) (length my/themes)))
  (my/apply-theme (nth my/current-theme-index my/themes)))

(global-set-key (kbd "C-c t t") #'my/toggle-theme)

Modeline

The default Emacs modeline is functional, but it looks ancient. I use doom-modeline to make it look more sleek and modern. It also provides a lot of useful information at a glance.

(use-package doom-modeline
  :ensure t
  :init (doom-modeline-mode 1)
  :custom
  (doom-modeline-icon t)
  (doom-modeline-major-mode-icon nil)
  ;; (doom-modeline-major-mode-color-icon t)
  (doom-modeline-project-detection 'auto)
  (doom-modeline-buffer-file-name-style 'truncate-upto-root)
  (doom-modeline-env-version nil)
  (doom-modeline-buffer-encoding nil))

Icons

Modern icons to enhance completion menus, directory listings and UI elements.

(use-package all-the-icons
  :if (display-graphic-p)
  :config
  ;; Check if the fonts have not been installed yet
  (unless (file-exists-p (expand-file-name "~/.local/share/fonts/all-the-icons.ttf"))
    (all-the-icons-install-fonts t)))

(use-package nerd-icons
  :ensure t
  ;; :custom
  ;; The Nerd Font you want to use in GUI
  ;; "Symbols Nerd Font Mono" is the default and is recommended
  ;; but you can use any other Nerd Font if you want
  ;; (nerd-icons-font-family "Symbols Nerd Font Mono")
  )

(use-package nerd-icons-corfu
  :ensure t
  :after corfu
  :config
  (add-to-list 'corfu-margin-formatters #'nerd-icons-corfu-formatter))

(use-package nerd-icons-completion
  :after marginalia
  :config
  (nerd-icons-completion-mode)
  (add-hook 'marginalia-mode-hook #'nerd-icons-completion-marginalia-setup))

(use-package nerd-icons-dired
  :ensure t
  :hook
  (dired-mode . nerd-icons-dired-mode))

I use winner-mode to easily undo and redo window configurations, and windmove to quickly switch between windows using direction keys.

;; windows restore
(use-package winner
  :ensure t
  :config (winner-mode 1))

;; window navigation
(use-package windmove
  :ensure t
  :config
  ;; (windmove-default-keybindings 'shift)
  (global-set-key (kbd "C-s-<left>") 'windmove-left)
  (global-set-key (kbd "C-s-<right>") 'windmove-right)
  (global-set-key (kbd "C-s-<up>") 'windmove-up)
  (global-set-key (kbd "C-s-<down>") 'windmove-down)
  :init (setq windmove-wrap-around t))

Editing and Productivity Enhancements

Turn undo history into a visual, branching tree.

(setq undo-dir "~/.emacs.d/undo")
(unless (file-exists-p undo-dir)
  (make-directory undo-dir))

(use-package undo-tree
  :diminish undo-tree-mode
  :init
  (setq undo-tree-history-directory-alist '(("." . "~/.emacs.d/undo")))
  :config
  (global-undo-tree-mode)
  (setq undo-tree-visualizer-timestamps t)
  (setq undo-tree-visualizer-diff t))

Multi-line editing with multiple-cursors.

(use-package multiple-cursors
  :ensure t
  :bind (("C-c m c" . mc/edit-lines)
         ("C->" . mc/mark-next-like-this)
         ("C-<" . mc/mark-previous-like-this)
         ("C-c C-<" . mc/mark-all-like-this)))

Multiple occurrence-based editing with iedit.

(use-package iedit
  :bind ("C-;" . iedit-mode))

Auto-inserting paired delimiters, like parentheses, brackets and quotes.

(electric-pair-mode)

Nudge a line or a block of code up or down without cutting and pasting.

(use-package move-text
  :ensure t
  :config
  (move-text-default-bindings))

Persist search history, file paths and other data across sessions.

(use-package savehist
  :init
  (savehist-mode))

Project Management

projectile turns Emacs into a project-oriented IDE - helping you navigate files, run commands and manage projects easily.

(use-package projectile
  :ensure t
  :config
  (define-key projectile-mode-map (kbd "C-c p") 'projectile-command-map)
  (projectile-mode +1)
  (setq projectile-completion-system 'default)
  (setq projectile-enable-caching nil)
  ;; (setq projectile-indexing-method 'alien)
  (setq projectile-indexing-method 'native)
  (setq projectile-switch-project-action #'projectile-find-file)

  (add-to-list 'projectile-globally-ignored-files "node_modules")
  (add-to-list 'projectile-globally-ignored-files ".cache")
  (add-to-list 'projectile-globally-ignored-files "_cache")
  (add-to-list 'projectile-globally-ignored-files "venv"))

While projectile gives fast navigation across projects, I also want a clean mental space for each one — with its own buffers and layout. That’s where perspective comes in.

(use-package perspective
  :bind ("C-x C-b" . persp-list-buffers)
  :custom (persp-mode-prefix-key (kbd "C-x x"))
  :init (persp-mode)
  :config
  (with-eval-after-load 'perspective
    (require 'my-perspectives)
    (add-hook 'emacs-startup-hook #'my/setup-initial-perspectives)))

I predefine my core default perspectives in a separate config file my-perspectives.el. This lets me boot into a ready-to-go Emacs layout with just the right files and windows open.

;;; my-perspectives.el --- Define initial perspectives -*- lexical-binding: t; -*-
(require 'perspective)

;;; Code:
(defun my/setup-initial-perspectives ()
  "Set up my standard perspective layout."

  ;; Main perspective
  (persp-switch "main")
  (find-file "~/.emacs.el")
  (split-window-horizontally)
  (balance-windows)

  ;; Org perspective
  (persp-switch "org")
  (find-file (expand-file-name "projects.org" vsr/org-agenda-directory))
  (org-overview)
  (split-window-horizontally)
  (balance-windows)
  (other-window 1)
  (org-agenda nil "x")

  ;; ... more perspectives can be added here

  ;; Back to main
  (persp-switch "main"))

(provide 'my-perspectives)

;;; my-perspectives.el ends here

treemacs is a sidebar-style file and project explorer for Emacs — like Dired, but with a modern, hierarchical view that stays open in its own window. While Dired is great for direct file operations, treemacs shines when you want a persistent, clickable tree of your project, complete with Git status indicators, icons, and integration with projectile and magit. It also comes with a cool admin management buffer that can be edited in org-mode.

(use-package treemacs
  :ensure t
  :defer t
  :config
  (treemacs-follow-mode t)
  (treemacs-project-follow-mode nil)
  (treemacs-filewatch-mode t)
  (treemacs-fringe-indicator-mode 'always)

  (when treemacs-python-executable
    (treemacs-git-commit-diff-mode t))

  (pcase (cons (not (null (executable-find "git")))
               (not (null treemacs-python-executable)))
    (`(t . t) (treemacs-git-mode 'deferred))
    (`(t . _) (treemacs-git-mode 'simple)))

  (treemacs-hide-gitignored-files-mode nil)

  :bind (:map global-map
              ("C-x t t" . treemacs)))

(use-package treemacs-perspective
  :after (treemacs perspective)
  :ensure t
  :config (treemacs-set-scope-type 'Perspectives))

;; (use-package treemacs-projectile
;;   :ensure t)

(use-package treemacs-magit
  :after (treemacs magit)
  :ensure t)

Completion Stack

Completion in Emacs used to be a bit all over the place — between M-x, file prompts, and completion-at-point, it felt like three different systems duct-taped together. Tools like helm tried to unify the experience into a single, powerful interface — and they succeeded in many ways. But helm (and ivy) are large, monolithic systems with their own abstractions, which can feel heavy or opinionated.

This newer stack — built around vertico, corfu, consult, marginalia, and embark — takes a different approach. It embraces Emacs’ native completion system, layering on small, composable packages to create a fast, minimal, and context-aware UI that feels more integrated and less intrusive. Each piece does one thing well, and together they offer a flexible, modern editing experience without reinventing everything under the hood.

vertico : clean, minimal vertical menu for everything from file names to commands.

(use-package vertico
  :ensure t
  :custom
  (vertico-cycle t)
  (vertico-resize t)
  :init
  (vertico-mode)
  (vertico-multiform-mode)
  (vertico-buffer-mode)
  :config
  (define-key vertico-map (kbd "M-n") #'vertico-next-group)
  (define-key vertico-map (kbd "M-p") #'vertico-previous-group)
  )

orderless : flexible matching that makes completions work with fuzzy search, regexes, and more.

(use-package orderless
  :custom
  (completion-styles '(orderless basic))
  (completion-category-defaults nil)
  (completion-category-overrides '((file (styles partial-completion)))))

marginalia : add rich metadata and annotations to completions.

(use-package marginalia
  :ensure t
  :hook (after-init . marginalia-mode))

corfu : completions inside the buffer (not minibuffer) and makes suggestions appear as you type.

(use-package corfu
  :ensure t
  :custom
  (corfu-auto t)
  (corfu-auto-delay 0.2)
  (corfu-auto-prefix 2)
  :init (global-corfu-mode))

cape : add more completion sources like dabbrev, file names, and more.

(use-package cape
  :ensure t
  :init
  ;; Add file path completion to existing completion-at-point-functions
  (add-to-list 'completion-at-point-functions #'cape-file)
  (setq-default completion-at-point-functions
                (cons #'cape-file (default-value 'completion-at-point-functions)))
  )

consult : collection of useful commands for searching, filtering, and navigating buffers, files and projects based on Emacs’ completing-read.

(use-package consult
  :ensure t
  :after projectile
  :bind
  (("C-c f" . consult-find)
   ("C-c g" . consult-ripgrep)
   ("C-c l" . consult-line)
   ("C-c i" . consult-imenu)
   ("C-x b" . consult-buffer)
   ("C-x p b" . consult-project-buffer)
   ("M-y" . consult-yank-pop)
   ("M-g g" . consult-goto-line)
   ("M-g o" . consult-outline))
  :config
  (setq consult-ripgrep-args
        "rg --hidden --null --line-buffered --color=never --max-columns=2048 --with-filename --line-number --no-heading")
  )

embark : context-aware actions for completions and buffer content, letting you act on anything — files, symbols, commands — anywhere.

(use-package embark
  :ensure t
  :bind (("C-." . embark-act)
         ("C-," . embark-dwim))
  :init
  ;; Optionally replace the key help with a completing-read interface
  (setq embark-prompter 'embark-completing-read-prompter)
  ;; Hide the default key bindings
  (setq embark-indicators nil)
  )

(use-package embark-consult
:ensure t ; only need to install it, embark loads it after consult if found
:hook
(embark-collect-mode . consult-preview-at-point-mode))

Git and Version Control

I can’t remember the last time I used the terminal to run git commands — because magit is just that good. It’s fast, keyboard-driven and makes complex Git operations feel intuitive. I use magit-delta for side-by-side diffs and syntax-highlighted Git views inside Magit. This requires installing delta separately.

(setq vc-follow-symlinks t)

(use-package magit
  :ensure t
  :bind (("C-c m s" . magit-status)
         ("C-c m l" . magit-log-all))
  :custom (magit-log-arguments '("-n100" "--graph" "--decorate"))
  :config (progn (use-package git-timemachine)
                 (use-package git-link
                   :init (setq git-link-open-in-browser t))))

;; install git-delta
(use-package magit-delta
  :hook (magit-mode . magit-delta-mode))

Org Mode

If Emacs is an operating system disguised as a text editor, then org-mode is its most powerful app — and also its own universe.

Originally built for note-taking and outlining, Org is a full-fledged ecosystem: task manager, knowledge base, publishing engine, time tracker, spreadsheet, writing tool, and more. It’s plain text on the surface, but underneath, it’s a programmable system for organizing anything.

Here is a brief overview of my Org setup.

(use-package org
  :bind (("C-c l" . org-store-link)
         ("C-c c" . org-capture)
         ("C-c a" . org-agenda))
  :init
  (setq default-major-mode 'org-mode)
  :config
  (setq org-startup-indented t
        org-startup-truncated nil
        org-startup-with-inline-images t
        org-hide-leading-stars t
        org-adapt-indentation t
        org-image-actual-width '(300)
        org-goto-interface 'outline-path-completion
        org-outline-path-complete-in-steps nil
        org-cycle-separator-lines 2
        org-show-notification-handler 'message
        org-pretty-entities t
        org-hide-emphasis-markers t
        org-fontify-whole-heading-line t
        org-fontify-done-headline t
        org-fontify-quote-and-verse-blocks t
        org-src-fontify-natively t
        org-use-sub-superscripts nil
        org-log-into-drawer "LOGBOOK"))

;; Org inbox file
(setq vsr/org-inbox-file (concat vsr/org-agenda-directory "inbox.org"))

;; Org templates
(require 'org-tempo)

;; Org cliplink
(use-package org-cliplink
  :bind ("C-x p i" . org-cliplink))

;; Org-goto configuration
(setq org-goto-interface 'outline-path-completion
      org-goto-max-level 10)

;; Org bullets
(use-package org-bullets
  :init
  (add-hook 'org-mode-hook
            (lambda ()
              (org-bullets-mode 1))))

;; Refile targets
(setq org-refile-targets '((nil :maxlevel . 9)
                           (org-agenda-files :maxlevel . 9)
                           (vsr/kb_files :maxlevel . 9))
      org-refile-use-outline-path 'file
      org-outline-path-complete-in-steps nil
      org-refile-allow-creating-parent-nodes 'confirm)

At the heart of my Org workflow is a simple rule: capture everything, sort it later. I use a single Org Capture template to send incoming tasks straight to my inbox. Org-mode allows you to define your own sequence of TODO keywords — and this is where it starts to feel like a true task management system.

;; capture template
(setq org-capture-templates '(("t" "Task" entry (file vsr/org-inbox-file) "* TODO %?\n")))

;; todo keywords
(setq org-todo-keywords '((sequence "TODO(t!)" "NEXT(n!)" "WEEK(w!)" "IN-PROGRESS(x!)" "WAITING(q@/!)"  "|" "DONE(d!)" "CANCELLED(c!)")
                          ;; (sequence "DATA(1!)" "TRAIN(2!)" "TEST(3!)")
                          ))

;; tags
(setq org-tag-alist
      '(("WRITING" . ?W)
        ("PERSONAL" . ?V)
        ("PROJECT" . ?P)
        ("INACTIVE" . ?I)))

Org-mode’s agenda is where everything comes together — tasks, events, projects, and priorities. But the real power lies in shaping it to reflect your way of thinking.

Scan relevant org files.

(setq org-agenda-files (file-expand-wildcards (substitute-in-file-name "$ORG_HOME/kb/gtd/*.org")))

(with-eval-after-load 'org
  ;; Remove temp/backup files
  (setq org-agenda-files
        (seq-remove
         (lambda (f)
           (let ((name (file-name-nondirectory f)))
             (or (string-prefix-p "#" name)
                 (string-suffix-p "~" name))))
         org-agenda-files))

  ;; Remove specific known files
  (setq org-agenda-files
        (cl-set-difference
         org-agenda-files
         (list
          (expand-file-name "someday.org" vsr/org-agenda-directory)
          (expand-file-name "projects-archive.org" vsr/org-agenda-directory))
         :test #'equal)))

Org-mode’s built-in agenda view is powerful, but it really shines when you define your own custom commands. Instead of sifting through all tasks, I use org-agenda-custom-commands to create focused views.

(setq org-agenda-window-setup 'current-window
      org-agenda-show-future-repeats t
      org-agenda-skip-deadline-if-done t
      org-agenda-start-on-weekday 6
      org-agenda-span 14)

(setq org-agenda-prefix-format
      '((todo . "  %b")
        (tags . "  %b")
        (agenda . "  %b")))

(setq org-agenda-custom-commands
      '(("x" "Agenda"
         ((agenda "" nil)
          (todo "IN-PROGRESS"
                ((org-agenda-overriding-header "IN-PROGRESS Tasks")))
          (todo "NEXT"
                ((org-agenda-overriding-header "NEXT Tasks")))
          (todo "WEEK"
                ((org-agenda-overriding-header "WEEK Tasks")))
          (todo "WAITING"
                ((org-agenda-overriding-header "WAITING Tasks")))
          (todo ""
                ((org-agenda-overriding-header "INBOX Tasks")
                 (org-agenda-files `(,vsr/org-inbox-file))
                 (org-tags-match-list-sublevels 'indented)))
          (tags-todo "WRITING"
                     ((org-agenda-overriding-header "WRITING Tasks")
                      ;; (org-tags-match-list-sublevels 'indented)
                      ))
          (tags "PROJECT"
                ((org-agenda-overriding-header "Projects")
                 (org-tags-match-list-sublevels 'nil)
                 ))
          (tags-todo "+PROJECT/+TODO"
                     ((org-agenda-overriding-header "Unscheduled TODO Project Tasks")
                      (org-tags-match-list-sublevels 'indented)
                      (org-agenda-sorting-strategy '(category-up todo-state-up))
                      (org-agenda-skip-function '(org-agenda-skip-entry-if 'deadline 'scheduled))))
          (tags-todo "-{.}"
                     ((org-agenda-overriding-header "Untagged TODO Tasks")
                      (org-tags-match-list-sublevels 'nil)))
          ))))

I also use Org-mode as my writing and publishing environment — including for this very post. ox-hugo is a brilliant extension that lets me write in Org and export directly to Markdown for my Hugo static site. ox-gfm is great for exporting notes or documentation as Github Flavored Markdown.

(use-package ox-hugo
  :ensure t
  :pin melpa
  :after ox)

(use-package ox-gfm
  :ensure t
  :after org)

Programming in Emacs

While Emacs isn’t a traditional IDE, with the right packages it becomes a clean, fast, and powerful Python environment — without ever leaving your keyboard.

I mainly program in python. My setup is built around python-mode and enhanced with LSP (eglot), auto-formatting (blacken), linting (flycheck), and inline help (eldoc-box). I also use numpydoc to generate numpy-style docstring templates. Everything kicks in automatically when I open a Python file.

(use-package python
  :ensure nil ;; python.el is built-in
  :hook ((python-mode . eglot-ensure)
         (python-mode . flycheck-mode)
         (python-mode . blacken-mode)
         (python-mode . pyvenv-mode))
  :config (setq python-shell-interpreter "python3"))

(use-package eglot
  :ensure t
  :config (add-to-list 'eglot-server-programs '(python-mode . ("pyright-langserver" "--stdio"))))

(use-package blacken
  :ensure t
  :hook (python-mode . blacken-mode)
  :config (setq blacken-line-length 88))

(use-package py-isort
  :ensure t)

(use-package flycheck
  :ensure t
  :init (global-flycheck-mode)
  :config
  (define-key flycheck-mode-map (kbd "C-c C-n") 'flycheck-next-error)
  (define-key flycheck-mode-map (kbd "C-c C-p") 'flycheck-previous-error)
  (setq flycheck-flake8-maximum-line-length 9999))

(use-package eldoc-box
  :ensure t
  :commands (eldoc-box-help-at-point)
  :config
  ;; Optional: disable any automatic clearing or popup behavior
  (setq eldoc-box-clear-with-C-g t
        eldoc-box-only-multiline-doc nil))

(with-eval-after-load 'python
  ;; (define-key python-mode-map (kbd "C-c C-d") #'eglot-help-at-point)
  (define-key python-mode-map (kbd "C-c C-d") #'eldoc-box-help-at-point)
  (define-key python-mode-map (kbd "C-c C-f") #'blacken-buffer)
  (define-key python-mode-map (kbd "C-c C-i") #'py-isort-buffer))

(use-package numpydoc
  :ensure t
  :bind (:map python-mode-map ("C-c SPC C-n" . numpydoc-generate))
  :init (setq numpydoc-insertion-style 'yas numpydoc-insert-examples-block nil))

Some lightweight language modes for other languages I use occasionally.

(use-package js2-mode
  :ensure t)

(use-package yaml-mode
  :ensure t)

Integrating GitHub Copilot into Emacs.

;; Copilot
(use-package editorconfig
  :ensure t
  :config (editorconfig-mode 1))

(use-package copilot
  :quelpa (copilot :fetcher github
                   :repo "copilot-emacs/copilot.el"
                   :branch "main"
                   :files ("dist" "*.el"))
  :init (add-hook 'prog-mode-hook 'copilot-mode)
  :bind (("C-M-<next>" . copilot-next-completion)
         ("C-M-<prior>" . copilot-previous-completion)
         ("C-M-<return>" . copilot-accept-completion)
         ("C-M-<right>" . copilot-accept-completion-by-word)
         ("C-M-<down>" . copilot-accept-completion-by-line)))

This Emacs config is the result of years of tweaking, pruning, and evolving alongside how I actually work. It’s not meant to be flashy or maximal — just focused, and frictionless. Every piece here earns its place by solving a real problem or smoothing out a daily workflow.

If you’re building your own setup, I hope this gave you a few ideas or shortcuts.

My system-level setup — including Ubuntu tweaks, i3 configuration, and other environment — is documented in a separate post: Minimal Ubuntu with i3.