My GNU Emacs configuration.
  • Shell 60.4%
  • Emacs Lisp 39.6%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-08-30 03:11:13 +03:00
bin add gdscript-mode 2026-08-16 01:55:13 +03:00
os move tangling on save to os-specific config 2026-06-02 18:57:41 +03:00
vendor add restclient.el package 2026-08-26 00:59:17 +03:00
.gitignore add gdscript-mode 2026-08-16 01:55:13 +03:00
init.el move tangling on save to os-specific config 2026-06-02 18:57:41 +03:00
init.org remove tramp 2026-08-30 03:11:13 +03:00
LICENSE add license 2026-05-03 01:30:47 +03:00
README.org use init.org as README 2025-10-02 02:51:01 +03:00

My GNU Emacs configuration

About

This config uses GNU Emacs version 31, which isn't released yet, meaning it's using basically the master branch build.

This configuration file is writtien in Org mode. It lets you keep all settings in one file and naturally encourages you to document them. Even if the file might get very long, navigating it inside Org mode is simple and convenient.

Configuration

This is an Org file which is tangled into actual configuration. Whenever you see a source code block like this:

;;; -*- lexical-binding: t -*-

It is usually tangled into init.el. But not every code block is tangled - some are marked with :tangle no.

  #+begin_src emacs-lisp :tangle no
    (defun greet (name) (message "Hello, %s!" name))
  #+end_src

Sometimes its also needed to target early-init.el with tangling. In this case, :tangle early-init.el is set:

;;; -*- lexical-binding: t -*-

To not deal with init.el changing on tangle, you can run:

  git update-index --assume-unchanged init.el

Additional load paths

I use some vendored packages, and defining additional load paths should be done as early as possible in the configuration.

  (defvar deci/vendored-packages
    '("geiser" "geiser-guile" "geiser-gambit" "slime" "base16-theme"
      "haskell-mode" "gdscript-mode" "restclient")
    "List of packages that exist in the vendor/ directory.")

  (dolist (package deci/vendored-packages)
    (add-to-list 'load-path (directory-file-name
                             (expand-file-name (format "vendor/%s/" package)
                                               user-emacs-directory))))

Automatic tangling of init.org

