Forgemacs' Literate Emacs Configuration
Table of Contents
- 1. Why a literate file?
- 2. Creating early-init.el
- 3. Initial configuration
- 4. Configuration
- 4.1. Help with Emacs commands
- 4.2. Look and feel
- 4.3. Versioning
- 4.4. Navigation
- 4.4.1. Winner mode
- 4.4.2. Relative buffer names
- 4.4.3. Buffer management
- 4.4.4. Quick navigation in the mini-buffer
- 4.4.5. Dired
- 4.4.6. Dired sidebar
- 4.4.7. Media player preview
- 4.4.8. Dired preview
- 4.4.9. File Explorer
- 4.4.10. Navigate within some buffers in read only mode a.k.a
- 4.4.11. Handling bookmarks
- 4.5. Project handling
- 4.6. Org mode
- 4.6.1. Basic customization
- 4.6.2. Org configuration
- 4.6.3. Toggle appearance of elements
- 4.6.4. TODO Bullets customization
- 4.6.5. Org-babel
- 4.6.6. LaTeX previews with Org
- 4.6.7. Customizing tables
- 4.6.8. Image handling in the Org file
- 4.6.9. TODO Create notes for files in Org mode
- 4.6.10. Rich paste
- 4.6.11. Table of contents
- 4.6.12. Embed org-files
- 4.6.13. Exporters
- 4.6.14. TODO Saving fold state of Org files
- 4.7. Editing
- 4.8. Completion modules
- 4.9. Shell customization
- 4.10. Reference management
- 4.11. PDF & EPUBs
- 4.12. LaTeX
- 4.13. TODO Typst
- 4.14. Handling audio & video
- 4.15. Programming
- 4.16. Social Networking
- 4.17. Personal Knowledge Management
- 4.18. TODO Provide Emacs externally
- 4.19. After-init hooks
1. Why a literate file?
1.1. Background
I've always been a fan of code that has in-line documentation, pretty much like a good Physics or Mathematics text book. Though I did not know of this in the computing paradigm until I was introduced to Donald Knuth's Literate Programming1.
Org-mode
's implementation of this method allows for documenting each decision, providing examples and then generating not just the necessary code, but additional viewing formats such as HTML, PDF, etc. Yes, but why choose this format for an Emacs init file? Well, Emacs, is as extensible as can be2, 3, 4 and things break often5, 6, and there is some joy in ‘yak-shaving’7, 8. Over the last year or so (between 2023 and 2024), I have been using Emacs again and I have been using it continuously. While the first foray was in 2003, and I have always enjoyed it for the short duration that I used it, it never stuck, for a variety of reasons ranging from the fact that my work-flow constantly changed (based on who I was working with and what I was working on)9.
1.2. Emacs literate file
There are different ways one can use an Org mode file to create Emacs init files,
- Use
org-babel-tangle
in order to generate the necessary file from the Org file. - The other possibility is to use initialise with org-babel using
org-babel-load-file
A preliminary search on your favourite site(s) will generate a number of detailed documentation on why and how people use a literate file (along with the drawbacks of doing so) which will perhaps give you a good idea.
1.3. My Set-up
Emacs typically searches for the configuration file in a few bog standard places.
For the purposes of this particular file, I have chosen the location to be ~/.config/emacs
having used ~/.emacs.el
, ~/.emacs
, or ~/.emacs.d/init.el
in the past. It is advisable to use a symlink to ~/.emacs.d
in case your machine does not have XDG
location setup. I have a symbolic link for /.config/emacs
from /.emacs.d
.
I will move all the comments out of the source blocks and put them in the Org file instead, the aim is to ensure that the generated init file is small (not that the size of it matters), but importantly, to ensure that I can read the logic without having to necessarily look at the code. Please use C-h v
and C-h f
generously in order to help with understanding each action10.
The configuration will be divided into the following parts,
- An initial shell-script that creates the required folder structure
- A bog-standard
early-init.el
with some custom configuration - An
init.el
file with the configuration I have included the generation of a HTML file with the theme from ReadTheOrg, I have made a minor few edits to the theme for my use case. In order to have some level of automation, a function
bmp/export-tangle
has been added in 4.6.5.Currently, I am using a pre-compiled version of Emacs on Fedora Linux 40, the output of
M-x emacs-version
details are below,GNU Emacs 29.4 (build 1, x86_64-redhat-linux-gnu, GTK+ Version 3.24.42, cairo version 1.18.0) of 2024-07-16
- Website: https://forgemacs.bharathpalavalli.com
- Repository: https://codeberg.org/bmp/forgemacs
1.4. Preamble for git forges
This section generates a markdown for code forges as they do not yet interpret Org files well.
# Introduction This is my literate Emacs configuration repository, this *README* is auto-generated as code forges do not always render [Org Mode](https://orgmode.org/) files well yet. - Website: https://forgemacs.bharathpalavalli.com - Repository: https://codeberg.org/bmp/forgemacs # Instructions 1. All details are available in the [Forgemacs Org Mode](forgemacs.org) file. The file is self-contained, if necessary, you can see the [https://forgemacs.bharathpalavalli.com/](HTML) version to read in your browser. 2. Upon tangling the contents of the Org file, it generates two files, `init.el` and `early-init.el` in the right places[^1]. 3. The [Forgemacs Org Mode](forgemacs.org) file, also has a section to create the necessary folder structures in the appropriate place, they are as follows, ``` ~/.config/emacs/ ├── early-init.el ├── init.el ├── gnu_color.webp ├── snippets/ ├── abbrevs/ └── secrets/ ``` 4. This folder structure is created by running `C-c C-c` within the source block in the Org file. 5. The `Templates` folder in this repository is a backup and is part of the templates for `org-roam`[^2]. 6. I have generously borrowed from the following publicly available init files, I have added notes to other links as and when possible in the file if they aren't listed below (in no particular order): - https://github.com/daviwil/dotfiles - https://github.com/sachac/.emacs.d/ - https://github.com/renzmann/.emacs.d - https://github.com/Panadestein/emacsd - https://gitlab.com/dwt1/dotfiles/-/tree/master/.emacs.d.gnu - https://github.com/justinbarclay/.emacs.d - https://gitlab.com/jdm204/dotfiles - https://github.com/munen/emacs.d/ - https://taingram.org/init.html - https://github.com/zzamboni/dot-emacs - https://github.com/lccambiaghi/vanilla-emacs - https://github.com/frap/emacs-literate - https://github.com/hrs/dotfiles - https://github.com/daedreth/UncleDavesEmacs - https://github.com/angrybacon/dotemacs.git - https://github.com/jamiecollinson/dotfiles - https://github.com/tecosaur/emacs-config - https://justin.abrah.ms/dotfiles/emacs.html - https://protesilaos.com/emacs/dotemacs *Footnotes* [^1]: In addition to this `README.md` file. [^2]: I must add a TODO in order to clean this configuration up a bit and standardize the flow, as an example, could these templates be generated with `yasnippets` that I already use?
1.5. TODO Copy necessary files and create folder structure
This only needs to be executed the first time, though there are checks in place that make it safe. This structure creates the folder structure mentioned in the preceding section.
# Create ~/.config/emacs/ folder and copy necessary files if [ -d ~/.config/emacs ] then echo "Emacs folder exists at ~/.config/emacs/" else echo "Emacs folder does not exist in ~/.config" mkdir -p ~/.config/emacs/secrets mkdir -p ~/.config/emacs/abbrevs mkdir -p ~/.config/emacs/config/yasnippet/snippets/org-mode cp ~/source/forgemacs/gnu_color.webp ~/.config/emacs/ cp ~/source/forgemacs/.authinfo.gpg ~/.config/emacs/secrets/ fi # Create ~/.emacs.d as a symlink to ~/.config/emacs/ if [ -L ~/.emacs.d ] && [ -d ~/.emacs.d ] then echo "~/.emacs.d is already a symlink" else echo "Creating symlink" if [ -d ~/.emacs.d ] then echo "~/.emacs.d exists, removing it" rm -rf ~/.emacs.d/ fi ln -s ~/.config/emacs/ ~/.emacs.d fi
2. Creating early-init.el
I use ~/.config/emacs/
in the src
block definition to tangle the file in the correct location. This file is loaded before the package system or GUI of Emacs is loaded, and is ideally code that does not depend on any packages or on the size of the frame.
- Inhibit packages at start-up with
(setq package-enable-at-startup nil)
as I will be using Elpaca - Increase garbage collection threshold, this speeds up launch of Emacs and while I set it up to be large to speed up launch of Emacs, we can return it back to normalcy later on. The reason it is good to reset is that when the threshold is hit, Emacs clears it the garbage, but it is a blocking process, so if I have a high number, then Emacs will hold up (hang/pause/wait) until the process is completed. By resetting it to a saner number after start-up, we can ensure that such hold-ups to not happen.
file-name-handler-alist
is used an a list that is used to decide how to handle various file types. Similarly, if we use the default values, it will slow down Emacs substantively, instead we set it to nil at start-up and then restore it after start-up using the start-up hook.- I have
--with-native-compilation
in my build configuration for Emacs, but since I do depend on external packages, when I load Emacs and it spews a bunch of warnings, which often I can do nothing about or have to wait for them to go through. Settingcomp-deferred-compilation
andnative-comp-async-report-warnings-errors
tonil
, speeds up this process. - Avoid initial white screen/flash of light and start in full-screen mode.
;; WARNING: This file is generated by the org file, please do not edit this file directly (setq package-enable-at-startup nil) (setq gc-cons-threshold most-positive-fixnum gc-cons-percentage 0.6) (defvar default--file-name-handler-alist file-name-handler-alist) (setq file-name-handler-alist nil) (add-hook 'emacs-startup-hook (lambda () (setq gc-cons-threshold (* 100 1024 1024) gc-cons-percentage 0.1 file-name-handler-alist default--file-name-handler-alist))) (setq comp-deferred-compilation nil) (setq native-comp-async-report-warnings-errors nil) (setq frame-resize-pixelwise t frame-inhibit-implied-resize t frame-title-format '("%b") ring-bell-function 'ignore use-dialog-box t use-file-dialog nil use-short-answers t inhibit-splash-screen t inhibit-startup-screen t inhibit-x-resources t inhibit-startup-echo-area-message user-login-name inhibit-startup-buffer-menu t) (setq default-frame-alist (append (list '(fullscreen . maximized) '(vertical-scroll-bars . nil) '(internal-border-width . 2) '(background-color . "#292c2f") '(undecorated-round . t) '(scroll-bar-mode . -1) '(tool-bar-lines . 0) '(menu-bar-lines . 0)))) (menu-bar-mode -1) (scroll-bar-mode -1) (tool-bar-mode -1) (setq scroll-margin 0 scroll-conservatively 100000 scroll-preserve-screen-position 1)
3. Initial configuration
A bunch of initial configuration for init.el
to ensure smooth start-up.
3.1. Lexical binding
It is an interesting feature that was introduced a while ago in Emacs11, using this in the init file will help ensure that
;; -*- lexical-binding: t; -*- ;; WARNING: This file is generated by an org file, please do not edit this file directly
3.2. Using Elpaca.el
Emacs package manager has come a long way and is fairly wonderful, but having tried straight.el and Elpaca, I prefer the simplicity and flexibility that Elpaca provides (not to mention the UX).
- The default portion is used from https://github.com/progfolio/elpaca to ensure that Elpaca is loaded.
- Elpaca can hang sometimes as it asynchronously tries to fetch a bunch of packages. Set a queue limit to ensure that this happens in a saner manner.
- Having migrated from straight.el to Elpaca, one of the things that made it smooth for migration was the coupling with
use-package
, in order to continue this, Elpaca is also integrated with use-package. - Elpaca provides support for ensure, it is a good way to add specific repository details, etc. Therefore using
(elpaca-wait)
is the suggested way to achieve this.
(defvar elpaca-installer-version 0.8) (defvar elpaca-directory (expand-file-name "elpaca/" user-emacs-directory)) (defvar elpaca-builds-directory (expand-file-name "builds/" elpaca-directory)) (defvar elpaca-repos-directory (expand-file-name "repos/" elpaca-directory)) (defvar elpaca-order '(elpaca :repo "https://github.com/progfolio/elpaca.git" :ref nil :depth 1 :files (:defaults "elpaca-test.el" (:exclude "extensions")) :build (:not elpaca--activate-package))) (let* ((repo (expand-file-name "elpaca/" elpaca-repos-directory)) (build (expand-file-name "elpaca/" elpaca-builds-directory)) (order (cdr elpaca-order)) (default-directory repo)) (add-to-list 'load-path (if (file-exists-p build) build repo)) (unless (file-exists-p repo) (make-directory repo t) (when (< emacs-major-version 28) (require 'subr-x)) (condition-case-unless-debug err (if-let* ((buffer (pop-to-buffer-same-window "*elpaca-bootstrap*")) ((zerop (apply #'call-process `("git" nil ,buffer t "clone" ,@(when-let* ((depth (plist-get order :depth))) (list (format "--depth=%d" depth) "--no-single-branch")) ,(plist-get order :repo) ,repo)))) ((zerop (call-process "git" nil buffer t "checkout" (or (plist-get order :ref) "--")))) (emacs (concat invocation-directory invocation-name)) ((zerop (call-process emacs nil buffer nil "-Q" "-L" "." "--batch" "--eval" "(byte-recompile-directory \".\" 0 'force)"))) ((require 'elpaca)) ((elpaca-generate-autoloads "elpaca" repo))) (progn (message "%s" (buffer-string)) (kill-buffer buffer)) (error "%s" (with-current-buffer buffer (buffer-string)))) ((error) (warn "%s" err) (delete-directory repo 'recursive)))) (unless (require 'elpaca-autoloads nil t) (require 'elpaca) (elpaca-generate-autoloads "elpaca" repo) (load "./elpaca-autoloads"))) (add-hook 'after-init-hook #'elpaca-process-queues) (elpaca `(,@elpaca-order)) (setq elpaca-queue-limit 30) (elpaca elpaca-use-package (elpaca-use-package-mode)) (elpaca-wait)
3.3. TODO Setting up user-defined variables
Based on the packages I am using, I plan to define variables in this section.
- Create directory for backing up files
- Add user name
- Set file for custom settings
- Create auto-save list
- Set variables for
fortune
command for use indashboard
- Set the paths for Projectile to search
- Set up file for abbreviations
(setq custom-file "~/.config/emacs/emacs-custom-settings.el" user-full-name "Bharath M. Palavalli" backup-directory-alist `(("." . ,(expand-file-name "~/.config/emacs/backups"))) auto-save-file-name-transforms `((".*" "~/.config/emacs/auto-save-list") t)) (setq fortune-dir "/usr/share/games/fortune/" fortune-file "/usr/share/games/fortune/cookie") (defvar org-roam-directory (file-truename "~/Dropbox/Notes/") "Define value for `org-roam-directory' to ensure it is available for helper functions.") (defvar org-roam-db-location (file-truename"~/Dropbox/Notes/org-roam.db") "Define value for `org-roam-db-directory' to ensure it is available for helper functions.") (setq projectile-project-search-path '("~/source/" "~/Org/" ("~/Dropbox/Notes" . 1))) (setq abbrev-file-name "~/.config/emacs/abbrevs")
3.4. Setting up some basic parameters
Emacs has some strange behaviour hard-coded, that almost everyone customises/modifies12, this section has a bunch of them modified.
- Use a single space in sentences
- When showing errors, scroll to the first error
- Accept short answers
- Emacs ellipses look ugly to say the least
- I do not foresee multiple/concurrent access to the same file(s), avoid locking files
- Avoid writing into init file that is auto-generated, create a different custom file for writing them and load the file.
- Treat all custom themes as safe and do not prompt for checking
- Set tab-width as 4 and do not mix the usage of tabs and spaces, all tabs are not converted as spaces13
Since I have space (hard disks are now in TBs and not in MBs/GBs),
- Do not delete old files
- Backup files that are under version control too
- Make suitable changes to the file name
- Save history for commands
- Enable recent files (for dashboard and others)
- Do not truncate history list
- If an entry exists previously in history, then delete it (I don't need a log of commands in a chronological manner)
- Save history from recorded mini-buffer too
- Ensure you save kill-ring, search-ring, regexp-search-ring and register list
- Save bookmarks to file every time it is created/deleted14
- Prefer Unicode and utf-8 system for Emacs and as I will be copying from multiple sources, set it to Unicode so that it is useful.
- Delete trailing white-spaces for Emacs
- Do not litter, yet another bad habit that Emacs has. Let us use the https://github.com/emacscollective/no-littering package as suggested by https://systemcrafters.net/emacs-tips/keeping-folders-clean/ to avoid any littering and text based on https://stackoverflow.com/questions/39091433/viewing-reloading-emacs-backup-files
- Convert "yes or no" to "y or n"
- Suppress “ad-handle-definition: .. redefined” warnings during Emacs start-up
- Don't delay warnings and show warnings appropriately
- Set-up time and date formats for local enIN
Modify controls for zooming
(setq sentence-end-double-space nil compilation-scroll-output 'first-error use-short-answers t truncate-string-ellipsis "…" create-lockfiles nil custom-safe-themes t delete-old-versions 0 vc-make-backup-files t history-length t history-delete-duplicates t savehist-save-minibuffer-history t savehist-additional-variables '(kill-ring search-ring regexp-search-ring register-alist) bookmark-save-flag 1 ) (fset 'yes-or-no-p 'y-or-n-p) (custom-set-variables '(ad-redefinition-action (quote accept))) (setq-default tab-width 4 indent-tabs-mode nil) (defun dont-delay-compile-warnings (fun type &rest args) (if (eq type 'bytecomp) (let ((after-init-time t)) (apply fun type args)) (apply fun type args))) (advice-add 'display-warning :around #'dont-delay-compile-warnings) (load custom-file t) (savehist-mode 1) (recentf-mode 1) (set-charset-priority 'unicode) (prefer-coding-system 'utf-8-unix) (customize-set-variable 'selection-coding-system 'utf-8) (add-hook 'before-save-hook #'delete-trailing-whitespace) (setq no-littering-etc-directory (expand-file-name "config/" user-emacs-directory)) (setq no-littering-var-directory (expand-file-name "data/" user-emacs-directory)) (use-package no-littering :ensure t :config (recentf-mode 1)) (defun force-backup-of-buffer () (setq buffer-backed-up nil)) (add-hook 'before-save-hook #'force-backup-of-buffer) (defun format-date (format) (let ((system-time-locale "en_IN.UTF-8")) (insert (format-time-string format)))) (defun bmp/insert-date () (interactive) (format-date "%A, %d %b %Y")) (defun bmp/insert-date-and-time () (interactive) (format-date "%d/%m/%Y, %r")) (defun bmp/insert-time () (interactive) (format-date "%r")) (global-set-key (kbd "C-+") 'text-scale-increase) (global-set-key (kbd "C--") 'text-scale-decrease)
3.5. GPG access
One of the running themes for this configuration file is that Emacs is not "just" a text editor in my case, I use it to connect to a number of services that require authentication, secrets and password management (not all of them use SSO or external token systems yet). Emacs provides a simple manner by which we can store them by encrypting the file with GPG. The post by Mickey Peterson at Mastering Emacs has more details.
(setq auth-source-debug t) (setq auth-sources '((:source "~/.config/emacs/secrets/.authinfo.gpg")))
This function helps me access the GPG keys from within this configuration so that I can connect to external services easily (sourced from https://github.com/daviwil/emacs-from-scratch/blob/master/show-notes/Emacs-Tips-Pass.org).
(defun bmp/lookup-password (&rest keys) (let ((result (apply #'auth-source-search keys))) (if result (funcall (plist-get (car result) :secret)) nil)))
4. Configuration
4.1. Help with Emacs commands
While the help system in Emacs is very useful, it is sometimes prudent to use on-demand and completion-at-point methods to understand the commands being executed. I use which-key
to remember short-cuts, helpful
to show a better help and guru-mode
to try and improve usage of Emacs commands rather than generic keys.
(use-package which-key :ensure t :init (which-key-mode 1)) (use-package which-key-posframe :ensure t :init (setq which-key-posframe-poshandler 'posframe-poshandler-frame-center) (which-key-posframe-mode 1)) (use-package helpful :ensure t) (global-set-key (kbd "C-h f") #'helpful-callable) (global-set-key (kbd "C-h v") #'helpful-variable) (global-set-key (kbd "C-h k") #'helpful-key) (global-set-key (kbd "C-h x") #'helpful-command) (global-set-key (kbd "C-c C-d") #'helpful-at-point) (global-set-key (kbd "C-h F") #'helpful-function) (use-package guru-mode :ensure t :config (setq guru-warn-only t) (guru-global-mode +1))
4.2. Look and feel
Some basic customization with the look and feel for Emacs.
4.2.1. Theme
I have now moved to using ef-themes
from Prot as they seem to have been tested well and fit well with KDE dark theme, and I also disable other themes to avoid any clash of colours.
(use-package ef-themes :ensure t :config (setq ef-themes-mixed-fonts t ef-themes-variable-pitch-ui t) (mapc #'disable-theme custom-enabled-themes) (load-theme 'ef-owl :no-confirm))
4.2.2. Display background colour for strings with the colour value
rainbow-mode
is a minor mode for Emacs which displays strings representing colours with the colour they represent as background.
(use-package rainbow-mode :ensure t :config (add-hook 'prog-mode-hook 'rainbow-mode))
4.2.3. Padding between elements
This adds some space between various elements in Emacs: https://protesilaos.com/codelog/2023-06-03-emacs-spacious-padding/
(setq spacious-padding-widths '( :internal-border-width 10 :header-line-width 4 :mode-line-width 4 :tab-width 4 :right-divider-width 10 :scroll-bar-width 2)) (use-package spacious-padding :ensure t :hook (after-init . spacious-padding-mode))
4.2.4. Modeline
The default modeline in Emacs is cluttered and does not suit me, I am using a number of modifications to improve the functionality of the modeline.
- Diminish
Removes clutter from the modeline with https://www.emacswiki.org/emacs/DiminishedModes
(use-package diminish :demand t :ensure t) ;; Diminish modes not loaded at Emacs startup from https://www.emacswiki.org/emacs/DiminishedModes (eval-after-load "filladapt" '(diminish 'filladapt-mode)) ;; To fix :eval from the same page (defun diminished-modes () "Echo all active diminished or minor modes as if they were minor. The display goes in the echo area; if it's too long even for that, you can see the whole thing in the *Messages* buffer. This doesn't change the status of any modes; it just lets you see what diminished modes would be on the mode-line if they were still minor." (interactive) (let ((minor-modes minor-mode-alist) message) (while minor-modes (when (symbol-value (caar minor-modes)) ;; This minor mode is active in this buffer (let* ((mode-pair (car minor-modes)) (mode (car mode-pair)) (minor-pair (or (assq mode diminished-mode-alist) mode-pair)) (minor-name (cadr minor-pair))) (when (symbolp minor-name) ;; This minor mode uses symbol indirection in the cdr (let ((symbols-seen (list minor-name))) (while (and (symbolp (callf symbol-value minor-name)) (not (memq minor-name symbols-seen))) (push minor-name symbols-seen)))) (push minor-name message))) (callf cdr minor-modes)) ;; Handle :eval forms (setq message (mapconcat (lambda (form) (if (and (listp form) (eq (car form) :eval)) (apply 'eval (cdr form)) form)) (nreverse message) "")) (when (= (string-to-char message) ?\ ) (callf substring message 1)) (message "%s" message)))
- Doom modeline
I have tried a few options such as telephone-line, spaceline, simple-modeline and then some home-brew possibilities, I settled on
doom-modeline
as it works out of the box for all my needs. Install and use Doom Modeline from https://github.com/seagle0128/doom-modeline/, this text below is from the sample configuration provided in the repository.(setq doom-modeline-support-imenu t) (setq doom-modeline-height 25) (setq doom-modeline-bar-width 4) (setq doom-modeline-hud t) (setq doom-modeline-window-width-limit 85) (setq doom-modeline-project-detection 'auto) (setq doom-modeline-buffer-file-name-style 'auto) (setq doom-modeline-icon t) (setq doom-modeline-major-mode-icon t) (setq doom-modeline-major-mode-color-icon t) (setq doom-modeline-buffer-state-icon t) (setq doom-modeline-buffer-modification-icon t) (setq doom-modeline-lsp-icon t) (setq doom-modeline-time-icon t) (setq doom-modeline-time-live-icon t) (setq doom-modeline-unicode-fallback t) (setq doom-modeline-buffer-name t) (setq doom-modeline-highlight-modified-buffer-name t) (setq doom-modeline-column-zero-based t) (setq doom-modeline-percent-position '(-3 "%p")) (setq doom-modeline-position-line-format '("L%l")) (setq doom-modeline-position-column-format '("C%c")) (setq doom-modeline-position-column-line-format '("%l:%c")) (setq doom-modeline-minor-modes nil) (setq doom-modeline-enable-word-count t) (setq doom-modeline-continuous-word-count-modes '(markdown-mode gfm-mode org-mode)) (setq doom-modeline-buffer-encoding t) (setq doom-modeline-indent-info nil) (setq doom-modeline-total-line-number t) (setq doom-modeline-check-simple-format t) (setq doom-modeline-number-limit 99) (setq doom-modeline-vcs-max-length 12) (setq doom-modeline-workspace-name t) (setq doom-modeline-persp-name t) (setq doom-modeline-display-default-persp-name nil) (setq doom-modeline-persp-icon t) (setq doom-modeline-lsp t) (setq doom-modeline-github nil) (setq doom-modeline-github-interval (* 30 60)) (setq doom-modeline-modal t) (setq doom-modeline-modal-icon t) (setq doom-modeline-modal-modern-icon t) (setq doom-modeline-always-show-macro-register nil) (setq doom-modeline-gnus nil) (setq doom-modeline-gnus-timer 0) (setq doom-modeline-gnus-excluded-groups '("dummy.group")) (setq doom-modeline-irc t) (setq doom-modeline-irc-stylize 'identity) (setq doom-modeline-battery nil) (setq doom-modeline-time nil) (setq doom-modeline-display-misc-in-all-mode-lines t) (setq doom-modeline-buffer-file-name-function #'identity) (setq doom-modeline-buffer-file-truename-function #'identity) (setq doom-modeline-env-version t) (setq doom-modeline-env-enable-python t) (setq doom-modeline-env-enable-ruby t) (setq doom-modeline-env-enable-perl t) (setq doom-modeline-env-enable-go t) (setq doom-modeline-env-enable-elixir t) (setq doom-modeline-env-enable-rust t) (setq doom-modeline-env-python-executable "python") (setq doom-modeline-env-ruby-executable "ruby") (setq doom-modeline-env-perl-executable "perl") (setq doom-modeline-env-go-executable "go") (setq doom-modeline-env-elixir-executable "iex") (setq doom-modeline-env-rust-executable "rustc") (setq doom-modeline-env-load-string "...") (setq doom-modeline-always-visible-segments '(mastodon irc)) (setq doom-modeline-before-update-env-hook nil) (setq doom-modeline-after-update-env-hook nil) (use-package doom-modeline :ensure t :init (doom-modeline-mode 1))
4.2.5. Fonts
Use custom fonts based on my needs.
(custom-set-faces `(variable-pitch ((t (:family "EB Garamond" :height 120)))) `(fixed-pitch ((t (:family "FiraCode Nerd Font Mono" :height 100))))) (set-frame-font "FiraCode Nerd Font Mono") (set-face-attribute 'default nil :font "FiraCode Nerd Font Mono" :height 100)
Additional font configurations,
- Use
Noto Emoji
for better symbols - Use
emojify
- Install
font-lock+
- Install necessary
all-the-icons
packages - Need to run
M-x all-the-icons-install
- I also have
svg-lib
andkind-icon
to for speed and reliability - I have removed the
ac-emoji-setup
hook formarkdown-mode-hook
as it throws an error
(setf use-default-font-for-symbols nil) (set-fontset-font t 'unicode "Noto Emoji" nil 'append) (use-package emojify :ensure t :hook (after-init . global-emojify-mode) :config (set-fontset-font "fontset-default" 'symbol "Noto Color Emoji" nil 'append) (set-fontset-font "fontset-default" 'symbol "Symbola" nil 'append) (set-fontset-font t 'unicode (font-spec :family "all-the-icons") nil 'append) (set-fontset-font t 'unicode (font-spec :family "file-icons") nil 'append) (set-fontset-font t 'unicode (font-spec :family "Material Icons") nil 'append) (set-fontset-font t 'unicode (font-spec :family "github-octicons") nil 'append) (set-fontset-font t 'unicode (font-spec :family "FontAwesome") nil 'append) (set-fontset-font t 'unicode (font-spec :family "Weather Icons") nil 'append) (require 'font-lock)) (use-package font-lock+ :ensure (:host github :repo "emacsmirror/font-lock-plus") :config (require 'font-lock+)) (use-package all-the-icons :ensure t :if (display-graphic-p)) (use-package all-the-icons-completion :after (marginalia all-the-icons) :ensure t :config (all-the-icons-completion-mode)) (add-hook 'marginalia-mode-hook #'all-the-icons-completion-marginalia-setup) (use-package all-the-icons-nerd-fonts :ensure (:host github :repo "mohkale/all-the-icons-nerd-fonts") :after all-the-icons :demand t :config (all-the-icons-nerd-fonts-prefer)) (use-package all-the-icons-ibuffer :ensure t :hook (ibuffer-mode . all-the-icons-ibuffer-mode)) (use-package svg-lib :ensure t) (use-package kind-icon :ensure t :after corfu :custom (kind-icon-use-icons t) (kind-icon-default-face 'corfu-default) :config (add-to-list 'corfu-margin-formatters #'kind-icon-margin-formatter))
4.2.6. Distinguish between types of buffers
Use solaire-mode
to distinguish between various types of buffers in order to make it easy to use them by visually looking at them.
(use-package solaire-mode :ensure t :config (setq solaire-global-mode t) (add-to-list 'solaire-mode-themes-to-face-swap "^ef-"))
4.2.7. Create transparency
I'd been using transparency largely as a vanity feature until I realised it could be used as a way to take notes in video calls. This makes it easier to take notes without having to minimize the video, or resize the windows. This snippet is from EmacsWiki.
(defun bmp/transparency (value) "Sets the transparency of the frame window. 0=transparent/100=opaque" (interactive "nTransparency Value(0=transparent|100=opaque): ") (set-frame-parameter nil 'alpha-background value) (add-to-list 'default-frame-alist '(alpha-background . value)))
4.2.8. Dashboard
While almost everyone that have a public init file disables the default Emacs starting screen, there are a number of ways people customize the home screen. I use Emacs Dashboard as a replacement for my needs. I will have to add further details and this is not complete yet (I need to add the specific icons files to set it up).
I first came across fortune
in Slackware in the early 2000's, and have been a fan of it ever since. I have customised the dashboard so that a fortune is displayed at the bottom.
- The dashboard is customized to show only certain keywords in for the agenda, and the path for recent files is truncated in the middle to show only 50 characters.
- Ensure that dashboard is opened when client is launched.
- The logos are from https://github.com/egstatsml/emacs_fancy_logos, I have used ImageMagick to convert the SVG image to a WEBP format with size 180x180 to make it compatible with transparency.
(use-package dashboard :ensure t :after projectile :config (dashboard-setup-startup-hook) :custom (dashboard-startup-banner (concat user-emacs-directory "gnu_color.webp")) (dashboard-image-banner-max-height 180) (dashboard-banner-logo-title nil) (dashboard-center-content t) (dashboard-vertically-center-content t) (dashboard-icon-type 'nerd-icons) (dashboard-set-heading-icons t) (dashboard-set-file-icons t) (dashboard-footer-icon nil) (dashboard-footer-messages (list (fortune-message))) (dashboard-projects-backend 'projectile) (dashboard-display-icons-p t) (dashboard-items '( (recents . 5) (agenda . 5) (projects . 5) (bookmarks . 5) )) (dashboard-filter-agenda-entry 'dashboard-no-filter-agenda) (dashboard-match-agenda-entry "TODO=\"TODO\"|TODO=\"STARTED\"") (dashboard-agenda-tags-format 'ignore) (dashboard-agenda-prefix-format "") (dashboard-path-style 'truncate-middle) (dashboard-path-max-length 50) (dashboard-bookmarks-item-format "%s") (dashboard-force-refresh t) (add-hook 'elpaca-after-init-hook #'dashboard-insert-startupify-lists) (add-hook 'elpaca-after-init-hook #'dashboard-initialize) (add-hook 'window-size-change-functions #'dashboard-resize-on-hook 100) (add-hook 'window-setup-hook #'dashboard-resize-on-hook)) (setq initial-buffer-choice (lambda () (get-buffer "*dashboard*")))
4.2.9. Nerd Icons in dired
After trying both all-the-icons-dired
, I have settled on nerd-icons-dired
as this looks better.
(use-package nerd-icons-dired :ensure t :hook (dired-mode . nerd-icons-dired-mode))
4.3. Versioning
Having moved from subversion systems to git-based systems, the best interface for Emacs is Magit.
4.3.1. Magit
Since, moving to Elpaca, I have had to load transient
to fix race condition for git-commit and Magit as per: https://github.com/progfolio/elpaca/issues/302. Magit is astoundingly intuitive for users like me who do not have a complicated git work-flow15.
(use-package transient :ensure t) (use-package magit :ensure t :bind ("C-x g" . magit-status) :init (setq magit-define-global-key-bindings nil) (setq magit-section-visibility-indicator '(" ▼")) :config (setq git-commit-summary-max-length 50) (setq git-commit-style-convention-checks '(non-empty-second-line)) (setq magit-diff-refine-hunk t))
4.3.2. TODO Diff-hl
Moved to diff-hl
after trying out git-gutter
for a while, this seems to provide the accurate highlights in the fringe/margin based on my needs. The options I have are to either use (diff-hl-margin-mode)
or to set-up a specific fringe to enable it to work well with (set-fringe-style '(2 . 2))
or (fringe-mode '(8 . 8))
. In order to increase the spaces in the fringe after the margin is set with (diff-hl-margin-mode)
, I have decided to use (fringe-mode nil)
to set-up a gap. I have also added Magit hooks so that it integrates well.
(use-package diff-hl :ensure t :config (global-diff-hl-mode) (diff-hl-flydiff-mode) (add-hook 'dired-mode-hook 'diff-hl-dired-mode) (add-hook 'magit-pre-refresh-hook 'diff-hl-magit-pre-refresh) (add-hook 'magit-post-refresh-hook 'diff-hl-magit-post-refresh))
4.4. Navigation
Moving around within Emacs (buffers, frames, windows, etc.)
4.4.1. Winner mode
Capture and restore window configuration
(use-package winner :ensure nil :defer 1 :config (winner-mode))
4.4.2. Relative buffer names
Emacs makes it hard to switch between buffers when they have full names, using buffer-name-relative
makes it easier to recognize the names.
(use-package buffer-name-relative :ensure t :config (setq buffer-name-relative-mode t))
4.4.3. Buffer management
Better buffer management with https://github.com/alphapapa/bufler.el.
(use-package bufler :ensure t :config (setq bufler-mode +1)) (use-package ibuffer :hook (ibuffer-mode . ibuffer-auto-mode) :defer t)
4.4.4. Quick navigation in the mini-buffer
(use-package consult-dir :ensure t :after vertico consult projectile :bind (("C-x C-d" . consult-dir) :map vertico-map ("C-x C-d" . consult-dir) ("C-x C-j" . consult-dir-jump-file)) :config (setq consult-dir-project-list-function #'consult-dir-projectile-dirs))
4.4.5. Dired
From Prot https://protesilaos.com/codelog/2023-06-26-emacs-file-dired-basics/.
(file-name-shadow-mode 1) (setq delete-by-moving-to-trash t) (add-hook 'rfn-eshadow-update-overlay-hook #'vertico-directory-tidy) (setq dired-dwim-target t) (add-hook 'dired-mode-hook #'dired-hide-details-mode) (setq dired-guess-shell-alist-user '(("\\.\\(png\\|jpe?g\\|tiff\\)" "feh" "xdg-open") ("\\.\\(mp[34]\\|m4a\\|ogg\\|flac\\|webm\\|mkv\\)" "mpv" "xdg-open") (".*" "xdg-open"))) (setq dired-recursive-copies 'always) (setq dired-recursive-deletes 'always)
4.4.6. Dired sidebar
Emulate a tree browser by leveraging Dired using https://github.com/jojojames/dired-sidebar
(use-package dired-sidebar :bind (("C-x C-n" . dired-sidebar-toggle-sidebar)) :ensure t :commands (dired-sidebar-toggle-sidebar) :init (add-hook 'dired-sidebar-mode-hook (lambda () (unless (file-remote-p default-directory) (auto-revert-mode)))) :config (push 'toggle-window-split dired-sidebar-toggle-hidden-commands) (push 'rotate-windows dired-sidebar-toggle-hidden-commands) (setq dired-sidebar-subtree-line-prefix "__") (setq dired-sidebar-theme 'vscode) (setq dired-sidebar-use-term-integration t) (setq dired-sidebar-use-custom-font t))
4.4.7. Media player preview
Using ready-player
to auto-play media from within Emacs.
(use-package ready-player :ensure t :config (ready-player-mode +1))
4.4.8. Dired preview
Preview files in dired
mode and use the ready-player
window function from https://protesilaos.com/emacs/dired-preview#h:1765efb1-f9fe-4379-8ef3-668e573e299b
(use-package dired-preview :ensure t :after ready-player :config (setq dired-preview-max-size (expt 2 20)) (setq dired-preview-delay 2) (setq dired-preview-ignored-extensions-regexp (concat "\\." "\\(gz\\|" "zst\\|" "tar\\|" "xz\\|" "rar\\|" "zip\\|" "iso\\|" "gpg\\|" "epub" "\\)")) (dired-preview-global-mode 1)) (defun bmp/ready-player-dired-preview-play-toggle () "Call `ready-player-toggle-play-stop' on the currently previewed media file." (interactive) (dired-preview-with-window (if-let ((file buffer-file-name) (media (concat "\\." (regexp-opt ready-player-supported-media t) "\\'")) (_ (string-match-p media file))) (call-interactively #'ready-player-toggle-play-stop) (user-error "Cannot do something useful with `ready-player' here"))))
4.4.9. File Explorer
Using a sidebar for browsing folders with treemacs
: https://github.com/Alexander-Miller/treemacs
(use-package treemacs :ensure t :defer t :init (with-eval-after-load 'winum (define-key winum-keymap (kbd "M-0") #'treemacs-select-window)) :config (progn (setq treemacs-collapse-dirs (if treemacs-python-executable 3 0) treemacs-deferred-git-apply-delay 0.5 treemacs-directory-name-transformer #'identity treemacs-display-in-side-window t treemacs-eldoc-display 'simple treemacs-file-event-delay 2000 treemacs-file-extension-regex treemacs-last-period-regex-value treemacs-file-follow-delay 0.2 treemacs-file-name-transformer #'identity treemacs-follow-after-init t treemacs-expand-after-init t treemacs-find-workspace-method 'find-for-file-or-pick-first treemacs-git-command-pipe "" treemacs-goto-tag-strategy 'refetch-index treemacs-header-scroll-indicators '(nil . "^^^^^^") treemacs-hide-dot-git-directory t treemacs-indentation 2 treemacs-indentation-string " " treemacs-is-never-other-window nil treemacs-max-git-entries 5000 treemacs-missing-project-action 'ask treemacs-move-forward-on-expand nil treemacs-no-png-images nil treemacs-no-delete-other-windows t treemacs-project-follow-cleanup nil treemacs-persist-file (expand-file-name ".cache/treemacs-persist" user-emacs-directory) treemacs-position 'left treemacs-read-string-input 'from-child-frame treemacs-recenter-distance 0.1 treemacs-recenter-after-file-follow nil treemacs-recenter-after-tag-follow nil treemacs-recenter-after-project-jump 'always treemacs-recenter-after-project-expand 'on-distance treemacs-litter-directories '("/node_modules" "/.venv" "/.cask") treemacs-project-follow-into-home nil treemacs-show-cursor nil treemacs-show-hidden-files t treemacs-silent-filewatch nil treemacs-silent-refresh nil treemacs-sorting 'alphabetic-asc treemacs-select-when-already-in-treemacs 'move-back treemacs-space-between-root-nodes t treemacs-tag-follow-cleanup t treemacs-tag-follow-delay 1.5 treemacs-text-scale nil treemacs-user-mode-line-format nil treemacs-user-header-line-format nil treemacs-wide-toggle-width 70 treemacs-width 35 treemacs-width-increment 1 treemacs-width-is-initially-locked t treemacs-workspace-switch-cleanup nil) (treemacs-resize-icons 14) (treemacs-follow-mode t) (treemacs-filewatch-mode t) (treemacs-fringe-indicator-mode 'always) (when treemacs-python-executable (treemacs-git-commit-diff-mode t)) (pcase (cons (not (null (executable-find "git"))) (not (null treemacs-python-executable))) (`(t . t) (treemacs-git-mode 'deferred)) (`(t . _) (treemacs-git-mode 'simple))) (treemacs-hide-gitignored-files-mode nil)) :bind (:map global-map ("M-0" . treemacs-select-window) ("C-x t 1" . treemacs-delete-other-windows) ("C-x t t" . treemacs) ("C-x t d" . treemacs-select-directory) ("C-x t B" . treemacs-bookmark) ("C-x t C-t" . treemacs-find-file) ("C-x t M-t" . treemacs-find-tag))) (use-package treemacs-projectile :after (treemacs projectile) :ensure t) (use-package treemacs-icons-dired :ensure t) (use-package treemacs-magit :after (treemacs magit) :ensure t) (use-package treemacs-tab-bar :after (treemacs) :ensure t :config (treemacs-set-scope-type 'Tabs))
4.4.10. Navigate within some buffers in read only mode a.k.a
Using the built-in view-mode
works like a charm, it converts buffers to view only and does not allow them to be edited. The following code is a reproduction from http://yummymelon.com/devnull/enhancing-navigation-in-emacs-view-mode.html.
(require 'view) (add-hook 'view-mode-hook (lambda () (cond ((derived-mode-p 'org-mode) (define-key view-mode-map (kbd "p") 'org-previous-visible-heading) (define-key view-mode-map (kbd "n") 'org-next-visible-heading)) ((derived-mode-p 'markdown-mode) (define-key view-mode-map (kbd "p") 'markdown-outline-previous) (define-key view-mode-map (kbd "n") 'markdown-outline-next)) ((derived-mode-p 'html-mode) (define-key view-mode-map (kbd "p") 'sgml-skip-tag-backward) (define-key view-mode-map (kbd "n") 'sgml-skip-tag-forward)) ((derived-mode-p 'python-mode) (define-key view-mode-map (kbd "p") 'python-nav-backward-block) (define-key view-mode-map (kbd "n") 'python-nav-forward-block)) ((derived-mode-p 'emacs-lisp-mode) (define-key view-mode-map (kbd "p") 'backward-sexp) (define-key view-mode-map (kbd "n") 'forward-sexp)) ((derived-mode-p 'makefile-mode) (define-key view-mode-map (kbd "p") 'makefile-previous-dependency) (define-key view-mode-map (kbd "n") 'makefile-next-dependency)) ((derived-mode-p 'c-mode) (define-key view-mode-map (kbd "p") 'c-beginning-of-defun) (define-key view-mode-map (kbd "n") 'c-end-of-defun)) (t (define-key view-mode-map (kbd "p") 'scroll-down-command) (define-key view-mode-map (kbd "n") 'scroll-up-command)))))
4.4.11. Handling bookmarks
I am now enabling visual bookmarks with bm.el
.
(use-package bookmark :ensure nil :defer t) (use-package bm :ensure t :config (setq bm-cycle-all-buffers t) (add-hook 'kill-buffer-hook #'bm-buffer-save) (add-hook 'kill-emacs-hook #'(lambda nil (bm-buffer-save-all) (bm-repository-save))) (add-hook 'after-save-hook #'bm-buffer-save) (add-hook 'find-file-hooks #'bm-buffer-restore) (add-hook 'after-revert-hook #'bm-buffer-restore) (setq bm-marker 'bm-marker-right) (global-set-key (kbd "<C-f2>") 'bm-toggle) (global-set-key (kbd "<f2>") 'bm-next) (global-set-key (kbd "<S-f2>") 'bm-previous) (global-set-key (kbd "<left-fringe> <mouse-5>") 'bm-next-mouse) (global-set-key (kbd "<left-fringe> <mouse-4>") 'bm-previous-mouse) (global-set-key (kbd "<left-fringe> <mouse-1>") 'bm-toggle-mouse))
4.5. Project handling
Interact with project directories in Emacs: https://github.com/bbatsov/projectile
- Automated Project Discovery
- Project indexing (fast mode)
- Sort recently opened files
- Projectile enable caching
- Open top-level directory of the project
(use-package projectile :ensure t :init (projectile-mode +1) :bind (:map projectile-mode-map ("s-p" . projectile-command-map) ("C-c p" . projectile-command-map))) (setq projectile-indexing-method 'alien) (setq projectile-sort-order 'recentf) (setq projectile-enable-caching t) (setq projectile-switch-project-action #'projectile-dired)
4.6. Org mode
4.6.1. Basic customization
Provide location of Org files as ~/Org
, and then ensure that Org mode starts automatically for the necessary files.
- Basic customization
- Improve Org mode looks
- Font-lock substitution for list markers
- Setting up variable-pitch and fixed-pitch faces
- Use long lines and visual-line-mode
;; Org customization (add-to-list 'load-path (expand-file-name "~/Org")) (add-to-list 'auto-mode-alist '("\\.\\(org\\|org_archive\\|txt\\)$" . org-mode)) (setq org-agenda-start-with-log-mode t) (setq org-log-done 'time) (setq org-log-into-drawer t) (setq-default org-startup-indented t org-pretty-entities t org-use-sub-superscripts "{}" org-hide-emphasis-markers t org-startup-with-inline-images t org-image-actual-width '(300)) (font-lock-add-keywords 'org-mode '(("^ +\\([-*]\\) " (0 (prog1 () (compose-region (match-beginning 1) (match-end 1) "◉")))))) (setq org-fold-catch-invisible-edits 'smart) (add-hook 'org-mode-hook 'variable-pitch-mode) (add-hook 'org-mode-hook 'visual-line-mode)
4.6.2. Org configuration
Ensure that Org is loaded now as required and then add org-latex
exporter along with org-latex
exporter customization.
- Ensuring code blocks are in
monospace
fonts. - Export with
org-latex
- Activate a bunch of languages for
org-babel
- Install
org-babel
dependencies
(use-package org :ensure t :bind (:map org-mode-map ("C-M-<return>" . org-insert-subheading)) :config (require 'oc-basic) (unless (functionp 'org-link-make-string) (fset 'org-link-make-string 'org-make-link-string)) (defun my-adjoin-to-list-or-symbol (element list-or-symbol) (let ((list (if (not (listp list-or-symbol)) (list list-or-symbol) list-or-symbol))) (require 'cl-lib) (cl-adjoin element list))) (eval-after-load "org" '(mapc (lambda (face) (set-face-attribute face nil :inherit (my-adjoin-to-list-or-symbol 'fixed-pitch (face-attribute face :inherit)))) (list 'org-code 'org-block ))) (with-eval-after-load 'ox-latex (add-to-list 'org-latex-classes '("org-plain-latex" "\\documentclass{article} [NO-DEFAULT-PACKAGES] [PACKAGES] [EXTRA]" ("\\section{%s}" . "\\section*{%s}") ("\\subsection{%s}" . "\\subsection*{%s}") ("\\subsubsection{%s}" . "\\subsubsection*{%s}") ("\\paragraph{%s}" . "\\paragraph*{%s}") ("\\subparagraph{%s}" . "\\subparagraph*{%s}"))) (setq org-latex-listings 't) (add-to-list 'org-latex-packages-alist '("" "listings")) (add-to-list 'org-latex-packages-alist '("" "color")) (add-to-list 'org-latex-packages-alist '("AUTO" "babel" t ("pdflatex" "xelatex" "lualatex"))) (add-to-list 'org-latex-packages-alist '("AUTO" "polyglossia" t ("xelatex" "lualatex"))) ) (org-babel-do-load-languages (quote org-babel-load-languages) (quote ((C . t) (css . t) (dot . t) (emacs-lisp . t) (http . t) (java . t) (latex . t) (makefile . t) (org . t) (perl . t) (python . t) (R . t) (shell . t) (sql . t)))) ) (use-package org-contrib :ensure (:host sourcehut :repo "bzg/org-contrib") :after org) (use-package ob-http :ensure t :after org)
4.6.3. Toggle appearance of elements
Ensure that visibility of elements can be toggled, i.e, emphasis markers, links, etc.
(use-package org-appear :ensure t :after org :hook (org-mode . org-appear-mode))
4.6.4. TODO Bullets customization
Currently, using org-superstar
, but it might be useful to at some point migrate to org-modern, it seems to present a few advantages.
- Set different bullets, with one getting a terminal fall-back.
- Stop cycling bullets to emphasize hierarchy of headlines.
- Hide away leading stars on terminal.
(use-package org-superstar :ensure t :config (add-hook 'org-mode-hook (lambda () (org-superstar-mode 1))) (setq org-superstar-headline-bullets-list '("◉" ("◉" ?◉) "◉" "◉")) (setq org-superstar-cycle-headline-bullets nil) (setq org-superstar-special-todo-items t) (setq org-superstar-leading-fallback ?\s))
4.6.5. Org-babel
Customize org-babel
to ensure that necessary outputs and inputs are possible.
- Make babel results blocks lower-case
- Turn on syntax colouring
- Add source code blocks for
org-babel
- Make responsive in-line images
- Added an export and tangle function where I can tangle this file and then export it to html as well from https://emacs.stackexchange.com/questions/24645/exporting-and-tangling-simultaneously-in-org-mode
- Ensured that an
index.html
is also generated rather than the earlier form of a symbolic link. Codeberg does not seem to render this symbolic link. - Added
corg
to enable completion-at-point for Org mode source blocks, details are at https://github.com/isamert/corg.el
(setq org-babel-results-keyword "results") (setq org-src-fontify-natively t) (setq org-structure-template-alist '(("c" . "center\n") ("C" . "comment\n") ("e" . "example\n") ("q" . "quote\n") ("v" . "verse\n") ("E" . "export") ("a" . "export ascii\n") ("h" . "export html\n") ("l" . "export latex\n") ("M" . "export markdown\n") ("m" . "markdown\n") ("P" . "src py\n") ("j" . "src java\n") ("s" . "src\n") ("t" . "tex\n") ("el" . "src emacs-lisp\n") ("ein" . "src emacs-lisp :tangle ~/.config/emacs/init.el\n") ("py" . "src python\n") ("sh" . "src sh\n") ("md" . "src markdown\n") ("go" . "src go\n") ("tex" . "src tex\n") ("java" . "src java\n") ("sr" . "src R\n") )) (defun bmp/export-tangle-init () "Function to tangle the init file in the correct location, and export it to index.html" (interactive) (org-export-to-file 'html "index.html") (org-html-export-to-html) (org-babel-tangle)) (use-package corg :ensure (:host github :repo "isamert/corg.el")) (add-hook 'org-mode-hook #'corg-setup)
4.6.6. LaTeX previews with Org
Using org-fragtog to ensure preview of LaTeX code within Org files
(use-package org-fragtog :ensure t :after org :hook (org-mode . org-fragtog-mode) :custom (org-startup-with-latex-preview t) (org-format-latex-options (plist-put org-format-latex-options :scale 2) (plist-put org-format-latex-options :foreground 'auto) (plist-put org-format-latex-options :background 'auto)))
4.6.7. Customizing tables
Provide visual alignment with valign
from https://github.com/casouri/valign
(use-package valign :ensure t) (add-hook 'org-mode-hook #'valign-mode)
4.6.8. Image handling in the Org file
One consistent headache with Org image handling is that it does not resize images correctly, by using this custom function from SO16, the aim is to at least set them to Window size.
(defun bmp/org-image-resize (frame) (when (derived-mode-p 'org-mode) (if (< (window-total-width) 80) (setq org-image-actual-width (window-pixel-width)) (setq org-image-actual-width (* 80 (window-font-width)))) (org-redisplay-inline-images))) (add-hook 'window-size-change-functions 'bmp/org-image-resize) (defun bmp/display-inline-images () (condition-case nil (org-display-inline-images) (error nil))) (add-hook 'org-babel-after-execute-hook 'bmp/display-inline-images 'append)
4.6.9. TODO Create notes for files in Org mode
org-noter
allows me to have notes for individual files outside of the files. This makes it easier to make it portable and at the same time ensure that the source files aren't modified. The notes are maintained within org-roam
to ensure compatibility.
(use-package djvu :ensure t) (use-package org-noter :ensure t :after pdf-tools nov org-roam :config (setq org-noter-always-create-frame t) (org-noter-enable-org-roam-integration)) (with-eval-after-load 'org-noter (setq org-noter-arrow-background-color "yellow" org-noter-arrow-foreground-color "red"))
4.6.10. Rich paste
Make it easy to paste code blocks in org mode from https://github.com/unhammer/org-rich-yank
(use-package org-rich-yank :ensure t :demand t :bind (:map org-mode-map ("C-M-y" . org-rich-yank)))
4.6.11. Table of contents
Let's install and load the toc-org package after org mode is loaded. This is the package that automatically generates an up to date table of contents for us.
(use-package toc-org :ensure t :after org :init (add-hook 'org-mode-hook #'toc-org-enable))
4.6.12. Embed org-files
After looking at various methods, org-transclutions
is the best option that is currently maintained and addition of the fringe bitmap is from https://github.com/nobiot/org-transclusion/issues/126 and https://github.com/nobiot/org-transclusion/issues/201. This configuration works as I do not use org-modern
(use-package org-transclusion :ensure t :after org :bind (:map global-map ("<f12>" . #'org-transclusion-add) ("C-c t" . #'org-transclusion-mode))) (defun org-transclusion-content-insert-add-overlay (beg end) "Add fringe after transclusion." (overlay-put (text-clone-make-overlay beg end (current-buffer)) 'line-prefix (org-transclusion-propertize-transclusion)) (overlay-put (text-clone-make-overlay beg end (current-buffer)) 'wrap-prefix (org-transclusion-propertize-transclusion))) (add-hook 'org-transclusion-after-add-functions #'org-transclusion-content-insert-add-overlay)
4.6.13. Exporters
Multiple export options for org-mode
- Export to Markdown
(use-package ox-gfm :ensure t :commands (org-gfm-export-as-markdown org-gfm-export-to-markdown) :after org )
- Pandoc Integration
I have included the use of
ox-pandoc
, to ensure that all default options are used, I preferxelatex
overpdflatex
and have setupgithub
options as indicated in the repository.(use-package ox-pandoc :ensure t :after org :config (setq org-pandoc-options '((standalone . t))) (setq org-pandoc-options-for-beamer-pdf '((pdf-engine . "xelatex"))) (setq org-pandoc-options-for-latex-pdf '((pdf-engine . "xelatex"))) (setq org-pandoc-format-extensions '(markdown_github+pipe_tables+raw_html)) )
I have also used
pandoc-mode
in order to make life simpler.(use-package pandoc-mode :ensure t :after org :config (add-hook 'markdown-mode-hook 'pandoc-mode) (add-hook 'pandoc-mode-hook 'pandoc-load-default-settings) )
- Presentation in HTMxL mode for org with org-re-reveal
Have a look at https://gitlab.com/oer/org-re-reveal I need to
M-x load-library
, and thenorg-re-reveal
to enable org-re-reveal in the exporter (i.eorg-export-dispatch (C-c C-e)
)(use-package org-re-reveal :ensure t :after org :init (setq org-re-reveal-root "https://cdn.jsdelivr.net/npm/reveal.js" org-re-reveal-revealjs-version "4") )
- Templates
(use-package templatel :ensure t)
- Ox-hugo
(use-package ox-hugo :ensure t :after ox)
4.6.14. TODO Saving fold state of Org files
In some cases, I would like to retain the fold/unfolded state of an Org file, however, Org Mode currently only provides a default state of folder or unfolded. It does provide a possibility to store the fold state for each tree/sub-tree within the file, which makes it messy. I have used the code verbatim from Emacs Wiki to provide this functionality for myself17.
(defun orgfold-get-fold-info-file-name () (concat (buffer-file-name) ".fold")) (defun orgfold-save () (save-excursion (goto-char (point-min)) (let (foldstates) (unless (looking-at outline-regexp) (outline-next-visible-heading 1)) (while (not (eobp)) (push (when (seq-some (lambda (o) (overlay-get o 'invisible)) (overlays-at (line-end-position))) t) foldstates) (outline-next-visible-heading 1)) (with-temp-file (orgfold-get-fold-info-file-name) (prin1 (nreverse foldstates) (current-buffer)))))) (defun orgfold-restore () (save-excursion (goto-char (point-min)) (let* ((foldfile (orgfold-get-fold-info-file-name)) (foldstates (when (file-readable-p foldfile) (with-temp-buffer (insert-file-contents foldfile) (when (> (buffer-size) 0) (read (current-buffer))))))) (when foldstates (show-all) (goto-char (point-min)) (unless (looking-at outline-regexp) (outline-next-visible-heading 1)) (while (and foldstates (not (eobp))) (when (pop foldstates) (hide-subtree)) (outline-next-visible-heading 1)) (message "Restored saved folding"))))) (add-hook 'org-mode-hook 'orgfold-activate) (defun orgfold-activate () (orgfold-restore) (add-hook 'kill-buffer-hook 'orgfold-kill-buffer nil t)) (defun orgfold-kill-buffer () ;; don't save folding info for unsaved buffers (unless (buffer-modified-p) (orgfold-save)))
4.7. Editing
A bunch of configuration to improve the text editing experience in Emacs.
4.7.1. Spell-checking
Trying out jinx
from https://github.com/minad/jinx, and I have enabled it globally.
(use-package jinx :ensure t :hook (emacs-startup . global-jinx-mode) :bind (("M-$" . jinx-correct) ("C-M-$" . jinx-languages)) :config (add-hook 'emacs-startup-hook #'global-jinx-mode) (setq jinx-languages "en_GB")) (defun jinx--add-to-abbrev (overlay word) "Add abbreviation to `global-abbrev-table'. The misspelled word is taken from OVERLAY. WORD is the corrected word." (let ((abbrev (buffer-substring-no-properties (overlay-start overlay) (overlay-end overlay)))) (message "Abbrev: %s -> %s" abbrev word) (define-abbrev global-abbrev-table abbrev word))) (advice-add 'jinx--correct-replace :before #'jinx--add-to-abbrev)
4.7.2. Highlighting line
Using pulsar
to pulse highlight line on demand or after running select functions, integrate it with consult
and with imenu
.
(use-package pulsar :ensure t :config (setq pulsar-pulse t) (setq pulsar-delay 0.055) (setq pulsar-iterations 10) (setq pulsar-face 'pulsar-magenta) (setq pulsar-highlight-face 'pulsar-yellow) (let ((map global-map)) (define-key map (kbd "C-c h p") #'pulsar-pulse-line) (define-key map (kbd "C-c h h") #'pulsar-highlight-line)) (add-hook 'minibuffer-setup-hook #'pulsar-pulse-line) (add-hook 'minibuffer-setup-hook #'pulsar-pulse-line-blue) ;; integration with the `consult' package: (add-hook 'consult-after-jump-hook #'pulsar-recenter-top) (add-hook 'consult-after-jump-hook #'pulsar-reveal-entry) ;; integration with the built-in `imenu': (add-hook 'imenu-after-jump-hook #'pulsar-recenter-top) (add-hook 'imenu-after-jump-hook #'pulsar-reveal-entry) (pulsar-global-mode 1))
4.7.3. Smart Parentheses
Using smartparens
to ensure parentheses are matched and balanced easily.
(use-package smartparens :ensure t :config (require 'smartparens-config) (add-hook 'org-mode #'smartparens-mode) (add-hook 'css-mode #'smartparens-mode) (add-hook 'python-mode #'smartparens-mode) (add-hook 'minibuffer-setup-hook 'turn-on-smartparens-strict-mode) (show-smartparens-global-mode t) (sp-with-modes '(minibuffer-inactive-mode minibuffer-mode) (sp-local-pair "'" nil :actions nil) (sp-local-pair "(" nil :wrap "C-(")) (sp-with-modes 'org-mode (sp-local-pair "=" "=" :wrap "C-=")) (sp-with-modes 'web-mode (sp-local-pair "{{#if" "{{/if") (sp-local-pair "{{#unless" "{{/unless")) (sp-with-modes '(tex-mode plain-tex-mode latex-mode) (sp-local-tag "i" "\"<" "\">")) (defun my-add-space-after-sexp-insertion (id action _context) (when (eq action 'insert) (save-excursion (forward-char (sp-get-pair id :cl-l)) (when (or (eq (char-syntax (following-char)) ?w) (looking-at (sp--get-opening-regexp))) (insert " "))))) (defun my-add-space-before-sexp-insertion (id action _context) (when (eq action 'insert) (save-excursion (backward-char (length id)) (when (or (eq (char-syntax (preceding-char)) ?w) (and (looking-back (sp--get-closing-regexp)) (not (eq (char-syntax (preceding-char)) ?')))) (insert " "))))) (sp-with-modes sp--lisp-modes (sp-local-pair "(" nil :wrap "C-(" :pre-handlers '(my-add-space-before-sexp-insertion) :post-handlers '(my-add-space-after-sexp-insertion))))
4.7.4. Auto-pair special characters and parentheses
electric-mode
does not always work well, so this needs some work and changes.
(electric-pair-mode 1) (setq electric-quote-paragraph t electric-quote-comment t) (setq electric-pair-pairs '( (?\( . ?\)) (?\< . ?\>) (?\[ . ?\]) (?\{ . ?\}) (?\~ . ?\~) ))
4.7.5. Recent files
This is an inbuilt Emacs package, I am only customizing it here.
(use-package recentf :ensure nil :hook (after-init . recentf-mode) :config (setq recentf-max-saved-items 100) (setq recentf-max-menu-items 25) (setq recentf-save-file-modes nil) (setq recentf-keep nil) (setq recentf-auto-cleanup nil) (setq recentf-initialize-file-name-history nil) (setq recentf-filename-handlers nil) (setq recentf-show-file-shortcuts-flag nil))
4.7.6. Undo
I was using undo-tree
earlier, but vundo
is quicker and is intuitive for my use-case. It is currently hard-coded to my current theme which is ef-owl
(use-package vundo :ensure t :bind ("C-x C-u" . vundo) :config (setq vundo-compact-display t) (setq vundo-window-max-height 8) (setq vundo-glyph-alist vundo-unicode-symbols) (setq vundo-compact-display t) '(vundo-default nil :family "FiraCode Nerd Font Mono") '(vundo-node ((t (:foreground (ef-themes-get-color-value 'fg-main))))) '(vundo-stem ((t (:foreground (ef-themes-get-color-value 'fg-dim))))) '(vundo-saved ((t (:foreground (ef-themes-get-color-value 'green-coolor))))) '(vundo-last-saved ((t (:foreground (ef-themes-get-color-value 'green-intense))))) '(vundo-highlight ((t (:foreground (ef-themes-get-color-value 'fg-changed)))))) (use-package undo-fu :ensure t :config (undo-fu-session-global-mode)) (use-package undo-fu-session :ensure t :config (setq undo-fu-session-incompatible-files '("/COMMIT_EDITMSG\\'" "/git-rebase-todo\\'")) (undo-fu-session-global-mode))
4.7.7. Markdown
(use-package markdown-mode :ensure t :mode ("README\\.md\\.njk\\'" . gfm-mode) :init (setq markdown-command "multimarkdown | pandoc")) (add-to-list 'auto-mode-alist '("\\.njk\\'" . web-mode)) (add-to-list 'auto-mode-alist '("\\.\\(?:md\\|markdown\\|mkd\\|mdown\\|mkdn\\|mdwn\\)\\'" . markdown-mode)) (add-to-list 'auto-mode-alist '("README\\.md\\'" . gfm-mode))
4.7.8. CSV Mode
Emacs is one of the best editors for csv
files and can guess and set the separator.
(use-package csv-mode :ensure t) (add-hook 'csv-mode-hook 'csv-guess-set-separator) (add-to-list 'auto-mode-alist '("\\.csv\\'" . csv-mode))
4.7.9. HTML
https://github.com/netguy204/imp.el allows us to see the changes in the browser as you type.
- Install
simple-httpd
for additional functions - Enable the web server provided by
simple-httpd: ~M-x httpd-start
- Publish buffers by enabling the minor mode impatient-mode:
M-x impatient-mode
. And then point your browser to http://localhost:8080/imp/, select a buffer, and watch your changes appear as you type!
(use-package simple-httpd :ensure t) (use-package impatient-mode :ensure t)
4.7.10. Translation
Using lingva.ml
without tracking from Google using https://codeberg.org/martianh/lingva.el
(use-package lingva :ensure t :config (setq lingva-instance "https://lingva.lunar.icu/"))
4.7.11. Using templates
Using yasnippets
is a powerful way to ensure that I can improve the writing quality within Emacs.
(use-package yasnippet :ensure t :demand t :diminish yas-minor-mode :config (yas-global-mode 1) ) (use-package yasnippet-snippets :ensure t :after yasnippet)
Here are a bunch of snippets that I currently use with my configuration, and this is a growing list of snippets. Since org-mode
is my primary mode, I will first create a directory for it.
# Create ~/.config/emacs/config/yasnippet/snippets/org-mode folder if [ -d ~/.config/emacs/config/yasnippet/snippets/org-mode ] then echo "Org-mode snippets folder exists" else echo "Org-mode snippets folder does not exist" mkdir -p ~/.config/emacs/config/yasnippet/snippets/org-mode/ fi
- Blog for Getting On The Air
# -*- mode: snippet -*- # name: Hugo Frontmatter Gota # key: !!gota # -- #+date: $0 #+hugo_base_dir: ~/source/GetOnTheAir/ #+hugo_auto_set_lastmod: t #+options: author:nil
- Org Image Caption Attributes
# -*- mode: snippet -*- # name: Org Image Caption # key: !!oic # -- #+caption: ${1:Caption} #+name: ${2:Name} #+attr_html: width="${2:Width}px" alt="${3:Alt}" :align center #+attr_org: :width ${2:Width}px $0
- Org Center Image
# -*- mode: snippet -*- # name: Org Image Center # key: !!oci # -- #+begin_export hugo <style> .img-center{ margin-left: auto; margin-right: auto; max-width: ${1}%; } </style> $0 #+end_export
- Org Reveal New Slide
# -*- mode: snippet -*- # name: Reveal split slide # key: !!rebr # -- #+REVEAL: split $0
- Org Reveal do not add to ToC
# -*- mode: snippet -*- # name: Reveal no ToC # key: !!rentoc # -- :PROPERTIES: :UNNUMBERED: notoc :END: $0
- Org Reveal front matter for footnotes
# -*- mode: snippet -*- # name: Reveal # key: !!refn # -- #+REVEAL_MARGIN: 0.1 #+REVEAL_HEAD_PREAMBLE: <style type="text/css"> #+REVEAL_HEAD_PREAMBLE: .footpara { display: inline; } #+REVEAL_HEAD_PREAMBLE: .footdef { text-align: left; font-size: .6em;} #+REVEAL_HEAD_PREAMBLE: font-family: "Georgia", Georgia, serif; #+REVEAL_HEAD_PREAMBLE: </style> $0
- Org Reveal Title Slide
# -*- mode: snippet -*- # name: Reveal Title # key: !!ret # -- #+REVEAL_TITLE_SLIDE: ${1:HTMLTitle} $0
4.7.12. Writing aids
Improvement plain-text writing within Emacs
- Grammar
Highlights text based on a set of weasel-words, passive-voice and duplicate words, by using https://github.com/bnbeckwith/writegood-mode.
(use-package writegood-mode :ensure (:host github :repo "bnbeckwith/writegood-mode") :config (require 'writegood-mode) (global-set-key "\C-cg" 'writegood-mode) (writegood-weasels-turn-on) (writegood-passive-voice-turn-on) (writegood-duplicates-turn-on) (add-hook 'text-mode-hook 'writegood-mode))
- Distraction free writing
After considering both Darkroom and Olivetti, I have settled on using
writeroom
mode for distraction free writing in Emacs from https://github.com/joostkremers/writeroom-mode.;; Distraction free writing using writeroom-mode https://github.com/joostkremers/writeroom-mode (use-package writeroom-mode :ensure t) (with-eval-after-load 'writeroom-mode (define-key writeroom-mode-map (kbd "<C-M-left>") #'writeroom-decrease-width) (define-key writeroom-mode-map (kbd "<C-M-right>") #'writeroom-increase-width) (define-key writeroom-mode-map (kbd "<C-M-=>") #'writeroom-adjust-width)) (advice-add 'text-scale-adjust :after #'visual-fill-column-adjust) (setq writeroom-mode-line '(" " global-mode-string)) (setq writeroom-local-effects '(display-time-mode))
- Thesaurus
emacs-powerthesaurus
is a plug-in that integrates Emacs with the amazing powerthesaurus.org service for finding synonyms, antonyms, and related terms from https://github.com/SavchenkoValeriy/emacs-powerthesaurus(use-package powerthesaurus :ensure t)
- Search and replace
ripgrep
can be handled in a number of ways in Emacs, I already haveconsult-ripgrep
working, in addition I am using https://github.com/Wilfred/deadgrep for a different interface.(use-package deadgrep :ensure t) (global-set-key (kbd "<f5>") #'deadgrep)
4.7.13. Using calculator
(use-package calc :defer t)
4.8. Completion modules
After trying multiple combinations of completion frameworks, I have started using a set orderless
, corfu
, consult
, embark
, marginalia
, vertico
as the current set of completions that would work for me18.
4.8.1. Saving minibuffer history
I'd like to save history of the mini-buffer
too so that it becomes accessible.
(use-package savehist :ensure nil :hook (after-init . savehist-mode) :config (setq savehist-file (locate-user-emacs-file "savehist")) (setq history-length 100) (setq history-delete-duplicates t) (setq savehist-save-minibuffer-history t) (add-to-list 'savehist-additional-variables 'kill-ring))
4.8.2. Dynamic abbreviation
Using the in-built package for text completion, dabbrev
reads the contents of the buffer and then expands the text for possible matches.
(use-package dabbrev :ensure nil :commands (dabbrev-expand dabbrev-completion) :config (setq dabbrev-abbrev-char-regexp "\\sw\\|\\s_") (setq dabbrev-abbrev-skip-leading-regexp "[$*/=~']") (setq dabbrev-backward-only nil) (setq dabbrev-case-distinction 'case-replace) (setq dabbrev-case-fold-search nil) (setq dabbrev-case-replace 'case-replace) (setq dabbrev-check-other-buffers t) (setq dabbrev-eliminate-newlines t) (setq dabbrev-upcase-means-case-search t) (setq dabbrev-ignored-buffer-modes '(archive-mode image-mode doc-view-mode pdf-view-mode tags-table-mode)))
4.8.3. Abbreviations
Abbreviation mode from http://xahlee.info/emacs/emacs/emacs_abbrev_mode_tutorial.html, http://xahlee.info/emacs/emacs/emacs_hippie_expand_setup.html and https://www.masteringemacs.org/article/text-expansion-hippie-expand
(setq-default abbrev-mode t) (setq save-abbrevs 'silently) (bind-key "M-/" 'hippie-expand) (remove-hook 'save-some-buffers-functions #'abbrev--possibly-save) (setq hippie-expand-try-functions-list '(yas-hippie-try-expand try-expand-all-abbrevs try-complete-file-name-partially try-complete-file-name try-expand-dabbrev try-expand-dabbrev-from-kill try-expand-dabbrev-all-buffers try-expand-list try-expand-line try-complete-lisp-symbol-partially try-complete-lisp-symbol))
4.8.4. Minibuffer
Some customization of how the minibuffer
is handled to enable the completion frameworks and it is based on https://protesilaos.com/emacs/dotemacs#h:de61a607-0bdf-462b-94cd-c0898319590e, the details for which has been helpfully provided by Prot.
(use-package minibuffer :ensure nil :config (setq completions-format 'one-column) (setq completion-auto-help 'always) (setq completion-auto-select t) (setq completions-detailed t) (setq completion-show-inline-help t) (setq completions-max-height 48) (setq completions-header-format (propertize "%s candidates:\n" 'face 'bold-italic)) (setq completions-highlight-face 'completions-highlight) (setq minibuffer-completion-auto-choose t) (setq minibuffer-visible-completions t) (setq completion-styles '(basic substring initials flex orderless)) (setq completion-category-defaults nil) (setq completion-category-overrides '((file (styles . (basic partial-completion orderless))) (command (styles . (basic partial-completion orderless))) (bookmark (styles . (basic substring))) (library (styles . (basic substring))) (embark-keybinding (styles . (basic substring))) (imenu (styles . (basic substring orderless))) (consult-location (styles . (basic substring orderless))) (kill-ring (styles . (emacs22 orderless))) (eglot (styles . (emacs22 substring orderless))))))
- Editing mini-buffer
Sometimes you want to be able to do fancy things with the text that you're entering into the minibuffer. Sometimes you just want to be able to read it, especially when it comes to lots of text. This binds
C-M-e
in a minibuffer so that you can edit the contents of the minibuffer before submitting it.(use-package miniedit :ensure t :commands minibuffer-edit :init (miniedit-install))
4.8.5. Corfu
corfu
provides in-buffer text-completion with a pop-up. It works well with vertico
. The current candidates are shown in a pop-up below or above the point. The candidates can be selected by moving up and down. As recommended at https://github.com/minad/corfu, I also use cape
(https://github.com/minad/cape) a.k.a "Completion At Point Extensions" in conjunction to enhance functionality
(use-package corfu :ensure t :custom (corfu-auto t) (corfu-cycle t) (corfu-preselect 'prompt) (corfu-history-mode 1) (add-to-list 'savehist-additional-variables 'corfu-history) :config (setq corfu-popupinfo-delay (cons nil 1.0)) :bind (:map corfu-map ("TAB" . corfu-next) ([tab] . corfu-next) ("M-TAB" . corfu-previous) ([backtab] . corfu-previous)) :init (global-corfu-mode) (corfu-popupinfo-mode)) (use-package cape :ensure t :bind (("C-c p p" . completion-at-point) ("C-c p t" . complete-tag) ("C-c p d" . cape-dabbrev) ("C-c p h" . cape-history) ("C-c p f" . cape-file) ("C-c p k" . cape-keyword) ("C-c p s" . cape-symbol) ("C-c p a" . cape-abbrev) ("C-c p l" . cape-line) ("C-c p w" . cape-dict) ("C-c p \\" . cape-tex) ("C-c p _" . cape-tex) ("C-c p ^" . cape-tex) ("C-c p &" . cape-sgml) ("C-c p r" . cape-rfc1345)) :init (add-to-list 'completion-at-point-functions #'cape-dabbrev) (add-to-list 'completion-at-point-functions #'cape-abbrev) (add-to-list 'completion-at-point-functions #'cape-file) (add-to-list 'completion-at-point-functions #'cape-elisp-block) (add-to-list 'completion-at-point-functions #'cape-history) (add-to-list 'completion-at-point-functions #'cape-keyword) (add-to-list 'completion-at-point-functions #'cape-tex))
4.8.6. Consult
consult
provides a way by which you can search, filter, preview and select entries based on the lists provided by completion-at-point. I have also included a few additional consult
packages.
consult-projectile
to help withprojectile
for project bindingsconsult-yasnippet
to help with expandyasnippet
(use-package consult :ensure t :hook (completion-list-mode . consult-preview-at-point-mode) :bind ( ("C-x M-:" . consult-complex-command) ("C-x b" . consult-buffer) ("C-x 4 b" . consult-buffer-other-window) ("C-x 5 b" . consult-buffer-other-frame) ("C-x r b" . consult-bookmark) ("C-x p b" . consult-project-buffer) ("M-y" . consult-yank-pop) ("M-s d" . consult-find) ("M-s D" . consult-locate) ("M-s g" . consult-grep) ("M-s G" . consult-git-grep) ("M-s r" . consult-ripgrep) ("M-s l" . consult-line) ("M-s L" . consult-line-multi) ("M-s k" . consult-keep-lines) ("M-s u" . consult-focus-lines) :map isearch-mode-map ("M-e" . consult-isearch-history) ("M-s e" . consult-isearch-history) ("M-s l" . consult-line) ("M-s L" . consult-line-multi) :map minibuffer-local-map ("M-s" . consult-history) ("M-r" . consult-history) :map consult-narrow-map ("?" . consult-narrow-help)) :config (setq consult-line-numbers-widen t) (setq consult-async-min-input 3) (setq consult-async-input-debounce 0.5) (setq consult-async-input-throttle 0.8) (setq consult-narrow-key nil) (setq consult-find-args (concat "find . -not ( " "-path */.git* -prune " "-or -path */.cache* -prune )")) (setq consult-preview-key 'any) (setq consult-project-function nil) (setq consult-narrow-key "<") (autoload 'projectile-project-root "projectile") (add-to-list 'consult-mode-histories '(vc-git-log-edit-mode . log-edit-comment-ring)) (require 'consult-imenu) (with-eval-after-load 'pulsar (setq consult-after-jump-hook nil) (dolist (fn '(pulsar-recenter-top pulsar-reveal-entry)) (add-hook 'consult-after-jump-hook fn)))) (use-package consult-projectile :ensure t) (use-package consult-yasnippet :ensure t)
4.8.7. Embark
The best way to describe embark
is to think of it as a contextual execution of actions. While I have the basic covered here, I will have to learn how to harness the embark
to automate some tasks.
(use-package embark :ensure t :bind (("C-." . embark-act) ("C-;" . embark-dwim) ("C-h B" . embark-bindings)) :config (setq prefix-help-command #'embark-prefix-help-command) (setq embark-confirm-act-all nil) (setq embark-mixed-indicator-both nil) (setq embark-mixed-indicator-delay 1.0) (setq embark-indicators '(embark-mixed-indicator embark-highlight-indicator)) (setq embark-verbose-indicator-nested nil) (setq embark-verbose-indicator-buffer-sections '(bindings)) (setq embark-verbose-indicator-excluded-actions '(embark-cycle embark-act-all embark-collect embark-export embark-insert))) (use-package embark-consult :ensure t :after consult :hook (embark-collect-mode . consult-preview-at-point-mode))
4.8.8. Vertico
Using vertico
with orderless
is a powerful combination. I am doing the following (based on the configuration provided in the sample),
- Enabling "multiform" mode in order to get the desired manner for each type and similar to
Ido
- Using
vertico-directory
to navigate directories in a manner similar toIdo
(use-package vertico :ensure t :hook (after-init . vertico-mode) :config (setq vertico-scroll-margin 0) (setq vertico-count 5) (setq vertico-resize t) (setq vertico-cycle t) (vertico-multiform-mode 1) ) (use-package emacs :init (defun crm-indicator (args) (cons (format "[CRM%s] %s" (replace-regexp-in-string "\\`\\[.*?]\\*\\|\\[.*?]\\*\\'" "" crm-separator) (car args)) (cdr args))) (advice-add #'completing-read-multiple :filter-args #'crm-indicator) (setq minibuffer-prompt-properties '(read-only t cursor-intangible t face minibuffer-prompt)) (add-hook 'minibuffer-setup-hook #'cursor-intangible-mode) (setq enable-recursive-minibuffers t)) (use-package vertico-directory :after vertico :ensure nil :bind (:map vertico-map ("RET" . vertico-directory-enter) ("DEL" . vertico-directory-delete-char) ("M-DEL" . vertico-directory-delete-word)) :hook (rfn-eshadow-update-overlay . vertico-directory-tidy)) (setq vertico-multiform-categories '((buffer flat (vertico-cycle . t))))
4.8.9. Orderless
orderless
is a pattern matching package for parsing user input and then parsing it appropriately. This configuration for orderless
is from https://github.com/minad/consult/wiki#minads-orderless-configuration, and the minibuffer completion removes ?
and SPC
to ensure it doesn't have an issue with other regexp constructs.
(use-package orderless :ensure t :config (defun +orderless--consult-suffix () "Regexp which matches the end of string with Consult tofu support." (if (and (boundp 'consult--tofu-char) (boundp 'consult--tofu-range)) (format "[%c-%c]*$" consult--tofu-char (+ consult--tofu-char consult--tofu-range -1)) "$")) (defun +orderless-consult-dispatch (word _index _total) (cond ((string-suffix-p "$" word) `(orderless-regexp . ,(concat (substring word 0 -1) (+orderless--consult-suffix)))) ((and (or minibuffer-completing-file-name (derived-mode-p 'eshell-mode)) (string-match-p "\\`\\.." word)) `(orderless-regexp . ,(concat "\\." (substring word 1) (+orderless--consult-suffix)))))) (orderless-define-completion-style +orderless-with-initialism (orderless-matching-styles '(orderless-initialism orderless-literal orderless-regexp))) (setq completion-styles '(orderless basic) completion-category-defaults nil completion-category-overrides '((file (styles partial-completion)) (command (styles +orderless-with-initialism)) (variable (styles +orderless-with-initialism)) (symbol (styles +orderless-with-initialism))) orderless-component-separator #'orderless-escapable-split-on-space orderless-style-dispatchers (list #'+orderless-consult-dispatch #'orderless-affix-dispatch)) :bind ( :map minibuffer-local-completion-map ("SPC" . nil) ("?" . nil)) )
4.8.10. Marginalia
Completion candidates are a great feature in Emacs, for people like me though, having annotations and notes next to the completion candidate is where it becomes useful. marginalia
does this specific function of providing a brief description. I have also included nerd-icons-completion
in conjunction to ensure better styling.
(use-package marginalia :ensure t :defer 1 :bind (("M-A" . marginalia-cycle) :map minibuffer-local-map ("M-A" . marginalia-cycle)) :config (setq marginalia-max-relative-age 0) (marginalia-mode t)) (use-package nerd-icons-completion :ensure t :after marginalia :config (nerd-icons-completion-mode t) (add-hook 'marginalia-mode-hook #'nerd-icons-completion-marginalia-setup))
4.9. Shell customization
A few modifications,
- Paths for shell execution are fetched and stored
Eshell
is customized to start from the starting of the pageShell commands can now be executed using https://github.com/xenodium/dwim-shell-command
(use-package exec-path-from-shell :ensure t :config (when (memq window-system '(mac ns x)) (exec-path-from-shell-initialize))) (require 'eshell) (require 'em-smart) (setq eshell-where-to-jump 'begin) (setq eshell-review-quick-commands nil) (setq eshell-smart-space-goes-to-end t) (use-package dwim-shell-command :ensure t :bind (([remap shell-command] . dwim-shell-command) :map dired-mode-map ([remap dired-do-async-shell-command] . dwim-shell-command) ([remap dired-do-shell-command] . dwim-shell-command) ([remap dired-smart-shell-command] . dwim-shell-command))) (setq dired-dwim-target t)
4.10. Reference management
Emacs and Zotero Integration from https://github.com/emacsmirror/zotxt https://github.com/egh/zotxt & https://github.com/egh/zotxt-emacs, requires the following within Zotero for this to work well
- https://github.com/retorquere/zotero-better-bibtex
- https://github.com/jlegewie/zotfile
- https://github.com/egh/zotxt
(use-package zotxt :ensure t)
4.11. PDF & EPUBs
Handling different formats such as PDFs and EPUBs for now
4.11.1. PDF Handling
Managing PDFs with PDFTools (https://pdftools.wiki & https://github.com/vedang/pdf-tools), and enable the view mode when a PDF is opened.
(use-package pdf-tools :ensure t :mode (("\\.pdf$" . pdf-view-mode)) :hook ((pdf-view-mode . auto-revert-mode) (pdf-view-mode . (lambda () cua-mode 0)) (pdf-view-mode . pdf-view-midnight-minor-mode) (pdf-view-mode . pdf-annot-minor-mode)) :config (setq-default pdf-view-display-size 'fit-width))
4.11.2. EPUBs
Handing Epubs from https://depp.brause.cc/nov.el/
(use-package nov :ensure t) (add-to-list 'auto-mode-alist '("\\.epub\\'" . nov-mode)) ;; Customize font (defun my-nov-font-setup () (face-remap-add-relative 'variable-pitch :family "EB Garamond" :height 1.0)) (add-hook 'nov-mode-hook 'my-nov-font-setup) ;; Text width (setq nov-text-width 80) (setq visual-fill-column-center-text t) (add-hook 'nov-mode-hook 'visual-line-mode) (add-hook 'nov-mode-hook 'visual-fill-column-mode)
4.11.3. Calibre
Handling Calibre library from https://github.com/chenyanming/calibredb.el and assumes pdf-tools
and nov.el
are installed, and the handling is from: https://dindi.garjola.net/calibredb-view.html
(use-package calibredb :ensure t :defer t :config (setq calibredb-root-dir "~/Calibre") (setq calibredb-db-dir (expand-file-name "metadata.db" calibredb-root-dir)) (setq calibredb-library-alist '(("~/Calibre"))) (setq calibredb-id-width 4) (setq calibredb-comment-width 0) (setq calibredb-tag-width 0) (setq calibredb-size-show t) (setq calibredb-format-width 12) (setq calibredb-format-all-the-icons t)) (defun bmp/calibredb-open-file-with-emacs (&optional candidate) "Open file with Emacs. Optional argument CANDIDATE is the selected item." (interactive "P") (unless candidate (setq candidate (car (calibredb-find-candidate-at-point)))) (find-file (calibredb-get-file-path candidate t)))
4.12. LaTeX
Based on a couple of posts by Michael Neuper and Karthink,
- Format LaTeX strings with
calc
- Use
cdlatex
to deal with AUCTeX and Emacs - Use
pdf-tools
to as the previewer and auto-update the view when the TeX file is modified/recompiled
(use-package auctex :ensure (:pre-build (("./autogen.sh") ("./configure" "--without-texmf-dir" "--with-packagelispdir=./" "--with-packagedatadir=./") ("make")) :build (:not elpaca--compile-info) ;; Make will take care of this step :files ("*.el" "doc/*.info*" "etc" "images" "latex" "style") :version (lambda (_) (require 'tex-site) AUCTeX-version))) (use-package latex :ensure nil :hook ((LaTeX-mode . prettify-symbols-mode)) :bind (:map LaTeX-mode-map ("C-S-e" . latex-math-from-calc)) :config ;; Format math as a Latex string with Calc (defun latex-math-from-calc () "Evaluate `calc' on the contents of line at point." (interactive) (cond ((region-active-p) (let* ((beg (region-beginning)) (end (region-end)) (string (buffer-substring-no-properties beg end))) (kill-region beg end) (insert (calc-eval `(,string calc-language latex calc-prefer-frac t calc-angle-mode rad))))) (t (let ((l (thing-at-point 'line))) (end-of-line 1) (kill-line 0) (insert (calc-eval `(,l calc-language latex calc-prefer-frac t calc-angle-mode rad)))))))) (use-package preview-latex :ensure nil :after latex :hook ((LaTeX-mode . preview-larger-previews)) :config (defun preview-larger-previews () (setq preview-scale-function (lambda () (* 1.25 (funcall (preview-scale-from-face))))))) (use-package cdlatex :ensure t :hook (LaTeX-mode . turn-on-cdlatex) :bind (:map cdlatex-mode-map ("<tab>" . cdlatex-tab))) (add-hook 'LaTeX-mode-hook #'turn-on-cdlatex) (add-hook 'latex-mode-hook #'turn-on-cdlatex) (setq TeX-view-program-selection '((output-pdf "PDF Tools")) TeX-source-correlate-start-server t) (add-hook 'TeX-after-compilation-finished-functions #'TeX-revert-document-buffer) (setq +latex-viewers '(pdf-tools)) (setq TeX-PDF-mode t) (setq TeX-view-program-selection '((output-pdf "PDF Tools"))) (add-hook 'TeX-after-compilation-finished-functions #'TeX-revert-document-buffer) (setq TeX-engine 'xetex)
4.13. TODO Typst
Typst is the latest open-source tool for typesetting and page layout19, and it seems to be fairly easier to typeset documents and handle them. Pandoc seems to be an intuitive way to convert between LaTeX documents and Typst documents20.
(use-package typst-ts-mode :ensure ( :host sourcehut :repo "meow_king/typst-ts-mode") :custom (typst-ts-mode-watch-options "--open") (typst-ts-mode-enable-raw-blocks-highlight t))
4.14. Handling audio & video
Use MPV through Emacs to handle audio and video.
(use-package mpv :ensure t)
4.15. Programming
This will be expanded over a period of time and need to explore eglot
and tree-sitter
to check which one works better for me.
4.15.1. Eglot
Emacs Polyglot is mostly preconfigured, here is an elementary configuration. In order to avoid a warning about eldoc
I have added the latest version from https://github.com/progfolio/elpaca/issues/236
- Added bash-language-server
- Added clangd llvm
- Added gopls
(use-package eglot :ensure t :hook (prog-mode . eglot-ensure) (sh-mode . eglot-ensure) (bash-ts-mode . eglot-ensure) (c-mode . eglot-ensure) (c++-mode . eglot-ensure) (go-mode . eglot-ensure) (rust-mode . eglot-ensure) (html-mode . eglot-ensure) (css-mode . eglot-ensure) :bind (:map eglot-mode-map ("C-c c a" . eglot-code-actions) ("C-c c o" . eglot-code-actions-organize-imports) ("C-c c r" . eglot-rename) ("C-c c f" . eglot-format) ("C-c c d" . eldoc)) :config (setq eglot-stay-out-of '(flymake)) (setq-default eglot-workspace-configuration '((:gopls . ((staticcheck . t) (matcher . "CaseSensitive"))))) ) (use-package jsonrpc :ensure t)
4.15.2. TODO Syntax checking
I'm using the default built-in flymake
for checking syntax and enabling vale integration with it.
(use-package flymake :ensure t :hook (prog-mode . flymake-mode) :bind (:map flymake-mode-map ("C-c ! n" . flymake-goto-next-error) ("C-c ! p" . flymake-goto-prev-error) ("C-c ! l" . flymake-show-buffer-diagnostics))) (use-package flymake-vale :ensure ( :host github :repo "tpeacock19/flymake-vale" ))
4.15.3. Adding documentation
Enable eldoc
across all Emacs with the latest version from https://github.com/progfolio/elpaca/issues/236#issuecomment-1879838229
(use-package eldoc :ensure t)
4.15.4. TODO Parsing programming languages
Using the built-in tree-sitter
for syntax and parsing and included ts-fold
to help with code folding.
(use-package treesit-auto :ensure t :custom (treesit-auto-install 'prompt) :config (treesit-auto-add-to-auto-mode-alist 'all) (global-treesit-auto-mode)) (use-package ts-fold :ensure (:host github :repo "emacs-tree-sitter/ts-fold")) (use-package ts-fold-indicators :ensure ( :host github :repo "emacs-tree-sitter/ts-fold") :config (add-hook 'tree-sitter-after-on-hook #'ts-fold-indicators-mode))
4.15.5. Languages & Frameworks
- Emacs ESS
Using Emacs with R using https://ess.r-project.org/Manual/ess.html
(use-package ess :ensure t :init (require 'ess-site) :config (setf (cdr (assoc 'ess-indent-with-fancy-comments ess-own-style-list)) nil) (setq ess-style 'OWN) (setq ess-indent-offset 4) ;; auto-width (setq ess-auto-width 'window) ;; Stop R repl eval from blocking emacs. (setq ess-eval-visibly 'nowait) ;; Syntax highlighting (setq ess-R-font-lock-keywords '((ess-R-fl-keyword:keywords . t) (ess-R-fl-keyword:constants . t) (ess-R-fl-keyword:modifiers . t) (ess-R-fl-keyword:fun-defs . t) (ess-R-fl-keyword:assign-ops . t) (ess-R-fl-keyword:%op% . t) (ess-fl-keyword:fun-calls . t) (ess-fl-keyword:numbers . t) (ess-fl-keyword:operators . t) (ess-fl-keyword:delimiters . t) (ess-fl-keyword:= . t) (ess-R-fl-keyword:F&T . t))))
- Python
Using
elpy
for Python(use-package elpy :ensure t :hook ((elpy-mode . flycheck-mode)) :config (setq elpy-modules (delq 'elpy-module-flymake elpy-modules)) (elpy-enable))
- GO
Using
go-mode
for Go(use-package go-mode :ensure t :config (add-to-list 'auto-mode-alist '("\\.go\\'" . go-mode)) ;; Set keybindings for Go commands :bind (("C-c C-g r" . go-remove-unused-imports) ("C-c C-g g" . go-goto-imports) ("C-c C-g f" . gofmt) ("C-c C-g k" . godoc) ("C-c C-g a" . go-import-add) ("C-c C-g d" . godef-describe)))
- Rust
Using
ruse-mode
(use-package rust-mode :ensure t :config (setq rust-format-on-save t) (add-hook 'rust-mode-hook (lambda () (prettify-symbols-mode))))
- Astro
Astro mode for
tree-sitter
;; (use-package astro-ts-mode ;; :ensure t ;; :config ;; (global-treesit-auto-mode))
4.16. Social Networking
I use Mastodon, Matrix and IRC through Emacs
4.16.1. Mastodon
Mastodon mode from https://codeberg.org/martianh/mastodon.el
(setq mastodon-toot-timestamp-format "%c" mastodon-toot--enable-completion-for-mentions "all" mastodon-toot--enable-custom-instance-emoji t mastodon-tl--enable-relative-timestamps nil mastodon-tl--show-avatars t mastodon-media--enable-image-caching t mastodon-media--avatar-height 30 mastodon-media--preview-max-height 200 mastodon-tl--enable-proportional-font t mastodon-toot-enable-completion t mastodon-tl--highlight-current-toot t) (use-package tp :ensure (:host "www.codeberg.org" :repo "martianh/tp.el")) (use-package mastodon :ensure (:host "www.codeberg.org" :repo "martianh/mastodon.el" :branch "main") :config (mastodon-discover)) (setq mastodon-instance-url "https://mastodon.sdf.org" mastodon-active-user "bmp") (defun bmp/mastodon-toot--download-emoji-from-instance (mastodon-instance-url) "Download custom emoji from MASTODON-INSTANCE-URL. Emoji images are stored in a subdir of `emojify-emojis-dir'. To use the downloaded emoji, run `bmp/mastodon-toot--download-emoji-from-instance'." (interactive "sEnter Mastodon instance URL: ") (let* ((url (concat mastodon-instance-url "/api/v1/custom_emojis")) (custom-emoji (mastodon-http--get-json url)) (mastodon-custom-emoji-dir (mastodon-toot--emoji-dir))) (if (not (file-directory-p emojify-emojis-dir)) (message "Looks like you need to set up emojify first.") (unless (file-directory-p mastodon-custom-emoji-dir) (make-directory mastodon-custom-emoji-dir t)) (mapc (lambda (x) (let ((url (alist-get 'url x)) (shortcode (alist-get 'shortcode x))) (when (and url shortcode (string-match-p "^[a-zA-Z0-9-_]+$" shortcode) (string-match-p "^[a-zA-Z]+$" (file-name-extension url))) (url-copy-file url (concat mastodon-custom-emoji-dir shortcode "." (file-name-extension url)) t)))) custom-emoji) (message "Custom emoji for %s downloaded to %s" mastodon-instance-url mastodon-custom-emoji-dir))))
4.16.2. Matrix
Connecting to Matrix using https://github.com/alphapapa/ement.el
(setq ement-save-sessions t ement-directory-item-indent 4 ement-room-left-margin-width 20 ement-room-timestamp-header-with-date-format "%A : %d-%m-%Y" ement-room-image-initial-height 0.4 ement-room-list-column-Name-max-width 32 ement-room-list-column-Topic-max-width 42) ;; Install Ement. (use-package ement :ensure t)
4.16.3. IRC
Since erc
had issues with respect to passwords, I had decided to switch to circe
from https://github.com/emacs-circe/circe. But I am now back on erc
after figuring out how to use passwords with it.
(use-package erc :ensure t :config (setq erc-interpret-mirc-color t) (setq erc-kill-buffer-on-part t) (setq erc-kill-queries-on-quit t) (setq erc-kill-server-buffer-on-quit t) (setq erc-query-display 'buffer) (erc-track-mode t) (setq erc-track-exclude-types '("JOIN" "NICK" "PART" "QUIT" "MODE" "324" "329" "332" "333" "353" "477")) (setq erc-save-buffer-on-part t) (erc-truncate-mode +1) (erc-spelling-mode 1) (erc-ring-mode t) (erc-fill-mode t) (erc-scrolltobottom-mode t) (setq erc-server-coding-system '(utf-8 . utf-8)) (setq erc-prompt-for-password nil) (setq erc-track-remove-disconnected-buffers t) (setq erc-nick "bharathmp") (setq erc-server "irc.libera.chat") (setq erc-autojoin-channels-alist '(("Libera.Chat" "#fedora" "#emacs-beginners" "#systemcrafters" "##fountainpens" "#gamingonlinux" "#poetry" "#hamradio" "##slackware" "##esperanto") ("irc.sdf.org" "#emacs" "#hamradio" "#mastodon"))) (use-package erc-hl-nicks :ensure t :after erc :config (add-to-list 'erc-modules 'hl-nicks)) (use-package erc-image :ensure t :after erc :config (setq erc-image-inline-rescale 300) (add-to-list 'erc-modules 'image)) (defun bmp/erc-clean-up () "Clean up dead ERC buffers." (interactive) (mapc #'kill-buffer (erc-buffer-list (lambda () (null (erc-server-process-alive))))) (erc-update-mode-line)) )
4.17. Personal Knowledge Management
I use of a combination of services to manage my PKM, I depend on org-roam
, and self-host Shaarli
, FreshRSS
, Wallabag
for my needs. In order to have a smooth work-flow I use the following Emacs packages to help me. At some point I would have to also find an interface for Shaarli
.
4.17.1. Note-taking
There are numerous options with Emacs to use, ranging from org-rr
, denote
and org-roam
to name a few. Based on my needs, I have decided to use org-roam
as the second brain with https://www.orgroam.com/manual.html#Introduction%5Bfn:11%5D.
- Install dependencies
- Custom templates from https://systemcrafters.net/build-a-second-brain-in-emacs/capturing-notes-efficiently/
- Load all tags instead of only the first tag for
org-roam-ui
from https://github.com/org-roam/org-roam-ui/issues/289#issuecomment-1830366138 - Lists subdirectories and current directory
- Custom
org-roam-capture
that creates a node in one of the subdirectory or in the base directory - Custom templates for creating nodes
- Sets the current tag, and filters by the tag
- Checks for nodes in subdirectories and base directory
- Insert a node in a subdirectory directly using
org-roam-node-insert
- Garbage collection
- Use
consult-org-roam
for greater flexibility - This is from https://gist.github.com/d12frosted/a60e8ccb9aceba031af243dff0d19b2e
- I was using the functions for
org-roam-bibtex
,bibtex-completion
andorg-ref
from https://github.com/progfolio/elpaca/issues/263, but have now reverted it to the basic definitions
(use-package f :ensure t) ;; (use-package emacsql ;; :ensure t) (use-package magit-section :ensure t) (use-package org-roam :ensure t :bind (("C-c n l" . org-roam-buffer-toggle) ("C-c n f" . org-roam-node-find) ("C-c n g" . org-roam-graph) ("C-c n i" . org-roam-node-insert) ("C-c n c" . org-roam-capture) ("C-c n j" . org-roam-dailies-capture-today) :map org-mode-map ("C-M-i" . completion-at-point)) :config (setq org-roam-completion-everywhere t) ;; (setq org-roam-database-connector 'sqlite) (setq org-roam-capture-templates '(("d" "Default" plain "%?" :if-new (file+head "%<%Y%m%d%H%M%S>-${slug}.org" "#+title: ${title}\n#+filetags: :${filetags}:\n\n") :unnarrowed t) ("b" "Book Notes" plain (file "~/Dropbox/Notes/Templates/BookNotesTemplate.org") :if-new (file+head "%<%Y%m%d%H%M%S>-${slug}.org" "#+title: ${title}\n#+filetags: :${filetags}:\n\n") :unnarrowed t) ("a" "Author" plain (file "~/Dropbox/Notes/Templates/AuthorTemplate.org") :if-new (file+head "%<%Y%m%d%H%M%S>-${slug}.org" "#+title: ${title}\n#+filetags: :${filetags}:\n\n") :unnarrowed t) ("p" "Podcast" plain (file "~/Dropbox/Notes/Templates/PodcastTemplate.org") :if-new (file+head "%<%Y%m%d%H%M%S>-${slug}.org" "#+title: ${title}\n#+filetags: :${filetags}:\n\n") :unnarrowed t) ("f" "RSSFeed" plain (file "~/Dropbox/Notes/Templates/RSSFeedTemplate.org") :if-new (file+head "%<%Y%m%d%H%M%S>-${slug}.org" "#+title: ${title}\n#+filetags: :${filetags}:\n\n") :unnarrowed t) )) (setq org-roam-node-display-template (concat "${title:*} " (propertize "${tags:10}" 'face 'org-tag))) (org-roam-db-autosync-mode) (require 'org-roam-protocol) (org-roam-setup)) (defun bmp/org-roam-subdirectories () (let ((org-roam-directory org-roam-directory)) (mapcar (lambda (abs-path) (file-relative-name abs-path org-roam-directory)) (directory-files-recursively org-roam-directory "\\`[^.]*\\'" 't)))) (defun bmp/org-roam-alldirectories () (let ((org-roam-directory org-roam-directory)) (cons org-roam-directory (mapcar (lambda (abs-path) (file-relative-name abs-path org-roam-directory)) (directory-files-recursively org-roam-directory "\\`[^.]*\\'" 't))))) (defun bmp/org-roam-capture () (interactive) (let* ((directory (completing-read "Subdirectory: " (bmp/org-roam-subdirectories))) (org-roam-directory (expand-file-name directory org-roam-directory))) (setq bmp/org-roam-current-tag directory) (org-roam-capture- :node (org-roam-node-read nil (bmp/org-roam-filter-by-tag directory)) :templates bmp/org-roam-capture-templates))) (defun bmp/org-roam-capture-all () (interactive) (let* ((directory (completing-read "Subdirectory: " (bmp/org-roam-alldirectories))) (org-roam-directory (expand-file-name directory org-roam-directory))) (setq bmp/org-roam-current-tag directory) (org-roam-capture- :node (org-roam-node-read nil (bmp/org-roam-filter-by-tag directory)) :templates bmp/org-roam-capture-templates))) (defcustom bmp/org-roam-capture-templates '(("d" "default" plain "%?" :if-new (file+head "%<%Y%m%d%H%M%S>-${slug}.org" "#+title: ${title}\n#+filetags: :%(bmp/org-roam-get-current-tag):${filetags}:") :unnarrowed t) ("b" "Book Notes" plain (file "~/Dropbox/Notes/Templates/BookNotesTemplate.org") :if-new (file+head "%<%Y%m%d%H%M%S>-${slug}.org" "#+title: ${title}\n#+filetags: :%(bmp/org-roam-get-current-tag):${filetags}:\n\n") :unnarrowed t) ("a" "Author" plain (file "~/Dropbox/Notes/Templates/AuthorTemplate.org") :if-new (file+head "%<%Y%m%d%H%M%S>-${slug}.org" "#+title: ${title}\n#+filetags: :%(bmp/org-roam-get-current-tag):${filetags}:\n\n") :unnarrowed t) ("p" "Podcast" plain (file "~/Dropbox/Notes/Templates/PodcastTemplate.org") :if-new (file+head "%<%Y%m%d%H%M%S>-${slug}.org" "#+title: ${title}\n#+filetags: :%(bmp/org-roam-get-current-tag):${filetags}:\n\n") :unnarrowed t) ("r" "RSS" plain (file "~/Dropbox/Notes/Templates/RSSFeedTemplate.org") :if-new (file+head "%<%Y%m%d%H%M%S>-${slug}.org" "#+title: ${title}\n#+filetags: :%(bmp/org-roam-get-current-tag):${filetags}:\n\n") :unnarrowed t) ) "Templates for the creation of new entries within Org-roam.") (defun bmp/org-roam-get-current-tag () bmp/org-roam-current-tag) (defun bmp/org-roam-filter-by-tag (tag-name) (lambda (node) (member tag-name (org-roam-node-tags node)))) (defun bmp/org-roam-node-find () (interactive) (let* ((tags (bmp/org-roam-subdirectories)) (tag (completing-read "tag: " tags)) (org-roam-directory (expand-file-name tag org-roam-directory))) (setq bmp/org-roam-current-tag tag) (org-roam-node-find nil nil (bmp/org-roam-filter-by-tag tag) nil :templates bmp/org-roam-capture-templates))) (defun bmp/org-roam-node-find-all () (interactive) (let* ((tags (bmp/org-roam-alldirectories)) (tag (completing-read "tag: " tags)) (org-roam-directory (expand-file-name tag org-roam-directory))) (setq bmp/org-roam-current-tag tag) (if (equal tag "~/Dropbox/Notes/") (org-roam-node-find nil nil nil nil :templates bmp/org-roam-capture-templates) (org-roam-node-find nil nil (bmp/org-roam-filter-by-tag tag) nil :templates bmp/org-roam-capture-templates)))) (defun bmp/org-roam-insert-in-subdirectory () "Find an Org-roam node in a selected subdirectory and insert an 'id:' link to it." (interactive) (let* ((subdirectories (bmp/org-roam-alldirectories)) (selected-subdir (completing-read "Select a subdirectory: " subdirectories)) (org-roam-directory (expand-file-name selected-subdir org-roam-directory))) (let ((filter-fn (lambda (node) (and (string-prefix-p (file-truename org-roam-directory) (file-truename (org-roam-node-file node))) (org-roam-node-id node))))) (let ((org-roam-capture-templates `(("d" "default" plain "%?" :if-new (file+head "%<%Y%m%d%H%M%S>-${slug}.org" ,(format "#+title: ${title}\n#+filetags: :%s:${filetags}:\n\n" selected-subdir)) :unnarrowed t) ("b" "Book Notes" plain (file "~/Dropbox/Notes/Templates/BookNotesTemplate.org") :if-new (file+head "%<%Y%m%d%H%M%S>-${slug}.org" ,(format "#+title: ${title}\n#+filetags: :%s:${filetags}:\n\n" selected-subdir)) :unnarrowed t) ("a" "Author" plain (file "~/Dropbox/Notes/Templates/AuthorTemplate.org") :if-new (file+head "%<%Y%m%d%H%M%S>-${slug}.org" ,(format "#+title: ${title}\n#+filetags: :%s:${filetags}:\n\n" selected-subdir)) :unnarrowed t) ("p" "Podcast" plain (file "~/Dropbox/Notes/Templates/PodcastTemplate.org") :if-new (file+head "%<%Y%m%d%H%M%S>-${slug}.org" ,(format "#+title: ${title}\n#+filetags: :%s:${filetags}:\n\n" selected-subdir)) :unnarrowed t) ("f" "RSSFeed" plain (file "~/Dropbox/Notes/Templates/RSSFeedTemplate.org") :if-new (file+head "%<%Y%m%d%H%M%S>-${slug}.org" ,(format "#+title: ${title}\n#+filetags: :%s:${filetags}:\n\n" selected-subdir)) :unnarrowed t) ))) (org-roam-node-insert filter-fn))))) (global-set-key (kbd "C-c n b") 'bmp/org-roam-node-find-all) (global-set-key (kbd "C-c n n") 'bmp/org-roam-capture-all) (global-set-key (kbd "C-c n a") 'bmp/org-roam-insert-in-subdirectory) (setq org-roam-db-node-include-function (lambda () (not (member "ATTACH" (org-get-tags))))) (add-to-list 'display-buffer-alist '("\\*org-roam\\*" (display-buffer-in-side-window) (side . right) (slot . 0) (window-width . 0.33) (window-parameters . ((no-other-window . t) (no-delete-other-windows . t))))) (setq org-roam-db-gc-threshold most-positive-fixnum) (use-package org-download :ensure t :after org :bind (:map org-mode-map (("s-Y" . org-download-screenshot) ("s-y" . org-download-yank))) :config (add-hook 'dired-mode-hook 'org-download-enable) (setq org-download-heading-lvl nil) (setq-default org-download-heading-lvl nil) (setq-default org-download-image-dir (concat org-directory "images"))) (use-package bibtex-completion :ensure t) (use-package org-ref :ensure t :after org bibtex) (use-package org-roam-bibtex :ensure t :after org-roam org-ref) (use-package org-roam-ui :ensure (:host github :repo "org-roam/org-roam-ui" :branch "main" :files ("*.el" "out")) :after org-roam :config (setq org-roam-ui-sync-theme t org-roam-ui-follow t org-roam-ui-update-on-save t org-roam-ui-open-on-start nil)) (use-package consult-org-roam :ensure t :after org-roam :init (require 'consult-org-roam) (consult-org-roam-mode 1) :custom (consult-org-roam-grep-func #'consult-ripgrep) (consult-org-roam-buffer-narrow-key ?r) (consult-org-roam-buffer-after-buffers t) :config (consult-customize consult-org-roam-forward-links :preview-key "M-.") :bind ("C-c n e" . consult-org-roam-file-find) ("C-c n b" . consult-org-roam-backlinks) ("C-c n l" . consult-org-roam-forward-links) ("C-c n r" . consult-org-roam-search))
4.17.2. TODO Agenda and scheduling and habit tracking with PKM
- I currently use code from https://gist.github.com/d12frosted/a60e8ccb9aceba031af243dff0d19b2e
- In addition I am using
org-ql
to be able to query agenda quickly - The drawback of this approach is that the first time Emacs is launched, the agenda will not load, I need to find some
org-roam
node before the agenda loads properly - I have customized colours for priorities
- Set-up
org-habit
- Add
org-super-agenda
to customize the view - Based on the snippet at https://systemcrafters.net/build-a-second-brain-in-emacs/5-org-roam-hacks/#automatically-copy-or-move-completed-tasks-to-dailies, I have used an LLM to generate a function to capture all tasks into a daily.
(setq org-todo-keywords '((sequence "TODO(t)" "STARTED(s!)" "WAITING(w@/!)" "SOMEDAY(.)" "BLOCKED(k@/!)" "|" "CANCELLED(c!)" "DONE(d!)") (sequence "RESEARCH(r)" "|" "POSTED-SM(q!)" "BOOKMARKED(b!)" "ZETTELED(z!)" "COMPLETE(x!)") (sequence "TOLEARN(-)" "|" "LEARNING(l!)"))) (setq org-tag-alist '(("tarkam" . ?t) ("fov" . ?f) ("games" . ?g) ("hobby" . ?h) ("calligraphy" . ?c) ("writing" . ?w) ("esperanto" . ?e) ("tea" . ?a) ("ham" . ?r) ("sketching" . ?s) ("whittling" . ?n) ("exercise" . ?x) ("modelling" . ?m))) (setq org-todo-keyword-faces '(("TODO" . (:foreground "#a02f50" :weight bold)) ("STARTED" . (:foreground "#4f509f" :weight bold)) ("DONE" . (:foreground "#67bb97" :weight bold)) ("CANCELLED" . (:foreground "#67bb97" :weight bold)) ("WAITING" . (:foreground "#72aff0" :weight bold)) ("BLOCKED" . (:foreground "#df885f" :weight bold)) ("SOMEDAY" . (:foreground "#d389af" :weight bold)))) (setq org-priority-faces '((?A . (:background "#98c06f" :foreground "#333539")) (?B . (:background "#8fa4e5" :foreground "#333539")) (?C . (:background "#99bfd0" :foreground "#333539")))) (setq org-log-done 'time) (setq org-enforce-todo-dependencies t) (setq org-track-ordered-property-with-tag t) (setq org-agenda-dim-blocked-tasks t org-agenda-window-setup 'current-window org-agenda-current-time-string "" org-agenda-time-grid '((daily) () "" "") org-agenda-hide-tags-regexp ".*" org-agenda-block-separator nil org-agenda-compact-blocks t) (setq org-agenda-prefix-format '((agenda . " %?-2i %t ") (todo . " %i %-12:c") (tags . " %i %-12:c") (search . " %i %-12:c"))) (defun vulpea-project-p () "Return non-nil if current buffer has any todo entry. TODO entries marked as done are ignored, meaning the this function returns nil if current buffer contains only completed tasks." (seq-find (lambda (type) (eq type 'todo)) (org-element-map (org-element-parse-buffer 'headline) 'headline (lambda (h) (org-element-property :todo-type h))))) (defun vulpea-project-update-tag () "Update PROJECT tag in the current buffer." (when (and (not (active-minibuffer-window)) (vulpea-buffer-p)) (save-excursion (goto-char (point-min)) (let* ((tags (vulpea-buffer-tags-get)) (original-tags tags)) (if (vulpea-project-p) (setq tags (cons "project" tags)) (setq tags (remove "project" tags))) ;; cleanup duplicates (setq tags (seq-uniq tags)) ;; update tags if changed (when (or (seq-difference tags original-tags) (seq-difference original-tags tags)) (apply #'vulpea-buffer-tags-set tags)))))) (defun vulpea-buffer-p () "Return non-nil if the currently visited buffer is a note." (and buffer-file-name (eq major-mode 'org-mode) ; Check if it's an org file (string-prefix-p (expand-file-name (file-name-as-directory org-roam-directory)) (file-name-directory buffer-file-name)))) (defun vulpea-project-files () "Return a list of note files containing 'project' tag." (seq-uniq (seq-map #'car (org-roam-db-query [:select [nodes:file] :from tags :left-join nodes :on (= tags:node-id nodes:id) :where (like tag (quote "%\"project\"%"))])))) (defun vulpea-agenda-files-update (&rest _) "Update the value of `org-agenda-files'." (setq org-agenda-files (vulpea-project-files))) (add-hook 'find-file-hook #'vulpea-project-update-tag) (add-hook 'before-save-hook #'vulpea-project-update-tag) (advice-add 'org-agenda :before #'vulpea-agenda-files-update) (advice-add 'org-todo-list :before #'vulpea-agenda-files-update) (defun vulpea-buffer-tags-get () "Return filetags value in current buffer." (vulpea-buffer-prop-get-list "filetags" "[ :]")) (defun vulpea-buffer-tags-set (&rest tags) "Set TAGS in current If filetags value is already set, replace it." (if tags (vulpea-buffer-prop-set "filetags" (concat ":" (string-join tags ":") ":")) (vulpea-buffer-prop-remove "filetags"))) (defun vulpea-buffer-tags-add (tag) "Add a TAG to filetags in current buffer." (let* ((tags (vulpea-buffer-tags-get)) (tags (append tags (list tag)))) (apply #'vulpea-buffer-tags-set tags))) (defun vulpea-buffer-tags-remove (tag) "Remove a TAG from filetags in current buffer." (let* ((tags (vulpea-buffer-tags-get)) (tags (delete tag tags))) (apply #'vulpea-buffer-tags-set tags))) (defun vulpea-buffer-prop-set (name value) "Set a file property called NAME to VALUE in buffer If the property is already set, replace its value." (setq name (downcase name)) (org-with-point-at 1 (let ((case-fold-search t)) (if (re-search-forward (concat "^#\\+" name ":\\(.*\\)") (point-max) t) (replace-match (concat "#+" name ": " value) 'fixedcase) (while (and (not (eobp)) (looking-at "^[#:]")) (if (save-excursion (end-of-line) (eobp)) (progn (end-of-line) (insert "\n")) (forward-line) (beginning-of-line))) (insert "#+" name ": " value "\n"))))) (defun vulpea-buffer-prop-set-list (name values &optional separators) "Set a file property called NAME to VALUES in current buffer. VALUES are quoted and combined into single string using `combine-and-quote-strings'. If SEPARATORS is non-nil, it should be a regular expression matching text that separates, but is not part of, the substrings. If nil it defaults to `split-string-default-separators', normally \"[ \f\t\n\r\v]+\", and OMIT-NULLS is forced to t. If the property is already set, replace its value." (vulpea-buffer-prop-set name (combine-and-quote-strings values separators))) (defun vulpea-buffer-prop-get (name) "Get a buffer property called NAME as a string." (org-with-point-at 1 (when (re-search-forward (concat "^#\\+" name ": \\(.*\\)") (point-max) t) (buffer-substring-no-properties (match-beginning 1) (match-end 1))))) (defun vulpea-buffer-prop-get-list (name &optional separators) "Get a buffer property NAME as a list using SEPARATORS. If SEPARATORS is non-nil, it should be a regular expression matching text that separates, but is not part of, the substrings. If nil it defaults to `split-string-default-separators', normally \"[ \f\t\n\r\v]+\", and OMIT-NULLS is forced to t." (let ((value (vulpea-buffer-prop-get name))) (when (and value (not (string-empty-p value))) (split-string-and-unquote value separators)))) (defun vulpea-buffer-prop-remove (name) "Remove a buffer property called NAME." (org-with-point-at 1 (when (re-search-forward (concat "\\(^#\\+" name ":.*\n?\\)") (point-max) t) (replace-match "")))) (use-package org-ql :ensure t) (use-package org-super-agenda :ensure t :config (org-super-agenda-mode) (setq org-super-agenda-groups '((:name " Today" :deadline today :date today :scheduled today :order 1 :face 'warning) (:name " Upcoming Deadlines" :deadline future :order 2) (:name " Work tasks" :tag ("tarkam" "fov") :order 4 :scheduled (nil today future)) )))
4.17.3. Read later
I self-host Wallabag
as a service to store articles to read later from https://github.com/chenyanming/wallabag.el.
(use-package request :ensure t) (use-package emacsql-sqlite :ensure (:host github :repo "magit/emacsql")) ;; :ensure t) (use-package wallabag :defer t :ensure (:host github :repo "chenyanming/wallabag.el" :files ("*.el" "*.alist" "*.css")) :config (let ((host (bmp/lookup-password :host "wallabag-host-pop"))) (setq wallabag-host (if host (format "%s" host) ""))) (setq wallabag-username "bmp") (setq wallabag-password (bmp/lookup-password :host "wallabag-pwd")) (setq wallabag-clientid (format "%s" (bmp/lookup-password :host "wallabag-clientid"))) (setq wallabag-secret (format "%s" (bmp/lookup-password :host "wallabag-secret"))))
4.17.4. RSS Feeds
elfeed
is the best tool for reading RSS feeds within Emacs, and when I combine it with elfeed-protocol
, it ticks all the boxes for my use-case.
- Fix error about
avl-tree
: https://github.com/skeeto/elfeed/issues/509 - Function to add entry into Wallabag from Elfeed
(use-package elfeed :ensure t :bind ("C-x w" . elfeed)) (define-advice elfeed-search--header (:around (oldfun &rest args)) (if elfeed-db (apply oldfun args) "No database loaded yet")) (use-package elfeed-protocol :ensure t :demand t :after elfeed :config (setq elfeed-use-curl t) (elfeed-set-timeout 36000) (setq elfeed-log-level 'debug) (toggle-debug-on-error) (setq elfeed-curl-extra-arguments '("--insecure")) (setq elfeed-protocol-fever-update-unread-only t) (setq elfeed-protocol-fever-fetch-category-as-tag t) (let* ((api-url (format "%s" (bmp/lookup-password :host "freshrss-user-url"))) (url (format "%s" (bmp/lookup-password :host "freshrss-url")))) (setq elfeed-protocol-feeds `((,api-url :api-url ,url :password (bmp/lookup-password :host "freshrss-api-pwd"))))) (elfeed-protocol-enable)) (defun bmp/wallabag-add-from-elfeed () "Add current Elf Feed entry URL to Wallabag." (interactive) (let ((entry (elfeed-search-selected :single))) (if entry (let ((url (elfeed-entry-link entry))) (if url (progn (unless (get-buffer "*wallabag*") (wallabag)) (wallabag-add-entry url "")) (message "No URL found in current Elf Feed entry."))) (message "No Elf Feed entry selected.")))) (eval-after-load 'elfeed '(define-key elfeed-search-mode-map (kbd "C-c w") #'bmp/wallabag-add-from-elfeed))
4.18. TODO Provide Emacs externally
This is based the code provided by Prot and helps to access Emacs anywhere in the system.
(defun prot-window-delete-popup-frame (&rest _) "Kill selected selected frame if it has parameter `prot-window-popup-frame'. Use this function via a hook." (when (frame-parameter nil 'prot-window-popup-frame) (delete-frame))) (defmacro prot-window-define-with-popup-frame (command) "Define interactive function which calls COMMAND in a new frame. Make the new frame have the `prot-window-popup-frame' parameter." `(defun ,(intern (format "prot-window-popup-%s" command)) () ,(format "Run `%s' in a popup frame with `prot-window-popup-frame' parameter. Also see `prot-window-delete-popup-frame'." command) (interactive) (let ((frame (make-frame '((prot-window-popup-frame . t))))) (select-frame frame) (switch-to-buffer " prot-window-hidden-buffer-for-popup-frame") (condition-case nil (call-interactively ',command) ((quit error user-error) (delete-frame frame)))))) (declare-function org-capture "org-capture" (&optional goto keys)) (defvar org-capture-after-finalize-hook) ;;;###autoload (autoload 'prot-window-popup-org-capture "prot-window") (prot-window-define-with-popup-frame org-capture) (add-hook 'org-capture-after-finalize-hook #'prot-window-delete-popup-frame)
4.19. After-init hooks
Since I use elpaca
, there are some after-init hooks that can be run based on https://github.com/progfolio/elpaca/wiki/after-init-hook%3F-emacs-startup-hook%3F,
- This ensures that
elpaca-after-init-hook
only runs once - Enable
vertico
forjinx
- Enable
repeat-mode
- Enable
dwim-shell-commands
;;END OF INIT FILE (setq elpaca-after-init-time (or elpaca-after-init-time (current-time))) (elpaca-wait) (require 'dwim-shell-commands) (vertico-multiform-mode) (add-to-list 'vertico-multiform-categories '(jinx grid (vertico-grid-annotate . 20))) (repeat-mode)
Footnotes:
The first time I had appreciated the power of such a paradigm was when I used IPython (which later became Jupyter), and when using Sweave to integrate R code into LaTeX.
It has sometimes been far more useful than Google Fu.
This has lead to proliferation of a number of starter kits, etc.
Ensures that file handling through different devices/applications does not corrupt the file.
A bunch of these customisation come from https://protesilaos.com/codelog/2023-06-28-emacs-mark-register-basics/
The origins of the term 'porcelain' in git that is aimed at exposing some high level information to users. The origin of the term is sometime attributed to a mail thread with Mike Taht and Linus Torvalds. There is a lot of material available on-line describing the differences between porcelain and plumbing.
As always mailing-lists are a treasure trove, I encountered this discussion at https://lists.gnu.org/archive/html/emacs-orgmode/2009-07/msg00769.html
There a number of wonderful ways to use these frameworks, one stand-out guide is by Karthink at https://karthinks.com/software/fifteen-ways-to-use-embark/
It has a guide for LaTeX users on their site at https://typst.app/docs/guides/guide-for-latex-users/
The online demo of Pandoc at https://pandoc.org/try/ is a good place to try it.