Forgemacs' Literate Emacs Configuration

Table of Contents

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 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,

  1. An initial shell-script that creates the required folder structure
  2. A bog-standard early-init.el with some custom configuration
  3. An init.el file with the configuration
  4. 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
    

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/snippets
    mkdir -p ~/.config/emacs/abbrevs
    cp gnu_color.webp ~/.config/emacs/
    cp .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. Setting comp-deferred-compilation and native-comp-async-report-warnings-errors to nil, 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.7)
(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 in dashboard
  • 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. In addition, I have added casual-info to help with commands in the Emacs Info-mode.

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

(use-package casual-info
  :ensure t
  :bind (:map Info-mode-map ("C-o" . 'casual-info-tmenu)))

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.

  1. 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)))
    
    
  2. 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 and kind-icon to for speed and reliability
  • I have removed the ac-emoji-setup hook for markdown-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 and using casual-buffer to create a transient menu for ibuffer

(use-package bufler
  :ensure t
  :config
  (setq bufler-mode +1))

(use-package ibuffer
  :hook (ibuffer-mode . ibuffer-auto-mode)
  :defer t)

(use-package casual-ibuffer
  :ensure t
  :bind (:map
         ibuffer-mode-map
         ("C-o" . casual-ibuffer-tmenu)
         ("F" . casual-ibuffer-filter-tmenu)
         ("s" . casual-ibuffer-sortby-tmenu)
         ("<double-mouse-1>" . ibuffer-visit-buffer)
         ("M-<double-mouse-1>" . ibuffer-visit-buffer-other-window)
         ("{" . ibuffer-backwards-next-marked)
         ("}" . ibuffer-forward-next-marked)
         ("[" . ibuffer-backward-filter-group)
         ("]" . ibuffer-forward-filter-group)
         ("$" . ibuffer-toggle-filter-group))
  :after (ibuffer))

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/, added casual-dired from https://github.com/kickingvegas/casual-dired to enable a better menu

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

(use-package casual-dired
:ensure t
:bind (:map dired-mode-map
            ("C-o" . #'casual-dired-tmenu)))

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 using casual-bookmarks to enable a menu for bookmarks.

(use-package bookmark
  :ensure nil
  :defer t)

(use-package casual-bookmarks
  :ensure t
  :bind (:map bookmark-bmenu-mode-map
              ("C-o" . casual-bookmarks-tmenu)
              ("S" . casual-bookmarks-sortby-tmenu)
              ("J" . bookmark-jump))
  :after (bookmark))

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.

(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

  1. Export to Markdown
    (use-package ox-gfm
      :ensure t
      :commands (org-gfm-export-as-markdown org-gfm-export-to-markdown)
      :after org
      )
    
  2. Presentation in HTML 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 then org-re-reveal to enable org-re-reveal in the exporter (i.e org-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")
      )
    
  3. Templates
    (use-package templatel
      :ensure t)
    

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
  :mode ("~/.emacs.d/snippets/" . snippet-mode)
  :diminish yas-minor-mode
  :config
  (yas-load-directory "~/.emacs.d/snippets/")
  (yas-global-mode 1)
  )

(use-package yasnippet-snippets
  :ensure t
  :after yasnippet)

4.7.12. Writing aids

Improvement plain-text writing within Emacs

  1. 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))
    
  2. 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))
    
  3. 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)
    
  4. Search and replace

    ripgrep can be handled in a number of ways in Emacs, I already have consult-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)
    
  5. Adding menu for isearch

    casual-isearch to enable a transient menu for isearch

    (use-package casual-isearch
      :ensure t
      :bind (:map isearch-mode-map ("C-o" . casual-isearch-tmenu)))
    

4.7.13. Using calculator

Enable casual-calc menu for the built-in calculator

(use-package calc
  :defer t)

(use-package casual-calc
  :ensure t
  :bind (:map
         calc-mode-map
         ("C-o" . casual-calc-tmenu)
         :map
         calc-alg-map
         ("C-o" . casual-calc-tmenu))
  :after (calc))

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))))))
  1. 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 with projectile for project bindings
  • consult-yasnippet to help with expand yasnippet