You can use C-c C-v t to run org-babel-tangle. It extracts all code blocks and inserts them into init.el (and some into early-init.el).

  (require 'org)

  (defun deci/tangle-init-org ()
    "Tangle the init.org file into init.el and byte-compile it."
    (interactive)
    ;; don't run prog-mode-hook when tangling
    (let (prog-mode-hook)
      (org-babel-tangle-file
       (expand-file-name "init.org" user-emacs-directory))
      (byte-compile-file
       (expand-file-name "init.el" user-emacs-directory))
      (message "init.org successfully tangled!")))

Startup performance

  (defun deci/display-startup-performance ()
    "Display information about startup time."
    (interactive)
    (message "Emacs started in %.2fs."
             (float-time (time-subtract after-init-time
                                        before-init-time))))
  (add-hook 'emacs-startup-hook #'deci/display-startup-performance)

An interesting piece of information I encountered online is the fact that you can improve startup time by setting gc-cons-threshold to a very large value during startup, and then setting it to a more sane value after startup. The values Im going to use are most-positive-fixnum and 50mb respectively.

  (let ((file-name-handler-alist-old file-name-handler-alist))
    (add-hook 'emacs-startup-hook
              (lambda ()
                (setq gc-cons-threshold (* 1024 1024 50))
                (setq file-name-handler-alist
                      file-name-handler-alist-old)))
    (setq gc-cons-threshold most-positive-fixnum)
    (setq file-name-handler-alist nil))
  (setq inhibit-default-init t)

UTF-8 as preferred coding system

  (set-language-environment "UTF-8")
  (prefer-coding-system 'utf-8)

Some variable defaults

  (setq create-lockfiles nil
        enable-recursive-minibuffers t
        inhibit-startup-screen t
        initial-scratch-message nil
        ring-bell-function 'ignore)

Some variables are local to buffers, so they should be changed with setq-default.

  (setq-default auto-revert-interval 1
                recentf-max-saved-items 10000
                fill-column 80
                indent-tabs-mode nil
                split-width-threshold 160
                split-height-threshold nil
                fringe-indicator-alist
                (assq-delete-all 'continuation fringe-indicator-alist))

Yes or No

I dont like writing yes and no every time, so I prefer to use y-or-n-p function instead of yes-or-no-p.

  (fset 'yes-or-no-p 'y-or-n-p)

Default global modes

These modes are global - meaning once they are enabled, they work by default in every buffer.

  (dolist (mode '(electric-pair-mode
                  electric-indent-mode
                  global-display-line-numbers-mode
                  recentf-mode
                  savehist-mode
                  fido-vertical-mode
                  column-number-mode))
    (funcall mode 1))

Windmove display keybindings

Windmove allows you to easily specify where to display a new buffer. By using the function below, you can use keybindings like M-S-Right or M-S-Left to display the buffer resulting by the next command as a right split or left split accordingly. You can also use this for horizontal splits, new tabs, new frames, etc.

  (windmove-display-default-keybindings)

HideShow mode

HideShow is the mode that allows you to hide or show blocks of code, as the name suggests.

  (require 'hideshow)
  (add-hook 'prog-mode-hook #'hs-minor-mode)

Deleting trailing whitespace

This makes Emacs delete trailing whitespace every time you save a buffer.

  (add-hook 'before-save-hook #'delete-trailing-whitespace)

Appearance settings

Font

I prefer the Hack font.

  (set-face-attribute 'default
                      nil :font "Hack" :height 120)

Frame settings

I also like my Emacs windows undecorated and maximized.

  (add-to-list 'default-frame-alist '(undecorated . t))
  (add-to-list 'default-frame-alist '(fullscreen . maximized))
  (add-to-list 'default-frame-alist '(font . "Hack-12"))
  (setq frame-resize-pixelwise t)

Disabling modes

And of course, no menu, toolbar, scrollbar and blinking cursor.

  (dolist (mode '(menu-bar-mode
                  tool-bar-mode
                  scroll-bar-mode
                  blink-cursor-mode))
    (when (fboundp mode)
      (funcall mode 0)))

Tab bar

I use the tab bar sometimes, here's my preferred configuration for it.

  (setq-default tab-bar-close-button-show nil
                tab-bar-format
                '(tab-bar-format-history tab-bar-format-tabs tab-bar-separator)
                tab-bar-history-mode nil
                tab-bar-mode t
                tab-bar-show 1)

Custom file

Custom file holds customizations applied by customize-something commands. It's nice to have for something local, like specific themes, and not commit it to version history.

  (setq custom-file (expand-file-name "custom.el" user-emacs-directory))
  (load custom-file t t)

Autosave directory

To avoid garbage in the filesystem, I prefer to place autosaves in a special directory.

  (setq backup-directory-alist
        `((".*" . ,(expand-file-name "autosave/" user-emacs-directory)))
        auto-save-file-name-transforms
        `((".*" ,(expand-file-name "autosave/" user-emacs-directory) t)))

Customization group

Emacs allows to have specific variables customizable by customize-something in customization groups. I will place my own customization variables in my own group.

  (defgroup deci nil
    "My local customization group."
    :group 'emacs)

Custom bindings

I use a minor mode that has an associated keymap to hold all of my custom keybindings.

  (defvar deci/custom-bindings-map (make-sparse-keymap)
    "Keymap for custom bindings.")
  (define-minor-mode deci/custom-bindings-mode
    "Minor mode that activates custom bindings."
    :init-value t
    :keymap deci/custom-bindings-map)

I prefer to use a sort of a leader key for my keybindings - this leader key is C-z. Since you can minimize Emacs by C-x C-z, I think this keybinding is free real estate for my own keybindings.

Emacs 31 adds a really nice function bind-keys. No more need to use use-package to define multiple keybindings.

  (require 'bind-key)
  (unbind-key "C-z")

  (bind-keys :map deci/custom-bindings-map
             ("C-z C-e" . eshell)
             ("C-z C-s" . shell)
             ("C-z C-w" . whitespace-mode)
             ("C-z C-p" . repeat-mode)
             ("C-z C-M-f" . recentf))

  (bind-keys :map hs-minor-mode-map
             ("C-z C-f" . hs-toggle-hiding)
             ("C-z C-S-f" . hs-show-all))

Custom functions

Here I will define some functions I like to use in my configuration.

deci/reset

  (defun deci/reset ()
    "Reset all buffers, windows and frames."
    (interactive)
    (scratch-buffer)
    (delete-other-windows)
    (delete-other-frames)
    (dolist (buf (buffer-list))
      (unless
          (member (buffer-name buf) '("*scratch*" "*Messages*" "*Warnings*"))
        (kill-buffer (buffer-name buf)))))

  (bind-key "C-z C-q" #'deci/reset deci/custom-bindings-map)

deci/insert-lambda

  (defun deci/insert-lambda ()
    "Insert λ."
    (interactive)
    (insert-char 955 1))

  (bind-key "C-z C-\\" #'deci/insert-lambda deci/custom-bindings-map)

deci/term

Launch ansi-term using the preferred shell (deci/preferred-shell).

  (defcustom deci/preferred-shell
    shell-file-name
    "Default shell to use with `deci/term`."
    :type 'string
    :group 'deci)

  (defun deci/term (&optional new-buffer-name)
    "Launch `ansi-term` using DECI/PREFERRED-SHELL."
    (interactive)
    (ansi-term deci/preferred-shell new-buffer-name))

  (bind-key "C-`" #'deci/term deci/custom-bindings-map)

Base16 themes

I'm using the base16-theme package, I quite like that collection of themes. For a light theme I'm using base16-equilibrium-light, and for a dark theme I'm using base16-gruvbox-dark-soft.

  (require 'base16-theme)

  (defvar deci/rotation-themes
    '(base16-equilibrium-light base16-gruvbox-dark-soft)
    "Selected themes for rotation.")
  (defvar deci/rotation-themes-index
    0
    "Index of the currently selected theme.")

  (defun deci/rotate-theme (&optional index)
    "Switch to the next theme from `deci/rotation-themes'."
    (interactive)
    (let* ((index (if index index (1+ deci/rotation-themes-index)))
           (index (if (< index (length deci/rotation-themes)) index 0)))
      (setq deci/rotation-themes-index index)
      (load-theme (nth index deci/rotation-themes) t)))

  (deci/rotate-theme 0)

  (bind-key "C-z C-t" #'deci/rotate-theme deci/custom-bindings-map)

Custom major modes

Emacs provides the variable auto-mode-alist to configure major modes based on file extensions. It is not customisable via the Custom mode, so I define a custom variable here that gets prepended to the alist instead.

  (defcustom deci/auto-mode-alist
    nil
    "This gets prepended to `auto-mode-alist`."
    :type '(alist
            :tag "Entries"
            :key-type (string :tag "File extension")
            :value-type (function :tag "Mode"))
    :group 'deci)

  (add-hook 'emacs-startup-hook
            (lambda ()
              (dolist (entry deci/auto-mode-alist)
                (push entry auto-mode-alist))))

Dired

To make Dired display file sizes in human-readable form, we can utilise the dired-listing-switches variable. I also love the dired-mouse-drag-files feature, since it allows to easily use Dired as a replacement for a "traditional" file manager and drag files into GUI applications, which I do quite a lot.

  (setq-default dired-listing-switches "-alh"
                dired-mouse-drag-files t)

I define K as dired-kill-subdir for use in Dired buffers.

  (require 'dired)
  (bind-key "K" #'dired-kill-subdir dired-mode-map)

Completion

I'm using Emacs's built-in completion interface instead of opting for Company, Corfu, etc. Setting completion-auto-select to second-tab allows to easily switch to the *Completions* buffer by pressing TAB twice.

  (setq-default completion-auto-select 'second-tab)

Vendored packages

I decided not to use a package manager in this configuration but to vendor all my packages instead, for simplicity's sake. I will provide links to the packages I used here. The source code is available in /vendor/.

  (defun deci/rebuild-vendor ()
    (interactive)
    (dolist (package deci/vendored-packages)
      (dolist (file (file-expand-wildcards
                     (expand-file-name (format "vendor/%s/*.el" package)
                                       user-emacs-directory)
                     t))
        (byte-compile-file file)))
    (message "vendor successfully rebuilt!"))

restclient.el

To automatically enable restclient-mode in files that end with .restclient, I modify auto-mode-alist.

  (require 'restclient)
  (add-to-list 'auto-mode-alist
               '("\\.restclient\\'" . restclient-mode))

Language-specific configurations

I'd like to add multiple functions to multiple hooks. Unfortunately, I don't think there's a way to set this sort of thing concisely. Fortunately, Emacs Lisp has macros.

  (defmacro deci/hooks (hooks functions)
    "Add to the value of HOOKS the functions FUNCTIONS.

  HOOKS should be a list of symbols.

  FUNCTION should be a list of function symbols (!). Each function from
  FUNCTIONS is added to each HOOK from HOOKS."
    `(progn
       ,@(mapcar
          (lambda (hook)
            `(progn
               ,@(mapcar
                  (lambda (fn)
                    `(add-hook ',hook #',fn))
                  functions)))
          hooks)))

C/C++

  (setq-default c-basic-offset 4
                c++-basic-offset 4)

GDScript

I include gdscript-mode in my vendored packages for GDScript support.

  (require 'gdscript-mode)

Haskell

I include haskell-mode in my vendored packages for Haskell support.

  (require 'haskell-mode-autoloads)

Lisp

The most powerful programming language.

Programs must be written for people to read, and only incidentally for machines to execute. Abelson & Sussman

  (require 'slime-autoloads)
  (setq inferior-lisp-program "sbcl")

Lua

  (setq-default lua-indent-level 4)

Org

  (bind-key "C-z C-l" #'org-store-link deci/custom-bindings-map)

Scheme

I am using Geiser for writing Scheme in Emacs.

  (require 'geiser-guile)
  (require 'geiser-gambit)

Web

I am using mhtml-mode for most of web development, except PHP.

  (add-to-list 'auto-mode-alist
               '("\\.vue\\'" . mhtml-mode))
  (add-to-list 'auto-mode-alist
               '("\\.html\\'" . mhtml-mode))
  (add-to-list 'auto-mode-alist
               '("\\.htm\\'" . mhtml-mode))

  (setq-default sgml-basic-offset 4
                html-ts-indent-offset 4
                mhtml-ts-js-css-indent-offset 4
                php-ts-html-indent-offset 4
                php-ts-js-css-indent-offset 4
                typescript-ts-indent-offset 4)

OS-specific configuration

Since I started using Guix System, I've been defining my own functions related to Guix in custom.el, which is unsatisfactory. To solve this, I've decided to dedicate an additional Org file for OS-specific configurations, which I will populate in different Git branches.

  (add-to-list 'load-path (directory-file-name
                           (expand-file-name "os/" user-emacs-directory)))
  (require 'deci-os)
  (defun deci/tangle-deci-os ()
    "Tangle the deci-os.org file into deci-os.el and byte-compile it."
    (interactive)
    ;; don't run prog-mode-hook when tangling
    (let (prog-mode-hook)
      (org-babel-tangle-file
       (expand-file-name "os/deci-os.org" user-emacs-directory))
      (byte-compile-file
       (expand-file-name "os/deci-os.el" user-emacs-directory))
      (message "deci-os.org successfully tangled!")))

deci/rebuild

Finally, a function to retangle and rebuild all configuration files.

  (defun deci/rebuild ()
    "Retangle and rebuild all configuration files."
    (interactive)
    (deci/tangle-deci-os)
    (deci/tangle-init-org)
    (deci/rebuild-vendor))

  (bind-key "C-z C-r" #'deci/rebuild deci/custom-bindings-map)

License

Copyright (C) 2026 decipaliz <unat7919@gmail.com>

This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.