This section contains configuration settings for installing
and configuring Emacs 30 built with GTK(V3+) and native
compilation support. The configuration is primarily written
to work with litdoc,
a GNU/Guix and Emacs based literate programming tool.
Emacs Configuration
Installing
Emacs with pure GTK(v3+)
and native-comp
support can be installed from
the official GNU/Guix repository.
(use-package emacs-next
:guix (:name emacs-next :profile default)
:disabled)
User Information
Instead of pushing personal information into a public repo, use environment variables to pass information to Emacs at runtime.
(setq user-full-name (getenv "USER_FULL_NAME"))
(setq user-mail-address (getenv "USER_EMAIL_ADDRESS"))
Startup
When loading emacs via litdoc emacs
, a user defined startup
shell script is used to check some Guix and Emacs environment
variables required to make this configuration
work with litdoc:
#! /usr/bin/env bash
source "${LITDOC_DIR}/libs/sh/log.sh"
# LitDoc specific elisp libraries:
EMACS_SITELISP_DIR="${HOME}/.cache/litdoc/emacs/share/elisp/site-lisp"
# add library paths to Emacs's load path
export EMACSLOADPATH="${EMACS_SITELISP_DIR}:${EMACSLOADPATH}"
[ -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
}
We also introduce shell command for launching emacs in daemon mode:
- TODO Daemon mode is broken
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]"
fi
Last, standard Emacs startup files: early-init.el
and init.el
:
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 28.0.50 or up"
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 on error
;; (setq toggle-debug-on-error t)
(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
;;; Temporary BUG: Disable
(require 'cl-lib)
(require 'log)
(require 'fs)
(require 'startup-profiler)
(setq debug-on-error t)
;;; 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))
Package Management
Introduce a use-package keyword, :guix
,
which enables use-package
syntax to be used to managing GNU/Guix
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 installs and configures some
elisp
libraries I commonly use.
fs.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>
(require 'profiler)
(defvar startup-profiler:started nil
"When set to none-nil value, assume Enacs profiler is running")
;; Startup Profiler
(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
I use esup for identifying startup issues.
(use-package esup
:guix ( :using channel :name emacs-esup)
:commands esup)
And, the built-in edebug package for debugging elisp
code.
(use-package edebug
:commands gix-edebug/on)
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 Gile
Key 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 )
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)
; Make horizontal movement cross lines
(setq-default evil-cross-lines t)
(evil-mode 1)
(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 here
Completion and Search
Configuration settings useful for command discovery, text completion, and search.
(use-package completion
:demand t
:config
(require 'kb))
Preface
;;; completion.el --- -*- lexical-binding: t coding: utf-8 -*-
;; Author: Aron Gile
Key Bindings
helm
key bindings
(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-files :which-key "find")
"fc" '(find-file :which-key "consult")
;;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")
"bc" '(consult-buffer :which-key "consult")
"bd" '(kill-this-buffer :which-key "delete this buffer")
)
helm-swoop
(kb::defg
:states '(normal motion)
"/" '(helm-swoop :which-key "Helm Swoop"))
evil-search
(kb::defg
:states '(normal motion)
"C-/" '(evil-search-forward :which-key "search forward")
"C-|" '(evil-search-backward :which-key "search backward"))
yasnippet
key bindings
(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"))
embark
(kb::defg
"C-." '(embark-act :which-key "act"))
Customizations
helm
is a completion framework for for searching
interactive 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>>
:config
;;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))))
helm-swoop
is a useful command for searching
text in the current buffer.
(use-package helm-swoop
:guix (:name emacs-helm-swoop)
:demand t
:commands helm-swoop
:general
<<helm-swoop>> )
helm-descbinds
(use-package helm-descbinds
:guix (:name emacs-helm-descbinds )
:hook
(helm-mode . helm-descbinds-mode))
embark
provides contextual action completion. It can be used to suggest
a list of actions to carry out on object the cursor is on
Never really gotten to using this package. Might need to re-visit its
docs and see what I can do with it.
(use-package marginalia
:guix (:name emacs-marginalia)
:config
(marginalia-mode))
(use-package embark
:guix (:name emacs-embark)
:custom
(embark-prompter 'embark-completing-read-prompter)
:general
<<embark>>
:init
;; Optionally replace the key help with a completing-read interface
(setq prefix-help-command #'embark-prefix-help-command)
:config
;; Hide the mode line of the Embark live/completions buffers
(add-to-list 'display-buffer-alist
'("\\`\\*Embark Collect \\(Live\\|Completions\\)\\*"
nil
(window-parameters (mode-line-format . none)))))
(use-package vertico
:guix (:name emacs-vertico)
:init
(vertico-mode +1))
(use-package orderless
:guix (:name emacs-orderless)
:init
(setq completion-styles '(orderless)
completion-category-defaults nil
read-file-name-completion-ignore-case t
completion-ignore-case t
completion-category-overrides '((file (styles partial-completion)))))
;; Persist history over Emacs restarts. Vertico sorts by history position.
(use-package savehist
:init
(savehist-mode))
(use-package consult
:guix (:name emacs-consult))
(use-package embark-consult
:after (embark consult)
:demand t
:hook
(embark-collect-mode . consult-preview-at-point-mode))
ivy
(use-package ivy
:guix (:name emacs-ivy ))
company
auto completion framework
(use-package company
:guix (:name emacs-company)
: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)))
yasnippets
: text snippets management 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))
Epilogue
(provide 'completion)
;;; completion.el ends here
Better Default
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 Gile
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)))
Disable showing warning buffer at startup
(setq warning-minimum-level :emergency)
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)
Enable Spell Checking
(use-package ispell-program
:guix (:name ispell)
:disabled)
(use-package flyspell-correct
:guix (:name emacs-flyspell-correct )
: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
(kb::def
:states 'normal
:keymaps 'override
"S" '(:ignore t :which-key "spell check")
"Sc" '(flyspell-correct-previous :which-key "corrections"))
:config
;;///////////////////////////////////////////////////////////////////////
;; Backend //////////////////////////////////////////////////////////////
;;//////////////////////////////////////////////////////////////////////
(require 'flyspell)
(require 'ispell)
(require 'flyspell-correct-helm))
Fine Grained Undo/Redo Approach
(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))
Indenting, Tabbing & White Space
(use-package aggressive-indent
:guix (:name emacs-aggressive-indent)
:diminish indent-mode nil
:hook
((before-save . delete-trailing-whitespace)
(prog-mode . aggressive-indent-mode))
:config
;;(add-to-list 'aggressive-indent-excluded-modes 'html-mode)))
(setq-default indent-tabs-mode nil
tab-width 4
indicate-empty-lines nil))
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)
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))
New Frame Undecorated by Default
(add-to-list 'default-frame-alist '(undecorated . t) )
Window Management
(defconst bd:doremi-dir
(expand-file-name
"applications/emacs/share/elisp/site-lisp/doremi"
(getenv "DISTROCONFIG_DIR"))
"Root directory for doremi library")
(use-package doremi
:load-path bd: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))
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)
Prompt uses Y/N instead of Yes/No
(fset 'yes-or-no-p 'y-or-n-p)
(setq visible-bell -1)
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))
Single Keystroke Quit
Exiting out of special buffers–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)))))
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")
;; "\\" '(evilmi-jump-items :which-key "evil")
;; "." '(evil-ex-repeat :which-key "evil")
;; "/" '(helm-swoop :which-key "evil")
;; "a" '(evil-previous-line-first-non-blank :which-key "evil")
;; "s" '(evil-next-line-first-non-blank :which-key "evil")
)
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)
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))
Epilogue
(provide 'better-defaults)
;;; better-defaults.el ends here
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
:demand t)
Preface
;;; themes.el --- -*- lexical-binding: t coding: utf-8 -*-
;; Author: Aron Gile
Customizations
(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
;; 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
;;themes
(use-package dimmer
:guix (:name emacs-dimmer )
: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)
:disabled)
Epilogue
(provide 'themes)
;;; themes.el ends here
Org
org-mode
is everything.
(use-package org-config
:demand t)
Preface
;;; org-mode.el --- -*- lexical-binding: t coding: utf-8 -*-
;; Author: Aron Gile
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 "org-cycle"))
Customizations
Enable org-mode
:
(use-package org
:mode
("\\.org" . org-mode)
: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
: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)
<<org-config>> )
Better org-babel
Support: Simpler alternative to org-bable-do-languages
when registering new Org/Babel languages.
(defun org::enable-babel-lang (lang-symbol)
"Enable language LANG-SYMBOL if not already enabled.
`emacs:org-babel/enable-lang', like`org-babel-do-load-languages' and 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)))
))
Configure org-babel
to support shell
, emacs-lisp
, scheme
and gnu-plot
out of the box.
;; disable source block evaluation confirmation prompt
(setq org-confirm-babel-evaluate nil)
;; add shell, emacs-lisp, and gnuplot
(org::enable-babel-lang 'shell)
(org::enable-babel-lang 'emacs-lisp)
(org::enable-babel-lang 'gnuplot)
(org::enable-babel-lang 'scheme)
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 a 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
:config
(setq org-use-speed-commands t
org-speed-commands-user '(("S" . org-store-link))))
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" "WORKING" "|" "DONE")))
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:
;; hooks
(defvar org-todo-working-hook nil
"A todo state is marked as working.")
(defvar org-todo-paused-hook nil
"A todo state is now in a paused state")
(defvar org-todo-done-hook nil
"A todo state is now marked as DONE.")
The hooks are integrated with org-mode via org-todo/on-state-changed
function
(add-hook 'org-after-todo-state-change-hook 'org-todo/on-state-changed)
Implementation of org-todo/on-state-changed
is as follows:
(defun org-todo/on-state-changed ( )
"TODO state transition handler hook"
(require 'log)
(cond
(;; TODO --> WORKING
(and (string= org-last-state "TODO")
(string= org-state "WORKING"))
;;(org-pomodoro-start)
(log::info "TODO -> WORKSING")
;;(org-clock-in)
(run-hooks 'org-todo-working-hook))
(;;WORKING --> TODO
(and (string= org-last-state "WORKING")
(string= org-state "TODO"))
(log::info "WORKING -> TODO <=> PAUSED")
;;(org-clock-out)
(run-hooks 'org-todo-paused-hook))
(;;WORKING --> DONE
(and (string= org-last-state "WORKING")
(string= org-state "DONE"))
(log::info "WORKING -> DONE")
;;(org-clock-out)
(run-hooks 'org-todo-done-hook))
(;; Ignore other cases
t
(log::info "<%s> --> <%s>" org-last-state org-state)
t)))
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-include-plain-lists t)
(remove-hook 'org-cycle-hook #'org-optimize-window-after-visibility-change)
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 '("●" "○" "●" "○" "●" "◉" "○" "◆")))
Epilogue
(provide 'org-config)
;;; org-config.el ends here
Markdown-Mode
(use-package markdown-mode
:guix (:name emacs-markdown-mode))
Lisp/Scheme
This section primarily targets lisp/scheme
code development tools.
(use-package lisping
:demand t
:config
(show-paren-mode)
<<iedit-kb>>
<<geiser-kb>>
<<paredit-kb>>)
Preface
;;; lisping.el --- -*- lexical-binding: t coding: utf-8 -*-
;; Author: Aron Gile
Key bindings
guile/scheme
Getting Help
(kb::def :states 'normal
:keybindings 'geiser-mode-map
"h" '(:ignore t :which-key "help")
"hG" '(geiser-doc-symbol-at-point :which-key "Guile help"))
Structural Navigation
(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") )
Multi edit-replace
(kb::defg :states 'normal
"C-;" '(iedit-mode :which-key "iedit-mode"))
Code Folding
(kb::defg :states '(normal visual)
:keymaps 'override
"za" '(evil-toggle-fold :which-key "toggle block fold")
"zz" '(lisping::toggle-all-fold :which-key "toggle buffer fold"))
Customizations
(defcustom gnu-guile-src-directory "~/Documents/dev/forks/org.gnu/guile"
"Directory containing the source code for GNU Guile."
:type 'string)
(defcustom gnu-guix-src-directory "~/Documents/dev/forks/org.gnu/guile"
"Directory containing the source code for GNU/Guix."
:type 'string)
Package: geiser
(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)))))
;; geiser
(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/distro-config/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)))
Package: scheme
(use-package scheme
:guix (:name guile)
:mode
("\\.scm" . scheme-mode)
:hook
(scheme-mode . lisping::run-geiser)
:custom
(scheme-mit-dialect nil)
:commands
lisping::run-geiser)
Package: paredit
, paren
, and iedit
Enable utility packages for better scheme code editing.
paredit
: Highlight matching parenthesis
(use-package paredit
:guix (:name emacs-paredit)
: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)))
iedit
: helps edit occurrences of a text in a region
(use-package iedit
:guix (:name emacs-iedit))
hs-minor-mode
: when paired with evil-mode
this
minor-mode can provide code folding capabilities.
(defun lisping::toggle-all-fold ()
"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
:demand t
:hook
(prog-mode . hs-minor-mode)
(org-mode . hs-minor-mode)
:general
<<hideshow-kb>> )
Epilogue
(provide 'lisping)
;;; lisping.el ends here.
Project Management
Emacs projectile is used to manage projects.
(use-package pm
:demand t)
Preface
;;; pm.el --- -*- lexical-binding: t coding: utf-8 -*-
;; Author: Aron Gile
(add-to-list 'warning-suppress-log-types '(Projectile files))
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" ))
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" )
Commands
projectile
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)
Functions
Project Information: functions useful for 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))
Packages
helm-projectile
(use-package helm-projectile
:guix (:name emacs-helm-projectile))
projectile
(use-package projectile
:guix (:name emacs-projectile )
:demand t
:commands
<<projectile-commands>>
:config
<<projectile-config>>
(projectile-mode +1)
(helm-projectile-on))
Epilogue
(provide 'pm)
;;; pm.el ends here.
Workflow Management
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.
For example, I have an org-mode
planner document. The document
depends on a spaced-repetition based learning document learn.org
, a note
taking document write.org
, task management workflow task.org
, and
a RSS feed management document feed.org
. So in my planner document I add these
workflows as a dependency for my planner.
#+use-doc: learm write task feed
And whenever the planner document is loaded into Emacs, the associated workflows will be resolved and loaded automatically.
(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 Gile
Customization
Add a set of default workflows
(defcustom workflow:default-workflows (list "sc" "tasks" "feeds")
"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" ))
Functions
Reading Org Document Properties(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) " "))))))
(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 for the current literate program"
(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 'ansi-color)
(let* (
(litdoc_sh (expand-file-name "litdoc" (substitute-in-file-name "${LITDOC_DIR}")))
(buffer (get-buffer-create "*Workflows Build Output*"))
(proc (start-file-process
"load-workflows" buffer
shell-file-name litdoc_sh "site" "build" workflow))
(proc-changed `(lambda (process signal)
(let ((status (process-status process)))
(when (memq status '(exit signal))
(cond
((string= (substring signal 0 -1) "finished")
(with-current-buffer ,buffer
(ansi-color-apply-on-region ,(point-min) ,(point-max)))
(workflow::load ,workflow))
(t
((message "[ERROR]: failed to load workflow: %s" ,workflow)
(message "litdoc site load exited with: %s" signal)))))))))
(set-process-sentinel proc proc-changed)))
Load all workflows in the current document,sitelisp-dir
(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
(unless (workflow::build workflow)
(log::info "- loading workflow: %s ... [FAILED]" workflow)))))
UI for selecting a workflow
(defun workflow::filter ( workflow-dir)
(--map (file-name-sans-extension (file-name-nondirectory it))
(--filter
(when (and (not (string-match ".git" it))
(string-suffix-p ".org" it)
(string-match-p "/workflows/" 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)
(configured-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 configured-workflows (workflow::get-org-keyword "workflows"))
(setq workflows (-difference available-workflows configured-workflows))
(setq workflows (cl-loop for wf in workflows
do (when (not (null wf)))
collect wf))
(when (<= (length workflows) 0)
(user-error "no workflows found"))
;; select workflow
(setq selected-workflow (ivy-read "select workflow: " workflows))
;; append selected workflow to existing keyword
(when selected-workflow
(progn
(workflow::build selected-workflow)
(setq configured-workflows (cl-pushnew selected-workflow configured-workflows))
(workflow::set-org-keyword "workflows" configured-workflows)))))
Epilogue
(provide 'workflow)
;;; workflow.el ends here