simfish:

Emacs Configuration

This section contains configuration settings for installing and configuring Emacs 28 built with GTK(V3+) and native compilation support. The settings were tested only in a Linux environment.

cover image

Install

The official GNU/Guix repository currently doesn’t contain Emacs v28+ built with pure GTK(v3+) and native-comp support. As a workaround, the emacs-pgtk-native-comp module provided by the flatwhatson channel is used.

The channel specification is See Specifying Additional Channels ,

(cons* (channel
        (name 'flat)
        (url "https://github.com/flatwhatson/guix-channel.git")
        (introduction
         (make-channel-introduction
          "33f86a4b48205c0dc19d7c036c85393f0766f806"
          (openpgp-fingerprint  "736A C00E 1254 378B A982  7AF6 9DBE 8265 81B6 4490"))))
       %default-channels)

By design Gix does not automatically apply system-wide changes. To make this channel available in the current system run This will add the channel description into ~/.config/guix/channels.scm.


gixd configure

Once the flatwhatson channel is setup, Gix will be able to find the desired Emacs version:


(use-package emacs-pgtk-native-comp
  :disabled
  :guix (:using channel :name emacs-pgtk-native-comp))

Startup

Environment Variables: variables that should be passed to Emacs during startup go here:


# GIX_ORGMODE
# Use this variable to use a version of org other
# than the one that shippes with this Emacs distribution
# export GIX_ORG_DIR=/path/to/your/custom/org-mode

Emacs Launch Script: the startup shell block below sets up some Guix and Emacs environment variables required to make literate configuration with Gix work

#! /usr/bin/env bash

source  "${GIX_DIR}/stdlib/share/sh/gix-log.sh"

# Gix specific elisp libraries:
GIX_STDLIB_ELISP_DIR="${GIX_DIR}/stdlib/share/elisp/site-lisp"
GIX_GUIX_ELISP_DIR="${GIX_DIR}/guix/share/elisp/site-lisp"

# Emacs specific elisp libraries:
GIX_EMACS_LIB_DIR="${GIX_COMPDIR}/applications/emacs/share/elisp/site-lisp"
GIX_EMACS_AUTOLIB_DIR="${HOME}/.cache/gix/cache/components/emacs/share/elisp/site-lisp"

# add library paths to Emacs's load path
export EMACSLOADPATH="${GIX_STDLIB_ELISP_DIR}:${GIX_GUIX_ELISP_DIR}:${GIX_EMACS_AUTOLIB_DIR}:${GIX_EMACS_LIB_DIR}:${EMACSLOADPATH}"

# check if the startup directory is configured properly
export XDG_CONFIG_HOME="${GIX_EMACS_LIB_DIR}"
[[ -d "${XDG_CONFIG_HOME}" ]] || {
    gix_error "failed to resolve Emacs startup directory: ${XGD_CONFIG_HOME}"
    exit 1
}
[[ -d "${HOME}/.emacs.d" ]] && {

    gix_error "default emacs directory found at: ${HOME}/.emacs.d"
    gix_error "move this directory to allow gix to configure Emacs to use your configuration files."
    exit 1
}

nargs=$#
index=0
args_rest=""
while [[ $index -lt ${nargs} ]]
do
    case "$1" in
        --server | -s )
            opt_daemon=true
            gix_attr "daemon-mode" "on"
            ;;
        *)
            args_rest="${args_rest} $1 "
            ;;
    esac
    shift
    index=$((index+1))
done
set -- "${args_rest}"

gix_hr
gix_info "Emacs"
gix_attr "startup directory" "${XDG_CONFIG_HOME}"
gix_attr "startup args" "$@"

if [[ "${opt_daemon}" = "true" ]]; then
    gix_info "starting Emacs using a daemon ..."
    if ! emacsclient -e 0 >& /dev/null; then
        emacs --daemon $@ &
        [[  $? -eq  0 ]] || {
            gix_error "failed start Emacs daemon "
            exit 1
        }
    fi
    emacsclient -c $@ || {
        gix_error "failed to start Emacs as a daemon"
        exit 1
    }
    gix_info "starting emacs using a daemon ... [OK]"
