Skip to content

Latest commit

 

History

History
553 lines (419 loc) · 16.4 KB

wal-config.org

File metadata and controls

553 lines (419 loc) · 16.4 KB

Config Package

Style Guide

To increase readability, I’ll try to unify how things are generally structured.

General musings

I’d like to move away from the cramped Lisp I’ve been writing to something I term a “relaxed” style. It’s important to let code breathe. A blank line can do a lot for readability.

I personally still favor longer functions over a collection of many one-use functions. But usually there’s a certain flow to a function, there’s a topography: this is where we are now, this is where we’ll get to.

use-package

Code after keywords

If the code after keywords serves more than a single goal, it should be grouped and each group separated by an empty line. Ideally, these groups should also have a comment if the purpose isn’t immediately clear.

This mostly applies to :init and :config blocks but can also make sense for long :custom and :bind blocks.

Keyword :demand might skip the t if an :after immediately follows, in that case they should also not be separated by a line.

Keyword Order

The order is:

  1. Include
    1. if
    2. wal-ways
  2. Load
    1. ensure
    2. load-path
    3. defer or demand
    4. after
    5. commands
    6. mode
    7. hook
  3. Configuration
    1. init
    2. config
    3. custom
  4. Bindings
    1. bind-keymap
    2. bind
    3. wal-bind
    4. general
  5. Delighting/Diminishing
    1. delight
    2. diminish

Deferred Loading

There’s no hard and fast rule there, but generally, only values 1, 2 and 3 should be used.

1 is for semi-important stand-alone packages or extensions; 2 for useful extensions, and 3 for nice-to-have extensions.

For example, the linters of language packages should use 1, their test packages 2.

Key Bindings

Prefixing

A letter may prefix an action or a nested keymap.

Commits

Commits are linted using commitlint. To set this up, load file setup/wal-setup.el and select commit hooks.

The rules are defined in commitlint.config.js and a description of all rules can be found here.

Order of Declarations

Each major section has a header block that contains the following in the following order:

  • declare-function and empty defvar
  • defgroup
  • defcustom

The order of declarations in subsections is:

  • defvar
  • defconst
  • defmacro
  • define-minor-mode
  • defun
  • defalias
  • use-package
  • other function calls

Variable Definition

The value of a variable should aim to be (or start) on the first line.

