montchr / ceamx

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

# -*- eval: (load-file "./ceamx-dev-loader.el") -*-
#+title: Ceamx: An Emacs Configuration
#+author: Chris Montgomery
#+email: chmont@proton.me
#+language: en
#+property: header-args:emacs-lisp+ :results silent
#+property: header-args:shell+      :results replace
#+property: header-args             :mkdirp yes :tangle no :tangle-mode o444 :exports code :noweb yes :comments no

#+begin_quote
generally no real magic in this world exists
#+end_quote

---Eli Zaretskii

* Overview

This is a user configuration for GNU Emacs.

The target operating system is the NixOS distribution of GNU/Linux, though the
configuration will usually also work on macOS.  E would like Ceamx to run well
on WSL2 when E is running MS Windows, however that is a very low priority.

** Agenda

*** DONE Consider moving =lisp/lib-common.el= to =lisp/core/ceamx-lib.el=
CLOSED: [2024-05-11 Sat 21:31]

- State "DONE"       from "TODO"       [2024-05-11 Sat 21:31]
*** DONE <https://elpa.gnu.org/packages/xr.html>
CLOSED: [2024-05-03 Fri 09:43]
- State "DONE"       from "TODO"       [2024-05-03 Fri 09:43]
*** TODO <https://github.com/tarsius/org-elisp-help/blob/main/org-elisp-help.el>
*** DONE org-mode needs ~tab-width~ set to 8 otherwise constant errors
CLOSED: [2024-03-20 Wed 22:43]
- State "DONE"       from "TODO"       [2024-03-20 Wed 22:43]

  <https://github.com/doomemacs/doomemacs/commit/2757a97a30e7c4a0c2a5d2de13f2214baf09b019>

*** TODO <https://orgmode.org/worg/org-contrib/org-choose.html>


** Styleguide
:PROPERTIES:
:VISIBILITY: folded
:END:

*** Boolean Variables vs. Predicate Functions
:PROPERTIES:
:CUSTOM_ID: boolean-variables-vs.-predicate-functions
:END:
The naming for booleans and predicates is different.

**** Example
:PROPERTIES:
:CUSTOM_ID: example
:END:
#+begin_src elisp
(defvar ceamx-foo-flag t)
(defvar ceamx-is-foo-enabled t)
(defun ceamx-foo-p ()
  ;; sketchy logic (don't do this)
  (or ceamx-foo-flag ceamx-is-foo-enabled))
#+end_src

**** Explanation
:PROPERTIES:
:CUSTOM_ID: explanation
:END:
From
[[https://www.gnu.org/software/emacs/manual/html_node/elisp/Coding-Conventions.html][Coding
Conventions (GNU Emacs Lisp Reference Manual)]]:

#+begin_quote
If the purpose of a function is to tell you whether a certain condition
is true or false, give the function a name that ends in 'p' (which
stands for "predicate".  If the name is one word, add just 'p'; if the
name is multiple words, add '-p'.  Examples are =framep= and
=frame-live-p=.  We recommend to avoid using this =-p= suffix in boolean
variable names, unless the variable is bound to a predicate function;
instead, use a =-flag= suffix or names like =is-foo=.

#+end_quote

**** Org-Mode: When to wrap inline text in ~tildes~ vs. =equals=

[[info:(org) Emphasis and Monospace][info (org) Emphasis and Monospace]]

[[https://emacs.stackexchange.com/a/21870][What's the relationship between ~foo~ and =foo= in org-mode? - Emacs Stack Exchange]]


* Anatomy
:PROPERTIES:
:VISIBILITY: folded
:END:

** Init Load Order
:PROPERTIES:
:header-args: :noweb-ref init
:END:

#+begin_src emacs-lisp :noweb yes
(require 'cl-lib)

;; Core variables
(require 'ceamx-paths)
(require 'ceamx-keymaps)

;; Core functions and macros
(require 'ceamx-lib)

(setq-default user-full-name "Chris Montgomery"
              user-mail-address "chmont@proton.me")

<<ceamx-bootstrap>>

<<ceamx-init-features>>

<<ceamx-postlude>>
#+end_src

** Target Files

*** =lisp/lib-tools.el=

#+name: lib-tools-file
#+begin_src emacs-lisp :tangle lisp/lib-tools.el
<<file-prop-line(feature="lib-tools",desc="Library for miscellaneous tooling")>>

;; Copyright (c) 2024  Chris Montgomery <chmont@proton.me>

;; Author: Chris Montgomery <chmont@proton.me>
;; URL: https://git.sr.ht/~montchr/ceamx
;; Version: 0.1.0

<<file-license>>

;;; Commentary:
;;; Code:

<<lib-tools>>

<<file-footer(feature="lib-tools")>>
#+end_src


*** =early-init.el=

#+name: early-init-file
#+begin_src emacs-lisp :tangle early-init.el
<<file-prop-line(feature="early-init",desc="Early initialization file",extraprops="no-byte-compile: t;")>>

;; Copyright (c) 2022-2024  Chris Montgomery <chmont@proton.me>

;; Author: Chris Montgomery <chmont@proton.me>
;; URL: https://git.sr.ht/~montchr/ceamx
;; Version: 0.1.0

<<file-license>>

;;; Commentary:
;;; Code:

<<early-init>>

<<file-footer(feature="early-init")>>
#+end_src

*** =init.el=

#+name: init-file
#+begin_src emacs-lisp :tangle init.el
<<file-prop-line(feature="init",desc="Initialize Ceamx",extraprops="no-byte-compile: t;")>>

;; Copyright (c) 2022-2024  Chris Montgomery <chmont@proton.me>

;; Author: Chris Montgomery <chmont@proton.me>
;; URL: https://git.sr.ht/~montchr/ceamx
;; Version: 0.1.0

<<file-license>>

;;; Commentary:
;;; Code:

<<init>>

<<file-footer(feature="init")>>
#+end_src

*** =lisp/core/ceamx-lib.el=

#+name: ceamx-lib-file
#+begin_src emacs-lisp :tangle lisp/core/ceamx-lib.el
<<file-prop-line(feature="ceamx-lib",desc="Ceamx common library")>>

;; Copyright (C) 2023-2024  Chris Montgomery <chmont@proton.me>
;; Copyright (C) 2014-2023  Henrik Lissner
;; Copyright (C) 2006-2021  Steve Purcell
;; Copyright (C) 2016–2022  Radian LLC and contributors
;; Copyright (C) 2018  Adam Porter
;; Copyright (C) 2013-2021  Bailey Ling <bling@live.ca>
;; Copyright (C) 2013-2023  7696122 <7696122@gmail.com>

;; Author: Chris Montgomery <chmont@proton.me>
;;         Henrik Lissner
;;         Steve Purcell
;;         Radon Rosborough <radon@intuitiveexplanations.com>
;;         Adam Porter <adam@alphapapa.net>
;;         Bailey Ling <bling@live.ca>
;;         7696122 <7696122@gmail.com>
;; URL: https://git.sr.ht/~montchr/ceamx

<<file-license>>
<<file-license-mit>>
<<file-license-bsd-2-clause>>

;;; Commentary:
;;; Code:

<<ceamx-lib>>

<<file-footer(feature="ceamx-lib")>>
#+end_src

*** =lisp/init-env.el=

#+name: init-env-file
#+begin_src emacs-lisp :tangle lisp/init-env.el
<<file-prop-line(feature="init-env",desc="Environmental integrations")>>

;; Copyright (c) 2022-2024  Chris Montgomery <chmont@proton.me>

;; Author: Chris Montgomery <chmont@proton.me>
;; URL: https://git.sr.ht/~montchr/ceamx
;; Version: 0.1.0

<<file-license>>

;;; Commentary:
;;; Code:

<<init-env>>

<<file-footer(feature="init-env")>>
#+end_src

*** =lisp/init-ui.el=

#+name: init-ui-file
#+begin_src emacs-lisp :tangle lisp/init-ui.el
<<file-prop-line(feature="init-ui",desc="General user interface customizations")>>

;; Copyright (c) 2022-2024  Chris Montgomery <chmont@proton.me>

;; Author: Chris Montgomery <chmont@proton.me>
;; URL: https://git.sr.ht/~montchr/ceamx
;; Version: 0.1.0

<<file-license>>

;;; Commentary:
;;; Code:

<<init-ui>>

<<file-footer(feature="init-ui")>>
#+end_src

*** =lisp/init-ui-graphical.el=

#+name: init-ui-graphical-file
#+begin_src emacs-lisp :tangle lisp/init-ui-graphical.el
<<file-prop-line(feature="init-ui-graphical",desc="Appearance customizations for graphical environments")>>

;; Copyright (c) 2022-2024  Chris Montgomery <chmont@proton.me>

;; Author: Chris Montgomery <chmont@proton.me>
;; URL: https://git.sr.ht/~montchr/ceamx
;; Version: 0.1.0

<<file-license>>

;;; Commentary:
;;; Code:

<<init-ui-graphical>>

<<file-footer(feature="init-ui-graphical")>>
#+end_src

*** =lisp/config-ui.el=

#+name: config-ui-file
#+begin_src emacs-lisp :tangle lisp/config-ui.el
<<file-prop-line(feature="config-ui",desc="User options for Emacs appearance")>>

;; Copyright (c) 2022-2024  Chris Montgomery <chmont@proton.me>

;; Author: Chris Montgomery <chmont@proton.me>
;; URL: https://git.sr.ht/~montchr/ceamx
;; Version: 0.1.0

<<file-license>>

;;; Commentary:
;;; Code:

<<config-ui>>

<<file-footer(feature="config-ui")>>
#+end_src

*** =lisp/lib-ui.el=

#+name: lib-ui-file
#+begin_src emacs-lisp :tangle lisp/lib-ui.el
<<file-prop-line(feature="lib-ui",desc="Appearance helper functions")>>

;; Copyright (c) 2022-2024  Chris Montgomery <chmont@proton.me>

;; Author: Chris Montgomery <chmont@proton.me>
;; URL: https://git.sr.ht/~montchr/ceamx
;; Version: 0.1.0

<<file-license>>

;;; Commentary:
;;; Code:

<<lib-ui>>

<<file-footer(feature="lib-ui")>>
#+end_src

*** =lisp/init-dashboard.el=

#+name: init-dashboard-file
#+begin_src emacs-lisp :tangle lisp/init-dashboard.el
<<file-prop-line(feature="init-dashboard",desc="Dashboard support")>>

;; Copyright (c) 2024  Chris Montgomery <chmont@proton.me>

;; Author: Chris Montgomery <chmont@proton.me>
;; URL: https://git.sr.ht/~montchr/ceamx
;; Version: 0.1.0

<<file-license>>

;;; Commentary:
;;; Code:

<<init-dashboard>>

<<file-footer(feature="init-dashboard")>>
#+end_src

*** =lisp/init-files.el=

#+name: init-files-file
#+begin_src emacs-lisp  :tangle lisp/init-files.el
<<file-prop-line(feature="init-files",desc="File handling")>>

;; Copyright (c) 2022-2024  Chris Montgomery <chmont@proton.me>

;; Author: Chris Montgomery <chmont@proton.me>
;; URL: https://git.sr.ht/~montchr/ceamx
;; Version: 0.1.0

<<file-license>>

;;; Commentary:
;;; Code:

<<init-files>>

<<file-footer(feature="init-files")>>
#+end_src

*** =lisp/lib-files.el=

#+name: lib-files-file
#+begin_src emacs-lisp  :tangle lisp/lib-files.el
<<file-prop-line(feature="lib-files",desc="Files support support")>>

;; Copyright (C) 2022-2024  Chris Montgomery <chmont@proton.me>
;; Copyright (C) 2014-2022  Henrik Lissner
;; Copyright (C) 2006-2021  Steve Purcell
;; Copyright (C) 2008-2024  Jonas Bernoulli
;; SPDX-License-Identifier: GPL-3.0-or-later AND MIT AND BSD-2-Clause

;; Author: Henrik Lissner
;;         Vegard Øye <vegard_oye at hotmail.com>
;;         Steve Purcell
;;         Chris Montgomery <chmont@proton.me>
;;         Jonas Bernoulli <jonas@bernoul.li>
;; URL: https://git.sr.ht/~montchr/ceamx
;; Created: 23 January 2023
;; Version: 0.1.0

<<file-license>>

;; Permission is hereby granted, free of charge, to any person obtaining
;; a copy of this software and associated documentation files (the
;; "Software"), to deal in the Software without restriction, including
;; without limitation the rights to use, copy, modify, merge, publish,
;; distribute, sublicense, and/or sell copies of the Software, and to
;; permit persons to whom the Software is furnished to do so, subject to
;; the following conditions:
;;
;; The above copyright notice and this permission notice shall be
;; included in all copies or substantial portions of the Software.

;; Redistribution and use in source and binary forms, with or without
;; modification, are permitted provided that the following conditions are met:
;;
;; 1. Redistributions of source code must retain the above copyright notice, this
;;    list of conditions and the following disclaimer.
;; 2. Redistributions in binary form must reproduce the above copyright notice,
;;    this list of conditions and the following disclaimer in the documentation
;;    and/or other materials provided with the distribution.
;;
;; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
;; ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
;; WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
;; DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
;; ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
;; (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
;; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
;; ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
;; (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
;; SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

;;; Commentary:
;;; Code:

<<lib-files>>

<<file-footer(feature="lib-files")>>
#+end_src

*** =lisp/init-flycheck.el=

#+begin_src emacs-lisp :tangle lisp/init-flycheck.el
<<file-prop-line(feature="init-flycheck",desc="Flycheck support")>>

;; Copyright (c) 2024  Chris Montgomery <chmont@proton.me>

;; Author: Chris Montgomery <chmont@proton.me>
;; URL: https://git.sr.ht/~montchr/ceamx
;; Version: 0.1.0

<<file-license>>

;;; Commentary:
;;; Code:

<<init-flycheck>>

<<file-footer(feature="init-flycheck")>>
#+end_src

*** =lisp/init-flymake.el=

#+name: init-flymake-file
#+begin_src emacs-lisp :tangle lisp/init-flymake.el
<<file-prop-line(feature="init-flymake",desc="Flymake support")>>

;; Copyright (c) 2024  Chris Montgomery <chmont@proton.me>

;; Author: Chris Montgomery <chmont@proton.me>
;; URL: https://git.sr.ht/~montchr/ceamx
;; Version: 0.1.0

<<file-license>>

;;; Commentary:
;;; Code:

<<init-flymake>>

<<file-footer(feature="init-flymake")>>
#+end_src

*** =lisp/init-abbrevs.el=

#+name: init-abbrevs-file
#+begin_src emacs-lisp :tangle lisp/init-abbrevs.el
<<file-prop-line(feature="init-abbrevs",desc="Abbrevs support")>>

;; Copyright (c) 2024  Chris Montgomery <chmont@proton.me>

;; Author: Chris Montgomery <chmont@proton.me>
;; URL: https://git.sr.ht/~montchr/ceamx
;; Version: 0.1.0

<<file-license>>

;;; Commentary:
;;; Code:

<<init-abbrevs>>

<<file-footer(feature="init-abbrevs")>>
#+end_src

*** =lisp/init-completion.el=

#+name: init-completion-file
#+begin_src emacs-lisp :tangle lisp/init-completion.el
<<file-prop-line(feature="init-completion",desc="Completion enhancements")>>

;; Copyright (c) 2022-2024  Chris Montgomery <chmont@proton.me>

;; Author: Chris Montgomery <chmont@proton.me>
;; URL: https://git.sr.ht/~montchr/ceamx
;; Version: 0.1.0

<<file-license>>

;;; Commentary:
;;; Code:

<<init-completion>>

<<file-footer(feature="init-completion")>>
#+end_src

*** =lisp/lib-completion.el=

#+name: lib-completion-file
#+begin_src emacs-lisp :tangle lisp/lib-completion.el
<<file-prop-line(feature="lib-completion",desc="Completion helpers")>>

;; Copyright (c) 2022-2024  Chris Montgomery <chmont@proton.me>

;; Author: Chris Montgomery <chmont@proton.me>
;; URL: https://git.sr.ht/~montchr/ceamx
;; Version: 0.1.0

<<file-license>>

;;; Commentary:
;;; Code:

<<lib-completion>>

<<file-footer(feature="lib-completion")>>
#+end_src

*** =lisp/init-templates.el=

#+name: init-templates-file
#+begin_src emacs-lisp :tangle lisp/init-templates.el
<<file-prop-line(feature="init-templates",desc="Expandable file templates and abbrevs")>>

;; Copyright (c) 2022-2024  Chris Montgomery <chmont@proton.me>

;; Author: Chris Montgomery <chmont@proton.me>
;; URL: https://git.sr.ht/~montchr/ceamx
;; Version: 0.1.0

<<file-license>>

;;; Commentary:
;;; Code:

<<init-templates>>

<<file-footer(feature="init-templates")>>
#+end_src

*** =lisp/init-outline.el=

#+name: init-outline-file
#+begin_src emacs-lisp  :tangle lisp/init-outline.el
<<file-prop-line(feature="init-outline",desc="Customizations for outline structures")>>

;; Copyright (c) 2024  Chris Montgomery <chmont@proton.me>

;; Author: Chris Montgomery <chmont@proton.me>
;; URL: https://git.sr.ht/~montchr/ceamx
;; Version: 0.1.0

<<file-license>>

;;; Commentary:
;;; Code:

<<init-outline>>

<<file-footer(feature="init-outline")>>
#+end_src

*** =lisp/init-org.el=

#+name: init-org-file
#+begin_src emacs-lisp  :tangle lisp/init-org.el
<<file-prop-line(feature="init-org",desc="Org-Mode support")>>

;; Copyright (c) 2022-2024  Chris Montgomery <chmont@proton.me>

;; Author: Chris Montgomery <chmont@proton.me>
;; URL: https://git.sr.ht/~montchr/ceamx
;; Version: 0.1.0

<<file-license>>

;;; Commentary:
;;; Code:

<<init-org>>

<<file-footer(feature="init-org")>>
#+end_src

*** =lisp/init-search.el=

#+name: init-search-file
#+begin_src emacs-lisp  :tangle lisp/init-search.el
<<file-prop-line(feature="init-search",desc="Searching and replacing features")>>

;; Copyright (c) 2023-2024  Chris Montgomery <chmont@proton.me>

;; Author: Chris Montgomery <chmont@proton.me>
;; URL: https://git.sr.ht/~montchr/ceamx
;; Version: 0.1.0

<<file-license>>

;;; Commentary:
;;; Code:

<<init-search>>

<<file-footer(feature="init-search")>>
#+end_src

*** =lisp/lib-search.el=

#+name: lib-search-file
#+begin_src emacs-lisp  :tangle lisp/lib-search.el
<<file-prop-line(feature="lib-search",desc="Search support functions")>>

;; Copyright (c) 2022-2024  Chris Montgomery <chmont@proton.me>

;; Author: Chris Montgomery <chmont@proton.me>
;; URL: https://git.sr.ht/~montchr/ceamx
;; Version: 0.1.0

<<file-license>>

;;; Commentary:
;;; Code:

<<lib-search>>

<<file-footer(feature="lib-search")>>
#+end_src

*** =lisp/init-window.el=

#+name: init-window-file
#+begin_src emacs-lisp  :tangle lisp/init-window.el
<<file-prop-line(feature="init-window",desc="Window management")>>

;; Copyright (c) 2022-2024  Chris Montgomery <chmont@proton.me>

;; Author: Chris Montgomery <chmont@proton.me>
;; URL: https://git.sr.ht/~montchr/ceamx
;; Version: 0.1.0

<<file-license>>

;;; Commentary:
;;; Code:

<<init-window>>

<<file-footer(feature="init-window")>>
#+end_src

*** =lisp/config-window.el=

#+name: config-window-file
#+begin_src emacs-lisp  :tangle lisp/config-window.el
<<file-prop-line(feature="config-window",desc="Window management user options")>>

;; Copyright (c) 2022-2024  Chris Montgomery <chmont@proton.me>

;; Author: Chris Montgomery <chmont@proton.me>
;; URL: https://git.sr.ht/~montchr/ceamx
;; Version: 0.1.0

<<file-license>>

;;; Commentary:
;;; Code:

<<config-window>>

<<file-footer(feature="config-window")>>
#+end_src

*** =lisp/lib-window.el=

#+name: lib-window-file
#+begin_src emacs-lisp  :tangle lisp/lib-window.el
<<file-prop-line(feature="lib-window",desc="Window management support library")>>

;; Copyright (c) 2022-2024  Chris Montgomery <chmont@proton.me>
;; Copyright (C) 2023 Free Software Foundation, Inc.
;; Copyright (C) 2024  Protesilaos Stavrou

;; Author: Chris Montgomery <chmont@proton.me>
;;         Vegard Øye <vegard_oye at hotmail.com>
;;         Karthik Chikmagalur <karthik.chikmagalur@gmail.com>
;;         Protesilaos Stavrou <public@protesilaos.com>

;; Author: Chris Montgomery <chmont@proton.me>
;; URL: https://git.sr.ht/~montchr/ceamx
;; Version: 0.1.0

<<file-license>>

;;; Commentary:
;;; Code:

<<lib-window>>

<<file-footer(feature="lib-window")>>
#+end_src

*** =lisp/init-editor.el=

#+name: init-editor-file
#+begin_src emacs-lisp  :tangle lisp/init-editor.el
<<file-prop-line(feature="init-editor",desc="Editor customizations")>>

;; Copyright (c) 2022-2024  Chris Montgomery <chmont@proton.me>

;; Author: Chris Montgomery <chmont@proton.me>
;; URL: https://git.sr.ht/~montchr/ceamx
;; Version: 0.1.0

<<file-license>>

;;; Commentary:
;;; Code:

<<init-editor>>

<<file-footer(feature="init-editor")>>
#+end_src

*** =lisp/lib-editor.el=

#+name: lib-editor-file
#+begin_src emacs-lisp  :tangle lisp/lib-editor.el
<<file-prop-line(feature="lib-editor",desc="Editor support library")>>

;; Copyright (C) 2023-2024  Chris Montgomery
;; Copyright (C) 2016–2022  Radian LLC and contributors

;; Author: Chris Montgomery <chmont@proton.me>
;; URL: https://git.sr.ht/~montchr/ceamx
;; Version: 0.1.0

;; Author: Chris Montgomery <chmont@proton.me>
;;         Radon Rosborough <radon@intuitiveexplanations.com>
;; Keywords: local


<<file-license>>

;; Permission is hereby granted, free of charge, to any person obtaining a copy
;; of this software and associated documentation files (the "Software"), to deal
;; in the Software without restriction, including without limitation the rights
;; to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
;; copies of the Software, and to permit persons to whom the Software is
;; furnished to do so, subject to the following conditions:

;; The above copyright notice and this permission notice shall be included in
;; all copies or substantial portions of the Software.

;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
;; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
;; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
;; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
;; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
;; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
;; SOFTWARE.


;;; Commentary:
;;; Code:

<<lib-editor>>

<<file-footer(feature="lib-editor")>>
#+end_src

*** =lisp/config-editor.el=

#+name: config-editor-file
#+begin_src emacs-lisp  :tangle lisp/config-editor.el
<<file-prop-line(feature="config-editor",desc="User options for editing")>>

;; Copyright (c) 2024  Chris Montgomery <chmont@proton.me>

;; Author: Chris Montgomery <chmont@proton.me>
;; URL: https://git.sr.ht/~montchr/ceamx
;; Version: 0.1.0

<<file-license>>

;;; Commentary:
;;; Code:

<<config-editor>>

<<file-footer(feature="config-editor")>>
#+end_src

*** =lisp/init-workspace.el=

#+name: init-workspace-file
#+begin_src emacs-lisp :tangle lisp/init-workspace.el
<<file-prop-line(feature="init-workspace",desc="Workspaces, activities, scopes, and other organizational closures")>>

;; Copyright (c) 2022-2024  Chris Montgomery <chmont@proton.me>

;; Author: Chris Montgomery <chmont@proton.me>
;; URL: https://git.sr.ht/~montchr/ceamx
;; Version: 0.1.0

<<file-license>>

;;; Commentary:
;;; Code:

<<init-workspace>>

<<file-footer(feature="init-workspace")>>
#+end_src

*** =lisp/init-vcs.el=

#+name: init-vcs-file
#+begin_src emacs-lisp :tangle lisp/init-vcs.el
<<file-prop-line(feature="init-vcs",desc="Version control support")>>

;; Copyright (c) 2022-2024  Chris Montgomery <chmont@proton.me>

;; Author: Chris Montgomery <chmont@proton.me>
;; URL: https://git.sr.ht/~montchr/ceamx
;; Version: 0.1.0

<<file-license>>

;;; Commentary:
;;; Code:

<<init-vcs>>

<<file-footer(feature="init-vcs")>>
#+end_src

*** =lisp/config-prog.el=

#+name: config-prog-file
#+begin_src emacs-lisp :tangle lisp/config-prog.el
<<file-prop-line(feature="config-prog",desc="User options for programming modes")>>

;; Copyright (c) 2024  Chris Montgomery <chmont@proton.me>

;; Author: Chris Montgomery <chmont@proton.me>
;; URL: https://git.sr.ht/~montchr/ceamx
;; Version: 0.1.0

<<file-license>>

;;; Commentary:
;;; Code:

<<config-prog>>

<<file-footer(feature="config-prog")>>
#+end_src

*** =lisp/lib-prog.el=

#+name: lib-prog-file
#+begin_src emacs-lisp :tangle lisp/lib-prog.el
<<file-prop-line(feature="lib-prog",desc="Assorted helper callables for programming modes")>>

;; Copyright (c) 2024  Chris Montgomery <chmont@proton.me>

;; Author: Chris Montgomery <chmont@proton.me>
;; URL: https://git.sr.ht/~montchr/ceamx
;; Version: 0.1.0

<<file-license>>

;;; Commentary:
;;; Code:

<<lib-prog>>

<<file-footer(feature="lib-prog")>>
#+end_src

*** =lisp/init-lsp.el=

#+name: init-lsp-file
#+begin_src emacs-lisp :tangle lisp/init-lsp.el
<<file-prop-line(feature="init-lsp",desc="Eglot support")>>

;; Copyright (c) 2023-2024  Chris Montgomery <chmont@proton.me>

;; Author: Chris Montgomery <chmont@proton.me>
;; URL: https://git.sr.ht/~montchr/ceamx
;; Version: 0.1.0

<<file-license>>

;;; Commentary:
;;; Code:

<<init-lsp>>

<<file-footer(feature="init-lsp")>>
#+end_src

*** =lisp/init-lang-data.el=

#+name: init-lang-data-file
#+begin_src emacs-lisp :tangle lisp/init-lang-data.el
<<file-prop-line(feature="init-lang-data",desc="Language support for data syntaxes")>>

;; Copyright (c) 2023-2024  Chris Montgomery <chmont@proton.me>

;; Author: Chris Montgomery <chmont@proton.me>
;; URL: https://git.sr.ht/~montchr/ceamx
;; Version: 0.1.0

<<file-license>>

;;; Commentary:
;;; Code:

<<init-lang-data>>

<<file-footer(feature="init-lang-data")>>
#+end_src

*** =lisp/init-lang-nix.el=

#+name: init-lang-nix-file
#+begin_src emacs-lisp :tangle lisp/init-lang-nix.el
<<file-prop-line(feature="init-lang-nix",desc="Nix language support")>>

;; Copyright (c) 2022-2024  Chris Montgomery <chmont@proton.me>

;; Author: Chris Montgomery <chmont@proton.me>
;; URL: https://git.sr.ht/~montchr/ceamx
;; Version: 0.1.0

<<file-license>>

;;; Commentary:
;;; Code:

<<init-lang-nix>>

<<file-footer(feature="init-lang-nix")>>
#+end_src

*** =lisp/init-lang-php.el=

#+name: init-lang-php-file
#+begin_src emacs-lisp :tangle lisp/init-lang-php.el
<<file-prop-line(feature="init-lang-php",desc="PHP language support")>>

;; Copyright (c) 2022-2024  Chris Montgomery <chmont@proton.me>

;; Author: Chris Montgomery <chmont@proton.me>
;; URL: https://git.sr.ht/~montchr/ceamx
;; Version: 0.1.0

<<file-license>>

;;; Commentary:
;;; Code:

<<init-lang-php>>

<<file-footer(feature="init-lang-php")>>
#+end_src

*** =lisp/init-writing.el=

#+name: init-writing-file
#+begin_src emacs-lisp :tangle lisp/init-writing.el
<<file-prop-line(feature="init-writing",desc="Writing augmentation")>>

;; Copyright (c) 2024  Chris Montgomery <chmont@proton.me>

;; Author: Chris Montgomery <chmont@proton.me>
;; URL: https://git.sr.ht/~montchr/ceamx
;; Version: 0.1.0

<<file-license>>

;;; Commentary:
;;; Code:

<<init-writing>>

<<file-footer(feature="init-writing")>>
#+end_src

*** =lisp/init-tools.el=

#+name: init-tools-file
#+begin_src emacs-lisp  :tangle lisp/init-tools.el
<<file-prop-line(feature="init-tools",desc="Tools and utilities")>>

;; Copyright (c) 2023-2024  Chris Montgomery <chmont@proton.me>

;; Author: Chris Montgomery <chmont@proton.me>
;; URL: https://git.sr.ht/~montchr/ceamx
;; Version: 0.1.0

<<file-license>>

;;; Commentary:
;;; Code:

<<init-tools>>

<<file-footer(feature="init-tools")>>
#+end_src


** Partials
:PROPERTIES:
:header-args: :results replace
:END:

*** CANCELLED ~file-header~ cannot accept passthrough noweb args?
CLOSED: [2024-05-18 Sat 15:50]

- State "CANCELLED"  from "TODO"       [2024-05-18 Sat 15:50] \\
  There are many limitations to Noweb, and E expects to move away from the current approach.
*** ~file-header~

#+name: file-header-default-authors-table
| Chris Montgomery | chmont@proton.me | 2024 |   | GPL-3.0-or-later |
| Someone Else?    |                  |      |   |                  |

#+name: file-header
#+begin_src emacs-lisp :noweb yes :var feature="file-header-example" :var desc="" :var table=file-header-default-authors-table
  <<file-prop-line(feature,desc)>>

  ;; <<file-authors(table=table)>>
  <<file-extra-meta>>

  <<file-license>>

  ;;; Commentary:
  ;;; Code:
#+end_src

*** ~file-prop-line~

- Note taken on [2024-03-27 Wed 01:47] \\
  Added the ~extraprops~ =noweb= variable for additional propline values and file-local
  variables.

#+name: file-prop-line
#+header: :var feature="" :var desc="" :var extraprops=""
#+begin_src emacs-lisp
(format ";;; %s.el --- %s  -*- lexical-binding: t; %s -*-" feature desc extraprops)
#+end_src

*** ~file-extra-meta~

#+name: file-extra-meta
#+begin_src emacs-lisp
;; URL: https://codeberg.org/montchr/ceamx
;; Version: 0.1.0
#+end_src

*** TODO ~file-footer~

Emacs calls this the "file trailer" in ~generate-lisp-file-trailer~.

- [ ] Adds significantly to tangle time

#+name: file-footer
#+begin_src emacs-lisp :var feature=""
(format "(provide '%1$s)\n;;; %1$s.el ends here\n" feature)
#+end_src

*** ~file-authors~

Example table:

#+name: file-authors-example-table
| Name           | Email               | Start Year | End Year | License          |
|----------------+---------------------+------------+----------+------------------|
| Gilles Deleuze | notrees@rhizome.net |       1925 |     1995 | GPL-3.0-or-later |
| Félix Guattari | poop@dada.net       |            |          | WTFPL            |

#+name: file-authors
#+header: :var table=file-authors-example-table
#+begin_src emacs-lisp
(defun ceamx--authors-table-sanitize-year (year)
  "TODO"
  (pcase year
    ((pred numberp)
     (number-to-string year))
    ((and (pred stringp)
          (pred string-empty-p))
     nil)
    (_ year)))

(defun ceamx--authors-table-sanitize-years (start end)
  "TODO"
  (let ((years (list start end)))
    (seq-keep #'ceamx--authors-table-sanitize-year years)))

(defun ceamx-authors-format-attribution (name email)
""
  (concat name (and (not (string-empty-p email))
                    (format " <%s>" email))))

(defun ceamx-authors-format-copyright-line (author years)
  (format "Copyright (C) %s  %s" years author))

(defun ceamx--authors-table-process-row (row)
  "TODO"
  (seq-let (name email start end license) row
    (let* ((attribution (ceamx-authors-format-attribution name email))
           (years (ceamx--authors-table-sanitize-years start end))
           (year-range (string-join years "-")))
      (list
        :attribution attribution
        :email email
        :license license
        :name name
        :year-initial start
        :year-latest end
        :years year-range))))

(defun ceamx-authors-from-table (table)
  ""
  (mapcar #'ceamx--authors-table-process-row table))

(require 'subr-x)

(defun ceamx-authors-format-author-lines (authors)
  ""
  (let* ((prefix "Author: "))
                ;; FIXME: omit padding from first entry
    (concat prefix

            (mapconcat (lambda (author)
                         (concat (make-string (length prefix) (string-to-char " "))
                                 (plist-get author :attribution)))
                       authors "\n"))))

(let ((authors (ceamx-authors-from-table table)))
  (format "%s\n\n%s"
          (mapconcat (lambda (author)
                       (ceamx-authors-format-copyright-line
                        (plist-get author :attribution)
                        (plist-get author :years)))
                     authors "\n")
          (ceamx-authors-format-author-lines authors)))
#+end_src

#+RESULTS: file-authors
: Copyright (C) 1925-1995  Gilles Deleuze <notrees@rhizome.net>
: Copyright (C)   Félix Guattari <poop@dada.net>

** License Headers
*** GNU General Public License 3.0 or later (=GPL-3.0-or-later=)

#+name: file-license
#+begin_src emacs-lisp
;; This file is NOT part of GNU Emacs.

;; This file is free software: you can redistribute it and/or modify it
;; under the terms of the GNU General Public License as published by the
;; Free Software Foundation, either version 3 of the License, or (at
;; your option) any later version.
;;
;; This file is distributed in the hope that it will be useful, but
;; WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
;; General Public License for more details.
;;
;; You should have received a copy of the GNU General Public License
;; along with this file.  If not, see <http://www.gnu.org/licenses/>.
#+end_src

*** MIT License (=MIT=)

#+name: file-license-mit
#+begin_src emacs-lisp
;; Permission is hereby granted, free of charge, to any person obtaining
;; a copy of this software and associated documentation files (the
;; "Software"), to deal in the Software without restriction, including
;; without limitation the rights to use, copy, modify, merge, publish,
;; distribute, sublicense, and/or sell copies of the Software, and to
;; permit persons to whom the Software is furnished to do so, subject to
;; the following conditions:
;;
;; The above copyright notice and this permission notice shall be
;; included in all copies or substantial portions of the Software.
#+end_src

*** BSD 2-Clause License (=BSD-2-Clause=)

#+name: file-license-bsd-2-clause
#+begin_src emacs-lisp
;; Redistribution and use in source and binary forms, with or without
;; modification, are permitted provided that the following conditions are met:
;;
;; 1. Redistributions of source code must retain the above copyright notice, this
;;    list of conditions and the following disclaimer.
;; 2. Redistributions in binary form must reproduce the above copyright notice,
;;    this list of conditions and the following disclaimer in the documentation
;;    and/or other materials provided with the distribution.
;;
;; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
;; ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
;; WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
;; DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
;; ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
;; (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
;; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
;; ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
;; (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
;; SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#+end_src

** Functions to clean and re-tangle Elisp files

#+begin_src emacs-lisp :noweb yes :results silent
<<ceamx-tangle-src>>
(ceamx/tangle-fresh)
#+end_src

#+name: ceamx-tangle-src
#+begin_src emacs-lisp
(require 'f)
(require 'llama)

(require 'ob-tangle)

(defun ceamx-list-tangled-init-files ()
  "List all tangled files in `user-emacs-directory'.
Note that this is a crude approximation reflective of our
expectations but not necessarily files that were truly tangled.

The assumptions are as follows:

-- All Emacs Lisp files within the \"lisp\" subdirectory
-- early-init.el
-- init.el

If there are any Emacs Lisp files within the \"lisp\"
subdirectory that have not been created as a result of tangling,
they will also be included in the result."
  (let ((dir user-emacs-directory))
    (append
     (mapcar (##f-join dir %) '("early-init.el" "init.el"))
     (f-files (f-join dir "lisp") (##f-ext-p % "el") t))))

(defun ceamx/purge-tangled-init-files ()
  "Delete all tangled init files according to `ceamx-list-tangled-init-files'."
  (interactive)
  (dolist (file (ceamx-list-tangled-init-files))
    (f-delete file)))

(defconst ceamx-literate-config-file (locate-user-emacs-file "config.org"))

(defun ceamx/tangle-fresh (&optional src-file)
  "Purge all existing tangled init files and re-tangle.
When SRC-FILE is non-nil, it will be used as the source file to
be tangled by `org-babel-tangle-file'.  Otherwise, if SRC-FILE is
nil, the value of `ceamx-literate-config-file' will be the
default source file."
  (interactive)
  (ceamx/purge-tangled-init-files)
  (org-babel-tangle-file
   (or src-file ceamx-literate-config-file)))
#+end_src




* Maintenance

** Debugging

- Source :: [[https://github.com/progfolio/.emacs.d/blob/master/init.org?plain=1][.emacs.d/init.org at master · progfolio/.emacs.d]]
- Retrieved :: [2024-06-04 Tue 22:20]

Running this form will launch the debugger after loading a package.

This is useful for finding out when a dependency is requiring a package (perhaps
earlier than you want).

Use by tangling this block and launching Emacs with =emacs --debug-init=.

#+begin_src emacs-lisp :var file="" :results silent :tangle no
(unless (string-empty-p file)
  (eval-after-load file
    '(debug)))
#+end_src

Similarly, this variable will hit the debugger when a message matches its
regexp.

#+begin_src emacs-lisp :tangle no
(setq debug-on-message "")
#+end_src

Adding a variable watcher can be a useful way to track down initialization and
mutation of a variable.

#+begin_src emacs-lisp :tangle no
(add-variable-watcher 'org-capture-after-finalize-hook
                      (lambda (symbol newval operation where)
                        (debug)
                        (message "%s set to %s" symbol newval)))
#+end_src

#+begin_src emacs-lisp :tangle no
(setq debug-on-error t)
#+end_src


* Resources

- [[info:elisp#Tips][(elisp) Tips]]
- [[info:elisp#Minor Mode Conventions][(elisp) Minor Mode Conventions]]


* TODO The Bucket of Uncertainty
:PROPERTIES:
:header-args: :noweb-ref ceamx-postlude
:END:

Stuff that doesn't fit anywhere else and easily hides amongst the mess of files.

Or, perhaps more accurately: stuff that needs a better home.

#+begin_src emacs-lisp
(package! persistent-scratch
  ;; `with-demoted-errors' or `ignore-errors' wrapper is recommended by project
  ;; readme, as there will be an error if the autosave file does not exist.
  (with-demoted-errors
    (persistent-scratch-autosave-mode 1)))
#+end_src

#+begin_src emacs-lisp
(keymap-set ceamx-launch-map "s" #'scratch-buffer)
#+end_src

** The Keybindings of Uncertainty :keybinds:

#+begin_src emacs-lisp
(define-keymap :keymap ceamx-session-map
  "q" #'save-buffers-kill-emacs
  "Q" #'kill-emacs)
#+end_src

** =lib-simple=: The Library of Uncertain Simplicity

#+begin_src emacs-lisp :tangle lisp/lib-simple.el :noweb-ref nil
;;; lib-simple.el --- Common utility commands        -*- lexical-binding: t; -*-

;; Copyright (C) 2024  Chris Montgomery
;; Copyright (C) 2020-2023  Protesilaos Stavrou
;; Copyright (c) 2023  Bruno Boal <egomet@bboal.com>

;; Author: Chris Montgomery <chmont@proton.me>
;;         Protesilaos Stavrou <info@protesilaos.com>
;;         Bruno Boal <egomet@bboal.com>
;; Keywords: local, convenience

;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.

;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.

;; You should have received a copy of the GNU General Public License
;; along with this program.  If not, see <https://www.gnu.org/licenses/>.

;;; Commentary:

;;; Sources:

;; <https://github.com/protesilaos/dotfiles/blob/df9834d8db815920bfd7aacfaf11ef16fa089c53/emacs/.emacs.d/ceamx-lisp/ceamx-simple.el>
;; <https://github.com/BBoal/emacs-config/blob/95520648c5f2ed0784d42e98afff035a6964fd2f/bb-lisp/bb-simple.el>

;;; Code:

;;; Requirements

(require 'config-common)

;;; Variables

(defgroup ceamx-simple ()
  "Generic utilities for editing."
  :group 'editing)

(defcustom ceamx-simple-date-specifier "%F"
  "Date specifier for `format-time-string'.
Used by `ceamx/insert-date'."
  :type 'string
  :group 'ceamx-simple)

(defcustom ceamx-simple-time-specifier "%R %z"
  "Time specifier for `format-time-string'.
Used by `ceamx/insert-date'."
  :type 'string
  :group 'ceamx-simple)

;;; Commands

;;;###autoload
(defun ceamx/insert-date (&optional arg)
  "Insert the current date as `ceamx-simple-date-specifier'.

With optional prefix ARG (\\[universal-argument]) also append the
current time understood as `ceamx-simple-time-specifier'.

When region is active, delete the highlighted text and replace it
with the specified date."
  (interactive "P")
  (let* ((date ceamx-simple-date-specifier)
          (time ceamx-simple-time-specifier)
          (format (if arg (format "%s %s" date time) date)))
    (when (use-region-p)
      (delete-region (region-beginning) (region-end)))
    (insert (format-time-string format))))

(defun ceamx-simple--pos-url-on-line (char)
  "Return position of `ceamx-common-url-regexp' at CHAR."
  (when (integer-or-marker-p char)
    (save-excursion
      (goto-char char)
      (re-search-forward ceamx-common-url-regexp (line-end-position) :noerror))))

;;;###autoload
(defun ceamx/escape-url-line (char)
  "Escape all URLs or email addresses on the current line.
When called from Lisp CHAR is a buffer position to operate from
until the end of the line.  In interactive use, CHAR corresponds
to `line-beginning-position'."
  (interactive
   (list
    (if current-prefix-arg
        (re-search-forward
         ceamx-common-url-regexp
         (line-end-position) :no-error
         (prefix-numeric-value current-prefix-arg))
      (line-beginning-position))))
  (when-let ((regexp-end (ceamx-simple--pos-url-on-line char)))
    (goto-char regexp-end)
    (unless (looking-at ">")
      (insert ">")
      (when (search-backward "\s" (line-beginning-position) :noerror)
        (forward-char 1))
      (insert "<"))
    (ceamx/escape-url-line (1+ regexp-end)))
  (goto-char (line-end-position)))

;;;###autoload
(defun ceamx/escape-url-region (&optional beg end)
  "Apply `ceamx/escape-url-line' on region lines between BEG and END."
  (interactive
   (if (region-active-p)
       (list (region-beginning) (region-end))
     (error "There is no region!")))
  (let ((beg (min beg end))
        (end (max beg end)))
    (save-excursion
      (goto-char beg)
      (setq beg (line-beginning-position))
      (while (<= beg end)
        (ceamx/escape-url-line beg)
        (beginning-of-line 2)
        (setq beg (point))))))

;;;###autoload
(defun ceamx/escape-url-dwim ()
  "Escape URL on the current line or lines implied by the active region.
Call the commands `ceamx/escape-url-line' and
`ceamx/escape-url-region' ."
  (interactive)
  (if (region-active-p)
    (ceamx/escape-url-region (region-beginning) (region-end))
    (ceamx/escape-url-line (line-beginning-position))))

(provide 'lib-simple)
;;; lib-simple.el ends here
#+end_src

** =lib-text=: The Material Fabric of Uncertainty

#+begin_src emacs-lisp :tangle lisp/lib-text.el :noweb-ref nil
;;; lib-text.el --- Text utilities                   -*- lexical-binding: t; -*-

;; Copyright (C) 2023-2024  Chris Montgomery

;; Author: Chris Montgomery <chmont@proton.me>
;; Keywords: local

;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.

;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.

;; You should have received a copy of the GNU General Public License
;; along with this program.  If not, see <https://www.gnu.org/licenses/>.

;;; Commentary:

;;;; Sources:

;; <https://github.com/doomemacs/doomemacs/blob/986398504d09e585c7d1a8d73a6394024fe6f164/lisp/lib/text.el>

;;; Code:

;;;###autoload
(defvar ceamx-point-in-comment-functions ()
  "List of functions to run to determine if point is in a comment.

Each function takes one argument: the position of the point. Stops on the first
function to return non-nil.

Used by `ceamx-point-in-comment-p'.")

;;;###autoload
(defun ceamx-point-in-comment-p (&optional pos)
  "Return non-nil if POS is in a comment.
POS defaults to the current position."
  (let ((pos (or pos (point))))
    (if ceamx-point-in-comment-functions
        (run-hook-with-args-until-success 'ceamx-point-in-comment-functions pos)
      (nth 4 (syntax-ppss pos)))))

(provide 'lib-text)
;;; lib-text.el ends here
#+end_src

** TODO Consolidate ~lib-simple~ and ~lib-text~


* Preparations
:PROPERTIES:
:header-args: :noweb-ref ceamx-bootstrap
:END:

** Add the =site-lisp= directory to ~load-path~

#+begin_src emacs-lisp
(add-to-list 'load-path ceamx-site-lisp-dir)
(prependq! load-path (ceamx-subdirs ceamx-site-lisp-dir))
#+end_src

** Initialize the =ceamx= user options

#+begin_src emacs-lisp
(defgroup ceamx nil
  "User-configurable options for Ceamx."
  ;; TODO: is this group appropriate?
  :group 'file)
#+end_src

*** The user option to determine whether to load ~custom-file~

#+begin_src emacs-lisp
(defcustom ceamx-load-custom-file nil
  "Whether to load the user `custom-file' (custom.el)."
  :group 'ceamx
  :type '(boolean))
#+end_src

** Define variables describing the current environment-context :env:

#+begin_src emacs-lisp
(require 'config-env)

;; TODO: see bbatsov/prelude for prior art
(when +sys-wsl-p
  (require 'lib-env-wsl))
#+end_src

** =site-lisp/on=: Define additional Emacs event hooks

#+begin_src emacs-lisp
(require 'on)
#+end_src

** TODO DISABLED Load commonly-useful Ceamx libraries
:PROPERTIES:
:header-args: :tangle no
:END:
*** TODO Unnecessary, probably

#+begin_src emacs-lisp
;; (require 'lib-files)
#+end_src

*** TODO Improve visibility -- =site-lisp=, maybe?

The functions and macros in ~lib-elisp~ are useful when hacking on Ceamx, but
E always forgets they exist.

#+begin_src emacs-lisp
;; (require 'lib-elisp)
#+end_src

** Initialize package management

Third-party package managers should be configured in init.el directly instead
of within a `require'd file so that they may be re-initialized properly.

*** Bootstrap the package manager

Packages are installed with Elpaca.

Previously, I have used =package.el= or Nixpkgs to manage packages.  See the
[[*Alternatives]] subheading for details
**** Elpaca
***** Set the Elpaca installer version

#+begin_src emacs-lisp
(defvar elpaca-installer-version 0.7)
#+end_src

***** Show the Elpaca to its house

#+begin_src emacs-lisp
(defvar elpaca-directory (expand-file-name "elpaca/" ceamx-packages-dir))
#+end_src

***** Summon the Elpaca

The installation code only needs to be changed when the Elpaca warns about an
installer version mismatch.

This should be copied verbatim from the Elpaca documentation, sans the
definitions for ~elpaca-installer-version~ and ~elpaca-directory~.

#+begin_src emacs-lisp
(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))
#+end_src

**** Alternatives
***** Nixpkgs

I actually think this is the easiest approach, with the least amount of fiddling
necessary.

But: I would prefer using standalone/portable Emacs-specific package
management so Nix is not a hard requirement.  This is primarily because I
am eagerly anticipating the stable release of the official Emacs for
Android, or at least some established norms/idioms/best-practices for
configuration on Android.

Currently I don't see any clear path towards supporting Nix there, at
least not for a while.  I would prefer to have interoperability between
Emacs for Android and Nix for Android instead of the GNU-signed Termux
app, but AFAIK that would require building both Emacs and Nix for
Android APKs from source to self-sign, which I don't want to do right
now.

Oh yeah, and there's also WSL when I am forced to use Microsoft Windows
for something (like Adobe Acrobat).  Ideally I would use the NixOS on WSL
setup but that's a little bit of a project to integrate into my
wasteland of a system configuration repo.

***** package.el

When I forget what pain feels like, I remind myself by attempting to use
=package.el=.  Soon I find that nothing works in a sane manner, and, the
worst part: it's barely configurable as Emacs should be.  This results in
many nasty hacks.  I do not think =package.el= is ready for daily usage
in my life.

I tried leaning into package.el because:

1. It is built into Emacs, allowing for portability and predictable
   behavior across machines.  Ostensibily.
2. I thought using it would be a matter of "back-to-the-basics"/KISS.

But:

While the documentation seems thorough, I frequently need to dive into
its source code to figure out why some weird behavior is happening.
Several configurations I've referenced apply advices to its internal
functions to hammer package.el into a usable machine.

Somehow, even though both =package.el= and =use-package= are part of
Emacs, they do not work well together.  TODO: add links to issues here

package.el forces the use of =user-custom-file=, with no option to
specify a different file or otherwise change this behavior.  I am
absolutely not interested in committing =custom.el= whenever the state
of my installed packages changes.  I like the idea of a lockfile, as
every package manager should use one (except Nix, which transcends such
barbaric practices entirely), but the current state of this behavior
makes that impossible without resorting to hacks.  TODO: mention the
snippet I recently came across (saved to bookmark manager).

I've left a lot of comments throughout this configuration's explaining
some of =package.el='s unintutive and sometimes downright terrible or
broken behavior.  TODO: resurface these, which were probably deleted.

I imagine one day these issues will be resolved and stabilized in future
versions of Emacs, but until then, I would only be a reluctant user and
find myself still prone to indecision in this field.

On that note, I have since switched back to Elpaca.


*** Bind some commonly-used package management commands :keybinds:

#+begin_src emacs-lisp
(define-keymap :keymap ceamx-packages-map
  "f" #'elpaca-fetch-all
  "m" #'elpaca-merge-all
  "t" #'elpaca-try)

(keymap-set ceamx-session-map "p" '("Packages" . ceamx-packages-map))
#+end_src

**** TODO Move this global binding somewhere else... but where? :keybinds:

#+begin_src emacs-lisp
(keymap-global-set "C-c q" ceamx-session-map)
#+end_src

*** Run our custom init and startup hooks on ~elpaca-after-init-hook~

#+begin_src emacs-lisp
(add-hook 'elpaca-after-init-hook #'ceamx-after-init-hook)
(add-hook 'elpaca-after-init-hook #'ceamx-emacs-startup-hook)
#+end_src

*** Pretend file-visiting-buffers in the package directory are read-only

#+begin_src emacs-lisp
(require 'config-buffer)

(def-hook! ceamx-register-read-only-buffers-h ()
  'ceamx-after-init-hook
  "Use read-only buffers for files in some directories.
The affected directories are listed in `ceamx-buffer-read-only-dirs-list'"

  ;; Define a read-only directory class
  (dir-locals-set-class-variables
   'read-only
   '((nil . ((buffer-read-only . t)))))

  ;; Associate directories with the read-only class
  (dolist (dir ceamx-buffer-read-only-dirs-list)
    (dir-locals-set-directory-class (file-truename dir) 'read-only)))
#+end_src

*** Encourage a ~no-littering~ policy for packages to artifice in the designated areas

- Website :: <https://github.com/emacscollective/no-littering/>

By default, Emacs features and many packages default to dumping their state
files into ~user-emacs-directory~.  This makes sense for the sake of visibility.
However, because E rarely thinks about any of those machine-generated and
non-human-friendly files, they may be effectively designated as clutter.  Ceamx
offloads these sanitation duties to the =no-littering= package because it works
effectively and almost-invisibly.

In some cases, especially for new packages / package features / targets, it may
be necessary to manage such configuration by hand.

Ceamx avoids ~use-package~ here so that:

- ~no-littering~ may be installed and loaded as early as possible
- the time-consuming invocations of ~elpaca-wait~ should be kept to the absolute minimum

#+begin_src emacs-lisp
(require 'ceamx-paths)

;; These must be set prior to package load.
(setq no-littering-etc-directory ceamx-etc-dir)
(setq no-littering-var-directory ceamx-var-dir)

(elpaca no-littering
  (require 'no-littering))
#+end_src

*** Install the latest versions of some builtin features and their dependencies

Installing the latest development versions of ~eglot~ and ~magit~ (for example)
comes with the significant caveat that their dependencies often track the latest
versions of builtin Emacs libraries.  Those can be installed via GNU ELPA.

Since core libraries like ~seq~ are often dependencies of many other packages or
otherwise loaded immediately (like ~eldoc~), installation and activation of the
newer versions needs to happen upfront to avoid version conflicts and
mismatches.  For example, Ceamx does not want some package loaded earlier in init to
think it is using the builtin version of ~seq~, while a package loaded later in
init uses a differnt version.  E is not sure how realistic such a scenario might
be, or whether it would truly pose a problem, but the point is that we should
aim for consistency.

Oftentimes, these builtins must be unloaded before loading the newer version.
This applies especially to core libraries like ~seq~ or the enabled-by-default
~global-eldoc-mode~ provided by ~eldoc~, but not ~jsonrpc~, since its
functionality is specific to more niche features like inter-process
communication in the case of ~eglot~.

A feature must only be unloaded once, *before* loading the version installed by
Elpaca.  Normally, that is not an issue because the init file is only loaded
once on session startup.  But upon re-loading the init file inside a running
session, the Elpaca-installed version will become unloaded.  To prevent that,
the unloading should happen only once -- during session startup -- hence the
check for a non-nil ~after-init-time~.

#+begin_verse
I don't understand why the Elpaca-installed feature\/package only seems to be
loaded during the initial session startup?  Unless the unloading happens
conditionally based on ~after-init-time~ as described above, every time the init
file is reloaded and ~elpaca-process-queues~ runs in
~+auto-tangle-reload-init-h~, I get a bunch of errors (not warnings!) about
~eglot~ and ~org~ as missing dependencies.
#+end_verse

**** Install the latest version of ~seq~ builtin library, carefully

~magit~ requires a more recent version of ~seq~ than the version included in
Emacs 29.

Requires special care because unloading it can make other libraries freak out.
<progfolio/elpaca#216 (comment)>

#+begin_src emacs-lisp
(defun +elpaca-unload-seq (e)
  "Unload the builtin version of `seq' and continue the `elpaca' build E."
  (and (featurep 'seq) (unload-feature 'seq t))
  (elpaca--continue-build e))

(defun +elpaca-seq-build-steps ()
  "Update the `elpaca' build-steps to activate the latest version of the builtin `seq' package."
  (append (butlast (if (file-exists-p (expand-file-name "seq" elpaca-builds-directory))
                       elpaca--pre-built-steps
                     elpaca-build-steps))
          (list '+elpaca-unload-seq 'elpaca--activate-package)))

(elpaca `(seq :build ,(+elpaca-seq-build-steps)))
#+end_src

**** Install the latest version of the builtin ~jsonrpc~ library

Required by (and originally extracted from) ~eglot~.

#+begin_src emacs-lisp
(elpaca jsonrpc
  (require 'jsonrpc))
#+end_src

**** Install the latest version of the ~eldoc~ builtin library, carefully

Required by ~eglot~.

~eldoc~ requires a delicate workaround to avoid catastrophy
<progfolio/elpaca#236 (comment)>


#+begin_src emacs-lisp
(unless after-init-time
  (unload-feature 'eldoc t)
  (setq custom-delayed-init-variables '())
  (defvar global-eldoc-mode nil))

(elpaca eldoc
  (require 'eldoc)
  (global-eldoc-mode))
#+end_src

**** Install the latest version of the builtin ~eglot~ package

#+begin_src emacs-lisp
(unless after-init-time
  (when (featurep 'eglot)
    (unload-feature 'eglot)))

(elpaca eglot)
#+end_src

**** Install the latest version of the builtin ~flymake~ package

#+begin_src emacs-lisp
(unless after-init-time
  (when (featurep 'flymake)
    (unload-feature 'flymake)))

(elpaca flymake)
#+end_src

**** Install the latest version of Org-Mode

#+begin_src emacs-lisp
(unless after-init-time
  (when (featurep 'org)
    (unload-feature 'org)))

(elpaca (org :autoloads "org-loaddefs.el"))
#+end_src

*** Install the latest version of Use-Package

#+begin_src emacs-lisp
(elpaca use-package)
#+end_src

*** Integrate Elpaca and Use-Package

#+begin_src emacs-lisp
(elpaca elpaca-use-package
  (elpaca-use-package-mode))
#+end_src

*** Use-Package: Ensure package installation by default

Equivalent to manually specifying =:ensure t= in each ~use-package~ expression.

#+begin_src emacs-lisp
(setopt use-package-always-ensure t)
#+end_src

*** Elpaca Wait № 1: finish processing current queue

Reason:

- Continuing otherwise will result in race conditions on the definition of storage paths
- ~use-package~ must be loaded for byte-compilation checks in [[*Configure ~use-package~ for improved debuggability and introspectability]]

#+begin_src emacs-lisp
(elpaca-wait)
#+end_src

*** Configure ~use-package~ for improved debuggability and introspectability

#+begin_src emacs-lisp
(setopt use-package-expand-minimally nil)
(when (bound-and-true-p init-file-debug)
  (require 'use-package)
  (setopt use-package-expand-minimally nil)
  (setopt use-package-verbose t)
  (setopt use-package-compute-statistics t))
#+end_src

*** Pre-install packages adding ~use-package~ keywords

Note that these packages are likely useful even without using their ~use-package~ keywords.

**** Install ~blackout~ for adjusting modeline indicators :modeline:

- Keyword :: =:blackout=

#+begin_src emacs-lisp
(elpaca blackout
  (require 'blackout))
#+end_src

*** Elpaca Wait № 2: finish processing current queue

- Reason :: Continuing otherwise will result in race conditions where the newly-installed
~use-package~ keywords may or may not be available, resulting in sporadic
initialization errors.

#+begin_src emacs-lisp
(elpaca-wait)
#+end_src

** Install ~gcmh~ to manage running garbage collection on idle

- Website :: <https://akrl.sdf.org/>
- Code :: <https://gitlab.com/koral/gcmh>

During normal use, the GC threshold will be set to a high value.
When idle, GC will be triggered with a low threshold.

#+begin_src emacs-lisp
(package! gcmh
  (blackout 'gcmh-mode)
  (add-hook 'ceamx-emacs-startup-hook #'gcmh-mode))
#+end_src

*** Background and Rationale

While browsing many other user configs, I have noticed several slightly
different approaches to managing garbage collection, especially with the
intent of reducing startup time.

One approach is to offload this configuration to ~gcmh~, safely hiding the
details away.  =gcmh= is written by Andrea Corallo, one of the Emacs
co-maintainers, also known for introducing =gccemacs=.

As a direct response to a Reddit thread sharing =gcmh=, Eli Zaretskii recommends
caution in this field.  Corallo also weighs in.  Basically, Zaretskii recommends
not overthinking things:

[[https://old.reddit.com/r/emacs/comments/bg85qm/garbage_collector_magic_hack/eln27qh/][eli-zaretskii
comments on Garbage Collector Magic Hack]]:

#+begin_quote
My problem with the advice to make the GC threshold at such high values
begins the moment you start publishing your personal tweaks as general
advice to others. IMO, this requires at least a lot of caveats, because
your advice is likely to be followed by people whose workflows and
system configurations are very different. Simply put, you might get
others in trouble by promoting your personal hacks as "magic".
#+end_quote

From what I gather, =gcmh= has evolved a bit, so the previous criticism
no longer applies.

And, again, with more detail about fiddling carelessly with
=gc-cons-threshold=:

[[https://old.reddit.com/r/emacs/comments/yzb77m/an_easy_trick_i_found_to_improve_emacs_startup/iwz1vek/][eli-zaretskii
comments on An easy trick I found to improve Emacs start-up time]]

#+begin_quote
The GC threshold setting after init is too high, IMNSHO, and its value
seems arbitrary.

If the OP thinks that Emacs will GC as soon as it allocates 100 MiB,
then that's a grave mistake. What really happens is the first time Emacs
/considers doing GC/, if at that time more than 100 MiB have been
allocated for Lisp objects, Emacs will GC. And since neither Lisp
programs nor the user have /any/ control on how soon Emacs will decide
to check whether GC is needed, the actual amount of memory by the time
Emacs checks could be many times the value of the threshold.

My advice is to spend some time measuring the effect of increased GC
threshold on operations that you care about and that take a long enough
time to annoy, and use the lowest threshold value which produces a
tangible improvement. Start with the default value, then enlarge it by a
factor of 2 until you see only insignificant speedups. I would not
expect the value you arrive at to be as high as 100 MiB.
#+end_quote

** Install utility libraries

#+begin_src emacs-lisp
;; FIXME: remove or alias (`##' is very difficult to search for)
(use-package llama) ;  `##' lambda shorthand =>
                                        ;  <https://git.sr.ht/~tarsius/llama>

(use-package f)
#+end_src

** Install the on-demand Emacs Start-Up Profiler (~esup~) :perf:

#+begin_src emacs-lisp
(package! esup)
#+end_src

Work around an upstream bug where profiling [[jschaf/esup#85 (comment) catastrophically]] because
~esup~ tries to parse the byte-compiled version of ~cl-lib~:

#+begin_src emacs-lisp
(after! esup
  (setq esup-depth 0))
#+end_src


* TODO Load Features
:PROPERTIES:
:header-args: :noweb-ref ceamx-init-features
:END:

** TODO Miscellaneous things that should go somewhere else

#+begin_src emacs-lisp
;; Increase number of messages saved in log.
(setq message-log-max 10000)

;; Unbind `suspend-frame'.
;; TODO: provide more context
(global-unset-key (kbd "C-x C-z"))

;; "A second, case-insensitive pass over `auto-mode-alist' is time wasted."
(setopt auto-mode-case-fold nil)

;; Prevent Emacs from pinging domain names unexpectedly.
(setopt ffap-machine-p-known 'reject)
#+end_src

** TODO Sections

#+begin_src emacs-lisp
(require 'init-env)
(require 'init-input-methods)

;; Site-specific configuration, to be ignored by version control.
(require 'site-config (file-name-concat user-emacs-directory "site-config") t)

(require 'init-secrets)
#+end_src

#+begin_src emacs-lisp
;;;; Displays + Appearance

;; Load configuration settings for conditional loading.
(require 'config-ui)

(require 'init-env-tty)

(require 'init-ui)

(when (display-graphic-p)
  (require 'init-ui-graphical))
#+end_src

#+begin_src emacs-lisp
;;;; Dashboard

(require 'init-dashboard)
#+end_src

#+begin_src emacs-lisp
;;;; Keyboard support

(require 'init-keys)
(require 'init-keys-which-key)
(require 'init-keys-meow)
#+end_src

#+begin_src emacs-lisp
;;;; Windows

(require 'init-window)
(require 'init-buffer)

;; FIXME: load earlier / in another section
(require 'init-history)
#+end_src

#+begin_src emacs-lisp
;;;; Text Expansion

(require 'init-abbrevs)
#+end_src

#+begin_src emacs-lisp
;;;; Completions and Selections

(require 'init-search)
(require 'init-completion)
#+end_src

#+begin_src emacs-lisp
;;;; Help

(require 'init-help)
#+end_src

#+begin_src emacs-lisp
;;;; Actions

(require 'init-embark)

;; Projects / Files
(require 'init-project)
(require 'init-vcs)
(require 'init-files)
(require 'init-dired)
#+end_src

#+begin_src emacs-lisp
;;;; Workspaces + activities + contexts

(require 'init-workspace)
#+end_src

#+begin_src emacs-lisp
;;;; Editing

(require 'init-editor)
(require 'init-writing)
(require 'init-templates)
#+end_src

#+begin_src emacs-lisp
;;;; Outlines & Memex

(require 'init-outline)
(require 'init-org)
(require 'init-notes)
(require 'init-notes-denote)
#+end_src

#+begin_src emacs-lisp
;;;; Linting

(require 'init-flymake)
;; (require 'init-flycheck)
#+end_src

#+begin_src emacs-lisp
;;;; Tree-Sitter

(require 'init-treesitter)
#+end_src

#+begin_src emacs-lisp
;;;; Language/syntax support

(require 'config-prog)
(require 'lib-prog)

(require 'init-prog)
(require 'init-lisp)
(require 'init-lsp)

(require 'init-lang-data)
(require 'init-lang-elisp)
(require 'init-lang-html)
(require 'init-lang-js)
(require 'init-lang-lua)
(require 'init-lang-markdown)
(require 'init-lang-nix)
(require 'init-lang-php)
(require 'init-lang-shell)
(require 'init-lang-misc)

;; FIXME: this is lang support, not integration -- rename to `init-lang-nu'
(require 'init-shell-nu)
#+end_src

#+begin_src emacs-lisp
;;;; Miscellaneous

(require 'init-tools)

(require 'init-term)
(require 'init-eww)
(require 'init-printing)

(require 'init-fun)

(require 'init-controls)
#+end_src


* Before Dawn: =early-init.el=
:PROPERTIES:
:header-args: :noweb-ref early-init
:END:

- [[https://old.reddit.com/r/emacs/comments/np6ey4/how_packageel_works_with_use_package/][How package.el Works with Use Package : emacs]]

** Prevent package.el from enabling all packages before init

When nil and using the builtin package manager, ~package-initialize~ must be
invoked in the init process prior to ~require~ing any packages installed with
~package-install~.

When non-nil, there is no need to invoke ~package-initialize~.

#+begin_src emacs-lisp
(setq package-enable-at-startup nil)
#+end_src

** Set up indirect init/startup hooks

#+begin_src emacs-lisp
(defvar ceamx-after-init-hook '())
(defun ceamx-after-init-hook ()
  (run-hooks 'ceamx-after-init-hook))

(defvar ceamx-emacs-startup-hook '())
(defun ceamx-emacs-startup-hook ()
  (run-hooks 'ceamx-emacs-startup-hook))
#+end_src

** Performance

*** Language servers

<https://emacs-lsp.github.io/lsp-mode/page/performance/#increase-the-amount-of-data-which-emacs-reads-from-the-process>

#+begin_src emacs-lisp
(setenv "LSP_USE_PLISTS" "true")
(setq lsp-use-plists t)

;; Read JSON streams in 1MiB chunks instead of the default 4kB.
;;
;; Language server responses tend to be in the 800kB to 3MB range,
;; according to the lsp-mode documentation (linked above).
;;
;; This is a general LSP concern, not specific to any particular implementation.
(when (functionp 'json-serialize)
  (setq read-process-output-max (* 1024 1024)))
#+end_src

*** Minimize garbage collection during startup

[[https://old.reddit.com/r/emacs/comments/yzb77m/an_easy_trick_i_found_to_improve_emacs_startup/iwz1vek/][eli-zaretskii comments on An easy trick I found to improve Emacs start-up time]]:

#+begin_quote
My advice is to spend some time measuring the effect of increased GC threshold
on operations that you care about and that take a long enough time to annoy,
and use the lowest threshold value which produces a tangible improvement.
Start with the default value, then enlarge it by a factor of 2 until you see
only insignificant speedups. I would not expect the value you arrive at to be
as high as 100 MiB.
#+end_quote

See also:

<https://github.com/jwiegley/dot-emacs/blob/master/init.org#startup>

*** Provide insight into garbage-collection activity to inform tuning decisions

#+begin_src emacs-lisp
;; TODO: will a `init-file-debug' check work here?
(setq garbage-collection-messages t)
#+end_src

*** Prevent garbage-collection during init

#+begin_src emacs-lisp
;; NOTE: Either use `gcmh' or make sure to reset this later.  Or else!
(setq gc-cons-threshold (* 128 1024 1024)) ; 128MiB
#+end_src

*** DISABLED Simplify filename pattern-matching during init

- Note taken on [2024-03-23 Sat 03:24] \\
  Quick followup: I would not be surprised if this had something to do with adding
  ~ceamx-restore-file-name-handler-alist-h~ on ~ceamx-after-init-hook~, which is
  equivalent to ~elpaca-after-init-hook~.
- Note taken on [2024-03-23 Sat 03:01] \\
  This snippet appears to cause Emacs to lose track of its own source files in most sessions.

  ~describe-function~ and ~helpful-function~ report symbols "without a source
  file".  I did not have much to go on until I became aware of ~find-function~.
  After invoking the latter on a function whose source could not be located, I
  noticed a clue: ~find-function~ somehow knew about the source file with a =.el=
  extension.  I investigated the Emacs =lisp/= directory and saw that there are no
  =.el= files -- only =.elc= and =.el.gz=.  The =.elc= files are unreadable, but
  Emacs is still able to read the compressed =gz= archives.  I still do not know
  exactly why it forgot.

  The issue has not been consistent, which likely indicates a race condition.  For
  a while, I assumed the issue occurred in consecutive sessions after calling
  ~restart-emacs~, but that was not the case.

  Disabling this hack is the only consistent way I found to help Emacs find
  itself.  Re-enabling it caused the issue to recur.  In the end, after
  disabling, I see absolutely no difference in startup performance.
  Optimization-by-copypasta strikes again.

  This has been a very frustrating regression in many/most of my Emacs sessions.
  I rely a lot on being able to read Emacs Lisp source code to understand how
  Emacs works -- that is how I learn.  Without being able to view source, I am
  left only with a lack of clarity.

- <https://github.com/jwiegley/dot-emacs/blob/79bc2cff3a28ecd1a315609bbb607eb4ba700f76/init.org#during-loading-of-this-module-clear-file-name-handler-alist>
- <https://old.reddit.com/r/emacs/comments/3kqt6e/2_easy_little_known_steps_to_speed_up_emacs_start/>

#+begin_src emacs-lisp :tangle no
(defvar ceamx-file-name-handler-alist file-name-handler-alist)
(setq file-name-handler-alist nil)

(defun ceamx-restore-file-name-handler-alist-h ()
  "Restore the original value of the `file-name-handler-alist' variable.
Intended for use as a callback on `ceamx-after-init-hook'."
  (setq file-name-handler-alist ceamx-file-name-handler-alist)
  (makunbound 'ceamx-file-name-handler-alist))

(add-hook 'ceamx-after-init-hook #'ceamx-restore-file-name-handler-alist-h)
#+end_src

** Directories and well-known-files

*** Add directories to load path

#+begin_src emacs-lisp
;; Configure load path
(dolist (subdir '("autoloads" "lisp" "lisp/core" "lisp/lib"))
  (let ((dir (expand-file-name subdir user-emacs-directory)))
    (add-to-list 'load-path dir)))
#+end_src

*** Load custom constants describing well-known paths

See [[*=ceamx-paths= :: common path constants]]

#+begin_src emacs-lisp
(require 'ceamx-paths)
#+end_src

*** Configure ~custom-file~ location

#+begin_src emacs-lisp
;; Normally, options configured in `user-init-file' won't need to be persisted
;; to `custom-file', but by default, when using package.el for package
;; management, `package-selected-packages' will always be written to
;; `custom-file' if available.  See `init-package' for details.
(setq custom-file (expand-file-name "custom.el" user-emacs-directory))
#+end_src

*** Store packages in the designated directory

#+begin_src emacs-lisp
(setq package-user-dir ceamx-packages-dir)
#+end_src

*** Use preferred cache directories for native-comp

#+begin_src emacs-lisp
(startup-redirect-eln-cache ceamx-eln-dir)
(add-to-list 'native-comp-eln-load-path ceamx-eln-dir)
#+end_src

** Native compilation settings

#+begin_src emacs-lisp
(setq native-comp-async-report-warnings-errors 'silent)
(setq native-compile-prune-cache t)

;; Don't load outdated byte-compiled files.
;;
;; NOTE: This does not handle *recompiling* the outdated files.
;; That would need to be handled during init.
;;
;; More info: <https://github.com/emacscollective/auto-compile/blob/main/README.org>
(setq load-prefer-newer t)

;; Package installation will provoke a lot of warnings from third-party
;; packages, but there's nothing we can do about those.
(setq byte-compile-warnings nil)
#+end_src

** Inhibit early annoyances

#+begin_src emacs-lisp
;; No bells.
(setq ring-bell-function #'ignore)

;; Allow answering yes/no questions with y/n.
(setq use-short-answers t)              ; affects `yes-or-no-p'
(setq read-answer-short t)              ; affects `read-answer' (completion)
#+end_src

** Frames and window-system integration

;; FIXME: seems to behave inconsistently when server is running?

#+begin_src emacs-lisp
;; Prevent X11 from taking control of visual behavior and appearance.
(setq inhibit-x-resources t)

;; Avoid expensive frame resizing.
(setq frame-inhibit-implied-resize t)

;; Allow resizing the frame to the maximum available space on the desktop.
(setq frame-resize-pixelwise t)

;; Remove some unnecessary frame elements by default.
(scroll-bar-mode -1)
(tool-bar-mode -1)

;; `tooltip-mode' is broken for me in pgtk -- might be an Emacs bug, causes
;; constant errors when moving mouse over modeline.
;;
;; FIXME: actually, this is behaving inconsistently: disabling it does not
;; necessarily work, and toggling it off/on allows `tooltip-mode' to function
;; normally...  maybe needs to happen later in init?
(tooltip-mode -1)
#+end_src

*** Rename the default/initial frame

#+begin_src emacs-lisp
(defvar ceamx-default-frame-name "home — [ceamx]"
  "Name for the default Emacs frame.")

(defun ceamx-after-init-default-frame-name-h ()
  "Set the name for the default frame.
Simple wrapper for a call to `set-frame-name' providing
`ceamx-default-frame-name' as the NAME argument.

Intended for use as a callback on the `ceamx-after-init-hook'."
  (set-frame-name ceamx-default-frame-name))

(add-hook 'ceamx-after-init-hook #'ceamx-after-init-default-frame-name-h)
#+end_src



** Quietize startup

#+begin_src emacs-lisp
(setq inhibit-startup-screen t)
(setq initial-scratch-message nil)
(setq initial-buffer-choice nil)
#+end_src


* Common: Paths

#+begin_src emacs-lisp :tangle lisp/core/ceamx-paths.el
;;; ceamx-paths.el --- Common paths variables        -*- lexical-binding: t; -*-

;; Copyright (C) 2023-2024  Chris Montgomery

;; Author: Chris Montgomery <chmont@proton.me>
;; Keywords:

;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.

;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.

;; You should have received a copy of the GNU General Public License
;; along with this program.  If not, see <https://www.gnu.org/licenses/>.

;;; Commentary:

;; Define variables pointing to commonly-used paths.

;;; Code:

;;
;;; Functions

(defun ceamx-format-version-subdir (parent)
  "Return a path-like string for a subdirectory of PARENT based on the current Emacs version."
  (format "%s/%s.%s/"
    parent
    emacs-major-version
    emacs-minor-version))

;;
;;; Variables

(defconst ceamx-site-lisp-dir
  (concat user-emacs-directory "site-lisp/")
  "Absolute path to the site-lisp directory.")

(defconst ceamx-home-dir (file-name-as-directory (getenv "HOME"))
  "Path to user home directory.")

(defconst ceamx-xdg-config-dir
  (file-name-as-directory
   (or (getenv "XDG_CONFIG_HOME")
       (concat ceamx-home-dir ".config"))))

(defconst ceamx-xdg-cache-dir
  (file-name-as-directory
   (or (getenv "XDG_CACHE_HOME")
       (concat ceamx-home-dir ".cache"))))

(defconst ceamx-config-dir ceamx-xdg-config-dir
  "The root directory for personal configurations.")

;; TODO: rename to something like `ceamx-storage-dir' to reduce confusion
(defconst ceamx-local-dir
  (concat ceamx-xdg-cache-dir "ceamx/")
  "The root directory for local Emacs files.
Use this as permanent storage for files that are safe to share
across systems.")

(defconst ceamx-etc-dir (concat ceamx-local-dir "etc/")
  "Directory for non-volatile storage.
Use this for files that don't change much, like servers binaries,
external dependencies or long-term shared data.")

(defconst ceamx-var-dir (concat ceamx-local-dir "var/")
  "Directory for volatile storage.
Use this for files that change often, like data and cache files.")

;; FIXME: avoid usage of `expand-file-name', which is incorrect -- read its
;; documentation / the manual section.  `convert-standard-filename' may also be
;; removed in this context.  the latter is likely better off used as needed, and
;; closer to the usage rather than in this declaration.  this declaration *is*
;; the standard Unix-like filename expected by `convert-standard-filename'.
(defconst ceamx-eln-dir (convert-standard-filename
                       (file-name-as-directory
                        (expand-file-name "eln/" ceamx-var-dir)))
  "Directory for natively-compiled eln files.")

(defconst ceamx-packages-dir
  (expand-file-name (ceamx-format-version-subdir "packages")
    ceamx-local-dir)
  "Where packages are stored.
Intended for setting the value of `package-user-dir' or the
equivalent settings for third-party package managers.

Packages will be stored in subdirectories based on the current
Emacs version to prevent bytecode incompatibility.")

;;; Feature-Specific Paths

<<config-feature-paths>>

(provide 'ceamx-paths)
;;; ceamx-paths.el ends here

;; Local Variables:
;; no-byte-compile: t
;; no-native-compile: t
;; no-update-autoloads: t
;; End:
#+end_src


* Common: Keymaps

#+begin_src emacs-lisp :tangle lisp/core/ceamx-keymaps.el
;;; ceamx-keymaps.el --- Keymap declarations               -*- lexical-binding: t; -*-


;; Copyright (C) 2024  Chris Montgomery

;; Author: Chris Montgomery <chmont@proton.me>
;; Keywords: local

;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.

;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.

;; You should have received a copy of the GNU General Public License
;; along with this program.  If not, see <https://www.gnu.org/licenses/>.

;;; Commentary:

;;

;;; Code:

(require 'ceamx-lib)

(defvar-keymap ceamx-file-map)
(define-prefix-command 'ceamx-file-map)

(defvar-keymap ceamx-insert-map)
(define-prefix-command 'ceamx-insert-map)

(defvar-keymap ceamx-launch-map)
(define-prefix-command 'ceamx-launch-map)

(defvar-keymap ceamx-packages-map)
(define-prefix-command 'ceamx-packages-map)

(defvar-keymap ceamx-pairs-map)
(define-prefix-command 'ceamx-pairs-map)

(defvar-keymap ceamx-replace-map)
(define-prefix-command 'ceamx-replace-map)

(defvar-keymap ceamx-session-map)
(define-prefix-command 'ceamx-session-map)

(defvar-keymap ceamx-toggle-map)
(define-prefix-command 'ceamx-toggle-map)

(provide 'ceamx-keymaps)
;;; ceamx-keymaps.el ends here
#+end_src


* Common: Functions & Macros
:PROPERTIES:
:header-args: :noweb-ref ceamx-lib
:END:

- src :: <https://github.com/doomemacs/doomemacs/blob/03d692f129633e3bf0bd100d91b3ebf3f77db6d1/lisp/doom-lib.el>
- src :: <https://github.com/radian-software/radian/blob/9a82b6e7395b3f1f143b91f8fe129adf4ef31dc7/emacs/radian.el>
- src :: <https://github.com/doomemacs/doomemacs/blob/986398504d09e585c7d1a8d73a6394024fe6f164/lisp/doom-keybinds.el#L93C1-L109C56>

This library is a requirement of just about every customization and library
file.  It is loaded early during initialization.

General, common, and generic library functions.

Avoid any library dependencies which are not already part of Emacs.  This
library will be loaded before packages are available.

#+begin_src emacs-lisp
(require 'cl-lib)
(require 'map)
(require 'seq)
#+end_src

** General
*** ~ceamx-host-p~: Determine whether Emacs is running on a given host

#+begin_src emacs-lisp
(defun ceamx-host-p (name)
  "Whether Emacs is running on the machine NAME."
  (string= name (system-name)))
#+end_src

*** ~ceamx-unquote~: Unquote an Elisp expression

#+begin_src emacs-lisp
(defun ceamx-unquote (exp)
  "Return EXP unquoted."
  (declare (pure t) (side-effect-free t))
  (while (memq (car-safe exp) '(quote function))
    (setq exp (cadr exp)))
  exp)
#+end_src

*** ~noop!~: Make the wrapped expression do nothing

- source :: <https://protesilaos.com/emacs/dotemacs#h:3563ceb5-b70c-4191-9c81-f2f5a202c4da>

#+begin_src emacs-lisp
(defmacro noop! (&rest _body)
  "Do nothing with BODY and return nil.
Unlike `ignore', produce no side effects."
  (declare (indent defun))
  nil)
#+end_src

** Loading and Evalling
*** ~after!~: Evaluate an expression after the given feature(s) have loaded

- Note taken on [2024-03-23 Sat 15:11] \\
  Replaced the existing version with Doom's version for its logical operator
  support.  Were it not for the logical operators, ~after!~ would be nothing
  other than a fancy wrapper around ~with-eval-after-load~.

- source :: <https://github.com/doomemacs/doomemacs/blob/bbadabda511027e515f02ccd7b70291ed03d8945/lisp/doom-lib.el#L628C1-L673C1>

#+begin_src emacs-lisp
(require 'cl-lib)

(defmacro after! (package &rest body)
  "Evaluate BODY after PACKAGE have loaded.

PACKAGE is a symbol (or list of them) referring to Emacs
features (aka packages).  PACKAGE may use :or/:any and :and/:all
operators.  The precise format is:

- An unquoted package symbol (the name of a package)
    (after! package-a BODY...)
- An unquoted, nested list of compound package lists, using any combination of
  :or/:any and :and/:all
    (after! (:or package-a package-b ...)  BODY...)
    (after! (:and package-a package-b ...) BODY...)
    (after! (:and package-a (:or package-b package-c) ...) BODY...)
- An unquoted list of package symbols (i.e. BODY is evaluated once both magit
  and git-gutter have loaded)
    (after! (magit git-gutter) BODY...)
  If :or/:any/:and/:all are omitted, :and/:all are implied.

This emulates `eval-after-load' with a few key differences:

1. No-ops for package that are disabled by the user (via `package!') or not
   installed yet.
2. Supports compound package statements (see :or/:any and :and/:all above).

Since the contents of these blocks will never by byte-compiled, avoid putting
things you want byte-compiled in them! Like function/macro definitions."
  (declare (indent defun) (debug t))
  (if (symbolp package)
      (list (if (or (not (bound-and-true-p byte-compile-current-file))
                    (require package nil 'noerror))
                #'progn
              #'with-no-warnings)
            `(with-eval-after-load ',package ,@body))
    (let ((p (car package)))
      (cond ((memq p '(:or :any))
             (macroexp-progn
              (cl-loop for next in (cdr package)
                       collect `(after! ,next ,@body))))
            ((memq p '(:and :all))
             (dolist (next (reverse (cdr package)) (car body))
               (setq body `((after! ,next ,@body)))))
            (`(after! (:and ,@package) ,@body))))))

#+end_src


*** ~defer!~: Evaluate an expression after Emacs is idle for some time

- source :: <https://github.com/bling/dotemacs/blob/97c72c8425c5fb40ca328d1a711822ce0a0cfa26/core/core-boot.el#L83C1-L88C25>

#+begin_src emacs-lisp
(defmacro defer! (secs &rest body)
  "Run BODY when Emacs is idle for SECS seconds."
  (declare (indent defun) (debug t))
  `(run-with-idle-timer
    ,secs
    nil
    (lambda () ,@body)))
#+end_src

*** ~defer-until!~: Evaluate an expression when a condition is non-nil

- source :: <https://github.com/doomemacs/doomemacs/blob/03d692f129633e3bf0bd100d91b3ebf3f77db6d1/lisp/doom-lib.el#L686-L701>

#+begin_src emacs-lisp
(defmacro defer-until! (condition &rest body)
  "Run BODY when CONDITION is non-nil.
Leverages checks via `after-load-functions'.
Meant to serve as a predicated alternative to `after!'."
  (declare (indent defun) (debug t))
  `(if ,condition
       (progn ,@body)
     ,(let ((fn (intern (format "ceamx--delay-form-%s-h" (sxhash (cons condition body))))))
       `(progn
          (fset ',fn (lambda (&rest args)
                       (when ,(or condition t)
                        (remove-hook 'after-load-functions #',fn)
                        (unintern ',fn nil)
                        (ignore args)
                        ,@body)))
          (put ',fn 'permanent-local-hook t)
          (add-hook 'after-load-functions #',fn)))))
#+end_src

** Variable Mutation
*** ~appendq!~: Append lists to a symbol in place

#+begin_src emacs-lisp
(defmacro appendq! (sym &rest lists)
  "Append LISTS to SYM in place."
  `(setq ,sym (append ,sym ,@lists)))
#+end_src

*** ~prependq!~: Prepend lists to a symbol in place

#+begin_src emacs-lisp
(defmacro prependq! (sym &rest lists)
  "Prepend LISTS to SYM in place."
  `(setq ,sym (append ,@lists ,sym)))
#+end_src

*** ~appendopt!~ Append lists to an existing user option

#+begin_src emacs-lisp
(defmacro appendopt! (variable &rest lists)
  "Append LISTS to the existing user option VARIABLE.
This uses `setopt' to set the new value of VARIABLE."
  `(setopt ,variable (append ,variable ,@lists)))
#+end_src

*** ~prependopt!~: Prepend lists to an existing user option

#+begin_src emacs-lisp
(defmacro prependopt! (variable &rest lists)
  "Prepend LISTS to the existing user option VARIABLE.
This uses `setopt' to set the new value of VARIABLE."
  `(setopt ,variable (append ,@lists ,variable)))
#+end_src

*** ~delq!~: Delete an element from a list in-place

#+begin_src emacs-lisp
(defmacro delq! (elt list &optional fetcher)
  "`delq' ELT from LIST in-place.
If FETCHER is a function, ELT is used as the key in LIST (an alist)."
  `(setq ,list (delq ,(if fetcher
                          `(funcall ,fetcher ,elt ,list)
                        elt)
                ,list)))
#+end_src

*** ~pushnew!~: Push values sequentially into a list uniquely

#+begin_src emacs-lisp
;; TODO: another version to test car of alist so that new additions with the
;;       same car will override the existing list
(defmacro pushnew! (place &rest values)
  "Push VALUES sequentially into PLACE, if they aren't already present.
This is a variadic `cl-pushnew'."
  (let ((var (make-symbol "result")))
    `(dolist (,var (list ,@values) (with-no-warnings ,place))
      (cl-pushnew ,var ,place :test #'equal))))
#+end_src

** Filesystem
*** ~ceamx-subdirs~: List every non-hidden subdirectory of a parent directory

#+begin_src emacs-lisp
(defun ceamx-subdirs (parent-dir)
  "Return every non-hidden subdirectory of PARENT-DIR."
  (cl-remove-if-not
   #'file-directory-p
   (directory-files
    (expand-file-name parent-dir) t "^[^\\.]")))
#+end_src

** Hooks & Advice
*** ~def-advice!~: Macro to define and add advice to a function

#+begin_src emacs-lisp
(defmacro def-advice! (name arglist how symbol docstring &rest body)
  "Define an advice called NAME and add it to a function.
ARGLIST, DOCSTRING, and BODY are as in `defun'.

HOW and SYMBOL are as in `advice-add'.  HOW describes how to add
the newly-defined advice.  SYMBOL is the function to be advised."
  (declare (indent 2)
           (doc-string 5))
  (unless (stringp docstring)
    (error "Ceamx: advice `%S' not documented'" name))
  (unless (and (listp symbol)
               (= 2 (length symbol))
               (eq (nth 0 symbol) 'function)
               (symbolp (nth 1 symbol)))
    (error "Ceamx: advice `%S' does not sharp-quote symbol `%S'" name symbol))
  `(progn
     (defun ,name ,arglist
      ,(let ((article (if (string-match-p "^:[aeiou]" (symbol-name how))
                          "an"
                        "a")))
        (format "%s\n\nThis is %s `%S' advice for\n`%S'."
         docstring article how
         (if (and (listp symbol)
              (memq (car symbol) ''function))
             (cadr symbol)
           symbol)))
      ,@body)
     (eval-when-compile
       (declare-function ,name nil))
     (advice-add ,symbol ',how #',name)
     ',name))
#+end_src

*** ~def-hook!~: Macro to define a hook function and add it to the given hook(s)

#+begin_src emacs-lisp
(defmacro def-hook! (name arglist hooks docstring &rest body)
  "Define function NAME and add it to HOOKS.
ARGLIST is as in `defun'.  HOOKS is a list of hooks to which to
add the function, or just a single hook.  DOCSTRING and BODY are
as in `defun'."
  (declare (indent defun)
           (doc-string 4))
  (setq hooks (ensure-list (ceamx-unquote hooks)))
  (dolist (hook hooks)
    (unless (string-match-p "-\\(hook\\|functions\\)$" (symbol-name hook))
      (error "Symbol `%S' is not a hook" hook)))
  (unless (stringp docstring)
    (error "Ceamx: no docstring provided for `def-hook!'"))
  (let ((hooks-str (format "`%S'" (car hooks))))
    (dolist (hook (cdr hooks))
      (setq hooks-str (format "%s\nand `%S'" hooks-str hook)))
    `(progn
       (defun ,name ,arglist
        ,(format "%s\n\nThis function is for use in %s."
          docstring hooks-str)
        ,@body)
       (dolist (hook ',hooks)
        (add-hook hook #',name)))))
#+end_src

** Packages & User Configuration
*** ~use-feature!~: Configuration-only wrapper for ~use-package~

#+begin_src emacs-lisp
(defmacro use-feature! (name &rest args)
  "Configuration-only wrapper for `use-package', passing through NAME and ARGS.

This macro is a wrapper for `use-package' (which see) disabling package
installation by setting package installation keywords to nil.

If `use-package-always-ensure' is non-nil, its effect will be
ignored in this `use-package' macro expansion because `:ensure'
will be nil."
  (declare (indent defun))
  `(use-package ,name
     :ensure nil
     ,@args))
#+end_src

*** ~package!~: Declare a package and its initial configuration

Wrapper for ~elpaca~ to avoid having to declare its autoloads in every file.

#+begin_src emacs-lisp
(defmacro package! (order &rest body)
  "Declare a package ORDER and its initial configuration BODY.
Provides the necessary autoloads so that we can declare packages
without needing to declare autoloads for `elpaca' in every file."
  (declare (indent defun))
  `(progn
     (autoload 'elpaca "elpaca" nil nil t)
     (elpaca ,order ,@body)))
#+end_src

** Input and Keybindings
*** ~ceamx-normalize-char~ :: Angry wrapper around ~string-to-char~

Usages of this function should be replaced with the result of evaluating
~string-to-char~.

#+begin_src emacs-lisp
(defun ceamx-normalize-char (char)
  "Normalize CHAR to a valid character matching `characterp'.
CHAR may either be a valid character or a string convertable to a
character with `string-to-char'.  If CHAR is already a character
matching `characterp', then it will be returned as-is.

When CHAR is a string containing more than one character, only
the first character will be transformed.  See `string-to-char' for
more info.

This function is impure because the interpretation of CHAR can
vary based on... various reasons?"
  (declare (side-effect-free t)
           (obsolete 'string-to-char "2024-03-23"))
  (cl-assert (char-or-string-p char) t)
  (if (stringp char)
      (cond ((length= char 0)
             (user-error "Character string `%s' is empty" char))
            ((length> char 1)
             (user-error "Character string `%s' should only contain a single character" char))
            (t
             (string-to-char char)))
    char))
#+end_src

*** ~global-keys!~: Define multiple global keybindings

#+begin_src emacs-lisp
(defmacro global-keys! (&rest keys)
  "Define keybindings KEYS in the global keymap.
Wrapper for `define-keymap' with `current-global-map' as target keymap."
  (declare (indent defun) (debug t))
  `(define-keymap :keymap (current-global-map)
     ,@keys))
#+end_src

*** ~ceamx-repeatify-keymap~: convert a regular keymap to a repeat-map

[[https://old.reddit.com/r/emacs/comments/1adwnse/repeatmode_is_awesome_share_you_useful_configs/kk9vpif/][oantolin comments on Repeat-mode is awesome, share you useful configs]]

#+begin_src emacs-lisp
(defun ceamx-repeatify-keymap (repeat-map)
  "Set the `repeat-map' property on all commands bound in REPEAT-MAP."
  (named-let process ((keymap (symbol-value repeat-map)))
    (map-keymap
     (lambda (_key cmd)
       (cond
        ((symbolp cmd) (put cmd 'repeat-map repeat-map))
        ((keymapp cmd) (process cmd))))
     keymap)))
#+end_src


* Common: Strings

#+begin_src emacs-lisp :tangle lisp/config-common.el
;;; config-common.el --- Common and miscellaneous configuration settings  -*- lexical-binding: t; -*-

;; Copyright (C) 2024  Chris Montgomery
;; Copyright (C) 2020-2023  Protesilaos Stavrou

;; Author: Chris Montgomery <chmont@proton.me>
;;         Protesilaos Stavrou <info@protesilaos.com>
;; Keywords: local

;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.

;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.

;; You should have received a copy of the GNU General Public License
;; along with this program.  If not, see <https://www.gnu.org/licenses/>.

;;; Commentary:

;;; Sources:

;; <https://github.com/protesilaos/dotfiles/blob/df9834d8db815920bfd7aacfaf11ef16fa089c53/emacs/.emacs.d/prot-lisp/prot-common.el>

;;; Code:

;; via <https://github.com/protesilaos/dotfiles/blob/df9834d8db815920bfd7aacfaf11ef16fa089c53/emacs/.emacs.d/prot-lisp/prot-common.el>
(defconst ceamx-common-url-regexp
  (concat
    "~?\\<\\([-a-zA-Z0-9+&@#/%?=~_|!:,.;]*\\)"
    "[.@]"
    "\\([-a-zA-Z0-9+&@#/%?=~_|!:,.;]+\\)\\>/?")
  "Regular expression to match (most?) URLs or email addresses.")

(provide 'config-common)
;;; config-common.el ends here
#+end_src


* Environment

** Options / Variables

#+begin_src emacs-lisp :tangle lisp/config-env.el
;;; config-env.el --- Variables regarding the environment  -*- lexical-binding: t; -*-

;; Copyright (C) 2023-2024  Chris Montgomery

;; Author: Chris Montgomery <chmont@proton.me>
;; Keywords: local

;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.

;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.

;; You should have received a copy of the GNU General Public License
;; along with this program.  If not, see <https://www.gnu.org/licenses/>.

;;; Commentary:

;; Where are we?

;;; Code:

(defvar +gui-p
  (display-graphic-p))

(defvar +xorg-p
  (memq window-system '(x)))

(defvar +user-root-p
  (string-equal "root" (getenv "USER")))

(defvar +sys-mac-p
  (or (memq window-system '(mac ns))
      (eq system-type 'darwin)))

(defvar +sys-linux-p
  (eq system-type 'gnu/linux))

(defvar +env-pgtk-p
  (bound-and-true-p pgtk-initialized)
  "Whether Emacs is running with pure-GTK windowing.")

;; via <https://emacsredux.com/blog/2021/12/19/wsl-specific-emacs-configuration/>
(defvar +sys-wsl-p
  (and (eq system-type 'gnu/linux)
    (or (getenv "WSLENV")
      (getenv "WSL_DISTRO_NAME")))
  "Whether Emacs is currently running in WSL.")

(defvar +env-dumb-p
  (string= (getenv "TERM") "dumb"))

;; TODO: is this really the way? and is it even necessary?
(defvar +env-iterm-p
  (string= (getenv "TERM_PROGRAM") "iTerm.app"))

(defvar +env-xterm-p
  (not (string-empty-p (getenv "XTERM_VERSION"))))

(defvar +env-gnome-terminal-p
  (string= (getenv "COLORTERM") "gnome-terminal"))

(defvar +env-konsole-p
  (not (string-empty-p (getenv "KONSOLE_PROFILE_NAME"))))

(defvar +env-apple-terminal-p
  (string= (getenv "TERM_PROGRAM") "Apple_Terminal"))

(provide 'config-env)
;;; config-env.el ends here
#+end_src

** Customizations
:PROPERTIES:
:header-args: :noweb-ref init-env
:END:

#+begin_src emacs-lisp
(require 'config-env)
(require 'ceamx-lib)
#+end_src

#+begin_src emacs-lisp
;; Disable unnecessary OS-specific command-line options.
(unless +sys-mac-p
  (setq command-line-ns-option-alist nil))
(unless +sys-linux-p
  (setq command-line-x-option-alist nil))
#+end_src

#+begin_src emacs-lisp
(package! exec-path-from-shell
  (require 'exec-path-from-shell)
  (dolist (var '("SSH_AUTH_SOCK" "SSH_AGENT_PID" "GPG_AGENT_INFO" "LANG" "LC_CTYPE" "NIX_SSL_CERT_FILE" "NIX_PATH" "LSP_USE_PLISTS"))
    (add-to-list 'exec-path-from-shell-variables var))
  (exec-path-from-shell-initialize))
#+end_src

*** Make temporary buffers inherit buffer-local environment variables with ~inheritenv~

- website :: <https://github.com/purcell/inheritenv>

#+begin_src emacs-lisp
(package! inheritenv
  (with-eval-after-load 'exec-path-from-shell
    (require 'inheritenv)))
#+end_src

*** ~with-editor~: Ensure shell/term modes use session as =$EDITOR=

#+begin_src emacs-lisp
(package! with-editor
  (keymap-global-set "<remap> <async-shell-command>"
                     #'with-editor-async-shell-command)
  (keymap-global-set "<remap> <shell-command>"
                     #'with-editor-shell-command)

  (add-hook 'shell-mode-hook #'with-editor-export-editor)
  (add-hook 'eshell-mode-hook #'with-editor-export-editor)
  (add-hook 'term-exec-hook #'with-editor-export-editor)

  ;; Make sure that `eat' does not break `magit-commit'.
  ;; <https://codeberg.org/akib/emacs-eat/issues/55#issuecomment-871388>
  (with-eval-after-load 'eat
    (add-hook 'eat-mode-hook #'shell-command-with-editor-mode)))
#+end_src

***  Support integration with Direnv via the ~envrc~ package

- src :: <https://github.com/purcell/envrc>
- upstream :: <https://github.com/direnv/direnv>

Q: How does this differ from `direnv.el`?

<https://github.com/wbolster/emacs-direnv> repeatedly changes the global
Emacs environment, based on tracking what buffer you're working on.

Instead, `envrc.el` simply sets and stores the right environment in each
buffer, as a buffer-local variable.

#+begin_src emacs-lisp
(package! envrc
  (with-eval-after-load 'exec-path-from-shell
    (envrc-global-mode)))
#+end_src

#+begin_src emacs-lisp
(elpaca-wait)
#+end_src

*** TRAMP Support

#+begin_src emacs-lisp
(setopt tramp-default-method "ssh")
(setopt tramp-default-remote-shell "/bin/bash")
(setopt tramp-connection-timeout (* 60 10))
;; Do not auto-save remote files. Note the reversed logic.
(setopt remote-file-name-inhibit-auto-save-visited t)

(after! tramp
  (dolist (path '("~/.local/bin"
                  "~/.nix-profile/bin"
                  "~/.local/state/nix/profiles/profile/bin/"
                  "/nix/var/nix/profiles/default/bin"
                  "/run/current-system/sw/bin"))
    (add-to-list 'tramp-remote-path path)))
#+end_src

** Terminal/TTY Support

#+begin_src emacs-lisp :tangle lisp/init-env-tty.el
;;; init-env-tty.el --- TTY environment support -*- lexical-binding: t -*-

;; Copyright (c) 2023-2024 Chris Montgomery
;; Copyright (c) 2006-2021 Steve Purcell

;; Author: Steve Purcell
;;         Chris Montgomery <chmont@proton.me>
;; URL: https://git.sr.ht/~montchr/ceamx
;; Created: 20 July 2023

;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.

;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.

;; You should have received a copy of the GNU General Public License
;; along with this program.  If not, see <https://www.gnu.org/licenses/>.

;; Redistribution and use in source and binary forms, with or without
;; modification, are permitted provided that the following conditions are met:

;; 1. Redistributions of source code must retain the above copyright notice, this
;;    list of conditions and the following disclaimer.
;; 2. Redistributions in binary form must reproduce the above copyright notice,
;;    this list of conditions and the following disclaimer in the documentation
;;    and/or other materials provided with the distribution.

;; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
;; ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
;; WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
;; DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
;; ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
;; (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
;; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
;; ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
;; (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
;; SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

;;; Commentary:

;; Specialized configurations for Emacs running in a TTY / terminal emulator
;; environment.

;; FIXME: most of this should be loaded conditionally

;;; Code:

(autoload 'mwheel-install "mwheel")

(defun ceamx/console-frame-setup ()
  (xterm-mouse-mode 1)
  (mwheel-install))

;; Make the mouse wheel scroll.
(global-set-key [mouse-4] (lambda () (interactive) (scroll-down 1)))
(global-set-key [mouse-5] (lambda () (interactive) (scroll-up 1)))

;; (add-hook 'after-make-console-frame-hooks 'ceamx/console-frame-setup)

(provide 'init-env-tty)
;;; init-env-tty.el ends here
#+end_src

** WSL Support

#+begin_src emacs-lisp :tangle lisp/lib-env-wsl.el
;;; lib-env-wsl.el --- Helpers for WSL environments  -*- lexical-binding: t; -*-

;; Copyright (C) 2024  Chris Montgomery

;; Author: Chris Montgomery <chmont@proton.me>
;; Keywords: local

;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.

;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.

;; You should have received a copy of the GNU General Public License
;; along with this program.  If not, see <https://www.gnu.org/licenses/>.

;;; Commentary:

;;; Sources:

;; <https://emacsredux.com/blog/2021/12/19/wsl-specific-emacs-configuration/>
;; <https://emacsredux.com/blog/2021/12/19/using-emacs-on-windows-11-with-wsl2/>

;;; Code:

(require 'config-env)

(defun ceamx-wsl/copy-selected-text (start end)
  "In WSL, copy text region with START and END to the host clipboard."
  (interactive "r")
  (if (use-region-p)
    (let ((text (buffer-substring-no-properties start end)))
      (shell-command (concat "echo '" text "' | clip.exe")))))

(provide 'lib-env-wsl)
;;; lib-env-wsl.el ends here
#+end_src


* Secrets

** Customizations

#+begin_src emacs-lisp :tangle lisp/init-secrets.el
;;; init-secrets.el --- Support for secretive operations  -*- lexical-binding: t; -*-

;; Copyright (C) 2024  Chris Montgomery

;; Author: Chris Montgomery <chmont@proton.me>
;; Keywords: local

;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.

;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.

;; You should have received a copy of the GNU General Public License
;; along with this program.  If not, see <https://www.gnu.org/licenses/>.

;;; Commentary:

;; GnuPG, pinentry, ~auth-source~, Unix password store, etc.

;;; Sources:

;; <https://github.com/jwiegley/dot-emacs/blob/9d595c427136e2709dee33271db1a658493265bd/init.org#auth-source-pass>

;;; Code:

(require 'epg)
(require 'auth-source)
(require 'auth-source-pass)

(require 'ceamx-paths)
(require 'ceamx-lib)

;;; Configure secrets lookup with ~auth-source~ and the Unix password store

;; Ensure secrets and auth credentials are not stored in plaintext (the default).
;;
;; It's best to list only a single file here to avoid confusion about where
;; secrets might be stored.
(setopt auth-sources (list "~/.authinfo.gpg"))

;; TODO: provide explanation as to why these functions are named like so -- they just magically work..?
(use-feature! auth-source-pass
  :preface
  (defvar auth-source-pass--cache (make-hash-table :test #'equal))

  (defun auth-source-pass--reset-cache ()
    (setq auth-source-pass--cache (make-hash-table :test #'equal)))

  (defun auth-source-pass--read-entry (entry)
    "Return a string with the file content of ENTRY."
    (run-at-time 45 nil #'auth-source-pass--reset-cache)
    (let ((cached (gethash entry auth-source-pass--cache)))
      (or cached
        (puthash
          entry
          (with-temp-buffer
            (insert-file-contents (expand-file-name
                                    (format "%s.gpg" entry)
                                    (getenv "PASSWORD_STORE_DIR")))
            (buffer-substring-no-properties (point-min) (point-max)))
          auth-source-pass--cache))))

  (defun ceamx-auth-source-pass-list-items ()
    "Return a list of all password store items."
    (let ((store-dir (getenv "PASSWORD_STORE_DIR")))
      (mapcar
        (lambda (file)
          (file-name-sans-extension (file-relative-name file store-dir)))
        (directory-files-recursively store-dir "\.gpg$"))))

  :config
  (auth-source-pass-enable))

;;; Use Emacs for pinentry

(use-feature! epg
  :defer 2
  :config
  (setopt epg-pinentry-mode 'loopback))

(provide 'init-secrets)
;;; init-secrets.el ends here
#+end_src

** Library

#+begin_src emacs-lisp :tangle lisp/lib-secrets.el
;;; lib-secrets.el --- Helpers for secrets           -*- lexical-binding: t; -*-

;; Copyright (C) 2024  Chris Montgomery

;; Author: Chris Montgomery <chmont@proton.me>
;; Keywords: local

;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.

;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.

;; You should have received a copy of the GNU General Public License
;; along with this program.  If not, see <https://www.gnu.org/licenses/>.

;;; Commentary:

;;

;;; Code:

(eval-when-compile
  (require 'auth-source)
  (require 'auth-source-pass))

;; via <https://github.com/jwiegley/dot-emacs/blob/9d595c427136e2709dee33271db1a658493265bd/init.org#lookup-a-password-using-auth-source>
(defun ceamx-lookup-password (host user port)
  (require 'auth-source)
  (require 'auth-source-pass)
  (let ((auth (auth-source-search :host host :user user :port port)))
    (if auth
      (let ((secretf (plist-get (car auth) :secret)))
        (if secretf
          (funcall secretf)
          (error "Auth entry for %s@%s:%s has no secret!"
            user host port)))
      (error "No auth entry found for %s@%s:%s" user host port))))

(provide 'lib-secrets)
;;; lib-secrets.el ends here
#+end_src


* Appearance
:PROPERTIES:
:header-args: :noweb-ref init-ui
:END:

#+begin_src emacs-lisp
(require 'ceamx-paths)
(require 'config-env)

(require 'ceamx-lib)
(require 'lib-ui)
#+end_src


** Cursor

#+begin_src emacs-lisp
;; Modal keybinding systems will change the cursor dynamically to indicate current state.
;; This value matches what I expect in an "insert" mode.
(setq-default cursor-type 'bar)

;; Enable cursor blinking.
(blink-cursor-mode 1)

;; Seeing a cursor in a window other than the active window is pretty confusing.
(setq-default cursor-in-non-selected-windows nil)
#+end_src

** Customize the Customization buffer and menu interface

#+begin_src emacs-lisp
(setopt custom-theme-allow-multiple-selections nil)

(setopt custom-unlispify-menu-entries nil)
(setopt custom-unlispify-tag-names nil)
(setopt custom-unlispify-remove-prefixes nil)
#+end_src

** Layout Utilities
*** =grid=

- Source :: [[https://github.com/ichernyshovvv/grid.el][ichernyshovvv/grid.el]]
- Retrieved :: [2024-06-07 Fri 11:45]

#+begin_quote
This library allows you to put text data into boxes and align them horizontally,
applying margin, padding, borders.
#+end_quote

#+begin_src emacs-lisp
(package! (grid :host github :repo "ichernyshovvv/grid.el"))
#+end_src

** Menus (Hydratics & Transients)

Required as dependencies for many packages, either as more recent versions
than those available in Emacs (e.g. ~transient ~IIRC), or, including some
(like ~nix-mode~) who don't seem to declare them.

*** Hydra

- Documentation :: <https://github.com/jerrypnz/major-mode-hydra.el/#pretty-hydra>

#+begin_src emacs-lisp
(package! hydra)
(package! pretty-hydra)
#+end_src

*** Transient

#+begin_src emacs-lisp
(package! transient)
(package! magit-section)
#+end_src

**** TODO Allow quitting ~transient~ menus with =ESC=

#+begin_src emacs-lisp
(with-eval-after-load 'transient
  (defvar transient-map)
  (declare-function transient-quit-one "transient")

  ;; Always close transient with ESC
  ;; FIXME: meow overrides this. waiting until it loads does not help.
  (keymap-set transient-map "ESC" #'transient-quit-one))
#+end_src

** Theme
*** Configure the preferred theme family

#+begin_src emacs-lisp :noweb-ref config-ui
(defcustom ceamx-ui-theme-family 'modus
  "Set of themes to load.
Valid values are the symbols `ef', `modus', and `standard', which
reference the `ef-themes', `modus-themes', and `standard-themes',
respectively.

A nil value does not load any of the above (use Emacs without a
theme)."
  :group 'ceamx
  :type '(choice :tag "Set of themes to load" :value modus
          (const :tag "The `ef-themes' module" ef)
          (const :tag "The `modus-themes' module" modus)
          (const :tag "The `standard-themes' module" standard)
          (const :tag "Do not load a theme module" nil)))
#+end_src

*** Configure the preferred light and dark themes

#+begin_src emacs-lisp :noweb-ref config-ui
(defcustom ceamx-ui-theme-light 'modus-operandi-tinted
  "The default light theme."
  :group 'ceamx
  :type 'symbol)

(defcustom ceamx-ui-theme-dark 'modus-vivendi-tinted
  "The default dark theme."
  :group 'ceamx
  :type 'symbol)
#+end_src

*** Consider all themes "safe"

#+begin_src emacs-lisp
(setopt custom-safe-themes t)
#+end_src

*** Ensure themes are applied in new frames to prevent flashing

- Source :: <https://protesilaos.com/emacs/dotemacs#h:7d3a283e-1595-4692-8124-e0d683cb15b2>

#+begin_src emacs-lisp :noweb-ref lib-ui
;; via prot-emacs
(defun ceamx-ui-re-enable-theme-in-frame (_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
`ceamx-ui-theme-no-bright-flash'."
  (when-let ((theme (car custom-enabled-themes)))
    (enable-theme theme)))
#+end_src

#+begin_src emacs-lisp
(add-hook 'after-make-frame-functions #'ceamx-ui-re-enable-theme-in-frame)
#+end_src

*** Add a custom hook ~ceamx-after-enable-theme-hook~ to run after enabling a theme

- Source :: <jdtsmith/kind-icon#34 (comment)>

#+begin_src emacs-lisp
(defvar ceamx-after-enable-theme-hook nil)

(defun ceamx-after-enable-theme (&rest _args)
  "Hook to run after enabling theme."
  (run-hooks 'ceamx-after-enable-theme-hook))

(advice-add 'enable-theme :after #'ceamx-after-enable-theme)
#+end_src

*** Modus Themes 📦

- Website :: <https://protesilaos.com/modus-themes/>

#+begin_src emacs-lisp
(package! modus-themes
  (require 'modus-themes)

  (setopt modus-themes-italic-constructs t)
  (setopt modus-themes-bold-constructs nil)
  (setopt modus-themes-mixed-fonts t)
  (setopt modus-themes-variable-pitch-ui nil)
  (setopt modus-themes-disable-other-themes t)
  (setopt modus-themes-to-toggle '(modus-operandi-tinted modus-vivendi))

  (setopt modus-themes-headings
          '((agenda-structure . (variable-pitch light 2.2))
            (agenda-date . (variable-pitch regular 1.3))
            (t . (regular 1.15))))

  (let ((overrides '((cursor blue)

                     ;; Syntax
                     (builtin magenta)
                     (comment red-faint)
                     (constant magenta-cooler)
                     (docstring magenta-faint)
                     (docmarkup green-faint)
                     (fnname magenta-warmer)
                     (keybind green-cooler)
                     (keyword cyan)
                     (preprocessor cyan-cooler)
                     (string red-cooler)
                     (type magenta-cooler)
                     (variable blue-warmer)
                     (rx-construct magenta-warmer)
                     (rx-backslash blue-cooler)

                     ;; Buttons
                     (bg-button-active bg-main)
                     (fg-button-active fg-main)
                     (bg-button-inactive bg-inactive)
                     (fg-button-inactive "gray50")

                     ;; Mode-line
                     (bg-mode-line-active bg-lavender)
                     (fg-mode-line-active fg-main)
                     (border-mode-line-active bg-lavender)
                     (border-mode-line-inactive unspecified)

                     ;; Fringe
                     (fringe unspecified)

                     ;; Prompts
                     ;; (fg-prompt fg-main)
                     ;; not really subtle! too loud.
                     ;; (bg-prompt bg-yellow-subtle)

                     ;; Pair-matching (parens)
                     (bg-paren-match unspecified)
                     (fg-paren-match magenta-intense)
                     (underline-paren-match magenta-intense)

                     ;; Link styles
                     ;; (underline-link border)
                     ;; (underline-link-visited border)
                     )))
    (setopt modus-operandi-palette-overrides overrides
            modus-vivendi-palette-overrides overrides)))
#+end_src

*** Ef-Themes 📦

- Website :: <https://protesilaos.com/emacs/ef-themes>

#+begin_src emacs-lisp
(defvar ceamx-font-headings-style-alist)
#+end_src

#+begin_src emacs-lisp
(package! ef-themes
  (require 'ef-themes)

  (setopt ef-themes-to-toggle '(ef-night ef-frost)
          ef-themes-mixed-fonts t
          ef-themes-variable-pitch-ui nil))
#+end_src

*** Circadian Theme Phasing

#+begin_src emacs-lisp :noweb-ref config-ui
(defcustom ceamx-ui-theme-circadian-interval nil
  "The circadian theme switching interval.
Value may be `period', `solar', or nil, corresponding
respectively to period-based switching with `theme-buffet' or
sunrise/sunset toggling from the combination of the `solar'
library and the `circadian' package.

A nil value means to disable automatic theme switching.
Theme-switching commands `ceamx/light' and `ceamx/dark' will
unconditionally use `ceamx-ui-theme-default-light' and
`ceamx-ui-theme-default-dark', respectively."
  :group 'ceamx
  :type '(choice :tag "Circadian theme switching interval" :value nil
          (const :tag "Time periods via `theme-buffet'" :value buffet)
          (const :tag "Sunrise or sunset via `solar' and `circadian'" :value solar)))
#+end_src

#+begin_src emacs-lisp :tangle no
(require 'cal-dst)

(require 'config-ui)

(require 'ceamx-lib)
#+end_src

**** TODO Sunrise/sunset interval via ~solar~ and ~circadian~

Recently in Emacs 29, these would behave erratically, locking up Emacs during
the minute of sundown.

#+begin_src emacs-lisp :tangle no
(use-feature! solar
  :when (eq 'solar ceamx-ui-theme-circadian-interval)

  :config
  (setopt calendar-latitude 39.968)
  (setopt calendar-longitude -75.133))

(use-package circadian
  :when (eq 'solar ceamx-ui-theme-circadian-interval)
  :ensure t
  :demand t
  :after solar

  :commands (circadian-setup)

  :init
  (setopt circadian-themes `((:sunrise . ,ceamx-ui-theme-light)
                             (:sunset . ,ceamx-ui-theme-dark)))
  (circadian-setup))
#+end_src

**** TODO Phase-of-day interval via ~theme-buffet~

- Website :: <https://git.sr.ht/~bboal/theme-buffet>

  #+begin_quote
The theme-buffet package arranges to automatically change themes during specific
times of the day or at fixed intervals. The collection of themes is
customisable, with the default options covering the built-in Emacs themes as
well as Prot's modus-themes and ef-themes.
#+end_quote

#+begin_src emacs-lisp :noweb-ref config-ui
(defconst ceamx-ui-theme-buffet-dark-periods
  '(:night :twilight :evening))

(defconst ceamx-ui-theme-buffet-light-periods
  '(:morning :day :afternoon))
#+end_src

#+begin_src emacs-lisp :tangle no
(use-package theme-buffet
  :ensure t
  :demand t
  :when (eq 'buffet ceamx-ui-theme-circadian-interval)

  :commands (theme-buffet-modus-ef)
  :defines (theme-buffet-menu)

  :init

  ;; Take Daylight Savings Time offset into account for time period boundaries.
  ;; I am not sure why the additional `1+' is necessary, but this is copied from
  ;; the author's recommendation.
  ;; via <https://git.sr.ht/~bboal/theme-buffet/tree/06f1be349e9c3d124520b18742911307de9abda3/item/theme-buffet.el#L68-70>
  (setopt theme-buffet-time-offset (1+ (/ (cadr (calendar-current-time-zone)) 60)))

  (setopt theme-buffet-menu 'end-user)

  (setopt theme-buffet-end-user
          '(:night (;; ef-autumn
                    ef-duo-dark
                    ef-trio-dark
                    ef-night
                    ef-winter
                    ef-dark)
            :twilight (ef-bio
                       ef-cherie
                       ef-dream
                       ef-rosa
                       modus-vivendi)
            :morning (ef-elea-light
                      ef-maris-light
                      ef-reverie
                      ef-spring)
            :day (ef-frost
                  ef-light
                  ef-spring
                  ef-trio-light
                  modus-operandi)
            :afternoon (ef-cyprus
                        ;; ef-arbutus
                        ef-day
                        ef-duo-light
                        ef-kassio
                        ef-melissa-light
                        ef-reverie
                        ef-summer
                        modus-operandi-tinted)
            :evening (ef-elea-dark
                      ef-dream
                      ef-maris-dark
                      ;; ef-melissa-dark
                      ef-symbiosis
                      ef-trio-dark
                      modus-vivendi-tinted)))

  :config

  (theme-buffet-end-user)

  ;; Activate some theme in the current period.
  (theme-buffet-a-la-carte))
#+end_src

***** Add helper function to load a random theme from the specified periods

#+begin_src emacs-lisp :noweb-ref lib-ui
(declare-function theme-buffet--load-random "theme-buffet")

(defun +theme-buffet--load-random-from-periods (periods)
  "Load a random theme from the specified `theme-buffet' PERIODS.
PERIODS can be a single keyword or list of keywords. Each keyword
must be a valid `theme-buffet' period as defined in
`theme-buffet--keywords'."
  (let ((period (if (listp periods) (seq-random-elt periods) periods)))
    (theme-buffet--load-random period)))
#+end_src

*** Theme commands

#+begin_src emacs-lisp :noweb-ref lib-ui
(require 'config-ui)
(require 'ceamx-lib)
#+end_src

**** Integration with the GNOME/GTK/GSettings color scheme

#+begin_src emacs-lisp :noweb-ref config-ui
(defconst ceamx-ui-gsettings-ui-namespace "org.gnome.desktop.interface")
#+end_src

#+begin_src emacs-lisp :noweb-ref lib-ui
(defun ceamx-ui-gsettings-theme ()
  "Get the currently-active GNOME/GTK color scheme."
  (shell-command-to-string (format "gsettings get %s color-scheme"
                         ceamx-ui-gsettings-ui-namespace)))

(defun ceamx-ui-gsettings-dark-theme-p ()
  "Whether GNOME/GTK are using a theme with a dark color scheme."
  (string-match-p "dark" (ceamx-ui-gsettings-theme)))

(defun ceamx-ui/gsettings-set-theme (theme)
  "Set the GNOME/GTK theme to THEME."
  ;; FIXME: prompt with completion
  (interactive "s")
  (let* ((namespace ceamx-ui-gsettings-ui-namespace)
         (value (pcase theme
                  ((rx (optional "prefer-") "dark")
                   "prefer-dark")
                  ((rx (optional "prefer-") "light")
                   "prefer-light")
                  (_ "prefer-dark")))
         (cmd (format "gsettings set %s color-scheme %s" namespace value)))
    (shell-command cmd)))

(defun ceamx-ui/gsettings-dark-theme ()
  "Enable the dark GNOME/GTK theme."
  (interactive)
  (ceamx-ui/gsettings-set-theme "dark"))

(defun ceamx-ui/gsettings-light-theme ()
  "Enable the light GNOME/GTK theme."
  (interactive)
  (ceamx-ui/gsettings-set-theme "light"))
#+end_src

**** Generalized commands for desktop environment integration

Taking all supported environments into account:

+ [[*Integration with the GNOME/GTK/GSettings color scheme]]

#+begin_src emacs-lisp
(defun ceamx-ui-desktop-dark-theme-p ()
  "Predicate whether a desktop environment is displaying a dark appearance."
  (or (ceamx-ui-gsettings-dark-theme-p)))
#+end_src

**** ~ceamx-ui-load-theme~: function to cleanly load a theme

Similar to the theme-family-specific ~modus-themes-load-theme~.

#+begin_src emacs-lisp :noweb-ref lib-ui
(defun ceamx-ui-load-theme (theme)
  "Load THEME after resetting any previously-loaded themes."
  (mapc #'disable-theme (remq theme custom-enabled-themes))
  (load-theme theme :no-confirm))
#+end_src

**** Commands to load a preferred light or dark Emacs theme

#+begin_src emacs-lisp :noweb-ref lib-ui
(defun ceamx-ui/load-dark-theme ()
  "Load a random dark theme."
  (interactive)
  (pcase ceamx-ui-theme-circadian-interval
    ('buffet
     (+theme-buffet--load-random-from-periods
      ceamx-ui-theme-buffet-dark-periods))
    (_
     (load-theme ceamx-ui-theme-dark :no-confirm))))

(defun ceamx-ui/load-light-theme ()
  "Load a random light theme."
  (interactive)
  (pcase ceamx-ui-theme-circadian-interval
    ('buffet
     (+theme-buffet--load-random-from-periods
      ceamx-ui-theme-buffet-light-periods))
    (_
     (load-theme ceamx-ui-theme-light :no-confirm))))
#+end_src

**** Commands to globally set a preferred light or dark theme

#+begin_src emacs-lisp :noweb-ref lib-ui
(defun ceamx-ui/light ()
  "Activate a light theme globally."
  (interactive)
  (ceamx-ui/gsettings-light-theme)
  (ceamx-ui/load-light-theme))

(defun ceamx-ui/dark ()
  "Activate a dark theme globally."
  (interactive)
  (ceamx-ui/gsettings-dark-theme)
  (ceamx-ui/load-dark-theme))
#+end_src

*** Load a default theme

Wait until all the theme support is available:

#+begin_src emacs-lisp
(elpaca-wait)
#+end_src

Configure some user options dependent on the loaded packages:

#+begin_src emacs-lisp
(setopt ceamx-ui-theme-light 'modus-operandi-tinted)
(setopt ceamx-ui-theme-dark 'modus-vivendi)
#+end_src

~circadian~ has been broken lately, causing lockups/crashes.  In such a situation,
fall back to loading a theme based on the desktop environment appearance:

#+begin_src emacs-lisp
(if (ceamx-ui-desktop-dark-theme-p)
    (ceamx-ui/load-dark-theme)
  (ceamx-ui/load-light-theme))
#+end_src

** Interaction & Feedback
*** Avy 📦

- Website :: <https://github.com/abo-abo/avy>
- Ref :: <https://karthinks.com/software/avy-can-do-anything/>

#+begin_src emacs-lisp
(package! avy
  ;; Reduce the number of possible candidates.
  ;; Can be overridden with the universal argument.
  (setopt avy-all-windows nil)
  ;; Prevent conflicts with themes.
  (setopt avy-background nil)
  (setopt avy-style 'at-full)
  ;; Anything lower feels unusable.
  (setopt avy-timeout-seconds 0.25)

  (keymap-global-set "M-j" #'avy-goto-char-timer)

  (after! lispy
    (defvar lispy-mode-map)
    (declare-function lispy-join "lispy")
    ;; Prevent conflict with newly-added M-j binding.
    (keymap-set lispy-mode-map "M-J" #'lispy-join)))
#+end_src

*** Pulse current line after function invocations with ~pulsar~ 📦

#+begin_src emacs-lisp
(package! pulsar
  (pulsar-global-mode 1)

  (add-hook 'minibuffer-setup-hook #'pulsar-pulse-line))

(after! pulsar
  (setopt pulsar-pulse t
          pulsar-delay 0.055
          pulsar-iterations 10
          pulsar-face 'pulsar-magenta
          pulsar-highlight-face 'pulsar-cyan)

  (dolist (fn '(pulsar-pulse-line-red pulsar-recenter-top pulsar-reveal-entry))
    (add-hook 'next-error-hook (function fn))))
#+end_src

** Defenestrography (Frames & Windows)
*** Configure frame decorations

#+begin_src emacs-lisp
(unless +sys-mac-p
  ;; Hide window decorations.
  (add-to-list 'default-frame-alist '(undecorated . t)))
#+end_src

**** Handle macOS-specific workarounds

#+begin_src emacs-lisp
(when +sys-mac-p
  ;; `undecorated-round' is macOS-specific.
  (add-to-list 'default-frame-alist '(undecorated-round . t))

  ;; GUI menu bar is necessary otherwise Emacs will be treated as a
  ;; non-application OS window (e.g. no focus capture).
  ;; <https://github.com/doomemacs/doomemacs/blob/d657be1744a1481dc4646d0b62d5ee1d3e75d1d8/lisp/doom-start.el#L118-L128>
  (def-hook! ceamx-frame--maybe-restore-gui-menu-bar-h (&optional frame)
    '(after-make-frame-functions window-setup-hook)
    "TODO: Provide source for this approach (Doom?), and why it does what it does."
    (when-let (frame (or frame (selected-frame)))
      (when (display-graphic-p frame)
        (set-frame-parameter frame 'menu-bar-lines 1))))

  ;; Stop C-z from minimizing windows.
  (keymap-global-unset "C-z" t))
#+end_src

*** General frame configuration

#+begin_src emacs-lisp
(menu-bar-mode -1)
(undelete-frame-mode 1)
#+end_src

*** Enable ~tab-bar-mode~ in Emacs 30

- ref :: <https://lists.gnu.org/r/bug-gnu-emacs/2023-07/msg01594.html>

~tab-bar-mode~ is currently broken in Emacs 29 due to upstream bug.  The fix is
present on the =master= branch (Emacs 30), but it will not be backported.

Unfortunately, the bug is impossibly distracting.  So I am avoiding
`tab-bar-mode' on Emacs 29.

As of <2024-06-06>, I am using the =nix-community/emacs-overlay#emacs-pgtk= package tracking the
Emacs =master= branch.  ~tab-bar-mode~ is that important to me.  Emacs 30 seems
stable enough so far.

#+begin_src emacs-lisp
(when (version< emacs-version "30.0")
  (tab-bar-mode 1))
#+end_src


*** Add frame borders and window dividers

#+begin_src emacs-lisp
(modify-all-frames-parameters
 ;; NOTE: `org-modern', whose readme provided this example, initially had these
 ;; set to 40.  but on <%tuvok%>, 40 looked absolutely ridiclous.  i figured i
 ;; should try halving that, which seemed perfect.  so i am guessing there is
 ;; some confodungin factors with high-density displays
 '((right-divider-width . 20)
   (internal-border-width . 20)))

(dolist (face '(window-divider
                window-divider-first-pixel
                window-divider-last-pixel))
  (face-spec-reset-face face)
  (set-face-foreground face (face-attribute 'default :background)))

(set-face-background 'fringe (face-attribute 'default :background))
#+end_src

*** DISABLED Improve the breathability of frame boundaries with ~spacious-padding~
:PROPERTIES:
:header-args: :noweb-ref nil
:END:

- Website :: <https://protesilaos.com/emacs/spacious-padding>

#+begin_src emacs-lisp
(use-package spacious-padding
  :demand t
  :commands (spacious-padding-mode)
  :defines (spacious-padding-widths)

  :init
  (setopt spacious-padding-widths
          '(
            ;; NOTE: `:internal-border-width' currently breaks `tab-bar-mode'
            ;;       display on Emacs 29. Fixed in master branch.
            ;;       <https://lists.gnu.org/r/bug-gnu-emacs/2023-07/msg01594.html>
            :internal-border-width 15
            :header-line-width 4
            :mode-line-width 4
            :tab-width 4
            :right-divider-width 30
            :scroll-bar-width 8))

  :config

  ;; Read the doc string of `spacious-padding-subtle-mode-line' as it
  ;; is very flexible.
  ;; TODO: v0.3.0 standardizes this a bit
  ;; (setq spacious-padding-subtle-mode-line
  ;;       `(:mode-line-active default     ; NOTE: assumes `modus-themes'
  ;;                           :mode-line-inactive vertical-border))

  (spacious-padding-mode 1))
#+end_src

*** Differentiate between focused and non-focused windows

#+begin_src emacs-lisp
(setopt highlight-nonselected-windows nil)
#+end_src

** Typography
:PROPERTIES:
:header-args: :noweb-ref init-ui-graphical
:END:

#+begin_src emacs-lisp :noweb-ref config-ui
(defcustom ceamx-font-height-multiplier 1.0
  "Multiplier for display font size.
Intended for use as a per-system (or, ideally, per-display)
accommodation for varying pixel densities."
  :group 'ceamx
  :type '(float))
#+end_src

#+begin_src emacs-lisp
(require 'ceamx-paths)
(require 'config-env)

(require 'ceamx-lib)
(require 'lib-ui)
#+end_src

#+begin_src emacs-lisp
(setq x-underline-at-descent-line nil)

(setq-default text-scale-remap-header-line t)
#+end_src

*** Configure font presets with the ~fontaine~ package

<https://protesilaos.com/emacs/fontaine>

TIP: You can test out alterations quickly with, for example:
     (internal-set-lisp-face-attribute 'default :weight 'semilight)

#+begin_src emacs-lisp
(package! fontaine
  (require 'fontaine)

  (setopt fontaine-latest-state-file (expand-file-name "fontaine-latest-state.eld" ceamx-var-dir))

  ;; For some reason I do not yet understand, according to some hearsay, font
  ;; sizes best scale in multiples of 3-point increments. So, each height value
  ;; is a multiple of 3.
  (setopt fontaine-presets
          `( (small
              :bold-weight medium
              :default-height ,(pcase (system-name)
                                (_ 90))
              :default-weight ,(pcase (system-name)
                                ("tuvok" 'semilight)
                                (_ 'regular)))
             (regular)
             (medium
              :default-height ,(pcase (system-name)
                                ("boschic" 124)
                                ("tuvok"
                                 120
                                 ;; 115

                                 )
                                (_ 120)))
             (large
              :default-height ,(pcase (system-name)
                                ;; ("tuvok" 140)
                                (_ 144))
              :default-weight semilight
              :bold-weight semibold)
             (xlarge
              :default-height ,(pcase (system-name)
                                (_ 156))
              :bold-weight bold)
             (big-mclarge-huge
              :default-weight semilight
              :default-height ,(pcase (system-name)
                                (_ 180))
              :bold-weight extrabold)
             (t
              :default-family "Iosevka Comfy"
              :default-weight regular
              :default-slant normal
              :default-height ,(pcase (system-name)
                                ("tuvok" 102)
                                (_ 105))

              :fixed-pitch-family "Iosevka Comfy"
              :fixed-pitch-weight nil
              :fixed-pitch-slant nil
              :fixed-pitch-height 1.0

              :fixed-pitch-serif-family nil
              :fixed-pitch-serif-weight nil
              :fixed-pitch-serif-slant nil
              :fixed-pitch-serif-height 1.0

              :variable-pitch-family "Iosevka Comfy Motion"
              :variable-pitch-weight nil
              :variable-pitch-slant nil
              :variable-pitch-height 1.0

              :header-line-family nil
              :header-line-height 1.0
              :header-line-slant nil
              :header-line-weight nil

              :line-number-family nil
              :line-number-height 1.0
              :line-number-slant nil
              :line-number-weight nil

              :mode-line-active-family nil
              :mode-line-active-weight nil
              :mode-line-active-slant nil
              :mode-line-active-height 1.0

              :mode-line-inactive-family nil
              :mode-line-inactive-weight nil
              :mode-line-inactive-slant nil
              :mode-line-inactive-height 1.0

              :tab-bar-family nil
              :tab-bar-weight nil
              :tab-bar-slant nil
              :tab-bar-height 1.0

              :tab-line-family nil
              :tab-line-weight nil
              :tab-line-slant nil
              :tab-line-height 1.0

              :bold-family nil
              :bold-weight medium
              ;; :bold-weight semibold
              :bold-slant nil
              :bold-height 1.0

              :italic-family nil
              :italic-weight nil
              :italic-slant italic
              :italic-height 1.0

              :line-spacing 1)))

  ;; Persist latest preset across sessions.
  (fontaine-set-preset (or (fontaine-restore-latest-preset) 'regular))
  (add-hook 'kill-emacs-hook #'fontaine-store-latest-preset))

(elpaca-wait)
#+end_src

*** Enable improved ligature support with the ~ligature.el~ package

<https://github.com/mickeynp/ligature.el>

A better implementation of ligature support than the builtin ~prettify-symbols-mode~.
<https://old.reddit.com/r/emacs/comments/keji66/what_is_bad_about_prettifysymbolsmode/>

#+begin_src emacs-lisp
(package! ligature
  (require 'ligature)

  ;; Enable all Iosevka ligatures in programming modes
  ;; <https://github.com/mickeynp/ligature.el/wiki#iosevka>
  (ligature-set-ligatures 'prog-mode '("<---" "<--"  "<<-" "<-" "->" "-->" "--->" "<->" "<-->" "<--->" "<---->" "<!--"
                                       "<==" "<===" "<=" "=>" "=>>" "==>" "===>" ">=" "<=>" "<==>" "<===>" "<====>" "<!---"
                                       "<~~" "<~" "~>" "~~>" "::" ":::" "==" "!=" "===" "!=="
                                       ":=" ":-" ":+" "<*" "<*>" "*>" "<|" "<|>" "|>" "+:" "-:" "=:" "<******>" "++" "+++"))

  ;; Enables ligature checks globally in all buffers. You can also do it
  ;; per mode with `ligature-mode'.
  (global-ligature-mode t))
#+end_src

*** TODO Separate presets per font i.e. Berkeley Mono + Iosevka

** Iconography & Symbolisms
*** Provide common dependency: ~nerd-icons~

#+begin_src emacs-lisp
(package! nerd-icons
  (setopt nerd-icons-font-family "Symbols Nerd Font Mono")
  (require 'nerd-icons))
#+end_src

*** Provide common dependency: ~svg-lib~

#+begin_src emacs-lisp
(package! svg-lib)
#+end_src

*** Improve appearance of form feed characters with ~page-break-lines~

- Website :: <https://github.com/purcell/page-break-lines/blob/master/README.md>

#+begin_src emacs-lisp
(package! page-break-lines
  (global-page-break-lines-mode))
#+end_src
*** TODO Decorate buffers with SVG via ~svg-tag-mode~
:PROPERTIES:
:header-args: :noweb-ref nil
:END:

- Source :: <https://github.com/Icy-Thought/emacs.d/blob/main/config.org#svg-tag-decorating-buffers-with-svg>

#+begin_src emacs-lisp
(package! svg-tag-mode
  (add-hook 'prog-mode-hook #'svg-tag-mode)
  (add-hook 'text-mode-hook #'svg-tag-mode))
#+end_src

** Modeline

#+begin_src emacs-lisp :noweb-ref config-ui
(defcustom ceamx-modeline-provider nil
  "Modeline provider to load.
Valid values are the symbols `doom', `nano', and `telephone'
which reference the `doom-modeline', `nano-modeline', and
`telephone-line' modules respectively.

A nil value will not load any modeline customizations (use Emacs
with its default modeline)."
  :group 'ceamx
  :type '(choice :tag "Modeline to load" :value nil
          (const :tag "The `doom-modeline' module" doom)
          (const :tag "The `nano-modeline' module" nano)
          (const :tag "The `telephone-line' module" telephone)
          (const :tag "Do not load a modeline module" nil)))
#+end_src

#+begin_src emacs-lisp
(line-number-mode 1)
(column-number-mode 1)

(setopt display-time-24hr-format t)
#+end_src

*** Minimize many mode-line minor-modes with ~minions~

#+begin_src emacs-lisp
(package! minions
  (minions-mode 1))
#+end_src

*** Show current command and its binding with ~keycast~

- Website :: <https://github.com/tarsius/keycast>

Supports display in the mode-line, header-line, tab-bar, and as messages in a
dedicated frame.

NOTE: Incompatible with kitchen-sink modeline packages like =doom-modeline= and
=telephone-line=.

#+begin_src emacs-lisp
(package! keycast)
#+end_src

#+begin_src emacs-lisp
(after! keycast
  (dolist (input '(self-insert-command org-self-insert-command))
    (add-to-list 'keycast-substitute-alist `(,input "." "Typing…")))

  (dolist (event '(mouse-event-p mouse-movement-p mwheel-scroll))
    (add-to-list 'keycast-substitute-alist `(,event nil))))
#+end_src

*** DISABLED Doom-Modeline
:PROPERTIES:
:header-args: :noweb-ref nil
:END:

#+begin_src emacs-lisp
(package! doom-modeline
  (setopt doom-modeline-support-imenu t)
  (setopt doom-modeline-unicode-fallback t)
  (setopt doom-modeline-buffer-encoding nil)
  (setopt doom-modeline-github nil)
  (setopt doom-modeline-buffer-file-name-style 'truncate-upto-project)

  ;; Enable HUD mode, providing a micromap of buffer position.
  (setopt doom-modeline-hud t)

  (setopt doom-modeline-icon t)

  ;; note that the major mode icon is not missing like most others.
  ;; git branch icon is also fine.
  (setopt doom-modeline-major-mode-icon t)

  ;; FIXME: missing icons when using nix-installed icon font
  (setopt doom-modeline-buffer-state-icon t)
  (setopt doom-modeline-buffer-modification-icon t)

  (setopt doom-modeline-modal t)
  ;; FIXME: missing icon with nix-installed font... but only when non-nil?! when nil, icon displays properly...
  (setopt doom-modeline-modal-icon t)

  (doom-modeline-mode 1))
#+end_src

** Keybindings

#+begin_src emacs-lisp
(define-keymap :keymap ceamx-session-map
  "a" (cons "Appearance" (define-prefix-command 'ceamx-session-appearance-prefix-command))
  "a f" #'fontaine-set-preset
  "a d" #'ceamx-ui/dark
  "a l" #'ceamx-ui/light
  "a o" #'olivetti-mode

  "f" (cons "Frame" (define-prefix-command 'ceamx-session-f-prefix))
  "f d" #'delete-frame)
#+end_src

** TODO Calculate sizes based on display monitor attributes
:PROPERTIES:
:header-args: :noweb-ref lib-ui
:END:

- Source :: <https://github.com/noctuid/dotfiles/blob/master/emacs/.emacs.d/awaken.org#gui-related>
- Ref :: <https://photo.stackexchange.com/questions/18494/what-is-the-difference-between-pixel-pitch-and-pixel-density>
- Ref :: <https://insights.samsung.com/2023/10/05/what-is-pixel-pitch-understanding-fine-pixel-pitch-led-displays/>

*** Measurement constants

#+begin_src emacs-lisp :noweb-ref config-ui
(defconst ceamx-inch-as-mm 25.4
  "One inch in millimeters.")

(defconst ceamx-pt-as-mm 0.353
  "One typographic point in millimeters.")
#+end_src

*** Function to get geometry attributes for the default display monitor

#+begin_src emacs-lisp :noweb-ref config-ui
(defun ceamx-default-monitor-geometry ()
  "Return geometry for the first monitor in `display-monitor-attributes-list'."
  (let* ((first-monitor (car (display-monitor-attributes-list))))
    (alist-get 'geometry first-monitor)))
#+end_src

*** Function to get the pixel pitch for a frame

#+begin_src emacs-lisp :noweb-ref config-ui
;; via <https://www.reddit.com/r/emacs/comments/7hzxb8/comment/dqywyqc/>
(defun ceamx-pixel-pitch (&optional frame)
  "Return the pixel pitch for FRAME in millimeters.
When FRAME is nil, the current frame will be used as default.

Pixel pitch is the distance from the center of a pixel to the
center of its adjacent pixel."
  (let ((monitor-attrs (frame-monitor-attributes frame)))
    (/ (float (nth 1 (assoc 'mm-size monitor-attrs)))
       (nth 3 (assoc 'geometry monitor-attrs)))))
#+end_src

*** Function to calculate font height scaling

#+begin_src emacs-lisp :noweb-ref config-ui
(defun ceamx-font-height (number &optional multiplier)
  "Return a numeric font height based on NUMBER multiplied by MULTIPLIER.
NUMBER should be a whole number. MULTIPLIER should be a float.

If MULTIPLIER is nil, the value of `ceamx-font-height-multiplier'
will be used as default."
  (truncate (* number (or multiplier ceamx-font-height-multiplier))))
#+end_src



* Window Management
:PROPERTIES:
:header-args: :noweb-ref init-window
:END:

    #+begin_src emacs-lisp
(require 'transient)

(require 'config-buffer)
(require 'config-window)

(require 'ceamx-lib)
(require 'lib-buffer)
(require 'lib-window)
    #+end_src

#+begin_src emacs-lisp :noweb-ref lib-window
(require 'windmove)

(require 'config-window)
#+end_src

** General buffer display settings

#+begin_src emacs-lisp
(setopt switch-to-buffer-in-dedicated-window 'pop)

;; Ensure interactive buffer switching behaves according to expectations.
(setopt switch-to-buffer-obey-display-actions t)

;; Hide buffer until there's output.
;; Prevents an extra window appearing during init.
(setopt async-shell-command-display-buffer nil)

;; TODO: causes which-key squishing against tiny window maybe?
(setopt fit-window-to-buffer-horizontally t)

;; TODO: this might be a solution to issues with childframes for embark etc.
(setopt fit-frame-to-buffer t)

;; (setopt even-window-sizes nil)
(setopt even-window-sizes 'height-only)
(setopt window-combination-resize t)
(setopt window-sides-vertical nil)
(setopt window-resize-pixelwise t)

(setopt display-buffer-base-action
        '((display-buffer-reuse-window
           display-buffer-in-previous-window)))
#+end_src

** Define the user option specifying a fallback buffer

#+begin_src emacs-lisp :noweb-ref config-window
(defcustom ceamx-fallback-buffer-name "*scratch*"
  "The name of the buffer to fall back to if no other buffers exist.
The buffer will be created if it does not exist."
  :group 'ceamx
  :type '(string))
#+end_src

** Declare rules for displaying buffers with `display-buffer-alist'

- Source :: <https://github.com/karthink/.emacs.d/blob/6aa2e034ce641af60c317697de786bedc2f43a71/lisp/setup-windows.el>

<karthink> has a helpful summary of ~display-buffer~ action functions and
alist entries in their Emacs configuration, which I am also including here
for my own reference. Note that this list is not necessarily complete.

~display-buffer-action-functions~ are:

- ~display-buffer-same-window~ :: Use the selected window
- ~display-buffer-reuse-window~ :: Use a window already showing the buffer
- ~display-buffer-reuse-mode-window~ :: Use a window with the same major-mode
- ~display-buffer-in-previous-window~ :: Use a window that did show the buffer before
- ~display-buffer-use-some-window~ :: Use some existing window
- ~display-buffer-pop-up-window~ :: Pop up a new window
- ~display-buffer-below-selected~ :: Use or pop up a window below the selected one
- ~display-buffer-at-bottom~ :: Use or pop up a window at the bottom of the selected frame
- ~display-buffer-pop-up-frame~ :: Show the buffer on a new frame
- ~display-buffer-in-child-frame~ :: Show the buffer in a child frame
- ~display-buffer-no-window~ :: Do not display the buffer and have ~display-buffer~ return nil immediately

Action alist entries are:

- ~inhibit-same-window~ :: A non-nil value prevents the sam
    window from being used for display
- ~inhibit-switch-frame~ :: A non-nil value prevents any fram
    used for showing the buffer from being raised or selected
- ~reusable-frames~ :: The value specifies the set of frames t
    search for a window that already displays the buffer.
    Possible values are nil (the selected frame), t (any live
    frame), visible (any visible frame), 0 (any visible or
    iconified frame) or an existing live frame.
- ~pop-up-frame-parameters~ :: The value specifies an alist o
    frame parameters to give a new frame, if one is created.
- ~window-height~ :: The value specifies the desired height of th
    window chosen and is either an integer (the total height of
    the window), a floating point number (the fraction of its
    total height with respect to the total height of the frame's
    root window) or a function to be called with one argument -
    the chosen window.  The function is supposed to adjust the
    height of the window; its return value is ignored.  Suitable
    functions are ~shrink-window-if-larger-than-buffer~ and
    ~fit-window-to-buffer~.
- ~window-width~ :: The value specifies the desired width of th
    window chosen and is either an integer (the total width of
    the window), a floating point number (the fraction of its
    total width with respect to the width of the frame's root
    window) or a function to be called with one argument - the
    chosen window.  The function is supposed to adjust the width
    of the window; its return value is ignored.
- ~preserve-size~ :: The value should be either (t . nil) t
    preserve the width of the chosen window, (nil . t) to
    preserve its height or (t . t) to preserve its height and
    width in future changes of the window configuration.
- ~window-parameters~ :: The value specifies an alist of windo
    parameters to give the chosen window.
- ~allow-no-window~ :: A non-nil value means that `display-buffer
    may not display the buffer and return nil immediately.


    <https://github.com/karthink/.emacs.d/blob/6aa2e034ce641af60c317697de786bedc2f43a71/lisp/setup-windows.el>


    #+begin_src emacs-lisp
;; TODO: move these to config-...
(defvar ceamx-checkers-buffer-names-regexp
  (rx "*" (or "Flycheck" "Package-Lint")))

(setopt display-buffer-alist
        `(
          ;; (,(rx "*" (or "Agenda Commands" "Org Select") "*")
          ;;   (display-buffer-below-selected
          ;;     display-buffer-in-side-window)
          ;;   (body-function . select-window)
          ;;   (window-parameters . ((mode-line-format . nil))))

          (,ceamx-checkers-buffer-names-regexp
           (display-buffer-in-direction
            display-buffer-in-side-window)
           (window-parameters . ((no-other-window . t))))

          ;; TODO: is there not a simpler way than using `ceamx-buffer-mode'?
          ;; e.g. `derived-mode-p' or similar
          ((lambda (buf act) (member (ceamx-buffer-mode buf) ceamx-message-modes-list))
           (display-buffer-at-bottom
            display-buffer-in-side-window))

          (,(rx "*" (group (or "Compile-Log" "Messages" "Warnings")) "*")
           (display-buffer-at-bottom
            display-buffer-in-side-window
            display-buffer-in-direction))

          (,(rx "*Backtrace*")
           (display-buffer-in-side-window)
           (window-height . 0.2)
           (side . bottom))))
    #+end_src

*** ~display-buffer~ functions

<https://github.com/karthink/popper/blob/570b0820f884a9c0e3d9cb07e7f7f523b39b836f/popper.el#L265-L283>


#+begin_src emacs-lisp :noweb-ref lib-window
(defun ceamx-window-display-popup-at-bottom (buffer &optional alist)
  "Display popup-buffer BUFFER at the bottom of the screen.
ALIST is an association list of action symbols and values.  See
Info node `(elisp) Buffer Display Action Alists' for details of
such alists."
  (display-buffer-in-side-window
   buffer
   (append alist
           `((side . bottom)
             (slot . 1)))))

(defun ceamx-window-display-popup (buffer &optional alist)
  "Display and switch to popup-buffer BUFFER at the bottom of the screen.
ALIST is an association list of action symbols and values.  See
Info node `(elisp) Buffer Display Action Alists' for details of
such alists."
  (let ((window (ceamx-window-display-popup-at-bottom buffer alist)))
    (select-window window)))

;; via <https://github.com/karthink/.emacs.d/blob/6aa2e034ce641af60c317697de786bedc2f43a71/lisp/setup-windows.el>
(defun +display-buffer-reuse-minor-mode-window (buffer alist)
  "Return a window sharing a minor mode with BUFFER.
ALIST is an association list of action symbols and values.  See
Info node \"(elisp) Buffer Display Action Alists\" for details of
such alists."
  (let* ((alist-entry (assq 'reusable-frames alist))
         (alist-mode-entry (assq 'minor-mode alist))
         (frames (cond (alist-entry (cdr alist-entry))
                       ((if (eq pop-up-frames 'graphic-only)
                            (display-graphic-p)
                          pop-up-frames)
                        0)
                       ;; TODO: remove or note the intention here -- not a call but
                       ;; a condition check to maintain support for the deprecated
                       ;; function. but really should be removed.
                       ;; (display-buffer-reuse-frames 0)
                       (t (last-nonminibuffer-frame))))
         (inhibit-same-window-p (cdr (assq 'inhibit-same-window alist)))
         (windows (window-list-1 nil 'nomini frames))
         (allowed-modes (if alist-mode-entry
                            (cdr alist-mode-entry)))
         (curwin (selected-window))
         (curframe (selected-frame)))
    (unless (listp allowed-modes)
      (setq allowed-modes (list allowed-modes)))
    (let ((same-mode-same-frame)
          (same-mode-other-frame))
      (dolist (window windows)
        (let ((mode?
               (with-current-buffer (window-buffer window)
                 (cl-some (lambda (m) (and (boundp m) (symbol-value m) 'same))
                          allowed-modes))))
          (when (and mode? (not (and inhibit-same-window-p (eq window curwin))))
            (push window (if (eq curframe (window-frame window))
                             same-mode-same-frame
                           same-mode-other-frame)))))
      (let ((window (car (nconc same-mode-same-frame
                                same-mode-other-frame))))
        (when (window-live-p window)
          (prog1 (window--display-buffer buffer window 'reuse alist)
            (unless (cdr (assq 'inhibit-switch-frame alist))
              (window--maybe-raise-frame (window-frame window)))))))))
#+end_src

#+begin_src emacs-lisp :noweb-ref lib-window
(defmacro with-safe-side-windows! (&rest body)
  "Toggle side windows, evaluate BODY, restore side windows.
Copied from the `evil' macro `evil-save-side-windows'."
  (declare (indent defun)
           (debug (&rest form)))
  (let ((sides (make-symbol "sidesvar")))
    `(let ((,sides (and (fboundp 'window-toggle-side-windows)
                    (window-with-parameter 'window-side))))
      ;; (declare-function window-toggle-side-windows "window")
      (when ,sides
       (window-toggle-side-windows))
      (unwind-protect
          (progn ,@body)
        (when ,sides
         (window-toggle-side-windows))))))
#+end_src

** ~popper~: Summon and dismiss "popup" windows

- Website :: <https://github.com/karthink/popper>

#+begin_src emacs-lisp
(package! popper
  (global-keys!
    "C-`" #'popper-toggle
    "C-~" #'popper-cycle
    "C-M-`" #'popper-toggle-type)

  (setopt popper-reference-buffers
          (append
           ceamx-help-modes-list
           ceamx-help-buffer-names-list
           ceamx-manual-modes-list
           ceamx-repl-modes-list
           ceamx-repl-buffer-names-list
           ceamx-occur-grep-modes-list
           '(+popper-current-buffer-popup-p)
           '(Custom-mode
             compilation-mode
             messages-buffer-mode)
           (list
            ceamx-checkers-buffer-names-regexp)
           `(,(rx "Output*" eol)
             ,(rx "*" (or
                       "Async-native-compile-log"
                       "Backtrace"
                       "Compile-Log"
                       "Completions"
                       "compilation"

                       "Messages"
                       "Shell Command Output"
                       "vc"
                       "Warnings")
               "*")
             "^\\*Embark Export"
             "^Calc:"
             "\\*Async Shell Command\\*"
             ;; ("\\*Async Shell Command\\*" . hide)
             ("\\*Detached Shell Command\\*" . hide))))

  ;; Load as early as possible to catch popups as early as possible.
  (popper-mode)
  (popper-echo-mode))
#+end_src

*** Define a custom predicate for identifying a "popup" buffer

- Reference :: <https://github.com/karthink/popper/blob/master/popper.el#L265-L283>

#+begin_src emacs-lisp :noweb-ref lib-window
(defun +popper-current-buffer-popup-p (buf)
  "Whether the buffer BUF should be considered a popup.
This is intended for use as a predicate in `popper-reference-buffers'."
  (with-current-buffer buf
    (and (derived-mode-p 'fundamental-mode)
         (not (bound-and-true-p scratch-buffer))
         ;; Less than `max-lines' but not empty.
         (let ((lines (count-lines (point-min) (point-max)))
               (max-lines 10))
           (and (not (zerop lines))
                (< lines max-lines))))))

(defun +popper-close-focused (&rest _)
  "Close any focused `popper' popup.
Intended as a general hook function."
  (declare-function popper-toggle "popper")
  (when (bound-and-true-p popper-popup-status)
    (popper-toggle)))

(defun +popper-select-below-fn (buffer &optional _alist)
  (funcall (if (> (frame-width) 170)
               ;; #'display-buffer-in-direction
               #'popper-select-popup-at-bottom
             #'display-buffer-at-bottom)
           buffer
           `((window-height . ,popper-window-height)
             (direction . below)
             (body-function . ,#'select-window))))
#+end_src


**** TODO Add buffers tracking files in nix store

These are only useful for reference purposes, often invoked when viewing
definition of low-level Emacs internals defined in C code (e.g. `string-equal')

*** Configure overrides in ~popper-repeat-map~

#+begin_src emacs-lisp
(after! popper
  (defvar-keymap popper-repeat-map
    :repeat t
    "`" #'popper-cycle
    "~" #'popper-cycle-backwards))
#+end_src

*** DISABLED Configure popup display control rules manually
:PROPERTIES:
:header-args: :tangle no
:END:

<https://github.com/karthink/popper/blob/master/README.org#popup-placement-controlled-using-display-buffer-alist-or-shackleel>

#+begin_src emacs-lisp
(after! popper
  (setopt popper-display-control nil)

  (prependopt! display-buffer-alist
               '((popper-display-control-p
                  (ceamx-window-display-popup)
                  (window-height . ,popper-window-height)))))
#+end_src

*** Configure ~projectile~ integration

#+begin_src emacs-lisp
(after! (popper projectile)
  (setopt popper-group-function #'popper-group-by-projectile))
#+end_src

** Restore previous window configurations with ~winner-mode~ [builtin]

#+begin_src emacs-lisp
(add-hook 'ceamx-after-init-hook #'winner-mode)
#+end_src

** Toggle a window's "dedicated" flag with ~dedicated-mode~

<https://github.com/emacsorphanage/dedicated/tree/f47b504c0c56fa5ab9d1028417ca1f65a713a2f0>

#+begin_src emacs-lisp
(package! dedicated
  (keymap-global-set "C-c W" #'dedicated-mode))
#+end_src

** Add "distraction-free" editing with ~olivetti-mode~

<https://github.com/rnkn/olivetti>

#+begin_src emacs-lisp
(package! olivetti)

;;  (setopt olivetti-style 'fancy) ; might not play well with `org-modern'
#+end_src

** =golden-ratio.el=: Automatically resize windows accordingly

#+begin_src emacs-lisp
(package! golden-ratio
  (setopt golden-ratio-auto-scale t)
  (setopt golden-ratio-max-width 100))
#+end_src

** Interactively manage windows with ~ace-window~

<https://github.com/abo-abo/ace-window>

#+begin_src emacs-lisp
(package! ace-window
  ;; Same frame only. While it'd be nice to use the default (global), I really
  ;; dislike that it orders window numbers leads to jarring gaps in window
  ;; numbers in the same frame. For example, frame A might have windows numbered
  ;; 1 and 3 and frame B will have window 2.
  (setopt aw-scope 'frame))
#+end_src

** ~switchy-window~: A most-recently-used window switcher

#+begin_src emacs-lisp
(require 'ceamx-lib)

(package! switchy-window
  ;; NOTE: This delay is slightly too short unless invoking within a repeat-map.
  (setopt switchy-window-delay 1.5)

  (switchy-window-minor-mode))

(after! switchy-window
  ;; NOTE: Handled by `ceamx/other-window'.
  ;; (keymap-set switchy-window-minor-mode-map "C-x o" #'switchy-window)

  (def-hook! +window-selection-change-pulse-h (frame)
    'window-selection-change-functions
    "Pulse the newly-selected window on focus change."
    (when (eq frame (selected-frame))
      (if (fboundp 'pulsar-pulse-line)
          (pulsar-pulse-line)
        (pulse-momentary-highlight-one-line)))))
#+end_src

** Add commands to transpose/rotate a frame's windows

#+begin_src emacs-lisp
(keymap-global-set "C-c w" (cons "Window" (define-prefix-command 'ceamx-custom-x-prefix)))

(package! transpose-frame
  (keymap-global-set "C-c w SPC" #'transpose-frame))
#+end_src

** Create a Transient menu for window management

#+begin_src emacs-lisp
(transient-define-prefix ceamx/window-dispatch ()
  "Window management transient."
  :transient-suffix 'transient--do-stay
  [["Move"
    ("h" "left" windmove-left)
    ("j" "down" windmove-down)
    ("k" "up" windmove-up )
    ("l" "right" windmove-right)
    ("w" "sel" ace-window)]

   ["Resize"
    ("=" "bal" balance-windows)
    ("+" "bal: area" balance-windows-area)
    ("-" "fit: buffer" fit-window-to-buffer)]

   ["Buffer"
    ("b" "buf" consult-buffer)
    ;; ("f" "ff: p" project-find-file)
    ("f" "file" find-file )
    ("F" "file" find-file-other-window)
    ("g" "grep" consult-ripgrep)]

   ["Swarp"
    ("H" "left" ceamx/window-move-left)
    ("J" "down" ceamx/window-move-down)
    ("K" "up" ceamx/window-move-up)
    ("L" "right" ceamx/window-move-right)
    ""
    ("s" "swap" ace-swap-window)
    ("2" "spl: dn" split-window-below)
    ("3" "spl: rt" split-window-right)
    ("SPC" "swap-or-rotate" ceamx/swap-or-rotate-windows)]

   ["Scroll"
    ;; TODO: allow selecting a window (with infix?) to act upon
    ;; NOTE: These are the correct scroll direction commands, which might
    ;; appear to be reversed when comparing with labels.
    ("." "left" scroll-right)
    ("," "right" scroll-left)
    ("SPC" "down" scroll-up)
    ("DEL" "up" scroll-down)]

   ["Lifecycle"
    ("d" "del (this)" delete-window)
    ("D" "del (select)" ace-delete-window)
    ;; ("D" "del: o" delete-other-windows :transient nil)
    ("u" "undo" winner-undo)
    ("U" "redo" winner-redo)
    ""
    ("0" "del" delete-window)
    ("1" "del other" delete-other-windows)
    ""
    ("S" "[ ] sides" window-toggle-side-windows)
    ("`" "[ ] popups" popper-toggle)
    ""
    ("q" "quit" transient-quit-all)]])
#+end_src

** Keybindings
*** Define additional bindings under the =C-x w= prefix (~window-prefix-map~)

#+begin_src emacs-lisp
(define-keymap :keymap window-prefix-map
  "w" #'ace-window

  "d" #'ace-delete-window
  "p" #'popper-toggle
  "P" #'popper-toggle-type
  "u" #'winner-undo

  "h" #'windmove-left
  "H" #'ceamx/window-move-left
  "j" #'windmove-down
  "J" #'ceamx/window-move-down
  "k" #'windmove-up
  "K" #'ceamx/window-move-up
  "l" #'windmove-right
  "L" #'ceamx/window-move-right

  "=" #'balance-windows
  "SPC" #'ceamx/swap-or-rotate-windows)
#+end_src

*** Define global window management keybindings

#+begin_src emacs-lisp
(define-keymap :keymap (current-global-map)
  "C-x o" #'ceamx/other-window
  "C-x O" #'ace-window

  "C-x =" #'balance-windows
  "C-x +" #'balance-windows-area

  "C-x C-n" #'next-buffer
  "C-x C-p" #'previous-buffer

  "C-x <up>" #'enlarge-window           ; also: C-x ^
  "C-x <down>" #'shrink-window
  "C-x <left>" #'shrink-window-horizontally
  "C-x <right>" #'enlarge-window-horizontally)
#+end_src

*** Define repeatable keybindings for resizing windows (~resize-window-repeat-map~)

#+begin_src emacs-lisp
(define-keymap :keymap resize-window-repeat-map
  "<up>" #'enlarge-window
  "<down>" #'shrink-window
  "<left>" #'shrink-window-horizontally
  "<right>" #'enlarge-window-horizontally)
#+end_src

*** Define repeatable keybindings for window actions (~ceamx-window-repeat-map~)

This is very similar to ~window-prefix-map~.  Unfortunately, it does not seem
possible to create a functional ~repeat-map~ inheriting from a parent keymap.  The
commands bound in the parent map are unaffected by the ~defvar-keymap~ =:repeat=
keyword of a child map.

#+begin_src emacs-lisp
(defvar-keymap ceamx-window-repeat-map
  :repeat (:exit (repeat-exit))

  "o" #'ceamx/other-window
  "P" #'popper-toggle-type
  "u" #'winner-undo

  "h" #'windmove-left
  "H" #'ceamx/window-move-left
  "j" #'windmove-down
  "J" #'ceamx/window-move-down
  "k" #'windmove-up
  "K" #'ceamx/window-move-up
  "l" #'windmove-right
  "L" #'ceamx/window-move-right

  "SPC" #'ceamx/swap-or-rotate-windows

  "RET" #'repeat-exit
  "ESC" #'repeat-exit)
#+end_src

** Define commands for window management

- Source :: <https://github.com/emacs-evil/evil/blob/5995f6f21f662484440ed67a28ce59e365feb9ad/evil-commands.el>

#+begin_src emacs-lisp :noweb-ref lib-window
;; FIXME: "display-buffer" is misleading
;; via <https://github.com/karthink/.emacs.d/blob/6aa2e034ce641af60c317697de786bedc2f43a71/lisp/setup-windows.el>
;;;###autoload
(defun ceamx/display-buffer-at-bottom ()
  "Move the current buffer to the bottom of the frame.
This is useful to take a buffer out of a side window.

The window parameters of this function are provided mostly for
didactic purposes."
  (interactive)
  (let ((buffer (current-buffer)))
    (with-current-buffer buffer
      (delete-window)
      (display-buffer-at-bottom
       buffer '((window-height .
                 (lambda (win)
                   (fit-window-to-buffer
                    win (/ (frame-height) 3)))))))))

;; TODO: this seems very similar to `windmove-swap-states-in-direction'...?
(defun ceamx-move-window (side)
  "Move the `selected-window' to SIDE.
The state of the `selected-window' is saved along with the state
of the window tree consisting of all the other windows. Then, all
windows are deleted, the remaining window is split according to
DIRECTION, the state of the window in DIRECTION is replace with
the saved state of the `selected-window', and, finally, the state
of the saved window tree is reconstructed on the opposite side.

SIDE has the same meaning as in `split-window'.

Copied from the `evil' function `evil-move-window'."
  (with-safe-side-windows!
    (unless (one-window-p)
      (save-excursion
        (let ((this-window-state (window-state-get (selected-window))))
          (delete-window)
          (let ((window-tree (window-state-get)))
            (delete-other-windows)
            (let ((sub-window (selected-window))
                  (new-window (ceamx--split-window-in-direction side)))
              (window-state-put window-tree sub-window)
              (window-state-put this-window-state new-window)
              (select-window new-window)))))
      (balance-windows))))

;; via <https://github.com/doomemacs/doomemacs/blob/ff33ec8f7a89d168ca533612e2562883c89e029f/modules/editor/evil/autoload/evil.el#L42-L73>
(defun ceamx--window-swap-or-split (direction)
  "Move current window to the next window in DIRECTION.
If there are no windows in DIRECTION and there is only one window
in the current frame, split the window in DIRECTION and place
this window there."
  (let* ((this-window (selected-window))
         (that-window (window-in-direction direction this-window)))
    (unless that-window
      (setq that-window (split-window this-window nil direction))
      (with-selected-window that-window
        (switch-to-buffer ceamx-fallback-buffer-name)))
    (window-swap-states this-window that-window)
    (select-window that-window)))

(defun ceamx/window-move-left ()
  "Swap or move selected window to the left."
  (interactive)
  (ceamx--window-swap-or-split 'left))

(defun ceamx/window-move-right ()
  "Swap or move selected window to the right."
  (interactive)
  (ceamx--window-swap-or-split 'right))

(defun ceamx/window-move-up ()
  "Swap or move selected window upwards."
  (interactive)
  (ceamx--window-swap-or-split 'up))

(defun ceamx/window-move-down ()
  "Swap or move selected window downwards."
  (interactive)
  (ceamx--window-swap-or-split 'down))

(defun ceamx/split-window (&optional count direction file)
  "TODO"
  (interactive "P\nS\nf")
  (select-window
   (split-window (selected-window)
                 (when count (- count))
                 direction))
  ;; (when (and (not count)
  ;;         ceamx-window-auto-balance)
  ;;   (balance-windows (window-parent)))
  (when file
    (find-file file)))

(defun ceamx/split-window-with-buffer (buffer)
  "Split window and switch to BUFFER.
If BUFFER is not the name of an existing buffer, then a new
buffer will be created with that name."
  (interactive "b")
  (ceamx/split-window)
  (switch-to-buffer buffer))

(defun ceamx/split-window-with-next-buffer ()
  "Split window and switch to the next buffer in the buffer list."
  (interactive)
  (ceamx/split-window-with-buffer (next-buffer)))

(defun ceamx/split-window-with-prev-buffer ()
  "Split window and switch to the previous buffer in the buffer list."
  (interactive)
  (ceamx/split-window-with-buffer (previous-buffer)))

(defun ceamx/buffer-create (&optional file)
  "Edit a new unnamed buffer or open FILE.
When called interactively, prompt the user for FILE."
  (interactive "F")
  (if file
      (find-file file)
    (let ((buffer (generate-new-buffer "*new*")))
      (set-buffer-major-mode buffer)
      (set-window-buffer nil buffer))))

(defun ceamx/window-increase-height (count)
  "Increase window height by COUNT."
  (interactive "p")
  (enlarge-window count))

(defun ceamx/window-decrease-height (count)
  "Decrease window height by COUNT."
  (interactive "p")
  (enlarge-window (- count)))

(defun ceamx/window-increase-width (count)
  "Increase window width by COUNT."
  (interactive "p")
  (enlarge-window count t))

(defun ceamx/window-decrease-width (count)
  "Decrease window width by COUNT."
  (interactive "p")
  (enlarge-window (- count) t))

;; via <https://github.com/protesilaos/dotfiles/blob/df9834d8db815920bfd7aacfaf11ef16fa089c53/emacs/.emacs.d/prot-lisp/prot-simple.el#L800C1-L814C68>
;;;###autoload
(defun ceamx/swap-or-rotate-windows (counter)
  "Swap states of live buffers.
With two windows, transpose their buffers.  With more windows,
perform a clockwise rotation.  Do not alter the window layout.
Just move the buffers around.

With COUNTER as a prefix argument, do the rotation
counter-clockwise."
  (interactive "P")
  (when-let* ((winlist (if counter (reverse (window-list)) (window-list)))
              (wincount (count-windows))
              ((> wincount 1)))
    (dotimes (i (- wincount 1))
      (window-swap-states (elt winlist i) (elt winlist (+ i 1))))))

;; via <https://github.com/protesilaos/dotfiles/blob/24670bf47f7aaefc9bb2613d090cc9113acd6d48/emacs/.emacs.d/prot-lisp/prot-simple.el#L590C1-L601C41>
;;;###autoload
(defun ceamx/other-window ()
  "Switch window in a multi-window frame or to a window in another frame.
If there is only one window and multiple frames, call
`next-multiframe-window'.  Otherwise, call `other-window' or, if
available, `switchy-window'."
  (interactive)
  (if (and (one-window-p) (length> (frame-list) 1))
      (progn
        (call-interactively #'next-multiframe-window)
        (setq this-command #'next-multiframe-window))
    (let ((other-window-command (or (and (fboundp 'switchy-window)
                                         (function switchy-window))
                                    (function other-window))))
      (call-interactively other-window-command)
      (setq this-command other-window-command))))
#+end_src


* Buffer


** Customizations
:PROPERTIES:
:header-args: :tangle lisp/init-buffer.el
:END:

#+begin_src emacs-lisp
;;; init-buffer.el --- Buffers configuration         -*- lexical-binding: t; -*-

;; Copyright (C) 2023-2024  Chris Montgomery

;; Author: Chris Montgomery <chmont@proton.me>
;; Keywords: local

;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.

;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.

;; You should have received a copy of the GNU General Public License
;; along with this program.  If not, see <https://www.gnu.org/licenses/>.

;;; Commentary:

;;; Code:
#+end_src

*** Requirements

#+begin_src emacs-lisp
(require 'ceamx-lib)
(require 'lib-buffer)
#+end_src

*** General

#+begin_src emacs-lisp
(setq-default indicate-empty-lines nil)
(setq-default fill-column 80)

;; Available cycle positions for `recenter-top-bottom'.
(setopt recenter-positions '(top middle bottom))

;; Disable buffer line wrapping by default.
(setq-default truncate-lines t)
#+end_src

*** Scrolling

#+begin_src emacs-lisp
(setopt scroll-error-top-bottom t)

;; Prevent unwanted horizontal scrolling upon navigation.
(setopt scroll-preserve-screen-position t)

(setopt scroll-conservatively 1)

;; Add a margin when scrolling vertically (or don't).
(setq-default scroll-margin 4)

(define-keymap :keymap (current-global-map)
  ;; The default bindings feel backwards to me.
  "C-x <" #'scroll-right
  "C-x >" #'scroll-left

  "<wheel-left>" #'scroll-left
  "<wheel-right>" #'scroll-right)
#+end_src

*** Auto-revert buffers

#+begin_src emacs-lisp
;; Ensure the non-file-visiting buffers are also auto-reverted as needed. For
;; example, this will cause Dired to refresh a file list when the directory
;; contents have changed.
(setopt global-auto-revert-non-file-buffers t)

;; Automatically revert a buffer if its file has changed on disk.
(add-hook 'ceamx-after-init-hook #'global-auto-revert-mode)
#+end_src

*** Buffer selection

#+begin_src emacs-lisp
(setopt ibuffer-movement-cycle t)

;; FIXME: auto-select window
(keymap-global-set "C-x C-b" #'ibuffer-list-buffers)
#+end_src

*** ~mwim~: Move-Where-I-Mean line positions

+ src :: <https://github.com/alezost/mwim.el/blob/master/README.org#usage>

#+begin_src emacs-lisp
(package! mwim
  (keymap-global-set "C-a" #'mwim-beginning)
  (keymap-global-set "C-e" #'mwim-end))
#+end_src

*** ~hl-line~: Enable highlighting of the current line

#+begin_src emacs-lisp
(add-hook 'prog-mode-hook #'hl-line-mode)
(add-hook 'package-menu-mode-hook #'hl-line-mode)
#+end_src

*** ~goto-address~: Linkify URLs and email addresses in buffers [builtin]

#+begin_src emacs-lisp
(autoload 'goto-address-prog-mode "goto-addr")

(add-hook 'prog-mode-hook #'goto-address-prog-mode)
#+end_src

*** ~uniquify~: Disambiguate identically-named buffers [builtin]

#+begin_src emacs-lisp
(with-eval-after-load 'uniquify
  (setopt uniquify-buffer-name-style 'forward)
  (setopt uniquify-separator "/")

  ;; Rename after killing uniquified buffer.
  (setopt uniquify-after-kill-buffer-p t)

  ;; Don't muck with special buffers.
  (setopt uniquify-ignore-buffers-re "^\\*"))
#+end_src

*** ~link-hint~: Activate links in buffer with ~avy~

<https://github.com/noctuid/link-hint.el>

#+begin_src emacs-lisp
(package! link-hint
  (global-keys!
    "M-g u" #'link-hint-open-link
    "M-g U" #'link-hint-copy-link))
#+end_src

*** ~expand-region~: Expand your regions

<https://github.com/magnars/expand-region.el>

#+begin_src emacs-lisp
(package! expand-region
  (keymap-global-set "C-=" #'er/expand-region))
#+end_src

*** ~rainbow-mode~: Colorize color names and hexcodes in buffers

<https://elpa.gnu.org/packages/rainbow-mode.html>

#+begin_src emacs-lisp
(package! rainbow-mode)
#+end_src

*** ~lentic~: Create decoupled views of the same content

#+begin_src emacs-lisp
(package! lentic
  (global-lentic-mode))

(with-eval-after-load 'lentic
  (add-to-list 'safe-local-variable-values '(lentic-init . lentic-orgel-org-init)))
#+end_src


*** Global Keybindings

#+begin_src emacs-lisp
(define-keymap :keymap (current-global-map)
  "C-c [" #'previous-buffer
  "C-c ]" #'next-buffer
  "C-c `" #'mode-line-other-buffer

  "C-x C-b" #'ibuffer)
#+end_src

*** DISABLED ~repeat-map~ for basic buffer navigation commands

<https://www.reddit.com/r/emacs/comments/1adwnse/repeatmode_is_awesome_share_you_useful_configs/>

It seemed like a great idea, but I had quite a few issues.

- [ ] Disable ~repeat-help-auto~ for these
- [ ] Does not work with Meow. See [[*Meow overwrites repeat-mode?]]
- [ ] Pauses between every single movement command not specified in the repeat map

#+begin_src emacs-lisp :tangle no
(defvar-keymap ceamx-buffer-movement-repeat-map
  :repeat t
  ;; :repeat (:enter '(next-line previous-line forward-word))

  "n" #'next-line
  "p" #'previous-line
  "f" #'forward-word
  "b" #'backward-word

  ;; FIXME: inconsistent with established binding mnemonics
  "u" #'scroll-down-command
  "d" #'scroll-up-command)
#+end_src

*** Footer

#+begin_src emacs-lisp
(provide 'init-buffer)
;;; init-buffer.el ends here
#+end_src

** Library

#+begin_src emacs-lisp :tangle lisp/lib-buffer.el
;;; lib-buffer.el --- Helpers for buffers            -*- lexical-binding: t; -*-

;; Copyright (C) 2024  Chris Montgomery

;; Author: Chris Montgomery <chmont@proton.me>
;; Keywords: local

;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.

;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.

;; You should have received a copy of the GNU General Public License
;; along with this program.  If not, see <https://www.gnu.org/licenses/>.

;;; Commentary:

;;; Code:

;;;###autoload
(defun ceamx-buffer-mode (&optional buffer-or-name)
  "Return the major mode associated with a buffer.
If BUFFER-OR-NAME is nil, return the current buffer's mode."
  (buffer-local-value 'major-mode
                      (if buffer-or-name
                          (get-buffer buffer-or-name)
                        (current-buffer))))

(provide 'lib-buffer)
;;; lib-buffer.el ends here
#+end_src

** User Options

#+begin_src emacs-lisp :tangle lisp/config-buffer.el
;;; config-buffer.el --- Variables relating to buffers and modes  -*- lexical-binding: t; -*-

;; Copyright (C) 2024  Chris Montgomery

;; Author: Chris Montgomery <chmont@proton.me>
;; Keywords: local

;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.

;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.

;; You should have received a copy of the GNU General Public License
;; along with this program.  If not, see <https://www.gnu.org/licenses/>.

;;; Commentary:

;;; Sources:

;; <https://github.com/karthink/.emacs.d/blob/6aa2e034ce641af60c317697de786bedc2f43a71/lisp/setup-windows.el>

;;; Code:

(require 'ceamx-paths)

(defvar ceamx-occur-grep-modes-list
  '(occur-mode
     grep-mode
     xref--xref-buffer-mode
     flymake-diagnostics-buffer-mode)
  "List of major-modes used in occur-type buffers.")

(defvar ceamx-repl-modes-list
  '(eshell-mode
    inferior-emacs-lisp-mode            ; ielm
    shell-mode
    eat-mode
    nix-repl-mode)
  "List of major-modes used in REPL buffers.")

(defvar ceamx-repl-buffer-names-list
  '("^\\*\\(?:.*?-\\)\\{0,1\\}e*shell[^z-a]*\\(?:\\*\\|<[[:digit:]]+>\\)$"
    "\\*.*REPL.*\\*"
    "\\*Inferior .*\\*$"
    "\\*ielm\\*"
    "\\*edebug\\*")
  "List of buffer names used in REPL buffers.")

(defvar ceamx-help-modes-list
  '(helpful-mode
     help-mode
     eldoc-mode)
  "List of major-modes used in documentation buffers.")

(defvar ceamx-help-buffer-names-list
  '("^\\*Apropos"
     "^\\*eldoc\\*")
  "List of buffer names used in help buffers.")

(defvar ceamx-manual-modes-list '(Man-mode woman-mode)
  "List of major-modes used in Man-type buffers.")

(defvar ceamx-message-modes-list
  '(compilation-mode
    edebug-eval-mode)
  "List of major-modes used in message buffers.")

(defvar ceamx-buffer-read-only-dirs-list
  (list ceamx-packages-dir)
  "List of directories whose files should be opened in read-only buffers.")

(provide 'config-buffer)
;;; config-buffer.el ends here
#+end_src


* Editor
:PROPERTIES:
:header-args: :noweb-ref init-editor
:END:

#+begin_src emacs-lisp
(require 'ceamx-keymaps)

(require 'config-editor)

(require 'ceamx-lib)
(require 'lib-editor)
(require 'lib-simple)
#+end_src

** Enable some commands that Emacs disables by default

#+begin_src emacs-lisp
(dolist (cmd '(downcase-region
               scroll-left
               upcase-region))
  (put cmd 'disabled nil))
#+end_src

** ~ceamx-insert-map~: [C-c i] keymap for inserting insertables

#+begin_src emacs-lisp
(keymap-global-set "C-c i" #'ceamx-insert-map)
#+end_src

** Replace region when inserting text

#+begin_src emacs-lisp
(delete-selection-mode 1)
#+end_src

*** TODO Meow does not seem to respect ~delete-selection-mode~?

** Improve sentence legibility and parsing with double-spaced ending

#+begin_src emacs-lisp
;; Yes, it may appear strange and archaic, and yet... I realized that I actually
;; do *prefer* this for readability.  And so, I am giving it a shot.  My
;; grandfather, who was an English teacher for most of the 20th century, would
;; be very proud.  Though even writing this paragraph has been difficult.

(setopt sentence-end-double-space t)

(keymap-global-set "M-Q" #'repunctuate-sentences)
#+end_src

** Don't consider camelCaseWORDs as separate words

#+begin_src emacs-lisp
(global-subword-mode -1)
#+end_src

** ~string-inflection~: Commands to cycle through word casing

#+begin_src emacs-lisp
(require 'lib-editor)

(package! string-inflection)

(defvar-keymap ceamx-string-repeat-map
  :repeat t

  "c" #'ceamx/cycle-string-inflection)
#+end_src

#+begin_src emacs-lisp :noweb-ref lib-editor
(defun ceamx/cycle-string-inflection ()
  "Cycle through `string-inflection' styles appropriate to the major-mode."
  (interactive)
  (pcase major-mode
    (`emacs-lisp-mode (string-inflection-all-cycle))
    (`python-mode (string-inflection-python-style-cycle))
    (`java-mode (string-inflection-java-style-cycle))
    (`elixir-mode (string-inflection-elixir-style-cycle))
    (_ (string-inflection-ruby-style-cycle))))
#+end_src


** Automatically wrap text at ~fill-column~ in some contexts

#+begin_src emacs-lisp
(setopt comment-auto-fill-only-comments nil)

(def-hook! +prog-mode-auto-fill-comments-only-h ()
  'prog-mode-hook
  "Set `auto-fill-mode' to only fill comments when in programming modes."
  (setq-local comment-auto-fill-only-comments t))

(dolist (mode-hook '(prog-mode-hook text-mode-hook))
  (add-hook mode-hook #'auto-fill-mode))
#+end_src

** Provide commands to "unfill" text

- Website :: <https://github.com/purcell/unfill>

#+begin_src emacs-lisp
(package! unfill
  (keymap-global-set "M-q" #'unfill-toggle))
#+end_src

** Comments

#+begin_src emacs-lisp
(keymap-global-set "<remap> <default-indent-new-line>" #'ceamx/continue-comment)
#+end_src

*** ~ceamx/continue-comment~
:PROPERTIES:
:header-args: :noweb-ref lib-editor
:END:

<https://github.com/radian-software/radian/blob/20c0c9d929a57836754559b470ba4c3c20f4212a/emacs/radian.el#L1781-L1797>

#+begin_src emacs-lisp
(defun ceamx/continue-comment ()
  "Continue current comment, preserving trailing whitespace.
This differs from `default-indent-new-line' in the following way:

If you have a comment like \";; Some text\" with point at the end
of the line, then running `default-indent-new-line' will get you
a new line with \";; \", but running it again will get you a line
with only \";;\" (no trailing whitespace). This is annoying for
inserting a new paragraph in a comment. With this command, the
two inserted lines are the same."
  (interactive)
  ;; `default-indent-new-line' uses `delete-horizontal-space'
  ;; because in auto-filling we want to avoid the space character at
  ;; the end of the line from being put at the beginning of the next
  ;; line. But when continuing a comment it's not desired.
  (cl-letf (((symbol-function #'delete-horizontal-space) #'ignore))
    (default-indent-new-line)))
#+end_src

** Semantic pair matching :pairs:

See the Info node [[info:emacs#Matching]]


#+begin_src emacs-lisp
(setopt blink-matching-paren t)
;; Avoid "expression" style, which looks too much like a selected region.
(setopt show-paren-style 'parenthesis)

(setopt electric-pair-preserve-balance t)
(setopt electric-pair-delete-adjacent-pairs t)
(setopt electric-pair-skip-whitespace t)
;; TODO: evaluating...
(setopt electric-pair-open-newline-between-pairs t)

(electric-pair-mode 1)
(show-paren-mode 1)
#+end_src

*** Set up a keymap for common usages of ~insert-pair~

#+begin_src emacs-lisp
(define-keymap :keymap ceamx-pairs-map
  "(" '("paren" . insert-pair)
  "[" '("square-b" . insert-pair)
  "{" '("curly-b" . insert-pair)
  "<" '("angle-b" . insert-pair)
  "'" '("s-quote" . insert-pair)
  "\"" '("d-quote" . insert-pair)
  "`" '("b-tick" . insert-pair)
  "_" '("u-score" . insert-pair)
  "*" '("star" . insert-pair)
  "=" '("equals" . insert-pair)
  ":" '("colon" . insert-pair))

(keymap-set ceamx-insert-map "P" ceamx-pairs-map)
#+end_src

** Formatting :formatting:

#+begin_src emacs-lisp
(setopt require-final-newline t)
#+end_src

*** ~whitespace-mode~ [builtin]: visualize whitespace

This mode is buffer-local. It might be undesireable in some cases, so enable
it selectively.

#+begin_src emacs-lisp
(add-hook 'prog-mode-hook #'whitespace-mode)

(setopt whitespace-style
        '(face
          tabs
          tab-mark
          trailing
          missing-newline-at-eof))
#+end_src

*** Indentation

#+begin_src emacs-lisp
(setq-default indent-tabs-mode nil)
(setopt indent-tabs-mode nil)
(setopt backward-delete-char-untabify-method 'untabify)
#+end_src

**** ~electric-indent-mode~ [builtin]: Emacs' best attempt at automatic indentation

#+begin_src emacs-lisp
(electric-indent-mode 1)
#+end_src

*** Delete trailing whitespace before saving files

#+begin_src emacs-lisp
(add-hook 'before-save-hook #'delete-trailing-whitespace)
#+end_src

*** ~editorconfig~: EditorConfig integration

#+begin_src emacs-lisp
;; <https://editorconfig.org>

(use-package editorconfig
  :commands (editorconfig-mode)
  :init
  (add-hook 'on-first-file-hook #'editorconfig-mode))
#+end_src

**** Prevent ~editorconfig~ from exploding ~org-mode~ buffers

#+begin_src emacs-lisp
;; <https://github.com/doomemacs/doomemacs/commit/43870bf8318f6471c4ce5e14565c9f0a3fb6e368>

(defun +editorconfig-enforce-org-mode-tab-width-h (props)
  "Prevent `editorconfig' from changing `tab-width' in `org-mode'.
A \"tab-width\" of any value other than 8 is an error state in
org-mode, so it must not be changed.

PROPS is as in `editorconfig-after-apply-functions'."
  (when (and (gethash 'indent_size props)
             (derived-mode-p 'org-mode))
    (setq tab-width 8)))

(with-eval-after-load 'editorconfig
  (add-hook 'editorconfig-after-apply-functions
            #'+editorconfig-enforce-org-mode-tab-width-h))
#+end_src

*** [[https://github.com/purcell/emacs-reformatter][purcell/emacs-reformatter]]: KISS DIY FMT

#+begin_src emacs-lisp
(package! reformatter
  (require 'reformatter))
#+end_src

**** Configure generalized polyglot formatters

***** =prettier=

- Source :: <https://github.com/akirak/flake-templates/blob/629b04932dc71e3e0213d66a0aa8a08cd0b64922/README.md#emacs>

#+begin_src emacs-lisp
(after! reformatter
  (reformatter-define prettier
    :program "prettier"
    :args (list (concat "--plugin-search-dir="
                        (expand-file-name
                         (locate-dominating-file default-directory "package.json")))
                "--stdin-filepath" (buffer-file-name))))
#+end_src

***** =biome=

- Docs :: <https://biomejs.dev/guides/integrate-in-editor/>

This should, possibly more ideally, be run as an LSP client, but AFAIK one does
not yet exist for Eglot (only LSP-Mode).

#+begin_src emacs-lisp
(after! reformatter
  (reformatter-define biome-format
    :program "biome"
    :args (list "format" "--stdin-file-path" (buffer-file-name))))
#+end_src

**** TODO Inhibit on-save formatting with prefix argument

*** [[https://github.com/radian-software/apheleia][radian-software/apheleia]]: opinionated code reformatting

In case you run into issues with ~web-mode~ not updating syntax highlighting after
formatting (or other arbitrary modifications):
<https://github.com/doomemacs/doomemacs/blob/35dc13632b3177b9efedad212f2180f69e756853/modules/editor/format/config.el#L74-L83>

#+begin_src emacs-lisp
(package! apheleia
  ;; (apheleia-global-mode 1)
  )

(after! (apheleia)
  (blackout 'apheleia-mode " Aph"))
#+end_src

**** Inhibit automatic formatting in some contexts

Unlike ~reformatter~, ~apheleia~ will /always/ run if it can.  A blessing and a curse.
This section handles the curse.

#+begin_src emacs-lisp :noweb-ref config-editor
(defcustom ceamx-format-on-save-disabled-modes
  '(emacs-lisp-mode                     ; conflict with `lispy' indent
    org-msg-edit-mode)
  "A list of major modes in which to not reformat the buffer upon saving.
When nil, buffers will always be formatted upon save. When
non-nil, buffers will never be formatted upon save."
  :group 'ceamx
  :type '(choice boolean (repeat symbol)))
#+end_src

#+begin_src emacs-lisp :noweb-ref lib-editor
(defun ceamx-editor-format-maybe-inhibit-h ()
  "Check if formatting should be disabled for current buffer."
  (or (eq major-mode 'fundamental-mode)
      (string-blank-p (buffer-name))
      (eq ceamx-format-on-save-disabled-modes t)
      (not (null (memq major-mode ceamx-format-on-save-disabled-modes)))))
#+end_src

#+begin_src emacs-lisp
(require 'lib-editor)

(after! (apheleia)
  (add-to-list 'apheleia-inhibit-functions #'ceamx-editor-format-maybe-inhibit-h))
#+end_src

**** Inhibit on-save formatting with prefix argument

- Source :: <https://github.com/radian-software/radian/blob/20c0c9d929a57836754559b470ba4c3c20f4212a/emacs/radian.el#L2266-L2270>

#+begin_src emacs-lisp
(def-advice! +apheleia-save-buffer-maybe-reformat-a (func &optional arg)
  :around #'save-buffer
  "Inhibit reformatting-on-save when providing a prefix argument to \\[save-buffer]."
  (let ((apheleia-mode (and apheleia-mode (member arg '(nil 1)))))
    (funcall func)))
#+end_src

*** Use the Biome formatter for supported major modes :lang:

- Reference :: <https://biomejs.dev/internals/language-support/>

As of <2024-05-24 Fri>

#+begin_src emacs-lisp :noweb-ref config-editor
;; As of <2024-05-24 Fri>
;; <https://biomejs.dev/internals/language-support/>
(defconst ceamx-editor-format-biome-modes-list
  '(javascript-mode js-mode js-ts-mode js3-mode
    typescript-mode typescript-ts-mode
    js-jsx-mode tsx-ts-mode
    json-mode json-ts-mode)
  "List of major-mode symbols for the languages supported by the Biome formatter.")
#+end_src

#+begin_src emacs-lisp
(after! reformatter
  (require 'derived)
  (dolist (hook (mapcar #'derived-mode-hook-name ceamx-editor-format-biome-modes-list))
    (add-hook hook #'biome-format-on-save-mode)))
#+end_src

#+begin_src emacs-lisp
(after! apheleia
  (add-to-list 'apheleia-formatters '(biome "biome" "format" "--stdin-file-path" filepath))

  (dolist (mode ceamx-editor-format-biome-modes-list)
    (add-to-list 'apheleia-mode-alist '(mode . biome))))
#+end_src

** ~puni~: versatile structured editing

<https://github.com/AmaiKinono/puni>

#+begin_src emacs-lisp
(package! puni
  ;; (puni-global-mode)
  ;; (add-hook 'prog-mode-hook #'puni-mode)
  ;; (add-hook 'term-mode-hook #'puni-disable-puni-mode)
  )

;; (after! puni
;;     ;; (define-keymap :keymap puni-mode-map
;;   ;;   "C-M-f" #'puni-forward-sexp
;;   ;;   "C-M-b" #'puni-backward-sexp
;;   ;;   "C-M-a" #'puni-beginning-of-sexp
;;   ;;   "C-M-e" #'puni-end-of-sexp
;;   ;;   "C-M-[" #'puni-backward-sexp-or-up-list
;;   ;;   "C-M-]" #'puni-forward-sexp-or-up-list

;;   ;;   "M-(" #'puni-syntactic-forward-punct
;;   ;;   "M-)" #'puni-syntactic-backward-punct
;;   ;;   )

;; )
#+end_src

** ~drag-stuff~: drag stuff around in arbitrary directions

<https://github.com/rejeep/drag-stuff.el>

 This package appears to be abandoned since 2017.
 But, as of <2023-09-06>, it still works well.


#+begin_src emacs-lisp
(use-package drag-stuff
  :bind
  (([M-up] . drag-stuff-up)
   ([M-right] . drag-stuff-right)
   ([M-down] . drag-stuff-down)
   ([M-left] . drag-stuff-left)))
#+end_src


*** Issues

Note that as of [2023-07-20] there are numerous warnings about deprecated functions in
recent versions of Emacs:

<rejeep/drag-stuff.el#36>

*** Alternatives

I haven't yet found any other package to move arbitrary regions up/down while
preserving column position.

~move-text-mode~ <https://github.com/emacsfodder/move-text>, claims to do
this but fails pretty badly, moving the region/selection to the first column
regardless of its original position.
#+end_src

** ~rect~ [builtin]: operate on a buffer rectangularly

<https://github.com/abo-abo/hydra/wiki/Rectangle-Operations#rectangle-2>

#+begin_src emacs-lisp
(use-feature! rect
  :config
  (use-feature! hydra
    :config
    (defhydra hydra-rectangle (:body-pre (rectangle-mark-mode 1)
                                         :color pink
                                         :hint nil
                                         :post (deactivate-mark))
      "
  ^_k_^       _w_ copy      _o_pen       _N_umber-lines            |\\     -,,,--,,_
_h_   _l_     _y_ank        _t_ype       _e_xchange-point          /,`.-'`'   ..  \-;;,_
  ^_j_^       _d_ kill      _c_lear      _r_eset-region-mark      |,4-  ) )_   .;.(  `'-'
^^^^          _u_ndo        _g_ quit     ^ ^                     '---''(./..)-'(_\_)
"
      ("k" rectangle-previous-line)
      ("j" rectangle-next-line)
      ("h" rectangle-backward-char)
      ("l" rectangle-forward-char)
      ("d" kill-rectangle)               ;; C-x r k
      ("y" yank-rectangle)               ;; C-x r y
      ("w" copy-rectangle-as-kill)       ;; C-x r M-w
      ("o" open-rectangle)               ;; C-x r o
      ("t" string-rectangle)             ;; C-x r t
      ("c" clear-rectangle)              ;; C-x r c
      ("e" rectangle-exchange-point-and-mark) ;; C-x C-x
      ("N" rectangle-number-lines)            ;; C-x r N
      ("r" (if (region-active-p)
               (deactivate-mark)
             (rectangle-mark-mode 1)))
      ("u" undo nil)
      ("g" nil))

    (when (fboundp 'hydra-rectangle/body)
      (keymap-global-set "C-x SPC" #'hydra-rectangle/body)
      (keymap-global-set "C-x M-r" #'rectangle-mark-mode))))
#+end_src

** ~multiple-cursors~: Sublime-esque multi-cursor editing

<https://github.com/magnars/multiple-cursors.el>

#+begin_src emacs-lisp
(use-package multiple-cursors
  :demand t
  ;; :autoload (mc/num-cursors)

  :config

  (use-feature! hydra
    :config

    ;; TODO: convert to transient
    (defhydra hydra-multiple-cursors (:hint nil)
      "
 Up^^             Down^^           Miscellaneous           % 2(mc/num-cursors) cursor%s(if (> (mc/num-cursors) 1) \"s\" \"\")
------------------------------------------------------------------
 [_p_]   Next     [_n_]   Next     [_l_] Edit lines  [_0_] Insert numbers
 [_P_]   Skip     [_N_]   Skip     [_a_] Mark all    [_A_] Insert letters
 [_M-p_] Unmark   [_M-n_] Unmark   [_s_] Search      [_q_] Quit
 [_|_] Align with input CHAR       [Click] Cursor at point"
      ("l" mc/edit-lines :exit t)
      ("a" mc/mark-all-like-this :exit t)
      ("n" mc/mark-next-like-this)
      ("N" mc/skip-to-next-like-this)
      ("M-n" mc/unmark-next-like-this)
      ("p" mc/mark-previous-like-this)
      ("P" mc/skip-to-previous-like-this)
      ("M-p" mc/unmark-previous-like-this)
      ("|" mc/vertical-align)
      ("s" mc/mark-all-in-region-regexp :exit t)
      ("0" mc/insert-numbers :exit t)
      ("A" mc/insert-letters :exit t)
      ("<mouse-1>" mc/add-cursor-on-click)
      ;; Help with click recognition in this hydra
      ("<down-mouse-1>" ignore)
      ("<drag-mouse-1>" ignore)
      ("q" nil))))
#+end_src

** ~easy-kill~

<https://github.com/leoliu/easy-kill/blob/master/README.rst>

#+begin_example
w => word
s => sexp
l => list
d => defun
D => defun name
f => file
b => buffer name
       ->"-": `default-directory'
       ->"+": full path
       ->"0": basename
#+end_example

#+begin_src emacs-lisp
(package! easy-kill
  (keymap-global-set "M-w" #'easy-kill)   ; override `kill-ring-save'
  (keymap-global-set "C-M-@" #'easy-mark) ; override `mark-sexp'
  )
#+end_src

** ~ialign~: Interactively ~align-regexp~

<https://github.com/mkcms/interactive-align/blob/master/README.org#usage>

#+begin_src emacs-lisp
(package! ialign
  (keymap-global-set "C-x l" #'ialign))
#+end_src

** Structural editing

Prepare a prefix commands for binding structural editing commands:

#+begin_src emacs-lisp
(define-prefix-command 'ceamx-structural-editing-prefix)
(keymap-global-set "C-c s" #'ceamx-structural-editing-prefix)
#+end_src

#+begin_src emacs-lisp :noweb-ref config-editor
(defcustom ceamx-structured-editing-style 'lispy
  "The structured editing provider."
  :group 'ceamx
  :type '(choice :tag "Structured editing style" :value lispy
          (const :tag "Lispy" lispy)
          (const :tag "Puni" puni)))
#+end_src

*** Structural editing with ~puni~

Work in progress.

This is still not quite usable as a Lispy replacement.  The goal is to use
similar structureal editing keybindings across many major-modes.

Note that this repeat-map should not be used in tandem with ~lispy-mode~ because
its bindings generally would need ~puni-mode~ to be active.

- <https://karthinks.com/software/a-consistent-structural-editing-interface/>
- <https://github.com/suliveevil/emacs.d?tab=readme-ov-file#repeat-repeat-mode>
- <https://github.com/karthink/.emacs.d/blob/master/init.el#L3209-L3241>


- [ ] Disable ~repeat-exit-timeout~ for this map only

#+begin_src emacs-lisp
(after! puni

  (defvar-keymap structural-editing-map
    :repeat t

    "d" #'puni-forward-delete-char
    ;; "DEL" #'puni-backward-delete-char
    ;; "D" #'puni-forward-kill-word
    ;; "M-DEL" #'puni-backward-kill-word
    ;; "C-k" #'puni-kill-line
    ;; "M-k" #'puni-backward-kill-line
    "k" #'kill-sexp

    "f" #'puni-forward-sexp
    "b" #'puni-backward-sexp
    "[" #'puni-backward-sexp-or-up-list
    "]" #'puni-forward-sexp-or-up-list
    "a" #'puni-beginning-of-sexp
    "e" #'puni-end-of-sexp
    "u" #'puni-up-list
    "M-(" #'puni-syntactic-forward-punct
    "M-)" #'puni-syntactic-backward-punct

    "\\" #'indent-region
    "/" #'undo

    ">" #'puni-slurp-forward
    "<" #'puni-slurp-backward
    "}" #'puni-barf-forward
    "{" #'puni-barf-backward
    "R" #'puni-raise
    "t" #'puni-transpose
    "C" #'puni-convolute
    ;; FIXME: avoid meow dependency -- no puni equivalent
    ;; "J" #'meow-join-sexp
    "S" #'puni-split
    ;; FIXME: for `emacs-lisp-mode' only
    "x" #'eval-defun

    ))

;; FIXME: wrong type argument symbolp
;; (map-keymap (lambda (_ cmd)
;;               (put cmd 'repeat-exit-timeout nil)) structural-editing-map)

#+end_src

** Miscellaneous keybindings

#+begin_src emacs-lisp
(keymap-set ceamx-insert-map "d" #'ceamx/insert-date)

(define-keymap :keymap (current-global-map)
  "C-=" #'ceamx/insert-date
  "C-<" #'ceamx/escape-url-dwim

  ;; Logical progression from M-f for `forward-word'.
  ;; See also `forward-sexp'
  "M-F" #'forward-symbol)
#+end_src


* Files
:PROPERTIES:
:header-args: :noweb-ref init-files
:END:

#+begin_src emacs-lisp
(require 'ceamx-keymaps)
(require 'ceamx-paths)

(require 'ceamx-lib)
(require 'lib-files)
#+end_src

** Manage backup files and prevent file-lock clutter
#+begin_src emacs-lisp
(setopt create-lockfiles nil)
(setopt make-backup-files nil)

(when make-backup-files
  (setopt version-control t)
  (setopt delete-old-versions t)
  (setopt kept-new-versions 5)
  (setopt kept-old-versions 5))

(setopt delete-by-moving-to-trash t)


#+end_src

** Add file headers to new files

#+begin_src emacs-lisp
(use-feature! autoinsert
  :config
  (auto-insert-mode t))
#+end_src

** Configure finding of files

#+begin_src emacs-lisp
(setopt find-file-suppress-same-file-warnings t)
(setopt find-file-visit-truename t)
#+end_src

*** Prompt to create missing parent directories for not-found files

<https://github.com/doomemacs/doomemacs/blob/e96624926d724aff98e862221422cd7124a99c19/lisp/doom-editor.el#L78-L89>

#+begin_src emacs-lisp
(defun ceamx-create-missing-directories-h ()
  "Automatically create missing directories when creating new files."
  (unless (file-remote-p buffer-file-name)
    (let ((parent-directory (file-name-directory buffer-file-name)))
      (and (not (file-directory-p parent-directory))
           (y-or-n-p (format "Directory `%s' does not exist! Create it?"
                             parent-directory))
           (progn (make-directory parent-directory 'parents)
                  t)))))

(add-hook 'find-file-not-found-functions #'ceamx-create-missing-directories-h)
#+end_src

** Configure auto-saving of file-visiting buffers

#+begin_src emacs-lisp
;; Prevent creation of the list of all auto-saved files.
(setopt auto-save-list-file-prefix nil)

;; Number of input events before autosave
(setopt auto-save-interval 300)

;; Idle interval for all file-visiting buffers
(setopt auto-save-visited-interval 30)

;; Idle interval before autosave
(setopt auto-save-timeout 30)

;; Don't create auto-save "~" files.
(setopt auto-save-default nil)

;; Save file-visiting buffers according to the configured timers.
(auto-save-visited-mode)
#+end_src

** Keybindings

#+begin_src emacs-lisp
(global-keys!
  "C-c f" '("[ File ]" . ceamx-file-map)

  ;; I mistakenly hit this sequence frequently instead of C-x C-f, but have never
  ;; once needed to configure `fill-column' on-demand (that should be configured
  ;; explicitly, or simply call `set-fill-column' with M-x).
  "C-x f" #'find-file)

(define-keymap :keymap ceamx-file-map
  ;; TODO
  ;; "y" #'+yank-this-file-name

  "c" '("copy..." . ceamx/copy-this-file)
  "d" '("delete" . ceamx/delete-this-file)
  "f" #'find-file
  "F" #'find-file-other-window
  "r" '("rename/move..." . ceamx/move-this-file)
  "s" #'save-buffer
  "S" '("save as..." . write-file)
  "U" #'ceamx/sudo-find-file

  "C-d" '("diff with..." . ceamx/diff-with-file))
#+end_src

** Functions and commands
:PROPERTIES:
:header-args: :noweb-ref lib-files
:END:

#+begin_src emacs-lisp
(require 'cl-lib)
#+end_src

#+begin_src emacs-lisp
;; FIXME: is this supposed to work on save? not working in either magit or projectile
;; via <https://github.com/doomemacs/doomemacs/blob/e96624926d724aff98e862221422cd7124a99c19/lisp/lib/files.el#L369-L391>
(defun ceamx-files--update-refs (&rest files)
  "Ensure FILES are updated in `recentf', `magit' and `save-place'."
  (let (toplevels)
    (dolist (file files)
      (when (featurep 'vc)
        (vc-file-clearprops file)
        (when-let (buffer (get-file-buffer file))
          (with-current-buffer buffer
            (vc-refresh-state))))
      (when (featurep 'magit)
        (when-let (default-directory (magit-toplevel (file-name-directory file)))
          (cl-pushnew default-directory toplevels)))
      (unless (file-readable-p file)
        (when (bound-and-true-p recentf-mode)
          (recentf-remove-if-non-kept file))
        (when (and
               (bound-and-true-p projectile-mode)
               ;; FIXME: de-doom
               ;; (doom-project-p)
               ;; (projectile-file-cached-p file (doom-project-root))
               )
          (projectile-purge-file-from-cache file)))
      )
    (dolist (default-directory toplevels)
      (magit-refresh))
    (when (bound-and-true-p save-place-mode)
      (save-place-forget-unreadable-files))))
#+end_src

*** Commands

#+begin_src emacs-lisp
;; via <https://github.com/emacs-evil/evil/blob/9eb69b7f5b3c72cfc66f69b3242e935015780654/evil-commands.el#L3325-L3332>
(defun ceamx/file-edit (file &optional bang)
  "Open FILE.
If no FILE is specified, reload the current buffer from disk."
  :repeat nil
  (interactive "<f><!>")
  (if file
      (find-file file)
    (revert-buffer bang (or bang (not (buffer-modified-p))) t)))
#+end_src

#+begin_src emacs-lisp
;; via <https://github.com/emacs-evil/evil/blob/9eb69b7f5b3c72cfc66f69b3242e935015780654/evil-commands.el#L4652-L4660>
(defun ceamx/buffer-new (&optional file)
  "Edit a new unnamed buffer or FILE."
  :repeat nil
  (interactive "<f>")
  (if file
      (ceamx/file-edit file)
    (let ((buffer (generate-new-buffer "*new*")))
      (set-buffer-major-mode buffer)
      (set-window-buffer nil buffer))))
#+end_src

#+begin_src emacs-lisp
;; FIXME: this does not actually kill its buffers -- buffer must be deleted manually
;; via <https://github.com/doomemacs/doomemacs/blob/e96624926d724aff98e862221422cd7124a99c19/lisp/lib/files.el#L397-L424>
(defun ceamx/delete-this-file (&optional path force-p)
  "Delete PATH, kill its buffers and expunge it from vc/magit cache.
If PATH is not specified, default to the current buffer's file.
If FORCE-P, delete without confirmation."
  (interactive
   (list (buffer-file-name (buffer-base-buffer))
         current-prefix-arg))
  (let* ((path (or path (buffer-file-name (buffer-base-buffer))))
         (short-path (and path (abbreviate-file-name path))))
    (unless path
      (user-error "Buffer is not visiting any file"))
    (unless (file-exists-p path)
      (error "File doesn't exist: %s" path))
    (unless (or force-p (y-or-n-p (format "Really delete %S?" short-path)))
      (user-error "Aborted"))
    (let ((buf (current-buffer)))
      (unwind-protect
          (progn (delete-file path t) t)
        (if (file-exists-p path)
            (error "Failed to delete %S" short-path)
          ;; Ensures that windows displaying this buffer will be switched to
          ;; real buffers (`doom-real-buffer-p')
          ;; FIXME: implement -- invent the universe -- but the stuff within is very useful to us (e.g. doom-real-buffer-p and filtering buffers)...
          ;; (doom/kill-this-buffer-in-all-windows buf t)
          ;; TODO: remove when the above is implemented -- `kill-this-buffer' only removes the one buffer
          (kill-this-buffer)
          (ceamx-files--update-refs path)
          (message "Deleted %S" short-path))))))
#+end_src

#+begin_src emacs-lisp
;; via <https://github.com/doomemacs/doomemacs/blob/e96624926d724aff98e862221422cd7124a99c19/lisp/lib/files.el#L427-L441>
(defun ceamx/copy-this-file (new-path &optional force-p)
  "Copy current buffer's file to NEW-PATH.
If FORCE-P, overwrite the destination file if it exists, without confirmation."
  (interactive
   (list (read-file-name "Copy file to: ")
         current-prefix-arg))
  (unless (and buffer-file-name (file-exists-p buffer-file-name))
    (user-error "Buffer is not visiting any file"))
  (let ((old-path (buffer-file-name (buffer-base-buffer)))
        (new-path (expand-file-name new-path)))
    (make-directory (file-name-directory new-path) 't)
    (copy-file old-path new-path (or force-p 1))
    (ceamx-files--update-refs old-path new-path)
    (message "File copied to %S" (abbreviate-file-name new-path))))
#+end_src

#+begin_src emacs-lisp
;; via <https://github.com/doomemacs/doomemacs/blob/e96624926d724aff98e862221422cd7124a99c19/lisp/lib/files.el#L427-L441>
(defun ceamx/move-this-file (new-path &optional force-p)
  "Move current buffer's file to NEW-PATH.
If FORCE-P, overwrite the destination file if it exists, without confirmation."
  (interactive
   (list (read-file-name "Move file to: ")
         current-prefix-arg))
  (unless (and buffer-file-name (file-exists-p buffer-file-name))
    (user-error "Buffer is not visiting any file"))
  (let ((old-path (buffer-file-name (buffer-base-buffer)))
        (new-path (expand-file-name new-path)))
    (when (directory-name-p new-path)
      (setq new-path (concat new-path (file-name-nondirectory old-path))))
    (make-directory (file-name-directory new-path) 't)
    (rename-file old-path new-path (or force-p 1))
    (set-visited-file-name new-path t t)
    (ceamx-files--update-refs old-path new-path)
    (message "File moved to %S" (abbreviate-file-name new-path))))
#+end_src

#+begin_src emacs-lisp
;; via <https://github.com/noctuid/dotfiles/blob/434ddb77c4b40f4b7ab2246cc2254aa4f408b16f/emacs/.emacs.d/awaken.org>
(defun ceamx/kill-this-buffer ()
  "`kill-this-buffer' with no menu-bar checks.
`kill-this-buffer' is supposed to be called from the menu bar.
See <https://www.reddit.com/r/emacs/comments/64xb3q/killthisbuffer_sometimes_just_stops_working/>."
  (interactive)
  (if (minibufferp)
      (abort-recursive-edit)
    (kill-buffer (current-buffer))))
#+end_src

#+begin_src emacs-lisp
(defun ceamx/diff-with-file (&optional arg)
  (interactive "P")
  (let ((buffer (when arg (current-buffer))))
    (diff-buffer-with-file buffer)))
#+end_src

#+begin_src emacs-lisp
;; via <https://github.com/tarsius/fwb-cmds/blob/88e823809067983acfaeafa57d0bb6e889429ad2/fwb-cmds.el#L140C1-L156C78>
;;;###autoload
(defun ceamx/sudo-find-file (&optional arg)
  "Edit the visited file as \"root\".
If the current buffer does not visit a file, the visited file is
writable or with a prefix argument, then read a file to visit."
  (interactive "P")
  (require 'tramp)
  (if (or arg
          (not buffer-file-name)
          (file-writable-p buffer-file-name))
      (let ((default-directory
             (concat "/sudo:root@localhost:" default-directory)))
        (apply #'find-file
               (find-file-read-args
                "Find file: "
                (confirm-nonexistent-file-or-buffer))))
    (find-alternate-file (concat "/sudo:root@localhost:" buffer-file-name))))
#+end_src


* Workspace
:PROPERTIES:
:header-args: :noweb-ref init-workspace
:END:

** Requirements

#+begin_src emacs-lisp
(require 'ceamx-lib)
#+end_src

#+begin_src emacs-lisp
(defvar edebug-inhibit-emacs-lisp-mode-bindings)
#+end_src

** DISABLED Beframe
:PROPERTIES:
:header-args: :noweb-ref nil
:END:

#+begin_src emacs-lisp
(defface +beframe-buffer
  '((t :inherit font-lock-string-face))
  "Face for `consult' framed buffers.")

(defun +beframe-buffer-names-sorted (&optional frame)
  "Return the list of buffers from `beframe-buffer-names' sorted by visibility.
With optional argument FRAME, return the list of buffers of FRAME."
  (declare-function beframe-buffer-names "beframe")
  (declare-function beframe-buffer-sort-visibility "beframe")
  (beframe-buffer-names frame :sort #'beframe-buffer-sort-visibility))
#+end_src

#+begin_src emacs-lisp
(package! beframe
  ;; FIXME: still listed as frame buffers
  (setopt beframe-global-buffers '("\\*scratch\\*" "\\*Messages\\*" "\\*Backtrace\\*"))

  (keymap-global-set "C-c b" beframe-prefix-map)
  (beframe-mode 1)

  (after! consult
    (declare-function consult--buffer-state "consult")
    (defvar +beframe-consult-source
      `(:name "Frame-specific buffers (current frame)"
        :narrow ?F
        :category buffer
        :face +beframe-buffer
        :history beframe-history
        :items ,#'+beframe-buffer-names-sorted
        :action ,#'switch-to-buffer
        :state ,#'consult--buffer-state))
    (add-to-list 'consult-buffer-sources '+beframe-consult-source)))
#+end_src

** Introduce an ~activities~-based workflow for workspace management

- Website :: <https://github.com/alphapapa/activities.el>

#+begin_src emacs-lisp
(require 'ceamx-lib)

(defun ceamx-after-init-define-activities-keys-h ()
  "Define keybindings for `activities' late to override `edebug'.
Intended for use as a hook on `ceamx-after-init-hook'."
  (setq edebug-inhibit-emacs-lisp-mode-bindings t)

  ;; (keymap-global-unset "C-x C-a" t)
  (keymap-global-set "C-x C-a" (cons "Activities" (define-prefix-command 'ceamx-activities-prefix)))

  ;; TODO: still shares bindings with edebug which is confusing
  (define-keymap :keymap (current-global-map)
    "C-x C-a C-n" #'activities-new
    "C-x C-a C-d" #'activities-define
    "C-x C-a C-a" #'activities-resume
    "C-x C-a C-s" #'activities-suspend
    "C-x C-a C-k" #'activities-kill
    "C-x C-a RET" #'activities-switch

    "C-x C-a b" #'activities-switch-buffer
    "C-x C-a g" #'activities-revert
    "C-x C-a l" #'activities-list))

(package! activities
  (activities-mode)

  (when tab-bar-mode
    (activities-tabs-mode))

  (add-hook 'ceamx-after-init-hook #'ceamx-after-init-define-activities-keys-h))
#+end_src

** Display header-line indication of current location in project with ~breadcrumb~

- Website :: <https://github.com/joaotavora/breadcrumb>

#+begin_src emacs-lisp
(package! breadcrumb
  (add-hook 'ceamx-after-init-hook #'breadcrumb-mode))
#+end_src


* Help

** Define the directory containing Es cheatsheets

#+begin_src emacs-lisp :noweb-ref config-feature-paths
(defconst ceamx-cheatsheets-dir
  (file-name-as-directory
   (concat ceamx-home-dir "Documents/cheatsheets"))
  "Absolute path to the directory containing user cheatsheets.")
#+end_src

** Code:

#+begin_src emacs-lisp :tangle lisp/init-help.el
;;; init-help.el --- Help -*- lexical-binding: t; -*-

;; Copyright (c) 2022-2024  Chris Montgomery <chmont@proton.me>

;; Author: Chris Montgomery <chmont@proton.me>
;; URL: https://git.sr.ht/~montchr/ceamx
;; Version: 0.1.0

;; This file is NOT part of GNU Emacs.

;; This file is free software: you can redistribute it and/or modify it
;; under the terms of the GNU General Public License as published by the
;; Free Software Foundation, either version 3 of the License, or (at
;; your option) any later version.
;;
;; This file is distributed in the hope that it will be useful, but
;; WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
;; General Public License for more details.
;;
;; You should have received a copy of the GNU General Public License
;; along with this file.  If not, see <http://www.gnu.org/licenses/>.

;;; Commentary:

;; "It looks like you're writing an Emacs.  Would you like help?"

;;; Code:

(require 'ceamx-lib)
(require 'lib-help)

;;; Configure window behavior for help buffers

;; Focus newly-opened help windows.
(setopt help-window-select t)

;; Also focus newly-opened manpages, which still do not follow `display-buffer'
;; rules (as of <2024-03-06>).
(setopt Man-notify-method 'aggressive)

;;; Bind commands to call ~consult-info~ filtered by commonly-used manual collections

(declare-function consult-info "consult-info")

;; Remove the default binding for the `describe-input-method' command.
(keymap-global-unset "C-h I" t)

(global-keys!
  "C-h i"    #'ceamx/consult-info-dwim
  "C-h I c"  #'ceamx/completion-info
  "C-h I e"  #'ceamx/emacs-info
  "C-h I i"  #'consult-info
  "C-h I o"  #'ceamx/org-info)

;;; Peruse local ~devdocs~ docsets corresponding to the current major-mode

;; <https://github.com/astoff/devdocs.el>

;; NOTE: Must run ~devdocs-install~ before a docset is available for reference.
;;
;; TODO: Install docsets automatically.
;;       See ~lib-help~ for WIP.

(package! devdocs
  (define-keymap :keymap help-map
    ;; Replace default `apropos-documentation' binding.
    "d" #'devdocs-lookup
    "D" #'apropos-documentation)

  ;; FIXME: on a stale timer! every week! not every session...
  (devdocs-update-all))

(after! popper
  (add-to-list 'popper-reference-buffers "\\*devdocs\\*"))

;;; Display keyboard macros or latest interactive commands as Elisp via ~elmacro~

;; <https://github.com/Silex/elmacro>

;; Avoid enabling this mode globally. It may cause some recurring errors, and
;; the package has not been updated in years. By nature, it is also quite
;; invasive, and should probably only be used as a development tool as needed.

(package! elmacro
  (setopt elmacro-show-last-commands-default 30)

  ;; <https://github.com/Silex/elmacro/blob/master/README.md#org-mode-smartparens-etc>
  ;; <https://github.com/Silex/elmacro/blob/master/README.md#elmacro-processor-prettify-inserts>
  (setopt elmacro-processor-prettify-inserts
          (unless (or (bound-and-true-p lispy-mode) ; not actually sure about lispy-mode
                      (bound-and-true-p smartparens-mode)
                      (bound-and-true-p org-mode))))

  ;; "a" "b" "c" => "abc"
  ;; FIXME: maybe causes errors?
  (setopt elmacro-processor-concatenate-inserts t))

;;; Provide improved alternatives to the builtin `describe-*' utilities with ~helpful~

;; <https://github.com/Wilfred/helpful>

;; NOTE: there are some blocking bugs that have gone unfixed for quite a while
;;        some symbols' helpful pages cannot be displayed.
;;        <Wilfred/helpful#329>

(package! helpful
  ;; Avoid a first-time lag when asking for help, which often happens before an
  ;; idle timer has the chance to run.
  (require 'helpful)
  (define-keymap :keymap help-map
    "c" #'helpful-callable
    "C" #'helpful-command
    "f" #'helpful-function              ; orig: `describe-face'
    "h" #'helpful-at-point
    ;; TODO: consider swapping with the original as a trial?
    "k" #'helpful-key                   ; orig: `describe-key-briefly'
    "o" #'helpful-symbol
    "v" #'helpful-variable

    ;; Parity with the corresponding unmodded keys.
    ;; Primarily for Meow keypad, but also sometimes feels more natural to keep
    ;; holding Ctrl anyway.
    "C-k" #'helpful-key
    "C-o" #'helpful-symbol

    ;; Rebind the originals
    "F" #'describe-face
    "K" #'describe-key-briefly

    ;; Unbind the default binding for "C-h C-h" to allow `which-key' paging.
    "C-h" nil))

;;; Eldoc: Display multiple composed messages

(setopt eldoc-documentation-function #'eldoc-documentation-compose)

;;; Tune the contexts in which Eldoc displays its messages

(use-feature! eldoc
  :config
  ;; via <https://github.com/radian-software/radian/blob/20c0c9d929a57836754559b470ba4c3c20f4212a/emacs/radian.el#L2800-L2810>
  (def-advice! +eldoc-better-display-message-p-a (&rest _)
    :override #'eldoc--message-command-p
    "Make ElDoc smarter about when to display its messages.
From the original author:

\"By default ElDoc has a customizable whitelist of commands that
it will display its messages after. The idea of this is to not
trample on messages that other commands may have printed.
However, this is a hopeless endeavour because there are a
virtually unlimited number of commands that don't conflict with
ElDoc. A better approach is to simply check to see if a message
was printed, and only have ElDoc display if one wasn't.\""
    (member (current-message) (list nil eldoc-last-message))))

;;; Display usage examples for Elisp callables inside their help buffers

;; <https://github.com/xuchunyang/elisp-demos>

(package! elisp-demos
  (after! helpful
    (require 'elisp-demos)
    (setopt elisp-demos-user-files (list (expand-file-name  "docs/elisp-demos.org" user-emacs-directory)))
    (advice-add 'helpful-update :after #'elisp-demos-advice-helpful-update)))

;;; DISABLED Improve display of hints in an active ~repeat-mode~ keymap

;; <https://github.com/karthink/repeat-help>

;; Essential reading for context and usage ideas:
;; <https://karthinks.com/software/it-bears-repeating/>

;; DISABLED:
;; - Hides default help, which I generally do want to see.
;; - No straightforward way to disable or customize per-map

(package! repeat-help
  (setopt repeat-help-auto nil)
  (setopt repeat-help-key (kbd "?"))

  ;; If `repeat-help' detects `which-key' because `embark' has not yet loaded,
  ;; then its default popup type will be `which-key', not `embark'. This does
  ;; not quite line up with the intended behavior as stated in the `repeat-help'
  ;; README.
  ;; Note, also, that Embark will not be loaded until one of its autoloaded
  ;; commands are invoked.
  ;; <karthink/repeat-help#10>
  (with-eval-after-load 'embark
    (setopt repeat-help-popup-type 'embark))

  ;; (require 'repeat-help)
  ;; (add-hook 'repeat-mode-hook #'repeat-help-mode)
  )

;;; Provide "Casual" transient menus for complex modes

;; <https://github.com/kickingvegas/cc-isearch-menu/blob/main/README.org>
(package! cc-isearch-menu
  (require 'cc-isearch-menu)

  (after! isearch
    (keymap-set isearch-mode-map "<f2>" #'cc-isearch-menu-transient)))

;; <https://github.com/kickingvegas/casual-dired>
(package! casual-dired
  (after! dired
    (keymap-set dired-mode-map "C-o" #'casual-dired-tmenu)))

;; <https://github.com/kickingvegas/casual-info>
(package! casual-info
  (after! info
    (keymap-set Info-mode-map "C-o" #'casual-info-tmenu)))

;;; `Info-mode' enchantments

(add-hook 'Info-mode-hook #'hl-line-mode)
(add-hook 'Info-mode-hook #'scroll-lock-mode)

;;; Keybindings

(define-keymap :keymap help-map
  "l" #'find-library

  ;; FIXME: no lambda binding
  ;; "t" `("text-props (pt)" . ,(cmd!!
  ;;                              #'describe-text-properties
  ;;                              current-prefix-arg
  ;;                              (point)))

  ;; Unbind the default binding for "C-h C-h" to allow `which-key' paging.
  "C-h" nil)

(provide 'init-help)
;;; init-help.el ends here
#+end_src


** Library

#+begin_src emacs-lisp :tangle lisp/lib-help.el
;;; lib-help.el --- Helpers for help and documentation  -*- lexical-binding: t; -*-

;; Copyright (C) 2024  Chris Montgomery

;; Author: Chris Montgomery <chmont@proton.me>
;; Keywords: local, help

;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.

;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.

;; You should have received a copy of the GNU General Public License
;; along with this program.  If not, see <https://www.gnu.org/licenses/>.

;;; Commentary:

;; We all need help sometimes.

;;; Code:

(require 'ceamx-lib)

(defvar devdocs-data-dir)

(declare-function consult-info "consult")

;;; Functions

;;;; Pre-defined filters for ~consult-info~ searches

;; via <https://github.com/minad/consult?tab=readme-ov-file#help>
(defun ceamx/emacs-info ()
  "Search through Emacs info pages."
  (interactive)
  (consult-info "emacs" "efaq" "elisp" "cl"))

(defun ceamx/org-info ()
  "Search through the Org info page."
  (interactive)
  (consult-info "org"))

(defun ceamx/completion-info ()
  "Search through completion info pages."
  (interactive)
  (consult-info "vertico" "consult" "marginalia" "orderless" "embark"
                "corfu" "cape" "tempel"))

(defun ceamx/consult-info-dwim (&optional buffer)
  "Search Info manuals appropriate to BUFFER's major-mode."
  (interactive)
  (with-current-buffer (or buffer (current-buffer))
    (let* ((mode major-mode)
           (fn (pcase mode
                 ((pred (lambda (x) (memq x '(emacs-lisp-mode))))
                  #'ceamx/emacs-info)
                 ((pred (lambda (x) (memq x '(org-mode org-agenda-mode))))
                  #'ceamx/org-info)
                 (_ #'consult-info))))
      (command-execute fn))))

;;;; ~devdocs~ support

(defun +devdocs--doc-directory-exists-p (slug)
  "Whether the directory for the doc SLUG exists."
  (file-directory-p (expand-file-name slug devdocs-data-dir)))

(defun +devdocs--doc-installed-p (slug)
  "Whether the document named SLUG is installed.
Installation can be defined as whether there exists a metadata
file inside a directory named SLUG within `devdocs-data-dir'."
  (defvar devdocs-data-dir)
  (let ((file (expand-file-name (concat slug "/metadata") devdocs-data-dir)))
    (file-exists-p file)))

(defun +devdocs-maybe-install (doc)
  "Install the `devdocs' documentation set for DOC if not already installed.
DOC is as in `devdocs-install'."
  (declare-function devdocs-install "devdocs")
  (unless (+devdocs--doc-installed-p doc)
    (devdocs-install doc)))

(defun +devdocs-maybe-install-docs (docs)
  "Install each `devdocs' documentation set in DOCS if not already installed.
DOCS is a quoted list of `devdocs' documentation identifiers as
accepted by `+devdocs-maybe-install'."
  (dolist (doc docs)
    (+devdocs-maybe-install doc)))

;; FIXME: return t if exists, whatever if new, otherwise throw
(defun ceamx/devdocs-maybe-install (doc)
  "Install the `devdocs' documentation set for DOC if not already installed.
DOC is as in `devdocs-install'."
  ;; TODO: prompt for selecting from available docs (see `devdocs-install')
  (interactive "s")
  (+devdocs-maybe-install doc))

(provide 'lib-help)
;;; lib-help.el ends here
#+end_src


* History

#+begin_src emacs-lisp :tangle lisp/init-history.el
;;; init-history.el --- History management           -*- lexical-binding: t; -*-

;; Copyright (C) 2023-2024  Chris Montgomery

;; Author: Chris Montgomery <chmont@proton.me>
;; Keywords: local, lisp

;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.

;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.

;; You should have received a copy of the GNU General Public License
;; along with this program.  If not, see <https://www.gnu.org/licenses/>.

;;; Commentary:

;; " Nothing here now but the recordings... "

;; Configuration for session history like undo/redo, edits, kill-ring,
;; recent files, and so on.

;;; Code:

;;; Requirements

(require 'cl-lib)

(require 'ceamx-paths)
(require 'ceamx-lib)

;;; Record some variables' values with ~savehist~ [builtin]

(use-feature! savehist
  :init
  (savehist-mode)

  :config
  (cl-dolist (save '(kill-ring
                      regexp-search-ring
                      search-ring))
    (cl-pushnew save savehist-additional-variables))

  (setopt savehist-autosave-interval 60))

;;; Record point position in buffers with ~saveplace~ [builtin]

(use-feature! saveplace
  :init
  (save-place-mode))

;;; Record recently-accessed files with ~recentf~ [builtin]

(use-feature! recentf
  :init
  (recentf-mode)

  :config
  (setopt recentf-max-saved-items 50)   ; default => 20
  (setopt recentf-max-menu-items 15)    ; default => 10

  ;; Disable recentf-cleanup on Emacs start, because it can cause
  ;; problems with remote files.
  (setopt recentf-auto-cleanup 'never)

  ;; Exclude internal plumbing files.
  (dolist (path '(ceamx-etc-dir ceamx-var-dir))
    (add-to-list 'recentf-exclude path)))

;;; Return to previously-visited buffer positions with ~dogears~

;; <https://github.com/alphapapa/dogears.el>

(package! dogears
  (add-hook 'on-first-buffer-hook #'dogears-mode)

  ;; Also see `ceamx/dogears-dispatch'.
  (global-keys!
    ;; TODO: find a new binding maybe
    ;; "M-g d" #'dogears-go
    "M-g M-b" #'dogears-back
    "M-g M-f" #'dogears-forward
    "M-g M-d" #'dogears-list
    "M-g M-D" #'dogears-sidebar)

  ;; Persist `dogears-list' between Emacs sessions.
  ;; via <alphapapa/dogears.el#4>
  (after! savehist
    (when (boundp 'savehist-additional-variables)
      (add-to-list 'savehist-additional-variables #'dogears-list))))

;; TODO: provide a little more context in transient (label for dogears, links maybe...)
(after! (transient dogears)
  (transient-define-prefix ceamx/dogears-dispatch ()
    "Transient menu for `dogears' history navigation commands."
    [["Navigate"
       ("b" "back" dogears-back :transient transient--do-stay)
       ("f" "forward" dogears-forward :transient transient--do-stay)]
      ;; TODO: when quit one of these Find commands, return to transient
      ["Find"
        ("d" "go..." dogears-go)
        ("l" "list" dogears-list)
        ("S" "sidebar" dogears-sidebar)]])

  (defer-until! (fboundp 'ceamx/dogears-dispatch)
    (keymap-global-set "M-g d" #'ceamx/dogears-dispatch)))

;;; Undo/redo

;;;; Increase undo history limits

;; Advice from the author of ~undo-fu~:
;;
;; > The default undo limits for emacs are quite low _(0.15mb at time of
;; > writing)_ undo-tree for example increases these limits.
;; >
;; > On modern systems you may wish to use much higher limits.
;; >
;; > This example sets the limit to 64mb, 1.5x (96mb) for the strong
;; > limit and 10x (960mb) for the outer limit. Emacs uses 100x for the
;; > outer limit but this may be too high when using increased limits.
;;
;; via <https://codeberg.org/ideasman42/emacs-undo-fu#undo-limits>

(setopt undo-limit 67108864) ; 64mb.
(setopt undo-strong-limit 100663296) ; 96mb.
(setopt undo-outer-limit 1006632960) ; 960mb.

;;;; Support optional linear undo/redo with ~undo-fu~

;; <https://codeberg.org/ideasman42/emacs-undo-fu>

(package! undo-fu
  (keymap-global-set "C-z" #'undo-fu-only-undo)
  (keymap-global-set "C-S-z" #'undo-fu-only-redo))

;;;; Record undo/redo steps across Emacs sessions with ~undo-fu-session~

;; <https://codeberg.org/ideasman42/emacs-undo-fu-session>

;; NOTE: This is *NOT* just for use with ~undo-fu~! It's an essential
;; enhancement to the builtin Emacs undo system as well.

(defvar undo-fu-session-directory
  (expand-file-name "undo-fu-session" ceamx-var-dir))

(package! undo-fu-session
  (setopt undo-fu-session-incompatible-files '("/COMMIT_EDITMSG\\'" "/git-rebase-todo\\'"))
  (setopt undo-fu-session-ignore-temp-files t)
  (setopt undo-fu-session-ignore-encrypted-files t)

  (setopt undo-fu-session-compression 'zst)

  (undo-fu-session-global-mode))

;;;; Visualize the Emacs undo tree with ~vundo~ (visual undo)

;; <https://github.com/casouri/vundo>

(package! vundo
  (keymap-global-set "C-x u" #'vundo))

(after! vundo
  (defvar vundo-unicode-symbols)
  (setopt vundo-glyph-alist vundo-unicode-symbols))

(provide 'init-history)
;;; init-history.el ends here
#+end_src


* Search
:PROPERTIES:
:header-args: :noweb-ref init-search
:END:

Configuration for search-related utilities like ~isearch~ and
~query-replace~.

See also [[*CONSULTing completing-read]], as ~consult~ often provides a frontend to
these utilities.

- Source :: <https://github.com/karthink/.emacs.d/blob/6aa2e034ce641af60c317697de786bedc2f43a71/lisp/setup-isearch.el>

** Requirements

  #+begin_src emacs-lisp
(require 'ceamx-keymaps)

(require 'ceamx-lib)
(require 'lib-search)
#+end_src

** Finding a library means finding a library

#+begin_src emacs-lisp
(setopt find-library-include-other-files nil)
#+end_src

** ~isearch~ [builtin]

#+begin_src emacs-lisp :noweb-ref lib-search
(defun ceamx/replace-symbol-at-point ()
  "Run `query-replace-regexp' for the symbol at point."
  (interactive)
  (require 'isearch)
  (isearch-forward-symbol-at-point)
  (isearch-query-replace-regexp))
#+end_src

#+begin_src emacs-lisp
(setopt search-highlight t)
(setopt isearch-lazy-highlight t)
(setopt isearch-lazy-count t)
(setopt lazy-count-prefix-format "[%s/%s] ")
(setopt lazy-count-suffix-format nil)
(setopt isearch-allow-scroll 'unlimited)

;; Allow extending search string by holding shift and using motion commands.
(setopt isearch-yank-on-move 'shift)

;; TODO: monitor behavior
;;       specifically, it looks like that regexp will consider any
;;       non-alphanumeric character to be whitespace, which might be a bit much.
;; via <https://github.com/karthink/.emacs.d/blob/6aa2e034ce641af60c317697de786bedc2f43a71/lisp/setup-isearch.el>
(setopt search-whitespace-regexp ".*?")
(setopt isearch-lax-whitespace t)
(setopt isearch-regexp-lax-whitespace nil)

(after! isearch
  (blackout 'isearch)

  (defvar-keymap isearch-repeat-map
    :repeat t
    "s" #'isearch-repeat-forward
    "r" #'isearch-repeat-backward)

  (define-keymap :keymap (current-global-map)
    "M-s M-o" #'multi-occur
    "M-s %" #'ceamx/replace-symbol-at-point)

  (define-keymap :keymap isearch-mode-map
    "M-<" #'isearch-beginning-of-buffer
    "M->" #'isearch-end-of-buffer
    "M-/" #'isearch-complete
    "M-w" #'isearch-yank-word-or-char

    "M-s <" #'isearch-beginning-of-buffer
    "M-s >" #'isearch-end-of-buffer

    "C-w" nil
    "M-e" nil)

  (keymap-set minibuffer-local-isearch-map "M-/" #'isearch-complete-edit))
#+end_src

** ~substitute~: Efficiently replace targets in the buffer or context

<https://protesilaos.com/emacs/substitute>

#+begin_src emacs-lisp
(package! substitute
  (define-keymap :keymap ceamx-replace-map
    "b" #'substitute-target-in-buffer
    "d" #'substitute-target-in-defun
    "r" #'substitute-target-above-point
    "s" #'substitute-target-below-point)

  (setopt substitute-hightlight t))

(after! substitute
  ;; Provide messages reporting on matches changed in the context.
  (add-hook 'substitute-post-replace-functions #'substitute-report-operation))
#+end_src

** ~wgrep~: writable grep buffers

"Writable grep buffer and apply the changes to files"

<https://github.com/mhayashi1120/Emacs-wgrep>

#+begin_src emacs-lisp
(package! wgrep
  (setopt wgrep-auto-save-buffer t)
  (setopt wgrep-change-readonly-file t))
#+end_src


#+begin_src emacs-lisp :noweb-ref lib-search
;; via <https://github.com/doomemacs/doomemacs/blob/e96624926d724aff98e862221422cd7124a99c19/modules/completion/vertico/autoload/vertico.el#L91-L108>
;;;###autoload
(defun +vertico/embark-export-write ()
  "Export the current vertico results to a writable buffer if possible.
Supports exporting consult-grep to wgrep, file to wdeired,
and consult-location to occur-edit."
  (interactive)
  (require 'embark)
  (require 'wgrep)
  (let* ((edit-command
          (pcase-let ((`(,type . ,candidates)
                       (run-hook-with-args-until-success 'embark-candidate-collectors)))
            (pcase type
              ('consult-grep #'wgrep-change-to-wgrep-mode)
              ('file #'wdired-change-to-wdired-mode)
              ('consult-location #'occur-edit-mode)
              (x (user-error "Embark category %S doesn't support writable export" x)))))
         (embark-after-export-hook `(,@embark-after-export-hook ,edit-command)))
    (embark-export)))
#+end_src

**** Keybindings
** ~re-builder~ [builtin]: the regular expression builder

<https://www.masteringemacs.org/article/re-builder-interactive-regexp-builder>
<https://francismurillo.github.io/2017-03-30-Exploring-Emacs-rx-Macro/>

Unfortunately, ~re-builder~ itself is poorly-documented.

#+begin_src emacs-lisp
;; "string" => recommended: \\(foo\\\|bar\\)
;; "rx"     => recommended; advanced sexp regexp engine
;; "read"   => default, avoid: backslash hell
(setopt reb-re-syntax 'string)
#+end_src

** Keybindings

#+begin_src emacs-lisp
(keymap-set search-map "r" '("replace..." . ceamx-replace-map))

(keymap-set minibuffer-local-map "C-c C-e" #'+vertico/embark-export-write)

(after! dired
  (keymap-set dired-mode-map "C-c C-e" #'wgrep-change-to-wgrep-mode))

(after! grep
  (keymap-set grep-mode-map "W" #'wgrep-change-to-wgrep-mode))

;; FIXME: wrong num args
;; the intention is to close the wgrep popup after abort/finish
;; (after! popper
;;   (advice-add #'wgrep-abort-changes :after #'popper-toggle)
;;   (advice-add #'wgrep-finish-edit :after #'popper-toggle))
#+end_src


* Input Methods

#+begin_src emacs-lisp :tangle lisp/init-input-methods.el
;;; init-input-methods.el --- Configuration for input methods  -*- lexical-binding: t; -*-

;; Copyright (C) 2024  Chris Montgomery

;; Author: Chris Montgomery <chmont@proton.me>
;; Keywords: local

;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.

;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.

;; You should have received a copy of the GNU General Public License
;; along with this program.  If not, see <https://www.gnu.org/licenses/>.

;;; Commentary:

;;

;;; Code:

;;
;;; Language environment

(set-language-environment "UTF-8")

;; `set-language-environment' also presumptively sets `default-input-method'.
(setopt default-input-method nil)

;; Disable bidirectional text scanning, because I don't need it.
(setq-default bidi-display-reordering 'left-to-right)
(setq-default bidi-paragraph-direction 'left-to-right)
(setq bidi-inhibit-bpa t)

;;
;;; Mouse input

(setopt mouse-yank-at-point t)

;; Avoid collision of mouse with point.
(mouse-avoidance-mode 'exile)

;; "More performant rapid scrolling over unfontified regions. May cause brief
;; spells of inaccurate fontification immediately after scrolling."
;;(setopt fast-but-imprecise-scrolling nil)

(setopt use-file-dialog nil)
(setopt use-dialog-box nil)

(provide 'init-input-methods)
;;; init-input-methods.el ends here
#+end_src


* Keys

** General

#+begin_src emacs-lisp :tangle lisp/init-keys.el
;;; init-keys.el --- Keybindings -*- lexical-binding: t -*-

;; Copyright (c) 2022-2024  Chris Montgomery <chmont@proton.me>

;; Author: Chris Montgomery <chmont@proton.me>
;; URL: https://git.sr.ht/~montchr/ceamx
;; Version: 0.1.0

;; This file is NOT part of GNU Emacs.

;; This file is free software: you can redistribute it and/or modify it
;; under the terms of the GNU General Public License as published by the
;; Free Software Foundation, either version 3 of the License, or (at
;; your option) any later version.
;;
;; This file is distributed in the hope that it will be useful, but
;; WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
;; General Public License for more details.
;;
;; You should have received a copy of the GNU General Public License
;; along with this file.  If not, see <http://www.gnu.org/licenses/>.

;;; Commentary:

;;  Keybindings configuration.

;; TODO: <https://github.com/jwiegley/dot-emacs/blob/master/init.org#smart-newline>
;; TODO: <https://github.com/ainame/smart-newline.el/tree/c50ab035839b307c66d439083b6761cb7db5e972>

;;; Code:

;;; Requirements

(require 'config-env)
(require 'ceamx-lib)

;;; Bind some CUA-like hotkeys for editing operations

;; "C-S" prefix is inspired by the use of this mod combo in terminal emulators,
;; where "C-c" for example would kill the current process.

;; FIXME: move to bindings file

(define-keymap :keymap (current-global-map)
  "C-S-c" #'kill-ring-save
  "C-S-v" #'yank
  "C-S-x" #'kill-region)

;;; macOS: Remap modifier keys for the Apple keyboard layout

(when (and +sys-mac-p (display-graphic-p))
  (setopt mac-control-modifier 'control)
  (setopt mac-option-modifier 'meta)
  (setopt ns-option-modifier 'meta)
  (setopt mac-command-modifier 'super)
  (setopt ns-command-modifier 'super)
  ;; Free up the right-side option key for character composition.
  (setopt mac-right-option-modifier 'none)
  (setopt ns-right-option-modifier 'none)
  ;; Common system hotkeys.
  (define-keymap :keymap (current-global-map)
    "s-c" #'kill-ring-save
    "s-v" #'yank
    "s-x" #'kill-region
    "s-q" #'save-buffers-kill-emacs))

;;; Enable and configure ~repeat-mode~

;; Allow any key sequence to exit `repeat-mode'.
(setopt repeat-exit-key nil)

(setopt repeat-exit-timeout 15)
(setopt repeat-on-final-keystroke t)
(setopt repeat-keep-prefix nil)

;; Related, but not technically part of `repeat-mode'.
(setopt set-mark-command-repeat-pop t)

;; Avoid running mode-hooks too early.
(add-hook 'ceamx-after-init-hook #'repeat-mode)

;;; Show free keybindings for modkeys or prefixes with ~free-keys~

;; <https://github.com/Fuco1/free-keys>

;; > If called with prefix argument C-u, you can specify a prefix map to be
;; > used, such as C-c or C-c C-x (these are specified as a string).

(package! free-keys)

(provide 'init-keys)
;;; init-keys.el ends here
#+end_src


*** DISABLED Library

Diabetic experiments with syntactic sugar.

#+begin_src emacs-lisp :tangle no

;; Helper functions and macros for keybindings.


;; TODO: add utility to apply prefix to provided keys and bind in maps,
;; deferring until maps are defined if necessary. see
;; <https://github.com/casouri/lunarymacs/blob/master/site-lisp/luna-key.el>

(require 'cl-lib)

;; NOTE: all in-use definitions have been moved to ~ceamx-lib~

;; FIXME: interface still doesn't feel right...
;; TODO: account for remapping (don't prefix)
;; (defun ceamx--key-with-prefix (prefix key &optional sep)
;;   "Return a key sequence string with PREFIX prepended to KEY.
;; The arguments PREFIX and KEY must be strings satisfying `key-valid-p'.

;; The optional argument SEP may specify a separator between PREFIX
;; and KEY. If nil, a single whitespace character will be used as
;; the default separator."
;;   (cl-assert (key-valid-p prefix))
;;   (cl-assert (key-valid-p key))
;;   (let* ((sep (or sep " "))
;;           (prefixed-key (concat prefix sep key)))
;;     (cl-assert (key-valid-p prefixed-key))
;;     prefixed-key))

;; FIXME: while logging something simple works, this does not yet return
;; anything useful -- it should return a modified version of DEFS
;; TODO: rework since this doesn't need to be about keydefs
;; (defun ceamx-map-keydefs (func &rest defs)
;;   "Apply the function FUNC to key/definition pairs DEFS.

;; FUNC should accept args KEY and DEF. KEY should be a string
;; matching ~key-valid-p~. DEF is anything that can be a key
;; definition. See the docstring for ~keymap-set~ for more info on
;; accepted values for DEF.

;; This function does not validate the validity of each KEY/DEF pair
;; in DEFS. However, this function will throw when there is an
;; uneven number of items in DEFS, or when there are duplicate
;; definitions for KEY.

;; This function is mostly copied from the source of
;; `define-keymap'."
;;   (let (seen-keys)
;;     (while defs
;;       (let ((key (pop defs)))
;;         (unless defs
;;           (error "Uneven number of key/definition pairs"))
;;         (let ((def (pop defs)))
;;           (if (member key seen-keys)
;;               (error "Duplicate definition for key: %S" key)
;;             (push key seen-keys))
;;           (funcall func key def))))))

;; (defmacro with-prefix! (prefix &rest defs)
;;   "Prepend string PREFIX to each key in key/definition pairs DEFS.
;; PREFIX must satisfy `key-valid-p'. DEFS is composed of keybinding
;;  pairs of the form accepted by `define-keymap'.

;; When PREFIX is prepended to each key string, a single space will
;;  be added between PREFIX and the value for the key. If PREFIX
;;  already includes trailing whitespace, that whitespace will be
;;  stripped and replaced by a single space unless the trailing
;;  whitespace is already a single space character."
;;   (declare (indent defun))
;;   (let* ((prefix (string-clean-whitespace prefix))
;;          ;; FIXME: how to return modified KEY and DEF? or find another way...
;;          (fn (lambda (key def) (format "%s %s" prefix key))))))
#+end_src


** =meow= modal keybindings

*** Target Files
:PROPERTIES:
:VISIBILITY: folded
:END:

#+name: init-keys-meow-file
#+begin_src emacs-lisp  :tangle lisp/init-keys-meow.el
<<file-prop-line(feature="init-keys-meow",desc="Meow modal keybindings support")>>

;; Copyright (c) 2023-2024  Chris Montgomery <chmont@proton.me>

;; Author: Chris Montgomery <chmont@proton.me>
;; URL: https://git.sr.ht/~montchr/ceamx
;; Version: 0.1.0

<<file-license>>

;;; Commentary:
;;; Code:

<<init-keys-meow>>

<<file-footer(feature="init-keys-meow")>>
#+end_src

#+name: lib-keys-meow-file
#+begin_src emacs-lisp  :tangle lisp/lib-keys-meow.el
<<file-prop-line(feature="lib-keys-meow",desc="Meow helpers")>>

;; Copyright (c) 2023-2024  Chris Montgomery <chmont@proton.me>

;; Author: Chris Montgomery <chmont@proton.me>
;; URL: https://git.sr.ht/~montchr/ceamx
;; Version: 0.1.0

<<file-license>>

;;; Commentary:
;;; Code:

<<lib-keys-meow>>

<<file-footer(feature="lib-keys-meow")>>
#+end_src


*** Code: =init-keys-meow.el=
:PROPERTIES:
:header-args: :noweb-ref init-keys-meow
:END:

#+begin_src emacs-lisp
(require 'ceamx-lib)
(require 'lib-keys-meow)
#+end_src

#+begin_src emacs-lisp
(package! meow)
#+end_src

**** Load meow after Which-Key to circumvent Meow-Keypad UI issues

Meow's leader functionality has a custom keybinding visualization helper that
looks almost exactly like ~which-key~ except that it is very buggy. It does try
to smooth over some of the guts of Meow, as ~which-key~ is cluttered with
keybinding entries for ~meow-digit-argument~. But even Meow's maintainers do not
recommend using the custom keypad helper UI.

Unfortunately, AFAIK there is no way to disable the UI feature explicitly
without disabling the Keypad entirely. The only way to prevent this feature from
causing workflow issues is by installing ~which-key~ and letting Meow defer to
~which-key~ for this kind of interface.

And so, we load Meow after Which-Key:

#+begin_src emacs-lisp
(with-eval-after-load 'which-key
  (require 'meow))
#+end_src

And allow Elpaca to finish processing its queues, since everything afterwards depends on Meow.

#+begin_src emacs-lisp
(elpaca-wait)
#+end_src

**** Set up the basic Meow state keybindings

***** Add initial keybindings to the Meow Leader keymap

#+begin_src emacs-lisp
(meow-leader-define-key
 ;; SPC j/k will run the original command in MOTION state.
 '("j" . "H-j")
 '("k" . "H-k")

 ;; Use SPC (0-9) for digit arguments.
 '("1" . meow-digit-argument)
 '("2" . meow-digit-argument)
 '("3" . meow-digit-argument)
 '("4" . meow-digit-argument)
 '("5" . meow-digit-argument)
 '("6" . meow-digit-argument)
 '("7" . meow-digit-argument)
 '("8" . meow-digit-argument)
 '("9" . meow-digit-argument)
 '("0" . meow-digit-argument)
 '("/" . meow-keypad-describe-key)
 '("?" . meow-cheatsheet))

(meow-motion-overwrite-define-key
 '("j" . meow-next)
 '("k" . meow-prev)
 '("<escape>" . ignore))
#+end_src

***** Add initial keybindings to the INSERT state keymap

I am not sure what I was doing here:

#+begin_src emacs-lisp :tangle no
(noop!
  ;; TODO: instead, because meow key definer syntax sucks:
  ;;
  ;; see `meow-keymap-alist' for available states (or use the lookup logic
  ;; from `meow-define-keys': (alist-get state meow-keymap-alist)
  (define-keymap :keymap meow-insert-state-keymap
    ;; etc.
    "0" #'meow-expand-0))
#+end_src

***** Add initial keybindings to the NORMAL state keymap

#+begin_src emacs-lisp
(meow-normal-define-key
 '("0" . meow-expand-0)
 '("9" . meow-expand-9)
 '("8" . meow-expand-8)
 '("7" . meow-expand-7)
 '("6" . meow-expand-6)
 '("5" . meow-expand-5)
 '("4" . meow-expand-4)
 '("3" . meow-expand-3)
 '("2" . meow-expand-2)
 '("1" . meow-expand-1)
 '("-" . negative-argument)
 '(";" . meow-reverse)
 '("," . meow-inner-of-thing)
 '("." . meow-bounds-of-thing)
 '("[" . meow-beginning-of-thing)
 '("]" . meow-end-of-thing)
 '("a" . meow-append)
 '("A" . meow-open-below)
 '("b" . meow-back-word)
 '("B" . meow-back-symbol)
 '("c" . meow-change-save)            ; default: `meow-change'
 '("d" . meow-delete)
 '("D" . meow-backward-delete)
 '("e" . meow-next-word)
 '("E" . meow-next-symbol)
 '("f" . meow-find)
 '("g" . meow-cancel-selection)
 '("G" . meow-grab)
 '("h" . meow-left)
 '("H" . meow-left-expand)
 '("i" . meow-insert)
 '("I" . meow-open-above)
 '("j" . meow-next)
 '("J" . meow-next-expand)
 '("k" . meow-prev)
 '("K" . meow-prev-expand)
 '("l" . meow-right)
 '("L" . meow-right-expand)
 '("m" . meow-join)
 '("n" . meow-search)
 '("o" . meow-block)
 '("O" . meow-to-block)
 '("p" . meow-yank)
 '("q" . meow-quit)
 ;; FIXME: duplicated with "X" binding
 '("Q" . meow-goto-line)
 '("r" . meow-replace)
 '("R" . meow-swap-grab)
 '("s" . meow-kill)
 '("t" . meow-till)
 '("T" . meow-till-expand)            ; custom addition
 '("u" . meow-undo)
 '("U" . meow-undo-in-selection)
 '("v" . meow-visit)
 '("w" . meow-mark-word)
 '("W" . meow-mark-symbol)
 '("x" . meow-line)
 ;; FIXME: duplicated with "Q" binding
 '("X" . meow-goto-line)
 '("y" . meow-save)
 '("Y" . meow-sync-grab)
 ;; There's no documentation, but this will essentially return to the
 ;; original position prior to beginning the selection.
 '("z" . meow-pop-selection)
 ;; TODO: no idea what the difference is at a glance, no docs
 ;; '("Z" . meow-pop-all-selection)    ; custom addition
 '("'" . repeat)
 '("<escape>" . ignore)
 '(":" . avy-goto-char-2))
#+end_src

***** Tell Meow about our keyboard layout to arrange its cheatsheet

#+begin_src emacs-lisp
;; NOTE: This is not a customizable variable, although it is required for meow.
(setq meow-cheatsheet-layout meow-cheatsheet-layout-qwerty)
#+end_src

**** Configure the Keypad

#+begin_src emacs-lisp
;; Don't pass through keys that aren't in keypad.
(setopt meow-keypad-self-insert-undefined nil)
#+end_src

***** Avoid the default binding for ~meow-keypad~ in motion state

The goal here is to preserve the established SPC/DEL keybindings to scroll in Info mode and some other similar modes.

#+begin_src emacs-lisp
(keymap-unset meow-motion-state-keymap "SPC" t)
#+end_src

***** Determine the dispatch target for the leader keymap with ~meow-keypad-leader-dispatch~

I will maintain sections on both options, tangling only one at a time.

I have not yet found a configuration with which I am completely comfortable -- I
tend to switch between the two. Part of the reason for that is that I have not
kept a record of my assessment of the benefits and drawbacks of the different
values I have tried. So, here I will do just that.

****** ACTIVE Leader keymap from ~meow-keymap-alist~ (default)

When ~meow-keypad-leader-dispatch~ is nil, the leader will dispatch to the
leader keymap in ~meow-keymap-alist~.

This is the default behavior.

| Pros                                               | Cons                                              |
|----------------------------------------------------+---------------------------------------------------|
| Filters out noise                                  | Requires additional keystroke =c= to get to =C-c= |
| With =c= press, Ctrl mod is primed (i.e. =C-c C-=) |                                                   |

#+begin_src emacs-lisp
(setopt meow-keypad-leader-dispatch nil)
#+end_src

****** Leader dispatch to ~mode-specific-map~ ("C-c")

| Pros                                | Cons                                                                             |
|-------------------------------------+----------------------------------------------------------------------------------|
| Direct access, no need to press =c= | Noisy, especially in Org-Mode                                                    |
| Direct access to Org-Mode keys      | Next keypress is unmodded, e.g. in Org: =SPC t= is "C-c t"                       |
|                                     | *To access main Org bindings under =C-c C-<CHAR>=, you still need to press =c=!* |
|                                     |                                                                                  |

=C-c= might be better for leader-centric bindings a la Doom/Spacemacs.

#+begin_src emacs-lisp :tangle no
(setopt meow-keypad-leader-dispatch "C-c")
#+end_src

**** Customize the appearance of the mode-line indicator for the current Meow state

This should happen before defining any new states so that they may provide their
own values upon definition.

#+begin_src emacs-lisp
(setopt meow-replace-state-name-list
        '( (normal . "🅝")
           (beacon . "🅑")
           (insert . "🅘")
           (motion . "🅜")
           (keypad . "🅚")))
#+end_src

**** DISABLED Define a new state for navigating Org-Mode trees
:PROPERTIES:
:header-args: :tangle no
:END:

Unfortunately, this is unusable. I lose access to "M-v" and even the "ESC"
binding doesn't work.

Perhaps a Transient menu is a better option.

Credit for the idea goes to this blog post:

[[https://aatmunbaxi.netlify.app/comp/meow_state_org_speed/][A meow-state for speedy org structure navigation | Aatmun Baxi]]

I made some adjustments for the sake of maintaining consistency with the default
bindings. I would rather not mix up the motions switching between this state and
the defaults.

#+begin_src emacs-lisp
(defvar-keymap meow-org-motion-keymap

  "ESC" #'meow-normal-mode

  "i" #'meow-insert-mode
  "g" #'meow-normal-mode
  "u" #'meow-undo

  ;; Move between headings
  "p" #'org-previous-visible-heading
  "n" #'org-next-visible-heading
  ;; FIXME: find an available spot
  ;; "C-M-p" #'org-previous-block
  ;; "C-M-n" #'org-next-block

  ;; Narrowing/widening
  "N" #'org-narrow-to-subtree
  "W" #'widen

  ;; Move between headings at same-level
  "b" #'org-backward-heading-same-level
  "f" #'org-forward-heading-same-level

  ;; Drag subtrees up/down
  "K" #'org-subtree-up
  "J" #'org-subtree-down

  ;; Promote/demote subtrees
  "L" #'org-demote-subtree
  "H" #'org-promote-subtree

  ;; Search
  "v" #'consult-org-heading

  ;; Setting subtree metadata
  "l" #'org-set-property-and-value
  "t" #'org-todo
  "d" #'org-deadline
  "s" #'org-schedule
  "e" #'org-set-effort)
#+end_src

Define the state itself, using the keymap we just initialized:

#+begin_src emacs-lisp
(meow-define-state org-motion
  "Org-Mode tree navigation"
  :lighter "🅞"
  :keymap meow-org-motion-keymap)
#+end_src

And provide access to this state:

#+begin_src emacs-lisp
(keymap-set meow-normal-state-keymap "O" #'meow-org-motion-mode)
#+end_src

**** Map preferred initial Meow states for some additional major-modes

#+begin_src emacs-lisp
(pushnew! meow-mode-state-list
          ;; shells
          ;; TODO: use `ceamx-repl-modes-list'
          '(comint-mode . insert)
          '(eat-mode . insert)
          '(eshell-mode . insert)

          ;; writing
          '(diary-mode . normal)

          ;; read-only
          ;; TODO: how to lock state? i.e. dont allow switching
          ;; TODO: set for all read-only buffers?
          '(Info-mode . motion)
          '(read-only-mode . motion)
          '(help-mode . motion))
#+end_src

**** Improve Meow integration with the default Emacs kill-ring and system clipboard

[[meow-edit/meow#543 Confusing Defaults and Documentation · Issue #543 · meow-edit/meow · GitHub]]

#+begin_src emacs-lisp
(setopt meow-use-clipboard t)
#+end_src

**** Expand upon the default Meow pair-things

See the helper functions/macros.

#+begin_src emacs-lisp
(meow-pair! 'angle "a" "<" ">")

(ceamx-meow-bind-thing 'round "(")
(ceamx-meow-bind-thing 'round ")")
(ceamx-meow-bind-thing 'curly "{")
(ceamx-meow-bind-thing 'curly "}")

;; TODO: i don't really thing i want to do this, but here for reference
;; (ceamx-meow-unbind-thing "r")
#+end_src

**** Activate Meow

#+begin_src emacs-lisp
(meow-global-mode 1)
#+end_src


*** Code: =lib-keys-meow.el=
:PROPERTIES:
:header-args: :noweb-ref lib-keys-meow
:END:

**** Working with Meow "things"

#+begin_src emacs-lisp
(require 'ceamx-lib)
#+end_src

***** ~meow-pair!~

#+begin_src emacs-lisp
(defmacro meow-pair! (thing char begin end)
  "Register a new Meow THING as a pair of BEGIN and END, and map it to CHAR.
This macro simplifies `meow-thing-register' by assuming that the
INNER and BOUNDS arguments of `meow-thing-register' will be an
identical pair expression. This macro also handles the additional
and necessary step of adding the newly-registered THING to
`meow-char-thing-table' as CHAR.

THING is a symbol for registering the new thing with
`meow-thing-register'. It may be quoted or unquoted.

BEGIN is a string delimiting the beginning or opening of the pair
while END is a string delimiting the end or closing of the pair.

The THING will be added to `meow-char-thing-table' as CHAR. CHAR
may either be a character constant matching `characterp' (e.g.
`?a' or `97'), or a string which can be converted to a character
with `string-to-char'. See Info node `(elisp) Basic Char Syntax)'
and Info node `(elisp) String Conversion' for more info."
  (declare (indent defun))
  (cl-assert (char-or-string-p char) t)
  (cl-assert (stringp begin) t)
  (cl-assert (stringp end) t)
  (let* ((sym (ceamx-unquote thing))
         (char (ceamx-normalize-char char))
         (open (list begin))
         (close (list end))
         (pair `(pair ,open ,close)))
    (cl-assert (symbolp sym) t)
    `(progn
       (meow-thing-register ',sym
        ',pair
        ',pair)
       (ceamx-meow-bind-thing ',sym ,char))))
#+end_src

***** ~ceamx-meow-bind-thing~

#+begin_src emacs-lisp
(defun ceamx-meow-bind-thing (thing char)
  "Add pre-registered THING to `meow-char-thing-table' as CHAR."
  (defvar meow-char-thing-table '())
  (let ((thing (ceamx-unquote thing))
        (char (ceamx-normalize-char char)))
    (add-to-list 'meow-char-thing-table `(,char . ,thing))))
#+end_src

***** ~ceamx-meow-unbind-thing~

#+begin_src emacs-lisp
(defun ceamx-meow-unbind-thing (char)
  "Remove the character association for character CHAR.
This function will destructively modify the alist
`meow-char-thing-table' by removing the association whose key
matches CHAR.

Note that despite its name, `meow-char-thing-table' is an alist,
not a character table.

CHAR may be a string or character constant, which will be passed
as the CHAR argument to `ceamx-normalize-char'."
  (defvar meow-char-thing-table '())
  (assoc-delete-all (ceamx-normalize-char char) meow-char-thing-table #'eq))
#+end_src


** ~which-key~

#+begin_src emacs-lisp :tangle lisp/init-keys-which-key.el
;;; init-keys-which-key.el --- Support for which-key  -*- lexical-binding: t; -*-

;; Copyright (C) 2023-2024  Chris Montgomery

;; Author: Chris Montgomery <chmont@proton.me>
;; Keywords: local, help

;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.

;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.

;; You should have received a copy of the GNU General Public License
;; along with this program.  If not, see <https://www.gnu.org/licenses/>.

;;; Commentary:

;; This configuration file is intended to be loaded after every keybinding is in
;; place, in an effort to help these packages reference the final state of all
;; keybindings.

;; For example, ~which-key~ does not seem to like the way that ~meow~ handles
;; keybindings -- see the documentation for
;; ~meow-keypad-describe-keymap-function~ -- displaying only
;; ~which-key-prefix-prefix~. `general.el' behaves similarly, from what I can
;; recall. I suspect this has something to do with key translations, a subject
;; with which I am currently unfamiliar.

;; 2024-01-13 UPDATE: My previous note about ~which-key~ and ~meow~ interaction
;; makes me wonder if in fact the behavior I was describing was actually Meow's
;; broken ~which-key~-like popups. Still, I have seen the described behavior
;; from both packages.

;; I would much rather have some slightly-more-manual method of compiling these
;; "cheatsheets" for specific maps in a hydra-like UI. But I don't want to
;; entirely sacrifice the usage of builtin keymap functionality for the
;; abstractions of hydra. Perhaps there's a way to shadow the keymaps similarly
;; to what ~which-key~ does, maybe? (i'm guessing) but with more control?

;;; Code:

(use-package which-key
  :demand t
  :blackout t
  :commands (which-key-mode
              which-key-setup-side-window-right-bottom)

  :init
  ;; Activate after all other keybinding stuff (hopefully).
  (add-hook 'after-init-hook #'which-key-mode)

  :config

  ;; Determine whether keys have been rebound, considering the active keymaps.
  ;; NOTE: Does not seem to work reliably -- see Commentary section above.
  (setopt which-key-compute-remaps t)
  (setopt which-key-idle-delay 1.0)

  ;; Sort non-prefix-keys above prefix keys.
  (setopt which-key-sort-order 'which-key-prefix-then-key-order)

  (setopt which-key-sort-uppercase-first nil)

  ;; The default (0) is difficult to read.
  (setopt which-key-add-column-padding 2)

  ;; FIXME: no effect?
  (setopt which-key-show-remaining-keys t))

(provide 'init-keys-which-key)
;;; init-keys-which-key.el ends here
#+end_src


** ~config-keys~ :: variables
:PROPERTIES:
:header-args: :tangle lisp/config-keys.el
:END:

*** File Header

#+begin_src emacs-lisp
;;; config-keys.el --- Key-related variables -*- lexical-binding: t -*-

;; Copyright (C) 2023-2024  Chris Montgomery

;; Author: Chris Montgomery <chmont@proton.me>
;; Keywords: local

;; This file is not part of GNU Emacs

;; This program is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.

;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.

;; You should have received a copy of the GNU General Public License
;; along with this program.  If not, see <https://www.gnu.org/licenses/>.

;;; Commentary:

;;; Code:
#+end_src

*** Code

#+begin_src emacs-lisp
(defconst ceamx-keys-repl-toggle "C-:")
#+end_src

*** File Footer

#+begin_src emacs-lisp
(provide 'config-keys)
;;; config-keys.el ends here
#+end_src


* Text Expansion

** Abbrevs (~abbrev-mode~)
:PROPERTIES:
:header-args: :noweb-ref init-abbrevs
:END:

*** Re-locate the abbrev file to the ~user-emacs-directory~ for quick access

#+begin_src emacs-lisp
(setopt abbrev-file-name (locate-user-emacs-file "abbrev-defs"))
#+end_src

*** Load custom abbrevs from ~abbrev-file-name~

#+begin_src emacs-lisp
(when (file-exists-p abbrev-file-name)
  (quietly-read-abbrev-file))
#+end_src

*** Display suggestions for available abbrevs

#+begin_src emacs-lisp
(setopt abbrev-suggest t)
#+end_src

*** Allow abbrevs with a prefix colon, semicolon, or underscore

- Source :: <https://github.com/protesilaos/dotfiles/blob/8fc72724cd6debd12c8258bf64adf6822a0bc90c/emacs/.emacs.d/prot-emacs-modules/prot-emacs-completion.el#L215-L225>
- Background :: <https://protesilaos.com/codelog/2024-02-03-emacs-abbrev-mode/>

Adapted from Prot's original version with the following changes:

- Converted the duplicated regexp string into an ~rx~ form via the ~xr~ utility.
- Abstracted the regexp to a variable ~ceamx-abbrev-prefix-regexp~ for reuse
  across =abbrev-table= contexts.

#+begin_src emacs-lisp
(defconst ceamx-abbrev-prefix-regexp
  (rx (or bol
          (1+ (any "\t ")))
      (group-n 1
        (or (seq (any ":;_") (0+ nonl))
            (0+ nonl)))))
(after! abbrev
  (abbrev-table-put global-abbrev-table :regexp ceamx-abbrev-prefix-regexp)

  (with-eval-after-load 'text-mode
    (abbrev-table-put text-mode-abbrev-table :regexp ceamx-abbrev-prefix-regexp))

  (with-eval-after-load 'org
    (abbrev-table-put org-mode-abbrev-table :regexp ceamx-abbrev-prefix-regexp)))
#+end_src

*** Enable =abbrev-mode=

#+begin_src emacs-lisp
(dolist (hook '(text-mode-hook prog-mode-hook git-commit-mode-hook))
  (add-hook hook #'abbrev-mode))
#+end_src




* Completions and Selections :completions:
:PROPERTIES:
:header-args: :noweb-ref init-completion
:END:

** Requirements

#+begin_src emacs-lisp
(require 'ceamx-lib)
(require 'lib-completion)
#+end_src

** Global Settings

#+begin_src emacs-lisp
;; Always resize mini-windows to fit their contents.
(setopt resize-mini-windows t)

;; TAB cycle if there are only few candidates
(setopt completion-cycle-threshold 2)

;; Hide commands in M-x which do not apply to the current mode.  Corfu commands
;; are hidden, since they are not used via M-x.  This setting is useful beyond
;; Corfu.
(setopt read-extended-command-predicate #'command-completion-default-include-p)

;; Don't let `completion-at-point' interfere with indentation.
(setopt tab-always-indent 'complete)

;; `completion-at-point' is often bound to M-TAB, but that conflicts with OS behavior.
;; We also want to preserve "C-S-SPC" , the Emacs default binding for `set-mark-command'.
(keymap-global-set "C-S-SPC" #'completion-at-point)
#+end_src

*** Enable recursive minibuffers

#+begin_src emacs-lisp
(setopt enable-recursive-minibuffers t)
#+end_src

*** Hide M-x commands which do not work in the current mode

Vertico commands are hidden in normal buffers.

#+begin_src emacs-lisp
(setopt read-extended-command-predicate #'command-completion-default-include-p)
#+end_src

*** Do not allow the cursor in the minibuffer prompt

#+begin_src emacs-lisp
(setopt minibuffer-prompt-properties '( read-only t
                                        cursor-intangible t
                                        face minibuffer-prompt))

(add-hook 'minibuffer-setup-hook #'cursor-intangible-mode)
#+end_src

*** Add an indicator to the ~completing-read-multiple~ prompt :ui:

#+begin_src emacs-lisp
(defvar crm-separator)

(defun +crm-indicator (args)
  "Add prompt indicator to `completing-read-multiple' (ARGS are candidates).
We display [CRM<separator>], e.g., [CRM,] if the separator is a comma."
  (cons (format "[CRM%s] %s"
                (replace-regexp-in-string
                 "\\`\\[.*?]\\*\\|\\[.*?]\\*\\'" ""
                 crm-separator)
                (car args))
        (cdr args)))

(advice-add #'completing-read-multiple :filter-args #'+crm-indicator)
#+end_src

** VERTical Interactive COmpletion :ui:minibuffer:

+ website :: <https://github.com/minad/vertico>

#+begin_src emacs-lisp
(defvar savehist-additional-variables)
#+end_src

*** Install Vertico and configure its global settings

#+begin_src emacs-lisp
(package! vertico
  ;; (setopt vertico-scroll-margin 0)
  (setopt vertico-count 5)
  (setopt vertico-resize t)
  ;; Enable cycling for `vertico-next' and `vertico-previous'.
  (setopt vertico-cycle t)

  (vertico-mode))

;; Avy-like candidate selection
(after! vertico
  (keymap-set vertico-map "M-q" #'vertico-quick-insert)
  (keymap-set vertico-map "C-q" #'vertico-quick-exit))
#+end_src

*** Prefix current candidate with arrow

+ src :: <https://github.com/minad/vertico/wiki#prefix-current-candidate-with-arrow>

#+begin_src emacs-lisp
(after! vertico
  (advice-add #'vertico--format-candidate :around
    (lambda (orig cand prefix suffix index start)
      (setq cand (funcall orig cand prefix suffix index start))
      (concat (if (= vertico--index index)
                  (propertize "» " 'face 'vertico-current)
                "  ")
              cand))))
#+end_src

*** Set up the ~vertico-directory~ extension

#+begin_src emacs-lisp
(after! vertico
  ;; FIXME: why is this disabled?
  ;; via <https://github.com/minad/vertico/wiki#additions-for-moving-up-and-down-directories-in-find-file>
  ;; (defun ceamx/vertico-directory-delete-entry ()
  ;;   "Delete directory or entire entry before point."
  ;;   (interactive)
  ;;   (when (and (> (point) (minibuffer-prompt-end))
  ;;              ;; Check vertico--base for stepwise file path completion
  ;;              (not (equal vertico--base ""))
  ;;              (eq 'file (vertico--metadata-get 'category)))
  ;;     (save-excursion
  ;;       (goto-char (1- (point)))
  ;;       (when (search-backward "/" (minibuffer-prompt-end) t)
  ;;         (delete-region (1+ (point)) (point-max))
  ;;         t))))

  (define-keymap :keymap vertico-map
    "RET"     #'vertico-directory-enter
    "DEL"     #'vertico-directory-delete-char
    "M-DEL"   #'vertico-directory-delete-word)

  ;; Tidy shadowed file names -- e.g. cleans `~/foo/bar///' to `/', and `~/foo/bar/~/' to `~/'.
  (add-hook 'rfn-eshadow-update-overlay-hook #'vertico-directory-tidy))
#+end_src

*** Set up the ~vertico-repeat~ extension for recalling session history

#+begin_src emacs-lisp
(after! (vertico savehist)
  (add-hook 'minibuffer-setup-hook #'vertico-repeat-save)
  (add-to-list 'savehist-additional-variables #'vertico-repeat-history))
#+end_src

*** Fine-tune Vertico appearance per-command or per-category

#+begin_src emacs-lisp
;;   M-B -> `vertico-multiform-buffer'
;;   M-F -> `vertico-multiform-flat'
;;   M-G -> `vertico-multiform-grid'
;;   M-R -> `vertico-multiform-reverse'
;;   M-U -> `vertico-multiform-unobtrusive'
;;   M-V -> `vertico-multiform-vertical'
(after! vertico
  ;; NOTE: Takes precedence over `vertico-multiform-categories'.
  (setopt vertico-multiform-commands
          `((consult-line buffer)
           (consult-imenu reverse buffer)
           (consult-outline buffer quick ,(lambda (_) (text-scale-set -1)))))

  (setopt vertico-multiform-categories
          '((file buffer grid)
            (buffer flat (vertico-cycle . t))
            (consult-grep buffer)
            (imenu (:not indexed mouse))
            (symbol (vertico-sort-function . vertico-sort-alpha))))

  (vertico-multiform-mode))
#+end_src

** CONSULTing completing-read :minibuffer:search:

- website :: <https://github.com/minad/consult>
- ref :: <https://www.gnu.org/software/emacs/manual/html_node/elisp/Minibuffer-Completion.html>

*** Install the Consult package

#+begin_src emacs-lisp
(package! consult)
#+end_src

*** Narrow/filter Consult candidates

#+begin_src emacs-lisp
(setopt consult-narrow-key "<") ;; alternatively: "C-+"
#+end_src

*** Configure the display of candidate previews :ui:

#+begin_src emacs-lisp
(setopt consult-preview-key 'any)
#+end_src

#+begin_src emacs-lisp
;; Enable automatic preview at point in the *Completions* buffer. This is
;; relevant when you use the default completion UI.
(add-hook 'completion-list-mode-hook #'consult-preview-at-point-mode)
#+end_src

**** Granularly refine per-command preview behavior

#+begin_src emacs-lisp
(after! consult
  ;; For some commands and buffer sources it is useful to configure the
  ;; :preview-key on a per-command basis using the `consult-customize' macro.
  (consult-customize
   consult-theme :preview-key '(:debounce 0.2 any)
   consult-ripgrep consult-git-grep consult-grep
   consult-bookmark consult-recent-file consult-xref
   consult--source-bookmark consult--source-file-register
   consult--source-recent-file consult--source-project-recent-file
   ;; :preview-key (kbd "M-.")
   :preview-key '(:debounce 0.4 any)))
#+end_src

**** Improve previews for ~consult-register~ and other register commands :registers:

#+begin_src emacs-lisp
(setopt register-preview-delay 0.5)
(setopt register-preview-function #'consult-register-format)
(advice-add #'register-preview :override #'consult-register-window)
#+end_src

**** Display xref locations with previews :xref:

#+begin_src emacs-lisp
(setopt xref-show-definitions-function #'consult-xref)
(setopt xref-show-xrefs-function #'consult-xref)
#+end_src

*** Keybindings :keybinds:

**** Define global keybindings for Consult commands

#+begin_src emacs-lisp
(define-keymap :keymap (current-global-map)
  "C-c M-x" #'consult-mode-command
  ;; TODO: needs configuration? see `consult-mode-histories'
  ;; "C-c h"   #'consult-history
  "C-c K" #'consult-kmacro
  "C-c M" #'consult-man

  ;; FIXME: use a keymap for s prefix
  ;; "C-c s h"  #'consult-history
  ;; "C-c s m"  #'consult-mode-command
  ;; "C-c s k"  #'consult-kmacro

  "<remap> <Info-search>" #'consult-info

  "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 t b" #'consult-buffer-other-tab   ; orig. `switch-to-buffer-other-tab'
  "C-x r"  #'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
  "C-M-#"  #'consult-register

  ;; Miscellaneous
  "M-y" #'consult-yank-pop            ; orig. `yank-pop'

  ;; M-g bindings (goto-map)
  "M-g e"  #'consult-compile-error
  "M-g f"  #'consult-flymake ;; Alternative: consult-flycheck
  "M-g g" #'consult-goto-line
  "M-g M-g" #'consult-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 (search-map)
  "M-s d"  #'consult-fd               ; or `consult-find'
  "M-s D"  #'consult-locate
  "M-s e"  #'consult-isearch-history
  "M-s g"  #'consult-ripgrep
  "M-s G"  #'consult-git-grep
  "M-s l"  #'consult-line
  "M-s L"  #'consult-line-multi
  "M-s k"  #'consult-keep-lines
  "M-s u"  #'consult-focus-lines)
#+end_src

**** Define keybindings in ~isearch-mode-map~ for ~consult-line~ integration :isearch:

#+begin_src emacs-lisp
(after! isearch
  (define-keymap :keymap 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))
#+end_src

**** Define minibuffer-local keybindings for searching its history :minibuffer:history:

#+begin_src emacs-lisp
(keymap-set minibuffer-local-map "M-s" #'consult-history) ; orig. next-matching-history-element
(keymap-set minibuffer-local-map "M-r" #'consult-history)
#+end_src

#+begin_src emacs-lisp
(after! consult

  ;; Make narrowing help available in the minibuffer.
  ;; FIXME: What if Embark commands are not autoloaded yet?
  (define-key consult-narrow-map (vconcat consult-narrow-key "?") #'embark-prefix-help-command)

  ;; By default `consult-project-function' uses `project-root' from project.el.
  (with-eval-after-load 'projectile
    (declare-function projectile-project-root "projectile")
    (setopt consult-project-function (lambda (_) (projectile-project-root)))))
#+end_src

*** Install ~embark-consult~ for Embark integration

#+begin_src emacs-lisp
(package! embark-consult
  (after! embark
    (add-hook 'embark-collect-mode-hook #'consult-preview-at-point-mode))

  (after! (embark consult)
    (require 'embark-consult)))
#+end_src

*** Pulse line for visual feedback upon Consult selection jump :ui:navigation:

+ [ ] Why after ~consult-imenu~?

#+begin_src emacs-lisp
(after! (consult consult-imenu pulsar)
  (setq consult-after-jump-hook nil)
  (dolist (fn '(pulsar-recenter-top pulsar-reveal-entry))
    (add-hook 'consult-after-jump-hook fn)))
#+end_src


** Marginalia for minibuffer completion candidates :minibuffer:help:

- website :: <https://github.com/minad/marginalia>

#+begin_src emacs-lisp
(package! marginalia
  (keymap-set minibuffer-local-map "M-A" #'marginalia-cycle)

  (marginalia-mode))
#+end_src

*** Projectile integration

#+begin_src emacs-lisp
(after! (marginalia)
  (when (featurep 'projectile)
    (add-to-list 'marginalia-command-categories '(projectile-find-file . file))))
#+end_src

** Orderlessly completion-match multiple regular expressions :search:

+ website :: <https://github.com/oantolin/orderless>
+ source :: <https://github.com/oantolin/orderless?tab=readme-ov-file#style-dispatchers>
+ source :: <https://github.com/minad/consult/wiki#minads-orderless-configuration>

#+begin_src emacs-lisp
(package! orderless
  ;; FIXME: unnecessary as long as stuff is configured
  (require 'orderless)

  ;; Allow escaping space with backslash.
  (setopt orderless-component-separator #'orderless-escapable-split-on-space))
#+end_src

*** Query syntax additions through style dispatchers

#+begin_src emacs-lisp
;; (defun +orderless-flex-if-twiddle-dispatch (pattern _index _total)
;;   "Return `orderless-flex' if PATTERN ends in a tilde character.
;; PATTERN, stripped of its tilde character, will be dispatched as
;; argument to `orderless-flex'."
;;   (when (string-suffix-p "~" pattern)
;;     `(orderless-flex . ,(substring pattern 0 -1))))

;; (defun +orderless-first-initialism-dispatch (_pattern index _total)
;;   "Return `orderless-initialism' when PATTERN has the initial INDEX value."
;;   (if (= index 0) 'orderless-initialism))

;; (defun +orderless-not-if-bang-dispatch (pattern _index _total)
;;   "Return `orderless-not' when PATTERN begins with an exclamation mark.
;; PATTERN, stripped of its exclamation mark, will be dispatched as
;; argument to `orderless-not'."
;;   (cond
;;    ((equal "!" pattern)
;;     #'ignore)
;;    ((string-prefix-p "!" pattern)
;;     `(orderless-not . ,(substring pattern 1)))))

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

;; Recognizes the following patterns:
;; + .ext (file extension)
;; + regexp$ (regexp matching at end)
(defun +orderless-consult-dispatch (word _index _total)
  (cond
   ;; Ensure that $ works with Consult commands, which add disambiguation suffixes
   ((string-suffix-p "$" word)
    `(orderless-regexp . ,(concat (substring word 0 -1) (+orderless--consult-suffix))))
   ;; File extensions
   ((and (or minibuffer-completing-file-name
             (derived-mode-p 'eshell-mode))
         (string-match-p "\\`\\.." word))
    `(orderless-regexp . ,(concat "\\." (substring word 1) (+orderless--consult-suffix))))))
#+end_src

*** Define custom completion syntax styles

#+begin_src emacs-lisp
(after! orderless
  (orderless-define-completion-style +orderless-with-initialism
    (orderless-matching-styles '(orderless-initialism
                                 orderless-literal
                                 orderless-regexp))))
#+end_src

** COmpletion in Region FUnction :capfs:

+ website :: <https://github.com/minad/corfu>
+ website :: <https://github.com/minad/cape>
+ ref :: <https://www.gnu.org/software/emacs/manual/html_node/emacs/Dynamic-Abbrevs.html>



*** Use ~orderless~ completion settings

See [[*Orderless]] for definitions.

#+begin_src emacs-lisp
(after! orderless
  (setopt completion-styles '(orderless basic))
  (setopt completion-category-defaults nil)
  (setopt completion-category-overrides '((file (styles partial-completion))
                                          (command (styles +orderless-with-initialism))
                                          (variable (styles +orderless-with-initialism))
                                          (symbol (styles +orderless-with-initialism))))

  (setopt orderless-matching-styles '(orderless-regexp))
  (setopt orderless-style-dispatchers (list ;; #'+orderless-first-initialism-dispatch
                                       ;; #'+orderless-flex-if-twiddle-dispatch
                                       ;; #'+orderless-not-if-bang-dispatch
                                       #'+orderless-consult-dispatch
                                       #'orderless-affix-dispatch)))
#+end_src

*** Install Corfu and configure its global settings

#+begin_src emacs-lisp
(package! corfu
  (setopt corfu-auto t)
  (setopt corfu-auto-delay 0.05)
  (setopt corfu-cycle t)
  (setopt corfu-preselect 'prompt)
  (setopt corfu-count 8)
  (setopt corfu-max-width 80)
  (setopt corfu-on-exact-match nil)
  (setopt corfu-scroll-margin 5)

  (setopt corfu-quit-at-boundary 'separator)
  (setopt corfu-quit-no-match 'separator)

  ;; Trigger insertion of the separator with "M-SPC".  The character will appear
  ;; to be inserted into the buffer.  Upon selecting a candidate (or aborting,
  ;; or whatver), the extra character will be removed.
  (setopt corfu-separator ?_)

  (global-corfu-mode))
#+end_src

*** Customize the ~corfu-map~ transient keybindings

These will be available when a ~corfu~ prompt is active.

#+begin_src emacs-lisp
(after! corfu
  (define-keymap :keymap corfu-map
    "TAB" #'corfu-next
    "S-TAB"  #'corfu-previous
    "RET" nil
    "C-h" #'corfu-info-documentation
    "C-n" nil
    "C-p" nil
    "C-RET" #'corfu-insert
    "M-." #'corfu-show-location
    "M-g" #'corfu-info-location ; default
    ;; "M-h" #'corfu-info-documentation    ; default
    "M-h" nil))
#+end_src

**** Ensure Corfu flyout closes when leaving insert state :keybinds:

#+begin_src emacs-lisp
(after! meow
  (add-hook 'meow-insert-exit-hook #'corfu-quit))
#+end_src

**** ~corfu-echo-mode~: Show candidate docs in echo area :messages:help:

Probably redundant when ~corfu-popupinfo-mode~ is in use.

#+begin_src emacs-lisp
(setopt corfu-echo-delay '(0.5 . 0.25))

(after! corfu
  (corfu-echo-mode))
#+end_src

**** ~corfu-popupinfo~: Display candidate docs or location in popup :ui:help:

#+begin_src emacs-lisp
(setopt corfu-popupinfo-delay '(1.0 . 0.5))

(after! corfu
  (corfu-popupinfo-mode))
#+end_src

**** ~corfu-history-mode~: Sort candidates by history position :history:

#+begin_src emacs-lisp
(after! corfu
  (corfu-history-mode))

;; Persist Corfu history across sessions.
(after! (savehist corfu-history)
  (add-to-list 'savehist-additional-variables 'corfu-history))
#+end_src

**** Install ~corfu-terminal~ for Corfu terminal support 📦

+ website :: <https://codeberg.org/akib/emacs-corfu-terminal>

Corfu-endorsed solution to making it usable in terminal.

See also ~popon~, the utility library powering the interface.

#+begin_src emacs-lisp
(package! corfu-terminal
  (after! (corfu)
    (unless (display-graphic-p)
      (corfu-terminal-mode 1))))
#+end_src

*** Install ~kind-icon~ to add icons to completion-at-point candidates :ui:icons:

<https://github.com/jdtsmith/kind-icon>

#+begin_src emacs-lisp
(package! kind-icon
  (setopt kind-icon-use-icons (display-graphic-p))
  (setopt kind-icon-blend-background t)

  (require 'kind-icon)

  (after! corfu
    (setopt kind-icon-default-face 'corfu-default)
    (add-to-list 'corfu-margin-formatters #'kind-icon-margin-formatter)))
#+end_src

**** Reduce icon size slightly

If you change this value, make sure to call ~kind-icon-reset-cache~ afterwards,
otherwise the icon size will likely not be accurate.

#+begin_src emacs-lisp
(after! kind-icon
  (plist-put kind-icon-default-style :height 0.9))
#+end_src

**** Update icon apperance after enabling a theme

#+begin_src emacs-lisp
(after! kind-icon
  ;; <jdtsmith/kind-icon#34 (comment)>
  (add-hook 'ceamx-after-enable-theme-hook #'kind-icon-reset-cache))
#+end_src

*** ~dabbrev~: Dynamic Abbreviations

#+begin_src emacs-lisp
(setopt dabbrev-upcase-means-case-search t)
#+end_src

**** Exclude some buffers and modes from ~dabbrev~ sources

#+begin_src emacs-lisp
;; FIXME: the `xr'-generated pattern below is mangled from:
;; (setopt dabbrev-ignored-buffer-regexps
;;         '("\\` "
;;           "\\(TAGS\\|tags\\|ETAGS\\|etags\\|GTAGS\\|GRTAGS\\|GPATH\\)\\(<[0-9]+>\\)?"))

(setopt dabbrev-ignored-buffer-regexps
        (list
         ;; TODO: what does this pattern represent?
         ;;       why is it not same as eval result: (rx "` ")
         "\\` "
         (rx line-start " ")
         (rx (group (or (seq (opt (or "e" (seq "g" (opt "r")))) "tags")
                        "gpath"))
             (optional (group "<" (+ (any numeric)) ">")))))

(after! dabbrev
  (dolist (mode '(doc-view-mode pdf-view-mode tags-table-mode))
    (add-to-list 'dabbrev-ignored-buffer-modes mode)))
#+end_src

**** Ignore sources buffers above size threshold

#+begin_src emacs-lisp :noweb-ref lib-completion
(defvar +dabbrev-friend-buffer-size-max (* 1 1024 1024) ; 1 MB
  "Size limit for a buffer to be scanned for dynamic abbreviations.")

(defun +dabbrev-friend-buffer-p (buf)
  "Whether to consider BUF a `dabbrev' friend buffer."
  (< (buffer-size buf) +dabbrev-friend-buffer-size-max))
#+end_src

#+begin_src emacs-lisp
(setopt dabbrev-friend-buffer-function #'+dabbrev-friend-buffer-p)
#+end_src

**** Override default ~dabbrev~ keybindings :keybinds:

#+begin_src emacs-lisp
(after! dabbrev
  ;; Swap M-/ and C-M-/
  (keymap-global-set "M-/" #'dabbrev-completion)
  (keymap-global-set "C-M-/" #'dabbrev-expand))
#+end_src

** Completion-At-Point Extensions (Cape)

- website :: <https://github.com/minad/cape/blob/main/README.org>

#+begin_src emacs-lisp
;; FIXME: something is real messed up in here... i think probably these get
;; applied by a mode hook but then never removed...?

(package! cape
 (setopt cape-dabbrev-check-other-buffers t)

  (def-hook! +corfu-add-cape-file-h ()
    'prog-mode-hook
    "Register the `cape-file' `completion-at-point-functions'."
    (add-hook 'completion-at-point-functions #'cape-file 0 t))

  (def-hook! +corfu-add-cape-elisp-block-h ()
    '(org-mode-hook markdown-mode-hook)
    "Register Elisp src block symbol completions."
    (add-hook 'completion-at-point-functions #'cape-elisp-block 0 t))

  ;; FIXME: weighted too high!
  (def-hook! +corfu-add-cape-dabbrev-h ()
    '(prog-mode-hook
      text-mode-hook
      conf-mode-hook
      comint-mode-hook
      minibuffer-setup-hook
      eshell-mode-hook)
    "Register `dabbrev' completions."
    (add-hook 'completion-at-point-functions #'cape-dabbrev 20 t))

  (def-hook! +corfu-add-cape-elisp-symbol-h ()
    '(emacs-lisp-mode-hook)
    "Register Emacs Lisp symbol completions."
    (add-to-list 'completion-at-point-functions #'cape-elisp-symbol))

  ;; (advice-add #'pcomplete-completions-at-point :around #'cape-wrap-nonexclusive)

  ;; FIXME: Enabling any of these outright will override everything else above!
  ;; maybe change order? i.e. move these closer to beginning of expression
  ;; (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-sgml)
  ;; (add-to-list 'completion-at-point-functions #'cape-abbrev)
  ;; (add-to-list 'completion-at-point-functions #'cape-dict)

  ;; (add-to-list 'completion-at-point-functions #'cape-line)
  ;; "Character Mnemonics & Character Sets": <https://datatracker.ietf.org/doc/html/rfc1345>
  ;; (add-to-list 'completion-at-point-functions #'cape-rfc1345)

  (define-keymap :keymap (current-global-map)
    "M-p p" #'completion-at-point ;; capf
    "M-p t" #'complete-tag        ;; etags
    "M-p d" #'cape-dabbrev        ;; or dabbrev-completion
    "M-p h" #'cape-history
    "M-p f" #'cape-file
    "M-p k" #'cape-keyword
    "M-p s" #'cape-elisp-symbol
    "M-p e" #'cape-elisp-block
    "M-p a" #'cape-abbrev
    "M-p l" #'cape-line
    "M-p w" #'cape-dict
    "M-p :" #'cape-emoji
    "M-p &" #'cape-sgml
    ;; ref: <https://datatracker.ietf.org/doc/html/rfc1345>
    "M-p r" #'cape-rfc1345))
#+end_src

**** Advise some modes' completion-at-point functions to work alongside ~cape~

#+begin_src emacs-lisp
(after! comint
  (advice-add #'comint-completion-at-point :around #'cape-wrap-nonexclusive))

(after! eglot
  (advice-add #'eglot-completion-at-point :around #'cape-wrap-nonexclusive))

(after! lsp-mode
  (advice-add #'lsp-completion-at-point :around #'cape-wrap-noninterruptible)
  (advice-add #'lsp-completion-at-point :around #'cape-wrap-nonexclusive))
#+end_src


* Embark

#+begin_quote
Emacs Mini-Buffer Actions Rooted in Keymaps
#+end_quote

<https://github.com/oantolin/embark>


** Customizations
:PROPERTIES:
:header-args: emacs-lisp :tangle lisp/init-embark.el
:END:


#+begin_src emacs-lisp
;;; init-embark.el --- Configuration for Embark  -*- lexical-binding: t; -*-

;; Copyright (C) 2023-2024  Chris Montgomery

;; Author: Chris Montgomery <chmont@proton.me>

;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.

;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.

;; You should have received a copy of the GNU General Public License
;; along with this program.  If not, see <https://www.gnu.org/licenses/>.

;;; Commentary:


;;; Code:
#+end_src

*** Install and pre-configure the Embark package

#+begin_src emacs-lisp :noweb yes
(require 'ceamx-lib)

(package! embark
  <<embark-help-key-completion>>

  <<embark-keybindings>>

  <<embark-load>>)
#+end_src

**** Use Embark's completion UI for displaying available key help

#+begin_src emacs-lisp :noweb-ref embark-help-key-completion :tangle no
(setopt prefix-help-command #'embark-prefix-help-command)
#+end_src

**** Keybindings

#+begin_src emacs-lisp :noweb-ref embark-keybindings :tangle no
(keymap-global-set "C-;" #'embark-act)
(keymap-global-set "M-." #'embark-dwim)
#+end_src

**** Load Embark after an idle delay

<2024-03-20 Wed> Embark weighs in at nearly 5k lines-of-code at the time of
writing, so loading it immediately would probably cause a noticeable lag in
session startup.

Embark, like any package with autoloaded commands, will not load until one of
its autoloads is invoked. That has a few consequences, since Embark is versatile
enough to be used as glue for some other, more niche packages.

The ~repeat-help~ package defaults to using Embark's key help facilities before
falling back to ~which-key~. I'd like to try out the Embark UI, as it seems more
legible.

Another benefit to a deferred load is a faster initial display in response to
invoking a command. If the feature needs to be loaded in response to invoking an
autoloaded command e.g. ~embark-act~, there will be a multi-second delay.

#+begin_src emacs-lisp :noweb-ref embark-load :tangle no
(defer! 2
  (require 'embark))
#+end_src

*** Appearance

**** Configure ~display-buffer-alist~ rules for Embark windows

#+begin_src emacs-lisp
(with-eval-after-load 'embark
  ;; 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)))))
#+end_src

*** Footer

#+begin_src emacs-lisp
(provide 'init-embark)
;;; init-embark.el ends here
#+end_src


* Terminal Emulation

#+begin_src emacs-lisp :tangle lisp/init-term.el
;;; init-term.el --- Terminal emulators inside Emacs  -*- lexical-binding: t; -*-

;; Copyright (C) 2024  Chris Montgomery

;; Author: Chris Montgomery <chmont@proton.me>
;; Keywords: local

;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.

;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.

;; You should have received a copy of the GNU General Public License
;; along with this program.  If not, see <https://www.gnu.org/licenses/>.

;;; Commentary:

;; Configuration for terminal emulators running inside Emacs.

;; Not to be confused with configurations for Emacs specific to a TTY
;; environment, which are handled in `init-env-tty'.

;;; Code:

(require 'ceamx-keymaps)
(require 'ceamx-lib)

(use-feature! eshell
  :config
  (setopt eshell-scroll-to-bottom-on-input 'this))

;;; ~eat~ :: <https://codeberg.org/akib/emacs-eat/>

;; "Emulate A Terminal"

(use-package eat
  :commands ( eat
              eat-eshell-mode
              eat-eshell-visual-command-mode)
  :init
  (add-hook 'eshell-load-hook #'eat-eshell-mode)
  (add-hook 'eshell-load-hook #'eat-eshell-visual-command-mode)

  (keymap-set ceamx-launch-map "t" #'eat)

  (use-feature! popper
    :config
    (defvar popper-reference-buffers)
    (setopt popper-reference-buffers
      (append popper-reference-buffers '("\\*eat\\*")))))

(provide 'init-term)
;;; init-term.el ends here
#+end_src


* Version Control (Git)
:PROPERTIES:
:header-args:emacs-lisp: :noweb-ref init-vcs
:END:

** Requirements

#+begin_src emacs-lisp
(require 'ceamx-paths)
(require 'ceamx-lib)
#+end_src

** Keep the ~ediff~ control panel in the same frame

#+begin_src emacs-lisp
(setopt ediff-window-setup-function #'ediff-setup-windows-plain)
#+end_src

** Configure the builtin =vc= support

#+begin_src emacs-lisp
;; Version control support is essential as soon as possible.
(require 'vc)

(setopt vc-follow-symlinks t)

;; No need for all that other nonsense.
(setopt vc-handled-backends '(Git))

;; NOTE: According to the documentation for ~diff-hl~, the diff algorithm
;; cannot be determined based on the user's global git config =diff.algorithm=
;; setting. The website source they linked to has disappeared with no archived
;; page available. So I have not verified this for certain.
(setopt vc-git-diff-switches '("--histogram"))
#+end_src

** ~diff-hl~: Display version control status indicators in margins

- Website :: <https://github.com/dgutov/diff-hl>

NOTE: Fringe indicators will conflict with Flycheck.

#+begin_src emacs-lisp
(package! diff-hl
  (add-hook 'ceamx-after-init-hook #'global-diff-hl-mode)

  ;; Display indicators in margins instead of fringes.
  ;; This will work in terminal sessions and also avoid the fringe conflict with
  ;; other indicators like Flycheck errors.
  (add-hook 'ceamx-after-init-hook #'diff-hl-margin-mode)

  ;; Support mouse click on indicator to show hunk.
  (when (display-graphic-p)
    (add-hook 'ceamx-after-init-hook #'diff-hl-show-hunk-mouse-mode)))

;; Committing changes using a package other than `vc' requires integration.
;; <https://github.com/dgutov/diff-hl#integration>
(after! (diff-hl magit)
  (add-hook 'magit-pre-refresh-hook #'diff-hl-magit-pre-refresh)
  (add-hook 'magit-post-refresh-hook #'diff-hl-magit-post-refresh))

;; Enable `dired' integration.
(after! (diff-hl dired)
  (add-hook 'dired-mode-hook #'diff-hl-dired-mode))
#+end_src

** ~git-commit~: Mode for editing Git commit messages

- Website :: <https://magit.vc/>

#+begin_src emacs-lisp
(package! git-commit
  (require 'git-commit)
  (add-hook 'ceamx-after-init-hook #'global-git-commit-mode))
#+end_src

*** Ensure ~git-commit-mode~ opens in modal insert state

#+begin_src emacs-lisp
(after! (git-commit evil)
  (declare-function evil-insert-state "evil")
  (add-hook 'git-commit-mode-hook #'evil-insert-state))

(after! (git-commit meow)
  (declare-function meow-insert-mode "meow")
  (add-hook 'git-commit-mode-hook #'meow-insert-mode))
#+end_src

** Install additional major modes for Git-related files

- website :: <https://github.com/magit/git-modes>

#+begin_src emacs-lisp
(package! git-modes)
#+end_src

** ~git-timemachine~: Interactively explore files' Git histories

<https://codeberg.org/pidu/git-timemachine>

#+begin_src emacs-lisp
(package! git-timemachine
  (keymap-global-set "C-x v t" #'git-timemachine))

(after! git-timemachine
  ;; XXX: broken, see `ceamx/git-timemachine-dispatch'
  ;; (add-hook 'git-timemachine-mode-hook #'ceamx/git-timemachine-dispatch)

  (define-keymap :keymap git-timemachine-mode-map
    "M-p" #'git-timemachine-show-previous-revision
    "M-n" #'git-timemachine-show-next-revision
    "M-b" #'git-timemachine-blame
    "M-c" #'git-timemachine-show-commit)

  ;; FIXME: like `ceamx/window-dispatch', this breaks because the commands
  ;; should be run in the original buffer/window
  (transient-define-prefix ceamx/git-timemachine-dispatch ()
    "Transient menu for `git-timemachine-mode'."
    ;; :transient-suffix 'transient--do-stack
    [["Navigation"
      ("p" "previous revision" git-timemachine-show-previous-revision :transient t)
      ("n" "next revision" git-timemachine-show-next-revision :transient t)]
     ["Display"
      ("b" "blame" git-timemachine-blame)
      ("c" "commit" git-timemachine-show-commit )]
     [""
      ("q" "quit" git-timemachine-quit :transient nil)]])

  (declare-function git-timemachine--show-minibuffer-details "git-timemachine")

  ;; via <https://github.com/doomemacs/doomemacs/blob/07fca786154551f90f36535bfb21f8ca4abd5027/modules/emacs/vc/config.el#L76C1-L90C47>
  (def-advice! +git-timemachine--details-in-header-line-a (revision)
    :override #'git-timemachine--show-minibuffer-details
    "Show REVISION details in the header-line instead of the minibuffer."
    (let* ((date-relative (nth 3 revision))
           (date-full (nth 4 revision))
           (author (if git-timemachine-show-author (concat (nth 6 revision) ": ") ""))
           (sha-or-subject (if (eq git-timemachine-minibuffer-detail 'commit) (car revision) (nth 5 revision))))
      (setq header-line-format
            (format "%s%s [%s (%s)]"
                    (propertize author 'face 'git-timemachine-minibuffer-author-face)
                    (propertize sha-or-subject 'face 'git-timemachine-minibuffer-detail-face)
                    date-full date-relative)))))
#+end_src

** ~browse-at-remote~: Open the current Git repo's remote forge page in a browser

- Website :: <https://github.com/rmuslimov/browse-at-remote>

#+begin_src emacs-lisp
(package! browse-at-remote
  (keymap-set vc-prefix-map "o" #'browse-at-remote))
#+end_src

** Magit
*** TODO Support closing ~transient~ menus with the escape key

Something very similar to this code seemed to work previously, but that is no
longer the case after switching this configuration to use ~package!~.

This section should also be moved to the main configuration section for
Transient, as it is not specific to ~magit~ or ~magit-section~.

#+begin_src emacs-lisp
(with-eval-after-load 'transient
  (defvar transient-map)
  (declare-function transient-quit-one "transient")

  ;; Always close transient with ESC
  (keymap-set transient-map "ESC" #'transient-quit-one))
#+end_src

*** Install Magit

#+begin_src emacs-lisp
(package! magit)
#+end_src

*** Configure Magit

#+begin_src emacs-lisp
(with-eval-after-load 'magit
  (defvar magit-mode-map)
  (defvar magit-status-mode-map)

  (declare-function magit-discard "magit-apply")
  (declare-function magit-dispatch "magit")
  (declare-function magit-display-buffer-fullframe-status-v1 "magit-mode")
  (declare-function magit-file-dispatch "magit-files")
  (declare-function magit-restore-window-configuration "magit-mode")
  (declare-function magit-revert "magit-sequence")
  (declare-function magit-status "magit-status")

  (setopt magit-diff-refine-hunk t)     ; show granular diffs in selected hunk
  (setopt magit-save-repository-buffers nil) ; avoid side-effects (e.g. auto-format)
  ;; (setopt magit-revision-insert-related-refs nil) ; parent/related refs: rarely useful
  (setopt magit-process-finish-apply-ansi-colors t) ; render ANSI colors in process output

  (setopt magit-bury-buffer-function #'magit-restore-window-configuration)
  ;; <https://magit.vc/manual/magit/Switching-Buffers.html#index-magit_002ddisplay_002dbuffer_002dfullframe_002dstatus_002dv1>
  (setopt magit-display-buffer-function #'magit-display-buffer-fullframe-status-v1))
#+end_src

*** Keep ~magit-section~ sections at the top of the window

- Reference :: <https://emacs.stackexchange.com/questions/3380/how-to-scroll-up-when-expanding-a-section-in-magit-status#comment4819_3383>

#+begin_src emacs-lisp
(after! magit
  (remove-hook 'magit-section-movement-hook 'magit-hunk-set-window-start)
  (add-hook 'magit-section-movement-hook #'magit-section-set-window-start))
#+end_src

*** [[https://github.com/alphapapa/magit-todos][alphapapa/magit-todos]]: Show source-code comment reminders in ~magit-status~

- docs :: <https://github.com/alphapapa/magit-todos/blob/master/README.org>

#+begin_src emacs-lisp
(package! magit-todos
  (after! magit
    (magit-todos-mode 1)))
#+end_src

** Configure Forge integration 📦secrets:api:

#+begin_src emacs-lisp
(package! forge
  (after! magit
    (require 'forge)))
#+end_src

*** Tell Forge which remote to track

Forge will prefer, in order, =upstream= and then =origin=.  I use that convention
for repositories where I am a contributor.  For personal repositories, I tend to
use Git remotes named after the forge.  For example, =github= or =codeberg=.  In
those cases, the recommended option for my setup is to set the Git config
setting =forge.remote= /per repository/ (not globally).

#+begin_src shell
git config forge.remote 'github'
#+end_src

*** GitHub token authentication

- Docs :: <https://magit.vc/manual/ghub.html#Creating-and-Storing-a-Token>
- Docs :: <https://magit.vc/manual/forge.html#Token-Creation>
- Docs :: [[info:auth#The Unix password store][info "(auth) The Unix password store"]]

Create the token at https://github.com/settings/tokens/new with these scopes:

+ =repo=  grants full read/write access to private and public repositories.
+ =user=  grants access to profile information.
+ =read:org=  grants read-only access to organization membership.

Add the token to the password store:

  #+begin_src shell
pass insert api.github.com/montchr^forge
#+end_src

Note that =^forge= is required by the =ghub= backend to denote the package using the
stored token:

#+begin_quote
The default Auth-Source backends only support storing three values per entry;
the "machine", the "login" and the "password". Because Ghub uses separate tokens
for each package, it has to squeeze four values into those three slots, and it
does that by using "USERNAME /^PACKAGE/ " as the "login".
#+end_quote


** Keybindings

#+begin_src emacs-lisp
(define-keymap :keymap (current-global-map)
  "C-c g"    #'magit-dispatch
  "C-c G"    #'magit-file-dispatch

  "C-x g"    #'magit-status
  "C-x M-g"  #'magit-dispatch)

(after! magit
  (define-keymap :keymap magit-status-mode-map
    "_" #'magit-revert
    ;; "V" nil
    "x" #'magit-discard)

  (transient-append-suffix 'magit-commit "-n"
    '("-S" "Disable GPG signing" "--no-gpg"))

  (transient-append-suffix 'magit-fetch "-p"
    '("-t" "Fetch all tags" ("-t" "--tags")))

  (transient-append-suffix 'magit-pull "-r"
    '("-a" "Autostash" "--autostash")))
#+end_src


* Projects

#+begin_src emacs-lisp :noweb-ref config-feature-paths
(defconst ceamx-projects-dir
  (file-name-as-directory
   (or (getenv "XDG_PROJECTS_DIR")
       (concat ceamx-home-dir "Developer")))
  "The root directory for projects.")
#+end_src


#+begin_src emacs-lisp :tangle lisp/init-project.el
;;; init-project.el --- Project support              -*- lexical-binding: t; -*-

;; Copyright (C) 2024  Chris Montgomery

;; Author: Chris Montgomery <chmont@proton.me>
;; Keywords: local

;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.

;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.

;; You should have received a copy of the GNU General Public License
;; along with this program.  If not, see <https://www.gnu.org/licenses/>.

;;; Commentary:

;; Configuration for project support, primarily with project.el.

;;; Code:

;; TODO: something?

(provide 'init-project)
;;; init-project.el ends here
#+end_src

** DISABLED Projectile


* Checkers (Flycheck + Flymake)

<https://www.flycheck.org/en/latest/index.html>
<https://www.flycheck.org/en/latest/user/flycheck-versus-flymake.html>

*** DISABLED Flycheck
:PROPERTIES:
# :header-args: :noweb-ref init-flycheck
:header-args: :noweb-ref nil
:END:

#+begin_src emacs-lisp
(require 'ceamx-keymaps)
(require 'ceamx-lib)
#+end_src

**** ~flycheck~: install and configure

#+begin_src emacs-lisp
(package! flycheck
  (setopt flycheck-emacs-lisp-load-path 'inherit)

  ;; The default includes `newline', which is just too often.
  (setopt flycheck-check-syntax-automatically '(save idle-change mode-enabled))

  (setopt flycheck-idle-change-delay 3.0)
  (setopt flycheck-display-errors-delay 0.5)
  (setopt flycheck-buffer-switch-check-intermediate-buffers nil)

  (keymap-set ceamx-toggle-map "f" #'flycheck-mode)

  (add-hook 'ceamx-after-init-hook #'global-flycheck-mode))
#+end_src

**** ~consult-flycheck~: integration with Consult

#+begin_src emacs-lisp
(package! consult-flycheck
  (keymap-global-set "M-g f" #'consult-flycheck)

  (after! (consult flycheck)
    (require 'consult-flycheck)))
#+end_src

*** Flymake [builtin]
:PROPERTIES:
:header-args: :noweb-ref init-flymake
:END:


Load just about everywhere:

#+begin_src emacs-lisp
(add-hook 'prog-mode-hook #'flymake-mode)
(add-hook 'text-mode-hook #'flymake-mode)
#+end_src

Configure Flymake behavior and apperance:

#+begin_src emacs-lisp
(setopt flymake-no-changes-timeout 1.0
        flymake-wrap-around t
        flymake-fringe-indicator-position 'right-fringe)
#+end_src

**** Display Flymake diagnostics in a childframe with ~flymake-popon~

- Website :: [[https://codeberg.org/akib/emacs-flymake-popon][akib/emacs-flymake-popon: Flymake diagnostics on cursor hover - Codeberg.org]]

#+begin_src emacs-lisp
(package! flymake-popon
  (add-hook 'flymake-mode-hook #'flymake-popon-mode)

  (setopt flymake-popon-method 'popon))
#+end_src

**** DISABLED Display diagnostics alongside Eldoc output

This is a bit noisy and causes a distracting resize of the minibuffer every time
diagnostics change.

#+begin_src emacs-lisp :noweb-ref nil
(after! (eldoc flymake)
  (def-hook! ceamx-flymake-eldoc-function-h ()
    'flymake-mode-hook
    "Use Flymake's Eldoc integration."
    (add-hook 'eldoc-documentation-functions #'flymake-eldoc-function nil t)))
#+end_src

**** Tell Flymake how to digest Flycheck checkers 📦

- Website :: [[https://github.com/purcell/flymake-flycheck][purcell/flymake-flycheck: Use any Emacs flycheck checker as a flymake backend]]

#+begin_src emacs-lisp
(package! flymake-flycheck
  (add-hook 'flymake-mode-hook #'flymake-flycheck-auto))
#+end_src

Disable Flycheck checkers covered by Flymake equivalents:

#+begin_src emacs-lisp
(after! flymake-flycheck
  (setq-default
   flycheck-disabled-checkers
   (append (default-value 'flycheck-disabled-checkers)
           '(emacs-lisp emacs-lisp-checkdoc emacs-lisp-package sh-shellcheck))))
#+end_src

***** Caveats

- Source :: [[https://github.com/purcell/flymake-flycheck][purcell/flymake-flycheck: Use any Emacs flycheck checker as a flymake backend]]
- Retrieved :: [2024-05-21 Tue 09:25]

+ Flycheck UI packages will have no idea of what the checkers are doing, because
  they are run without flycheck's coordination.
+ Flycheck's notion of "chained checkers" is not handled automatically, so
  although multiple chained checkers can be used, they will all be executed
  simultaneously even if earlier checkers fail.  This could either be considered
  a feature, or lead to redundant confusing messages.

**** Keybindings :keybinds:

Mirror the Flycheck default prefix of =C-c !=

#+begin_src emacs-lisp
(after! flymake
  (define-keymap :keymap flymake-mode-map
    "C-c ! l" #'flymake-show-buffer-diagnostics
    "C-c ! n" #'flymake-goto-next-error
    "C-c ! p" #'flymake-goto-previous-error
    "C-c ! c" #'flymake-show-buffer-diagnostics))
#+end_src


* Template Expansion
:PROPERTIES:
:header-args: :noweb-ref init-templates
:END:

Configuration for expandable file templates.

** Define the location of the custom template directory

#+begin_src emacs-lisp :noweb-ref config-feature-paths
(defconst ceamx-templates-dir
  (file-name-as-directory (file-name-concat user-emacs-directory "templates"))
  "Directory for user-defined expandable templates.
Templates, in this sense, refer to the primary focus of packages
like \"tempo\", \"tempel\", and \"yasnippet\".")
#+end_src

** Install and configure TempEl

#+begin_src emacs-lisp
(require 'ceamx-lib)

(package! tempel
  (setopt tempel-path (file-name-concat ceamx-templates-dir "tempel/*.eld"))

  ;; Require this prefix before triggering template name completion.
  (setopt tempel-trigger-prefix "<")

  ;; Setup completion at point for Tempel templates.
  (def-hook! +tempel-setup-capf-h ()
    '(conf-mode-hook prog-mode-hook text-mode-hook)
    "Add the Tempel Capf to `completion-at-point-functions'.

`tempel-expand' only triggers on exact matches.  Alternatively
use `tempel-complete' if you want to see all matches, but then
you should also configure `tempel-trigger-prefix', such that
Tempel does not trigger too often when you don't expect it.

NOTE: We add `tempel-expand' *before* the main programming mode
Capf, such that it will be tried first."
    (setq-local completion-at-point-functions
                (cons #'tempel-expand
                      completion-at-point-functions))))
#+end_src

*** Add custom template element =i= to include nested templates

See <https://github.com/minad/tempel/blob/main/README.org#defining-custom-elements>

#+begin_src emacs-lisp
(defun +tempel-include (elt)
  "Tempel user element ELT to include a nested template."
  (when (eq (car-safe elt) 'i)
    (if-let (template (alist-get (cadr elt) (tempel--templates)))
        (cons 'l template)
      (message "Template %s not found" (cadr elt))
      nil)))

(after! tempel
  (add-to-list 'tempel-user-elements #'+tempel-include))
#+end_src

*** Keybindings

#+begin_src emacs-lisp
(define-keymap :keymap (current-global-map)
  "M-+" #'tempel-complete
  "M-*" #'tempel-insert)

(after! tempel
  (define-keymap :keymap tempel-map
    "TAB" #'tempel-next
    "S-TAB" #'tempel-previous))
#+end_src

*** Install the community ~tempel~ template collection

- Website :: <https://github.com/Crandel/tempel-collection>
- Code :: <https://github.com/Crandel/tempel-collection/tree/main/templates>

#+begin_src emacs-lisp
(package! tempel-collection
  (after! tempel
    (require 'tempel-collection)))
#+end_src

*** Issues

**** DONE ~tempel-abbrev-mode~ results in an error when pressing SPC after a word
CLOSED: [2024-04-13 Sat 00:00]

- State "DONE"       from "TODO"       [2024-04-13 Sat 00:00]

** Install and configure YASnippet

- Documentation :: <https://github.com/joaotavora/yasnippet/blob/master/README.mdown>

#+begin_src emacs-lisp
(package! yasnippet
  (setopt yas-snippet-dirs
          (list (file-name-as-directory
                 (concat ceamx-templates-dir "yasnippet"))))

  (defer! 2
    (yas-global-mode 1)))
#+end_src

Add the official snippets bundle:

#+begin_src emacs-lisp
(package! yasnippet-snippets)
#+end_src

** Reference
*** Tempo Syntax

- Source :: [[https://github.com/minad/tempel/blob/main/README.org#template-syntax][tempel/README.org at main · minad/tempel · GitHub]]

All the Tempo syntax elements are fully supported. The syntax elements are described in detail in the docstring of ~tempo-define-template~  in tempo.el. We document the important ones here:

- “string” Inserts a string literal.
- =p=  Inserts an unnamed placeholder field.
- =n=  Inserts a newline.
- =>= Indents with ~indent-according-to-mode~ .
- =r=  Inserts the current region. If no region is active, quits the containing template when jumped to.
- =r>= Acts like =r= , but indent region.
- =n>=  Inserts a newline and indents.
- =&=  Insert newline unless there is only whitespace between line start and point.
- =%=  Insert newline unless there is only whitespace between point and line end.
- =o= Like =%=  but leaves the point before newline.
- =(s NAME)=  Inserts a named field.
- =(p PROMPT <NAME> <NOINSERT>)= Insert an optionally named field with a prompt. The =PROMPT= is displayed directly in the buffer as default value. If =NOINSERT= is non-nil, no field is inserted. Then the minibuffer is used for prompting and the value is bound to =NAME= .
- =(r PROMPT <NAME> <NOINSERT>)= Insert region or act like =(p ...)= .
- =(r> PROMPT <NAME> <NOINSERT>)= Act like =(r ...)= , but indent region.

Furthermore Tempel supports syntax extensions:

- =(p FORM <NAME> <NOINSERT>)= Like =p= described above, but =FORM=  is evaluated.
- =(FORM ...)=  Other Lisp forms are evaluated. Named fields are lexically bound.
- =q=  Quits the containing template when jumped to.

** Commentary
*** ~tempel~ vs ~yasnippet~

~tempel~ is simple and powerful, built on top of the Emacs internal ~tempo~
library.  I prefer to use ~tempel~ where possible.

~yasnippet~, by the author of ~eglot~, is generally much more powerful.
~yasnippet~ supports nested template fields and provides many more opportunities
for complex interactive prompts.  ~yasnippet~ has been around for quite a few
years and provides a large degree of compatibility with the text-expansion
template syntax established by the classic macOS text editor TextMate.

Wait...  Why am I explaining this?  Who is my audience?  What drives this desire
to /explain/ information that is readily-available?  Could it be that I am
compelled to reproduce the empty rhetorical stylings of a mad parrot-god whose
half-digested productions drop from its beak as worm-words crawling in-to and
out-from every crack and corner of this arachnid desert, driven by some kind of
hive-mind manufacturing reductive, homogenizing, totalitarian thoughtforms?  And
if so, what is this parrot-god's end goal?  I have so many questions.  I think a
table of pros/cons and a list of differences would likely suffice.

*** "Templates" or "Snippets"?"

Templates may have placeholders to be filled and expanded.  ~tempel~ and
~yasnippet~ do this, despite the name of the latter package.

Snippets are static chunks of text, often "tricks" and easy-to-forget reference
material.  Abbrevs (via ~abbrev-mode~) are an example of this.

Templates may include a snippet in their expansion.  Snippets in themselves have
no relation to templates, unless we are coincidentally talking about a snippet
whose subject is templates.  I will say no more on that matter so that we may
avoid further confusion.

** TODO Add templates for inserting SPDX license headers

- src :: <https://github.com/condy0919/spdx.el>

  #+begin_src emacs-lisp
(package! spdx
  (keymap-set ceamx-insert-map "L" #'spdx-insert-spdx))
#+end_src

*** TODO Add this package's Tempo templates to Tempel


* Language Support

** General Programming (~prog-mode~ derived modes)

#+begin_src emacs-lisp :tangle lisp/init-prog.el
;;; init-prog.el --- Programming mode support        -*- lexical-binding: t; -*-

;; Copyright (C) 2023-2024  Chris Montgomery

;; Author: Chris Montgomery <chmont@proton.me>
;; Keywords: local

;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.

;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.

;; You should have received a copy of the GNU General Public License
;; along with this program.  If not, see <https://www.gnu.org/licenses/>.

;;; Commentary:

;; When programming happens, here we are.

;;; Code:

;;; Requirements

(require 'ceamx-keymaps)

(require 'ceamx-lib)
(require 'lib-lisp)

;;; Hooks

(defun ceamx-prog-mode-init-h ()
  "Enable features and defaults useful in any `prog-mode'-derived major modes.
This function is especially useful to ensure functions are called
in a specific order. For this reason, condition checks on
`boundp'/`fboundp' are preferable to using `after!' or
`with-eval-after-load', as the latter may result in a
non-deterministic execution order.

Intended for use as a hook callback on `prog-mode-hook'."

  ;; `highlight-function-calls-mode' should be enabled after other highlighters
  ;; (e.g. `rainbow-delimiters-mode'), according to its readme.
  (when (fboundp 'highlight-function-calls-mode)
    (highlight-function-calls-mode 1)))

(add-hook 'prog-mode-hook #'ceamx-prog-mode-init-h)
#+end_src

*** Configure ~xref~

#+begin_src emacs-lisp :tangle lisp/config-prog.el
(defvar xref-ignored-files '()
  "List of files to be ignored by `xref'.")
#+end_src

#+begin_src emacs-lisp :tangle lisp/init-prog.el
;; Always find references of symbol at point.
(setopt xref-prompt-for-identifier nil)
#+end_src

*** Packages

#+begin_src emacs-lisp :tangle lisp/init-prog.el
;;; hl-todo :: <https://github.com/tarsius/hl-todo>
;;  Highlight TODO and other codetags in comments and strings
;;  <https://peps.python.org/pep-0350/#specification>
(use-package hl-todo
  :commands (hl-todo-mode)
  :init
  (add-hook 'prog-mode-hook #'hl-todo-mode))
#+end_src

#+begin_src emacs-lisp :tangle lisp/init-prog.el
;;; ~dumb-jump~ :: <https://github.com/jacktasia/dumb-jump>

;;  "zero-configuration" jump-to-definition package with support for many langs

(package! dumb-jump
  ;; Add to end of list as a fallback for when there are no smart options.
  (add-hook 'xref-backend-functions #'dumb-jump-xref-activate 100))

(after! (hydra)
  ;; via <https://github.com/jacktasia/dumb-jump?tab=readme-ov-file#hydra-for-effieciency>
  (defhydra ceamx-prog-dumb-jump-dispatch (:color blue :columns 3)
    "Jump (dumbly)"
    ("j" dumb-jump-go "Go")
    ("o" dumb-jump-go-other-window "Other window")
    ("e" dumb-jump-go-prefer-external "Go external")
    ("x" dumb-jump-go-prefer-external-other-window "Go external other window")
    ("i" dumb-jump-go-prompt "Prompt")
    ("l" dumb-jump-quick-look "Quick look")
    ("b" dumb-jump-back "Back")))
#+end_src

#+begin_src emacs-lisp :tangle lisp/init-prog.el
;;; ~rainbow-delimiters~ :: <https://github.com/Fanael/rainbow-delimiters>
(use-package rainbow-delimiters
  :commands (rainbow-delimiters-mode)
  :init
  (add-hook 'ceamx-lisp-init-hook #'rainbow-delimiters-mode))
#+end_src

#+begin_src emacs-lisp :tangle lisp/init-prog.el
;;; ~repl-toggle~ :: <https://git.sr.ht/~tomterl/repl-toggle>
;;  Switch between `prog-mode' buffers and their corresponding REPLs.
;; FIXME: "cannot load"
;; (use-package repl-toggle
;;   :defines (rtog/mode-repl-alist)
;;   :config
;;   ;; TODO: `setopt'?
;;   (setq rtog/mode-repl-alist '( (php-mode . psysh)
;;                                 (emacs-lisp-mode . ielm)
;;                                 (nix-mode . nix-repl))))
#+end_src

#+begin_src emacs-lisp :tangle lisp/init-prog.el
;;; Keybindings

(keymap-global-set "C-c l" (cons "Code" (define-prefix-command 'ceamx-code-prefix)))

(keymap-global-set "C-c l d" #'xref-find-definitions)
(keymap-global-set "C-c l j" #'ceamx-prog-dumb-jump-dispatch/body)
#+end_src

#+begin_src emacs-lisp :tangle lisp/init-prog.el
(provide 'init-prog)
;;; init-prog.el ends here
#+end_src


** Tree-Sitter
:PROPERTIES:
:header-args: :tangle lisp/init-treesitter.el
:END:

#+begin_src emacs-lisp
;;; init-treesitter.el --- Tree-Sitter support          -*- lexical-binding: t; -*-

;; Copyright (C) 2023-2024  Chris Montgomery

;; Author: Chris Montgomery <chmont@proton.me>
;; Keywords: languages, local

;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.

;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.

;; You should have received a copy of the GNU General Public License
;; along with this program.  If not, see <https://www.gnu.org/licenses/>.
#+end_src

#+begin_src emacs-lisp
;;; Commentary:
#+end_src

#+begin_src emacs-lisp
;;;; Requirements:

;; Emacs 29+ built with tree-sitter support.
;; If using Nix, this is handled by default, done.
;;
;; Linux: `pkgs.emacs29' or `emacs-overlay.packages.emacs-unstable-pgtk'
;; Darwin: `pkgs.emacs29-macport'

;; Add these to `programs.emacs.extraPackages':
;;
;;  - `epkgs.treesit-auto'
;;  - `epkgs.treesit-grammars.with-all-grammars'
#+end_src

#+begin_src emacs-lisp
;;;; Mode Association:

;; NOTE: This feature is intended to be loaded *after* all other language
;;       packages have been installed so that ~treesit-auto~ it can override ~auto-mode-alist~.

;;  By default, Emacs plays it safe with tree-sitter language support so as not
;;  to override legacy mode file extension associations. This makes sense as a
;;  default, but it's a pain to have to override ~auto-mode-alist~ for every
;;  language individually.
;;
;;  ~treesit-auto~ is pretty smart about how it handles these behaviors; its
;;  readme provides more in-depth details.

;;  In short, ~global-treesit-auto-mode~ will:
;;
;;  - Automatically switch to <name>-ts-mode when the grammar for <name> is installed
;;  - Stick with <name>-mode if the grammar isn’t installed
;;  - Automatically install a grammar before opening a compatible file
;;  - Modify auto-mode-alist for tree-sitter modes

;;  See also <https://github.com/purcell/emacs.d/blob/master/lisp/init-treesitter.el>
;;  for a more manual approach.
#+end_src

#+begin_src emacs-lisp
;;; Code:

(require 'treesit)

(require 'ceamx-lib)
#+end_src

#+begin_src emacs-lisp
;;; Automatically use available ~treesit~ modes via ~treesit-auto~

;; <https://github.com/renzmann/treesit-auto>
#+end_src

#+begin_src emacs-lisp
;; NOTE: This package does *not* automatically manage mode-hook translation.
;; Those should be managed manually on a case-by-case basis. For example,
;; ~nix-ts-mode-hook~ does not currently inherit the value of ~nix-mode-hook~.
;; Some Tree-Sitter modes, however, still derive from their non-Tree-Sitter
;; predecessor, and so will also run that mode's hooks in addition to its own.
#+end_src

#+begin_src emacs-lisp
(package! treesit-auto
  (require 'treesit-auto)

  ;; Grammars should be installed via Nixpkgs.
  (setopt treesit-auto-install nil)

  (treesit-auto-add-to-auto-mode-alist 'all)

  (global-treesit-auto-mode))
#+end_src

#+begin_src emacs-lisp
(provide 'init-treesitter)
;;; init-treesitter.el ends here
#+end_src

** General LISPs

Configuration for working with Lisps of all kinds.

*** Target Files

**** =init-lisp.el=

#+begin_src emacs-lisp  :tangle lisp/init-lisp.el
<<file-prop-line(feature="init-lisp",desc="Lisp support")>>

;; Copyright (c) 2022-2024  Chris Montgomery <chmont@proton.me>

;; Author: Chris Montgomery <chmont@proton.me>
;; URL: https://git.sr.ht/~montchr/ceamx
;; Version: 0.1.0

<<file-license>>

;;; Commentary:
;;; Code:

<<init-lisp>>

<<file-footer(feature="init-lisp")>>
#+end_src

**** =config-lisp.el=

#+begin_src emacs-lisp  :tangle lisp/config-lisp.el
<<file-prop-line(feature="config-lisp",desc="Lispy variable definitions")>>

;; Copyright (c) 2023-2024  Chris Montgomery <chmont@proton.me>

;; Author: Chris Montgomery <chmont@proton.me>
;; URL: https://git.sr.ht/~montchr/ceamx
;; Version: 0.1.0

<<file-license>>

;;; Commentary:
;;; Code:

<<config-lisp>>

<<file-footer(feature="config-lisp")>>
#+end_src

**** =lib-lisp.el=

#+begin_src emacs-lisp  :tangle lisp/lib-lisp.el
<<file-prop-line(feature="lib-lisp",desc="Lisp support functions")>>

;; Copyright (c) 2022-2024  Chris Montgomery <chmont@proton.me>

;; Author: Chris Montgomery <chmont@proton.me>
;; URL: https://git.sr.ht/~montchr/ceamx
;; Version: 0.1.0

<<file-license>>

;;; Commentary:
;;; Code:

<<lib-lisp>>

<<file-footer(feature="lib-lisp")>>
#+end_src

*** Code
:PROPERTIES:
:header-args: :noweb-ref init-lisp
:END:

#+begin_src emacs-lisp
(require 'derived)

(require 'ceamx-lib)
(require 'lib-lisp)

(require 'config-lisp)
#+end_src

**** Configure behavior for all Lisp modes with ~ceamx-lisp-init-hook~

#+begin_src emacs-lisp
(add-hook 'ceamx-lisp-init-hook #'ceamx-enable-check-parens-on-save)

(after! flycheck
  (declare-function flycheck-mode "flycheck")
  (add-hook 'ceamx-lisp-init-hook #'flycheck-mode))

;; Add hooks to supported Lisp modes.
(dolist (mode ceamx-lisp-modes-list)
  (add-hook (derived-mode-hook-name mode) #'ceamx-lisp-init))
#+end_src

**** Configure indentation and formatting

I am honestly surprised at how complex, sometimes-broken, and inconsistent is
the process of formatting Lisp code in Emacs.

***** Always indent Lisp code with two spaces

Even if Emacs core does things differently.

#+begin_src emacs-lisp
(dolist (sym '(add-function add-to-list advice-add plist-put))
  (put sym 'lisp-indent-function 2))
#+end_src

***** Prevent ~calculate-lisp-indent~ from indenting quoted lists as functions

[[https://emacs.stackexchange.com/a/52789/40956][indentation - How to indent keywords aligned? - Emacs Stack Exchange]]

#+begin_src emacs-lisp
(advice-add #'calculate-lisp-indent :override #'ceamx-calculate-lisp-indent-a)
#+end_src

**** ~lispy~: the structural expression editing experience

- Website :: [[https://github.com/abo-abo/lispy][GitHub - abo-abo/lispy: Short and sweet LISP editing]]
- API Reference :: [[https://oremacs.com/lispy/][lispy.el function reference]]

#+begin_src emacs-lisp
(package! lispy
  (when (eq 'lispy ceamx-structured-editing-style)
    (add-hook 'ceamx-lisp-init-hook #'lispy-mode)))

(after! lispy
    ;; Prevent `lispy' from inserting escaped quotes when already inside a string,
    ;; in favor of just moving past the closing quote as I would expect.
    ;;
    ;; FIXME: This actually results in creating the quote pair *after* the
    ;; closing quote. "for example:"" "
;;    (setopt lispy-close-quotes-at-end-p t)

    (setopt lispy-completion-method 'default)

    (setopt lispy-eval-display-style 'message)

    ;; I have mixed feelings about this one because it can be jarring and easily
    ;; lead to mass-commenting expressions. Default is non-nil.
    (setopt lispy-move-after-commenting t)

    (define-keymap :keymap lispy-mode-map
      "M-j" nil                         ; shadows custom binding

      ;; via <abo-abo/lispy#619>
      "`" #'self-insert-command)

    (after! outli
      ;; `outli-mode' overrides `lispy-mode' outline functionality, so it must
      ;; be activated afterwards.
      (add-hook 'ceamx-lisp-init-hook #'outli-mode))

    (after! macrostep
      (push 'macrostep lispy-compat))

    (after! popper
      (push "\\*lispy-message\\*" popper-reference-buffers)))
#+end_src

**** ~kbd-mode~: syntax support for =kmonad= and =kanata= configs

[[https://github.com/kmonad/kbd-mode][GitHub - kmonad/kbd-mode: Emacs mode for syntax highlighting kmonad's .kbd files.]]

#+begin_src emacs-lisp
(package! (kbd-mode :host github :repo "kmonad/kbd-mode"))
#+end_src

***** Inhibit formatters

Unfortunately, we need to do this because whitespace is used to convey
non-syntactic meaning to the reader.

#+begin_src emacs-lisp
(require 'config-editor)
(require 'ceamx-lib)

(after! kbd-mode
  (add-to-list 'ceamx-format-on-save-disabled-modes #'kbd-mode)
  (after! lispy
    (add-to-list 'lispy-no-indent-modes #'kbd-mode)))
#+end_src

***** TODO Add =kanata= symbols to list of fontified


*** Options & Variables
:PROPERTIES:
:header-args: :noweb-ref config-lisp
:END:

#+begin_src emacs-lisp
(defvar ceamx-lisp-modes-list '(emacs-lisp-mode
                                ielm-mode
                                lisp-mode
                                inferior-lisp-mode
                                lisp-interaction-mode)
  "Supported Lisp modes.")

(defvar +emacs-lisp-outline-regexp "[ \t]*;;;\\(;*\\**\\) [^ \t\n]"
  "Regexp to use for `outline-regexp' in `emacs-lisp-mode'.
This marks a foldable marker for `outline-minor-mode' in elisp buffers.")
#+end_src

*** Library
:PROPERTIES:
:header-args: :noweb-ref lib-lisp
:END:

**** ~ceamx-lisp-init-hook~

#+begin_src emacs-lisp
(defvar ceamx-lisp-init-hook '()
  "Hook to run in all supported Lisp modes.")

(defun ceamx-lisp-init ()
  "Enable features useful in any Lisp mode."

  (when (and (fboundp 'lispy-mode) (eq 'lispy ceamx-structured-editing-style))
    (lispy-mode))

  ;; `outli' overrides some `lispy' features.
  ;; <https://github.com/jdtsmith/outli?tab=readme-ov-file#configuration>
  (when (fboundp 'outli-mode)
    (outli-mode))

  (run-hooks 'ceamx-lisp-init-hook))
#+end_src

***** Check for unmatched parentheses upon saving files

#+begin_src emacs-lisp
(defun ceamx-enable-check-parens-on-save ()
  "Run `check-parens' when the current buffer is saved."
  (add-hook 'after-save-hook #'check-parens nil t))

(defun +emacs-lisp--in-package-buffer-p ()
  (let* ((file-path (buffer-file-name (buffer-base-buffer)))
         (file-base (if file-path (file-name-base file-path))))
    (and (derived-mode-p 'emacs-lisp-mode)
         (or (null file-base)
             (locate-file file-base (custom-theme--load-path) '(".elc" ".el"))))))
#+end_src

**** ~ceamx/indent-last-sexp~: format the s-expression before pint

#+begin_src emacs-lisp
(defun ceamx/indent-last-sexp ()
  "Apply indentation to sexp before point."
  (interactive)
  (save-excursion
    (backward-list)
    (indent-sexp)))
#+end_src

**** ~ceamx-calculate-lisp-indent-a~: improve indentation logic

- URL :: <https://emacs.stackexchange.com/a/52789/40956>
- SPDX-License-Identifier :: CC-BY-SA-4.0

#+begin_src emacs-lisp
(defun ceamx-calculate-lisp-indent-a (&optional parse-start)
  "Calculate Lisp indentation from PARSE-START with proper quoted list handling.
Intended as overriding advice to `calculate-lisp-indent'.

This function improves upon `calculate-lisp-indent' by fixing
longstanding bugs with its handling of quoted and backquoted
lists."
  ;; This line because `calculate-lisp-indent-last-sexp` was defined with `defvar`
  ;; with it's value ommited, marking it special and only defining it locally. So
  ;; if you don't have this, you'll get a void variable error.
  (defvar calculate-lisp-indent-last-sexp)
  (save-excursion
    (beginning-of-line)
    (let ((indent-point (point))
          state
          ;; setting this to a number inhibits calling hook
          (desired-indent nil)
          (retry t)
          calculate-lisp-indent-last-sexp containing-sexp)
      (cond ((or (markerp parse-start) (integerp parse-start))
             (goto-char parse-start))
            ((null parse-start) (beginning-of-defun))
            (t (setq state parse-start)))
      (unless state
        ;; Find outermost containing sexp
        (while (< (point) indent-point)
          (setq state (parse-partial-sexp (point) indent-point 0))))
      ;; Find innermost containing sexp
      (while (and retry
                  state
                  (> (elt state 0) 0))
        (setq retry nil)
        (setq calculate-lisp-indent-last-sexp (elt state 2))
        (setq containing-sexp (elt state 1))
        ;; Position following last unclosed open.
        (goto-char (1+ containing-sexp))
        ;; Is there a complete sexp since then?
        (if (and calculate-lisp-indent-last-sexp
                 (> calculate-lisp-indent-last-sexp (point)))
            ;; Yes, but is there a containing sexp after that?
            (let ((peek (parse-partial-sexp calculate-lisp-indent-last-sexp
                                            indent-point 0)))
              (if (setq retry (car (cdr peek))) (setq state peek)))))
      (if retry
          nil
        ;; Innermost containing sexp found
        (goto-char (1+ containing-sexp))
        (if (not calculate-lisp-indent-last-sexp)
            ;; indent-point immediately follows open paren.
            ;; Don't call hook.
            (setq desired-indent (current-column))
          ;; Find the start of first element of containing sexp.
          (parse-partial-sexp (point) calculate-lisp-indent-last-sexp 0 t)
          (cond ((looking-at "\\s(")
                 ;; First element of containing sexp is a list.
                 ;; Indent under that list.
                 )
                ((> (save-excursion (forward-line 1) (point))
                    calculate-lisp-indent-last-sexp)
                 ;; This is the first line to start within the containing sexp.
                 ;; It's almost certainly a function call.
                 (if (or
                      ;; Containing sexp has nothing before this line
                      ;; except the first element. Indent under that element.
                      (= (point) calculate-lisp-indent-last-sexp)

                      ;; First sexp after `containing-sexp' is a keyword. This
                      ;; condition is more debatable. It's so that I can have
                      ;; unquoted plists in macros. It assumes that you won't
                      ;; make a function whose name is a keyword.
                      ;; (when-let (char-after (char-after (1+ containing-sexp)))
                      ;;   (char-equal char-after ?:))

                      ;; Check for quotes or backquotes around.
                      (let* ((positions (elt state 9))
                             (last (car (last positions)))
                             (rest (reverse (butlast positions)))
                             (any-quoted-p nil)
                             (point nil))
                        (or
                         (when-let (char (char-before last))
                           (or (char-equal char ?')
                               (char-equal char ?`)))
                         (progn
                           (while (and rest (not any-quoted-p))
                             (setq point (pop rest))
                             (setq any-quoted-p
                                   (or
                                    (when-let (char (char-before point))
                                      (or (char-equal char ?')
                                          (char-equal char ?`)))
                                    (save-excursion
                                      (goto-char (1+ point))
                                      (looking-at-p
                                       "\\(?:back\\)?quote[\t\n\f\s]+(")))))
                           any-quoted-p))))
                     ;; Containing sexp has nothing before this line
                     ;; except the first element.  Indent under that element.
                     nil
                   ;; Skip the first element, find start of second (the first
                   ;; argument of the function call) and indent under.
                   (progn (forward-sexp 1)
                          (parse-partial-sexp (point)
                                              calculate-lisp-indent-last-sexp
                                              0 t)))
                 (backward-prefix-chars))
                (t
                 ;; Indent beneath first sexp on same line as
                 ;; `calculate-lisp-indent-last-sexp'.  Again, it's
                 ;; almost certainly a function call.
                 (goto-char calculate-lisp-indent-last-sexp)
                 (beginning-of-line)
                 (parse-partial-sexp (point) calculate-lisp-indent-last-sexp
                                     0 t)
                 (backward-prefix-chars)))))
      ;; Point is at the point to indent under unless we are inside a string.
      ;; Call indentation hook except when overridden by lisp-indent-offset
      ;; or if the desired indentation has already been computed.
      (let ((normal-indent (current-column)))
        (cond ((elt state 3)
               ;; Inside a string, don't change indentation.
               nil)
              ((and (integerp lisp-indent-offset) containing-sexp)
               ;; Indent by constant offset
               (goto-char containing-sexp)
               (+ (current-column) lisp-indent-offset))
              ;; in this case calculate-lisp-indent-last-sexp is not nil
              (calculate-lisp-indent-last-sexp
               (or
                ;; try to align the parameters of a known function
                (and lisp-indent-function
                     (not retry)
                     (funcall lisp-indent-function indent-point state))
                ;; If the function has no special alignment
                ;; or it does not apply to this argument,
                ;; try to align a constant-symbol under the last
                ;; preceding constant symbol, if there is such one of
                ;; the last 2 preceding symbols, in the previous
                ;; uncommented line.
                (and (save-excursion
                       (goto-char indent-point)
                       (skip-chars-forward " \t")
                       (looking-at ":"))
                     ;; The last sexp may not be at the indentation
                     ;; where it begins, so find that one, instead.
                     (save-excursion
                       (goto-char calculate-lisp-indent-last-sexp)
                       ;; Handle prefix characters and whitespace
                       ;; following an open paren.  (Bug#1012)
                       (backward-prefix-chars)
                       (while (not (or (looking-back "^[ \t]*\\|([ \t]+"
                                                     (line-beginning-position))
                                       (and containing-sexp
                                            (>= (1+ containing-sexp) (point)))))
                         (forward-sexp -1)
                         (backward-prefix-chars))
                       (setq calculate-lisp-indent-last-sexp (point)))
                     (> calculate-lisp-indent-last-sexp
                        (save-excursion
                          (goto-char (1+ containing-sexp))
                          (parse-partial-sexp (point) calculate-lisp-indent-last-sexp 0 t)
                          (point)))
                     (let ((parse-sexp-ignore-comments t)
                           indent)
                       (goto-char calculate-lisp-indent-last-sexp)
                       (or (and (looking-at ":")
                                (setq indent (current-column)))
                           (and (< (line-beginning-position)
                                   (prog2 (backward-sexp) (point)))
                                (looking-at ":")
                                (setq indent (current-column))))
                       indent))
                ;; another symbols or constants not preceded by a constant
                ;; as defined above.
                normal-indent))
              ;; in this case calculate-lisp-indent-last-sexp is nil
              (desired-indent)
              (t
               normal-indent))))))
#+end_src

** Emacs Lisp

*** Customizations

#+begin_src emacs-lisp :tangle lisp/init-lang-elisp.el
;;; init-lang-elisp.el --- Emacs Lisp development support  -*- lexical-binding: t; -*-

;; Copyright (C) 2023-2024  Chris Montgomery

;; Author: Chris Montgomery <chmont@proton.me>
;; Keywords: local, lisp

;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.

;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.

;; You should have received a copy of the GNU General Public License
;; along with this program.  If not, see <https://www.gnu.org/licenses/>.

;;; Commentary:

;;; Code:

;;; Requirements

(require 'config-keys)
(require 'config-lisp)

(require 'ceamx-lib)
(require 'lib-lisp)

(declare-function blackout "blackout")

;;; Hooks

(defun ceamx-emacs-lisp-init ()
  "Sensible defaults for `emacs-lisp-mode'."
  (ceamx-lisp-init)
  (eldoc-mode 1)
  ;; TODO: do we really want this for `ielm' and other derived modes as well?
  (blackout "EL"))

(add-hook 'emacs-lisp-mode-hook #'ceamx-emacs-lisp-init)
(add-hook 'ielm-mode-hook #'ceamx-emacs-lisp-init)

(when (boundp 'eval-expression-minibuffer-setup-hook)
  (add-hook 'eval-expression-minibuffer-setup-hook #'eldoc-mode))

;;; Advices

;; via <https://github.com/doomemacs/doomemacs/blob/98d753e1036f76551ccaa61f5c810782cda3b48a/modules/lang/emacs-lisp/config.el#L124C1-L138C15>
(def-advice! +elisp-flymake-byte-compile-fix-load-path-a (orig-fn &rest args)
  :around #'elisp-flymake-byte-compile
  "Set load path for the `emacs-lisp' byte compilation `flymake' backend."
  (let ((elisp-flymake-byte-compile-load-path
         (append elisp-flymake-byte-compile-load-path load-path)))
    (apply orig-fn args)))

;; via <https://github.com/doomemacs/doomemacs/blob/98d753e1036f76551ccaa61f5c810782cda3b48a/modules/lang/emacs-lisp/config.el#L124C1-L138C15>
(def-advice! +emacs-lisp-append-value-to-eldoc-a (fn sym)
  :around #'elisp-get-var-docstring
  "Display variable value next to documentation in eldoc."
  (when-let (ret (funcall fn sym))
    (if (boundp sym)
      (concat ret " "
        (let* ((truncated " [...]")
                (print-escape-newlines t)
                (str (symbol-value sym))
                (str (prin1-to-string str))
                (limit (- (frame-width) (length ret) (length truncated) 1)))
          (format (format "%%0.%ds%%s" (max limit 0))
            (propertize str 'face 'warning)
            (if (< (length str) limit) "" truncated))))
      ret)))

;;; Keybinds

(keymap-global-set "<remap> <indent-pp-sexp>" #'ceamx/indent-last-sexp)

(define-keymap :keymap emacs-lisp-mode-map
  ceamx-keys-repl-toggle #'ielm

  "C-S-t" #'transpose-sexps)

(with-eval-after-load 'ielm
  (defvar ielm-map)
  (keymap-set ielm-map ceamx-keys-repl-toggle #'quit-window))

;;; Packages

;;;; ~eros~ :: <https://github.com/xiongtx/eros>

;;  Evaluation Result OverlayS for Emacs Lisp

(use-package eros
  :commands (eros-mode eros-eval-last-sexp)
  :init
  (add-hook 'emacs-lisp-mode-hook #'eros-mode)
  (keymap-set emacs-lisp-mode-map "<remap> <eval-last-sexp>" #'eros-eval-last-sexp)

  (use-feature! lispy
    :autoload (lispy-define-key)
    :config
    (def-hook! +lispy-use-eros-eval-h () 'lispy-mode-hook
      "Use `eros-eval-last-sexp' in place of `lispy-eval' bindings."
      ;; FIXME: there is currently no way to hide lispy-eval output.
      ;;        nil results in an error.
      ;;        because of this, output is duplicated in the minibuffer and the
      ;;        eros overlay...
      ;;
      ;; (setopt lispy-eval-display-style nil)
      (lispy-define-key lispy-mode-map "e" #'eros-eval-last-sexp))))

;;;; ~suggest~ :: <https://github.com/Wilfred/suggest.el>

;;  discover elisp functions that do what you want,
;;  brought to you by enumerative program synthesis

(use-package suggest
  :commands (suggest)
  :init
  (keymap-set emacs-lisp-mode-map "C-c S" #'suggest))

;;;; ~macrostep~ :: <https://github.com/emacsorphanage/macrostep>

;;  "interactive macro-expander for Emacs"

(use-package macrostep
  :commands (macrostep-expand)

  :preface
  ;; <joddie/macrostep#11>
  ;; <emacsorphanage/macrostep#8>
  (defun ceamx/macrostep-expand ()
    "Wrapper for `macrostep-expand' providing workaround for errors.
The original function fails in the presence of whitespace after a sexp."
    (interactive)
    (when (and (= ?\n (char-after))
            (= (point) (cdr (bounds-of-thing-at-point 'sexp))))
      (backward-char))
    (macrostep-expand))

  :init
  (keymap-set emacs-lisp-mode-map "C-c x" #'ceamx/macrostep-expand))

;;; Install ~xr~ to convert string regexps to ~rx~ forms

;; <https://github.com/mattiase/xr>

;; TODO: keybindings...

(package! xr)

(provide 'init-lang-elisp)
;;; init-lang-elisp.el ends here
#+end_src


*** Library

#+begin_src emacs-lisp :tangle lisp/lib-elisp.el
;;; lib-elisp.el --- Helper utilities for Emacs Lisp development and language support  -*- lexical-binding: t; -*-

;; Copyright (C) 2024  Chris Montgomery
;; Copyright (C) 2018  Adam Porter

;; Author: Chris Montgomery <chmont@proton.me>
;;         Adam Porter <adam@alphapapa.net>
;; Keywords: local, lisp, tools, internal, convenience

;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.

;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.

;; You should have received a copy of the GNU General Public License
;; along with this program.  If not, see <https://www.gnu.org/licenses/>.

;;; Commentary:

;;; Sources:

;; <https://github.com/alphapapa/emacs-package-dev-handbook/blob/4a78d753e965bc2cb87f72a72974a4514c4d18dd/README.org#emacs-lisp-macroreplace>

;;; Code:

;;; Functions

;; via <https://emacs.stackexchange.com/a/58078/40956>
(defun ceamx-dev-list-mode-ancestors (mode)
  "Return a list of the ancestor modes that MODE is derived from."
  (let ((modes   ())
        (parent  nil))
    (while (setq parent (get mode 'derived-mode-parent))
      (push parent modes)
      (setq mode parent))
    (setq modes  (nreverse modes))))

;;; Macros

;; via <https://github.com/alphapapa/emacs-package-dev-handbook/blob/master/README.org#debug-warn-macro>
(cl-defmacro debug-warn! (&rest args)
  "Display a debug warning showing the runtime value of ARGS.
The warning automatically includes the name of the containing
function, and it is only displayed if `warning-minimum-log-level'
is `:debug' at expansion time (otherwise the macro expands to nil
and is eliminated by the byte-compiler).  When debugging, the
form also returns nil so, e.g. it may be used in a conditional in
place of nil.

Each of ARGS may be a string, which is displayed as-is, or a
symbol, the value of which is displayed prefixed by its name, or
a Lisp form, which is displayed prefixed by its first symbol.

Before the actual ARGS arguments, you can write keyword
arguments, i.e. alternating keywords and values.  The following
keywords are supported:

  :buffer BUFFER   Name of buffer to pass to `display-warning'.
  :level  LEVEL    Level passed to `display-warning', which see.
                   Default is :debug."
  ;; TODO: Can we use a compiler macro to handle this more elegantly?
  (pcase-let* ((fn-name (when byte-compile-current-buffer
                          (with-current-buffer byte-compile-current-buffer
                            ;; This is a hack, but a nifty one.
                            (save-excursion
                              (beginning-of-defun)
                              (cl-second (read (current-buffer)))))))
                (plist-args (cl-loop while (keywordp (car args))
                              collect (pop args)
                              collect (pop args)))
                ((map (:buffer buffer) (:level level)) plist-args)
                (level (or level :debug))
                (string (cl-loop for arg in args
                          concat (pcase arg
                                   ((pred stringp) "%S ")
                                   ((pred symbolp)
                                     (concat (upcase (symbol-name arg)) ":%S "))
                                   ((pred listp)
                                     (concat "(" (upcase (symbol-name (car arg)))
                                       (pcase (length arg)
                                         (1 ")")
                                         (_ "...)"))
                                       ":%S "))))))
    ;; FIXME: ensure available (from where?) or just defvar
    (when (eq :debug warning-minimum-log-level)
      `(let ((fn-name ,(if fn-name
                         `',fn-name
                         ;; In an interpreted function: use `backtrace-frame' to get the
                         ;; function name (we have to use a little hackery to figure out
                         ;; how far up the frame to look, but this seems to work).
                         `(cl-loop for frame in (backtrace-frames)
                            for fn = (cl-second frame)
                            when (not (or (subrp fn)
                                        (special-form-p fn)
                                        (eq 'backtrace-frames fn)))
                            return (make-symbol (format "%s [interpreted]" fn))))))
         (display-warning fn-name (format ,string ,@args) ,level ,buffer)
         nil))))

;;; Commands

;;;###autoload
(defun ceamx/emacs-lisp-macroreplace ()
  "Replace macro form before or after point with its expansion."
  (interactive)
  (if-let* ((beg (point))
             (end t)
             (form (or (ignore-errors
                         (save-excursion
                           (prog1 (read (current-buffer))
                             (setq end (point)))))
                     (ignore-errors
                       (forward-sexp -1)
                       (setq beg (point))
                       (prog1 (read (current-buffer))
                         (setq end (point))))))
             (expansion (macroexpand-all form)))
    ;; FIXME: replace obsolete function with what?
    (setf (buffer-substring beg end) (pp-to-string expansion))
    (user-error "Unable to expand")))

(provide 'lib-elisp)
;;; lib-elisp.el ends here
#+end_src

** Language Server and Debugger Protocol Support :lsp:lang:
:PROPERTIES:
:header-args: :noweb-ref init-lsp
:END:

#+begin_src emacs-lisp :noweb-ref config-prog
(defcustom ceamx-lsp-client 'eglot
  "The preferred LSP client."
  :group 'ceamx
  :type '(choice :tag "LSP client" :value eglot
          (const :tag "Eglot [builtin]" eglot)
          (const :tag "LSP-Mode" lsp-mode)))

(defvar ceamx-lsp-mode-cache-dir (file-name-as-directory (concat ceamx-var-dir "lsp")))
#+end_src

#+begin_src emacs-lisp :noweb-ref config-prog
(setopt ceamx-lsp-client 'eglot)
#+end_src

*** Requirements

#+begin_src emacs-lisp
(require 'ceamx-keymaps)
(require 'ceamx-lib)
#+end_src

*** DISABLED LSP-Mode
:PROPERTIES:
:header-args: :noweb-ref nil
:END:

**** Install ~lsp-mode~ and ~lsp-ui~

#+begin_src emacs-lisp
(package! lsp-mode
  (setopt lsp-keymap-prefix "C-c l")
  (add-hook 'lsp-mode-hook #'lsp-enable-which-key-integration))

(package! lsp-ui)

(after! popper
  (add-to-list 'popper-reference-buffers (rx bol "*lsp-" (group (or "help" "install")))))
#+end_src

**** LSP-Mode settings :formatting:

#+begin_src emacs-lisp
(setopt
 lsp-enable-folding nil
 lsp-enable-on-type-formatting nil
 lsp-headerline-breadcrumb-enable nil)
#+end_src

**** Adjust LSP-UI appearance :ui:

#+begin_src emacs-lisp
(setopt lsp-ui-peek-enable t
        lsp-ui-doc-max-width 72
        lsp-ui-doc-max-height 8
        lsp-ui-doc-delay 0.2
        ;; Prevent the doc flyout from disappearing on hover.
        lsp-ui-doc-show-with-mouse nil
        lsp-ui-doc-position 'at-point
        ;; Prevent conflict with Flycheck error overlays.
        lsp-ui-sideline-show-hover nil)

(after! lsp-ui
  lsp-ui-sideline-actions-icon lsp-ui-sideline-actions-icon-default)
#+end_src

**** Tell ~lsp-mode~ to respect ~corfu~ and ~orderless~ :capt:

#+begin_src emacs-lisp
(after! (lsp-mode corfu)
  (setopt lsp-completion-provider :none)

  (defun +orderless-dispatch-flex-first-h (_pattern index _total)
    "Dispatch flex completion styles before all others.
Intended for use as a hook function on `orderless-style-dispatchers'."
    (and (eq index 0) 'orderless-flex))

  (def-hook! +lsp-mode-setup-completion-h ()
    'lsp-completion-mode-hook
    "Use `orderless' completion style with `lsp-mode'."
    (setf (alist-get 'styles (alist-get 'lsp-capf completion-category-defaults))
          '(orderless))
    ;; Consider the first word as a flex filter.
    (add-hook 'orderless-style-dispatchers #'+orderless-dispatch-flex-first-h nil 'local)
    (setq-local completion-at-point-functions
                (list (cape-capf-buster #'lsp-completion-at-point)))))
#+end_src

**** Parse LSP JSON data as bytecode with =lsp-booster= :perf:

- Source :: <https://github.com/blahgeek/emacs-lsp-booster/blob/master/README.md#configure-lsp-mode>

#+begin_src emacs-lisp
(require 'json)

(after! lsp-mode
  (def-advice! lsp-booster--json-parse-as-bytecode-a (old-fn &rest args)
    :around #'json-parse-buffer
    "Maybe parse JSON as bytecode from \"emacs-lsp-booster\"."
    (or
     (when (equal (following-char) ?#)
       (let ((bytecode (read (current-buffer))))
         (when (byte-code-function-p bytecode)
           (funcall bytecode))))
     (apply old-fn args))))
#+end_src

***** Wrap LSP server commands with =lsp-booster= :perf:

- Source :: <https://github.com/blahgeek/emacs-lsp-booster/blob/master/README.md#configure-lsp-mode>

#+begin_src emacs-lisp
(after! lsp-mode
  (def-advice! lsp-booster--resolve-final-command-a (old-fn cmd &optional test?)
    :around #'lsp-resolve-final-command
    "Wrap the LSP server command CMD with a call to \"emacs-lsp-booster\"."
    (let ((orig-result (funcall old-fn cmd test?)))
      (if (and (not test?)                             ;; for check lsp-server-present?
               (not (file-remote-p default-directory)) ;; see lsp-resolve-final-command, it would add extra shell wrapper
               lsp-use-plists
               (not (functionp 'json-rpc-connection))  ;; native json-rpc
               (executable-find "emacs-lsp-booster"))
          (progn
            (message "Using emacs-lsp-booster for %s!" orig-result)
            (cons "emacs-lsp-booster" orig-result))
        orig-result))))
#+end_src

**** Provide workspace symbols as Consult datasource :completion:

#+begin_src emacs-lisp
(package! consult-lsp)
#+end_src

**** Define function to change the priority of a pre-defined LSP client

- source :: <https://github.com/doomemacs/doomemacs/blob/9620bb45ac4cd7b0274c497b2d9d93c4ad9364ee/modules/tools/lsp/autoload/lsp-mode.el#L4-L11>

#+begin_src emacs-lisp :noweb-ref lib-prog
(defun ceamx-lsp-client-set-priority (client priority)
  "Change the PRIORITY of an LSP-Mode CLIENT."
  (require 'lsp-mode)
  (if-let (client (gethash client lsp-clients))
      (setf (lsp--client-priority client)
            priority)
    (error "No LSP client named %S" client)))
#+end_src

*** Eglot

#+begin_src emacs-lisp :noweb-ref config-feature-paths
(defvar ceamx-eglot-storage-dir (file-name-as-directory (concat ceamx-var-dir "eglot")))
#+end_src

#+begin_src emacs-lisp
(setopt eglot-sync-connect 1)
(setopt eglot-autoshutdown t)
(setopt eglot-send-changes-idle-time 0.5)

;; Disable events buffer, which poses performance issues over time as the
;; buffer grows in a longer-running Emacs instance.
(setopt eglot-events-buffer-size 0)

;; Prevent frequent focus-stealing.
(setopt eglot-auto-display-help-buffer nil)
#+end_src

**** Use =emacs-lsp-booster= via ~eglot-booster~ :perf:

- Website :: <https://github.com/jdtsmith/eglot-booster>
- Website :: <https://github.com/blahgeek/emacs-lsp-booster>

Requires =emacs-lsp-booster= to be installed into the environment.  Available by that name in Nixpkgs.

#+begin_src emacs-lisp
(package! (eglot-booster :host github :repo "jdtsmith/eglot-booster")
  (after! eglot
    (eglot-booster-mode)))
#+end_src

Though I have not tried it, I am thinking that using =lsp-booster= over TRAMP is
not worth the trouble of ensuring that the executable is available on every
remote server.  At least not as a default behavior.  Consider enabling this
per-project or server as desired.

elsp
#+begin_src emacs-lisp
(setopt eglot-booster-no-remote-boost t)
#+end_src

**** Run language servers automatically in supported major modes

The timing here may be delicate...

#+begin_src emacs-lisp
(add-hook 'prog-mode-hook #'eglot-ensure)

(after! eglot
  (defvar eglot-server-programs)

  (def-advice! +eglot--ensure-available-mode (fn)
    :around #'eglot-ensure
    "Run `eglot-ensure' in supported modes."
    (when (alist-get major-mode eglot-server-programs nil nil
                     (lambda (modes key)
                       (if (listp modes)
                           (member key modes)
                         (eq key modes))))
      (funcall fn))))
#+end_src

**** Define helper functions for specifying server configurations

#+begin_src emacs-lisp :noweb-ref config-prog
(defvar ceamx-eglot-server-configurations-alist '()
  "Alist of language server initialization options as accepted in `eglot-server-programs'.")
#+end_src

#+begin_src emacs-lisp :noweb-ref lib-prog
(require 'config-prog)

(defun ceamx-eglot-server-default-settings (name)
  "Return the custom initialization options for the NAME language server."
  (alist-get name ceamx-eglot-server-configurations-alist nil nil #'string=))

(defun ceamx-eglot-server-contact (name &optional program &rest args)
  "Return a contact specification for the language server NAME.
NAME is a string of the \"<lang>-<program>\" format for naming
language servers in Ceamx.  This format is based on the format
commonly used by `lsp-mode'.

PROGRAM and ARGS are as in `eglot-server-programs', which see.

Unless PROGRAM is provided, the program name used in
`eglot-server-programs' will be the value of NAME."
  (let ((options (ceamx-eglot-server-default-settings name))
        (program (or program (string-trim-left name "[[:alpha:]]+-"))))
    ;; The use of `append' here is significant because it will filter out a nil
    ;; value for `options'.
    (append (ensure-list program)
            args
            (when options (list :initializationOptions options)))))
#+end_src

**** Declare some Eglot buffers as popup windows

#+begin_src emacs-lisp
(after! (eglot popper)
  (defvar popper-reference-buffers)
  (add-to-list 'popper-reference-buffers "^\\*eglot-help"))
#+end_src

**** Configure ~flycheck-eglot~ integration

#+begin_src emacs-lisp
(package! flycheck-eglot
  (add-hook 'eglot-managed-mode-hook #'flycheck-eglot-mode))
#+end_src

**** Add workspace symbols as Consult datasource with ~consult-eglot~

<https://github.com/mohkale/consult-eglot>

#+begin_src emacs-lisp
(package! consult-eglot
  (defalias 'ceamx/list-workspace-symbols #'consult-eglot-symbols))
#+end_src

**** Keybindings

#+begin_src emacs-lisp
(keymap-global-set "C-c l a" '("action.." . eglot-code-actions))
(keymap-global-set "C-c l r" '("rename..." . eglot-rename))
(keymap-global-set "C-c l o" #'consult-eglot-symbols)

(after! eglot
  ;; Override the default binding for `xref-find-apropos'.
  (keymap-set eglot-mode-map "C-M-." #'consult-eglot-symbols))

(after! lsp-mode
    (keymap-global-set "C-c l o" #'consult-lsp-symbols)
    ;; Override the default binding for `xref-find-apropos'.
    (keymap-set lsp-mode-map "C-M-." #'consult-lsp-symbols))
#+end_src

*** TODO Debugging with ~dap-mode~ and the Debug Adapter Protocol (DAP)
:PROPERTIES:
:header-args: :noweb-ref nil
:END:

- website :: <https://github.com/emacs-lsp/dap-mode>
- spec :: <https://microsoft.github.io/debug-adapter-protocol/>

  This might be dependent on LSP-Mode?

#+begin_src emacs-lisp
(package! dap-mode
  (dap-auto-configure-mode))
#+end_src


** Data Formats (JSON, TOML, YAML, XML, CSV...)
:PROPERTIES:
:ID:       137d1e04-d0dc-4512-a450-fbafeef65804
:header-args: :noweb-ref init-lang-data
:END:

*** Requirements

#+begin_src emacs-lisp
(require 'ceamx-lib)
#+end_src

*** JSON [builtin]

**** Navigate arborescent JSON structures with ~json-navigator~

+ website :: <https://github.com/DamienCassou/json-navigator>

#+begin_src emacs-lisp
;; TODO: add bindings

(package! json-navigator)
#+end_src

*** TODO TOML

**** LSP-Mode: Use the correct cache base directory

#+begin_src emacs-lisp
(when (featurep 'lsp-toml)
  (setopt lsp-toml-cache-path (file-name-as-directory
                               (concat ceamx-lsp-mode-cache-dir "server/toml"))))
#+end_src

*** YAML

**** Install the =yaml-mode= package

#+begin_src emacs-lisp
(package! yaml-mode)
#+end_src

**** Install and configure ~yaml-pro~ for structural editing

#+begin_src emacs-lisp
(package! yaml-pro
  (add-hook 'yaml-mode-hook #'yaml-pro-mode)
  (add-hook 'yaml-ts-mode-hook #'yaml-pro-mode)

  ;; The package does not expose `yaml-pro-ts-mode' until `yaml-pro-mode' is
  ;; activated.  I consider this less-than-ideal behavior and should probably
  ;; file a bug report.
  (when (featurep 'treesit)
    (add-hook 'yaml-pro-mode-hook (lambda () (yaml-pro-ts-mode)))))
#+end_src


***** TODO Configure repeat map

<https://github.com/zkry/yaml-pro?tab=readme-ov-file#easy-movement-with-repeat-map>

**** Start the LSP server

Install the =yaml-language-server= from Nixpkgs first.

#+begin_src emacs-lisp
(when (eq 'lsp ceamx-lsp-client)
  (after! (yaml-mode)
    (add-hook 'yaml-mode-hook #'lsp-deferred)
    (add-hook 'yaml-ts-mode-hook #'lsp-deferred)))
#+end_src

**** Add support for YAML Schema validation

#+begin_src emacs-lisp
(when (eq 'lsp ceamx-lsp-client)
  (setopt lsp-yaml-schemas nil)

  ;; Keep this cached file with all of the other LSP server caches.
  (setopt lsp-yaml-schema-store-local-db
          (file-name-concat ceamx-lsp-mode-cache-dir "server/yaml/lsp-yaml-schemas.json"))

  ;; Download the YAML Schema Store database if not present.
  ;; FIXME: handle periodic updates of cached data
  (after! lsp-yaml
    (defer! 2
      (unless (file-exists-p lsp-yaml-schema-store-local-db)
        (lsp-yaml-download-schema-store-db)))))
#+end_src

*** XML [builtin]

#+begin_src emacs-lisp
(use-feature! nxml-mode
  :mode "\\.p\\(?:list\\|om\\)\\'"      ; plist, pom
  :mode "\\.xs\\(?:d\\|lt\\)\\'"        ; xslt, xsd
  :mode "\\.rss\\'"

  :config
  (setq nxml-slash-auto-complete-flag t)
  (setq nxml-auto-insert-xml-declaration-flag t))
#+end_src

*** CSV + TSV

#+begin_src emacs-lisp
(package! csv-mode)

(after! csv-mode
  (define-keymap :keymap csv-mode-map
    "a" #'csv-align-fields
    "u" #'csv-unalign-fields
    "s" #'csv-sort-fields
    "S" #'csv-sort-numeric-fields
    "k" #'csv-kill-fields
    "t" #'csv-transpose))
#+end_src

** HTML

#+begin_src emacs-lisp :tangle lisp/init-lang-html.el
;;; init-lang-html.el ---HTML and templating engine support  -*- lexical-binding: t; -*-

;; Copyright (C) 2023-2024  Chris Montgomery

;; Author: Chris Montgomery <chmont@proton.me>
;; Keywords:

;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.

;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.

;; You should have received a copy of the GNU General Public License
;; along with this program.  If not, see <https://www.gnu.org/licenses/>.

;;; Commentary:

;; Configuration for ~web-mode~ and related.

;; <https://web-mode.org/>

;;; Code:

(use-package web-mode
  :commands (web-mode)

  :init
  ;; TODO: refactor
  (add-to-list 'auto-mode-alist '("\\.html?\\'" . web-mode))
  (add-to-list 'auto-mode-alist '("\\.tpl\\.php\\'" . web-mode))
  (add-to-list 'auto-mode-alist '("\\.phtml\\'" . web-mode))
  (add-to-list 'auto-mode-alist '("\\.[agj]sp\\'" . web-mode))
  (add-to-list 'auto-mode-alist '("\\.as[cp]x\\'" . web-mode))
  (add-to-list 'auto-mode-alist '("\\.erb\\'" . web-mode))
  (add-to-list 'auto-mode-alist '("\\.mustache\\'" . web-mode))
  (add-to-list 'auto-mode-alist '("\\.djhtml\\'" . web-mode))

  :config
  (setopt web-mode-engines-alist
          '(("php" . "\\.phtml\\'")
            ("blade" . "\\.blade\\.")))

  ;; TODO: revisit this...
  ;; NOTE: This method of setting customizations is unusual,
  ;;       but recommended by `web-mode' documentation.
  (defun +web-mode--customization-hook ()
    "Customization hook for `web-mode'"
    (setopt web-mode-markup-indent-offset 2)
    (setopt web-mode-css-indent-offset 2)
    (setopt web-mode-code-indent-offset 2)
    (setopt web-mode-enable-auto-pairing t)
    (setopt web-mode-enable-css-colorization t)
    (setopt web-mode-enable-block-face t)
    (setopt web-mode-enable-part-face t))
  (add-hook 'web-mode-hook '+web-mode--customization-hook)

  ;; NOTE: The following customizations should NOT be set
  ;;       in `+web-mode--customization-hook'.
  (setopt web-mode-enable-current-element-highlight t))

(provide 'init-lang-html)
;;; init-lang-html.el ends here
#+end_src

** JavaScript

#+begin_src emacs-lisp :tangle lisp/init-lang-js.el
;;; init-lang-js.el --- JavaScript/TypeScript language support improvements  -*- lexical-binding: t; -*-

;; Copyright (C) 2023-2024  Chris Montgomery

;; Author: Chris Montgomery <chmont@proton.me>
;; Keywords: local, languages

;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.

;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.

;; You should have received a copy of the GNU General Public License
;; along with this program.  If not, see <https://www.gnu.org/licenses/>.

;;; Commentary:

;; Emacs supports JavaScript and TypeScript out of the box, however it needs some help.

;; TODO: try <https://github.com/llemaitre19/jtsx> but it's not in melpa or nixpkgs yet

;;; Code:

(require 'ceamx-lib)

(defun ceamx-init-javascript-modes ()
  (setopt js-indent-level 2)

  (after! lsp-mode
    (lsp-deferred)
    (lsp-lens-mode)
    (dolist (hook '(lsp-format-buffer
                     lsp-organize-imports))
      (add-hook 'before-save-hook hook nil t))))

;; TODO: must happen before `treesit-auto' so it can override
;; (add-to-list 'auto-mode-alist '("\\.js\\'"     . js2-mode))
;; (add-to-list 'auto-mode-alist '("\\.[cm]js\\'" . js2-mode))
;; (add-to-list 'auto-mode-alist '("\\.pac\\'"    . js2-mode))
;; (add-to-list 'interpreter-mode-alist '("node"  . js2-mode))

(use-feature! typescript-ts-mode
  :init
  (add-hook 'typescript-ts-base-mode #'ceamx-init-javascript-modes))

(provide 'init-lang-js)
;;; init-lang-js.el ends here
#+end_src

** Lua

#+begin_src emacs-lisp :tangle lisp/init-lang-lua.el
;;; init-lang-lua.el --- Lua language support        -*- lexical-binding: t; -*-

;; Copyright (C) 2023-2024  Chris Montgomery

;; Author: Chris Montgomery <chmont@proton.me>
;; Keywords: languages, local

;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.

;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.

;; You should have received a copy of the GNU General Public License
;; along with this program.  If not, see <https://www.gnu.org/licenses/>.

;;; Commentary:

;; Lua language support.

;;;; Sources:

;; - <https://github.com/purcell/emacs.d/blob/28194a035ca9a259030ba7ef58089561078c4893/lisp/init-lua.el>

;;; Code:

(require 'ceamx-lib)

(use-package lua-mode
  :config
  (setq-default lua-indent-level 2))

(provide 'init-lang-lua)
;;; init-lang-lua.el ends here
#+end_src

** Markdown


*** Customization

#+begin_src emacs-lisp :tangle lisp/init-lang-markdown.el
;;; init-lang-markdown.el --- Markdown support customizations  -*- lexical-binding: t; -*-

;; Copyright (C) 2023-2024  Chris Montgomery

;; Author: Chris Montgomery <chmont@proton.me>
;; Keywords: local, languages

;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.

;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.

;; You should have received a copy of the GNU General Public License
;; along with this program.  If not, see <https://www.gnu.org/licenses/>.

;;; Commentary:

;;; Code:

;;; Requirements

(require 'config-env)

(require 'ceamx-lib)

;;; Install ~markdown-mode~

;; <https://github.com/jrblevin/markdown-mode>

(package! markdown-mode
  (setopt markdown-enable-wiki-links t)
  (setopt markdown-italic-underscore t)
  (setopt markdown-asymmetric-header t)
  (setopt markdown-gfm-additional-languages '("sh"))
  (setopt markdown-make-gfm-checkboxes-buttons t)
  (setopt markdown-fontify-whole-heading-line t)

  ;; HACK Due to jrblevin/markdown-mode#578, invoking `imenu' throws a
  ;;      'wrong-type-argument consp nil' error if you use native-comp.
  ;;      <jrblevin/markdown-mode#578>
  (setopt markdown-nested-imenu-heading-index (not (ignore-errors (native-comp-available-p))))

  ;; This is set to `nil' by default, which causes a wrong-type-arg error
  ;; when you use `markdown-open'. These are more sensible defaults.
  (setopt markdown-open-command (cond
                                 (+sys-mac-p "open")
                                 (+sys-linux-p "xdg-open")))

  (with-eval-after-load 'org-src
    (add-to-list 'org-src-lang-modes '("md" . markdown))))

(with-eval-after-load 'markdown-mode
  (defvar markdown-mode-map)
  (declare-function markdown-match-generic-metadata "markdown-mode")
  (declare-function markdown-insert-link "markdown-mode")
  (declare-function markdown-insert-blockquote "markdown-mode")

  (define-keymap :keymap markdown-mode-map
    "C-c i l" #'markdown-insert-link
    "C-c i q" #'markdown-insert-blockquote)

  ;; <jrblevin/markdown-mode#328 (comment)>
  ;; <https://github.com/radian-software/radian/blob/b2fac3a615186f77de0bdc7e4f06e9aa46c222bb/emacs/radian.el#L3199-L3206>.
  (def-advice! +markdown-disable-front-matter-fontification-a (&rest _)
    :override #'markdown-match-generic-metadata
    "Prevent fontification of YAML metadata blocks in `markdown-mode'.
This prevents a mis-feature wherein if the first line of a
Markdown document has a colon in it, then it's distractingly and
usually wrongly fontified as a metadata block."
    (ignore (goto-char (point-max)))))

(provide 'init-lang-markdown)
;;; init-lang-markdown.el ends here
#+end_src


*** Library

#+begin_src emacs-lisp :tangle lisp/lib-lang-markdown.el
;;; lib-lang-markdown.el --- Markdown helpers        -*- lexical-binding: t; -*-

;; Copyright (C) 2023-2024  Chris Montgomery

;; Author: Chris Montgomery <chmont@proton.me>
;; Keywords: languages, local

;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.

;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.

;; You should have received a copy of the GNU General Public License
;; along with this program.  If not, see <https://www.gnu.org/licenses/>.

;;; Commentary:

;;

;;; Code:

;;;###autoload
(defun ceamx-markdown-compile (beg end output-buffer)
  "Compile Markdown with pandoc if available.
Returns process exit code."
  (when (executable-find "pandoc")
    (call-process-region beg end "pandoc" nil output-buffer nil
      "-f" "markdown"
      "-t" "html"
      "--highlight-style=pygments")))

(provide 'lib-lang-markdown)
;;; lib-lang-markdown.el ends here
#+end_src

** Nix :lang:
:PROPERTIES:
:header-args: :noweb-ref init-lang-nix
:END:

*** Requirements

#+begin_src emacs-lisp
(require 'ceamx-lib)

(require 'config-prog)

(require 'lib-help)
#+end_src

*** Install and configure ~nix-mode~ 📦

<https://github.com/NixOS/nix-mode>

NOTE: ~nix-mode~ should not be loaded when using ~nix-ts-mode~.

#+begin_src emacs-lisp
(package! nix-mode
  (when (eq 'eglot ceamx-lsp-client)
    (add-hook 'nix-mode-hook #'eglot-ensure))
  (when (eq 'lsp-mode ceamx-lsp-client)
    (add-hook 'nix-mode-hook #'lsp-deferred)))
#+end_src

*** Install and configure ~nix-ts-mode~ 📦

<https://github.com/remi-gelinas/nix-ts-mode>

#+begin_src emacs-lisp
(package! nix-ts-mode
  (when (eq 'eglot ceamx-lsp-client)
    (add-hook 'nix-ts-mode-hook #'eglot-ensure))
  (when (eq 'lsp-mode ceamx-lsp-client)
    (add-hook 'nix-ts-mode-hook #'lsp-deferred)))
#+end_src

*** Configure formatters :formatting:
**** Set the official formatter (=nixfmt=) as the default formatter

#+begin_src emacs-lisp
(after! reformatter
  (reformatter-define nixfmt-format
    :group 'ceamx
    :program "nixfmt")

  (add-hook 'nix-mode-hook #'nixfmt-format-on-save-mode)
  (add-hook 'nix-ts-mode-hook #'nixfmt-format-on-save-mode))
#+end_src

#+begin_src emacs-lisp
(with-eval-after-load 'apheleia
  (add-to-list 'safe-local-variable-values '(apheleia-formatter . nixfmt))
  (add-to-list 'apheleia-mode-alist '(nix-mode . nixfmt))
  (add-to-list 'apheleia-mode-alist '(nix-ts-mode . nixfmt)))
#+end_src

**** Register =alejandra= as an additional formatter

#+begin_src emacs-lisp
(after! reformatter
  (reformatter-define alejandra-format
    :group 'ceamx
    :program "alejandra"))
#+end_src

#+begin_src emacs-lisp
(with-eval-after-load 'apheleia
  (add-to-list 'safe-local-variable-values '(apheleia-formatter . alejandra))
  (add-to-list 'apheleia-formatters '(alejandra "alejandra")))
#+end_src

*** Configure Nix language servers :lsp:

#+begin_src emacs-lisp :noweb-ref config-prog
;; TODO: defcustom
(defvar ceamx-lsp-server-nix-lang "nix-nil")

(defvar ceamx-lsp-nix-nixd-default-config
  `(:nixpkgs (:expr "import (builtins.getFlake \"/etc/nix/inputs/nixpkgs\") { } ")
    :formatting (:command ["nixfmt"])
    :options (:nixos (:expr ,(format "import (builtins.getFlake \"%s\").%s.\"%s\".options"
                              "/etc/nixos"
                              "nixosConfigurations"
                              (system-name)))
              :home-manager (:expr ,(format "import (builtins.getFlake \"%s\").%s.\"%s@%s\".options"
                                     "/etc/nixos"
                                     "homeConfigurations"
                                     (user-login-name)
                                     (system-name))))))
#+end_src

#+begin_src emacs-lisp
(require 'config-prog)
(require 'lib-prog)

(add-to-list 'ceamx-eglot-server-configurations-alist '("nix-nil" . nil))
(add-to-list 'ceamx-eglot-server-configurations-alist
             (cons "nix-nixd" ceamx-lsp-nix-nixd-default-config))

(with-eval-after-load 'eglot
  (defvar eglot-server-programs)

  (add-to-list 'eglot-server-programs
               (cons '(nix-mode nix-ts-mode)
                     (ceamx-eglot-server-contact ceamx-lsp-server-nix-lang))))
#+end_src

#+begin_src emacs-lisp
(after! lsp-nix
  (setopt lsp-nix-nil-formatter nil)

  (when (string= "nix-nixd" ceamx-lsp-server-nix-lang)
    (lsp-register-client
     (make-lsp-client :new-connection (lsp-stdio-connection "nixd")
                      :major-modes '(nix-mode nix-ts-mode)
                      :priority 0
                      :server-id 'nixd))))
#+end_src

*** Install ~devdocs~ Nix docset :docs:

#+begin_src emacs-lisp
(def-hook! +devdocs-install-nix-docs ()
  '(nix-mode-hook nix-ts-mode-hook)
  "Install `devdocs' documents for the Nix language."
  (+devdocs-maybe-install "nix"))
#+end_src

*** Keybindings :keybinds:

#+begin_src emacs-lisp
(require 'config-keys)

(with-eval-after-load 'nix-mode
  (keymap-set nix-mode-map ceamx-keys-repl-toggle #'nix-repl)

  ;; FIXME: this is dumb, but the simplest way i found to avoid yelling without use-package
  (with-eval-after-load 'tempel
    ;; (eval-when-compile (require 'tempel))
    (tempel-key "C-c i t a" modargs nix-mode-map)))

(with-eval-after-load 'nix-ts-mode
  (keymap-set nix-ts-mode-map ceamx-keys-repl-toggle #'nix-repl))

(with-eval-after-load 'nix-repl
  (keymap-set nix-repl-mode-map ceamx-keys-repl-toggle #'quit-window))
#+end_src

** Nushell

#+begin_src emacs-lisp :tangle lisp/init-shell-nu.el
;;; init-shell-nu.el --- Nushell support             -*- lexical-binding: t; -*-

;; Copyright (C) 2023-2024  Chris Montgomery

;; Author: Chris Montgomery <chmont@proton.me>
;; Keywords: local

;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.

;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.

;; You should have received a copy of the GNU General Public License
;; along with this program.  If not, see <https://www.gnu.org/licenses/>.

;;; Commentary:

;; Nushell integration and language major-mode.

;; <https://github.com/mrkkrp/nushell-mode>

;;; Code:

;; TODO: move to ~init-lang-misc~

(use-package nushell-mode

  ;; FIXME: :elpaca (nushell-mode :host github :repo "mrkkrp/nushell-mode")
  )


(provide 'init-shell-nu)
;;; init-shell-nu.el ends here
#+end_src
*** TODO Rename file to =init-lang-nushell.el= or include in =init-lang-shell.el=

** PHP
:PROPERTIES:
:header-args: :noweb-ref init-lang-php
:END:

*** Requirements
:PROPERTIES:
:VISIBILITY: folded
:END:

#+begin_src emacs-lisp
(require 'ceamx-lib)
#+end_src

*** Feature Settings

#+begin_src emacs-lisp :noweb-ref config-prog
(defconst ceamx-lang-php-extension-regexp "\\.\\(php\\|phtml\\)\\'"
  "Pattern matching files with PHP syntax.")

(defcustom ceamx-lang-php-major-mode-provider 'php-ts-mode
  "Which of several major-modes to provide PHP language support."
  :group 'ceamx
  :type '(choice :tag "PHP language support provider" :value php-mode
          (const :tag "`php-ts-mode': Tree-Sitter-based" php-ts-mode)
          (const :tag "`phps-mode': claims of intelligence" phps-mode)
          (const :tag "`php-mode': regular-degular" php-mode)))
#+end_src

*** Ignore PHP-specific directories and files

#+begin_src emacs-lisp
(appendq! xref-ignored-files
          '("_ide_helper_models.php"
            "_ide_helper.php"))
#+end_src

*** Install a PHP major-mode... but which one?

**** DISABLED [[https://github.com/emacs-php/php-mode][emacs-php/php-mode]] 📦

#+begin_src emacs-lisp :noweb-ref nil
(package! php-mode
  ;; PHP is not a templating language and it never was.
  ;; `web-mode' does templates better.
  ;; Furthermore, by its nature, `php-ts-mode' does not support embedded HTML
  ;; syntax, so disabling this feature of `php-mode' adds consistency.
  (setopt php-mode-template-compatibility nil))
  #+end_src

**** [[https://github.com/emacs-php/php-ts-mode][emacs-php/php-ts-mode]] 📦

#+begin_src emacs-lisp
(package! (php-ts-mode :host github :repo "emacs-php/php-ts-mode")
  (when (eq 'php-ts-mode ceamx-lang-php-major-mode-provider)
    (add-to-list 'major-mode-remap-alist '(php-mode . php-ts-mode))
    (add-to-list 'major-mode-remap-alist '(php-mode-maybe . php-ts-mode))))
#+end_src

**** DISABLED [[https://github.com/cjohansson/emacs-phps-mode][cjohansson/phps-mode]] 📦

#+begin_src emacs-lisp :noweb-ref nil
(package! phps-mode
  (when (eq 'phps-mode ceamx-lang-php-major-mode-provider)
    (add-to-list 'auto-mode-alist `(,ceamx-lang-php-extension-regexp . phps-mode))))
#+end_src

*** [[https://github.com/Fuco1/neon-mode][Fuco1/neon-mode]]: major-mode for NEON, the PHP-centric franken-YAML DSL 📦

- Reference :: <https://ne-on.org/>

I have never ever encountered NEON in any context other than a =phpstan.neon= PHPStan
configuration file.

#+begin_src emacs-lisp
(package! neon-mode)
#+end_src

*** Debugging with Xdebug and ~dap-mode~

+ ref :: <https://emacs-lsp.github.io/dap-mode/page/configuration/#php>

Requires:

+ [[https://github.com/xdebug/vscode-php-debug?tab=readme-ov-file][GitHub - xdebug/vscode-php-debug: PHP Debug Adapter for Visual Studio Code]]

#+begin_src emacs-lisp
(after! (:or php-mode phps-mode php-ts-mode)
  (when (featurep 'dap)
    (require 'dap-php)))
#+end_src

*** DISABLED Integration with the proprietary Intelephense language server
:PROPERTIES:
:header-args: :noweb-ref nil
:END:

- docs :: <https://github.com/bmewburn/intelephense-docs>

#+begin_src emacs-lisp :noweb-ref config-feature-paths
(defvar ceamx-php-intelephense-global-storage-dir
  (file-name-as-directory (concat ceamx-xdg-cache-dir "intelephense")))

(defvar ceamx-eglot-php-iph-storage-dir
  (file-name-as-directory (concat ceamx-eglot-storage-dir "php-iph")))
#+end_src

#+begin_src emacs-lisp :noweb-ref config-prog
;; Available options listed in schema: https://github.com/bmewburn/intelephense-docs/blob/master/installation.md#configuration-options
(defvar ceamx-php-iph-default-stubs
  ["apache" "bcmath" "bz2" "calendar"
   "com_dotnet" "Core" "ctype" "curl" "date" "dba" "dom" "enchant"
   "exif" "fileinfo" "filter" "fpm" "ftp" "gd" "hash" "iconv" "imap" "interbase"
   "intl" "json" "ldap" "libxml" "mbstring" "mcrypt" "meta" "mssql" "mysqli"
   "oci8" "odbc" "openssl" "pcntl" "pcre" "PDO" "pdo_ibm" "pdo_mysql"
   "pdo_pgsql" "pdo_sqlite" "pgsql" "Phar" "posix" "pspell" "readline" "recode"
   "Reflection" "regex" "session" "shmop" "SimpleXML" "snmp" "soap" "sockets"
   "sodium" "SPL" "sqlite3" "standard" "superglobals" "sybase" "sysvmsg"
   "sysvsem" "sysvshm" "tidy" "tokenizer" "wddx" "xml" "xmlreader" "xmlrpc"
   "xmlwriter" "Zend OPcache" "zip" "zlib"])

(defvar ceamx-php-iph-extra-stubs [])

(defvar ceamx-php-iph-stubs
  (seq-concatenate 'vector ceamx-php-iph-default-stubs ceamx-php-iph-extra-stubs))
#+end_src

#+begin_src emacs-lisp
(dolist (dir (list ceamx-eglot-storage-dir
                   ceamx-php-intelephense-global-storage-dir
                   ceamx-eglot-php-iph-storage-dir))
  (unless (file-directory-p dir)
    (make-directory dir)))
#+end_src

#+begin_src emacs-lisp
;; <https://github.com/bmewburn/intelephense-docs/blob/master/installation.md#configuration-options>
;; TODO: read from JSON file
(defvar ceamx-eglot-php-iph-default-settings
  `(:globalStoragePath ,ceamx-php-intelephense-global-storage-dir
    :storagePath ,ceamx-eglot-php-iph-storage-dir
    ;; FIXME: data corruption non-utf-8 when loading from `auth-source-pass'
    ;;        currently i am defining this in site-config.el as a workaround
    :licenceKey ,ceamx-php-iph-license-key
    :intelephense (:completion (:maxItems 100)
                   :diagnostics (:embeddedLanguages nil)
                   :files (;; :exclude []
                           :maxSize ,(* (* 1024 1024) 3))
                   :format (:enable t
                            :braces "psr12")
                   :environment (:includePaths ["vendor"]
                                 :phpVersion "8.2.0")
                   :phpdoc (:returnVoid nil)
                   :stubs ,ceamx-php-iph-stubs
                   ))
  "Initial settings for the Intelephense language server.")

(add-to-list 'ceamx-eglot-server-configurations-alist
             (cons "php-iph" ceamx-eglot-php-iph-default-settings))
#+end_src

#+begin_src emacs-lisp
(after! eglot
  (add-to-list 'eglot-server-programs
    ;; TODO: add `phps-mode' (which is supported in eglot core btw)
    ;; TODO: consider contributing documentation fix for `eglot-alternatives' to
    ;;       indicate that it can accept `:initializationOptions' (see source code)
    `((php-mode php-ts-mode) . ,(eglot-alternatives
                                 ;; FIXME: somehow include phpactor here, but
                                 ;; there's not really an easy way to simply
                                 ;; append to or otherwise access the existing entry
                                 (list
                                  (ceamx-eglot-server-contact "php-iph" "intelephense" "--stdio"))))))
#+end_src

**** DISABLED LSP Mode integration
:PROPERTIES:
:header-args: :noweb-ref nil
:END:

***** Keep Intelephense storage in its place

E wants to avoid Intelephense but just in case it is still used in some case,
its cache should stay out of the way.

#+begin_src emacs-lisp
(setopt lsp-intelephense-storage-path
  (file-name-as-directory (concat ceamx-lsp-mode-cache-dir "server/intelephense/cache")))
#+end_src

*** Checkers

**** [[https://github.com/emacs-php/phpstan.el][emacs-php/phpstan.el]]: Provide PHPStan checks

#+begin_src emacs-lisp
(package! flymake-phpstan
  (add-hook 'php-mode-hook #'flymake-phpstan-turn-on)
  ;; NOTE: I'm not positive that this is the right name.
  (after! flycheck
    (add-to-list 'flycheck-disabled-checkers 'phpstan)))
#+end_src

*** Formatters

#+begin_src emacs-lisp
(defun +reformatter--phpcbf-fmt-exit-code-success-p (exit-code)
    "Handle PHPCBF non-standard exit codes."
    (or (= 0 exit-code)
        (= 1 exit-code)))

(after! reformatter
  ;; FIXME: okay this hasn't worked either...
  (reformatter-define php-cs-fixer-fmt
    :program (format "%s/vendor/bin/php-cs-fixer" (getenv "PRJ_ROOT"))
    :args '("fix" "--diff" "--using-cache=no" "-q" "-"))

  ;; FIXME: phpcbf is really finicky and doesn't play nice with the usual
  ;; formatter standards.  the exit codes are nonsense.  and apparently the
  ;; `:exit-code-success-p' lambda is not a function?
  (reformatter-define phpcbf-fmt
    :program (format "%s/vendor/bin/phpcbf" (getenv "PRJ_ROOT"))
    :args (list "--stdin-path" input-file
                "-q"
                "-")
    :exit-code-success-p +reformatter--phpcbf-fmt-exit-code-success-p))
#+end_src

*** Projectile integration

#+begin_src emacs-lisp
(after! projectile
  (add-to-list 'projectile-globally-ignored-directories "vendor"))
#+end_src

*** Register Blade templates (=*.blade.php=) to open in ~web-mode~

#+begin_src emacs-lisp
(after! web-mode
  ;; This should override the default file extension association.
  (pushnew! web-mode-engines-alist '(("blade"  . "\\.blade\\."))))
#+end_src

*** TODO PHP enhancements from Doom

#+begin_example
;; TODO: from doom
  ;; (set-docsets! 'php-mode "PHP" "PHPUnit" "Laravel" "CakePHP" "CodeIgniter" "Doctrine_ORM")
  ;; (set-repl-handler! 'php-mode #'+php/open-repl)
  ;; (set-lookup-handlers! 'php-mode :documentation #'php-search-documentation)
  ;; (set-ligatures! 'php-mode
  ;;   ;; Functional
  ;;   :lambda "function()" :lambda "fn"
  ;;   :def "function"
  ;;   ;; Types
  ;;   :null "null"
  ;;   :true "true" :false "false"
  ;;   :int "int" :float "float"
  ;;   :str "string"
  ;;   :bool "list"
  ;;   ;; Flow
  ;;   :not "!"
  ;;   :and "&&" :and "and"
  ;;   :or "||" :or "or"
  ;;   :for "for"
  ;;   :return "return"
  ;;   :yield "use")
  #+end_example

** Shell Scripting

#+begin_src emacs-lisp :tangle lisp/init-lang-shell.el
;;; init-lang-shell.el --- Shell script language support  -*- lexical-binding: t; -*-

;; Copyright (C) 2024  Chris Montgomery

;; Author: Chris Montgomery <chmont@proton.me>
;; Keywords: local

;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.

;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.

;; You should have received a copy of the GNU General Public License
;; along with this program.  If not, see <https://www.gnu.org/licenses/>.

;;; Commentary:

;; NOTE: Make sure ~flycheck-mode~ is not enabled in shell script buffers, as
;; ~flymake~ will handle it just fine.

;;; Code:

(require 'ceamx-lib)

(use-feature! emacs
  :config
  ;; Make files executable if their first line has a shebang.
  (add-hook 'after-save-hook #'executable-make-buffer-file-executable-if-script-p))

(after! eglot
  (add-to-list 'eglot-server-programs '((sh-mode bash-ts-mode) . ("bash-language-server" "start"))))

(let ((lsp-fn (if (eq 'lsp-mode ceamx-lsp-client)
                  #'lsp-deferred
                #'eglot-ensure)))
  (add-hook 'sh-mode-hook lsp-fn)
  (add-hook 'bash-ts-mode-hook lsp-fn))

(use-feature! flymake
  :config
  (add-hook 'sh-mode-hook #'flymake-mode)
  (add-hook 'bash-ts-mode-hook #'flymake-mode))

(provide 'init-lang-shell)
;;; init-lang-shell.el ends here
#+end_src

** Miscellaneous Languages
:PROPERTIES:
:header-args: :tangle lisp/init-lang-misc.el
:END:

Configuration for miscellaneous languages that don't warrant
their own initialization files.

*** Customizations

**** File Header

#+begin_src emacs-lisp
;;; init-lang-misc.el --- Miscellaneous language support -*- lexical-binding: t -*-

;; Copyright (c) 2022-2024  Chris Montgomery <chmont@proton.me>

;; Author: Chris Montgomery <chmont@proton.me>
;; URL: https://git.sr.ht/~montchr/ceamx
;; Version: 0.1.0

;; This file is NOT part of GNU Emacs.

;; This file is free software: you can redistribute it and/or modify it
;; under the terms of the GNU General Public License as published by the
;; Free Software Foundation, either version 3 of the License, or (at
;; your option) any later version.
;;
;; This file is distributed in the hope that it will be useful, but
;; WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
;; General Public License for more details.
;;
;; You should have received a copy of the GNU General Public License
;; along with this file.  If not, see <http://www.gnu.org/licenses/>.

;;; Commentary:

;;; Code:

(require 'ceamx-lib)
#+end_src

**** Language support for Apache Web Server configuration files

#+begin_src emacs-lisp
(package! apache-mode)
#+end_src

**** Language support for the Just task runner configuration files

#+begin_src emacs-lisp
(package! just-mode)
#+end_src

**** Syntax highlighting for robots.txt files

#+begin_src emacs-lisp
(package! robots-txt-mode)
#+end_src

**** Language support for =vimrc= syntax

#+begin_src emacs-lisp
(package! vimrc-mode
  (add-to-list 'auto-mode-alist '("\\.(idea)?vim\\(rc\\)?\\'" . vimrc-mode)))
#+end_src

**** Footer

#+begin_src emacs-lisp
(provide 'init-lang-misc)
;;; init-lang-misc.el ends here
#+end_src


* Outlines
:PROPERTIES:
:header-args: :noweb-ref init-outline
:END:

** TODO Expand the existing repeat map for outline navigation

I think this needs to be /contracted/ a bit.  Some of these aren't even real commands.

#+begin_src emacs-lisp
(after! (repeat outline)
  (define-keymap :keymap outline-navigation-repeat-map
    "C-x" #'foldout-exit-fold
    "x" #'foldout-exit-fold
    "C-z" #'foldout-zoom-subtree
    "z" #'foldout-zoom-subtree
    "C-a" #'outline-show-all
    "a" #'outline-show-all
    "C-c" #'outline-hide-entry
    "c" #'outline-hide-entry
    "C-d" #'outline-hide-subtree
    "C-e" #'outline-show-entry
    "e" #'outline-show-entry
    "TAB" #'outline-show-children
    "C-k" #'outline-show-branches
    "k" #'outline-show-branches
    "C-l" #'outline-hide-leaves
    "l" #'outline-hide-leaves
    "RET" #'outline-insert-heading
    "C-o" #'outline-hide-other
    "o" #'outline-hide-other
    "C-q" #'outline-hide-sublevels
    "q" #'outline-hide-sublevels
    "C-s" #'outline-show-subtree
    "s" #'outline-show-subtree
    "C-t" #'outline-hide-body
    "t" #'outline-hide-body
    "@" #'outline-mark-subtree)

  (ceamx-repeatify-keymap 'outline-navigation-repeat-map))
#+end_src


** ~outli~: Enable simple comment-based outline features in many modes

<https://github.com/jdtsmith/outli>

#+begin_src emacs-lisp
;; NOTE: In `emacs-lisp-mode' buffers, `outli-mode' should be enabled *after*
;; `lispy-mode'. See the package configuration for `lispy'.

(package! (outli :host github :repo "jdtsmith/outli")

  (def-hook! +outli-mode-maybe-enable-h ()
    '(prog-mode-hook text-mode-hook)
    "Enable `outli-mode' conditionally, excluding some modes."
    (let ((exclude-modes '(emacs-lisp-mode))
          (excludep (lambda (excluded-mode)
                      (eq major-mode excluded-mode))))
      (unless (seq-some excludep exclude-modes)
        (outli-mode)))))

(with-eval-after-load 'outli
  (defvar outli-mode-map)
  (declare-function outline-next-heading "outline")
  (declare-function outline-previous-heading "outline")
  (declare-function outline-promote "outline")
  (declare-function outline-demote "outline")

  ;; FIXME: function definition is void -- from readme:
  ;; (advice-add 'load-theme :after #'outli-reset-all-faces)

  (define-keymap :keymap outli-mode-map
    "C-c C-n" #'outline-next-heading
    "C-c C-p" #'outline-previous-heading
    "C-c M-h" #'outline-promote
    "C-c M-l" #'outline-demote))
#+end_src


** TODO A transient menu for outline navigation

#+begin_src emacs-lisp
;; (after! (transient outline)
;;   (transient-define-prefix ceamx/outline-dispatch ()
;;     "Outline navigation transient menu."
;;     [["Navigate"
;;       ("u" "up" outline-up-heading)
;;       ("n" "next" outline-next-visible-heading)
;;       ("p" "prev" outline-previous-visible-heading)
;;       ("f" "forward" outline-forward-same-level)
;;       ("b" "backward" outline-backward-same-level)]]))

;; (after! (hydra outline)
;;   (defhydra ceamx/outline-hydra ( :color red)
;;     "
;; ^Navigate^            ^Subtree^        ^Metadata^
;; ^--------^----------  ^-------^-----  ^---------^--
;; _n_ext visible        _I_: drag up    _t_odo-state
;; _p_revious visible    _J_: promote    _d_eadline
;; _f_orward same level  _K_: drag down  _s_chedule
;; _b_ack same level     _L_: demote
;; _u_p level            _N_: narrow     _xp_: set property
;;                       _W_: widen
;; "))
#+end_src


* Writing
:PROPERTIES:
:header-args: :noweb-ref init-writing
:END:

#+begin_src emacs-lisp :noweb-ref ceamx-augmentation
(require 'init-writing)
#+end_src

#+begin_src emacs-lisp
;; Prevent excessive completion-spamming.
;; Without this, on Emacs 30.0, typing causes constant `corfu' errors.
;; <minad/corfu#457>
(setopt text-mode-ispell-word-completion nil)
#+end_src

** [[https://github.com/minad/jinx][minad/jinx]]: the enchanted spell checker :broken:

- Docs :: <https://github.com/minad/jinx/wiki>

Ideally =emacsPackages.jinx= from Nixpkgs would install the required =enchant=
package, as =jinx= depends on the latter.  Unfortunately, as of
<2024-05-24 Fri 15:21>, the autoloads from the Nixpkgs package are not set up
properly, and even after setting up autoloads manually below, dictionaries are
unavailable.  Apparently =aspell= dictionaries are used behind the scenes, but
they are not detected from within emacs.

Despite all that:

#+begin_src shell :noweb-ref nil
enchant-lsmod-2 -list-dicts
#+end_src

#+begin_src emacs-lisp
(autoload 'global-jinx-mode "jinx")
(autoload 'jinx-correct "jinx")
(autoload 'jinx-languages "jinx")

(add-hook 'ceamx-emacs-startup-hook #'global-jinx-mode)
#+end_src

#+begin_src emacs-lisp
(keymap-global-set "M-$" #'jinx-correct)
(keymap-global-set "C-M-$" #'jinx-languages)

(setopt jinx-languages "en")
#+end_src


* Notetaking

** Define locations for important directories and files

#+begin_src emacs-lisp :noweb-ref config-feature-paths
(defconst ceamx-notes-dir
  (file-name-as-directory (concat ceamx-home-dir "Documents/notes"))
  "Base directory for note storage.")

(defconst ceamx-agenda-dir
  (file-name-as-directory (concat ceamx-notes-dir "g2d")))

(defconst ceamx-dailies-dir
  (file-name-as-directory (concat ceamx-notes-dir "daily")))

(defconst ceamx-notes-default-dir
  (file-name-as-directory (concat ceamx-notes-dir "default")))

(defconst ceamx-journal-dir
  (file-name-as-directory (concat ceamx-notes-dir "journal")))

(defconst ceamx-work-notes-dir
  (file-name-as-directory (concat ceamx-notes-dir "work")))
#+end_src


#+begin_src emacs-lisp :tangle lisp/init-notes.el
;;; init-notes.el --- Configuration for notetaking   -*- lexical-binding: t; -*-

;; Copyright (C) 2023-2024  Chris Montgomery

;; Author: Chris Montgomery <chmont@proton.me>
;; Keywords:

;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.

;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.

;; You should have received a copy of the GNU General Public License
;; along with this program.  If not, see <https://www.gnu.org/licenses/>.

;;; Commentary:

;; Generic notetaking configurations.

;;; Code:

(require 'f)

(require 'ceamx-paths)

;; Create all required directories if not already present.
(dolist (dir (list ceamx-notes-dir ceamx-journal-dir ceamx-agenda-dir))
  (f-mkdir-full-path dir))

;;
;;; consult-notes <https://github.com/mclear-tools/consult-notes>

(use-package consult-notes
  ;; FIXME: :elpaca (:host github :repo "mclear-tools/consult-notes")
  :commands (consult-notes
              consult-notes-search-in-all-notes))

;; via <https://github.com/mclear-tools/consult-notes#embark-support>
;; (after! (consult-notes embark)
;; 	(defun ceamx/consult-notes-embark-action (cand)
;;     "Do something with CAND."
;;     (interactive "fNote: ")
;;     ;; FIXME: needs function
;;     ;;
;;     ;; > Note that Embark will run on the CAND at point, which will often return
;;     ;; > either a file name, or a file name plus other annotations, depending on
;;     ;; > what your sources are. So you’ll have to write a function to manipulate
;;     ;; > CAND to give you a viable path to the file or a directory containing
;;     ;; > the file.
;;     (my-function))

;;   (defvar-keymap consult-notes-map
;;     :doc "Keymap for Embark notes actions."
;;     :parent embark-file-map
;;     "m" #'ceamx/consult-notes-embark-action)

;;   (add-to-list 'embark-keymap-alist `(,consult-notes-category . consult-notes-map))

;;   ;; Make `embark-export' use dired for notes.
;;   (setf (alist-get consult-notes-category embark-exporters-alist) #'embark-export-dired))


(provide 'init-notes)
;;; init-notes.el ends here
#+end_src



** Denote

#+begin_src emacs-lisp :tangle lisp/init-notes-denote.el
;;; init-notes-denote.el --- Denote configuration          -*- lexical-binding: t; -*-

;; Copyright (C) 2023-2024  Chris Montgomery

;; Author: Chris Montgomery <chmont@proton.me>

;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.

;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.

;; You should have received a copy of the GNU General Public License
;; along with this program.  If not, see <https://www.gnu.org/licenses/>.

;;; Commentary:

;; <https://protesilaos.com/emacs/denote>

;;; Code:

(require 'ceamx-paths)

(use-package denote
  :config
  (setopt denote-directory ceamx-notes-default-dir)
  (setopt denote-known-keywords '("emacs"))
  (setopt denote-infer-keywords t)
  (setopt denote-sort-keywords t)
  (setopt denote-prompts '(title keywords))
  ;; TODO: exclude ".archive"
  ;; (setopt denote-excluded-directories-regexp nil)
  (setopt denote-excluded-keywords-regexp nil)
  ;; Pick dates, where relevant, with Org's advanced interface:
  (setopt denote-date-prompt-use-org-read-date t)
  (setopt denote-allow-multi-word-keywords t)
  (setopt denote-date-format nil)       ; read doc string
  ;; By default, we do not show the context of links.  We just display
  ;; file names.  This provides a more informative view.
  ;; Also see ~denote-link-backlinks-display-buffer-action~ which is a bit
  ;; advanced.
  (setopt denote-backlinks-show-context t)
  ;; If you use Markdown or plain text files (Org renders links as buttons
  ;; right away)
  (add-hook 'find-file-hook #'denote-link-buttonize-buffer)
  ;; We use different ways to specify a path for demo purposes.
  (setopt denote-dired-directories
    (list denote-directory
      (thread-last denote-directory (expand-file-name "attachments"))))
  (add-hook 'dired-mode-hook #'denote-dired-mode)
  ;; Alternatively:
  ;; (add-hook 'dired-mode-hook #'denote-dired-mode-in-directories)

  ;; Here is a custom, user-level command from one of the examples we
  ;; showed in this manual.  We define it here and add it to a key binding
  ;; below.
  (defun my-denote-journal ()
    "Create an entry tagged 'journal' with the date as its title.
If a journal for the current day exists, visit it.  If multiple
entries exist, prompt with completion for a choice between them.
Else create a new file."
    (interactive)
    (let* ((today (format-time-string "%A %e %B %Y"))
           (string (denote-sluggify today))
           (files (denote-directory-files-matching-regexp string)))
      (cond
       ((> (length files) 1)
        (find-file (completing-read "Select file: " files nil :require-match)))
       (files
        (find-file (car files)))
       (t
        (denote
         today
         '("journal"))))))

  ;; Key bindings specifically for Dired.
  (define-keymap :keymap dired-mode-map
    ;; FIXME: avoid C-c C-* for stuff like this
    "C-c C-d C-i" #'denote-link-dired-marked-notes
    "C-c C-d C-r" #'denote-dired-rename-marked-files
    "C-c C-d C-R" #'denote-dired-rename-marked-files-using-front-matter)

  (with-eval-after-load 'org-capture
    (setopt denote-org-capture-specifiers "%l\n%i\n%?")
    (add-to-list 'org-capture-templates
                 '("n" "New note (with denote.el)" plain
                   (file denote-last-path)
                   #'denote-org-capture
                   :no-save t
                   :immediate-finish nil
                   :kill-buffer t
                   :jump-to-captured t)))

  ;; Also check the commands ~denote-link-after-creating~,
  ;; ~denote-link-or-create~.  You may want to bind them to keys as well.

  ;; If you want to have Denote commands available via a right click
  ;; context menu, use the following and then enable
  ;; ~context-menu-mode~.
  (add-hook 'context-menu-functions #'denote-context-menu))


;;
;;; denote-menu <https://github.com/namilus/denote-menu>
;;

(use-package denote-menu
  :after (denote)
  :commands (list-denotes
              denote-menu-clear-filters
              denote-menu-filter
              denote-menu-filter-by-keyword
              denote-menu-filter-out-keyword
              denote-menu-export-to-dired)
  :config
  (keymap-global-set "C-c z" #'list-denotes)

  ;; TODO: `define-keymap'
  (keymap-set denote-menu-mode-map "c" #'denote-menu-clear-filters)
  (keymap-set denote-menu-mode-map "/ r" #'denote-menu-filter)
  (keymap-set denote-menu-mode-map "/ k" #'denote-menu-filter-by-keyword)
  (keymap-set denote-menu-mode-map "/ o" #'denote-menu-filter-out-keyword)
  (keymap-set denote-menu-mode-map "e" #'denote-menu-export-to-dired))

;;
;;; Integrations
;;

;; <https://github.com/mclear-tools/consult-notes#denote>
(after! (denote consult-notes)
  (consult-notes-denote-mode)
  ;; Search only for text files in Denote dir.
  (setopt consult-notes-denote-files-function
        (function denote-directory-text-only-files)))



(provide 'init-notes-denote)
;;; init-notes-denote.el ends here
#+end_src


* Org-Mode
:PROPERTIES:
:header-args: :noweb-ref init-org
:END:

** Requirements

#+BEGIN_SRC emacs-lisp
(require 'ceamx-keymaps)
(require 'ceamx-paths)
(require 'ceamx-lib)
#+END_SRC

** Set up essential files & directories :paths:

#+begin_src emacs-lisp :noweb-ref config-feature-paths
(defconst ceamx-default-agenda-files
  (file-expand-wildcards (file-name-concat ceamx-agenda-dir "*.org"))
  "List of absolute paths of all files that should be included in the agenda.")

(defconst ceamx-default-todo-file
  (expand-file-name "todo.org" ceamx-agenda-dir)
  "Absolute path to default file for active G2D.")

(defconst ceamx-default-capture-file
  (expand-file-name "inbox.org" ceamx-agenda-dir)
  "Absolute path to default inbox file for new G2D waiting to be processed.")
#+end_src

These must be set before loading Org-Mode or any of its sub-features are used.

Most notes will be stored in ~ceamx-notes-dir~.

The value of ~org-directory~ will be used as a default destination for new
notes, especially as they relate to tasks and agendas.  For that reason, use
the ~ceamx-agenda-dir~.

Because these directories are managed by Syncthing, creating them automatically
is not a great idea if they do not already exist.  A better workaround to the
issue of Org-Mode failing to load might be setting the default target to a
subdirectory of ~user-emacs-directory~.

Why?  Because you do not want to end up with two Syncthing entries with the same
intended target path but with different IDs.  If Syncthing is not set up yet,
then any directory created via the Emacs configuration will probably result in a
conflict when Syncthing tries to take control of these paths.

#+BEGIN_SRC emacs-lisp
(defvar org-directory ceamx-agenda-dir)

;; TODO: I would prefer to check for the directory's existence explicitly --
;; this feels strange at the top-level.
(f-mkdir-full-path org-directory)

(setopt org-agenda-files ceamx-default-agenda-files)
#+END_SRC

** Install common libraries
*** ~doct~: ~org-capture~ template definer :package:

<https://github.com/progfolio/doct>

#+BEGIN_SRC emacs-lisp
(package! doct
  (require 'doct))

(with-eval-after-load 'doct
  (declare-function doct "doct")

  (setopt org-capture-templates
          (doct `(("Inbox"
                   :keys "t"
                   ;; TODO: make sure this icon spec is up to date with 2024
                   :icon ("checklist" :set "octicon" :color "green")
                   ;; TODO: should this be evaled/expanded?
                   :file ceamx-default-capture-file
                   :prepend t
                   :headline "Inbox"
                   :type entry
                   :template ("* TODO %?"
                              "%i %a"))))))
#+END_SRC

*** ~org-ql~ 📦

<https://github.com/alphapapa/org-ql>

#+BEGIN_SRC emacs-lisp
(package! org-ql)
#+END_SRC

*** ~org-contrib~: community library 📦

<https://orgmode.org/worg/org-contrib/>

#+begin_src emacs-lisp
(package! org-contrib)
#+end_src

** Define structure templates :snippets:

#+begin_src emacs-lisp
(setopt org-structure-template-alist
        '(("s" . "src")
          ("e" . "src emacs-lisp")
          ("E" . "src emacs-lisp :results value code :lexical t")
          ("t" . "src emacs-lisp :tangle FILENAME")
          ("T" . "src emacs-lisp :tangle FILENAME :mkdirp yes")
          ("x" . "example")
          ("X" . "export")
          ("q" . "quote")))
#+end_src

** Headings / List Items

Prevent TAB behavior oddities at the end of headlines.

When nil, pressing TAB at the end of a headline whose content is folded will act
on the folded (non-visible) area instead of the headline, which may cause
unexpected changes to the content (depending on the setting of
~org-catch-invisible-edits~.

#+BEGIN_SRC emacs-lisp
;; Instead of forcing this always, use the function
;; `org-insert-heading-respect-content' directly, bound to [C-<return>].
(setopt org-insert-heading-respect-content nil)

(setopt org-M-RET-may-split-line nil)
(setopt org-blank-before-new-entry '((heading . nil) (plain-list-item . nil)))
;; (setopt org-blank-before-new-entry '((heading . t) (plain-list-item . auto)))

(setopt org-id-link-to-org-use-id 'create-if-interactive-and-no-custom-id)
#+END_SRC

** Activate visual overhaul provided by ~org-modern~ 📦ui:

<https://github.com/minad/org-modern>

#+BEGIN_SRC emacs-lisp
(package! org-modern
  (add-hook 'org-mode-hook #'org-modern-mode)
  (add-hook 'org-agenda-finalize-hook #'org-modern-agenda))
#+END_SRC

** Text appearance :font:ui:

<https://github.com/minad/org-modern#configuration>

#+begin_src emacs-lisp
(add-hook 'org-mode-hook #'prettify-symbols-mode)

(setopt org-pretty-entities t
        org-pretty-entities-include-sub-superscripts nil)

(setopt org-hide-emphasis-markers t)
(setopt org-link-descriptive t)

(package! org-appear
  (add-hook 'org-mode-hook #'org-appear-mode))
#+end_src

** Folding and indentation

#+BEGIN_SRC emacs-lisp
(setopt org-cycle-emulate-tab t)
(setopt org-indent-indentation-per-level 2)
(setopt org-startup-folded 'content)

;; Avoid unnecessary indentation effects unless specified in file header.
(setopt org-startup-indented nil)

;; Ensure leading stars are replaced by spaces.
(setopt org-modern-hide-stars "   ")
;; (setopt org-modern-hide-stars nil)
(setopt org-modern-star nil)

(setopt org-ellipsis "…")
;; (setopt org-ellipsis " ⇢") ; prefix is nbsp
(after! org
  (set-face-attribute 'org-ellipsis nil :inherit 'default :box nil))
#+END_SRC

** Workflow and state settings

#+BEGIN_SRC emacs-lisp
(setopt org-log-done 'time)
(setopt org-todo-keywords
        '((sequence
           "TODO(t)"
           "INPRG(i@/!)"
           "BLOCKED(b@)"
           "HOLD(h@)"
           "PROJ(p)"
           "|"
           "DONE(d!)"
           "CANCELLED(x@/!)")))
#+END_SRC

** Display visual feedback after actions :feedback:

- Source :: <https://github.com/protesilaos/dotfiles/blob/4d4e82fc63dd74971a7bf7895e4e0e24c3d446da/emacs/.emacs.d/prot-emacs-modules/prot-emacs-org.el#L112-L115>

#+begin_src emacs-lisp
(after! (org pulsar)
  (dolist (hook '(org-agenda-after-show-hook org-follow-link-hook))
    (add-hook hook #'pulsar-recenter-center)
    (add-hook hook #'pulsar-reveal-entry)))
#+end_src

** Agenda

#+BEGIN_SRC emacs-lisp
(setopt org-agenda-tags-column 0)
(setopt org-agenda-block-separator ?─)
(setopt org-agenda-time-grid
        '((daily today require-timed)
          (800 1000 1200 1400 1600 1800 2000)
          " ┄┄┄┄┄ " "┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄"))
(setopt org-agenda-current-time-string
        "⭠ now ─────────────────────────────────────────────────")
#+END_SRC

*** ~org-super-agenda~

<https://github.com/alphapapa/org-super-agenda>

TODO: Configure

#+BEGIN_SRC emacs-lisp
(package! org-super-agenda
  ;; FIXME: probably can use autoloads instead
  (require 'org-super-agenda))
#+END_SRC

** Tags

#+BEGIN_SRC emacs-lisp
(setopt org-auto-align-tags nil)
(setopt org-tags-column 0)

(setopt org-fold-catch-invisible-edits 'show-and-error) ; default: smart
#+END_SRC

** Refiling and Archiving

#+BEGIN_SRC emacs-lisp
(setopt org-refile-targets `((,ceamx-default-todo-file . (:level . 2))
                             ;; (org-agenda-files . (:maxlevel . 1))
                             (,(locate-user-emacs-file "TODO.org") . (:level . 1))
                             (nil . (:maxlevel . 5))))

(setopt org-refile-use-outline-path 'file)
(setopt org-outline-path-complete-in-steps nil)
(setopt org-refile-allow-creating-parent-nodes 'confirm)
(setopt org-refile-use-cache nil)

;; TODO: move this setting elsewhere
(setopt org-reverse-note-order t)       ; prepend new notes
#+END_SRC

*** TODO Provide commands to archive DONE tasks

| Author | Sacha Chua                                                                                 |
| URL    | <https://pages.sachachua.com/.emacs.d/Sacha.html#quick-way-to-archive-all-done-from-inbox> |

I don't think this is working properly...

#+begin_src emacs-lisp :noweb-ref nil
(require 'ceamx-paths)

(defun ceamx/org-clean-up-inbox ()
  "Archive all DONE tasks and sort the remainder by TODO order."
  (interactive)
  (with-current-buffer (find-file ceamx-default-capture-file)
    (ceamx/org-archive-done-tasks 'file)
    (goto-char (point-min))
    (if (org-at-heading-p) (save-excursion (insert "\n")))
    (org-sort-entries nil ?p)
    (goto-char (point-min))
    (org-sort-entries nil ?o)
    (save-buffer)))

(defun ceamx/org-archive-done-tasks (&optional scope)
  "Archive finished or cancelled tasks.
       SCOPE can be 'file or 'tree."
  (interactive)
  (org-map-entries
   (lambda ()
     (org-archive-subtree)
     (setq org-map-continue-from (outline-previous-heading)))
   "TODO=\"DONE\"|TODO=\"CANCELLED\"" (or scope (if (org-before-first-heading-p) 'file 'tree))))
#+end_src

** Rich Media

#+BEGIN_SRC emacs-lisp
(setopt org-image-actual-width 300)
(setopt org-startup-with-inline-images t)
#+END_SRC

*** ~org-download~: support dragging-and-dropping images into Org buffers :media:network📦

<https://github.com/abo-abo/org-download>

#+BEGIN_SRC emacs-lisp
(package! org-download
  (require 'org-download)
  (add-hook 'dired-mode-hook #'org-download-enable))
#+END_SRC

** Enforce the correct ~tab-width~ to prevent errors :hack:formatting:

<https://github.com/doomemacs/doomemacs/commit/43870bf8318f6471c4ce5e14565c9f0a3fb6e368>

#+BEGIN_SRC emacs-lisp
(defun +org-mode--local-set-tab-width-h ()
  "Set the `tab-width' in `org-mode' buffers to 8 columns.
Any `tab-width' value other than 8 will result in an error.

This should be set as late as possible, after all other
`org-mode-hook' functions added by packages and
configurations.  Hence the use of `after-change-major-mode-hook',
which runs at the very end of major-mode activation.

Intended for use as a local hook function on
`after-change-major-mode-hook' as added within `org-mode-hook'."

  ;; This check is necessary to handle, for example, `org-edit-src-code', which
  ;; clones the `org-mode' buffer and changes its major-mode.
  (when (derived-mode-p 'org-mode)
    (setq tab-width 8)))

(def-hook! +org-mode-enforce-tab-width-h ()
  'org-mode-hook
  "Add a local hook to control `tab-width' on `after-change-major-mode-hook'."
  (add-hook 'after-change-major-mode-hook #'+org-mode--local-set-tab-width-h 0 t))
#+END_SRC

** ~org-web-tools~: view, capture, and archive webpages in org-mode 📦network:web:

#+BEGIN_SRC emacs-lisp
(package! org-web-tools
  (keymap-set ceamx-insert-map "l" #'org-web-tools-insert-link-for-url))
#+END_SRC

** ~org-sidebar~: provide a sidebar for Org buffers 📦

<https://github.com/alphapapa/org-sidebar>

#+BEGIN_SRC emacs-lisp
(package! org-sidebar)
#+END_SRC

** ~org-bookmark-heading~: Support heading bookmarks :bookmarks📦

#+begin_src emacs-lisp
(package! org-bookmark-heading
  (setopt org-bookmark-jump-indirect t)
  (after! org
    (require 'org-bookmark-heading)))
#+end_src

** Literate programming with Org-Babel

#+BEGIN_SRC emacs-lisp
;; Changing the indentation of source code is unhelpful and destructive.
(setopt org-edit-src-content-indentation 0)

(setopt org-edit-src-persistent-message nil)
(setopt org-src-fontify-natively t)
(setopt org-src-preserve-indentation t)
(setopt org-src-tab-acts-natively t)
;; TODO: current window when narrow/short frame, but otherwise reorganize-frame is good
;; (setopt org-src-window-setup 'current-window)
(setopt org-src-window-setup 'other-window)
#+END_SRC

*** Ensure common languages are loaded

#+begin_src emacs-lisp
(setopt org-babel-load-languages '((emacs-lisp . t)
                                   (shell . t)
                                   (sql . t)))
#+end_src

*** Load other supported languages on-demand during execution

+ source :: <https://github.com/Icy-Thought/emacs.d/blob/e9c75d87bf61c456b26332787cde27bdfc188830/config.org#org-babel-language-on-demand>

#+begin_src emacs-lisp
(def-advice! +org-babel-load-language-on-demand-a (orig-fun &rest args)
  :around #'org-babel-execute-src-block
  "Load language if needed before executing a source block."
  (let ((language (org-element-property :language (org-element-at-point))))
    (unless (cdr (assoc (intern language) org-babel-load-languages))
      (add-to-list 'org-babel-load-languages (cons (intern language) t))
      (org-babel-do-load-languages 'org-babel-load-languages org-babel-load-languages))
    (apply orig-fun args)))
#+end_src

*** ~auto-tangle-mode~: automatically tangle literate Org files 📦

#+BEGIN_SRC emacs-lisp
(package! (auto-tangle-mode
           :host github
           :repo "progfolio/auto-tangle-mode.el")
  (autoload 'auto-tangle-mode "auto-tangle-mode"))
#+END_SRC

*** ~org-rich-yank~: surround pasted code with src-block markup 📦

<https://github.com/unhammer/org-rich-yank>

Paraphrasing from the README:

This package should be loaded immediately. We never know when the user will
hit =C-M-y=, so we always have to store the current buffer on kills. You can
remove the ~require~ and have lazy/deferred loading, but then the first
time you hit =C-M-y= after startup, you’ll get a message that you have to
kill the selection again.

#+BEGIN_SRC emacs-lisp
(package! org-rich-yank
  (with-eval-after-load 'org-download
    (require 'org-rich-yank)

    (keymap-set org-mode-map "C-M-y" #'org-rich-yank)))
#+END_SRC

** Keybindings :kbd:

#+begin_src emacs-lisp
(keymap-global-set "C-c c" #'org-capture)

(define-keymap :keymap ceamx-launch-map
  "a" #'org-agenda
  "c" #'org-capture)
#+end_src

*** Key-related settings

#+BEGIN_SRC emacs-lisp
(setopt org-return-follows-link t)
(setopt org-special-ctrl-a/e t)
#+END_SRC

*** Support the special shifted-arrow-key commands

#+begin_src emacs-lisp
;; While nil is the default value, it must be set explicitly to opt-in.
(setopt org-support-shift-select nil)
#+end_src

From the documentation for that setting:

#+begin_quote
Shifted cursor keys will then execute Org commands in the following contexts:

- on a headline, changing TODO state (left/right) and priority (up/down)
- on a time stamp, changing the time
- in a plain list item, changing the bullet type
- in a property definition line, switching between allowed values
- in the BEGIN line of a clock table (changing the time block).
- in a table, moving the cell in the specified direction.

Outside these contexts, the commands will throw an error.
#+end_quote

*** Org-Mode-specific keybindings

#+BEGIN_SRC emacs-lisp
(with-eval-after-load 'org
  (define-keymap :keymap org-mode-map
    "C-c <up>" #'org-priority-up
    "C-c <down>" #'org-priority-down

    ;; "C-c l" #'org-store-link

    ;; Kill subtree or table region.
    "C-c s k" #'org-cut-special
    "C-M-S-w" #'org-cut-special

    ;; Swap these around, as I am more likely to adjust subtree than insert an
    ;; arbitrary date from the calendar.
    "C-c <" #'org-promote-subtree
    "C-c C-<" #'org-date-from-calendar
    "C-c >" #'org-demote-subtree
    "C-c C->" #'org-goto-calendar

    ;; C-c t :: `ceamx-toggle-map'
    "C-c t l" #'org-toggle-link-display

    "C-M-<return>" #'org-insert-subheading

    ;; Override the global binding to `narrow-to-region', which is disabled by
    ;; default.  I have not once (yet) wanted to actually use `narrow-to-regin',
    ;; but I do often type "C-x n n" in `org-mode', expecting the behavior of
    ;; `org-narrow-to-subtree'.  That's a waste of a potential DWIM key
    ;; sequence.d
    "C-x n n" #'org-narrow-to-subtree

    ;; Mnemonic is the global key to goto definition/references.  Without this,
    ;; C finds Elph using this unavailingly in src blocks.
    "M-." #'org-edit-special ; also: C-c '

    ;; Override earlier binding to `consult-outline'.
    "M-g o" #'consult-org-heading))
#+END_SRC

*** Tree navigation repeat map

#+begin_src emacs-lisp
;; TODO: Activate with a dedicated easy-to-access binding...
;;       Maybe the same binding for other per-major mode navigation maps?
(defvar-keymap org-mode-navigation-repeat-map
  :repeat t
  ;; Double-edged sword; quick, but gets in the way usually.
  ;; "TAB" #'org-cycle
  ;; "S-TAB" #'org-cycle-global

  "C-n" #'org-next-visible-heading
  "C-p" #'org-previous-visible-heading
  "C-f" #'org-forward-heading-same-level
  "C-b" #'org-backward-heading-same-level
  "C-u" #'outline-up-heading

  "n" #'org-next-visible-heading
  "p" #'org-previous-visible-heading
  "f" #'org-forward-heading-same-level
  "b" #'org-backward-heading-same-level
  "u" #'outline-up-heading

  "H" #'org-promote-subtree
  "J" #'org-move-subtree-down
  "K" #'org-move-subtree-up
  "L" #'org-demote-subtree

  "<" #'org-promote-subtree
  ">" #'org-demote-subtree)
#+end_src

** Resources
*** [[https://github.com/james-stoup/emacs-org-mode-tutorial][GitHub - james-stoup/emacs-org-mode-tutorial: A primer for users trying to make sense of Org Mode]]


** Tasks

*** TODO Org-Mode: electric-pair for equals-sign


* TODO Controls

What even is this though...?

#+begin_src emacs-lisp :tangle lisp/init-controls.el
;;; init-controls.el --- Controlling various subsystems  -*- lexical-binding: t; -*-

;; Copyright (C) 2024  Chris Montgomery

;; Author: Chris Montgomery <chmont@proton.me>
;; Keywords: local

;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.

;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.

;; You should have received a copy of the GNU General Public License
;; along with this program.  If not, see <https://www.gnu.org/licenses/>.

;;; Commentary:

;; " Is Control controlled by its need to control? "

;;; Code:

;;; Requirements

(require 'ceamx-keymaps)

(require 'ceamx-lib)

;;; Launch

(keymap-global-set "C-c o" '("[ Launch ]" . ceamx-launch-map))


;;; Toggle

(keymap-global-set "C-c t" '("[ Toggle ]" . ceamx-toggle-map))

(define-keymap :keymap ceamx-toggle-map
  "l" #'display-line-numbers-mode
  "w" '("side windows" . window-toggle-side-windows))

(provide 'init-controls)
;;; init-controls.el ends here
#+end_src


* Dashboard (powered by =enlight=)
:PROPERTIES:
:header-args: :noweb-ref init-dashboard
:END:

** Install and activate =enlight=

#+begin_src emacs-lisp
(package! enlight
  (require 'enlight)

  (setopt initial-buffer-choice #'enlight))
#+end_src

** Define dashboard-specific faces :faces:

#+begin_src emacs-lisp
;; FIXME: use theme palette
(defface enlight-yellow-bold
  '((t (:foreground "#cabf00" :bold t)))
  "Yellow bold face.")
#+end_src

#+begin_src emacs-lisp
(after! enlight
  (require 'grid)

  (defvar enlight-calendar
    (progn
      (calendar)
      ;;      (diary-mark-entries)
      (prog1 (with-current-buffer (buffer-name (current-buffer))
               (buffer-string))
        (calendar-exit))))

  (setopt enlight-content
          (concat (grid-get-box `(:align center :content "C E A M X" :width 80))
                  enlight-calendar "\n"
                  (grid-get-row
                   (list (concat
                          (propertize "MENU" 'face 'highlight) "\n"
                          (enlight-menu '(("Org-Mode"
                                           ("Agenda (current day)" (org-agenda nil "a") "A"))
                                          ("Contexts"
                                           ("Activity..." activities-resume "a")
                                           ("Project..." project-switch-project "p"))
                                          ("Open"
                                           ("~/Downloads/" (dired "~/Downloads") "d"))))))))))
#+end_src

#+begin_src emacs-lisp
;;;; TODO: not yet working, might not be do-able in the early days of `enlight'

;; (keymap-set enlight-mode-map "a" (define-prefix-command '+enlight-menu-a-prefix))

;; (grid-get-column (list (propertize "[ACTIVITIES]" 'face 'highlight)
;;                        `(:content ,(enlight-menu `(("Seadome"
;;                                                     ()
;;                                                     ("CEAMX" (activities-resume (activities-named "ceamx")) "a c")
;;                                                     ("Dotfield" (activities-resume (activities-named  "dotfield") "a d")))
;;                                                   ("Kleinweb"
;;                                                    ("TUTV" (activities-resume (activities-named "tutv")) "a w 1"))))
;;                          :width 50)))
#+end_src

* Dired, the Directory Editor

#+begin_src emacs-lisp :tangle lisp/init-dired.el
;;; init-dired.el --- Dired -*- lexical-binding: t -*-

;; Copyright (c) 2022-2024  Chris Montgomery <chmont@proton.me>

;; Author: Chris Montgomery <chmont@proton.me>
;; URL: https://git.sr.ht/~montchr/ceamx
;; Version: 0.1.0

;; This file is NOT part of GNU Emacs.

;; This file is free software: you can redistribute it and/or modify it
;; under the terms of the GNU General Public License as published by the
;; Free Software Foundation, either version 3 of the License, or (at
;; your option) any later version.
;;
;; This file is distributed in the hope that it will be useful, but
;; WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
;; General Public License for more details.
;;
;; You should have received a copy of the GNU General Public License
;; along with this file.  If not, see <http://www.gnu.org/licenses/>.

;;; Commentary:

;;  Configuration for Dired and extensions.

;; FIXME: Hide directories like ".git" and ".direnv" by default...
;;        eza/fd/rg/etc. do this by default but Dired should prob use GNU ls

;;; Code:

(require 'ceamx-lib)

;;; Dired, the Directory Editor

(use-feature! dired
  :commands (dired-omit-mode)

  :config
  (setopt dired-auto-revert-buffer t)
  (setopt dired-dwim-target t)
  (setopt dired-kill-when-opening-new-dired-buffer t)
  (setopt dired-listing-switches "-al --group-directories-first")
  (setopt dired-mouse-drag-files t)

  ;; TODO: does this really belong here?
  (setopt mouse-drag-and-drop-region-cross-program t)

  (define-keymap :keymap dired-mode-map
    "M-p" #'dired-up-directory))

;;; Provide Dired with polished interface and feature enhancements with ~dirvish~

;;  <https://github.com/alexluigit/dirvish>
;;  <https://github.com/alexluigit/dirvish/blob/main/docs/CUSTOMIZING.org#Sample-config>

(use-package dirvish
  :commands (dirvish-override-dired-mode
              dirvish-peek-mode
              dirvish-side-follow-mode)

  :init
  (after! dired
    (dirvish-override-dired-mode))

  ;; Omit "uninteresting" files.
  ;; See `dired-omit-files', `dired-omit-lines', `dired-omit-extensions'
  (add-hook 'dired-mode-hook #'dired-omit-mode)

  :config
  (dirvish-peek-mode)                   ; Preview minibuffer file selections
  (dirvish-side-follow-mode)

  ;; TODO: use consts; there's no straightforward way to access these values
  ;; without custom elisp afaik
  ;; FIXME: ensure directories exist!
  (setopt dirvish-quick-access-entries
    '(("c" "~/Documents/cheatsheets/" "Cheatsheets")
       ("D" "~/Downloads/" "Downloads")
       ("r" "~/Documents/reference" "Reference")
       ("n" "~/Documents/notes/" "Notes")))

  (setopt dirvish-mode-line-format
    '( :left (sort symlink)
       :right (omit yank index)))

  ;; previous value, in case:
  ;; (setopt dirvish-attributes '(all-the-icons file-time file-size collapse subtree-state vc-state))
  (setopt dirvish-attributes
    '(vc-state
       subtree-state
       nerd-icons
       collapse
       file-time
       file-size))
  (setopt dirvish-subtree-state-style 'nerd)

  ;; <https://github.com/alexluigit/dirvish/blob/main/docs/CUSTOMIZING.org#mouse-settings>
  (def-hook! ceamx--dirvish-no-mouse-follows-link (&rest _)
    'dirvish-find-entry-hook
    "Disable `mouse-1-click-follows-link' in `dirvish' buffers."
    (setopt mouse-1-click-follows-link nil))

  (define-keymap :keymap dirvish-mode-map
    ;; NOTE: `mouse-1-click-follows-link' must be nil (see above)
    "<mouse-1>" #'dirvish-subtree-toggle-or-open
    "<mouse-2>" #'dired-mouse-find-file-other-window
    "<mouse-3>" #'dired-mouse-find-file
    "a" #'dirvish-quick-access
    "f" #'dirvish-file-info-menu
    "y" #'dirvish-yank-menu
    "N" #'dirvish-narrow
    "^" #'dirvish-history-last
    "h" #'dirvish-history-jump          ; remapped `describe-mode'
    "s" #'dirvish-quicksort             ; remapped `dired-sort-toggle-or-edit'
    "v" #'dirvish-vc-menu               ; remapped `dired-view-file'
    "q" #'dirvish-quit
    "TAB" #'dirvish-subtree-toggle
    "M-f" #'dirvish-history-go-forward
    "M-b" #'dirvish-history-go-backward
    "M-l" #'dirvish-ls-switches-menu
    "M-m" #'dirvish-mark-menu
    "M-t" #'dirvish-layout-toggle
    "M-s" #'dirvish-setup-menu
    "M-e" #'dirvish-emerge-menu
    "M-j" #'dirvish-fd-jump))

;;; Provide addtional syntax highlighting for Dired with ~diredfl~

;; <https://github.com/purcell/diredfl>

(use-package diredfl
  :hook
  ((dired-mode . diredfl-mode)
    ;; highlight parent and directory preview as well
    (dirvish-directory-view-mode . diredfl-mode))

  :config
  (set-face-attribute 'diredfl-dir-name nil :bold t))

(provide 'init-dired)
;;; init-dired.el ends here
#+end_src

**


* Printing

#+begin_src emacs-lisp :tangle lisp/init-printing.el
;;; init-printing.el --- Support for printing documents  -*- lexical-binding: t; -*-

;; Copyright (C) 2024  Chris Montgomery

;; Author: Chris Montgomery <chmont@proton.me>
;; Keywords: local

;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.

;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.

;; You should have received a copy of the GNU General Public License
;; along with this program.  If not, see <https://www.gnu.org/licenses/>.

;;; Commentary:

;; FIXME: prints raw PDF data

;;; Code:

(require 'ceamx-lib)

(use-feature! printing
  :defer 2
  :commands (pr-update-menus)
  :config
  (require 'printing)
  ;; EPSON WF-3520
  (setopt printer-name "LABORTTY")
  ;; (setopt lpr-switches '())
  (pr-update-menus))

(provide 'init-printing)
;;; init-printing.el ends here
#+end_src


**


* DISABLED Newsreader



#+begin_src emacs-lisp :tangle lisp/init-news.el
;;; init-feeds.el --- News feed (RSS/Atom) subscription support  -*- lexical-binding: t; -*-

;; Copyright (C) 2023-2024  Chris Montgomery

;; Author: Chris Montgomery <chmont@proton.me>
;; Keywords: news, local

;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.

;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.

;; You should have received a copy of the GNU General Public License
;; along with this program.  If not, see <https://www.gnu.org/licenses/>.

;;; Commentary:

;; But do I really want to know what's happening outside of Emacs?

;; FIXME: use <https://github.com/skeeto/elfeed>
;; FIXME: OPML instead of weird lists

;;; Code:

;; (require 'ceamx-user)

;; (use-feature! [newsticker]
;;   (setopt newsticker-url-list ceamx-news-feed-url-list))



(provide 'init-news)
;;; init-news.el ends here
#+end_src


** TODO Feature name ~init-feeds~ does not match the filename =init-news.el=


* Surfing the Wild Web of Words

#+begin_src emacs-lisp :tangle lisp/init-eww.el
;;; init-eww.el --- Surfing the Wild Web of Words  -*- lexical-binding: t; -*-

;; TODO: headers

;;; Commentary:

;;; Code:

;;; Requirements

(require 'ceamx-keymaps)

(require 'ceamx-lib)
(require 'lib-eww)

(autoload 'eww "eww")

;;; Configuration
#+end_src


** DISABLED Tell Emacs we want its Web Wowser to handle URL browsing by default

Currently, since so many URLs point to GitHub, and GitHub will not function
without JavaScript, it does not make sense to use EWW by default.

#+begin_src emacs-lisp :tangle lisp/init-eww.el
;; (setopt browse-url-browser-function 'eww-browse-url)
#+end_src


*** TODO Use a different browser for some domains


** Configure EWW settings before loading

#+begin_src emacs-lisp :tangle lisp/init-eww.el
(setopt shr-use-colors t)
(setopt shr-folding-mode t)
(setopt shr-bullet "• ")

(setopt eww-search-prefix "https://duckduckgo.com/html?q=")

;; HTTP headers may contain user information, which we can limit as needed.
;; When providing a list of symbols, the symbols indicate what NOT to send.
;; TODO: Move this elsewhere, as it probably affects other HTTP requests.
(setopt url-privacy-level '(email lastloc))
#+end_src


** EWW: Keybindings

#+begin_src emacs-lisp :tangle lisp/init-eww.el
(define-keymap :keymap ceamx-launch-map
  "b" #'eww
  "W" #'ceamx/eww-wiki)

(after! eww
  (define-keymap :keymap eww-mode-map
    "," '("scroll down" . scroll-up-command)
    "." '("scroll up" . scroll-down-command)
    "o" '("open link" . link-hint-open-link)))
#+end_src

** Cleanup the rendering of some pages

#+begin_src emacs-lisp :tangle lisp/init-eww.el
(add-hook 'eww-after-render-hook #'ceamx-eww-rerender)
#+end_src

#+begin_src emacs-lisp :tangle lisp/init-eww.el
(provide 'init-eww)
;;; init-eww.el ends here
#+end_src


** EWW: Library

#+begin_src emacs-lisp :tangle lisp/lib-eww.el
;;; lib-eww.el --- Helpers for Ceamx EWW  -*- lexical-binding: t; -*-

;; Copyright (C) 2023-2024  Chris Montgomery
;; Copyright (C) 2018  Howard X. Abrams

;; Author: Chris Montgomery <chmont@proton.me>
;;         Howard X. Abrams <howard.abrams@workday.com>
;; Keywords: local

;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;;
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.
;;
;; You should have received a copy of the GNU General Public License
;; along with this program.  If not, see <https://www.gnu.org/licenses/>.

;; Permission is hereby granted, free of charge, to any person obtaining
;; a copy of this software and associated documentation files (the
;; "Software"), to deal in the Software without restriction, including
;; without limitation the rights to use, copy, modify, merge, publish,
;; distribute, sublicense, and/or sell copies of the Software, and to
;; permit persons to whom the Software is furnished to do so, subject to
;; the following conditions:
;;
;; The above copyright notice and this permission notice shall be
;; included in all copies or substantial portions of the Software.

;;; Commentary:

;;; Sources:

;; - <https://gitlab.com/howardabrams/spacemacs.d/-/blob/51196e861da9a76a02f1159397ba85b936cdfe27/layers/ha-eww/funcs.el>
;; - <https://writequit.org/org/settings.html#sec-1-61>

;;; Code:

;;; Requirements

(require 'cl-lib)
(require 'url)

;;; Variables

(defun ceamx/eww-wiki (text)
  "Search Wikipedia for TEXT."
  (interactive (list (read-string "Wiki for: ")))
  (eww (format "https://en.m.wikipedia.org/wiki/Special:Search?search=%s"
               (url-encode-url text))))

(defconst ceamx-eww-reddit-comment-header-regexp
  (rx "level "
      (one-or-more digit)
      (zero-or-more anything)
      line-end

      (group (one-or-more anything))
      line-end

      (one-or-more digit)
      " points"
      (one-or-more anything)
      line-end)
  "Regular expression for matching Reddit comments.")

(defconst ceamx-eww-github-repo-landing-readme-header-regexp
  (rx line-start
      "• "
      (one-or-more anything) ; SVG icon for the readme
      "README"
      line-end)
  "Regular expression matching the header for the README file content.")

(defconst ceamx-eww-github-begin-file-content-regexp
  (rx
   (one-or-more digit)
   " lines ("
   (one-or-more digit)
   " sloc) "
   (one-or-more digit)
   " ")
  "Regular expression for matching the start of a repo file on GitHub.")

;; TODO: support formats other than markdown too
(defconst ceamx-eww-github-footer-text-regexp
  (rx line-start
      (one-or-more anything)            ; SVG of GitHub logo
      " © "
      (one-or-more digit)
      " GitHub, Inc."
      line-end)
  "GitHub's copyright line is a good indication of the end of the content.
Note that as of 2024-03-13, repo file views no longer have a footer.")

;;; Functions

(defun ceamx/eww-clean-reddit ()
  "Remove a lot of the cruft in a rendered Reddit page."
  (interactive)
  (read-only-mode -1)

  ;; 2 comments
  ;; 79% Upvoted
  ;; What are your thoughts? Log in or Sign uplog insign up
  ;; Sort by

  ;; level 1
  ;; vale_fallacia
  ;; 1 point · 12 hours ago

  (flush-lines (rx line-start
                   (zero-or-more whitespace)
                   "Submit"))
  (while (re-search-forward ceamx-eww-reddit-comment-header-regexp nil t)
    (replace-match (concat "** " (match-string 1))))
  (read-only-mode 1))

(defun ceamx/eww-clean-github ()
  "Jump to the beginning of the content on a GitHub repo page."
  (interactive)
  (when (re-search-forward ceamx-eww-github-footer-text-regexp nil t)
    (read-only-mode -1)
    ;; (previous-line 2) ; "interactive use only"
    (forward-line -2)
    (delete-region (point) (point-max))
    (goto-char (point-min)))

  (when (or (re-search-forward ceamx-eww-github-begin-file-content-regexp nil t)
            (re-search-forward ceamx-eww-github-repo-landing-readme-header-regexp nil t))
    (forward-line 2)
    (recenter-top-bottom 0)))

(defun ceamx/eww-clean-stackoverflow ()
  "Jump to the start of interesting content on a Stack Overflow page."
  (interactive)
  (read-only-mode -1)

  ;; (mapcar (lambda (regex) (flush-lines regex))
  ;;         '("^up vote "
  ;;           "^answered "
  ;;           "^asked [A-Z]" "^edited [A-Z]"
  ;;           "^add a comment "
  ;;           "^share|"
  ;;           "^active oldest"))
  (mapc (lambda (regex) (flush-lines regex))
        '("^up vote "
          "^answered "
          "^asked [A-Z]" "^edited [A-Z]"
          "^add a comment "
          "^share|"
          "^active oldest"))

  (goto-char 0)
  (re-search-forward "Ask Question" nil t)
  (backward-paragraph 2)
  (forward-line)
  (recenter-top-bottom 0)

  (flush-lines "^Ask Question")
  (read-only-mode 1))

(defun ceamx-eww-rerender ()
  "Invoke a rerenderer function based on the URL to be displayed."
  (declare-function eww-current-url "eww")
  (declare-function eww-readable "eww")

  (let* ((url  (url-generic-parse-url (eww-current-url)))
         (host (url-host url))
         (path (car (url-path-and-query url)))
         (bits (split-string host "\\."))
         (site (cl-first (last bits 2))))
    (cond
     ((equal site "google")        (eww-readable))
     ((equal site "reddit")        (ceamx/eww-clean-reddit))
     ((equal site "github")        (ceamx/eww-clean-github))
     ((equal site "stackoverflow") (ceamx/eww-clean-stackoverflow)))))

;; FIXME: dependency i am not using -- replace function
;; (defun ceamx/eww-copy-feed-url ()
;;   "Take the EWW's current URL location and pass it to the `feed-discovery' function."
;;   (interactive)
;;   (feed-discovery-copy-feed-url (eww-current-url)))

(provide 'lib-eww)
;;; lib-eww.el ends here
#+end_src

** EWW: Sources

*** [[https://gitlab.com/howardabrams/spacemacs.d/-/blob/51196e861da9a76a02f1159397ba85b936cdfe27/layers/ha-eww/funcs.el][layers/ha-eww/funcs.el · 51196e861da9a76a02f1159397ba85b936cdfe27 · Howard Abrams / spacemacs.d · GitLab]]


* Tools
:PROPERTIES:
:header-args: :noweb-ref init-tools
:END:

#+begin_src emacs-lisp
(require 'seq)

(require 'ceamx-lib)
#+end_src

** [[https://joostkremers.github.io/pandoc-mode/][Pandoc-mode]]: filetype conversion multitool

#+begin_src emacs-lisp
(package! pandoc-mode
  (add-hook 'markdown-mode-hook #'pandoc-mode)

  (add-hook 'pandoc-mode-hook #'pandoc-load-default-settings))
#+end_src

** [[https://github.com/alphapapa/unpackaged.el][alphapapa/unpackaged.el]]: a library of useful yet "unsubstantial" Emacs Lisp code

#+begin_src emacs-lisp
(package! (unpackaged :host github :repo "alphapapa/unpackaged.el"))
#+end_src

** ~mugur~: a configurator for QMK keyboards

#+begin_src emacs-lisp
(package! mugur)
#+end_src

** DISABLED ~org-tanglesync~: sync tangled src blocks
:PROPERTIES:
:header-args: :tangle no
:END:

This mode... works?!  Kind of.  It is actually kind of unpredictable, at least in
my initial usage of it.  I would only enable it as needed.  It did really help me
pull in all of the existing =*.el= files to =config.org=!

#+begin_src emacs-lisp
(package! org-tanglesync
  ;; FIXME: try to not do this
  (require 'org-tanglesync)

  (add-hook 'org-mode-hook #'org-tanglesync-mode)
  ;; (remove-hook 'org-mode-hook #'org-tanglesync-mode)

  (add-hook 'prog-mode-hook #'org-tanglesync-watch-mode)
  ;; (remove-hook 'prog-mode-hook #'org-tanglesync-watch-mode)

  ;; (add-hook 'text-mode-hook #'org-tanglesync-watch-mode)
  ;; (remove-hook 'text-mode-hook #'org-tanglesync-watch-mode)

  (setopt org-tanglesync-watch-files
    (seq-map (apply-partially #'file-name-concat user-emacs-directory)
      '("config-sync.org")))

  (global-keys!
    "C-c M-i" #'org-tanglesync-process-buffer-interactive
    "C-c M-a" #'org-tanglesync-process-buffer-automatic))
#+end_src



** Augementated Intelligentry (AI)

Providing integrations with LLMs and other simulation machines.

M is not eager to board the train bound for Hype City.

*** [[https://github.com/xenodium/chatgpt-shell/blob/main/README.org][xenodium/chatgpt-shell]]

#+begin_src emacs-lisp :noweb-ref config-feature-paths
(defconst ceamx-tools-chatgpt-shell-cache-dir
  (file-name-as-directory (concat ceamx-var-dir "chatgpt-shell")))
#+end_src


#+BEGIN_SRC emacs-lisp
(package! chatgpt-shell
  (setopt chatgpt-shell-root-path ceamx-tools-chatgpt-shell-cache-dir))

(after! chatgpt-shell
    (setopt chatgpt-shell-openai-key
            (lambda ()
              (auth-source-pass-get 'secret "openai-key"))))
#+END_SRC

** Personal Finance with =hledger=
:PROPERTIES:
:header-args: :noweb-ref init-tools
:END:

- website :: <https://hledger.org/>
- docs :: <https://hledger.org/1.30/hledger.html#journal>
- website :: <https://github.com/narendraj9/hledger-mode>

*** Define important feature paths :paths:

#+begin_src emacs-lisp :noweb-ref config-feature-paths
(defconst ceamx-ledger-dir (expand-file-name "~/ledger"))
(defconst ceamx-ledger-main-journal-file (file-name-concat ceamx-ledger-dir "main.journal"))
#+end_src

*** Install ~hledger-mode~ 📦

#+begin_src emacs-lisp
(require 'ceamx-paths)

(package! hledger-mode
  (setopt hledger-jfile ceamx-ledger-main-journal-file))
#+end_src

*** Register buffers as popups 🪟

#+begin_src emacs-lisp
(after! popper
  (add-to-list 'popper-reference-buffers "\\*Personal Finance\\*"))
#+end_src

*** Keybindings :kbd:

#+begin_src emacs-lisp
(require 'lib-tools)

(after! hledger-mode
  (define-keymap :keymap hledger-mode-map
    "C-c e" #'hledger-jentry
    ;; NOTE: Overrides global binding for completion-at-point/cape commands.
    "M-p" #'ceamx/hledger-prev-entry
    "M-n" #'ceamx/hledger-next-entry))
#+end_src

*** Register the =hledger= checker for =Flycheck= :checkers:

#+begin_src emacs-lisp
(package! flycheck-hledger
  (when (fboundp 'flycheck-mode)
    (add-hook 'hledger-mode-hook #'flycheck-mode))

  (setopt flycheck-hledger-strict t))
#+end_src

*** TODO =hledger-input= 📦
:PROPERTIES:
:header-args: :noweb-ref nil
:END:

#+begin_src emacs-lisp
;; TODO
;; (use-package hledger-input
;;   ;; :pin manual
;;   ;; :load-path "packages/rest/hledger-mode/"
;;   ;; :bind (("C-c e" . hledger-capture)
;;   ;;        :map hledger-input-mode-map
;;   ;;        ("C-c C-b" . popup-balance-at-point))
;;   :preface
;;   (defun popup-balance-at-point ()
;;     "Show balance for account at point in a popup."
;;     (interactive)
;;     (if-let ((account (thing-at-point 'hledger-account)))
;;         (message (hledger-shell-command-to-string (format " balance -N %s "
;;                                                           account)))
;;       (message "No account at point")))

;;   :config
;;   (setq hledger-input-buffer-height 20)
;;   (add-hook 'hledger-input-post-commit-hook #'hledger-show-new-balances)
;;   (add-hook 'hledger-input-mode-hook #'auto-fill-mode)
;;   (add-hook 'hledger-input-mode-hook
;;             (lambda ()
;;               (make-local-variable 'company-idle-delay)
;;               (setq-local company-idle-delay 0.1))))
#+end_src

*** Define =hledger= helper commands

#+begin_src emacs-lisp :noweb-ref lib-tools
(defun ceamx-finance/hledger-next-entry ()
  "Move to next entry and pulse."
  (interactive)
  (declare-function hledger-next-or-new-entry "hledger-mode")
  (declare-function hledger-pulse-momentary-current-entry "hledger-mode")

  (hledger-next-or-new-entry)
  (hledger-pulse-momentary-current-entry))

(defun ceamx-finance/hledger-prev-entry ()
  "Move to last entry and pulse."
  (interactive)
  (declare-function hledger-backward-entry "hledger-mode")
  (declare-function hledger-pulse-momentary-current-entry "hledger-mode")

  (hledger-backward-entry)
  (hledger-pulse-momentary-current-entry))
#+end_src

*** Register completions for =hledger= data :capfs:

#+begin_src emacs-lisp
(require 'lib-tools)

(after! hledger-mode
  (add-hook 'hledger-mode-hook #'+hledger-accounts-capf-h))
#+end_src

#+begin_src emacs-lisp :noweb-ref lib-tools
(defun +hledger-accounts-completion-at-point ()
  "Return completion candidates for hledger accounts."

  (when-let ((bounds (and (boundp 'hledger-accounts-cache)
                          (bounds-of-thing-at-point 'symbol))))
    (list (car bounds) (point) hledger-accounts-cache)))

(defun +hledger-accounts-capf-h ()
  "Add hledger accounts to `completion-at-point' functions."
  (add-hook 'completion-at-point-functions
            '+hledger-accounts-completion-at-point 20 t))
#+end_src



** PDF-Tools

- website :: <https://github.com/vedang/pdf-tools>
- ref :: <https://github.com/jwiegley/dot-emacs/blob/master/init.org>

~pdf-tools~ should be installed installed via Nixpkgs because it requires
some separate binaries.

#+begin_src emacs-lisp
(require 'ceamx-lib)

(defvar pdf-tools-handle-upgrades nil)

(after! pdf-tools
  (dolist
      (pkg
       '(pdf-annot pdf-cache pdf-dev pdf-history pdf-info pdf-isearch
         pdf-links pdf-misc pdf-occur pdf-outline pdf-sync
         pdf-util pdf-view pdf-virtual))
    (require pkg))
  (pdf-tools-install))
#+end_src

** TODO <https://github.com/doomemacs/doomemacs/blob/master/modules/tools/pdf/config.el>
** TODO =saveplace-pdf-view=

#+begin_src emacs-lisp
;; (use-package saveplace-pdf-view
;;   :defer 5)
#+end_src


* "Fun"

#+begin_src emacs-lisp :tangle lisp/init-fun.el
;;; init-fun.el --- Configure the fun                -*- lexical-binding: t; -*-

;; Copyright (C) 2023-2024  Chris Montgomery

;; Author: Chris Montgomery <chmont@proton.me>

;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.

;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.

;; You should have received a copy of the GNU General Public License
;; along with this program.  If not, see <https://www.gnu.org/licenses/>.

;;; Commentary:

;; It's fun.

;;; Code:

(require 'ceamx-lib)

;;;; ~zone~ [builtin]

;; <https://www.emacswiki.org/emacs/ZoneMode>

(use-feature! zone
  :autoload (zone-when-idle)
  :defines (zone-timer)

  :config
  ;; TODO: verify
  (def-hook! ceamx-zone-when-idle-h ()
    'ceamx-emacs-startup-hook
    "Zone out when idle.
Return the new `zone' timer."
    (zone-when-idle (* 60 10))))

;; FIXME: broken: wrong type argument arrayp (for pgm arg)
;;        (where did this even come from? emacswiki?)
;; (defun zone-choose (pgm)
;;   "Choose a PGM to run for `zone'."
;;   (interactive
;;     (list
;;       (completing-read
;;         "Program: "
;;         (mapcar 'symbol-name zone-programs))))
;;   (let ((zone-programs (list (intern pgm))))
;;     (zone)))

(provide 'init-fun)
;;; init-fun.el ends here
#+end_src


* Postlude
:PROPERTIES:
:header-args: :noweb-ref ceamx-postlude
:END:

** Start the Emacs server process if not already running

#+begin_src emacs-lisp
(defun ceamx/maybe-start-server ()
  "Allow this Emacs process to act as server process if not already running."
  (require 'server)
  (unless (and (fboundp 'server-running-p)
               (server-running-p))
    (server-start)))
#+end_src

#+begin_src emacs-lisp
(add-hook 'ceamx-emacs-startup-hook #'ceamx/maybe-start-server)
#+end_src

** macOS: Restart Yabai after init

Otherwise, =yabai= will not "see" the Emacs GUI window.

#+begin_src emacs-lisp
(when (and (display-graphic-p) +sys-mac-p)
  (def-hook! ceamx-after-init-restart-yabai-h ()
    'ceamx-after-init-hook
    "Restart the yabai service after init."
    (after! exec-path-from-shell
      (async-shell-command "yabai --restart-service"))))
#+end_src

** Optionally load the ~custom-file~

#+begin_src emacs-lisp
(defun ceamx/load-custom-file ()
  "Load the user `custom-file'."
  (interactive)
  (when (file-exists-p custom-file)
    (load custom-file 'noerror)))
#+end_src

#+begin_src emacs-lisp
(add-hook 'ceamx-after-init-hook #'ceamx/load-custom-file)
#+end_src


* End

This is the section for file-local variables.  File-local variables must be on
the last "page" of a document, beginning no more than 3000 characters from EOF.
See [[info:emacs#Specifying File Variables]["(emacs) Specifying File Variables"]] for more information.

23 SKIDOO



# Local Variables:
# org-refile-targets: ((nil . (:maxlevel . 4)))
# End:

About

License:GNU General Public License v3.0


Languages

Language:Emacs Lisp 98.5%Language:YASnippet 0.8%Language:Nix 0.6%Language:Shell 0.1%