else
    gix_info "starting Emacs  ..."
    emacs $@ || {
        gix_error "failed to start emacs"
    }
    gix_info "starting Emacs ... [OK]"
fi

Package Management: similar to the shell environment setup, the elisp code below bootstraps Gix’s package management mechanism. In particular, it enables an extended use-package keyword, :guix, which enable using use-package syntax to specify Guix packages.


;; load use package
(require 'use-package)
(setq     use-package-always-defer t)
(require 'use-package-guix)

(use-package use-package
  :guix (:using channel :name emacs-use-package)
  :init
  (setq use-packae-always-defer t)
  (require 'use-package-guix))

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 those files by moving them elsewhere.

User Data Directory: Content of this directory is suitable for storing user data. In particular, data that can be managed using version-control. The user preference directory which is configured using gix:preferences-dir variable:


(defconst gix:preferences-dir
  (gix:fs/dir-ensure "${HOME}/.cache/gix/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 gix:cache-dir variable:


(defconst gix:cache-dir
  (gix:fs/dir-ensure "${HOME}/.cache/gix/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" gix: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" gix:cache-dir))
  :init
  (recentf-mode)
  :config
  ;; create recent files directory
  ;; if it isn't  already created
  (require 'gix-fs)
  (let ((recentf-dir (file-name-directory recentf-save-file)))
    (gix: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-" gix:cache-dir)
   auto-save-file-name-transforms   `((".*" ,gix: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  `( ( "."  . ,gix:cache-dir)  ( ,tramp-file-name-regexp nil) )))

Emacs Shell History Files


(use-package em-hist
  :custom
  (eshell-history-file-name
   (gix:fs/dir-ensure
    (expand-file-name  "eshell/history" gix:cache-dir))))

Transient Files


(use-package transient
  :custom
  (transient-history-file
   (expand-file-name "transient.el" (gix:fs/dir-ensure gix: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 'gix-fs)
     (setq nsm-settings-dir (expand-file-name "nsm" gix:preferences-dir))
     (gix: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 :profile emacs)
  :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 gix-edebug/on ()
  "Toggle on instrumentalisation for the function under `defun'."
  (interactive)
  (eval-defun 'edebugit))

(defun gix-edebug/off ()
  "Toggle off instrumentalisation for the function under `defun'."
  (interactive)
  (eval-defun nil))

Libraries

This section installs and configures common elisp libraries.

async.el is a module for doing asynchronous processing in Emacs.


(use-package async
  :guix (:using channel :name emacs-async))

s.el is a string manipulation library. see: https://github.com/magnars/s.el


(use-package s
  :guix ( :using channel :name emacs-s))

dash is a modern list API for Emacs. see: https://github.com/magnars/dash.el


(use-package dash
  :guix ( :using channel :name emacs-dash))

f provides an Emacs library for working with files and directories. see: https://github.com/rejeep/f.el


(use-package f
  :guix ( :using channel :name emacs-f))

flycheck provides modern syntax checking facilities. see: www.flycheck.org.


(use-package flycheck
  :guix (:using channel :name emacs-flycheck))

persist This package provides variables which persist across sessions. see: Persist


(use-package persist
  :guix (:using channel :name emacs-persist)
  :config
  (setq persist--directory-location
        (gix:fs/dir-ensure (expand-file-name  "persist" gix:cache-dir))))

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.

Key Bindings

The default lead key can be configured using gix-kb-lead-key:

(gix-kb-lead-key  "SPC"  "Lead key for commands")

Key bindings can be defined using either gix-kb, which is leader-key aware, or using gix-kb, which defines global key bindings.


(general-create-definer gix-kb :prefix gix-kb-lead-key)
(general-create-definer gix-gkb)

Customizations

package: gix-keybinding(this package)


(use-package gix-keybinding
  :demand t
  :init
  (require 'hydra)
  :custom
  <<gix-keybinding-custom>>)

package: general

(use-package general
  :demand t
  :guix (:using channel :name emacs-general )
  :config
  <<general-kb>>)

package: evil


(use-package evil
  :demand t
  :guix   (:using channel :name emacs-evil :profile emacs )
  :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
  :after evil
  :guix (:using channel :name emacs-evil-collection))

package: which-key


(use-package which-key
  :defer 2
  :guix (:using channel :name emacs-which-key)
  :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
  :demand t
  :guix (:using channel :name emacs-hydra ))

Configuration settings useful for command discovery, text completion, and search.

Key Bindings

helm key bindings


(gix-kb  :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


(gix-gkb
 :states '(normal motion)
 "/"  '(helm-swoop :which-key "Helm Swoop"))

evil-search


(gix-gkb
 :states '(normal motion)
 "C-/"   '(evil-search-forward   :which-key "search forward")
 "C-|"   '(evil-search-backward  :which-key "search backward"))

yasnippet key bindings


(gix-kb  :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"))

(gix-gkb
 :states   'normal
 :keymaps  'company-active-map
 "C-<tab>" '(company-yasnippet))

(gix-gkb
 :states 'normal
 "M-/" '(yas-expand :which-key "Expand Snippet"))

embark


(gix-gkb
 "C-." '(embark-act :which-key "act"))

Customizations

gix-completion: this package

(use-package gix-completion
  :demand t
  :config
  (require 'gix-keybinding))

helm is a completion framework for for searching interactive Emacs commands See Emacs-Helm project

(use-package helm
  :guix   (:using channel :name emacs-helm :profile emacs )
  :general
  ;; enable navigating helm file menu
  ;; using left and right arrow keys
  ;; to move up and down directory tree.
  (gix-gkb :keymaps   'helm-find-files
	   "<left>"   '(helm-find-files-up-one-level)
	   "<right>"  '(helm-execute-persistent-action))
  ;; helm keybindings
  <<gix-helm-kb>>
  :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 (:using channel :name emacs-helm-swoop :profile emacs )
  :commands helm-swoop
  :general
  <<gix-helm-swoop-kb>>)

helm-descbinds

(use-package helm-descbinds
  :guix (:using channel :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 (:using channel :name emacs-marginalia)
  :config
  (marginalia-mode))

(use-package embark
  :guix (:using channel :name emacs-embark)
  :custom
  (embark-prompter 'embark-completing-read-prompter)
  :general
  <<embark-kb>>

  :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 (:using channel :name emacs-vertico)
  :init
  (vertico-mode +1))

(use-package orderless
  :guix (:using channel :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 (:using channel :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 (:using channel :name emacs-ivy ))

company auto completion framework

(use-package company
  :guix (:using channel :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 gix-yasnippet-dir
  (gix:fs/dir-ensure (expand-file-name "snippets" gix: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 (:using channel :name emacs-yasnippet-snippets :profile emacs)
  :diminish  yas-minor-mode
  :load-path gix-yasnippet-dir
  :general
  <<gix-yasnippet>>
  :config
  (cl-pushnew gix-yasnippet-dir yas-snippet-dirs )
  (require 'yasnippet-snippets)
  (yas-minor-mode 1))

References

https://www.gnu.org/software/emacs/manual/html_node/elisp/Syntax-Class-Table.html https://emacs.stackexchange.com/questions/35557/how-can-i-define-a-yasnippet-for-if-and-if

Better Default

The gix-better-defaults package, specified in this section, provides a set of common sense default settings and commands useful when running Emacs.


(use-package gix-better-defaults
  :demand t)

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)

Fine Grained Undo/Redo Approach

(use-package undo-fu
  :guix (:using channel :name emacs-undo-fu :profile emacs )
  :demand t
  :general
  (dc-gkb
   :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))

Text Scaling

(gix-gkb
 :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")
 )

Window Management

(defconst gix-doremi-dir
  (expand-file-name
   "applications/emacs/share/elisp/site-lisp/doremi"
   (getenv "GIX_COMPDIR"))
  "Root directory for doremi library")


(use-package doremi
  :load-path gix-doremi-dir
  :general
  (gix-gkb
   :states '(normal motion visual)
   "C-<up>"     '(text-scale-increase   :wich-key  "++ Font Size")
   "C-<down>"   '(text-scale-decrease   :which-key "-- Font Size"))

  (gix-kb :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))

Indenting, Tabbing & White Space


(use-package aggressive-indent
  :guix (
	 :using channel
	 :name emacs-aggressive-indent
	 ;;:version "1.9.0"
	 ;;:source (
	 ;;:git       "https://github.com/Malabarba/aggressive-indent-mode.git"
	 ;;:commit    "70b3f0add29faff41e480e82930a231d88ee9ca7"
	 ;;:revision  "0"
	 ;;:sha256    "0rz010ypy1rg8v6na05zs942ikzqg1l32inzpzq12g2q2rnfa3wkx1")
	 )
  :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 (:using channel :name emacs-diminish))

New Frame Undecorated by Default

(add-to-list 'default-frame-alist '(undecorated . t) )

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
(gix-gkb
 :states '(normal motion visual)
 "q"  '(gix-better-defaults/keyboard-quit  :which-key "Quit Window"))

;; Quit with q in minibuffer
(gix-gkb
 :states '(normal motion visual)
 :keymaps
 '( minibuffer-local
    minibuffer-local-completion
    minibuffer-local-ns
    minibuffer-must-match
    minibuffer-local-shell-command
    minibuffer-local-isearch)
 "q" '(gix-better-defaults/keyboard-quit :which-key "quit"))

  ;;; Quit with C-q in evil-emacs keymap
(gix-gkb
 :states  'emacs
 :keymaps 'override
 "C-q"
 '(evil-exit-emacs-state :which-key "Exit Emacs State" ))

  ;;; Quit with q help-mode map
(gix-gkb
 :states  '(normal motion visual emacs)
 :keymaps 'help-mode
 "q" '(quit-window :which-key "quit help"))

Functions

(defun gix-better-defaults/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)))))

Smooth Scrolling

(use-package good-scroll
  :defer 3
  :guix( ;;guix specification
        :using git
        :profile emacs
        :version "0.3.0"
        :source (;; git repository
		 :git      "https://github.com/io12/good-scroll.el.git"
		 :commit   "beac144c37227d23e5ceefb8ce1f45aa484ca5c9"
		 :revision  "0"
		 :sha256    "1yv07vpgqla7nk0imn7dlzsv392jvhk3w63frvhcmijq5ciafc45"))

  :custom
  ;; https://www.masteringemacs.org/article/improving-performance-emacs-display-engine
  ;; https://www.reddit.com/r/emacs/comments/8sw3r0/finally_scrolling_over_large_images_with_pixel/
  ;; https://www.reddit.com/r/emacs/comments/9rwb4h/why_does_fast_scrolling_freeze_the_screen/
  ;; https://emacs.stackexchange.com/questions/10354/smooth-mouse-scroll-for-inline-images
  ;; https://emacs.stackexchange.com/questions/28736/emacs-pointcursor-movement-lag
  (redisplay-dont-pause            t) ;; Fully redraw the display before it processes queued input events.
  (next-screen-context-lines       2) ;; Number of lines of continuity to retain when scrolling by full screens
  (scroll-conservatively       10000) ;; only 'jump' when moving this far off the screen
  (scroll-step                     1) ;; Keyboard scroll one line at a time
  (fast-but-imprecise-scrolling    t) ;; No (less) lag while scrolling lots.
  (auto-window-vscroll           nil) ;; Cursor move faster
  :config
  (good-scroll-mode 1))

Unique Buffer Names

;;/////////////////////////////////////////////////////////////////
					; Better Defaults
;;///////////////////////////////////////////////////////////////////

;;; Buffers
;; Unique Buffer Name
(setq uniquify-buffer-name-style 'post-forward-angle-brackets)
;; Recursive Minibuffer Edit
(setq enable-recursive-minibuffe t)

Visual Cue Instead of Bell

Prefer visual feedback instead of ringing mode-line.

(use-package mode-line-bell
  :defer 3
  :guix (
	 :using git
	 :profile emacs
	 :version "0.2"
	 :source (
		  :git      "https://github.com/purcell/mode-line-bell.git"
		  :commit   "26ac7d97abdeb762ceaeab6b892f3ed7e3412494"
		  :revision  "0"
		  :sha256    "0qbd4y10510q6r21zzxnr16ylrm7qh1qc7ll5wxab0yi03jaas3s"))
  :config
  (mode-line-bell-mode))

Enable Spell Checking

(use-package ispell-program
  :guix (:using channel :name ispell)
  :disabled)

(use-package flyspell-correct
  :guix (:using channel :name emacs-flyspell-correct )
  :diminish flyspell-mode "abc_✓"
  :custom
  ;; personal dictionary file name
  (ispell-personal-dictionary
   (let ((dic-dir (expand-file-name "i18n" gix:preferences-dir)))
     (gix: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
  (gix-kb
   :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))

Copy to Clipboard current buffer’s file path

(defun gix:clipboard/copy-current-buffer-dir ()
  "Copy the current directory into the kill ring."
  (interactive)
  (kill-new default-directory))

(defun gix:clipboard/copy-current-file-name ()
  "Copy the current directory into the kill ring."
  (interactive)
  (kill-new (file-truename buffer-file-name)))

Themes

This section specifies the gix-theme package, which configures Emacs UI feel and style

(use-package gix-theme
  :demand t)

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
  :demand t
  :guix (:using channel :name emacs-doom-themes )
  :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
  :defer 3
  :guix (:using channel :name emacs-doom-modeline :profile emacs)
  ;;: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

;;gix-theme
(use-package dimmer
  :guix (:using channel :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
  :defer 3
  :guix (:using channel :name emacs-all-the-icons)
  :config
  (font-lock-mode))

Package: font-hack is typeface designed for working with source code.

(use-package font-hack
  :disabled
  :guix (:using channel :name font-hack))

Terminal Emulator

This section sets terminal emulator within Emacs buffer See Improving Ansi-Term for an excellent configuration walk-through

Key bindings


(gix-kb :states 'normal
	";" '(ansi-term :which-key "open terminal"))

Functions


(defun gix: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 gix:ansi-term/enable-URL-navigation ()
  "Enable clicking URL"
  (goto-address-mode))

(defun gix:ansi-term/enable-C-y-paste ()
  "Enable C-y copy paste in terminal"
  (define-key term-raw-map "\C-y" 'my-term-paste))


(defun gix:ansi-term/paste (&optional string)
  (interactive)
  (process-send-string
   (get-buffer-process (current-buffer))
   (if string string (current-kill 0))))

(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 gix: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 . gix:ansi-term/use-utf8)
   (term-mode . gix:ansi-term/enable-URL-navigation)
   (term-mode . gix:ansi-term/enable-C-y-paste))

  :general
  <<ansi-term-kb>> )

Org-Mode

Org-Mode is everything.

Key Bindings


;;global key bindings
(gix-kb
 :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
(gix-gkb
 :states  'normal
 :kemaps  'org-mpde-map
 "TAB" '(org-cycle :which-key "org-cycle"))

Customizations

Package gix-org: this package


(use-package gix-org
  :demand t)

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-config>> )

Better org-babel Support: Simpler alternative to org-bable-do-languages when registering new Org/Babel languages.


(defun gix:org-babel/lang-enable (lang-symbol)
  "Enable language LANG-SYMBOL if not already enabled.
  `gix: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
(gix:org-babel/lang-enable 'shell)
(gix:org-babel/lang-enable 'emacs-lisp)
(gix:org-babel/lang-enable 'gnuplot)
(gix:org-babel/lang-enable '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. See Programmatically read and set buffer-wide org-mode property


(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 'gix-log)
  (cond

   (;; TODO --> WORKING
    (and (string= org-last-state   "TODO")
         (string= org-state        "WORKING"))
    ;;(org-pomodoro-start)
    (gix:info "TODO -> WORKSING")
    ;;(org-clock-in)
    (run-hooks 'org-todo-working-hook))

   (;;WORKING --> TODO
    (and  (string= org-last-state "WORKING")
          (string= org-state "TODO"))
    (gix: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"))
    (gix:info "WORKING -> DONE")
    ;;(org-clock-out)
    (run-hooks 'org-todo-done-hook))
   (;; Ignore other cases
    t
    (gix: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 See How to indent without the two extra spaces at the beginning of code blocks :


(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
      org-src-tab-acts-natively t)

Enable Transclusion: reference external org-file content as if it was part of the current document.


(use-package org-transclusion
  :guix ( :using channel :name emacs-org-transclusion )
  :hook
  (org-mode . org-transclusion-mode))

Disable Text Repositioning when Cycling Visibility: Org erratically moves the current cursor position while cycling visibility of nodes See How to prevent org-mode from repositioning text in a window when cycling visibility :


(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
 org-src-tab-acts-natively                        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
  :defer t
  :guix  ( :using channel :name emacs-org-bullets  )
  :hook  ( org-mode . org-bullets-mode )
  :config
  (setq org-bullets-bullet-list '("●" "○" "●" "○" "●" "◉" "○" "◆")))

Programming Tools

This section provides programming environment building block utilities.

The configuration primarily targets lisp/scheme code. However, it does introduce bare minimum LSP (Language Server Protocol) configuration and some generic key bindings useful for when working with most programming languages.

Key bindings

guile/scheme Getting Help

(gix-kb :states 'normal
	:keybindings 'geiser-mode-map
	"h"    '(:ignore t                   :which-key "help")
	"hG"   '(geiser-doc-symbol-at-point  :which-key "Guile help"))

Structural Navigation

(gix-gkb :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

(gix-gkb :states 'normal
	 "C-;" '(iedit-mode :which-key "iedit-mode"))

Code Folding

(gix-gkb :states  '(normal visual)
         :keymaps 'override
         "za"     '(evil-toggle-fold        :which-key  "toggle block fold")
         "zz"     '(gix:code/toggle-all-fold :which-key  "toggle buffer fold"))

Customizations

gix-code: this package


(use-package gix-code
  :demand t
  :config
  (show-paren-mode)
  <<gix-iedit-kb>>
  <<gix-geiser-kb>>
  <<gix-paredit-kb>>)

Install major mode for Guile/Scheme


(defun  gix:scheme/run-geiser( )
  "Run Guile interpreter"
  (interactive)
  (require 'geiser-repl)
  (require 'ac-geiser)
  (require 'geiser-mode)
  (save-window-excursion
    (geiser-mode t)
    (let ((guile-proc (get-process "Guile REPL")))
      (unless (process-live-p guile-proc)
        (geiser 'guile)))))

(use-package scheme

  :guix (:using channel :name guile)

  :mode
  ("\\.scm"   . scheme-mode)

  :hook
  (scheme-mode . gix:scheme/run-geiser)

  :custom
  (scheme-mit-dialect nil)
  :commands
  gix:scheme/run-geiser)

;; geiser
(use-package geiser-guile
  :guix (:using channel  :name emacs-geiser-guile )
  :hook
  (scheme-mode  . geiser-mode)
  :general
  <<gix-geiser-kb>>
  :custom
  (geiser-repl-history-filename
   (expand-file-name
    "guile-repl-history"
    (gix:fs/dir-ensure "${HOME}/.config/gix/geiser/guile")))
  (geiser-default-implementation 'guile)
  (geiser-active-implementations '(guile))
  :config
  (require 'geiser-guile)
  (add-to-list
   'geiser-guile-load-path
   "${GIX_SOURCE_DIR}/guile.gnu.org")
  (add-to-list
   'geiser-guile-load-path
   "${GIX_SOURCE_DIR}/guix.gnu.org")
  (add-to-list
   'geiser-guile-load-path
   "${GIX_SOURCE_DIR}/gwl.guix.gnu.org"))

;; ac-geiser
(use-package ac-geiser
  :guix (:using channel :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)))

Enable utility packages for better scheme code editing. paredit: Highlight matching parenthesis


(use-package paredit
  :guix ( :using channel :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 (:using channel :name emacs-iedit))

hs-minor-mode: when paired with evil-mode this minor-mode can provide code folding capabilities.


(defun gix:code/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>> )

lsp: Language Server Protocol


(defun gix:lsp ( )
  "Start lsp-mode with `read-process-output-max configured properly"
  (interactive)
  (lsp-mode)
  (setq read-process-output-max  (* 1024 1024) )
  (lsp-headerline-breadcrumb-mode -1))

(use-package lsp-mode
  :demand t
  :guix (:using channel :name emacs-lsp-mode )
  :init
  (setq read-process-output-max  (* 1024 1024) )
  ;; set prefix for lsp-command-keymap (few alternatives - "C-l", "C-c l")
  (setq lsp-keymap-prefix "C-c l")

  :hook (;; replace XXX-mode with concrete major-mode(e. g. python-mode)
         ;;  (XXX-mode . lsp)
         ;; if you want which-key integration
         (lsp-mode . lsp-enable-which-key-integration))
  ;;:custom
  ;;(lsp-client-packages  (list ))
  :commands lsp
  :config
  (lsp-headerline-breadcrumb-mode -1)  )


(use-package lsp-ui
  :demand t
  :guix (:using channel :name emacs-lsp-ui)
  :commands lsp-ui-mode
  :config
  (setq lsp-ui-doc-show-with-cursor nil)
  (setq lsp-ui-doc-show-with-mouse  nil)
  (setq lsp-headerline-breadcrumb-enable nil)
  (setq lsp-signature-auto-activate nil)
  (setq lsp-signature-render-documentation nil)
  (setq lsp-ui-sideline-enable nil)
  (setq lsp-modeline-diagnostics-enable t)

  (gix-kb :states 'normal
	  "cd" '(lsp-ui-doc-show  :which-key "documentation")))


;; if you are helm user
(use-package helm-lsp
  :demand t
  :guix (:using channel :name emacs-helm-lsp)
  :commands helm-lsp-workspace-symbol)

;; optionally if you want to use debugger
(use-package dap-mode
  :demand t
  :guix (:using channel :name emacs-dap-mode))

;; (use-package dap-LANGUAGE) to load the dap adapter for your language

Project Management

Emacs projectile is used to manage projects.

Key Bindings

Project management key bindings:

(gix-kb  :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" ))

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 gix:project/is-available ( )
  "Return non-nil if inside a project; otherwise nil is returned."
  (projectile-project-root))

(defun gix:project/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))

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/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 (gix:fs/dir-ensure "${HOME}/.cache/gix/emacs/projectile" )))
  (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 gix-project-current-changed-hook nil
  "Hook called whenever the current project is changed" )

helm-projectile


(use-package helm-projectile
  :guix ( :using channel :name emacs-helm-projectile))

projectile


(use-package projectile
  :guix (:using channel :name emacs-projectile  )

  :commands
  <<projectile-commands>>

  :general
  <<projectile-kb>>

  :config
  (require 'gix-fs)
  (require 'helm-projectile)

  ;;strart configuration settings
  <<projectile-config>>
  ;; end configuration settings
  ;;(add-to-list 'projectile-globally-ignored-directories  dc-fs-var-dir)
  (projectile-mode +1)
  (helm-projectile-on))

gix-project: this package


(use-package gix-project
  :demand t)

Workflow Management

A workflow is a way to modularize configuration management. Instead of placing all configuration settings into a single org file it can be organized into multiple org files called workflows. Each workflow is a Gix document that can also specify a list of other workflows as its dependency via the gix-workflows keyword.

For example, I have an org-mode planner document that depends on my spaced-repition based learning environment learn.org, a note taking workflow write.org, task management workflow task.org, and a feed workflow feed.org. So in my planner document I add these workflows as a dependency for my planner.


#+gix-workflows: sc learn write task feeds

And whenever the planner document is loaded into Emacs, the associated workflows will be loaded automatically.

Key Bindings


(gix-kb  :states 'normal
         :keymaps 'override
	 "p"   '(:ignore t            :which-key "project")
	 "pw"  '(gix:workflow-select  :which-key "workflow" ))

Functions

Functions and commands in this section are used for managing gix-workflows keyword. Other functions are implemented to mainly facilitate writing, reading, and taking action on values of gix-workflows keyword.

Reading Org Document Properties(Support): functions for reading org-mode properties The functions are adopted from John Kitchen 2012 blog post


(defun jk-get-file-keyword (KEYWORD)
  "Get the value from a line like this #+KEYWORD: value in a file"
  (let ((case-fold-search  t)
        (re                (format "^#\\+%s:[ \t]+\\([^\t\n]+\\)" KEYWORD))
        (start             nil)
        (end               nil)
        (value             nil))
    (with-current-buffer (current-buffer)
      (unless (string= major-mode "org-mode")
        (user-error
         "[ERROR] document is not org-mode: failed to read  configured workflows."))
      (when (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 jk-set-file-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 "#+gix-workflows: %s" str-value))
          (progn (goto-char (point-min))
                 (insert (format "\n#+gix-workflows: %s\n"  str-value))))))))

Workflow Management Functions: Functions in this block provide the actual workflow management.


(defun gix:workflow-load (workflow)
  "Load WORKFLOW for the current literate program"
  (let (;; default library
        (default-workflow-lib (expand-file-name
                               (format  "workflows/%s/share/elisp/site-lisp/%s.el" workflow workflow)
                               (substitute-in-file-name "${GIX_COMPDIR}")))

        ;; auto generated library
        (auto-workflow-lib (expand-file-name
                            (format  "%s/share/elisp/site-lisp/%s.el" workflow workflow)
                            (substitute-in-file-name "${HOME}/.cache/gix/cache/components"))))

    (cond
     (;; is auto workflow lib?
      (file-exists-p auto-workflow-lib)
      (gix:fs/load-dir-ensure (file-name-directory auto-workflow-lib))
      (load auto-workflow-lib 'NOERROR))

     (;; is default workflow lib?
      (file-exists-p default-workflow-lib)
      (gix:fs/load-dir-ensure (file-name-directory default-workflow-lib))
      (load default-workflow-lib 'NOERROR))

     (t;; no library found
      (user-error "emacs.org: no default or auto generated lib found: \n -%s \n -%s"
                  default-workflow-lib
                  auto-workflow-lib)))))


(defun gix:workflow-load-all ( )
  "Load all configured WORKFLOWS for the current literate program"
  (require 'gix-log)
  (let ((workflows (jk-get-file-keyword "gix-workflows")))
    (cl-loop
     for workflow in workflows
     do
     (gix:info "loading workflow: %s" workflow)
     (unless (gix:workflow-load workflow)
       (gix:sub-info "loading workflow: %s ... [FAILED]" workflow)))))


(defun gix:workflow-select ( )
  "Prompt and select a workflow."
  (interactive)
  (require 'ivy)
  (require 'f)
  (require 'map)
  (let ((selected-worflow)
        (available-workflows)
        (configured-workflows)
        (workflows)
        (workflow-lib)
        (workflows-dir))

    ;; workflow
    (setq workflows-dir
          (expand-file-name "workflows" (substitute-in-file-name "${GIX_COMPDIR}")))

    ;; available-workflows in file system
    (setq available-workflows
          (--map
           (file-name-sans-extension (file-name-nondirectory it))
           (--filter
            (when (string-suffix-p ".org" it) t)
            (f-files workflows-dir nil 'RECURSIVE))))

    ;; configured available-workflows
    (setq configured-workflows
          (jk-get-file-keyword "gix-workflows"))

    (setq workflows (-difference available-workflows configured-workflows))

    ;; select workflow
    (setq selected-workflow (ivy-read "select workflow: " workflows))

    ;; append selected workflow to existing keyword
    (if selected-workflow

        (progn
          (gix:workflow-load selected-workflow)
          (setq configured-workflows (cl-pushnew selected-workflow configured-workflows))
          (jk-set-file-keyword "gix-workflows" configured-workflows))

      (user-error "failed to load workflow: %s" selected-workflow))))

Customizations

Package gix-workflow: this package


(use-package gix-workflow
  :demand t
  :hook
  (org-mode . gix:workflow-load-all)
  :general
  <<gix-workflow-kb>>
  :config
  (gix:workflow-load "task")
  (when (string= major-mode "org-mode")
    (gix:workflow-load-all)))