Emacs is a customizable lisp based entity.
Emacs Configuration
Requirements
- Git: distributed version control system
- GNU/GUIX: a functional package management system
- LitDoc: literate programming utility
Introduction
This document contains LitDoc configuration setting for installing
and configuring Emacs v31.0+ built with GTK(V3+) and native
compilation support. The configuration is primarily written
to work with <a href="https://gitlab.com/aarongile/projects/litdoc">litdoc</a>,
a GNU/Guix and Emacs based literate programming tool.
Usage
To run Emacs from this document, first clone this repo & build it,
# clone project repo
git clone https://codeberg.org/distroconfig/emacs
# add repo path to LITDOC_PATH (optional)
export LITDOC_PATH="$(pwd):${LITDOC_PATH}"
# or alternatively switch to the emacs git directory
# cd ./emacs
# run build
guix ld build emacs
After first build completes, Emacs can be subsequently stared using,
guix ld emacs
Dependencies
This configuration sets-up Emacs built with pure GTK(v3+) and native-comp
support via the official GNU/Guix package. The package is added here as
the main dependency for the project.
(specifications->manifest
'( "emacs-next-pgtk" ))Startup
When running Emacs using guix ld emacs, a startup shell script
is used to setup EMACSLOADPATH with additional Emacs/Lisp package library
paths.
#! /usr/bin/env bash
source "${LITDOC_DIR}/share/sh/log.sh"
# define LitDoc specific elisp library path and add it to EMACSLOADPATH
EMACS_SITELISP_DIR="${LITDOC_CACHEDIR}/emacs/share/elisp/site-lisp"
export EMACSLOADPATH="${EMACS_SITELISP_DIR}:${EMACSLOADPATH}"
# verify the default emacs directory is not interfering
# with this configuration.
[ -d "${HOME}/.emacs.d" ] && {
log_error "default emacs directory found at: ${HOME}/.emacs.d"
log_error "move this directory to allow litdoc to configure Emacs to use your configuration files."
exit 1
}A shell command for launching emacs in daemon mode is also introduced,
nargs=$#
index=0
args_rest=""
while [ $index -lt ${nargs} ]
do
case "$1" in
--server | -s )
opt_daemon=true
log_info "daemon-mode" "on"
;;
,*)
args_rest="${args_rest} $1 "
;;
esac
shift
index=$((index+1))
done
set -- "${args_rest}"
log_info "emacs"
log_info ": startup directory" "${XDG_CONFIG_HOME}"
log_info ": startup args" "$@"
if [ "${opt_daemon}" = "true" ]; then
log_info "starting Emacs using a daemon ..."
if ! emacsclient -e 0 >& /dev/null; then
emacs --daemon $@ &
[ $? -eq 0 ] || {
log_error "failed start Emacs daemon "
exit 1
}
fi
emacsclient --init-dir="${EMACS_SITELISP_DIR}" -c $@ || {
log_error "failed to start Emacs as a daemon"
exit 1
}
log_info "starting emacs using a daemon ... [OK]"
else
log_info "starting Emacs ..."
emacs --init-dir="${EMACS_SITELISP_DIR}" $@ || {
log_error "failed to start emacs"
}
log_info "starting Emacs ... [OK]"
fiStandard Emacs startup files: early-init.el and init.el are also
available to change Emacs's startup behavior.
early-init.el
;;; early-init.el --- -*- lexical-binding: t coding: utf-8 -*-
;; Author: Aron Gile
(when (version< emacs-version "30.0.50")
(error
"this version of Emacs(%s) is not supported. Use version 30.0.50+"
version))
(require 'cl-lib)
(require 'log)
(require 'fs)
(require 'startup-profiler)
(log::info "starting Emacs ...")
(log::sub-info "version: %s" emacs-version)
(add-to-list 'command-switch-alist '("--profile-startup" . startup-profiler::start))
;; Toggle debug off
(setq toggle-debug-on-error nil)
(log::sub-info "debug on error: %s"
(if (and (boundp 'toggle-debug-on-error) toggle-debug-on-error) "true" "false"))
(log::sub-info "profiler: %s"
(if (boundp 'startup-profiler:enabled)
"on" "off"))
;; user-emacs-directory
(setq user-emacs-directory
(fs::dir-ensure (substitute-in-file-name "${HOME}/.config/litdoc/emacs/user-directory")))
;; //////////////////////////////////////////////////////////////////////////////
;; startup speed
;; majority of these settings are copied of Doom's configuration
;; see: https://github.com/hlissner/doom-emacs/blob/develop/early-init.el
;; /////////////////////////////////////////////////////////////////////////////
;; Prevent unwanted runtime compilation for gccemacs (native-comp) users;
;; packages are compiled ahead-of-time when they are installed and site files
;; are compiled when gccemacs is installed.
(setq native-comp-deferred-compilation nil)
;; In noninteractive sessions, prioritize non-byte-compiled source files to
;; prevent the use of stale byte-code. Otherwise, it saves us a little IO time
;; to skip the mtime checks on every *.elc file.
(setq load-prefer-newer noninteractive)
;; NO need to perform package initilaization since all packages are managed using either
;; Guix or using source code directly
(setq package-enable-at-startup nil)
;;disable garbage collection
(setq gc-cons-threshold (* 300 1024 1024)
gc-cons-percentage 0.6)
(log::info "garbage collection" )
(log::sub-info "threshold: %s MB" (format "%f" (/ gc-cons-threshold 1048576)))
(log::sub-info "percentage: %s" gc-cons-percentage)
;;; prevent glitchy UI
(unless (or (daemonp) noninteractive)
;; Prevent glichy UI from displaying during init
(push '(menu-bar-lines . 0) default-frame-alist)
(push '(tool-bar-lines . 0) default-frame-alist)
(push '(vertical-scroll-bars . 0) default-frame-alist)
(setq frame-inhibit-implied-resize t
inhibit-startup-screen t
auto-window-vscroll nil))
;; prefered coding-system is UTF-8
(set-language-environment "UTF-8")
;; enable only safe local variables
(setq enable-local-variables 'safe)
;; change eln load path
(setq native-comp-eln-load-path
(let ((comp-dir (fs::dir-ensure "${HOME}/.cache/litdoc/emacs/eln")))
(when (boundp 'native-comp-eln-load-path)
(cons comp-dir native-comp-eln-load-path))))
(when (boundp 'native-comp-eln-load-path)
(cl-loop
for eln-path in native-comp-eln-load-path
do
(log::info "eln-path: %s" eln-path)))
;; Local Variables:
;; no-byte-compile: t
;; indent-tabs-mode: nil
;; End:
;;; early-init.el ends here
init.el
;;; init.el --- main init file -*- lexical-binding: t coding: utf-8 -*-
;; Authors: Aron Gile
(require 'cl-lib)
(require 'log)
(require 'fs)
(require 'startup-profiler)
;;; main configuration loader
(defun init::lazy-load ( )
"Lazy Load the main Emacs config emacs.el file"
(let* ((emacs-site-lisp (substitute-in-file-name "${HOME}/.cache/litdoc/emacs/share/elisp/site-lisp"))
(debug-on-error t)
(emacs-el-file (expand-file-name "emacs.el" emacs-site-lisp )))
;; check emacs
(unless (file-exists-p emacs-el-file)
(error "[ERROR]: unable to read startup library: %s" emacs-el-file))
;; load: emacs.el
(unless (load emacs-el-file 'NOERROR)
(error "[ERROR]: failed to load startup library: %s" emacs-el-file))
;; restore resource consumption settings
(setq gc-cons-threshold (* 32 1024 1024))
(setq gc-cons-percentage 0.1)
(garbage-collect)
(startup-profiler::stop)))
;; bulk of this Emacs configuration does not apply to
;; a non GUI environment. Load it only if necessary.
(unless (or (daemonp) noninteractive)
(set-frame-parameter nil 'environment nil)
(set-frame-parameter nil 'undecorated t)
(add-to-list 'default-frame-alist '(fullscreen . fullboth))
(setq frame-inhibit-implied-resize t
inhibit-startup-screen t
auto-window-vscroll nil
help-window-select t)
(toggle-frame-fullscreen)
;;(init::lazy-load)
(run-with-idle-timer 1 nil #'init::lazy-load))User Information
Personal information is passed to Emacs using environment variables at runtime.
;; -*- lexical-binding: t; -*-
(setq user-full-name (getenv "USER_FULL_NAME"))
(setq user-mail-address (getenv "USER_EMAIL_ADDRESS"))Package Management
Introduce a use-package keyword, :guix used for managing
GNU/Guix Emacs packages associated with an org document.
Note: use-package-guix.el is here only to configure
use-package.el and mark which packages should be fetched
from GNU/Guix repository.
;;; use-package-guix.el --- install emacs packages using GNU/Guix software manager -*- lexical-binding: t; -*-
(require 'cl-lib)
(require 'use-package)
;;////////////////////////////////////////////////////////////////////////////
;;; Use-package extension
;;;////////////////////////////////////////////////////////////////////////////
(defun guix-use-package-init ()
;; initialize :guix keyword
(unless (member :guix use-package-keywords)
(setq use-package-keywords
(let* (
(pos (cl-position :unless use-package-keywords))
(head (cl-subseq use-package-keywords 0 (+ 1 pos)))
(tail (nthcdr (+ 1 pos) use-package-keywords))
)
(append head (list :guix) tail)))))
;;;###autoload
(defun use-package-normalize/:guix ( name:symbol keyword args )
"Consumes the :guix keyword without performing any operation."
(use-package-only-one
(symbol-name keyword) ;;label
args ;;arguments
`(lambda (_label arg) ;;functions
arg)))
;;;###autoload
(defun use-package-handler/:guix (name:symbol keyword spec rest state)
"Install Guix package specified by PACKAGE-SPEC.
- NAME-SYMBOL: symbolic name of the elisp-package
- KEYWORD: this is always :guix
- REST: property list of the rest of the keywords applied to the use-package definition.
- STATE: custom data"
(use-package-process-keywords name:symbol rest state))
(guix-use-package-init)
(provide 'use-package-guix)
;;; use-package-guix.el ends here
Make the use-package-=guix syntax available at startup:
;; load use package
(require 'use-package)
(setq use-package-always-defer t)
(require 'use-package-guix)
(use-package use-package
:init
(setq use-packae-always-defer t)
(require 'use-package-guix))Libraries
This section specifies common elisp libraries.
-
f.el:filesystem utility;;; fs.el --- file system utily -*- lexical-binding: t; coding: utf-8 -*- ;;; ;;; Copyright (C) 2024 Aron Gile(aarongile@gmail.com) ;;; ;;; This file is part of Litdoc. ;;; Litdoc 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. ;;; ;;; Litdoc 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 Litdoc. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;;; Code: (require 'cl-macs) ;;;###autoload (defun fs::dir-ensure (dir) "Create =DIR= if it does not exist and return its path." (cl-assert (stringp dir) t " fs::dir-ensure: DIR is a string type" ) (let ((full-name (expand-file-name (substitute-in-file-name dir)))) (unless (file-directory-p full-name) (shell-command-to-string (format "mkdir -p %s" full-name))) full-name)) ;;;###autoload (defun fs::load-dir-ensure (dir) "Create =DIR= if it does not exist and add its path to `load-path'" (cl-assert dir 'show-args " fs::load-dir-ensure: parameter DIR must be string type" ) (cl-assert (stringp dir) 'show-args " fs::load-dir-ensure: parameter DIR must be string type" ) (let ((default-directory ( fs::dir-ensure dir))) (add-to-list 'load-path default-directory) (setq load-path (delete-dups load-path)) (normal-top-level-add-subdirs-to-load-path) default-directory)) ;;;###autoload (defun fs::file-ensure ( file ) "Create an empty file if FILE does not exist." (cl-assert (not (null file)) 'show-args " fs::load-dir-ensure: parameter DIR can't be nil" ) (cl-assert (stringp file) 'show-args " fs::file-ensure: parameter FILE must be of type string") (let ((full-name (expand-file-name (substitute-in-file-name dir)))) (unless (file-exists-p full-name) ( fs::dir-ensure (file-name-directory full-name)) (f-touch full-name)) full-name)) (provide 'fs) ;;; fs.el ends here -
log.el;;; log.el --- log utilities -*- lexical-binding: t; coding: utf-8 *- ;;; SPDX-License-Identifier: GPL-3.0-or-later ;;; Copyright © 2024 Aron Gile <aronggile@gmail.com> ;;;###autoload (defmacro log::info ( msg &rest args) "Writes log message MSG into the *Messages* buffer. When MSG is a format specifier ARGS can be used to pass formatting arguments. See `format' for details" `(with-current-buffer (get-buffer-create "*Messages*") (let ((inhibit-read-only t)) (message (concat (cond (load-file-name (file-name-nondirectory load-file-name)) (buffer-file-name (file-name-nondirectory buffer-file-name)) (t "*")) ": " ,msg) ,@args)))) ;;;###autoload (defmacro log::sub-info ( msg &rest args) "MSG is prefixed with the string--'|_'. A list of arguments ARGS can be specified if MSG is a format specifier." ;; (let ((prefixed-msg (format "|_ %s" msg))) `(with-current-buffer (get-buffer-create "*Messages*") (let ((inhibit-read-only t)) (message (concat "|_ " ,msg) ,@args)))) (provide 'log) ;;; log.el ends here -
startup-profiler.el;;; startup-profiler.el --- -*- lexical-binding: t coding: utf-8 -*- ;;; SPDX-License-Identifier: GPL-3.0-or-later ;;; Copyright © 2024 Aron Gile <aronggile@gmail.com> ;;; Emacs Startup Profiler (require 'profiler) (defvar startup-profiler:started nil "When set to none-nil value, assume Enacs profiler is running") (defun startup-profiler::start (switch) (setq startup-profiler:started t) (profiler-start 'cpu+mem)) (defun startup-profiler::stop ( ) "If profiler has been running, report startup time" (when (boundp 'startup-profiler:started) (profiler-stop) (message (format "Startup(%.2fsec) Load(%.2fsec) #gc(%d)" (float-time (time-subtract after-init-time before-init-time)) (float-time (time-subtract (current-time) before-init-time)) gcs-done)))) (provide 'startup-profiler) ;;; startup-profiler.el ends here -
async.el:a module for doing asynchronous processing in Emacs.(use-package async :guix (:name emacs-async)) s.el:a string manipulation library.
(use-package s
:guix (:name emacs-s))dash.el: a modern list API for Emacs.
(use-package dash
:guix (:name emacs-dash))f.el: provides an Emacs library for working with files and directories.
(use-package f
:guix (:name emacs-f))flycheck: provides modern syntax checking facilities.
(use-package flycheck
:guix (:name emacs-flycheck))persist.el: provides variables which persist across sessions.
(use-package persist
:guix (:name emacs-persist)
:config
(setq persist--directory-location
(fs::dir-ensure (expand-file-name "persist" emacs:cache-dir))))Files & Directories
Emacs auto generates several backup, cache, lock and similar files that tend to pollute the file system overtime. This section attempts to organize and tidy up some of these files by moving them elsewhere.
User Data Directory: is a directory for storing user data. The directory can be configured using emacs-preferences-dir variable:
(defconst emacs:preferences-dir
(fs::dir-ensure "${HOME}/.config/litdoc/emacs/preferences")
"File path for user configuration settings")Cache Directory: Content of this directory is for storing auto generated files we're not interested in keeping track of. The cache directory is managed using emacs-cache-dir variable:
(defconst emacs:cache-dir
(fs::dir-ensure "${HOME}/.cache/litdoc/var/emacs")
"File path for cached Emacs data")The rest of this section tries to move noisy files to either the preference or cache directory.
Customization File
A customization setting is user generated data using Emacs's customization widget.
(setq custom-file
(expand-file-name "custom.el" emacs:preferences-dir))
(when (file-exists-p custom-file)
(load custom-file 'NOERROR ))History Files
(use-package recentf
:demand t
:custom
(recentf-save-file (expand-file-name "recentf" emacs:cache-dir))
:init
(recentf-mode)
:config
;; create recent files directory
;; if it isn't already created
(require 'fs)
(let ((recentf-dir (file-name-directory recentf-save-file)))
(fs::dir-ensure recentf-dir)))Backup files
(use-package files
:demand t
:config
;; auto saving
(setq
auto-save-default t ;; disable auto-save
auto-save-visited-file-name nil ;; don't
auto-save-interval 1500 ;; 1500 keystrokes
auto-save-list-file-prefix (expand-file-name ".auto-saves-" emacs:cache-dir)
auto-save-file-name-transforms `((".*" ,emacs:cache-dir t))
auto-save-timeout 1 ;;1 second of idle time
auto-save-include-big-deletions nil)
;; backup
(setq
backup-by-copying t
backup-directory-alist `( ( "." . ,emacs:cache-dir) ( ,tramp-file-name-regexp nil) )))Emacs Shell History Files
(use-package em-hist
:custom
(eshell-history-file-name
(fs::dir-ensure
(expand-file-name "eshell/history" emacs:cache-dir))))Transient Files
(use-package transient
:custom
(transient-history-file
(expand-file-name "transient.el" emacs:cache-dir)))Emacs Network Security Manager
Set the Emacs Network Security Manager level to high See Emacs TLS defaults are downright dangerous .
(use-package nsm
:demand t
:custom
(nsm-settings-file
(let ((nsm-settings-dir nil))
(require 'fs)
(setq nsm-settings-dir (expand-file-name "nsm" emacs:preferences-dir))
(fs::dir-ensure nsm-settings-dir)
(expand-file-name "network-security.data" nsm-settings-dir)))
(network-security-level 'high))Diagnostics
Use esup to identifying Emasc startup issues.
(use-package esup
:guix ( :using channel :name emacs-esup)
:defer t
:commands esup)And, the built-in edebug package for debugging elisp code.
(use-package edebug
:commands (emacs::debug-on emacs::debug-off))Available interactive commands for debugging are:
(defun emacs::debug-on ()
"Toggle on instrumentalisation for the function under `defun'."
(interactive)
(eval-defun 'edebugit))
(defun emacs::debug-off ()
"Toggle off instrumentalisation for the function under `defun'."
(interactive)
(eval-defun nil))Key Binding Management
Key bindings in this configuration target evil-mode.
The general.el package,
which provides evil friendly utilities, is used extensively to setup key
bindings.
(use-package kb
:demand t)Preface
;;; kb.el --- -*- lexical-binding: t coding: utf-8 -*-
;; Author: Aron GileKey Bindings
Lead-key the default lead key can be configured using kb:lead-key variable:
(defcustom kb:lead-key "SPC"
"Lead key for commands"
:type 'string)
New key bindings are introduced using general.el definers kb::def, which
is leader-key aware, or using kb::defg, which defines
global key bindings.
(general-create-definer kb::def :prefix kb:lead-key)
(general-create-definer kb::defg )
Disable C-z from invoking suspend-frame.
(global-unset-key (kbd "C-z"))
(global-unset-key (kbd "C-x C-z"))Customizations
package: general
(use-package general
:guix (:name emacs-general )
:demand t
:config
<<general>>)
package: evil
(use-package evil
:guix (:name emacs-evil)
:demand t
:after general
:init
(setq evil-want-keybinding nil)
:config
(setq evil-move-cursor-back t
evil-want-integration t
evil-default-state 'normal)
;; Make movement keys work like they should
(define-key evil-normal-state-map (kbd "<remap> <evil-delete>") 'evil-delete)
(define-key evil-normal-state-map (kbd "<remap> <evil-next-line>") 'evil-next-visual-line)
(define-key evil-normal-state-map (kbd "<remap> <evil-previous-line>") 'evil-previous-visual-line)
(define-key evil-motion-state-map (kbd "<remap> <evil-next-line>") 'evil-next-visual-line)
(define-key evil-motion-state-map (kbd "<remap> <evil-previous-line>") 'evil-previous-visual-line)
(setq-default evil-cross-lines t)
(evil-mode 1)
(setq evil-default-state 'normal)
(unbind-key "SPC" evil-normal-state-map))
(use-package evil-collection
:guix (:name emacs-evil-collection)
:after evil )
package: which-key
(use-package which-key
:guix (:name emacs-which-key)
:defer 2
:config
(which-key-setup-side-window-right)
(setq which-key-separator " ")
(setq which-key-prefix-prefix "+")
(setq which-key-idle-secondary-delay 0)
(setq which-key-sort-order 'which-key-description-order)
(add-to-list 'which-key-replacement-alist '( ("TAB" . nil) . ("↹" . nil)))
(add-to-list 'which-key-replacement-alist '( ("RET" . nil) . ("⏎" . nil)))
(add-to-list 'which-key-replacement-alist '( ("DEL" . nil) . ("⇤" . nil)))
(add-to-list 'which-key-replacement-alist '( ("SPC" . nil) . ("␣" . nil)))
(which-key-mode))
package: hydar
(use-package hydra
:guix (:name emacs-hydra )
:demand t)Epilogue
(provide 'kb)
;;; kb.el ends hereWindow Management
(defconst emacs:doremi-dir
(expand-file-name
"emacs/share/elisp/site-lisp/doremi"
(getenv "DISTROCONFIG_DIR"))
"Root directory for doremi library")
(use-package doremi
:load-path emacs:doremi-dir
:general
(kb::defg
:states '(normal motion visual)
"C-<up>" '(text-scale-increase :wich-key "++ Font Size")
"C-<down>" '(text-scale-decrease :which-key "-- Font Size"))
(kb::def :states 'normal
;;winow
"w" '(:ignore t :which-key "window")
"w <right>" '(split-window-right :which-key "split window right")
"w <down>" '(split-window-below :which-key "split window below")
"wd" '(delete-window :which-key "delete active window")
"w:" '(doremi-window-width+ :which-key "widndow width +/-")
"w=" '(balance-windows :which-key "balance windows"))
:commands doremi-window-width+
:config
(require 'doremi-cmd))Command Completion
This section defines the completion & search setup, which is useful for command discovery, text completion, and search within the Emacs mini buffer.
Key Bindings
Keybindings for discovering commands, files, kill-ring, and buffer from within the mini-buffer.
(kb::def :states 'normal
;; commands
"SPC" '(helm-M-x :which-key "helm")
"xx" '(execute-extended-command :which-key "meta-x")
"xe" '(eval-expression :which-key "eval")
;;files
"f" '(:ignore t :which-key "files")
"ff" '(helm::find-file :which-key "helm")
;;Kill ring
"k" '(:ignore t :which-key "kill ring")
"kr" '(helm-show-kill-ring :which-key "search kill ring")
;;buffer
"b" '(:ignore t :which-key "buffer")
"bb" '(helm-mini :which-key "list")
"bd" '(kill-current-buffer :which-key "delete current buffer"))(kb::def
:states 'normal
:keymaps 'override
"S" '(:ignore t :which-key "spell check")
"Sc" '(flyspell-correct-previous :which-key "corrections"))Keybindings for searching text in the current buffer.
(kb::defg
:states '(normal motion)
"/" '(helm-swoop :which-key "Helm Swoop"))Setup
Helm
The helm package provides is a completion framework for searching interactively
Emacs commands
See
Emacs-Helm project
(use-package helm
:guix (:name emacs-helm )
:general
;; enable navigating helm file menu
;; using left and right arrow keys
;; to move up and down directory tree.
(kb::defg :keymaps 'helm-find-files
"<left>" '(helm-find-files-up-one-level)
"<right>" '(helm-execute-persistent-action))
;; helm keybindings
<<helm>>
:init
;;BUG: `helm-find-files` fails to open a file if
;;`directory-backup-alist-file` is not set to empty list
(defun helm::find-file (arg)
"Helm find file helper.
This fuction provider a wrapper for `helm-find-files` command."
(interactive "P")
(require 'helm-files)
(let ((backup-directory-alist '()))
(helm-find-files arg)))
:config
(setq directory-backup-alist-file '())
;;BUG: Needs further investigation
;; Helm fails to check `browse-url-galeon' and `browse-url-netscape'
;; variables for nullity in Emacs 29. Maybe browser.el got updated
;; and Helm didn't catch up?
;;
;; As a work around we set both variables to an empty string
(setq browse-url-galeon-program "")
(setq browse-url-netscape-program "")
(require 'helm-command)
(setq helm-boring-file-regexp-list '("\\.git$" "\\.svn$" "\\.elc$")
helm-ff-skip-boring-files t
helm-buffers-fuzzy-matching t
helm-ff-auto-update-initial-value t
helm-input-idle-delay 0.01
helm-idle-delay 0.1
helm-candidate-number-limit 100
helm-yas-display-key-on-candidate t
helm-quick-update t
helm-M-x-requires-pattern nil
helm-mode-fuzzy-match t
helm-completion-in-region-fuzzy-match t)
;; Make Helm window at the bottom WITHOUT using any extra package
;; see: https://www.reddit.com/r/emacs/comments/345vtl/make_helm_window_at_the_bottom_without_using_any/
(add-to-list
'display-buffer-alist
`(,(rx bos "*helm" (* not-newline) "*" eos)
(display-buffer-in-side-window)
(inhibit-same-window . t)
(window-height . 0.4))))
The helm-swoop package is a useful command for searching
text in the current buffer.
(use-package helm-swoop
:guix (:name emacs-helm-swoop)
:defer t
:commands helm-swoop
:general
<<helm-swoop>> )
helm-descbinds
(use-package helm-descbinds
:guix (:name emacs-helm-descbinds )
:defer t
:hook
(helm-mode . helm-descbinds-mode))Company
Company is a general purpose auto completion framework targeting
primarily the users current working buffer.
(use-package company
:guix (:name emacs-company)
:defer t
:general
<<company-kb>>
:custom
(company-begin-commands '(self-insert-command))
(company-idle-delay .1)
(company-minimum-prefix-length 1)
(company-show-numbers nil)
(company-tooltip-align-annotations 't)
:hook
((prog-mode . company-mode)
(text-mode . company-mode)))Ivy
ivy is lightweight modern completion system.
It's straight forward API makes it suitable for
making quick custom commands that support completion.
(use-package ivy
:guix (:name emacs-ivy)
:defer t)Spelling Checking
(use-package ispell-program
:guix (:name ispell)
:ensure nil)
(use-package flyspell-correct
:guix (:name emacs-flyspell-correct )
:defer t
:diminish flyspell-mode "abc_✓"
:custom
;; personal dictionary file name
(ispell-personal-dictionary
(let ((dic-dir (expand-file-name "i18n" emacs:preferences-dir)))
(fs::dir-ensure dic-dir)
(expand-file-name "en_US.dic" dic-dir)))
;; spelling correction completion callback
(flyspell-correct-interface #'flyspell-correct-helm)
:hook
((org-mode . flyspell-mode))
:general
<<flyspell-correct-kb>>
:config
;;///////////////////////////////////////////////////////////////////////
;; Backend //////////////////////////////////////////////////////////////
;;//////////////////////////////////////////////////////////////////////
(require 'flyspell)
(require 'ispell)
(require 'flyspell-correct-helm))Terminal Emulator
This section sets terminal emulator within Emacs buffer <em>See</em> <a href="https://echosa.net/blog/2012/06/06/improving-ansi-term/">Improving Ansi-Term </a> for an excellent configuration walk-through
Key bindings
(kb::def :states 'normal
";" '(ansi-term :which-key "open terminal"))Functions
(defun ansi-term::use-utf8 ( )
"Set the default terminal emulator encoding to UTF-8"
(set-buffer-process-coding-system 'utf-8-unix 'utf-8-unix))
(defun ansi-term::enable-URL-navigation ()
"Enable clicking URL"
(goto-address-mode))
(defun ansi-term::enable-C-y-paste ()
"Enable C-y copy paste in terminal"
(define-key term-raw-map "\C-y" 'my-term-paste))
(defun ansi-term::paste (&optional string)
(interactive)
(process-send-string
(get-buffer-process (current-buffer))
(if string string (current-kill 0))))Configuration
(use-package ansi-term
:init
;; kill terminal buffer and window on exit
;; see:
;; https://blog.echosa.net/blog/2012/06/06/improving-ansi-term/
(defadvice term-sentinel
(around ansi-term::advice-term-sentinel (proc msg))
(if (memq (process-status proc) '(signal exit))
(let ((buffer (process-buffer proc)))
ad-do-it
(kill-buffer buffer))
ad-do-it))
(ad-activate 'term-sentinel)
;; use Bash always
(defadvice ansi-term (before force-bash)
(interactive (list "bash")))
(ad-activate 'ansi-term)
:custom
(explicit-shell-file-name "bash")
:hook
((term-exec . ansi-term::use-utf8)
(term-mode . ansi-term::enable-URL-navigation)
(term-mode . ansi-term::enable-C-y-paste))
:general
<<ansi-term-kb>> )Themes
This section configures Emacs UI feel and style
(use-package themes
:defer 3)Preface
;;; themes.el --- -*- lexical-binding: t coding: utf-8 -*-
;; Author: Aron GileCustomizations
(set-face-attribute 'default nil :family "Source Code Pro" :height 120)
(let* ((variable-tuple '( ))
(headline `( :weight normal )))
(custom-theme-set-faces
'user
`(org-level-8 ((t (,@headline ,@variable-tuple :foreground "yellow" :height 1.0 ))))
`(org-level-7 ((t (,@headline ,@variable-tuple :foreground "yellow" :height 1.0))))
`(org-level-6 ((t (,@headline ,@variable-tuple :foreground "Steelblue4" :height 1.0))))
`(org-level-5 ((t (,@headline ,@variable-tuple :foreground "Steelblue3" :height 1.0))))
`(org-level-4 ((t (,@headline ,@variable-tuple :foreground "Steelblue2" :height 1.0))))
`(org-level-3 ((t (,@headline ,@variable-tuple :foreground "Pink4" :height 1.0))))
`(org-level-2 ((t (,@headline ,@variable-tuple :foreground "RosyBrown" :height 1.0 ))))
`(org-level-1 ((t (,@headline ,@variable-tuple :foreground "SkyBlue" :height 1.0))))
`(org-document-title ((t (,@headline ,@variable-tuple :height 1.0 :underline nil))))))
Package: doom-themes
(use-package doom-themes
:guix (:name emacs-doom-themes )
:demand t
:config
;;https://github.com/doomemacs/themes/issues/875
(setcdr (assoc 'gnus-group-news-low-empty doom-themes-base-faces)
'(:inherit 'gnus-group-mail-1-empty :weight 'normal))
;; 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
;; select theme
(load-theme 'doom-spacegrey 'no-confirm )
;; Enable flashing mode-line on errors
;; (doom-themes-visual-bell-config)
;; Enable custom neotree theme (all-the-icons must be installed!)
;; (doom-themes-neotree-config)
;; or for treemacs users
;; (setq doom-themes-treemacs-theme "doom-nord") ; use the colorful treemacs theme
;; (doom-themes-treemacs-config)
;; Corrects (and improves) org-mode's native fontification.
(doom-themes-org-config)
;;different cursor color for different states
(setq evil-insert-state-cursor '( (bar . 2) "#58c94b" )
evil-normal-state-cursor '( (bar . 3) "#f7a543")
evil-visual-state-cursor '( (box . 5) "#dcdcdc")
evil-replace-state-cursor '( (box . 5) "#ff0000" )
evil-motion-state-cursor '( (box . 5) "#9400d3" ))
<<doom-theme-custom>> )
Package: doom-modeline
(use-package doom-modeline
:guix (:name emacs-doom-modeline )
:defer 3
;;:after all-the-icons
;; :hook (after-init . doom-modeline-mode)
:config
;; How tall the mode-line should be. It's only respected in GUI.
;; If the actual char height is larger, it respects the actual height.
(setq doom-modeline-height 23)
;; How wide the mode-line bar should be. It's only respected in GUI.
(setq doom-modeline-bar-width 3)
;; Determines the style used by `doom-modeline-buffer-file-name'.
;;
;; Given ~/Projects/FOSS/emacs/lisp/comint.el
;; truncate-upto-project => ~/P/F/emacs/lisp/comint.el
;; truncate-from-project => ~/Projects/FOSS/emacs/l/comint.el
;; truncate-with-project => emacs/l/comint.el
;; truncate-except-project => ~/P/F/emacs/l/comint.el
;; truncate-upto-root => ~/P/F/e/lisp/comint.el
;; truncate-all => ~/P/F/e/l/comint.el
;; relative-from-project => emacs/lisp/comint.el
;; relative-to-project => lisp/comint.el
;; file-name => comint.el
;; buffer-name => comint.el<2> (uniquify buffer name)
;;
;; If you are expereicing the laggy issue, especially while editing remote files
;; with tramp, please try `file-name' style.
;; Please refer to https://github.com/bbatsov/projectile/issues/657.
(setq doom-modeline-buffer-file-name-style 'truncate-upto-project)
;; Whether display icons in mode-line or not.
(setq doom-modeline-icon t)
;; Whether display the icon for major mode. It respects `doom-modeline-icon'.
(setq doom-modeline-major-mode-icon t)
;; Whether display color icons for `major-mode'. It respects
;; `doom-modeline-icon' and `all-the-icons-color-icons'.
(setq doom-modeline-major-mode-color-icon t)
;; Whether display icons for buffer states. It respects `doom-modeline-icon'.
(setq doom-modeline-buffer-state-icon t)
;; Whether display buffer modification icon. It respects `doom-modeline-icon'
;; and `doom-modeline-buffer-state-icon'.
(setq doom-modeline-buffer-modification-icon t)
;; Whether display minor modes in mode-line or not.
(setq doom-modeline-minor-modes nil)
;; If non-nil, a word count will be added to the selection-info modeline segment.
(setq doom-modeline-enable-word-count nil)
;; Whether display buffer encoding.
(setq doom-modeline-buffer-encoding t)
;; Whether display indentation information.
(setq doom-modeline-indent-info nil)
;; If non-nil, only display one number for checker information if applicable.
(setq doom-modeline-checker-simple-format t)
;; The maximum displayed length of the branch name of version control.
(setq doom-modeline-vcs-max-length 12)
;; Whether display perspective name or not. Non-nil to display in mode-line.
(setq doom-modeline-persp-name t)
;; Whether display `lsp' state or not. Non-nil to display in mode-line.
(setq doom-modeline-lsp t)
;; Whether display github notifications or not. Requires `ghub` package.
(setq doom-modeline-github nil)
;; The interval of checking github.
(setq doom-modeline-github-interval (* 30 60))
;; Whether display environment version or not
(setq doom-modeline-env-version t)
;; Or for individual languages
(setq doom-modeline-env-enable-python t)
(setq doom-modeline-env-enable-go t)
(setq doom-modeline-env-enable-rust t)
;; Change the executables to use for the language version string
(setq doom-modeline-env-python-executable "python3")
(setq doom-modeline-env-go-executable "go")
(setq doom-modeline-env-rust-executable "rustc")
;; Whether display mu4e notifications or not. Requires `mu4e-alert' package.
(setq doom-modeline-mu4e t)
;; Whether display irc notifications or not. Requires `circe' package.
(setq doom-modeline-irc t)
;; Function to stylize the irc buffer names.
(setq doom-modeline-irc-stylize 'identity)
(doom-modeline-mode 1))
Package: dimmer
BUG: dimmer currently messes with many other system states when setting fonts.
;;themes
(use-package dimmer
:guix (:name emacs-dimmer )
:disabled
:defer 3
:config
(setq dimmer-fraction 0.5)
(dimmer-configure-helm)
(dimmer-configure-which-key)
(dimmer-mode t))
Package: all-the-icons is a collection of useful icon font
(use-package all-the-icons
:guix (:name emacs-all-the-icons)
:defer 3
:config
(font-lock-mode))
Package: font-hack is typeface designed for working with
source code.
(use-package font-hack
:guix (:name font-hack)
:ensure nil)Epilogue
(provide 'themes)
;;; themes.el ends hereBetter Defaults
This section, provides a set of common sense default settings and commands useful when running Emacs.
(use-package better-defaults
:demand t)Preface
;;; better-defaults.el --- -*- lexical-binding: t coding: utf-8 -*-
;; Author: Aron GileUI
New Frame Undecorated by Default
(add-to-list 'default-frame-alist '(undecorated . t) )Disable showing warning buffer at startup
(setq warning-minimum-level :emergency)Line Based Keyboard Scrolling
(setq scroll-step 1
scroll-conservatively 1000
auto-window-vscroll nil)Line Wrapping
(setq-default fill-column 80)
(global-visual-line-mode 1)
(setq line-move-visual t) ;; move via visual lines
(diminish 'visual-line-mode)
(remove-hook 'text-mode-hook #'turn-on-auto-fill)
(add-hook 'text-mode-hook 'turn-on-visual-line-mode)
Disable Ctrl+Z: Frame Suspend
Typing accidentally ctrl-x-ctrl-z triggers window minimize
See
Can't seem to get rid of ctrl-x-ctrl-z key-binding in Emacs for minimize window
;;; disable frame suspend
;;; the most annoying minimize keybinding oh my GOD!
(put 'suspend-frame 'disabled t)
(global-set-key "\C-x\C-z" nil)
(global-set-key (kbd "C-x C-z") nil)Visual Cue Instead of Bell
Prefer visual feedback instead of ringing mode-line.
(use-package mode-line-bell
:guix (:name emacs-mode-line-bell)
:config
(mode-line-bell-mode))Minimal Information in Mode-Line
diminish implements hiding or
abbreviation of the mode line displays(lighters) of minor-modes.
(use-package diminish
:guix (:name emacs-diminish))No widgets in Window
Remove unnecessary widgets
(menu-bar-mode -1)
(scroll-bar-mode -1)
(tool-bar-mode -1)
(tooltip-mode -1)
(blink-cursor-mode -1)
(setq use-dialog-box nil)
;;automatically focus on help window
(setq help-window-select t)
(mouse-avoidance-mode t)Render Color codes
Render color codes instead of showing their hex values.
(use-package rainbow-mode
:demand t
:guix (:using channel :name emacs-rainbow-mode))Text Scaling
(kb::defg
:states '(normal motion visual)
"C-<up>" '(text-scale-increase :wich-key "++ Font Size")
"C-<down>" '(text-scale-decrease :which-key "-- Font Size")
"C-w" '(other-window :which-key "Next Window"))Prompt uses Y/N instead of Yes/No
(fset 'yes-or-no-p 'y-or-n-p)
(setq visible-bell -1)Unique Buffer Names
;;/////////////////////////////////////////////////////////////////
; Better Defaults
;;///////////////////////////////////////////////////////////////////
;;; Buffers
;; Unique Buffer Name
(setq uniquify-buffer-name-style 'post-forward-angle-brackets)
;; Recursive Minibuffer Edit
(setq enable-recursive-minibuffe t)Parenthesis Visual Cues
(kb::defg :states 'normal
:keybindings 'paredit-mode-map
"M-j" '(paredit-forward :which-key "par-forward")
"M-k" '(paredit-backward :which-key "par-backward")
"M-J" '(paredit-forward-down :which-key "par-down")
"M-K" '(paredit-backward-up :which-key "par-up") )
paredit: Highlight matching parenthesis
(use-package paredit
:guix (:name emacs-paredit)
:general
<<paredit-kb>>
:hook
(emacs-lisp-mode . paredit-mode)
(eval-expression-minibuffer-setup-hook . paredit-mode)
(lisp-mode . paredit-mode)
(lisp-interaction-mode . paredit-mode)
(scheme-mode . paredit-mode))paren: highlight Matching Parenthesis
(use-package paren
:hook
((prog-mode . show-paren-mode)))Commands
Undo/Redo
(use-package undo-fu
:guix (:name emacs-undo-fu)
:demand t
:general
(kb::defg
:states 'normal
"u" '(undo-fu-only-undo :which-key "Undo")
"C-r" '(undo-fu-only-redo :which-key "Redo"))
:config
(setq evil-want-fine-undo t
evil-undo-system 'undo-fu)
;;; Fix Undo granularity down to a single charachter
;;; instead of eating up an entire chnage.
;;; see https://emacs.stackexchange.com/questions/47801/change-how-much-undo-tree-undo-undoes
(when (timerp undo-auto-current-boundary-timer)
(cancel-timer undo-auto-current-boundary-timer))
(fset 'undo-auto--undoable-change
(lambda () (add-to-list 'undo-auto--undoably-changed-buffers (current-buffer))))
(fset 'undo-auto-amalgamate 'ignore)
(advice-add 'undo-auto--last-boundary-amalgamating-number :override #'ignore))Code Folding/Unfolding
Package hs-minor-mode is paired with evil-mode to provide code folding
capabilities.
(kb::defg :states '(normal visual)
:keymaps 'override
"za" '(evil-toggle-fold :which-key "toggle block fold")
"zz" '(bd::toggle-fold-all :which-key "toggle buffer fold"))
Package: hideshow
(defun bd::toggle-fold-all ()
"Toggles code fold all blocks in the current buffer"
(interactive)
(if (boundp 'hs-all-hide-state)
(setq-local hs-all-hide-state (not hs-all-hide-state))
(setq-local hs-all-hide-state t))
(if hs-all-hide-state (hs-hide-all) (hs-show-all)))
(use-package hideshow
:defer t
:commands
(bd::fold-all evil-toggle-fold)
:hook
(prog-mode . hs-minor-mode)
(org-mode . hs-minor-mode)
:general
<<hideshow-kb>> )Mini-buffer single Keystroke quit
Exiting out of mini-buffer, messages, compilation log…, with one keystroke. Key Bindings
;;; Quit with q in evil keymap
(kb::defg
:states '(normal motion visual)
"q" '(bd::keyboard-quit :which-key "Quit Window"))
;; Quit with q in minibuffer
(kb::defg
:states '(normal motion visual)
:keymaps
'( minibuffer-local
minibuffer-local-completion
minibuffer-local-ns
minibuffer-must-match
minibuffer-local-shell-command
minibuffer-local-isearch)
"q" '(bd::keyboard-quit :which-key "quit"))
;;; Quit with C-q in evil-emacs keymap
(kb::defg
:states 'emacs
:keymaps 'override
"C-q"
'(evil-exit-emacs-state :which-key "Exit Emacs State" ))
;;; Quit with q help-mode map
(kb::defg
:states '(normal motion visual emacs)
:keymaps 'help-mode
"q" '(quit-window :which-key "quit help"))Functions
(defun bd::keyboard-quit ( )
(interactive)
(ignore-errors
(cond
(;; is in minibuffer?
(and
delete-selection-mode
transient-mark-mode
mark-active)
(setq deactivate-mark t)
(abort-recursive-edit))
(;; is completeion buffer on?
(get-buffer "*Completions*")
(delete-windows-on "*Completions*")
(abort-recursive-edit))
(t ;; default
(quit-window)))))Copy to Clipboard current buffer's file path
(defun clipboard::copy-current-buffer-dir ()
"Copy the current directory into the kill ring."
(interactive)
(kill-new default-directory))
(defun clipboard::copy-current-file-name ()
"Copy the current directory into the kill ring."
(interactive)
(kill-new (file-truename buffer-file-name)))Shortcut to Restore Evil Normal Mode
(kb::defg
"C-0" '(evil-normal-state :which-key "evil normal state"))Enable External Image Converter
Use external image converter to view image file formats that don't have built-in support.
(use-package image
:config
(setq image-use-external-converter t))Support Colored Shell Command Output Buffer
Add support for rendering buffers containing colored terminal output
using xterm-color.
(use-package xterm-color
:guix (:name emacs-xterm-color)
:demand t)(defun bd::colorize ( )
(interactive)
(font-lock-mode -1)
(make-local-variable 'font-lock-function)
(setq font-lock-function (lambda (_) nil))
(make-local-variable 'comint-output-filter-functions)
(remove-hook 'comint-output-filter-functions 'ansi-color-process-output t)
(add-hook 'comint-preoutput-filter-functions 'xterm-color-filter nil t))Multi-Line Edit-Replace
iedit: helps edit multiple occurrences of a text in a region
(use-package iedit
:guix (:name emacs-iedit))
(kb::defg :states 'normal
"C-;" '(iedit-mode :which-key "iedit-mode"))File Types
Markdown Support
(use-package markdown-mode
:guix (:name emacs-markdown-mode )
:mode ("\\.md\\'" . gfm-mode)
:init (setq markdown-command "multimarkdown")
:bind (:map markdown-mode-map
("C-c C-e" . markdown-do)))
(use-package markdown-preview-mode
:guix (:name emacs-markdown-preview-mode)
:defer t)Epilogue
(provide 'better-defaults)
;;; better-defaults.el ends hereOrg
org-mode is everything.
(use-package org-config
:defer 3
:mode
("\\.org" . org-mode))Key Bindings
;;global key bindings
(kb::def
:states 'normal
;;edit
"e" '(:ignore t :which-key "edit special")
"es" '(org-edit-special :which-key "start edit special")
"ew" '(org-edit-src-exit :which-key "end edit special")
"eq" '(org-edit-src-abort :which-key "quit edit special"))
;; lead key bindings
(kb::defg
:states 'normal
:kemaps 'org-mpde-map
"TAB" '(org::cycle :which-key "toggle-subtree"))
Package: org-config
Preface
;;; org-mode.el --- -*- lexical-binding: t coding: utf-8 -*-
;; Author: Aron GileSetup
(use-package org
:hook
(org-mode . org-indent-mode)
:init
;; org init
<<org-init>>
;; keybindings
:general
<<org-kb>>
:custom
(enable-local-variables :all)
(enable-local-eval t)
;; never leave empty lines in collapsed view
(org-cycle-separator-lines 0)
:config
;; org-mode 29+ has a bug that is triggered when
;; when interacting with source block with keyboard(evil-mode).
;; while org-src-tab-acts-natively is set to none nil value.
;; see:
;;https://github.com/syl20bnr/spacemacs/issues/13465
(setq org-src-tab-acts-natively nil)
;; disable source block evaluation confirmation prompt
(setq org-confirm-babel-evaluate nil)
<<org-config>> )
Buffer Wide org-mode Properties: the idea is to have a pair of functions for
reading and writing key-value properties within an org-mode buffer.
<em>See</em>
<a href="https://emacs.stackexchange.com/questions/21459/programmatically-read-and-set-buffer-wide-org-mode-property"> Programmatically read and set buffer-wide org-mode property
</a>
(defun org-props-key-re (key)
"Construct a regular expression matching key and an optional plus and eating the spaces behind.
Test for existence of the plus: (match-beginning 1)"
(concat "^" (regexp-quote key) "\\(\\+\\)?[[:space:]]+"))
(defun org-global-props (&optional buffer)
"Get the plists of global org properties of current buffer."
(with-current-buffer (or buffer (current-buffer))
(org-element-map (org-element-parse-buffer) 'keyword (lambda (el) (when (string-equal (org-element-property :key el) "PROPERTY") (nth 1 el))))))
(defun org-global-prop-value (key)
"Get global org property KEY of current buffer.
Adding up values for one key is supported."
(require 'cl-lib)
(let ((key-re (org-global-props-key-re key))
(props (org-global-props))
ret)
(cl-loop with val for prop in props
when (string-match key-re (setq val (plist-get prop :value))) do
(setq
val (substring val (match-end 0))
ret (if (match-beginning 1)
(concat ret " " val)
val)))
ret))
(defun org-global-prop-set (key value)
"Set the value of the first occurence of
,#+PROPERTY: KEY
add it at the beginning of file if there is none."
(save-excursion
(let* ((key-re (org-global-props-key-re key))
(prop (cl-find-if (lambda (prop)
(string-match key-re (plist-get prop :value)))
(org-global-props))))
(if prop
(progn
(assert (null (match-beginning 1)) "First occurence of key %s is followed by +." key)
(goto-char (plist-get prop :begin))
(kill-region (point) (plist-get prop :end)))
(goto-char 1))
(insert "#+PROPERTY: " key " " value "\n"))))To set and read global buffer property do something like this:
(org-global-prop-set "KEY" "VALUE")
Similarly, to read a value
(let ((pvalue (org-global-prop-value "KEY"))) (message "KEY=%s" pvalue))
Enable <s Expansion: load org-tempo package to enable keyboard shortcuts
like <s which can be <tab> expanded to org-mode source code block.
(require 'org-tempo)
Enable Org Speed Key bindings: org-keys package adds speed keys when cursor is at
the beginning of a heading
(use-package org-keys
:demand t
:after org)Enable Local variables:
(setq enable-local-variables :all)
Enable Consistent Task Status Tracking: clocked tasks are essential for integrating
various time tracking and task management tools with org-mode. This block simplifies
task status tracking by baking into Emacs org-mode the following TODO keywords:
(setq org-todo-keywords '((sequence "TODO(t)" "WORKING(w)" "BLOCKED(b)" "|" "DONE(d)")))
(setq org-todo-keyword-faces
'(("TODO" . (:weight bold))
("WORKING" . (:foreground "orange" :weight bold))
("BLOCKED" . (:foreground "red" :weight bold))
("DONE" . (:foreground "forest green" :weight bold))))
The idea is to have consistent TODO keywords across configuration documents.
That way it will be easier for other documents,such as workflows,
to hook into and respond to TODO state change in Emacs.org.
Available TODO state change hooks are:
;; ;; NEUTRAL
;; (defvar org-enter-neutral-state nil
;; "Enter neutral state")
;; (defvar org-exit-neutral-state nil
;; "Exit neutral state")
;; ;;TODO
;; (defvar org-enter-todo-state nil
;; "Enter TODO state")
;; (defvar org-exit-todo-state nil
;; "Exit TODO state")
;; ;;WORKING
;; (defvar org-enter-working-state nil
;; "Enter WORKING state")
;; (defvar org-exit-working-state nil
;; "Exit WORKING state")
;; ;; BLOCKED
;; (defvar org-enter-blocked-state nil
;; "Enetr BLOCKED state")
;; (defvar org-exit-blocked-state nil
;; "Exit BLOCKED state")
;; ;; DONE
;; (defvar org-enter-done-state nil
;; "Enetr DONE state")
;; (defvar org-exit-done-state nil
;; "Exit DONE state")
The hooks are integrated with org-mode via org::todo-state-changed function
;; (add-hook 'org-after-todo-state-change-hook 'org::todo-state-changed)
;;(add-hook 'org-after-todo-state-changed-hook 'tasks::todo-state-changed)
Implementation of org::todo-state-changed is as follows:
;; (defvar org::todo-state-tr-table (make-hash-table :test 'equal))
;; (puthash "TODO"
;; '(org-enter-todo-state . org-exit-todo-state)
;; org::todo-state-tr-table)
;; (puthash "WORKING"
;; '(org-enter-working-state . org-exit-working-state)
;; org::todo-state-tr-table)
;; (defun org::todo-state-changed ( )
;; "TODO state transition handler hook"
;; (require 'log)
;; (cond
;; (;; same state transition?
;; (equal org-state org-last-state)
;; (log::info (format "%s %s" org-last-state org-state)))
;; (;; enter neutral state
;; (not (stringp org-state))
;; (log::info (format "%s <N>" org-state))
;; (run-hooks org-enter-neutral-state)
;; (let ((exit-state (cdr (gethash org-last-state org::todo-state-tr-table))))
;; (apply 'run-hooks `(,exit-state))))
;; (;; exit neutral state
;; (not (stringp org-last-state))
;; (log::info (format "<N> %s" org-state))
;; (run-hooks org-exit-neutral-state)
;; (apply 'run-hooks (car (gethash org-state org::todo-state-tr-table))))
;; (t
;; (let ((entry-hook (car (gethash org-state org::todo-state-tr-table)))
;; (exit-hook (cdr (gethash org-last-state org::todo-state-tr-table))))
;; (log::info "[%s] [%s]" org-last-state org-state)
;; (apply 'run-hooks `( ,entry-hook))
;; (apply 'run-hooks `( ,exit-hook))))))
Disable Source block Auto Indent: extra two spaces are added whenever
return key, a key bound to evil-ret, is pressed within org-mode
source block
<em>See</em>
<a href="https://emacs.stackexchange.com/questions/18877/how-to-indent-without-the-two-extra-spaces-at-the-beginning-of-code-blocks-in-or">
How to indent without the two extra spaces at the beginning of code blocks
</a>:
(setq org-src-fontify-natively t
org-src-window-setup 'current-window
org-src-strip-leading-and-trailing-blank-lines t
org-src-preserve-indentation t)Enable Transclusion: reference external org-file content as if it was part of the current document.
(use-package org-transclusion
:guix ( :name emacs-org-transclusion )
:disabled
:hook
(org-mode . org-transclusion-mode))Disable Text Repositioning when Cycling Visibility: Org erratically moves the current cursor position while cycling visibility of nodes<em>See</em> <a href="https://emacs.stackexchange.com/questions/31276/how-to-prevent-org-mode-from-repositioning-text-in-the-window-when-cycling-visib"> How to prevent org-mode from repositioning text in a window when cycling visibility </a>:
(setq org-cycle-emulate-tab t
org-cycle-open-archived-trees nil
org-cycle-hide-drawer-startup nil
org-cycle-separator-lines 0
org-cycle-include-plain-lists t
org-cycle-max-level 10)
(remove-hook 'org-cycle-hook #'org-optimize-window-after-visibility-change)Improve Org Cycle
(defun org::cycle-header-only ()
"Toggle visibility of the current header only.
If not on a header, do nothing (allowing other bindings to take over)."
(interactive)
(when (org-at-heading-p)
(save-excursion
(let* ((beg (line-end-position))
(end (save-excursion (org-end-of-subtree t) (point)))
(folded (save-excursion
(goto-char beg)
;; Check if the very next character is invisible
(org-invisible-p))))
(if folded
(progn
(org-show-entry)
(org-show-children))
;; Use 'outline to ensure we are hiding based on org-mode's spec
(org-flag-region beg end t 'outline))))))
(defun org::cycle ()
(interactive)
(if (org-at-heading-p)
(org::cycle-header-only)
(org-cycle)))Customize Appearance: Custom org-mode appearance settings
;;UX
(setq
org-display-custom-times t
header-line-format " "
org-startup-folded t ;;when opening org-mode keep all nodes folded
org-hide-block-startup t
org-return-follows-link t ;;Pressing [Return] while on a link follows it
org-hide-emphasis-markers t ;; if enabled bold and italic words will NOT show markup
org-src-window-setup 'other-frame
org-src-fontify-natively t
org-support-shift-select t
org-confirm-babel-evaluate nil
;; org-agenda-skip-deadline-prewarning-if-scheduled t
org-catch-invisible-edits t
org-list-allow-alphabetical t
;;scale of latex preview fragement
;; org-format-latex-options (plist-put org-format-latex-options :scale 1.6))
)
;;font locks
(font-lock-add-keywords
'org-mode
'(("^ *\\([-]\\) "
(0 (prog1 () (compose-region (match-beginning 1) (match-end 1) "•"))))))
There are several packages that provide well encapsulated and useful
org-mode specific appearance configurations. org-bullets for instance
can be used to customize the header level symbol(* by default)
(use-package org-bullets
:guix (:name emacs-org-bullets )
:defer t
:hook ( org-mode . org-bullets-mode )
:config
(setq org-bullets-bullet-list '("●" "○" "●" "○" "●" "◉" "○" "◆")))Enable Subtree Expand/Hide: The function below enable expanding or hiding a subtree without having to cycle through the entire document.
(defun org::toggle-subtree ()
(interactive)
(org-back-to-heading)
(if ;; already folded?
(outline-invisible-p (point-at-eol))
(org-show-subtree)
(org-fold-hide-subtree)))Epilogue
(provide 'org-config)
;;; org-config.el ends hereCoding
This sections introduces configuration for setting up baseline
programming environment. It adds support for code navigation
and syntax parsing using tree-sitter. It also enables Language Server Protocol (LSP) client
using eglot.
(use-package emacs-code
:defer t
:commands eglot
:general
<<emacs-code-kb>>
:hook
((prog-mode . flyspell-prog-mode)
(prog-mode . eglot-ensure)))Keybindings
Code Navigation & Syntax Highlighting
(kb::def :states 'normal :keymaps 'prog-mode-map
"c" '(:ignore t :which-key "Coding")
"cx" '(eglot :which-key "prepare")
"cc" '(eglot-format-buffer :which-key "format buffer")
"cd" '(elgot-find-declaration :which-key "declaration")
"ct" '(elgot-find-typeDefinition :which-key "type definition")
"ci" '(elgot-find-implementation :which-key "implementation")
"ch" '(eldoc :which-key "doc"))Code snippets and template text blocks,
(kb:def :states 'normal
"y" '(:ignore t :which-key "Yasnippets")
"ye" '(yas-expand :which-key "expand snippet")
"yv" '(yas-visit-snippet-file :which-key "Visit snippet")
"yd" '(yas-describe-tables :which-key "describe snippet")
"yn" '(yas-new-snippet :which-key "new snippet"))
(kb::defg
:states 'normal
:keymaps 'company-active-map
"C-<tab>" '(company-yasnippet))
(kb::defg
:states 'normal
"M-/" '(yas-expand :which-key "Expand Snippet"))
Package: emacs-code
Preface
;;; emcs-code.el --- -*- lexical-binding: t coding: utf-8 -*-
;; Author: Aron GileSetup
tree-sitter: Static Syntax Analysis
This section adds static syntax analysis using Tree-sitter.
(use-package treesit
:demand t
:init
(setq treesit-language-source-alist '()))
Elglot is used as the default Emacs client for the Language Server
Protocol (LSP).
(use-package eglot
:demand t)Yasnippets is text snippets management and completion
framework for Emacs
;;////////////////////////////////////////////////////////////////
;; Make load path static and available to the bytecompiler
;; see: https://github.com/jwiegley/use-package#extending-the-load-path
;;////////////////////////////////////////////////////////////////
(defconst yasnippet-dir
(fs::dir-ensure (expand-file-name "snippets" emacs:preferences-dir))
"File path for storing yas snippets")
;;; snippet helpers
(defun yasnippet-user-name ()
"Returns user name"
(format
"%s%s"
(user-full-name)
(if user-mail-address
(format "(%s)" user-mail-address)
"")))
(use-package yasnippet
:guix (:name emacs-yasnippet-snippets)
:diminish yas-minor-mode
:load-path yasnippet-dir
:general
<<yasnippet>>
:config
(cl-pushnew yasnippet-dir yas-snippet-dirs )
(require 'yasnippet-snippets)
(yas-minor-mode 1))Functions
emacs-code::enable-org-babel-lang
A simpler alternative to modifying org-bable-do-languages list when registering
new Org/Babel languages. To configure org-babel to support a new babel language
use (code::enable-babel-lang 'newlang). This of course assumes the necessary
package implementing
(defun emacs-code::enable-org-babel-lang (lang-symbol)
"Enable Org babel language LANG-SYMBOL if not already enabled.
`code::enable-org-bable-lang', like`org-babel-do-load-languages', can
be used to enable language support for `org-babel'.
However, unlike`org-babel-do-load-languages' it does not process all
the languages in `org-babel-load-languages' alist. It will only
process the language specifed by LANG-SYMBOL argument."
(let ((assoc-test (lambda (first second)
(string= (symbol-name first) (symbol-name second))))
(lang (symbol-name lang-symbol))
(pair (assoc lang-symbol org-babel-load-languages)))
(when (null pair)
(cl-pushnew
(cons lang-symbol t) org-babel-load-languages)
(setf
pair
(assoc lang-symbol org-babel-load-languages assoc-test))
;; add lang and activate?
(autoload
(intern (concat "org-babel-execute:" lang))
(format "ob-%s.el" lang)))))
emacs-code::enable-treesit-lang
Function code::enable-treesit-lang can be used to add new tree-sitter based
language parser. The actual tree-sitter parser library has to be in the
execution environment library path, otherwise the function will fail.
(defun emacs-code::enable-treesit-parser (lang url &optional branch)
"Register tree-sitter parser for language `LANG`.
The repository location is specified using `URL`and optional parameter `BRANCH`
can be used to set a release tag, commit, or branch name to use (default is master)."
(add-to-list
'treesit-language-source-alist
(cons lang (list url (or branch "master")))))
emacs-code::enable-lsp-server
(defun emacs-code::enable-lsp-server (program &rest modes)
"Register LSP server PROGRAM for MODES in `eglot-server-programs'.
SERVER can be a command list such as '(\"clangd\").
"
(let ((cmd (if (stringp program) (list program) program)))
(dolist (mode modes)
(setf (alist-get mode eglot-server-programs)
cmd))))Epilogue
(provide 'emacs-code)
;;; emacs-code.el ends here.
Coding: Lisp/Scheme
This section primarily targets lisp/scheme
code development tools.
(use-package lisping
:demand t
:config
(show-paren-mode)
<<geiser-kb>>)Customizations
Add path to local GNU Guile/Scheme and Guix source code repositories
for better code completion and navigation.
(defcustom gnu-guile-src-directory (getenv "GUILE_SRC")
"Directory containing the source code for GNU Guile."
:type 'string)
(defcustom gnu-guix-src-directory (getenv "GUIX_SRC")
"Directory containing the source code for GNU/Guix."
:type 'string)
Add Guile/Scheme LSP server,
(defcustom enable-guile-eglot-integration t
"Enable Guile LSP server integration with Eglot"
:type 'string)Keybindings
guile/scheme keybindings
(kb::def :states 'normal
:keybindings 'geiser-mode-map
"h" '(:ignore t :which-key "help")
"hG" '(geiser-doc-symbol-at-point :which-key "Guile help"))
package: lisping
Preface
;;; lisping.el --- -*- lexical-binding: t; coding: utf-8 -*-
;; Author: Aron GileSetup
Guile/Scheme
REPL Based Development
geiser
(use-package geiser
:guix (:name emacs-geiser)
:demand t)geiser-guile:
(use-package geiser-guile
:guix ( :name emacs-geiser-guile )
:hook
(scheme-mode . geiser-mode)
:general
<<geiser-kb>>
:custom
(geiser-repl-history-filename
(expand-file-name
"guile-repl-history"
(fs::dir-ensure "${HOME}/.config/distroconfig/geiser")))
(geiser-default-implementation 'guile)
(geiser-active-implementations '(guile))
:config
(require 'geiser-guile)
(add-to-list 'geiser-guile-load-path gnu-guile-src-directory)
(add-to-list 'geiser-guile-load-path gnu-guix-src-directory))ac-Geiser
(use-package ac-geiser
:guix (:name emacs-ac-geiser)
:hook
(geiser-mode . ac-geiser-setup)
(geiser-repl-mode . ac-geiser-setup)
:config
(eval-after-load "auto-complete"
'(add-to-list 'ac-modes 'geiser-repl-mode)))guile/scheme
(defun lisping::run-geiser( )
"Run Guile interpreter"
(interactive)
(require 'geiser-repl)
(require 'ac-geiser)
(require 'geiser-mode)
(save-window-excursion
(geiser-mode t)
(let ((guile-proc (get-process "Guile REPL")))
(unless (process-live-p guile-proc)
(geiser 'guile)))))
(use-package scheme
:guix (:name guile@3.0.9)
:mode
("\\.scm" . scheme-mode)
:hook
(scheme-mode . lisping::run-geiser)
:custom
(scheme-mit-dialect nil)
:commands
lisping::run-geiser)
Eglot LSP Server
(when enable-guile-eglot-integration
(use-package guile-lsp-server
:guix (:name guile-lsp-server)
:ensure nil
:init
(require 'emacs-code)
(emacs-code::enable-lsp-server
"guile-lsp-server"
'scheme-mode)))Epilogue
(provide 'lisping)
;;; lisping.el ends here.Project Management
Emacs projectile is the core package used to manage projects.
(use-package pm
:defer 3
:commands
(projectile-ag
projectile-compile-project
projectile-find-file
projectile-find-tag
projectile-test-project
projectile-invalidate-cache
projectile-kill-buffers
projectile-multi-occur
projectile-replace
projectile-replace-regexp
projectile-run-async-shell-command-in-root
projectile-run-shell-command-in-root
projectile-switch-project
projectile-switch-to-buffer))Customizations
Project Directories: Projectile can be configured to search for projects in predefined directories:
(setq projectile-project-search-path
'( "~/Documents/dev/blogging"
"~/Documents/dev/forks"
"~/Documents/dev/projects"
"~/Documents/research/"
"~/Documents/KB/"))
Project Root Directory: Several features in this configuration depend on
availability of the root project directory. Examples include the work-flow system.
The setting bellow forces projectile to prompt whenever project root directory is changed.
(setq projectile-require-project-root 'prompt)Project File Caching: enable file caching to improved performance:
(let ((projectile-config-dir (fs::dir-ensure "${HOME}/.cache/distro-config/pm" )))
(setq projectile-enable-caching t
projectile-cache-file
(expand-file-name "cache" projectile-config-dir)
projectile-known-projects-file
(expand-file-name "bookmarks" projectile-config-dir)))
Project Completion System: use helm as the preferred project completion system.
(setq projectile-completion-system 'helm)Detecting Project Change: the following hook is meant to be used by other packages outside of this configuration.
; add my custom hook
(defvar pm-current-changed-hook nil
"Hook called whenever the current project is changed" )Key Bindings
(kb::def :states 'normal
:keymaps 'override
"p" '(:ignore t :which-key "project")
"pp" '(projectile-switch-project :which-key "switch" )
"pc" '(projectile-compile-project :which-key "compile")
"pt" '(projectile-test-project :which-key "test" )
"p<" '(projectile-run-shell-command-in-root :which-key "sudo shell")
"p>" '(projectile-run-async-shell-command-in-root :which-key "async shell")
"p/" '(projectile-replace-regexp :which-key "replace")
"pe" '(projectile-edit-dir-locals :which-key "edit dir locals")
"pF" '(projectile-find-file-dwim :which-key "find dwim")
"pf" '(projectile-find-file :which-key "find file")
"p#" '(projectile-find-tag :which-key "find tags" )
"pk" '(projectile-kill-buffers :which-key "kill buffers" ))
Package: pm
Preface
;;; pm.el --- -*- lexical-binding: t coding: utf-8 -*-
;; Author: Aron Gile
(add-to-list 'warning-suppress-log-types '(Projectile files))Functions
Project Information: query project information.
(defun pm::is-available ( )
"Return non-nil if inside a project; otherwise nil is returned."
(projectile-project-root))
(defun pm::root-dir ( )
"Return the current project's root directory."
(require 'projectile)
(require 'helm-projectile)
(let* ((root-dir (projectile-project-root))
(found-root-dir-p (and root-dir (file-directory-p root-dir))))
(unless found-root-dir-p
(helm-projectile-switch-project)
(setq root-dir (projectile-project-root)))
root-dir))Setup
Helm integration using helm-projectile
(use-package helm-projectile
:guix (:name emacs-helm-projectile))
projectile package,
(use-package projectile
:guix (:name emacs-projectile )
:demand t
:config
<<projectile-config>>
(projectile-mode +1)
(helm-projectile-on))Epilogue
(provide 'pm)
;;; pm.el ends here.Workflow Management
Introduction
A workflow is a way to modularize configuration management.
Instead of placing all configuration settings in a single org
file it can be organized across multiple org files called workflows.
Each workflow is a Litdoc document that can also specify a
list of other documents as its dependency via the #+workflows:
keyword.
To create a new workflow, example.org, which depends on
leanr.orgnotes.orgfeeds.org
Add the following line at the beginning of a workflow document,
#+workflow: learm write task feed
Whenever the example.org workflow is loaded into Emacs, the associated
dependency workflows will be resolved and loaded automatically.
Customization
Add a set of default workflows
(defcustom workflow:default-workflows
(list "sc" "tasks" "feeds" "notes" "learn" "refdb")
"List of default workflows to load when loading Emacs")Key Bindings
(kb::def :states 'normal
:keymaps 'override
"p" '(:ignore t :which-key "project")
"pw" '(:ignore t :which-key "workflows")
"pws" '(workflow::select :which-key "select" )
"pwl" '(workflow::build-all :which-key "load" ))
Package: workflow
The main workflow package is defined here,
(use-package workflow
:demand t
:commands (workflow::build-all workflow::select)
:config
<<workflow-custom>>
:general
<<workflow-kb>>)Preface
;;; workflow.el --- -*- lexical-binding: t coding: utf-8 -*-
;; Author: Aron GileSetup
Read Org Document Property(Support): functions for reading org-mode properties The functions are adopted from John Kitchen 2012 <a href="https://kitchingroup.cheme.cmu.edu/blog/2013/05/05/Getting-keyword-options-in-org-files/">blog post</a>
(defun workflow::get-org-keyword (value)
"Get the value `VALUE of an org-document keyword.
for example, for a document with the following entry,
,#+KEY: VALUE value in a file
`workflow::get-org-keyword returns VALUE"
(let ((case-fold-search t)
(re (format "^#\\+%s:[ \t]+\\([^\t\n]+\\)" value))
(start nil)
(end nil)
(value nil))
(with-current-buffer (current-buffer)
(when (and (string= major-mode "org-mode")
(save-excursion (goto-char (point-min))
(re-search-forward re nil t)))
(setq start (match-beginning 1))
(setq end (match-end 1))
(setq value (split-string (buffer-substring-no-properties start end) " "))))))Write Org document property:
(defun workflow::set-org-keyword (keyword value)
"Set the value from a link like #+KEYWORD: value in a file"
(let ((case-fold-search t)
(re (format "^#\\+%s:[ \t]+\\([^\t\n]+\\)" keyword))
(str-value (cond (;; multiple values?
(listp value)
(mapconcat
'identity
(delete-dups value)
" "))
(;; single value?
(stringp value) value)
(;; not nil?
(not (and value (null value)))
(format "%s" value))
(t;;
(user-error
"unexpected keyword value format")))))
(with-current-buffer (current-buffer)
(unless (string= major-mode "org-mode")
(user-error
"[ERROR] document is not org-mode: failed to read configured workflows."))
(save-excursion
(if (progn (goto-char (point-min)) (re-search-forward re nil 'NOERROR))
(replace-match (format "#+%s: %s" keyword str-value))
(with-temp-buffer
(insert (concat "#+" keyword ": " str-value))
(clipboard-kill-region (point-min) (point-max))
(message "copied workflows to clippboard: %s" str-value)))))))Workflow Management Functions: Functions in this block provide the actual workflow management.
Load a workflow
(defun workflow::load (workflow)
"Load WORKFLOW"
(let (;; default library
(workflow-lib (expand-file-name
(format "%s/share/elisp/site-lisp/%s.el" workflow workflow)
(substitute-in-file-name "${LITDOC_CACHEDIR}")))
(default-directory (expand-file-name
(format "%s/guix/%s/%s/share/emacs/site-lisp"
workflow
workflow
workflow)
(substitute-in-file-name "${LITDOC_CACHEDIR}"))))
(when (file-directory-p default-directory)
(normal-top-level-add-subdirs-to-load-path))
(cond
(;; is default workflow lib?
(file-exists-p workflow-lib)
(load workflow-lib 'NOERROR))
(t ;; no library found
(log::info "[%s]: no workflow file to load: %s\n"
workflow
workflow-lib)))))building a workflow
(defun workflow::build (workflow)
"Build workflow `WORKFLOW asynchronously."
(require 'xterm-color)
(let* ((auto-save-default nil)
(buffer (get-buffer-create "*Workflow Build Output*")))
(make-process
:name "build-workflows"
:buffer buffer
:command
`("guix" "ld" "build" ,workflow)
:sentinel
`(lambda (proc signal)
(when (memq (process-status proc) '(exit signal))
(with-current-buffer ,buffer
(xterm-color-colorize-buffer)))))))Load all workflows in the current document
(defun workflow::build-all( )
"Load all configured WORKFLOWS for the current literate document"
(interactive)
(require 'log)
(require 'dash)
(let ((workflows (workflow::get-org-keyword "workflows")))
(setq workflows (-difference workflow:default-workflows workflows))
(cl-loop
for workflow in workflows
do
(progn
(workflow::build workflow)
(workflow::load workflow)))))UI for selecting a workflow
(defvar configured-workflows (workflow::get-org-keyword "workflows"))
(defun workflow::filter ( workflow-dir)
(--map (file-name-sans-extension (file-name-nondirectory it))
(--filter
(and (not (string-match ".git" it))
(string-suffix-p ".org" it))
(f-files workflow-dir nil 'RECURSIVE))))
(defun workflow::select ( )
"Prompt and select a workflow."
(interactive)
(require 'ivy)
(require 'f)
(require 'map)
(let ((selected-worflow)
(available-workflows)
(workflows))
(cl-loop for workflow-dir in (s-split ":" (substitute-in-file-name "${LITDOC_PATH}"))
do (cl-loop
for file in (workflow::filter workflow-dir)
do (cl-pushnew file available-workflows)))
(setq workflows (-difference available-workflows configured-workflows))
(setq workflows (cl-loop for wf in workflows
do (when (not (null wf)))
collect wf))
(if (<= (length workflows) 0)
(message "no workflows found")
(setq selected-workflow (ivy-read "select workflow: " workflows))
(when selected-workflow
(progn
(workflow::build selected-workflow)
(workflow::load selected-workflow)
(setq configured-workflows (cl-pushnew selected-workflow configured-workflows))
;;(workflow::set-org-keyword "workflows" configured-workflows)
)))))
(defun workflow::save-config ( )
"Save configured workflows into the current org-file"
(interactive)
(when (boundp configured-workflows)
(workflow::set-org-keyword "workflows" configured-workflows)))Epilogue
(provide 'workflow)
;;; workflow.el ends here