This is (will be) my Emacs literate configuration file. A self
contained file with all my configuration is useful for documentation
purposes. It will be modeled using the technique described by
Protesilaos for his own Emacs config file:
<https://protesilaos.com/emacs/dotemacs>.

This method consists in generating all files /a priori/, after modifying
this file, and *not* at load time, as that would be too slow.

#+begin_src emacs-lisp :tangle no :results none
(org-babel-tangle)
#+end_src

* Overview of files and directories

- =early-init.el=: quoting the [[https://www.gnu.org/software/emacs/manual/html_node/emacs/Early-Init-File.html][Emacs documentation]], this file is "loaded
  before the package system and GUI is initialized, so in it you can
  customize variables that affect the package initialization process"
- =init.el=: the skeleton of my configuration framework. It will load
  the rest of the modules.
- =rul-emacs-modules/=: a directory with Emacs modules specific to my
  configuration. Modules group code related to a topic or theme of
  configuration. For example, =rul-prog.el= contains code related to
  programming, and =rul-org.el= contains code related to org-mode. If a
  module gets too big, I can create a smaller module under the same
  topic; for example, =rul-org-agenda.el=.
- =rul-post-init.el=: this file will be loaded after =init.el=, and will
  normally live in other git repository. Here I normally add overrides
  needed in my work computer.
- =rul-emacs.org=: this file. It (will) generate the rest of the structure.

* Early configuration file (=early-init.el=)
** Graphical aspects
Customization of graphical aspects of Emacs, such as size, panels, etc.

#+begin_src emacs-lisp :tangle "early-init.el"
;; I don't use any of these
(menu-bar-mode -1)
(tool-bar-mode -1)
(scroll-bar-mode -1)

;; Avoid initial flash of light.
(defun prot-emacs-re-enable-frame-theme (_frame)
  "Re-enable active theme, if any, upon FRAME creation.
Add this to `after-make-frame-functions' so that new frames do
not retain the generic background set by the function
`prot-emacs-avoid-initial-flash-of-light'."
  (when-let* ((theme (car custom-enabled-themes)))
    (enable-theme theme)))

(defun prot-emacs-avoid-initial-flash-of-light ()
  "Avoid flash of light when starting Emacs, if needed.
New frames are instructed to call `prot-emacs-re-enable-frame-theme'."
  (setq mode-line-format nil)
  (set-face-attribute 'default nil :background "#000000" :foreground "#ffffff")
  (set-face-attribute 'mode-line nil :background "#000000" :foreground "#ffffff" :box 'unspecified)
  (add-hook 'after-make-frame-functions #'prot-emacs-re-enable-frame-theme))

(prot-emacs-avoid-initial-flash-of-light)
#+end_src

** Frame configuration
I like to keep a few frames open all the time. A main frame, where I
open my org files, code, etc. A frame for communication and reading,
such as email and feeds, and a frame for terminals.

Currently, the frames are all the same, but I will add configuration
to distinguish them so I can automate their placement in my desktop
environment.

#+begin_src emacs-lisp :tangle "early-init.el"
;; Do not resize when font size changes
(setq frame-resize-pixelwise t)

;; By default, start maximized, undecorated
(add-to-list 'default-frame-alist '(fullscreen . maximized))
(add-to-list 'default-frame-alist '(undecorated . t))

;; Name frames to ease switching between them
(add-hook 'after-init-hook (lambda () (set-frame-name "main")))
#+end_src

** Miscellany
#+begin_src emacs-lisp :tangle "early-init.el"
;; Initialise installed packages, otherwise, basic functions are not
;; available during the initialization stage.
(setq package-enable-at-startup t)

;; Do not report warnings. It's too noisy.
(setq native-comp-async-report-warnings-errors 'silent)

;; Keep things minimal
(setq inhibit-startup-screen t)
(setq inhibit-startup-echo-area-message user-login-name)
#+end_src

* Main configuration file (=init.el=)
** Package matters

I use package from both stable and bleeding-edge Melpa.

#+begin_src emacs-lisp :tangle "init.el"
;; package.el
(require 'package)
(add-to-list 'package-archives
             '("melpa-stable" . "https://stable.melpa.org/packages/") t)

(add-to-list 'package-archives
             '("melpa" . "https://melpa.org/packages/") t)
#+end_src
** Backups
Emacs tends to clutter the filesystem with backup files. A backup file is normally the filename with a =~= suffix. I rather have my filesystem clean, and centralize all backups in a single directory.

#+begin_src emacs-lisp :tangle "init.el"
(let ((backup-dir "~/.backup"))
  (unless (file-directory-p backup-dir)
    (make-directory backup-dir t))
  (setq backup-directory-alist `(("." . ,backup-dir))))

(setq
 backup-by-copying t    ; Don't delink hardlinks
 delete-old-versions t  ; Clean up the backups
 kept-new-versions 3    ; keep some new versions
 kept-old-versions 2   ; and some old ones, too
 version-control t)      ; Use version numbers on backups
#+end_src
** Customizations
Customizations don't place nicely with version control, so I do them in a random file that won't get persisted.

Configurations that need persisting will be added to =custom-set-variables= and =custom-set-faces=.

#+begin_src emacs-lisp :tangle "init.el"
;; Do not persist customizations
(setq custom-file (make-temp-file "emacs-custom-"))
#+end_src

** Editor interface
General configurations related to text editing across all modes.

#+begin_src emacs-lisp :tangle "init.el"
(setq fill-column 79) ; Wrap lines
(setq mouse-yank-at-point t) ; Do not follow mouse curors when mouse-yanking

(setq-default indent-tabs-mode nil) ; No tabs when indenting
(setq-default tab-width 4) ; How many spaces a tab represents

(setq initial-scratch-message "")

(defalias 'yes-or-no-p 'y-or-n-p)

;; Only flash the mode line
(setq ring-bell-function
      (lambda ()
        (let ((orig-fg (face-foreground 'mode-line)))
          (set-face-foreground 'mode-line "#F2804F")
          (run-with-idle-timer 0.1 nil
                               (lambda (fg) (set-face-foreground 'mode-line fg))
                               orig-fg))))

;; Highlight parens
(setq show-paren-delay 0)
(show-paren-mode 1)

(savehist-mode 1) ; Save histories, including minibuffer

(save-place-mode 1) ; Remember and restore cursor information

(setq auto-save-no-message t) ; Do not print a message when auto-saving

(pixel-scroll-precision-mode 1) ; Precision scrolling


;; Source: https://protesilaos.com/codelog/2024-12-11-emacs-diff-save-some-buffers/
(add-to-list 'save-some-buffers-action-alist
             (list "d"
                   (lambda (buffer) (diff-buffer-with-file (buffer-file-name buffer)))
                   "show diff between the buffer and its file"))
#+end_src
** Emacs server
I used to run Emacs as a systemd daemon, but it was not too deterministic as sometimes it would break.

  https://rbenencia.name/blog/emacs-daemon-as-a-systemd-service/

Now, I simply start it from Emacs itself. This approach works well for me.

#+begin_src emacs-lisp :tangle "init.el"
;; Server
(require 'server)
(setq server-client-instructions nil) ; Keep it quiet when opening an ec

(unless (server-running-p)
  (server-start))
#+end_src
** Modules machinery
#+begin_src emacs-lisp :tangle "init.el"
(dolist (path '("~/.emacs.d/rul-lisp/packages"))
  (add-to-list 'load-path path))

(when-let* ((file (locate-user-emacs-file "rul-pre-init.el"))
            ((file-exists-p file)))
  (load-file file))

(require 'rul-themes)
(require 'rul-bindings)
(require 'rul-completion)
(require 'rul-fm)
(require 'rul-fonts)
(require 'rul-io)
(require 'rul-mail)
(require 'rul-modeline)
(require 'rul-org)
(require 'rul-prog)
(require 'rul-terminals)
(require 'rul-vc)
(require 'rul-wm)
(require 'rul-write)

(when-let* ((file (locate-user-emacs-file "rul-post-init.el"))
            ((file-exists-p file)))
  (load-file file))

;; init.el ends here
#+end_src

* Modules
I group my configuration in logical modules. In general, a module
contains configuration for more than one package.

** The =themes= module
The =themes= module contains code pertaining to Emacs themes. 

#+begin_src emacs-lisp :tangle "rul-lisp/packages/rul-themes.el"
(use-package ef-themes :ensure t)
(use-package modus-themes
  :ensure t
  :config
  (setq
   modus-themes-mode-line '(accented borderless padded)
   modus-themes-region '(bg-only)
   modus-themes-bold-constructs t
   modus-themes-italic-constructs t
   modus-themes-paren-match '(bold intense)
   modus-themes-headings (quote ((1 . (rainbow variable-pitch 1.3))
                                 (2 . (rainbow 1.1))
                                 (t . (rainbow))))
   modus-themes-org-blocks 'tinted))
#+end_src


Additionally, this module subscribes to =org.freedesktop.appearance color-theme=
to detect what color theme is preferred, and set our Emacs theme accordingly.

#+begin_src emacs-lisp :tangle "rul-lisp/packages/rul-themes.el"
(use-package dbus)
(defun mf/set-theme-from-dbus-value (value)
  "Set the appropiate theme according to the color-scheme setting value."
  (message "value is %s" value)
  (if (equal value '1)
      (progn (message "Switch to dark theme")
             (modus-themes-select 'modus-vivendi))
    (progn (message "Switch to light theme")
           (modus-themes-select 'modus-operandi))))

(defun mf/color-scheme-changed (path var value)
  "DBus handler to detect when the color-scheme has changed."
  (when (and (string-equal path "org.freedesktop.appearance")
             (string-equal var "color-scheme"))
    (mf/set-theme-from-dbus-value (car value))
    ))

;; Register for future changes
(dbus-register-signal
 :session "org.freedesktop.portal.Desktop"
 "/org/freedesktop/portal/desktop" "org.freedesktop.portal.Settings"
 "SettingChanged"
 #'mf/color-scheme-changed)

;; Request the current color-scheme
(dbus-call-method-asynchronously
 :session "org.freedesktop.portal.Desktop"
 "/org/freedesktop/portal/desktop" "org.freedesktop.portal.Settings"
 "Read"
 (lambda (value) (mf/set-theme-from-dbus-value (caar value)))
 "org.freedesktop.appearance"
 "color-scheme"
 )


(provide 'rul-themes)
#+end_src

** The =bindings= module
This module contains code pertaining to keybindings. It starts by
defining a set global keys.

#+begin_src emacs-lisp :tangle "rul-lisp/packages/rul-bindings.el"
;; Global keybindings
(global-set-key (kbd "C-c R") 'revert-buffer)
(global-set-key (kbd "C-c w") 'whitespace-cleanup)

(defun help/insert-em-dash ()
  "Inserts an EM-DASH (not a HYPEN, not an N-DASH)"
  (interactive)
  (insert "—"))

(global-set-key (kbd "C--") #'help/insert-em-dash)
#+end_src

Next, we define a few /hydras/. /Hydras/ are a way of grouping keybindings
together, offering a menu on the way.

#+begin_src emacs-lisp :tangle "rul-lisp/packages/rul-bindings.el"
(use-package hydra
  :ensure t
  :defer 1)

;; tab-bar
(defhydra hydra-tab-bar (:color amaranth)
  "Tab Bar Operations"
  ("t" tab-new "Create a new tab" :column "Creation" :exit t)
  ("d" dired-other-tab "Open Dired in another tab")
  ("f" find-file-other-tab "Find file in another tab")
  ("x" tab-close "Close current tab")
  ("m" tab-move "Move current tab" :column "Management")
  ("r" tab-rename "Rename Tab")
  ("<return>" tab-bar-select-tab-by-name "Select tab by name" :column "Navigation")
  ("l" tab-next "Next Tab")
  ("j" tab-previous "Previous Tab")
  ("q" nil "Exit" :exit t))

(global-set-key (kbd "C-x t") 'hydra-tab-bar/body)

;; Zoom
(defhydra hydra-zoom ()
  "zoom"
  ("g" text-scale-increase "in")
  ("l" text-scale-decrease "out"))

(global-set-key (kbd "C-c z") 'hydra-zoom/body)

;; Go
(defhydra hydra-go ()
  "zoom"
  ("=" gofmt :exit t)
  ("c" go-coverage :exit t))

;; vterm
(defhydra hydra-vterm ()
  "zoom"
  ("t" multi-vterm "Open a terminal" :exit t)
  ("d" multi-vterm-dedicated-open "Dedicated" :exit t)
  ("p" multi-vterm-prev "Previous terminal")
  ("n" multi-vterm-next "Next terminal")
  ("r" multi-vterm-rename-buffer "Rename buffer" :exit t)
  )

(global-set-key (kbd "C-c t") 'hydra-vterm/body)
(global-set-key (kbd "C-c m") 'hydra-go/body)
#+end_src

Finally, we make use of =which-key=, which will show a menu with all
keybinding options after a prefix is pressed. I think this package has
the potential to obsolete =hydra=, so I'll have to revisit that code.

#+begin_src emacs-lisp :tangle "rul-lisp/packages/rul-bindings.el"
(use-package which-key
  :ensure t
  :config
  (which-key-mode))

(provide 'rul-bindings)
#+end_src

** The =completions= module
This module contains code pertaining to completion and the minibuffer.

#+begin_src emacs-lisp :tangle "rul-lisp/packages/rul-completion.el"
(use-package orderless :ensure t)

(setq completion-styles '(basic substring initials orderless))
(setq completion-category-overrides
      '(
        (file (styles . (basic partial-completion orderless)))
        (project-file (styles . (flex basic substring partial-completion orderless)))
        ))

(setq completion-ignore-case t)
#+end_src

The =vertico= package provides a vertical completion UI based on the default completion
system.

#+begin_src emacs-lisp :tangle "rul-lisp/packages/rul-completion.el"
;; Enable vertico
(use-package vertico
  :ensure t
  :init
  (vertico-mode)

  :config
  (add-hook 'rfn-eshadow-update-overlay-hook #'vertico-directory-tidy))
#+end_src

The =marginalia= package annotates the completion candidates with useful contextual
information.

#+begin_src emacs-lisp :tangle "rul-lisp/packages/rul-completion.el"
;; Enable rich annotations using the Marginalia package
(use-package marginalia
  :ensure t
  :bind (:map minibuffer-local-map
         ("M-A" . marginalia-cycle))
  :init
  (marginalia-mode))
#+end_src

The =consult= package replaces most of Emacs core functions with
completion-friendly alternatives that integrates well with =vertico= and
=marginalia=.

#+begin_src emacs-lisp :tangle "rul-lisp/packages/rul-completion.el"
(use-package consult
  :ensure t
  :bind (;; C-c bindings in `mode-specific-map'
         ("C-c M-x" . consult-mode-command)
         ("C-c h" . consult-history)
         ("C-c k" . consult-kmacro)
         ("C-c m" . consult-man)
         ("C-c i" . consult-info)
         ([remap Info-search] . consult-info)

         ;; C-x bindings in `ctl-x-map'
         ("C-x M-:" . consult-complex-command)     ;; orig. repeat-complex-command
         ("C-x b" . consult-buffer)                ;; orig. switch-to-buffer
         ("C-x 4 b" . consult-buffer-other-window) ;; orig. switch-to-buffer-other-window
         ("C-x 5 b" . consult-buffer-other-frame)  ;; orig. switch-to-buffer-other-frame
         ("C-x r b" . consult-bookmark)            ;; orig. bookmark-jump
         ("C-x p b" . consult-project-buffer)      ;; orig. project-switch-to-buffer

         ;; Custom M-# bindings for fast register access
         ("M-#" . consult-register-load)
         ("M-'" . consult-register-store)          ;; orig. abbrev-prefix-mark (unrelated)
         ("C-M-#" . consult-register)

         ;; Other custom bindings
         ("M-y" . consult-yank-pop)                ;; orig. yank-pop

         ;; M-g bindings in `goto-map'
         ("M-g e" . consult-compile-error)
         ("M-g f" . consult-flymake)               ;; Alternative: consult-flycheck
         ("M-g g" . consult-goto-line)             ;; orig. goto-line
         ("M-g M-g" . consult-goto-line)           ;; orig. goto-line
         ("M-g o" . consult-outline)               ;; Alternative: consult-org-heading
         ("M-g m" . consult-mark)
         ("M-g k" . consult-global-mark)
         ("M-g i" . consult-imenu)
         ("M-g I" . consult-imenu-multi)

         ;; M-s bindings in `search-map'
         ("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)

         ;; Isearch integration
         ("M-s e" . consult-isearch-history)

         :map isearch-mode-map
         ("M-e" . consult-isearch-history)         ;; orig. isearch-edit-string
         ("M-s e" . consult-isearch-history)       ;; orig. isearch-edit-string
         ("M-s l" . consult-line)                  ;; needed by consult-line to detect isearch
         ("M-s L" . consult-line-multi)            ;; needed by consult-line to detect isearch

         ;; Minibuffer history
         :map minibuffer-local-map
         ("M-s" . consult-history)                 ;; orig. next-matching-history-element
         ("M-r" . consult-history))                ;; orig. previous-matching-history-element

  :init
  (setq xref-show-xrefs-function #'consult-xref)
  (setq xref-show-definitions-function #'consult-xref)
  (add-hook 'completion-list-mode-hook #'consult-preview-at-point-mode)

  :config
  (setq consult-preview-key 'any)
  (setq consult-narrow-key "<")
)
#+end_src

The next piece of code corresponds to =embark=, a package that enables
context-specific actions in the minibuffer, or common buffers.

#+begin_src emacs-lisp :tangle "rul-lisp/packages/rul-completion.el"
(use-package embark
  :ensure t

  :bind
  (("C-." . embark-act)         ;; pick some comfortable binding
   ("M-." . embark-dwim)        ;; good alternative: M-.
   ("C-h B" . embark-bindings)) ;; alternative for `describe-bindings'

  :init
  (setq prefix-help-command #'embark-prefix-help-command)

  :config
  ;; Hide the mode line of the Embark live/completions buffers
  (add-to-list 'display-buffer-alist
               '("\\`\\*Embark Collect \\(Live\\|Completions\\)\\*"
                 nil
                 (window-parameters (mode-line-format . none)))))

(use-package embark-consult
  :ensure t
  :hook
  (embark-collect-mode . consult-preview-at-point-mode))

(provide 'rul-completion)
#+end_src

** The =fm= module
The =fm= module contains code pertaining to file management. In
particular, it's the module that configures =dired= and adds a few extra
packages.

#+begin_src emacs-lisp :tangle "rul-lisp/packages/rul-fm.el"
;;; rul-fm.el --- File management

;; dired
(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-kill-when-opening-new-dired-buffer t)
(put 'dired-find-alternate-file 'disabled nil)

;;; Icons
(use-package nerd-icons :ensure t )
(use-package nerd-icons-dired :ensure t
  :config
  (add-hook 'dired-mode-hook #'nerd-icons-dired-mode))

(provide 'rul-fm)
#+end_src

** The =fonts= module
The =fonts= module contains code pertaining to fonts. In particular, it
installs =fontaine=, a software that allows defining font presets.

#+begin_src emacs-lisp :tangle "rul-lisp/packages/rul-fonts.el"
;;; rul-fonts.el --- Fonts configuration

(use-package fontaine
  :ensure t
  :config
  (setq fontaine-presets
      '((tiny
         :default-height 100)
        (small
         :default-height 120)
        (medium
         :default-height 140)
        (large
         :default-weight semilight
         :default-height 180
         :bold-weight extrabold)
        (presentation
         :default-weight semilight
         :default-height 200
         :bold-weight extrabold)
        (jumbo
         :default-weight semilight
         :default-height 230
         :bold-weight extrabold)
        (t
         :default-family "Iosevka"
         :default-weight regular
         :default-height 140
         :variable-pitch-family "Iosevka Aile")))

  ;; Set desired style from `fontaine-presets'
  (fontaine-set-preset 'medium))

(provide 'rul-fonts)
#+end_src

** The =io= module
The =io= module contains configurations for packages related to Internet
services and media. I don't have excessive costumizations in these
packages, so they're somewhat unrelated fragments of code grouped in
the same file.

We install =elfeed= to browse RSS and Atom feeds.
#+begin_src emacs-lisp :tangle "rul-lisp/packages/rul-io.el"
;;; rul-io.el --- Configuration for Internet and media packages

(use-package elfeed :ensure t)
(provide 'rul-feeds)
#+end_src

The =empv= package allow us to use the =mpv= player from within
Emacs. Here we're simply installing it and configuring it with some
Internet radio channels. It requires =mpv= to be installed.
#+begin_src emacs-lisp :tangle "rul-lisp/packages/rul-io.el"
(use-package empv
:ensure t
:config
  (bind-key "C-x m" empv-map)
  (setq empv-radio-channels
      '(
       ("SomaFM - Groove Salad" . "http://www.somafm.com/groovesalad.pls")
       ("SomaFM - DEFCON" . "https://somafm.com/defcon256.pls")
       ("SomaFM - Metal" . "https://somafm.com/metal.pls")
       ("SomaFM - Lush" . "https://somafm.com/lush130.pls")
       ("KCSM Jazz 91" . "http://ice5.securenetsystems.net/KCSM")
       )))

(provide 'rul-io)
#+end_src
** The =mail= module
Emacs can act as Mail User Agent. My preferred package for this is
=notmuch=.

#+begin_src emacs-lisp :tangle "rul-lisp/packages/rul-mail.el"
;;; rul-mail.el --- Email configuration

;; mml-sec.el
;; Use sender to find GPG key.
(setq mml-secure-openpgp-sign-with-sender t)

(use-package notmuch
  :ensure t
  :config
  ;; UI
  (setq notmuch-show-logo nil
        notmuch-column-control 1.0
        notmuch-hello-auto-refresh t
        notmuch-hello-recent-searches-max 20
        notmuch-hello-thousands-separator ""
        notmuch-show-all-tags-list t)

  ;; Keymaps
  (defun rul/capture-mail()
    "Capture mail to org mode."
    (interactive)
    (org-store-link nil)
    (org-capture nil "m")
    )

  (bind-key "c" 'rul/capture-mail notmuch-show-mode-map)

  (define-key notmuch-show-mode-map "R" 'notmuch-show-reply)
  (define-key notmuch-search-mode-map "R" 'notmuch-search-reply-to-thread)

  ;; Spam
  (define-key notmuch-show-mode-map "S"
              (lambda ()
                "mark message as spam"
                (interactive)
                (notmuch-show-tag (list "+spam" "-inbox" "-unread"))))

  (define-key notmuch-search-mode-map "S"
              (lambda (&optional beg end)
                "mark thread as spam"
                (interactive (notmuch-search-interactive-region))
                (notmuch-search-tag (list "+spam" "-inbox" "-unread") beg end)))

  ;; Archive
  (setq notmuch-archive-tags (list "-inbox" "+archive"))
  (define-key notmuch-show-mode-map "A"
              (lambda ()
                "archive"
                (interactive)
                (notmuch-show-tag (list "+archive" "-inbox" "-unread"))
                (notmuch-refresh-this-buffer)))

  (define-key notmuch-search-mode-map "A"
              (lambda (&optional beg end)
                "archive thread"
                (interactive (notmuch-search-interactive-region))
                (notmuch-search-tag (list "+archive" "-inbox" "-unread") beg end)
                (notmuch-refresh-this-buffer)))

  ;; Mark as read
  (define-key notmuch-search-mode-map "r"
              (lambda (&optional beg end)
                "mark thread as read"
                (interactive (notmuch-search-interactive-region))
                (notmuch-search-tag (list "-unread") beg end)
                (notmuch-search-next-thread)))

  (define-key notmuch-search-mode-map (kbd "RET")
              (lambda ()
                "Show the selected thread with notmuch-tree if it has more
than one email. Use notmuch-show otherwise."
                (interactive)
                (if (= (plist-get (notmuch-search-get-result) :total) 1)
                    (notmuch-search-show-thread)
                  (notmuch-tree (notmuch-search-find-thread-id)
                                notmuch-search-query-string
                                nil
                                (notmuch-prettify-subject (notmuch-search-find-subject))))))

  (defun color-inbox-if-unread () (interactive)
         (save-excursion
           (goto-char (point-min))
           (let ((cnt (car (process-lines "notmuch" "count" "tag:inbox and tag:unread"))))
             (when (> (string-to-number cnt) 0)
               (save-excursion
                 (when (search-forward "inbox" (point-max) t)
                   (let* ((overlays (overlays-in (match-beginning 0) (match-end 0)))
                          (overlay (car overlays)))
                     (when overlay
                       (overlay-put overlay 'face '((:inherit bold) (:foreground "green")))))))))))

  (defvar notmuch-hello-refresh-count 0)
  (defun notmuch-hello-refresh-status-message ()
    (let* ((new-count
            (string-to-number
             (car (process-lines notmuch-command "count"))))
           (diff-count (- new-count notmuch-hello-refresh-count)))
      (cond
       ((= notmuch-hello-refresh-count 0)
        (message "You have %s messages."
                 (notmuch-hello-nice-number new-count)))
       ((> diff-count 0)
        (message "You have %s more messages since last refresh."
                 (notmuch-hello-nice-number diff-count)))
       ((< diff-count 0)
        (message "You have %s fewer messages since last refresh."
                 (notmuch-hello-nice-number (- diff-count)))))
      (setq notmuch-hello-refresh-count new-count)))

  (add-hook 'notmuch-hello-refresh-hook 'color-inbox-if-unread)
  (add-hook 'notmuch-hello-refresh-hook 'notmuch-hello-refresh-status-message)

  (setq notmuch-hello-sections '(notmuch-hello-insert-saved-searches
                                 notmuch-hello-insert-search
                                 notmuch-hello-insert-recent-searches
                                 notmuch-hello-insert-alltags
                                 ))

  ;; https://git.sr.ht/~tslil/dotfiles/tree/4e51afbb/emacs/notmuch-config.el#L76-82
  (defmacro make-binds (mode-map binds argfunc &rest body)
    "Create keybindings in `mode-map' using a list of (keystr . arg)
pairs in `binds' of the form ( ... (argfunc arg) body)."
    `(progn ,@(mapcar (lambda (pair)
                        `(define-key ,mode-map (kbd ,(car pair))
                                     (lambda () (interactive) (,argfunc ,(cdr pair)) ,@body)))
                      (eval binds))))

  (defvar notmuch-hello-tree-searches '(("u" . "tag:unread")
                                        ("i" . "tag:inbox")
                                        ("*" . "*"))
    "List of (key . query) pairs to bind in notmuch-hello.")

  (make-binds notmuch-hello-mode-map
              notmuch-hello-tree-searches
              notmuch-search)
) ;; ends use-package notmuch

(use-package notmuch-indicator :ensure t)

(provide 'rul-mail)
#+end_src
** The =modeline= module
The =modeline= module contains code pertaining to Emacs modeline.

#+begin_src emacs-lisp :tangle "rul-lisp/packages/rul-modeline.el"
;;; rul-modeline.el --- Modeline configuration

;; Most of the code in this file is based on:
;;   https://git.sr.ht/~protesilaos/dotfiles/tree/cf26bc34/item/emacs/.emacs.d/prot-lisp/prot-modeline.el
;;
;; All Kudos to Prot.

;;;; Faces
(defface rul-modeline-indicator-red
  '((default :inherit bold)
    (((class color) (min-colors 88) (background light))
     :foreground "#880000")
    (((class color) (min-colors 88) (background dark))
     :foreground "#ff9f9f")
    (t :foreground "red"))
  "Face for modeline indicators.")

;;;; Common helper functions
(defcustom rul-modeline-string-truncate-length 9
  "String length after which truncation should be done in small windows."
  :type 'natnum)

(defun rul-modeline--string-truncate-p (str)
  "Return non-nil if STR should be truncated."
  (and (< (window-total-width) split-width-threshold)
       (> (length str) rul-modeline-string-truncate-length)
       (not (one-window-p :no-minibuffer))))

(defun rul-modeline-string-truncate (str)
  "Return truncated STR, if appropriate, else return STR.
Truncation is done up to `rul-modeline-string-truncate-length'."
  (if (rul-modeline--string-truncate-p str)
      (concat (substring str 0 rul-modeline-string-truncate-length) "...")
    str))

;;;; Major mode
(defun rul-modeline-major-mode-indicator ()
  "Return appropriate propertized mode line indicator for the major mode."
  (let ((indicator (cond
                    ((derived-mode-p 'text-mode) "§")
                    ((derived-mode-p 'prog-mode) "λ")
                    ((derived-mode-p 'comint-mode) ">_")
                    (t "◦"))))
    (propertize indicator 'face 'shadow)))

(defun rul-modeline-major-mode-name ()
  "Return capitalized `major-mode' without the -mode suffix."
  (capitalize (string-replace "-mode" "" (symbol-name major-mode))))

(defun rul-modeline-major-mode-help-echo ()
  "Return `help-echo' value for `rul-modeline-major-mode'."
  (if-let ((parent (get major-mode 'derived-mode-parent)))
      (format "Symbol: `%s'.  Derived from: `%s'" major-mode parent)
    (format "Symbol: `%s'." major-mode)))

(defvar-local rul-modeline-major-mode
    (list
     (propertize "%[" 'face 'rul-modeline-indicator-red)
     '(:eval
       (concat
        (rul-modeline-major-mode-indicator)
        " "
        (propertize
         (rul-modeline-string-truncate
          (rul-modeline-major-mode-name))
         'mouse-face 'mode-line-highlight
         'help-echo (rul-modeline-major-mode-help-echo))))
     (propertize "%]" 'face 'rul-modeline-indicator-red))
  "Mode line construct for displaying major modes.")

(with-eval-after-load 'eglot
  (setq mode-line-misc-info
        (delete '(eglot--managed-mode (" [" eglot--mode-line-format "] ")) mode-line-misc-info)))

(defvar-local prot-modeline-eglot
    `(:eval
      (when (and (featurep 'eglot) (mode-line-window-selected-p))
        '(eglot--managed-mode eglot--mode-line-format)))
  "Mode line construct displaying Eglot information.
Specific to the current window's mode line.")

;;;; Miscellaneous
(defvar-local rul-modeline-misc-info
    '(:eval
      (when (mode-line-window-selected-p)
        mode-line-misc-info))
  "Mode line construct displaying `mode-line-misc-info'.
Specific to the current window's mode line.")

;;;; Display current time
(setq display-time-format " %a %e %b, %H:%M ")
(setq display-time-default-load-average nil)
(setq display-time-mail-string "")

;;;; Variables used in the modeline need to be in `risky-local-variable'.
(dolist (construct '(
                     rul-modeline-major-mode
                     rul-modeline-misc-info
                     ))
  (put construct 'risky-local-variable t))

;;;; Finally, define the modeline format
(setq-default mode-line-format
              '("%e"
                mode-line-front-space
                mode-line-buffer-identification
                mode-line-front-space
                rul-modeline-major-mode
                prot-modeline-eglot
                mode-line-format-right-align
                rul-modeline-misc-info
                ))

(provide 'rul-modeline)
#+end_src
** The =org= module

My org mode configuration is quite big, so I split it across multiple files.

#+begin_src emacs-lisp :tangle "rul-lisp/packages/rul-org.el"
;;; rul-org.el --- Org configuration
(require 'org)
(require 'org-capture)
(require 'org-protocol)
(require 'org-habit)

(require 'rul-org-agenda)

(setq org-attach-use-inheritance t)
(setq org-cycle-separator-lines 0)
(setq org-hide-leading-stars nil)
(setq org-startup-indented t)
(setq org-edit-src-content-indentation 0)

(use-package org-modern :ensure t)
(use-package org-pomodoro
  :ensure t
  :config
  (defun rul/disable-notifications ()
    "Disable GNOME notifications."
    (shell-command "gsettings set org.gnome.desktop.notifications show-banners false"))

  (defun rul/enable-notifications ()
    "Enable GNOME notifications."
    (shell-command "gsettings set org.gnome.desktop.notifications show-banners true"))

  ;; Add hooks for Pomodoro start and finish
  (add-hook 'org-pomodoro-started-hook #'rul/disable-notifications)
  (add-hook 'org-pomodoro-finished-hook #'rul/enable-notifications)
  (add-hook 'org-pomodoro-killed-hook #'rul/enable-notifications))

;; (add-hook 'org-mode-hook 'turn-off-auto-fill)
;; (add-hook 'auto-save-hook 'org-save-all-org-buffers)
(add-hook 'org-mode-hook 'visual-line-mode)

(use-package org-download
  :ensure t
  :config
  (add-hook 'dired-mode-hook 'org-download-enable))

(setq org-startup-indented t
      org-pretty-entities nil
      org-hide-emphasis-markers t
      ;; show actually italicized text instead of /italicized text/
      org-fontify-whole-heading-line t
      org-fontify-done-headline t
      org-fontify-quote-and-verse-blocks t)

;; ORG BINDINGS ;;
(global-set-key (kbd "C-c l") #'org-store-link)
(global-set-key (kbd "C-c c") #'org-capture)
(global-set-key (kbd "C-c s") #'org-schedule)

;; ORG STATES ;;
(setq org-todo-keywords
      (quote ((sequence "TODO(t)" "MAYBE(m)" "NEXT(n)" "|" "DONE(d)")
              (sequence "WAITING(w@/!)" "HOLD(h@/!)" "|" "CANCELLED(c@/!)" "MEETING"))))

(setq org-use-fast-todo-selection t)

(setq org-todo-state-tags-triggers
      (quote (("CANCELLED" ("CANCELLED" . t))
              ("WAITING" ("WAITING" . t))
              ("HOLD" ("WAITING") ("HOLD" . t))
              (done ("WAITING") ("HOLD"))
              ("TODO" ("WAITING") ("CANCELLED") ("HOLD"))
              ("NEXT" ("WAITING") ("CANCELLED") ("HOLD"))
              ("DONE" ("WAITING") ("CANCELLED") ("HOLD")))))

(setq org-enforce-todo-dependencies t)
(setq org-log-done (quote time))
(setq org-log-redeadline (quote time))
(setq org-log-reschedule (quote time))

;; CAPTURE ;;
(setq org-capture-templates
      (quote
       (

        ("w" "Todo" entry
         (file+headline org-refile-path "Tasks")
         "* TODO %?"
         :empty-lines 1)

        ("m"
         "Capture incoming email"
         entry
         (file+headline org-refile-path "Incoming")
         "* TODO Re: %:description\n\n  Source: %u, %a\n"
         :empty-lines 1)

        ("e" "Elfeed entry" entry
         (file+headline org-refile-path "Read later")
         "* %? [[%:link][%:description]]\n  %U\n  %:description\n")

        ("L" "Web Link" entry
         (file+headline org-refile-path "Read later")
         "* %?[[%:link][%:description]] \"\")\n %:initial\n \nCaptured On: %U"
         )

        ("l" "Web Link with Selection" entry
         (file+headline org-refile-path "Read later")
         "* [[%:link][%:description]] \n %:initial\n \nCaptured On: %U")

        )))

;; REFILE ;;

; Targets include this file and any file contributing to the agenda - up to 3 levels deep
(setq org-refile-targets
      '((nil :maxlevel . 3)
        (org-agenda-files :maxlevel . 3)))

; Targets complete directly with IDO
(setq org-outline-path-complete-in-steps nil)

; Allow refile to create parent tasks with confirmation
(setq org-refile-allow-creating-parent-nodes (quote confirm))



;; ORG REPORTS ;;
; Set default column view headings: Task Effort Clock_Summary
(setq org-columns-default-format "%80ITEM(Task) %10Effort(Effort){:} %10CLOCKSUM")

(defun my-org-clocktable-indent-string (level)
  (if (= level 1)
      ""
    (let ((str "^"))
      (while (> level 2)
        (setq level (1- level)
              str (concat str "--")))
      (concat str "-> "))))

(advice-add 'org-clocktable-indent-string :override #'my-org-clocktable-indent-string)

(setq org-clock-clocktable-default-properties '(:maxlevel 4 :scope file :formula %))

; global Effort estimate values
; global STYLE property values for completion
(setq org-global-properties (quote (("Effort_ALL" . "0:15 0:30 0:45 1:00 2:00 3:00 4:00 5:00 6:00 0:00")
                                    ("STYLE_ALL" . "habit"))))

;; TAGS ;;
; Tags with fast selection keys
(setq org-tag-alist (quote ((:startgroup)
                            ("@errand" . ?e)
                            ("@office" . ?o)
                            ("@home" . ?H)
                            (:endgroup)
                            ("WAITING" . ?w)
                            ("HOLD" . ?h)
                            ("CANCELLED" . ?c)
                            ("FLAGGED" . ??))))

(setq org-stuck-projects
      '("+LEVEL=2+PROJECT/-MAYBE-DONE" ("NEXT") ("@shop")
        "\\<IGNORE\\>"))

; Allow setting single tags without the menu
(setq org-fast-tag-selection-single-key (quote expert))

;; org-modern
(add-hook 'org-mode-hook 'org-modern-mode)
(add-hook 'org-agenda-finalize-hook #'org-modern-agenda)

;; Honor ATTR_ORG attribute. Defaults to image's width if not set.
(setq org-image-actual-width nil)

(provide 'rul-org)
#+end_src

#+begin_src emacs-lisp :tangle "rul-lisp/packages/rul-org-agenda.el"
;;; rul-org-agenda.el --- Org agenda configuration
(require 'org)

(global-set-key (kbd "<f12>") #'org-agenda)
(global-set-key (kbd "C-c a") #'org-agenda)

(defun bh/is-project-p ()
  "Any task with a todo keyword subtask"
  (save-restriction
    (widen)
    (let ((has-subtask)
          (subtree-end (save-excursion (org-end-of-subtree t)))
          (is-a-task (member (nth 2 (org-heading-components)) org-todo-keywords-1)))
      (save-excursion
        (forward-line 1)
        (while (and (not has-subtask)
                    (< (point) subtree-end)
                    (re-search-forward "^\*+ " subtree-end t))
          (when (member (org-get-todo-state) org-todo-keywords-1)
            (setq has-subtask t))))
      (and is-a-task has-subtask))))

(defun bh/is-project-subtree-p ()
  "Any task with a todo keyword that is in a project subtree.
Callers of this function already widen the buffer view."
  (let ((task (save-excursion (org-back-to-heading 'invisible-ok)
                              (point))))
    (save-excursion
      (bh/find-project-task)
      (if (equal (point) task)
          nil
        t))))

(defun bh/is-task-p ()
  "Any task with a todo keyword and no subtask"
  (save-restriction
    (widen)
    (let ((has-subtask)
          (subtree-end (save-excursion (org-end-of-subtree t)))
          (is-a-task (member (nth 2 (org-heading-components)) org-todo-keywords-1)))
      (save-excursion
        (forward-line 1)
        (while (and (not has-subtask)
                    (< (point) subtree-end)
                    (re-search-forward "^\*+ " subtree-end t))
          (when (member (org-get-todo-state) org-todo-keywords-1)
            (setq has-subtask t))))
      (and is-a-task (not has-subtask)))))

(defun bh/is-subproject-p ()
  "Any task which is a subtask of another project"
  (let ((is-subproject)
        (is-a-task (member (nth 2 (org-heading-components)) org-todo-keywords-1)))
    (save-excursion
      (while (and (not is-subproject) (org-up-heading-safe))
        (when (member (nth 2 (org-heading-components)) org-todo-keywords-1)
          (setq is-subproject t))))
    (and is-a-task is-subproject)))

(defun bh/list-sublevels-for-projects-indented ()
  "Set org-tags-match-list-sublevels so when restricted to a subtree we list all subtasks.
  This is normally used by skipping functions where this variable is already local to the agenda."
  (if (marker-buffer org-agenda-restrict-begin)
      (setq org-tags-match-list-sublevels 'indented)
    (setq org-tags-match-list-sublevels nil))
  nil)

(defun bh/list-sublevels-for-projects ()
  "Set org-tags-match-list-sublevels so when restricted to a subtree we list all subtasks.
  This is normally used by skipping functions where this variable is already local to the agenda."
  (if (marker-buffer org-agenda-restrict-begin)
      (setq org-tags-match-list-sublevels t)
    (setq org-tags-match-list-sublevels nil))
  nil)

(defvar bh/hide-scheduled-and-waiting-next-tasks t)

(defun bh/toggle-next-task-display ()
  (interactive)
  (setq bh/hide-scheduled-and-waiting-next-tasks (not bh/hide-scheduled-and-waiting-next-tasks))
  (when  (equal major-mode 'org-agenda-mode)
    (org-agenda-redo))
  (message "%s WAITING and SCHEDULED NEXT Tasks" (if bh/hide-scheduled-and-waiting-next-tasks "Hide" "Show")))

(defun bh/skip-stuck-projects ()
  "Skip trees that are not stuck projects"
  (save-restriction
    (widen)
    (let ((next-headline (save-excursion (or (outline-next-heading) (point-max)))))
      (if (bh/is-project-p)
          (let* ((subtree-end (save-excursion (org-end-of-subtree t)))
                 (has-next ))
            (save-excursion
              (forward-line 1)
              (while (and (not has-next) (< (point) subtree-end) (re-search-forward "^\\*+ NEXT " subtree-end t))
                (unless (member "WAITING" (org-get-tags-at))
                  (setq has-next t))))
            (if has-next
                nil
              next-headline)) ; a stuck project, has subtasks but no next task
        nil))))

(defun bh/skip-non-stuck-projects ()
  "Skip trees that are not stuck projects"
  ;; (bh/list-sublevels-for-projects-indented)
  (save-restriction
    (widen)
    (let ((next-headline (save-excursion (or (outline-next-heading) (point-max)))))
      (if (bh/is-project-p)
          (let* ((subtree-end (save-excursion (org-end-of-subtree t)))
                 (has-next ))
            (save-excursion
              (forward-line 1)
              (while (and (not has-next) (< (point) subtree-end) (re-search-forward "^\\*+ NEXT " subtree-end t))
                (unless (member "WAITING" (org-get-tags-at))
                  (setq has-next t))))
            (if has-next
                next-headline
              nil)) ; a stuck project, has subtasks but no next task
        next-headline))))

(defun bh/skip-non-projects ()
  "Skip trees that are not projects"
  ;; (bh/list-sublevels-for-projects-indented)
  (if (save-excursion (bh/skip-non-stuck-projects))
      (save-restriction
        (widen)
        (let ((subtree-end (save-excursion (org-end-of-subtree t))))
          (cond
           ((bh/is-project-p)
            nil)
           ((and (bh/is-project-subtree-p) (not (bh/is-task-p)))
            nil)
           (t
            subtree-end))))
    (save-excursion (org-end-of-subtree t))))

(defun bh/skip-non-tasks ()
  "Show non-project tasks.
Skip project and sub-project tasks, habits, and project related tasks."
  (save-restriction
    (widen)
    (let ((next-headline (save-excursion (or (outline-next-heading) (point-max)))))
      (cond
       ((bh/is-task-p)
        nil)
       (t
        next-headline)))))

(defun bh/skip-project-trees-and-habits ()
  "Skip trees that are projects"
  (save-restriction
    (widen)
    (let ((subtree-end (save-excursion (org-end-of-subtree t))))
      (cond
       ((bh/is-project-p)
        subtree-end)
       ((org-is-habit-p)
        subtree-end)
       (t
        nil)))))

(defun bh/skip-projects-and-habits-and-single-tasks ()
  "Skip trees that are projects, tasks that are habits, single non-project tasks"
  (save-restriction
    (widen)
    (let ((next-headline (save-excursion (or (outline-next-heading) (point-max)))))
      (cond
       ((org-is-habit-p)
        next-headline)
       ((and bh/hide-scheduled-and-waiting-next-tasks
             (member "WAITING" (org-get-tags-at)))
        next-headline)
       ((bh/is-project-p)
        next-headline)
       ((and (bh/is-task-p) (not (bh/is-project-subtree-p)))
        next-headline)
       (t
        nil)))))

(defun bh/skip-project-tasks-maybe ()
  "Show tasks related to the current restriction.
When restricted to a project, skip project and sub project tasks, habits, NEXT tasks, and loose tasks.
When not restricted, skip project and sub-project tasks, habits, and project related tasks."
  (save-restriction
    (widen)
    (let* ((subtree-end (save-excursion (org-end-of-subtree t)))
           (next-headline (save-excursion (or (outline-next-heading) (point-max))))
           (limit-to-project (marker-buffer org-agenda-restrict-begin)))
      (cond
       ((bh/is-project-p)
        next-headline)
       ((org-is-habit-p)
        subtree-end)
       ((and (not limit-to-project)
             (bh/is-project-subtree-p))
        subtree-end)
       ((and limit-to-project
             (bh/is-project-subtree-p)
             (member (org-get-todo-state) (list "NEXT")))
        subtree-end)
       (t
        nil)))))

(defun bh/skip-project-tasks ()
  "Show non-project tasks.
Skip project and sub-project tasks, habits, and project related tasks."
  (save-restriction
    (widen)
    (let* ((subtree-end (save-excursion (org-end-of-subtree t))))
      (cond
       ((bh/is-project-p)
        subtree-end)
       ((org-is-habit-p)
        subtree-end)
       ((bh/is-project-subtree-p)
        subtree-end)
       ((not (org-entry-is-todo-p))
        subtree-end)
       (t
        nil)))))

(defun bh/skip-non-project-tasks ()
  "Show project tasks.
Skip project and sub-project tasks, habits, and loose non-project tasks."
  (save-restriction
    (widen)
    (let* ((subtree-end (save-excursion (org-end-of-subtree t)))
           (next-headline (save-excursion (or (outline-next-heading) (point-max)))))
      (cond
       ((bh/is-project-p)
        next-headline)
       ((org-is-habit-p)
        subtree-end)
       ((and (bh/is-project-subtree-p)
             (member (org-get-todo-state) (list "NEXT")))
        subtree-end)
       ((not (bh/is-project-subtree-p))
        subtree-end)
       (t
        nil)))))

(defun bh/skip-projects-and-habits ()
  "Skip trees that are projects and tasks that are habits"
  (save-restriction
    (widen)
    (let ((subtree-end (save-excursion (org-end-of-subtree t))))
      (cond
       ((bh/is-project-p)
        subtree-end)
       ((org-is-habit-p)
        subtree-end)
       (t
        nil)))))

(defun bh/skip-non-subprojects ()
  "Skip trees that are not projects"
  (let ((next-headline (save-excursion (outline-next-heading))))
    (if (bh/is-subproject-p)
        nil
      next-headline)))

;; CLOCKING ;;
;; Resume clocking task when emacs is restarted
(org-clock-persistence-insinuate)
;;
;; Show lot of clocking history so it's easy to pick items off the C-F11 list
(setq org-clock-history-length 23)
;; Resume clocking task on clock-in if the clock is open
(setq org-clock-in-resume t)
;; Change tasks to NEXT when clocking in
(setq org-clock-in-switch-to-state 'bh/clock-in-to-next)
;; Separate drawers for clocking and logs
(setq org-drawers (quote ("PROPERTIES" "LOGBOOK")))
;; Save clock data and state changes and notes in the LOGBOOK drawer
(setq org-clock-into-drawer t)
;; Sometimes I change tasks I'm clocking quickly - this removes clocked tasks with 0:00 duration
(setq org-clock-out-remove-zero-time-clocks t)
;; Clock out when moving task to a done state
(setq org-clock-out-when-done t)
;; Save the running clock and all clock history when exiting Emacs, load it on startup
(setq org-clock-persist t)
;; Do not prompt to resume an active clock
(setq org-clock-persist-query-resume nil)
;; Enable auto clock resolution for finding open clocks
(setq org-clock-auto-clock-resolution (quote when-no-clock-is-running))
;; Include current clocking task in clock reports
(setq org-clock-report-include-clocking-task t)


(setq bh/keep-clock-running nil)

(defun bh/clock-in-to-next (kw)
  "Switch a task from TODO to NEXT when clocking in.
Skips capture tasks, projects, and subprojects.
Switch projects and subprojects from NEXT back to TODO"
  (when (not (and (boundp 'org-capture-mode) org-capture-mode))
    (cond
     ((and (member (org-get-todo-state) (list "TODO"))
           (bh/is-task-p))
      "NEXT")
     ((and (member (org-get-todo-state) (list "NEXT"))
           (bh/is-project-p))
      "TODO"))))

(defun bh/find-project-task ()
  "Move point to the parent (project) task if any"
  (save-restriction
    (widen)
    (let ((parent-task (save-excursion (org-back-to-heading 'invisible-ok) (point))))
      (while (org-up-heading-safe)
        (when (member (nth 2 (org-heading-components)) org-todo-keywords-1)
          (setq parent-task (point))))
      (goto-char parent-task)
      parent-task)))

(defun bh/punch-in (arg)
  "Start continuous clocking and set the default task to the
selected task.  If no task is selected set the Organization task
as the default task."
  (interactive "p")
  (setq bh/keep-clock-running t)
  (if (equal major-mode 'org-agenda-mode)
      ;;
      ;; We're in the agenda
      ;;
      (let* ((marker (org-get-at-bol 'org-hd-marker))
             (tags (org-with-point-at marker (org-get-tags-at))))
        (if (and (eq arg 4) tags)
            (org-agenda-clock-in '(16))
          (bh/clock-in-organization-task-as-default)))
    ;;
    ;; We are not in the agenda
    ;;
    (save-restriction
      (widen)
      ; Find the tags on the current task
      (if (and (equal major-mode 'org-mode) (not (org-before-first-heading-p)) (eq arg 4))
          (org-clock-in '(16))
        (bh/clock-in-organization-task-as-default)))))

(defun bh/punch-out ()
  (interactive)
  (setq bh/keep-clock-running nil)
  (when (org-clock-is-active)
    (org-clock-out))
  (org-agenda-remove-restriction-lock))

(defun bh/clock-in-default-task ()
  (save-excursion
    (org-with-point-at org-clock-default-task
      (org-clock-in))))

(defun bh/clock-in-parent-task ()
  "Move point to the parent (project) task if any and clock in"
  (let ((parent-task))
    (save-excursion
      (save-restriction
        (widen)
        (while (and (not parent-task) (org-up-heading-safe))
          (when (member (nth 2 (org-heading-components)) org-todo-keywords-1)
            (setq parent-task (point))))
        (if parent-task
            (org-with-point-at parent-task
              (org-clock-in))
          (when bh/keep-clock-running
            (bh/clock-in-default-task)))))))

(defvar bh/organization-task-id "eb155a82-92b2-4f25-a3c6-0304591af2f9")

;; https://stackoverflow.com/a/10091330
(defun zin/org-agenda-skip-tag (tag &optional others)
  "Skip all entries that correspond to TAG.

If OTHERS is true, skip all entries that do not correspond to TAG."
  (let ((next-headline (save-excursion (or (outline-next-heading) (point-max))))
        (current-headline (or (and (org-at-heading-p)
                                   (point))
                              (save-excursion (org-back-to-heading)))))
    (if others
        (if (not (member tag (org-get-tags-at current-headline)))
            next-headline
          nil)
      (if (member tag (org-get-tags-at current-headline))
          next-headline
        nil))))

(defun bh/clock-in-organization-task-as-default ()
  (interactive)
  (org-with-point-at (org-id-find bh/organization-task-id 'marker)
    (org-clock-in '(16))))

(defun bh/clock-out-maybe ()
  (when (and bh/keep-clock-running
             (not org-clock-clocking-in)
             (marker-buffer org-clock-default-task)
             (not org-clock-resolving-clocks-due-to-idleness))
    (bh/clock-in-parent-task)))

(add-hook 'org-clock-out-hook 'bh/clock-out-maybe 'append)

;; AGENDA VIEW ;;

;; Do not dim blocked tasks
(setq org-agenda-compact-blocks nil)
(setq org-agenda-dim-blocked-tasks nil)
(setq org-agenda-block-separator 61)

;; Agenda log mode items to display (closed and state changes by default)
(setq org-agenda-log-mode-items (quote (closed state)))

; For tag searches ignore tasks with scheduled and deadline dates
(setq org-agenda-tags-todo-honor-ignore-options t)

(setq org-icalendar-include-body nil)
(setq org-icalendar-include-bbdb-anniversaries t)
(setq org-icalendar-include-todo t)
(setq org-icalendar-use-scheduled '(todo-start event-if-not-todo event-if-todo-not-done))

(provide 'rul-org-agenda)
#+end_src
** The =prog= module
This package contains code related to programming or markup languages
modes. As my configurations are generally small, I prefer to have them
on a single file.

#+begin_src emacs-lisp :tangle "rul-lisp/packages/rul-prog.el"
;;; rul-prog.el --- Configuration related to programming and markup
;;; languages
(use-package eglot :ensure t)

;; Go
(use-package go-mode
  :ensure t
  :init
  (progn
    (bind-key [remap find-tag] #'godef-jump))
  :config
  (add-hook 'go-mode-hook #'yas-minor-mode)
  (add-hook 'go-mode-hook 'electric-pair-mode)
  (add-hook 'go-mode-hook 'my-go-mode-hook)
  (add-hook 'before-save-hook 'gofmt-before-save))

(use-package go-eldoc
  :ensure t
  :init
  (add-hook 'go-mode-hook 'go-eldoc-setup))

;; Latex
(add-hook 'latex-mode-hook 'flyspell-mode)
(setq TeX-PDF-mode t)

(defun pdfevince ()
  (add-to-list 'TeX-output-view-style
		'("^pdf$" "." "evince %o %(outpage)")))

(add-hook  'LaTeX-mode-hook  'pdfevince  t) ; AUCTeX LaTeX mode

;; Markdown
(use-package markdown-mode
  :ensure t
  :config
  (setq auto-mode-alist
        (cons '("\\.mdwn" . markdown-mode) auto-mode-alist)))

;; Python
(use-package blacken :ensure t :defer t)
(add-hook 'python-mode-hook 'py-autopep8-enable-on-save)

;; Terraform
(use-package terraform-mode :ensure t :defer t)

;; YAML
(use-package yaml-mode :ensure t :defer t)

;; Rust
(use-package rust-mode
  :defer t
  :init
  (setq rust-mode-treesitter-derive t)
  :config
  (add-hook 'rust-mode-hook 'eglot-ensure))

(provide 'rul-prog)
#+end_src