- Emacs Configuration
This configuration is no longer maintained, so it has been archived.
I used this Emacs configuration in 2018–2021, but I made a [[https://github.com/akirak/emacs-config/][rewrite]] in 2022.
** Table of contents
:PROPERTIES:
:TOC: siblings
:END:
** Prerequisites
- Linux or Window Subsystem for Linux
- Nix package manager for installing dependencies
** Installation
This configuration depends on some external programs as well as Emacs
Lisp packages built by Nix.
At present, a suggested installation procedure is to first install my [[https://github.com/akirak/home.nix][home.nix]] and then run =mr checkout= at your home directory.
In the future, it may support an alternative for trying out.
** License
GPL v3.
Some libraries were originally written by other people, and they
follow their respective licenses.
** Initialization
#+begin_src emacs-lisp
(when (version< emacs-version "27.1")
(error "Use GNU Emacs version 27.1 or later"))
(let ((local-custom-file "~/local/emacs/custom.el"))
(if (file-exists-p local-custom-file)
(setq custom-file local-custom-file)
(message "%s does not exist, so custom-file is not set"
local-custom-file)))
(when custom-file
(load custom-file nil :nomessage))
(unless (fboundp 'whitespace-cleanup-mode)
(defun whitespace-cleanup-mode (&rest args)
(when (require 'whitespace-cleanup-mode nil t)
(apply #'whitespace-cleanup-mode args))))
(add-to-list 'exec-path (expand-file-name "~/.nix-profile/bin"))
(defconst akirak/to-be-run-as-exwm (member "--exwm" command-line-args))
(defun akirak/exwm-session-p ()
akirak/to-be-run-as-exwm)
#+end_src
*** Configure straight.el
#+begin_src emacs-lisp
(load-file (expand-file-name "core/straight.el" user-emacs-directory))
#+end_src
Install use-package using straight.el
#+begin_src emacs-lisp
(straight-use-package 'use-package)
#+end_src
Use straight.el by default in use-package directives
#+begin_src emacs-lisp
(setq straight-use-package-by-default t)
#+end_src
*** Benchmarking the startup process
#+begin_src emacs-lisp
(use-package benchmark-init
:hook
(after-init . benchmark-init/deactivate))
#+end_src
*** Use the latest Git version of Org mode
#+begin_src emacs-lisp
(require 'cl-lib)
(require 'subr-x)
#+end_src
Remove org-mode shipped with Emacs from load-path
#+begin_src emacs-lisp
(use-package org
:straight (:type built-in)
:config
(require 'org-loaddefs))
#+end_src
*** Recipe overrides
#+begin_src emacs-lisp
(akirak/straight-use-recipes-from-file
akirak/straight-default-recipes-file)
(setq straight-x-pinned-packages
'(("dracula-theme" . "11391ea531d40fb08c64313bbb86e4d29d7fe1c5")))
#+end_src
*** Load configuration files
#+begin_src emacs-lisp
(add-to-list 'load-path (expand-file-name "lisp" user-emacs-directory))
(require 'my/const/system)
#+end_src
TODO: Move to lisp/
#+begin_src emacs-lisp
(add-to-list 'load-path (expand-file-name "extras" user-emacs-directory))
#+end_src
** Configuration
Prevent a confirmation dialog when the org file is loaded.
Don't forget to revert this variable at the beginning of the Org file.
#+begin_src emacs-lisp
(setq-default enable-local-variables :all)
(load-file (expand-file-name "core/setup.el" user-emacs-directory))
#+end_src
*** Things to set up before using =use-package=
#+begin_src emacs-lisp
(akirak/require 'setup-gc)
(setq-default enable-local-variables :safe)
#+end_src
These packages are required in other use-package directives declared in this
configuration.
#+begin_src emacs-lisp
(use-package el-patch
:custom
(el-patch-enable-use-package-integration t))
#+end_src
Package-specific configuration files, including snippets, are kept in [[https://github.com/akirak/emacs-config-library][a separate repository]], not in this repository.
#+begin_src emacs-lisp
(use-package no-littering
:preface
(let* ((var-dir "~/local/emacs/var/"))
(unless (file-directory-p var-dir)
(make-directory var-dir t))
(setq no-littering-var-directory var-dir)))
#+end_src
Use the executable path from the shell
#+begin_src emacs-lisp
(use-package exec-path-from-shell
:disabled t
:if (memq window-system '(mac ns x))
:init
(exec-path-from-shell-initialize))
(use-package use-package-company
;; Originally written by Foltik, but I use my fork
:straight (use-package-company :host github :repo "akirak/use-package-company"))
(use-package info
:straight (:type built-in)
:config
(add-to-list 'Info-directory-list
(expand-file-name "share/info"
(file-name-directory
(string-remove-suffix "/" invocation-directory)))))
#+end_src
*** Keybindings
#+begin_src emacs-lisp
(use-package which-key
:init
(which-key-mode t)
:config
(which-key-setup-side-window-bottom)
(defmacro akirak/which-key-add-stripped-prefix (prefix)
"Add PREFIX as a stripped prefix to which-key-replacement-alist'."
(add-to-list 'which-key-replacement-alist
(quote ((nil . ,prefix) .
(lambda (kb)
(cons (car kb)
(string-remove-prefix ,prefix (cdr kb))))))))
(akirak/which-key-add-stripped-prefix "akirak/")
(akirak/which-key-add-stripped-prefix "helm-org-multi-wiki-create/"))
#+end_src
Use general.el to define keybindings. It has made several
improvements over bind-key, including a built-in integration with
which-key.
This also adds support for =:general= keyword in use-package
directives.
#+begin_src emacs-lisp
(use-package general
:config
(general-create-definer akirak/bind-search :prefix "M-s")
(general-create-definer akirak/bind-jump :prefix "M-g")
(general-create-definer akirak/bind-register :prefix "C-x r")
(general-create-definer akirak/bind-help :prefix "")
(general-create-definer akirak/bind-file-extra :prefix "")
;; is currently free
(general-create-definer akirak/bind-f8 :prefix "")
;; is reserved for recompile
(general-create-definer akirak/bind-admin :prefix ""
:prefix-map 'akirak/admin-map)
;; ~C-c~ is reserved for the user.
;; Package developers should not use them for their packages.
(general-create-definer akirak/bind-user :prefix "C-c")
;; bind-mode (C-,) for major-mode-specific commands
(defconst akirak/mode-prefix-key "C-,"
"Prefix for mode-specific keys.")
(general-create-definer akirak/bind-mode :prefix akirak/mode-prefix-key)
;; Use ~<C-return>~ for starting a REPL session
(general-create-definer akirak/bind-mode-repl
:prefix "<C-return>")
;; TODO: I want to change this key to something else
(general-create-definer akirak/bind-customization :prefix "C-x ESC"))
(use-package defrepeater
:general
([remap other-window] (defrepeater #'other-window)
[remap winner-undo] (defrepeater #'winner-undo)
[remap winner-redo] (defrepeater #'winner-redo)
[remap text-scale-increase] (defrepeater #'text-scale-increase)
[remap text-scale-decrease] (defrepeater #'text-scale-decrease)))
#+end_src
*** Default settings
#+begin_src emacs-lisp
(require 'setup-defaults)
(when (akirak/running-on-crostini-p)
(require 'my/system/platform/crostini))
(require 'setup-gpg)
#+end_src
*** Migrating
In case there are functions that depends on these modules,
load them first.
#+begin_src emacs-lisp
(require 'my/project)
(require 'my/buffer/predicate)
(org-babel-load-file (expand-file-name "main.org" user-emacs-directory))
#+end_src
** Packages
#+begin_src emacs-lisp
(use-package dash-docs)
(use-package emacs-everywhere
;; Use my fork until the path issue is fixed
:straight (:host github :repo "akirak/emacs-everywhere" :branch "with-editor-1")
:functions (emacs-everywhere)
:general
(:keymaps 'emacs-everywhere-mode-map
;; Analogous to the post command in most web applications,
;; and it's also bound to mode-aware repl commands, which
;; is irrelevant in text-mode.
"<C-return>" #'emacs-everywhere-finish))
(use-package helm-dash
:custom
(dash-docs-browser-func #'akirak/browse-url))
(use-package discover-my-major
:commands (discover-my-major))
(use-package electric
:straight (:type built-in)
:hook
(text-mode . electric-pair-local-mode))
(use-package epkg)
(use-package helm-tail
:commands (helm-tail))
(use-package org-recent-headings
:disabled t
:after org
:config
(general-add-hook 'org-recent-headings-advise-functions
'(org-multi-wiki-follow-link
org-multi-wiki-visit-entry
akirak/avy-org-heading
org-insert-heading
helm-org-ql-show-marker
helm-org-ql-show-marker-indirect))
(org-recent-headings-mode 1)
(setq org-recent-headings-reject-any-fns
(list (defun akirak/org-recent-headings-reject-journal-date (entry)
(when (featurep 'org-multi-wiki)
(let ((file (org-recent-headings-entry-file entry))
(olp (org-recent-headings-entry-outline-path entry)))
(when-let (plist (org-multi-wiki-entry-file-p file))
(and (eq 'journal (plist-get plist :namespace))
(= 1 (length olp)))))))))
(defun akirak/org-recent-headings-cleanup ()
(interactive)
(let ((m (length org-recent-headings-list))
(start-time (float-time))
(n (progn
(dolist (x org-recent-headings-list)
(condition-case _
(org-recent-headings--entry-marker x)
(error (cl-delete x org-recent-headings-list
:test #'org-recent-headings--equal))))
(length org-recent-headings-list))))
(unless (= m n)
(message "Deleted %d non-existent items from org-recent-headings-list in %.1f s"
(- m n)
(- (float-time) start-time))))
;; Prevent automatic GC toon soon after getting back to work
(garbage-collect))
(run-with-idle-timer 1200 t #'akirak/org-recent-headings-cleanup))
(use-package helm-org-recent-headings
:disabled t
:after (helm org-recent-headings)
:config
;; Modified from helm-org-recent-headings-source'. (defvar akirak/helm-org-recent-headings-source (helm-build-sync-source " Recent Org headings" :candidates (lambda () org-recent-headings-list) :candidate-number-limit 'org-recent-headings-candidate-number-limit :candidate-transformer 'helm-org-recent-headings--truncate-candidates :keymap helm-org-recent-headings-map :action 'akirak/helm-org-recent-headings-actions) "Helm source for
org-recent-headings'.")
(defvar akirak/helm-org-recent-headings-actions
(helm-make-actions
"Show entry (default function)" 'org-recent-headings--show-entry-default
"Show entry in real buffer" 'org-recent-headings--show-entry-direct
"Show entry in indirect buffer" 'org-recent-headings--show-entry-indirect
"Insert a link to the heading"
(defun akirak/org-recent-headings-insert-link (entry)
(unless (derived-mode-p 'org-mode)
(user-error "Not in org-mode"))
(let ((marker (org-recent-headings--entry-marker entry)))
(with-current-buffer (marker-buffer marker)
(org-with-wide-buffer
(goto-char marker)
(org-store-link nil 'interactive))))
(org-insert-last-stored-link 1))
"Remove entry" 'helm-org-recent-headings-remove-entries
"Bookmark heading" 'org-recent-headings--bookmark-entry)))
(use-package license-templates)
(use-package project
:config
(add-hook 'project-find-functions
(defun akirak/project-tramp-root (dir)
(-some->> (file-remote-p dir)
(cons 'remote))))
(add-hook 'project-find-functions
(defun akirak/project-syncthing-root (dir)
(-some->> (locate-dominating-file dir ".stfolder")
(cons 'syncthing)))))
(use-package su)
(use-package valign
:disabled t
:hook
(org-mode . valign-mode))
(use-package whole-line-or-region)
#+end_src
*** Modules
#+begin_src emacs-lisp
(require 'setup-project)
(require 'setup-git-bookmark)
(require 'setup-info)
(require 'setup-unicode)
(require 'setup-mmm)
#+end_src
*** Starting the server
This may fail if there is another Emacs session running a server.
#+begin_src emacs-lisp
(ignore-errors
(unless (server-running-p)
(server-start)))
#+end_src
** Commands and keybindings
*** Basic keybindings
These keybindings basically emulate UNIX shells (i.e. sh, bash,
etc.).
I also like to define "dwim" commands, if applicable, to save the
keybinding space and key strokes.
**** C-a
By default, ~C-a~ is bound to =beginning-of-line=.
This command first jump to the indentation and then visits the
beginning of line.
#+begin_src emacs-lisp
(general-def prog-mode-map
"C-a"
(defun akirak/back-to-indentation-or-beginning-of-line ()
(interactive)
(if (or (looking-at "^")
(string-match-p (rx (not (any space)))
(buffer-substring-no-properties
(line-beginning-position)
(point))))
(back-to-indentation)
(beginning-of-line))))
#+end_src
In =org-mode=, I prefer =org-beginning-of-line=.
#+begin_src emacs-lisp
(general-def :keymaps 'org-mode-map :package 'org
"C-a" #'org-beginning-of-line)
#+end_src
**** C-e
#+begin_src emacs-lisp
(general-def :keymaps 'org-mode-map :package 'org
"C-e" #'org-end-of-line)
#+end_src
**** C-h
#+begin_src emacs-lisp
(general-def
"C-h" 'backward-delete-char)
#+end_src
**** C-w
#+begin_src emacs-lisp
(general-def
"C-w"
(defun akirak/kill-region-or-backward-kill-word (&optional arg)
"If a region is active, run kill-region'. Otherwise, run
backward-kill-word'."
(interactive "p")
(if (region-active-p)
(kill-region (region-beginning) (region-end))
(backward-kill-word arg))))
(general-def minibuffer-local-map
"C-w" #'backward-kill-word)
(general-def ivy-minibuffer-map :package 'ivy
"C-w" #'ivy-backward-kill-word)
#+end_src
**** C-u
#+begin_src emacs-lisp
(general-def minibuffer-local-map
"C-u" #'backward-kill-sentence)
(general-def ivy-minibuffer-map :package 'ivy
"C-u"
(defun ivy-backward-kill-sentence ()
(interactive)
(if ivy--directory
(progn (ivy--cd "/")
(ivy--exhibit))
(if (bolp)
(kill-region (point-min) (point))
(backward-kill-sentence)))))
#+end_src
**** C-r
In minibuffers, ~C-r~ should call history.
#+begin_src emacs-lisp
(general-def ivy-minibuffer-map :package 'ivy
"C-r" 'counsel-minibuffer-history)
#+end_src
*** Key translation and simulation
Since I have bound C-h to =backward-delete-char= but still use the
help system frequently, I bind ~M-`~ to ~~ in
=key-translation-map=.
#+begin_src emacs-lisp
(general-def key-translation-map
;; * Obsolete
;; As