- Shell 60.4%
- Emacs Lisp 39.6%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| bin | ||
| os | ||
| vendor | ||
| .gitignore | ||
| init.el | ||
| init.org | ||
| LICENSE | ||
| README.org | ||
My GNU Emacs configuration
- About
- Configuration
- Additional load paths
- Automatic tangling of
init.org - Startup performance
- UTF-8 as preferred coding system
- Some variable defaults
- Yes or No
- Default global modes
- Windmove display keybindings
- HideShow mode
- Deleting trailing whitespace
- Appearance settings
- Custom file
- Autosave directory
- Customization group
- Custom bindings
- Custom functions
- Base16 themes
- Custom major modes
- Dired
- Completion
- Vendored packages
- Language-specific configurations
- OS-specific configuration
deci/rebuild- License
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 it’s 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 I’m 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 don’t 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/.
- geiser (BSD 3-Clause License)
- geiser-guile (BSD 3-Clause License)
- geiser-gambit (BSD 3-Clause License)
- slime (public domain)
- base16-theme (Expat license)
- haskell-mode (GNU GPLv3)
- gdscript-mode (GNU GPLv3)
- restclient.el (public domain)
(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/>.