(defcustom some-variable "value"
  "Doc string."
  :type 'string
  :group 'some-group)

(defvar func-variable (expand-file-name "hello" "~")
  "Doc string.")

(defcustom some-list-variable '(a
                                list
                                of
                                items)
  "Doc string."
  :type '(repeat symbol)
  :group 'some-group)

(defconst sexp-variable (let ((var "hello"))
                          var)
  "Doc string.")

Advice

Advising functions should follow a common template.

CombinatorTemplate
:afterwal-then-<do>
:aroundwal-with{out}-<do>
:beforewal-first-<do>
:after-untilwal-otherwise-<do>
:before-whilewal-ignore-if-<sth>
:before-untilwal-in-case-of-<sth>-<do>
:filter-argswal-pick-<sth>
:filter-returnwal-adjust-by-<sth>
:overridewal-instead-<do>

Example for advising :around.

(defun wal-with-big-vertico (fun &rest args)
  "Call FUN with ARGS but increase the `vertico-count'."
  (defvar vertico-count)
  (let ((vertico-count 20))

    (apply fun args)))

(advice-add
 'consult-ripgrep :around
 #'wal-with-big-vertico)

Header

;;; wal-config.el --- Walheimat's literate Emacs configuration. -*- lexical-binding: t -*-

;; Version: 2.3.1
;; Package-Requires: ((emacs "28.1"))

;;; Commentary:
;;
;; Require all sub-packages.

;;; Code:

(eval-when-compile
  (require 'wal-useful nil t)
  (require 'wal-visuals nil t))

(declare-function consult-org-heading "ext:consult.el")
(declare-function magit-diff-range "ext:magit.el")
(declare-function posframe-delete "ext:posframe.el")
(declare-function posframe-show "ext:posframe.el")
(declare-function project-buffers "ext:project.el")
(declare-function transient-prefix "ext:transient.el")
(declare-function transient-setup "ext:transient.el")
(declare-function wal-async-process "wal-useful.el")
(declare-function wal-flycheck-file "wal-fix.el")
(declare-function wal-flycheck-file--erase "wal-fix.el")
(declare-function wal-flycheck-file--get-buffer "wal-fix.el")
(declare-function wal-tangle-library "wal.el")
(declare-function wal-transparency--param "wal-visuals.el")

(defvar vertico-preselect)
(defvar wal-default-path)
(defvar wal-emacs-config-build-path)

(defgroup wal-config nil
  "Customize configuring the packages."
  :group 'wal
  :tag "Configuration")

(defcustom wal-config-show-animation t
  "Whether to show an animated whale while editing the config."
  :type 'boolean
  :group 'wal-config)

(defcustom wal-config-animation-type 'blue
  "The whale to use in the config animation."
  :type '(choice (const :tag "Blue whale" blue)
                 (const :tag "Cachalot" cachalot))
  :group 'wal-config)

Lovable Things

Whale Animation

Animate a swimming whale in a posframe.

(defvar wal-config-animation--fins (list "-" "'" "-" ","))

(defun wal-config-animation--build-key-frames (pattern)
  "Build key frames using PATTERN."
  (seq--into-vector
   (seq-map (lambda (it)
              (format pattern it))
            wal-config-animation--fins)))

(defvar wal-config-animation--cachalot-key-frames
  (wal-config-animation--build-key-frames "(__.%s >{"))

(defvar wal-config-animation--blue-whale-key-frames
  (wal-config-animation--build-key-frames "⎝   ﬞ %s    {"))

(defvar wal-config-animation-key-frames nil)
(defvar wal-config-animation-frame-index 0)
(defvar wal-config-animation-animation-speed 0.4)
(defvar wal-config-animation-buffer "*swimming-whale*")
(defvar wal-config-animation-timer nil)

(defvar-local wal-config-animation-indirect-buffer nil)
(defvar-local wal-config-animation-parent-buffer nil)

(defvar wal-config-animation--ignored-buffers '("COMMIT_EDITMSG")
  "List of buffer names to ignore.")

(defun wal-config-animation-animate ()
  "Animate the ASCII whale."
  (with-current-buffer (get-buffer-create wal-config-animation-buffer)
    ;; Clear.
    (erase-buffer)

    ;; Render current frame.
    (let* ((frame (aref wal-config-animation-key-frames wal-config-animation-frame-index))
           (colored (propertize frame 'face `(:background ,(face-attribute 'default :background)
                                              :foreground ,(face-attribute 'cursor :background)))))

      (insert colored)

      ;; Advance to the next frame.
      (setq wal-config-animation-frame-index
            (mod
             (1+ wal-config-animation-frame-index)
             (length wal-config-animation-key-frames))))))

(defun wal-config-animation--start-animation ()
  "Start the animation.

No-op if it is already running."
  (unless wal-config-animation-timer
    ;; Set up key frames.
    (setq wal-config-animation-key-frames
          (pcase wal-config-animation-type
            ('cachalot wal-config-animation--cachalot-key-frames)
            ('blue wal-config-animation--blue-whale-key-frames)
            (_ wal-config-animation--blue-whale-key-frames)))

    ;; Make sure the first frame is animated before we display.
    (wal-config-animation-animate)

    ;; Start timer.
    (setq wal-config-animation-timer (run-with-timer
                                      0
                                      wal-config-animation-animation-speed
                                      #'wal-config-animation-animate))))

(defun wal-config-animation--stop-animation ()
  "Stop the animation if it is running.

Will not do anything if there are still buffers who display the
whale."
  (when (and wal-config-animation-timer
             (not (seq-find
                   (lambda (it) (buffer-local-value 'wal-config-animation-parent-buffer it))
                   (buffer-list))))

    (cancel-timer wal-config-animation-timer)
    (setq wal-config-animation-timer nil)

    (kill-buffer wal-config-animation-buffer)))

(defun wal-config-animation-setup ()
  "Set up the animated whale."
  ;; Queue up timer if it isn't running.
  (wal-config-animation--start-animation)

  ;; Set this buffer as the parent and create an indirect buffer of
  ;; the animation buffer.
  (setq wal-config-animation-parent-buffer (current-buffer)
        wal-config-animation-indirect-buffer (make-indirect-buffer
                                              (get-buffer wal-config-animation-buffer)
                                              (generate-new-buffer-name wal-config-animation-buffer)))

  ;; Set font size for indirect buffer.
  (wal-config-animation--set-font-height)

  ;; Set up hooks to clean up and re-display.
  (add-hook 'wal-fonts-updated-hook #'wal-config-animation--reset nil t)
  (add-hook 'kill-buffer-hook #'wal-config-animation-clean-up nil t)
  (add-hook 'window-configuration-change-hook #'wal-config-animation-display nil t))

(defun wal-config-animation--set-font-height ()
  "Set font height to double of `wal-fixed-font-height'."
  (defvar wal-fixed-font-height)

  (with-current-buffer wal-config-animation-indirect-buffer

    (let ((fheight (* 2 wal-fixed-font-height)))

      (face-remap-add-relative 'default :height fheight))))

(defun wal-config-animation--reset ()
  "Reset the animation."
  (wal-config-animation--set-font-height)
  (wal-config-animation-display))

(defun wal-config-animation-clean-up ()
  "Clean up the animation."
  ;; Delete the buffer.
  (when wal-config-animation-indirect-buffer
    (posframe-delete wal-config-animation-indirect-buffer))

  (setq wal-config-animation-parent-buffer nil
        wal-config-animation-indirect-buffer nil)

  ;; Remove animation and re-positioning hooks.
  (remove-hook 'wal-fonts-updated-hook #'wal-config-animation--reset t)
  (remove-hook 'kill-buffer-hook #'wal-config-animation-clean-up t)
  (remove-hook 'window-configuration-change-hook #'wal-config-animation-display t)

  ;; Maybe cancel the timer.
  (wal-config-animation--stop-animation))

(defun wal-config-animation-poshandler (info)
  "Position handler for ASCII whale.

INFO contains positioning information."
  (let* ((window-left (plist-get info :parent-window-left))
         (window-top (plist-get info :parent-window-top))
         (window-width (plist-get info :parent-window-width))
         (posframe-width (plist-get info :posframe-width))

         ;; Offset the frame, taking the pixel-height of a line into
         ;; account.
         (p-window (plist-get info :parent-window))
         (p-line-height (with-selected-window p-window (line-pixel-height)))
         (offset-x p-line-height)
         (offset-y p-line-height))

    (cons (- (+ window-left window-width
                (- 0 posframe-width))
             offset-x)
          (+ window-top offset-y))))

(defun wal-config-animation-hidehandler (info)
  "Check INFO whether the parent buffer is invisible."
  (and-let* ((parent (cdr (plist-get info :posframe-parent-buffer)))
             (invisible (not (get-buffer-window parent t))))))

(defun wal-config-animation-display ()
  "Display the running animation in a posframe."
  (when (require 'posframe nil t)
    (let ((default-frame-alist nil)
          (frame (posframe-show
                  wal-config-animation-indirect-buffer
                  :accept-focus nil
                  :poshandler 'wal-config-animation-poshandler
                  :posframe-parent-buffer wal-config-animation-parent-buffer
                  :hidehandler 'wal-config-animation-hidehandler)))

      (when (eq 'alpha-background (wal-transparency--param))
        (modify-frame-parameters frame `((alpha-background . ,wal-transparency)))))))

(defun wal-config-animation--maybe-display (buffer &optional no-record &rest _args)
  "Maybe display the animation for BUFFER.

This only happens for project buffers as long as NO-RECORD is not t."
  (when wal-config-show-animation
    (and-let* (((not no-record))
               (buffer (get-buffer buffer))
               ((not (and-let* ((name (buffer-file-name buffer)))
                       (seq-find (lambda (it) (string-match-p it name)) wal-config-animation--ignored-buffers))))
               ((null (buffer-local-value 'wal-config-animation-parent-buffer buffer)))
               ((memq buffer (project-buffers (project-current nil wal-default-path)))))

      (wal-config-animation-setup)

      (wal-config-animation-display))))

(defun wal-config-animation--on-find-file ()
  "Maybe display animation for new file."
  (wal-config-animation--maybe-display (current-buffer)))

(wal-on-boot config
  (advice-add 'switch-to-buffer :after #'wal-config-animation--maybe-display)
  (add-hook 'find-file-hook #'wal-config-animation--on-find-file))

Editing the Config

Minor mode for editing this config.

Version Info

(defvar wal-config-mode-map (make-sparse-keymap)
  "Map for `wal-config-mode'.")

(defun wal-describe-config-version ()
  "Describe the config's version.

This returns the tag and its annotation as propertized strings."
  (interactive)

  (let* ((default-directory wal-default-path)
         (version (propertize
                   (string-trim
                    (shell-command-to-string "git describe --abbrev=0"))
                   'face 'bold))
         (cat (propertize
               (string-trim
                (shell-command-to-string (format "git cat-file tag %s" version)))
               'face 'italic))
         (out (concat version ": " (car (last (string-lines cat))))))

    (cond
     (noninteractive out)
     (t (message out)))))

(defun wal-show-config-diff-range ()
  "Call `magit-diff-range' with the latest tag."
  (interactive)

  (let ((version (shell-command-to-string "git describe --abbrev=0")))

    (magit-diff-range (string-trim version) '("--stat"))))

The Config Itself

(defun wal-config-switch-project ()
  "Switch to the config project."
  (interactive)

  (project-switch-project wal-default-path))

(defun wal-config-lib-files ()
  "Get a list of all lib files."
  (defvar wal-lib-path)

  (nthcdr 2 (directory-files wal-lib-path t)))

(defun wal-config-consult-org-heading ()
  "Find a heading in any of library file."
  (interactive)

  (consult-org-heading nil (wal-config-lib-files)))

(defun wal-customize-group ()
  "Customize `wal' group."
  (interactive)

  (customize-group 'wal t))

(defvar wal-config--package-tag "package")

(defun wal-config-org-tags-view (&optional all-packages)
  "Show tags for the config.

Calls out to `org-tags-view' while setting `org-agenda-fiels' to
the library files of the config.

Sets up `vertico' to select the prompt to allow for usage of &
and | selectors.

If ALL-PACKAGES is t, call `org-tags-view' with a pre-constructed
matcher."
  (interactive "P")

  (defvar wal-lib-path)
  (defvar org-agenda-files)

  (let ((org-agenda-files (wal-config-lib-files))
        (vertico-preselect 'prompt))

    (if all-packages
        (org-tags-view nil wal-config--package-tag)
      (call-interactively 'org-tags-view))))

Command Map

(with-eval-after-load 'transient
  ;; Create `transient' for config package.
  (transient-define-prefix whaler ()
    "Facilitate the usage of or the working on Walheimat's config."
    [["Find"
      ("f" "project" wal-config-switch-project)
      ("h" "heading" wal-config-consult-org-heading)
      (":" "tags" wal-config-org-tags-view)]
     ["Act"
      ("t" "tangle" wal-tangle)
      ("x" "install expansion pack" junk-install)
      ("c" "customize group" wal-customize-group)
      ("u" "update" wal-update)]
     ["Help"
      ("m" "show diff" wal-show-config-diff-range)
      ("r" "show compilation result" wal-show-compilation-result)]])

  (advice-add 'whaler :around 'wal-with-delayed-transient-popup))

Footer

(provide 'wal-config)

;;; wal-config.el ends here