(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 to Ido
(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 page
  • Shell 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

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

(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)
  :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))
  (add-to-list 'eglot-server-programs
               '((sh-mode bash-ts-mode) . ("bash-language-server" "start"))
               '((c-ts-mode c++-ts-mode c-mode c++-mode) "clangd-17")
               )
  (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.

(use-package treesit-auto
  :ensure t
  :custom
  (treesit-auto-install 'prompt)
  :config
  (treesit-auto-add-to-auto-mode-alist 'all)
  (global-treesit-auto-mode))

4.15.5. Languages & Frameworks

  1. 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))))
    
  2. 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))
    
  3. 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)))
    
  4. 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))))
    
  5. 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 mastodon
  :ensure (:host "www.codeberg.org" :repo "martianh/mastodon.el" :branch "develop")
  :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)) ; create parent directories
      (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.

(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, scheduling and habit tracking with PKM

(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!)")
        (sequence "HABIT(h)" "|" "DONE(p!)")))

(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-habit-show-all-today t
      org-habit-show-habits-only-for-today t
      org-habit-graph-column 80
      org-habit-preceding-days 28
      org-habit-following-days 7)


(setq org-agenda-prefix-format '(
                                 (agenda . "  %?-2i %t ")
                                 (todo . " %i %-12:c")
                                 (tags . " %i %-12:c")
                                 (search . " %i %-12:c")))

(use-package org-habit-stats
  :ensure t
  :hook ('org-after-todo-state-change-hook 'org-habit-stats-update-properties))

;; (defun bmp/org-roam-copy-todo-to-today-after-any-state-change ()
;;   "Copy or update the current TODO subtree in today's file after any TODO state change."
;;   (let ((org-refile-keep t)
;;         (current-heading (org-get-heading t t t t))  ;; Get the current heading text
;;         (org-roam-dailies-capture-templates
;;          '(("t" "tasks" entry "* TODO %?"
;;             :if-new (file+head "%<%Y-%m-%d>.org" "#+title: %<%Y-%m-%d>\n"))))
;;         today-file
;;         pos)

;;     ;; Copy the current subtree at point
;;     (org-copy-subtree)

;;     (save-window-excursion
;;       ;; Capture today's file
;;       (org-roam-dailies--capture (current-time) t)
;;       (setq today-file (buffer-file-name))
;;       (setq pos (point)))

;;     ;; Only proceed if the current file is different from today-file
;;     (unless (equal (file-truename today-file)
;;                    (file-truename (buffer-file-name)))
;;       (with-current-buffer (find-file-noselect today-file)
;;         (goto-char (point-min))  ;; Start at the beginning of the file
;;         (if (re-search-forward (regexp-quote current-heading) nil t)
;;             ;; If the heading exists, move to it and update its state
;;             (progn
;;               (org-back-to-heading)
;;               (org-todo org-state))  ;; Update the TODO state to the current state
;;           ;; If the heading doesn't exist, add the copied subtree
;;           (goto-char (point-max))    ;; Go to the end of the file
;;           (newline)                  ;; Ensure we are on a new line
;;           (org-yank)                 ;; Paste the copied subtree
;;           ;; Promote the subtree to level 1 heading
;;           (let ((current-level (org-current-level)))
;;             (when current-level
;;               (dotimes (_ (1- current-level)) ;; Promote until it becomes level 1
;;                 (org-promote-subtree)))))))))

;; (add-hook 'org-after-todo-state-change-hook 'bmp/org-roam-copy-todo-to-today-after-any-state-change)

(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 " Habits"
                 :habit t
                 :order 3)
          (:name " Work tasks"
                 :tag ("tarkam" "fov")
                 :order 4
                 :scheduled (nil today future))
          )))

(use-package casual-agenda
  :ensure (
           :host github
           :repo "kickingvegas/casual-agenda")
  :bind (:map
         org-agenda-mode-map
         ("C-o" . casual-agenda-tmenu))
  :after (org-agenda))


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 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.

(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. 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 for jinx
  • 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:

1

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.

9

This time around, I have finally figured out a system that hopefully will make it sticky. A big part of this reason is Org Mode, and the recent switch back to Linux with a Framework laptop.

10

It has sometimes been far more useful than Google Fu.

12

This has lead to proliferation of a number of starter kits, etc.

13

Ensures that file handling through different devices/applications does not corrupt the file.

15

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.

17

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

18

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/

19

It has a guide for LaTeX users on their site at https://typst.app/docs/guides/guide-for-latex-users/

20

The online demo of Pandoc at https://pandoc.org/try/ is a good place to try it.

Author: Bharath M. Palavalli

Created: 2024-09-18 Wed 20:11

Validate