simfish:

Emacs Configuration

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.

cover image

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