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.
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 ))
Completion & Search
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)))