#+title: GNU Emacs configuration for Emacs 30 and above
#+author: Joe Ardent
#+language: en
#+options: ':t toc:nil num:t author:t

* Quick Start
:PROPERTIES:
:CUSTOM_ID: h:2818C76B-3A6D-4676-8159-ED7BBFA45501
:CREATED:  <2024-12-31 Tue>
:END:

1. Install the latest available Emacs version, with native-compilation
   and tree-sitter support.
2. Clone this repository as your =.emacs.d= folder and start Emacs.
3. Go through the Table of Contents below and read details of
   whichever section you are interested in knowing more about. Reach
   out with questions, they will help me improve the README further!

* Table of Contents :TOC:
- [[#quick-start][Quick Start]]
- [[#introduction][Introduction]]
- [[#the-early-initel-file][The =early-init.el= file]]
  - [[#the-early-initel-basic-frame-settings][The =early-init.el= basic frame settings]]
  - [[#the-early-initel-tweaks-to-startup-time-and-garbage-collection][The =early-init.el= tweaks to startup time and garbage collection]]
  - [[#if-you-have-both-el-and-elc-files-load-the-newer-one][If you have both .el and .elc files, load the newer one]]
  - [[#the-early-initel-package-cache-settings][The =early-init.el= package cache settings]]
  - [[#finally-the-early-initel-local-variable-settings][Finally, the =early-init.el= local variable settings]]
- [[#the-initel-file][The =init.el= file]]
  - [[#the-initel-tweaks-to-make-native-compilation-silent][The =init.el= tweaks to make native compilation silent]]
  - [[#the-initel-setting-to-send-custom-file-to-oblivion][The =init.el= setting to send ~custom-file~ to oblivion]]
  - [[#the-initel-settings-to-enable-commands-disabled-by-default][The =init.el= settings to enable commands disabled by default]]
  - [[#the-initel-settings-to-disable-unnecessary-commands-enabled-by-default][The =init.el= settings to disable unnecessary commands enabled by default]]
  - [[#the-initel-section-to-add-the-modules-folder-to-the-load-path][The =init.el= section to add the modules folder to the load-path]]
  - [[#the-initel-section-for-using-the-elpaca-package-manager-elpaca][The =init.el= section for using the Elpaca package manager (~elpaca~)]]
  - [[#the-initel-macro-to-do-nothing-with-elisp-code-prot-emacs-comment][The =init.el= macro to do nothing with Elisp code (~prot-emacs-comment~)]]
  - [[#the-initel-macro-to-define-abbreviations-prot-emacs-abbrev][The =init.el= macro to define abbreviations (~prot-emacs-abbrev~)]]
  - [[#the-initel-section-to-load-the-individual-modules][The =init.el= section to load the individual modules]]
  - [[#finally-the-initel-section-for-local-variables][Finally, the =init.el= section for local variables]]
- [[#the-nebkor-themeel-module][The =nebkor-theme.el= module]]
  - [[#the-nebkor-themeel-section-for-cool-modern-themes-ef-themes][The =nebkor-theme.el= section for cool, modern themes (~ef-themes~)]]
  - [[#the-nebkor-themeel-section-for-highlighting-lines-lin][The =nebkor-theme.el= section for highlighting lines (~lin~)]]
  - [[#the-nebkor-themeel-section-for-color-previews-rainbow-mode][The =nebkor-theme.el= section for color previews (~rainbow-mode~)]]
  - [[#the-nebkor-themeel-section-for-cursor-styles-cursory][The =nebkor-theme.el= section for cursor styles (~cursory~)]]
  - [[#the-nebkor-themeel-section-about-font-styles-fontaine][The =nebkor-theme.el= section about font styles (~fontaine~)]]
  - [[#the-nebkor-themeel-section-for-font-previews-show-font][The =nebkor-theme.el= section for font previews (~show-font~)]]
  - [[#the-nebkor-themeel-section-about-font-resizing-variable-pitch-mode][The =nebkor-theme.el= section about font resizing (~variable-pitch-mode~)]]
  - [[#finally-we-provide-the-nebkor-themeel-module][Finally, we provide the =nebkor-theme.el= module]]
- [[#the-nebkor-essentialsel-module][The =nebkor-essentials.el= module]]
  - [[#the-nebkor-essentialsel-block-with-basic-configurations][The =nebkor-essentials.el= block with basic configurations]]
  - [[#the-nebkor-essentialsel-section-for-fixing-path-on-osx-exec-path-from-shell][The =nebkor-essentials.el= section for fixing PATH on OSX (~exec-path-from-shell~)]]
  - [[#the-nebkor-essentialsel-configuration-to-track-recently-visited-files-recentf][The =nebkor-essentials.el= configuration to track recently visited files (~recentf~)]]
  - [[#the-nebkor-essentialsel-configuration-to-track-my-place-in-visited-files-saveplace][The =nebkor-essentials.el= configuration to track my place in visited files (~saveplace~)]]
  - [[#the-nebkor-essentialsel-settings-for-bookmarks][The =nebkor-essentials.el= settings for bookmarks]]
  - [[#the-nebkor-essentialsel-settings-for-registers][The =nebkor-essentials.el= settings for registers]]
  - [[#the-nebkor-essentialsel-settings-for-files][The =nebkor-essentials.el= settings for files]]
  - [[#the-nebkor-essentialsel-section-for-delete-selection-mode][The =nebkor-essentials.el= section for ~delete-selection-mode~]]
  - [[#the-nebkor-essentialsel-settings-for-tooltips][The =nebkor-essentials.el= settings for tooltips]]
  - [[#the-nebkor-essentialsel-arrangement-to-run-emacs-as-a-server][The =nebkor-essentials.el= arrangement to run Emacs as a server]]
  - [[#the-nebkor-essentialsel-section-about-quick-copying-easy-kill][The =nebkor-essentials.el= section about quick copying (~easy-kill~)]]
  - [[#the-nebkor-essentialsel-section-about-auto-management-of-treesit-modules-treesit-auto][The =nebkor-essentials.el= section about auto management of treesit modules (~treesit-auto~)]]
  - [[#the-nebkor-essentialsel-section-about-using-tree-sitter-for-marking-expreg][The =nebkor-essentials.el= section about using tree-sitter for marking (~expreg~)]]
  - [[#the-nebkor-essentialsel-section-for-osx-changes][The =nebkor-essentials.el= section for OSX changes]]
  - [[#the-nebkor-essentialsel-section-for-simpleel-changes][The =nebkor-essentials.el= section for ~simple.el~ changes]]
  - [[#the-nebkor-essentialsel-section-for-better-help-helpful][The =nebkor-essentials.el= section for better help (~helpful~)]]
  - [[#the-nebkor-essentials-section-for-indent-tools][The =nebkor-essentials= section for ~indent-tools~]]
  - [[#the-nebkor-essentials-section-for-undo-tree][The =nebkor-essentials= section for ~undo-tree~]]
  - [[#the-nebkor-essentials-section-for-fancy-keyboard-shortcuts-key-chord][The =nebkor-essentials= section for fancy keyboard shortcuts (~key-chord~)]]
  - [[#the-nebkor-essentials-section-for-which-key][The =nebkor-essentials= section for ~which-key~]]
  - [[#the-nebkor-essentials-section-for-blackout-and-other-random-shit][The =nebkor-essentials= section for blackout and other random shit]]
  - [[#finally-we-provide-the-nebkor-essentialsel-module][Finally, we provide the =nebkor-essentials.el= module]]
- [[#the-nebkor-completionel-module][The =nebkor-completion.el= module]]
  - [[#the-nebkor-completionel-settings-for-completion-styles][The =nebkor-completion.el= settings for completion styles]]
  - [[#the-nebkor-completionel-for-the-orderless-completion-style][The =nebkor-completion.el= for the ~orderless~ completion style]]
  - [[#the-nebkor-completionel-settings-to-ignore-letter-casing][The =nebkor-completion.el= settings to ignore letter casing]]
  - [[#the-nebkor-completionel-settings-for-recursive-minibuffers][The =nebkor-completion.el= settings for recursive minibuffers]]
  - [[#the-nebkor-completionel-settings-for-default-values][The =nebkor-completion.el= settings for default values]]
  - [[#the-nebkor-completionel-settings-for-common-interactions][The =nebkor-completion.el= settings for common interactions]]
  - [[#the-nebkor-completionel-generic-minibuffer-ui-settings][The =nebkor-completion.el= generic minibuffer UI settings]]
  - [[#the-nebkor-completionel-settings-for-saving-the-history-savehist-mode][The =nebkor-completion.el= settings for saving the history (~savehist-mode~)]]
  - [[#the-nebkor-completionel-settings-for-dynamic-text-expansion-dabbrev][The =nebkor-completion.el= settings for dynamic text expansion (~dabbrev~)]]
  - [[#the-nebkor-completionel-settings-for-dynamic-text-expansion-hippie][The =nebkor-completion.el= settings for dynamic text expansion (~hippie~)]]
  - [[#the-nebkor-completionel-for-in-buffer-completion-popup-corfu-and-cape][The =nebkor-completion.el= for in-buffer completion popup (~corfu~ and ~cape~)]]
  - [[#the-nebkor-completionel-settings-for-filtering-previewing-candidates-consult][The =nebkor-completion.el= settings for filtering, previewing candidates (~consult~)]]
  - [[#the-nebkor-completionel-section-to-configure-completion-annotations-marginalia][The =nebkor-completion.el= section to configure completion annotations (~marginalia~)]]
  - [[#the-nebkor-completionel-section-for-vertical-minibuffer-layout-vertico][The =nebkor-completion.el= section for vertical minibuffer layout (~vertico~)]]
  - [[#finally-we-provide-the-nebkor-completionel-module][Finally, we provide the ~nebkor-completion.el~ module]]
- [[#the-nebkor-functionsel-module][The =nebkor-functions.el= module]]
- [[#the-nebkor-searchel-module][The =nebkor-search.el= module]]
  - [[#the-nebkor-searchel-section-for-heading-navigation-imenu][The =nebkor-search.el= section for heading navigation (~imenu~)]]
  - [[#the-nebkor-searchel-section-on-relaxed-searching-isearch][The =nebkor-search.el= section on relaxed searching (~isearch~)]]
  - [[#the-nebkor-searchel-settings-for-highlighting-search-results-isearch][The =nebkor-search.el= settings for highlighting search results (~isearch~)]]
  - [[#the-nebkor-searchel-section-on-showing-search-result-count-isearch][The =nebkor-search.el= section on showing search result count (~isearch~)]]
  - [[#the-nebkor-searchel-tweaks-for-the-search-results-in-buffer-occur][The =nebkor-search.el= tweaks for the search results in buffer (~occur~)]]
  - [[#the-nebkor-searchel-section-for-search-key-bindings][The =nebkor-search.el= section for search key bindings]]
  - [[#the-nebkor-searchel-tweaks-to-xref-re-builder-and-grep][The =nebkor-search.el= tweaks to (~xref~), (~re-builder~) and (~grep~)]]
  - [[#the-nebkor-searchel-setup-for-editable-grep-buffers-grep-edit-mode-or-wgrep][The =nebkor-search.el= setup for editable grep buffers (~grep-edit-mode~ or ~wgrep~)]]
  - [[#the-nebkor-searchel-settings-for-jumping-avy][The =nebkor-search.el= settings for jumping (~avy~)]]
  - [[#finally-we-provide-the-nebkor-searchel-module][Finally, we provide the =nebkor-search.el= module]]
- [[#the-nebkor-diredel-module][The =nebkor-dired.el= module]]
  - [[#the-nebkor-diredel-settings-for-common-operations][The =nebkor-dired.el= settings for common operations]]
  - [[#the-nebkor-diredel-switches-for-how-files-are-listed-ls][The =nebkor-dired.el= switches for how files are listed (~ls~)]]
  - [[#the-nebkor-diredel-setting-for-dual-pane-dired][The =nebkor-dired.el= setting for dual-pane Dired]]
  - [[#the-nebkor-diredel-miscellaneous-tweaks][The =nebkor-dired.el= miscellaneous tweaks]]
  - [[#the-nebkor-diredel-section-for-using-multi-occur-noccur][The =nebkor-dired.el= section for using multi-occur (~noccur~)]]
  - [[#the-nebkor-diredel-section-about-various-conveniences][The =nebkor-dired.el= section about various conveniences]]
  - [[#the-nebkor-diredel-section-about-subdirectory-contents-dired-subtree][The =nebkor-dired.el= section about subdirectory contents (~dired-subtree~)]]
  - [[#the-nebkor-diredel-section-about-writable-dired-wdired][The =nebkor-dired.el= section about writable Dired (~wdired~)]]
  - [[#the-nebkor-diredel-section-about-moving-to-trash-trashed][The =nebkor-dired.el= section about moving to Trash (~trashed~)]]
  - [[#finally-we-provide-the-nebkor-diredel-module][Finally, we provide the =nebkor-dired.el= module]]
- [[#the-nebkor-windowel-module][The =nebkor-window.el= module]]
  - [[#the-nebkor-windowel-section-about-uniquifying-buffer-names][The =nebkor-window.el= section about uniquifying buffer names]]
  - [[#the-nebkor-windowel-section-about-frame-oriented-workflows-beframe][The =nebkor-window.el= section about frame-oriented workflows (~beframe~)]]
  - [[#the-nebkor-windowel-use-of-contextual-header-line-breadcrumb][The =nebkor-window.el= use of contextual header line (~breadcrumb~)]]
  - [[#finally-we-provide-the-nebkor-windowel-module][Finally, we provide the =nebkor-window.el= module]]
- [[#the-nebkor-gitel-module][The =nebkor-git.el= module]]
  - [[#the-nebkor-gitel-section-about-ediff][The =nebkor-git.el= section about ediff]]
  - [[#the-nebkor-gitel-section-about-project-management-project][The =nebkor-git.el= section about project management (~project~)]]
  - [[#the-nebkor-gitel-section-about-diff-management-diff-mode][The =nebkor-git.el= section about diff management (~diff-mode~)]]
  - [[#the-nebkor-gitel-section-about-using-git-magit][The =nebkor-git.el= section about using Git (~magit~)]]
  - [[#finally-we-provide-the-nebkor-gitel-module][Finally, we provide the =nebkor-git.el= module]]
- [[#the-nebkor-orgel-module][The =nebkor-org.el= module]]
  - [[#the-nebkor-orgel-section-on-calendar][The =nebkor-org.el= section on (~calendar~)]]
  - [[#the-nebkor-orgel-section-about-appointment-reminders-appt][The =nebkor-org.el= section about appointment reminders (~appt~)]]
  - [[#the-nebkor-orgel-section-on-paragraphs][The =nebkor-org.el= section on paragraphs]]
  - [[#the-nebkor-orgel-section-with-basic-org-settings][The =nebkor-org.el= section with basic Org settings]]
  - [[#the-nebkor-orgel-section-for-archival-settings][The =nebkor-org.el= section for archival settings]]
  - [[#the-nebkor-orgel-section-for-narrowing-and-folding][The =nebkor-org.el= section for narrowing and folding]]
  - [[#the-nebkor-orgel-org-to-do-and-refile-settings][The =nebkor-org.el= Org to-do and refile settings]]
  - [[#the-nebkor-orgel-org-heading-tags][The =nebkor-org.el= Org heading tags]]
  - [[#the-nebkor-orgel-org-priorities-settings][The =nebkor-org.el= Org priorities settings]]
  - [[#the-nebkor-orgel-org-timestate-logging][The =nebkor-org.el= Org time/state logging]]
  - [[#the-nebkor-orgel-org-link-settings][The =nebkor-org.el= Org link settings]]
  - [[#the-nebkor-orgel-org-list-items-settings][The =nebkor-org.el= Org list items settings]]
  - [[#the-nebkor-orgel-org-code-block-settings][The =nebkor-org.el= Org code block settings]]
  - [[#the-nebkor-orgel-org-export-settings][The =nebkor-org.el= Org export settings]]
  - [[#the-nebkor-orgel-org-capture-templates-org-capture][The =nebkor-org.el= Org capture templates (~org-capture~)]]
  - [[#the-nebkor-orgel-section-on-yasnippet][The =nebkor-org.el= section on YASnippet]]
  - [[#the-nebkor-orgel-org-agenda-settings][The =nebkor-org.el= Org agenda settings]]
  - [[#finally-we-provide-the-nebkor-orgel-module][Finally, we provide the =nebkor-org.el= module]]
- [[#the-nebkor-langsel-module][The =nebkor-langs.el= module]]
  - [[#the-nebkor-langsel-settings-for-tab][The =nebkor-langs.el= settings for TAB]]
  - [[#the-nebkor-langsel-settings-highlighting-parens-show-paren-mode][The =nebkor-langs.el= settings highlighting parens (~show-paren-mode~)]]
  - [[#the-nebkor-langsel-settings-for-showing-relevant-documentation-eldoc][The =nebkor-langs.el= settings for showing relevant documentation (~eldoc~)]]
  - [[#the-nebkor-langsel-settings-for-connecting-to-lsp-servers-eglot][The =nebkor-langs.el= settings for connecting to LSP servers (~eglot~)]]
  - [[#the-nebkor-langsel-settings-for-writing-markdown-markdown-mode][The =nebkor-langs.el= settings for writing Markdown (~markdown-mode~)]]
  - [[#the-nebkor-langsel-settings-for-dealing-with-csv-files-csv-mode][The =nebkor-langs.el= settings for dealing with CSV files (~csv-mode~)]]
  - [[#the-nebkor-langsel-settings-for-spell-checking-flyspell][The =nebkor-langs.el= settings for spell checking (~flyspell~)]]
  - [[#the-nebkor-langsel-settings-for-code-linting-flymake][The =nebkor-langs.el= settings for code linting (~flymake~)]]
  - [[#the-nebkor-langsel-settings-for-quick-outlines-outline-minor-mode][The =nebkor-langs.el= settings for quick outlines (~outline-minor-mode~)]]
  - [[#the-nebkor-langsel-settings-for-definitions-dictionary][The =nebkor-langs.el= settings for definitions (~dictionary~)]]
  - [[#the-nebkor-langsel-settings-for-paren-matching-paredit][The =nebkor-langs.el= settings for paren matching (~paredit~)]]
  - [[#the-nebkor-langsel-settings-for-code-formatting-apheleia][The =nebkor-langs.el= settings for code formatting (~apheleia~)]]
  - [[#the-nebkor-langsel-settings-for-changing-many-things-multiple-cursors][The =nebkor-langs.el= settings for changing many things (~multiple-cursors~)]]
  - [[#the-nebkor-langsel-section-for-lsp][The =nebkor-langs.el= section for LSP]]
  - [[#the-nebkor-langsel-section-for-rust][The =nebkor-langs.el= section for Rust]]
  - [[#the-nebkor-langsel-section-for-python][The =nebkor-langs.el= section for Python]]
  - [[#the-nebkor-langsel-section-for-ziglang-zig-mode][The =nebkor-langs.el= section for Ziglang (~zig-mode~)]]
  - [[#the-nebkor-langsel-section-for-clojure-programming][The =nebkor-langs.el= section for Clojure programming]]
  - [[#the-nebkor-langsel-section-for-emacs-lisp][The =nebkor-langs.el= section for Emacs Lisp]]
  - [[#finally-we-provide-the-nebkor-langsel-module][Finally, we provide the =nebkor-langs.el= module]]
- [[#the-nebkor-studyel-module][The =nebkor-study.el= module]]
  - [[#the-nebkor-studyel-section-for-notes-and-file-naming-denote][The =nebkor-study.el= section for notes and file-naming (~denote~)]]
  - [[#the-nebkor-studyel-section-for-reading-and-annotation-of-pdfs-pdf-tools][The =nebkor-study.el= section for reading and annotation of PDFs (~pdf-tools~)]]
  - [[#the-nebkor-studyel-section-for-annotation-of-org-and-eww-files-org-remark][The =nebkor-study.el= section for annotation of org and eww files (~org-remark~)]]
  - [[#the-nebkor-studyel-section-for-flashcards-org-fc][The =nebkor-study.el= section for flashcards (~org-fc~)]]
  - [[#the-nebkor-studyel-section-for-table-of-contents-toc-org][The =nebkor-study.el= section for table of contents (~toc-org~)]]
  - [[#the-nebkor-studyel-section-for-archiving-web-content-org-board][The =nebkor-study.el= section for archiving web content (~org-board~)]]
  - [[#finally-we-provide-the-nebkor-studyel-module][Finally, we provide the =nebkor-study.el= module]]
- [[#custom-libraries][Custom libraries]]
  - [[#the-prot-commonel-library][The =prot-common.el= library]]
  - [[#the-prot-embarkel-library][The =prot-embark.el= library]]
  - [[#the-prot-windowel-library][The =prot-window.el= library]]
  - [[#the-nebkor-personalel-module][The =nebkor-personal.el= module]]

* Introduction
:PROPERTIES:
:CUSTOM_ID: h:D8816D6D-3B30-4ED5-9AE5-892029BA6C24
:CREATED:  <2024-12-30>
:END:

This configuration is inspired by [[https://github.com/unravel-team/emacs][Vedang's Prot-inspired literate config]]. As in
Vedang's, any unattributed quotes are taken directly from [[https://github.com/protesilaos/dotfiles/blob/master/emacs/.emacs.d/prot-emacs.org][Prot's org file]]. Also as in Vedang's, any
errors are probably his ;)

To make a change to this Emacs configuration, edit this file and then
type =C-c C-v C-t= (=M-x org-babel-tangle=) to republish all the
relevant files.

#+begin_quote
What you are now reading is not a common literate configuration of
Emacs. In most such cases, you have a generic =init.el= with a call to
the ~org-babel-load-file~ function that gets an Org document as its
value. That method works but is very slow, because we have to load Org
before starting Emacs (and Org loads a bunch of other things we do not
need at such an early stage).

Whereas this Org document serves as (i) a single point of entry to my
Emacs setup and (ii) the origin of all of my Emacs configurations.
While I am defining everything in a single Org file, I am not actually
starting Emacs by reading this file. Rather, I am instructing Org to
put the code blocks defined herein in standalone files, organised by
scope. The end result is something where you cannot tell whether a
literate program was executed or not.

This is the beauty of it. I can keep editing a single file as the
"source of truth", though I can still handle each of the files
individually (e.g. someone wants to see how I do a specific thing, so
I share only that file as an email attachment---no need to send over
this massive document).

When I want to modify my Emacs setup, I edit this file and then
evaluate the following code block or do =C-c C-v C-t=. All files will
be updated accordingly.
#+end_quote

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


* The =early-init.el= file

#+begin_quote
This is the first file that Emacs reads when starting up. It should
contain code that does not depend on any package or the proportions of
the Emacs frame. In general, this early initialisation file is meant
to set up a few basic things before Emacs produces the initial frame
by delegating to the =init.el
#+end_quote

** The =early-init.el= basic frame settings
:PROPERTIES:
:CUSTOM_ID: h:a1288a07-93f6-4e14-894e-707d5ad8b6dc
:ID:       01JGD3335N000DX6AG8WW5NJZA
:END:

#+begin_src emacs-lisp :tangle "early-init.el"
  ;; early-init.el -*- lexical-binding: t; -*-

  ;;;; No GUI
  ;; I do not use those graphical elements by default, but I do enable
  ;; them from time-to-time for testing purposes or to demonstrate
  ;; something.  NEVER tell a beginner to disable any of these.  They
  ;; are helpful.
  (menu-bar-mode -1)
  (scroll-bar-mode -1)
  (tool-bar-mode -1)
#+end_src

** The =early-init.el= tweaks to startup time and garbage collection
:PROPERTIES:
:CUSTOM_ID: h:50d28f3c-3ada-4db5-b830-bbbbee7fec4e
:ID:       01JGD3335V000DC3NGG4C0CBBH
:END:

#+begin_src emacs-lisp :tangle "early-init.el"
  ;; A big contributor to startup times is garbage collection.

  ;; We up the gc threshold to temporarily prevent it from running, then
  ;; reset it later after startup is complete. Not resetting it will
  ;; cause stuttering/freezes.

  (setq gc-cons-threshold most-positive-fixnum)
  (setq gc-cons-percentage 0.5)

  ;; Same idea as above for the `file-name-handler-alist' and the
  ;; `vc-handled-backends' with regard to startup speed optimisation.
  ;; Here I am storing the default value with the intent of restoring it
  ;; via the `emacs-startup-hook'.
  (defvar prot-emacs--file-name-handler-alist file-name-handler-alist)
  (defvar prot-emacs--vc-handled-backends vc-handled-backends)

  (setq file-name-handler-alist nil
        vc-handled-backends nil)

  (add-hook 'emacs-startup-hook
            (lambda ()
              (setq gc-cons-threshold (* 1000 1000 8))
              (setq gc-cons-percentage 0.1)
              (setq file-name-handler-alist prot-emacs--file-name-handler-alist)
              (setq vc-handled-backends prot-emacs--vc-handled-backends)))
#+end_src

** If you have both .el and .elc files, load the newer one
:PROPERTIES:
:CUSTOM_ID: h:F8987E20-3E36-4E27-9EAE-D0680303A95B
:ID:       01JGD33360000BCR54T9VJDAYE
:END:

#+begin_src emacs-lisp :tangle "early-init.el"
  ;; When both .el and .elc / .eln files are available,
  ;; load the latest one.

  (setq load-prefer-newer t)
#+end_src

** The =early-init.el= package cache settings
:PROPERTIES:
:CUSTOM_ID: h:7a037504-8a2f-4df0-8482-ce6476354440
:ID:       01JGD33365000DGT26EDXZ3SVD
:END:

Do not initialize the package cache, we want to use ~elpaca~ instead of ~package.el~

#+begin_src emacs-lisp :tangle "early-init.el"
  ;; Make sure that we do not enable `package.el', so that we can use
  ;; `elpaca' instead.
  (setq package-enable-at-startup nil)
#+end_src

** Finally, the =early-init.el= local variable settings
:PROPERTIES:
:CUSTOM_ID: h:C65F8419-568D-4B37-B864-5FF29F72F17B
:CREATED:  [2024-12-10 Tue 14:39]
:ID:       01JGD3336B0008SMGXGH47ABD3
:END:

Note that this should be the last section of the ~early-init.el~ file.

#+begin_src emacs-lisp :tangle "early-init.el"
  ;; Local Variables:
  ;; no-byte-compile: t
  ;; no-native-compile: t
  ;; no-update-autoloads: t
  ;; End:
#+end_src

* The =init.el= file
:PROPERTIES:
:CUSTOM_ID: h:dae63bd9-93a8-41c4-af1b-d0f39ba50974
:END:

This is the main initialisation file of Emacs. Everything loads from here, even if it has been split
into multiple files for convenience.

** The =init.el= tweaks to make native compilation silent
:PROPERTIES:
:CUSTOM_ID: h:3563ceb5-b70c-4191-9c81-f2f5a202c4da
:ID:       01JGD3336G0004H4FS2D0AYMA0
:END:

These warnings are unnecessarily scary.

#+begin_quote
The =--with-native-compilation=yes= build option of Emacs is very
nice: it enables the "native compilation" of Emacs Lisp, translating
it down to machine code. However, the default setting for reporting
errors is set to a verbose value which, in my coaching experience,
confuses users: it produces warnings for compilation issues that only
the developer of the given package needs to deal with. These include
innocuous facts like docstrings being wider than a certain character
count. To make things even worse, the buffer that shows these warnings
uses the stop sign character, resulting in a long list of lines with
red spots everywhere, as if we have totally broken Emacs.
#+end_quote

#+begin_src emacs-lisp :tangle "init.el"
  ;; init.el -*- lexical-binding: t; -*-

  (set-language-environment "UTF-8")

  ;; Make native compilation silent and prune its cache.
  (setq native-compile-prune-cache t)
#+end_src

** The =init.el= setting to send ~custom-file~ to oblivion
:PROPERTIES:
:CUSTOM_ID: h:f2ffe0e9-a58d-4bba-9831-cc35940ea83f
:ID:       01JGD3336P00087XGHY73810B9
:END:

There is no need to use the =M-x customize= infrastructure. It's easier to just rely on the init
file instead. But I'll get there in baby steps, so for now, load my custom file.

#+begin_quote
I would prefer to just have an option to avoid the Custom
infrastructure altogether, but this is not possible. So here we are...
#+end_quote

#+begin_src emacs-lisp :tangle "init.el"
  (setq custom-file "~/.emacs.d/custom.el")
  (load custom-file)
  ;; Disable custom.el by making it disposable.
  ;; (setq custom-file (make-temp-file "emacs-custom-"))
#+end_src

** The =init.el= settings to enable commands disabled by default
:PROPERTIES:
:CUSTOM_ID: h:4ed6593f-6f55-4258-a1c2-ddb50e9e2465
:ID:       01JGD3336W000EAX31DZ1A75R4
:END:

These commands are actually useful, especially in org-mode.

#+begin_src emacs-lisp :tangle "init.el"
;; Enable these commands which have been disabled by default
(mapc
 (lambda (command)
   (put command 'disabled nil))
 '(list-timers narrow-to-region narrow-to-page upcase-region downcase-region))
#+end_src

** The =init.el= settings to disable unnecessary commands enabled by default
:PROPERTIES:
:ID:       01JGD333710006SE327410PMMM
:END:

These commands are "unsafe", in that we should be using the alternatives (like ~vterm~ and ~org~)

#+begin_src emacs-lisp :tangle "init.el"
;; Disable these commands which have been enabled by default
(mapc
 (lambda (command)
   (put command 'disabled t))
 '(eshell project-eshell overwrite-mode iconify-frame diary))
#+end_src

** The =init.el= section to add the modules folder to the load-path
:PROPERTIES:
:CUSTOM_ID: h:e289a614-4f17-4d6c-a028-42fe45aebe66
:ID:       01JGD333760004GJ8RRH5D0Z0W
:END:

This is where all the custom configuration sits for all the packages we use. We write configuration
on a per-file basis instead of in a giant file, because these smaller files are more readable,
approachable and shareable.

#+begin_src emacs-lisp :tangle "init.el"
  (mapc
   (lambda (string)
     (add-to-list 'load-path (locate-user-emacs-file string)))
   '("nebkor-modules" "custom-lisp"))
#+end_src

** COMMENT The =init.el= settings for packages (=package.el=)
:PROPERTIES:
:CUSTOM_ID: h:424340cc-f3d7-4083-93c9-d852d40dfd40
:END:

#+begin_quote
The =package.el= is built into Emacs and is perfectly fine for my
use-case. We do not need to load it explicitly, as it will be called
by ~use-package~ when it needs it. Since the introduction of the
=early-init.el= file, we also do not need to initialise the packages
at this point: we activate the cache instead ([[#h:7a037504-8a2f-4df0-8482-ce6476354440][The =early-init.el= initialises the package cache]]).

With regard to the settings here, make sure to read my article about
package archives, pinning packages, and setting priorities:
<https://protesilaos.com/codelog/2022-05-13-emacs-elpa-devel/>.
#+end_quote

#+begin_src emacs-lisp :tangle "init.el"
  ;;;; Packages

  (setq package-vc-register-as-project nil) ; Emacs 30

  (add-hook 'package-menu-mode-hook #'hl-line-mode)

  ;; Also read: <https://protesilaos.com/codelog/2022-05-13-emacs-elpa-devel/>
  (setq package-archives
        '(("gnu-elpa" . "https://elpa.gnu.org/packages/")
          ("gnu-elpa-devel" . "https://elpa.gnu.org/devel/")
          ("nongnu" . "https://elpa.nongnu.org/nongnu/")
          ("melpa" . "https://melpa.org/packages/")))

  ;; Highest number gets priority (what is not mentioned has priority 0)
  (setq package-archive-priorities
        '(("gnu-elpa" . 3)
          ("melpa" . 2)
          ("nongnu" . 1)))

  ;; Let `package-install' suggest upgrades for built-in packages too.
  (setq package-install-upgrade-built-in t)
#+end_src

** The =init.el= section for using the Elpaca package manager (~elpaca~)
:PROPERTIES:
:CUSTOM_ID: h:13B17ABF-19E3-4723-9B72-E1201F7298AA
:CREATED:  [2024-12-10 Tue 14:43]
:ID:       01JGD3337C000BVH5SAFT1D9BR
:END:

I use ~elpaca~ for package management, and ~use-package~ for
configuration management. This means that ~elpaca~ replaces the
built-in ~package.el~ functionality.

When using ~elpaca~, here are some gotchas you should be aware of
(Note: these only apply for =:ensure t= cases):

1. Use ~elpaca-after-init-hook~ instead of ~after-init-hook~ or
   ~emacs-startup-hook~. Elpaca runs in the background and does not
   guarantee order. Using this special hook ensures that config is
   loaded in the expected order.
2. Prefer ~:demand~ or ~:wait~ over ~:defer~, especially when
   deferring for a certain number of seconds. Do not make any
   assumptions about order of execution.

#+begin_src emacs-lisp :tangle "init.el"
  ;;; Install Elpaca

  (defvar elpaca-installer-version 0.8)
  (defvar elpaca-directory (expand-file-name "elpaca/" user-emacs-directory))
  (defvar elpaca-builds-directory (expand-file-name "builds/" elpaca-directory))
  (defvar elpaca-repos-directory (expand-file-name "repos/" elpaca-directory))
  (defvar elpaca-order '(elpaca :repo "https://github.com/progfolio/elpaca.git"
                                :ref nil :depth 1
                                :files (:defaults "elpaca-test.el" (:exclude "extensions"))
                                :build (:not elpaca--activate-package)))
  (let* ((repo  (expand-file-name "elpaca/" elpaca-repos-directory))
         (build (expand-file-name "elpaca/" elpaca-builds-directory))
         (order (cdr elpaca-order))
         (default-directory repo))
    (add-to-list 'load-path (if (file-exists-p build) build repo))
    (unless (file-exists-p repo)
      (make-directory repo t)
      (when (< emacs-major-version 28) (require 'subr-x))
      (condition-case-unless-debug err
          (if-let* ((buffer (pop-to-buffer-same-window "*elpaca-bootstrap*"))
                    ((zerop (apply #'call-process `("git" nil ,buffer t "clone"
                                                    ,@(when-let* ((depth (plist-get order :depth)))
                                                        (list (format "--depth=%d" depth) "--no-single-branch"))
                                                    ,(plist-get order :repo) ,repo))))
                    ((zerop (call-process "git" nil buffer t "checkout"
                                          (or (plist-get order :ref) "--"))))
                    (emacs (concat invocation-directory invocation-name))
                    ((zerop (call-process emacs nil buffer nil "-Q" "-L" "." "--batch"
                                          "--eval" "(byte-recompile-directory \".\" 0 'force)")))
                    ((require 'elpaca))
                    ((elpaca-generate-autoloads "elpaca" repo)))
              (progn (message "%s" (buffer-string)) (kill-buffer buffer))
            (error "%s" (with-current-buffer buffer (buffer-string))))
        ((error) (warn "%s" err) (delete-directory repo 'recursive))))
    (unless (require 'elpaca-autoloads nil t)
      (require 'elpaca)
      (elpaca-generate-autoloads "elpaca" repo)
      (load "./elpaca-autoloads")))
  (add-hook 'after-init-hook #'elpaca-process-queues)
  (elpaca `(,@elpaca-order))

  ;; Install use-package support for Elpaca
  (elpaca elpaca-use-package
    ;; Enable use-package :ensure support for Elpaca.
    (elpaca-use-package-mode))
#+end_src

** The =init.el= macro to do nothing with Elisp code (~prot-emacs-comment~)
:PROPERTIES:
:CUSTOM_ID: h:3b14faa6-83fd-4d5f-b3bc-85f72fd572d4
:ID:       01JGD3337H0005C5TPY1Q8PVZS
:END:

#+begin_quote
This is something I learnt while studying Clojure: a ~comment~ macro
that wraps some code, effectively commenting it out, while keeping
indentation and syntax highlighting intact.

What I have here is technically not commenting out the code, because
the expansion of the macro is nil, not the actual code with comments
around it.
#+end_quote

#+begin_example emacs-lisp
(defmacro prot-emacs-comment (&rest body)
  "Do nothing with BODY and return nil, with no side effects."
  (declare (indent defun))
  nil)
#+end_example

#+begin_quote
The above is an example. What I actually use is the following. It
behaves the same as above, except when it reads a plist of the form
=(:eval t)=. The idea is for me to quickly activate something I want
to test by passing that to the macro. So here we have it:
#+end_quote

#+begin_src emacs-lisp :tangle "init.el"
(defmacro prot-emacs-comment (&rest body)
  "Determine what to do with BODY.

If BODY contains an unquoted plist of the form (:eval t) then
return BODY inside a `progn'.

Otherwise, do nothing with BODY and return nil, with no side
effects."
  (declare (indent defun))
  (let ((eval))
    (dolist (element body)
      (when-let* (((plistp element))
                  (key (car element))
                  ((eq key :eval))
                  (val (cadr element)))
        (setq eval val
              body (delq element body))))
    (when eval `(progn ,@body))))
#+end_src

** The =init.el= macro to define abbreviations (~prot-emacs-abbrev~)
:PROPERTIES:
:CUSTOM_ID: h:e7a12825-7848-42bd-b99b-b87903012814
:ID:       01JGD3337P0004RW7VP6HFQW7F
:END:

[ Watch Prot's video: [[https://protesilaos.com/codelog/2024-02-03-emacs-abbrev-mode/][abbreviations with abbrev-mode (quick text expansion)]] (2024-02-03). ]

#+begin_src emacs-lisp :tangle "init.el"
(defmacro prot-emacs-abbrev (table &rest definitions)
  "Expand abbrev DEFINITIONS for the given TABLE.
DEFINITIONS is a sequence of (i) string pairs mapping the
abbreviation to its expansion or (ii) a string and symbol pair
making an abbreviation to a function."
  (declare (indent 1))
  (unless (zerop (% (length definitions) 2))
    (error "Uneven number of key+command pairs"))
  `(if (abbrev-table-p ,table)
       (progn
         ,@(mapcar
            (lambda (pair)
              (let ((abbrev (nth 0 pair))
                    (expansion (nth 1 pair)))
                (if (stringp expansion)
                    `(define-abbrev ,table ,abbrev ,expansion)
                  `(define-abbrev ,table ,abbrev "" ,expansion))))
            (seq-split definitions 2)))
     (error "%s is not an abbrev table" ,table)))
#+end_src

** The =init.el= section to load the individual modules
:PROPERTIES:
:CUSTOM_ID: h:e6c4acf5-5b51-4b38-a86a-bf3f698ac872
:ID:       01JGD3337W000DMNV6V386R0Q4
:END:

Now we are ready to load our per-module configuration files:

#+begin_src emacs-lisp :tangle "init.el"
  (require 'nebkor-theme)
  (require 'nebkor-essentials)
  (require 'nebkor-functions)
  (require 'nebkor-completion)
  (require 'nebkor-search)
  (require 'nebkor-dired)
  (require 'nebkor-window)
  (require 'nebkor-git)
  (require 'nebkor-org)
  (require 'nebkor-langs)
  (require 'nebkor-study)
  ;;; Comment this next line if you don't want to use my personal
  ;;; settings (like specific directories or org variables)
  (require 'nebkor-personal)
#+end_src

** Finally, the =init.el= section for local variables
:PROPERTIES:
:ID:       01JGCWAQD5000DT5BKY7AC94Z4
:END:

#+begin_src emacs-lisp :tangle "init.el"
  ;; Local Variables:
  ;; no-byte-compile: t
  ;; no-native-compile: t
  ;; no-update-autoloads: t
  ;; End:
#+end_src

* The =nebkor-theme.el= module
:PROPERTIES:
:CUSTOM_ID: h:8cf67c82-1ebb-4be8-b0e7-161bbf5419ce
:ID:       01JGD333820001G48BX1C8MR2R
:END:

This module defines everything related to the aesthetics of Emacs.

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-theme.el" :mkdirp yes
  ;;; Everything related to the look of Emacs

#+end_src

** The =nebkor-theme.el= section for cool, modern themes (~ef-themes~)
:PROPERTIES:
:CUSTOM_ID: h:2b2a27a1-6d2e-4b59-bf60-94682e173f2f
:ID:       01JGD33387000F4AZ3ZBKKE507
:END:

I have an ancient color scheme that's mostly inside the custom.el file, but I'd like to make a
proper theme some day. Until then...

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-theme.el"
  ;;(add-to-list 'default-frame-alist '(background-color . "snow"))
#+end_src

** The =nebkor-theme.el= section for highlighting lines (~lin~)
:PROPERTIES:
:CUSTOM_ID: h:bf5b4d08-8f33-4a8c-8ecd-fca19bf2497a
:ID:       01JGD3338D000CFHSKXPKGB71Y
:END:

~lin~ is an improvement on ~hl-line-mode~.

Prot is the lead developer and maintainer of this package.

+ Package name (GNU ELPA): ~lin~
+ Official manual: <https://protesilaos.com/emacs/lin>
+ Change log: <https://protesilaos.com/emacs/lin-changelog>
+ Git repositories:
  - GitHub: <https://github.com/protesilaos/lin>
  - GitLab: <https://gitlab.com/protesilaos/lin>

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-theme.el"
  ;;;; Lin
  ;; Read the lin manual: <https://protesilaos.com/emacs/lin>.
  (use-package lin
    :ensure t
    :hook (elpaca-after-init . lin-global-mode) ; applies to all `lin-mode-hooks'
    :config
    (setopt lin-face 'lin-cyan))
#+end_src

** The =nebkor-theme.el= section for color previews (~rainbow-mode~)
:PROPERTIES:
:CUSTOM_ID: h:9438236e-a8a4-45e0-8c61-8268c634d50b
:ID:       01JGD3338R0006WTSGQED8JAE5
:END:

#+begin_quote
This package produces an in-buffer preview of a colour value. I use
those while developing my themes, hence the ~prot/rainbow-mode-in-themes~
to activate ~rainbow-mode~ if I am editing a theme file.
#+end_quote

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-theme.el"
;;;; Rainbow mode for colour previewing (rainbow-mode.el)
(use-package rainbow-mode
  :ensure t
  :init
  (setq rainbow-ansi-colors nil)
  (setq rainbow-x-colors nil)

  (defun prot/rainbow-mode-in-themes ()
    (when-let* ((file (buffer-file-name))
                ((derived-mode-p 'emacs-lisp-mode))
                ((string-match-p "-theme" file)))
      (rainbow-mode 1)))
  :bind ( :map ctl-x-x-map
          ("c" . rainbow-mode)) ; C-x x c
  :hook (emacs-lisp-mode . prot/rainbow-mode-in-themes))
#+end_src

** The =nebkor-theme.el= section for cursor styles (~cursory~)
:PROPERTIES:
:CUSTOM_ID: h:34ce98fe-0b57-44d9-b5f3-0224632114a5
:ID:       01JGD3338X0004DJKAPD4MFQCV
:END:

#+begin_quote
My ~cursory~ package provides a thin wrapper around built-in variables
that affect the style of the Emacs cursor on graphical terminals. The
intent is to allow the user to define preset configurations such as
"block with slow blinking" or "bar with fast blinking" and set them on
demand. The use-case for such presets is to adapt to evolving
interface requirements and concomitant levels of expected comfort,
such as in the difference between writing and reading.
#+end_quote

Prot is the lead developer and maintainer.

+ Package name (GNU ELPA): ~cursory~
+ Official manual: <https://protesilaos.com/emacs/cursory>
+ Change log: <https://protesilaos.com/emacs/cursory-changelog>
+ Git repositories:
  - GitHub: <https://github.com/protesilaos/cursory>
  - GitLab: <https://gitlab.com/protesilaos/cursory>

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-theme.el"
;;; Cursor appearance (cursory)
;; Read the manual: <https://protesilaos.com/emacs/cursory>.
(use-package cursory
  :ensure t
  :demand t
  :if (display-graphic-p)
  :config
  (setq cursory-presets
        '((box
           :blink-cursor-interval 1.2)
          (box-no-blink
           :blink-cursor-mode -1)
          (bar
           :cursor-type (bar . 2)
           :blink-cursor-interval 0.8)
          (bar-no-other-window
           :inherit bar
           :cursor-in-non-selected-windows nil)
          (bar-no-blink
           :cursor-type (bar . 2)
           :blink-cursor-mode -1)
          (underscore
           :cursor-type (hbar . 3)
           :blink-cursor-interval 0.3
           :blink-cursor-blinks 50)
          (underscore-no-other-window
           :inherit underscore
           :cursor-in-non-selected-windows nil)
          (underscore-thick
           :cursor-type (hbar . 8)
           :blink-cursor-interval 0.3
           :blink-cursor-blinks 50
           :cursor-in-non-selected-windows (hbar . 3))
          (underscore-thick-no-blink
           :blink-cursor-mode -1
           :cursor-type (hbar . 8)
           :cursor-in-non-selected-windows (hbar . 3))
          (t ; the default values
           :cursor-type box
           :cursor-in-non-selected-windows hollow
           :blink-cursor-mode 1
           :blink-cursor-blinks 5
           :blink-cursor-interval 0.2
           :blink-cursor-delay 0.2)))

  ;; I am using the default values of `cursory-latest-state-file'.

  ;; Set last preset or fall back to desired style from `cursory-presets'.
  (cursory-set-preset (or (cursory-restore-latest-preset) 'box-no-blink))

  (cursory-mode 1))
#+end_src

** The =nebkor-theme.el= section about font styles (~fontaine~)
:PROPERTIES:
:CUSTOM_ID: h:cb41fef0-41a5-4a85-9552-496d96290258
:ID:       01JGD333B5000D6VCHQWSPH6SY
:END:

[ Watch Prot's video: [[https://protesilaos.com/codelog/2024-01-16-customize-emacs-fonts/][Customise Emacs fonts]] (2024-01-16) ]

#+begin_quote
My ~fontaine~ package allows the user to define detailed font
configurations and set them on demand. For example, one can have a
=regular-editing= preset and another for =presentation-mode= (these
are arbitrary, user-defined symbols): the former uses small fonts
which are optimised for writing, while the latter applies typefaces
that are pleasant to read at comfortable point sizes.
#+end_quote

Prot is the lead developer and maintainer.

+ Package name (GNU ELPA): ~fontaine~
+ Official manual: <https://protesilaos.com/emacs/fontaine>
+ Change log: <https://protesilaos.com/emacs/fontaine-changelog>
+ Git repositories:
  - GitHub: <https://github.com/protesilaos/fontaine>
  - GitLab: <https://gitlab.com/protesilaos/fontaine>

Another section defines some complementary functionality
([[#h:60d6aae2-6e4b-402c-b6a8-411fc49a6857][The =nebkor-theme.el= section about ~variable-pitch-mode~ and font resizing]]).

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-theme.el"
  ;;;; Fontaine (font configurations)
  ;; Read the manual: <https://protesilaos.com/emacs/fontaine>
  (use-package fontaine
    :ensure t
    :if (display-graphic-p)
    :hook
    ;; Persist the latest font preset when closing/starting Emacs and
    ;; while switching between themes.
    ((elpaca-after-init . fontaine-mode)
     (elpaca-after-init . (lambda ()
                     ;; Set last preset or fall back to desired style from `fontaine-presets'.
                     (fontaine-set-preset (or (fontaine-restore-latest-preset) 'regular))))
     (enable-theme-functions . fontaine-apply-current-preset))
    :config
    ;; This is defined in Emacs C code: it belongs to font settings.
    (setq x-underline-at-descent-line nil)

    ;; This is the default value.  Just including it here for
    ;; completeness.
    (setq fontaine-latest-state-file (locate-user-emacs-file "fontaine-latest-state.eld"))

    (setq fontaine-presets
          '((small
             :default-family "Noto Sans Mono"
             :default-height 130)
            (regular
             :default-height 150)
            (medium
             :default-weight semilight
             :default-height 170
             :bold-weight extrabold)
            (large
             :inherit medium
             :default-height 190)
            (presentation
             :inherit medium
             :default-height 250)
            (jumbo
             :inherit medium
             :default-height 330)
            (t
             ;; I keep all properties for didactic purposes, but most can be
             ;; omitted.  See the fontaine manual for the technicalities:
             ;; <https://protesilaos.com/emacs/fontaine>.
             :default-family "Noto Sans Mono"
             :default-weight regular
             :default-slant normal
             :default-width normal
             :default-height 150

             :fixed-pitch-family nil ; falls back to :default-family
             :fixed-pitch-weight nil ; falls back to :default-weight
             :fixed-pitch-slant nil
             :fixed-pitch-width nil
             :fixed-pitch-height 1.0

             :fixed-pitch-serif-family nil
             :fixed-pitch-serif-weight nil
             :fixed-pitch-serif-slant nil
             :fixed-pitch-serif-width nil
             :fixed-pitch-serif-height 1.0

             :variable-pitch-family "Iosevka"
             :variable-pitch-weight nil
             :variable-pitch-slant nil
             :variable-pitch-width nil
             :variable-pitch-height 1.0

             :mode-line-active-family nil
             :mode-line-active-weight nil
             :mode-line-active-slant nil
             :mode-line-active-width 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-width nil
             :mode-line-inactive-height 1.0

             :header-line-family nil
             :header-line-weight nil
             :header-line-slant nil
             :header-line-width nil
             :header-line-height 1.0

             :line-number-family nil
             :line-number-weight nil
             :line-number-slant nil
             :line-number-width nil
             :line-number-height 1.0

             :tab-bar-family nil
             :tab-bar-weight nil
             :tab-bar-slant nil
             :tab-bar-width nil
             :tab-bar-height 1.0

             :tab-line-family nil
             :tab-line-weight nil
             :tab-line-slant nil
             :tab-line-width nil
             :tab-line-height 1.0

             :bold-family nil
             :bold-slant nil
             :bold-weight bold
             :bold-width nil
             :bold-height 1.0

             :italic-family nil
             :italic-weight nil
             :italic-slant italic
             :italic-width nil
             :italic-height 1.0

             :line-spacing nil))))
#+end_src

** The =nebkor-theme.el= section for font previews (~show-font~)
:PROPERTIES:
:CUSTOM_ID: h:60a005be-77bd-49f1-a865-78d7cf75bd2a
:ID:       01JGD333BC000BGHW3SXPF8ZQG
:END:

#+begin_quote
This is yet another package of mine. It lets you preview a font inside
of Emacs. It does so in three ways:

- Prompt for a font on the system and display it in a buffer.
- List all known fonts in a buffer, with a short preview for each.
- Provide a major mode to preview a font whose file is among the
  installed ones.
#+end_quote

Prot is the developer and maintainer of this package.

+ Package name (GNU ELPA): ~show-font~
+ Official manual: <https://protesilaos.com/emacs/show-font>
+ Change log: <https://protesilaos.com/emacs/show-font-changelog>
+ Git repository: <https://github.com/protesilaos/show-font>

To actually set fonts, use the ~fontaine~ package ([[#h:cb41fef0-41a5-4a85-9552-496d96290258][The =nebkor-theme.el= section about ~fontaine~]]).

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-theme.el"
  ;;;; Show Font (preview fonts)
  ;; Read the manual: <https://protesilaos.com/emacs/show-font>
  (use-package show-font
    :ensure t
    :commands (show-font-select-preview show-font-list)
    :config
    ;; These are the defaults, but I keep them here for easier access.
    (setq show-font-pangram 'prot)
    (setq show-font-character-sample
          "
  ABCDEFGHIJKLMNOPQRSTUVWXYZ
  abcdefghijklmnopqrstuvwxyz
  0123456789   !@#$¢%^&*~|
  `'\"‘’“”.,;:  ()[]{}—-_+=<>

  ()[]{}<>«»‹› 6bB8&0ODdoa 1tiIlL|\/
  !ij c¢ 5$Ss 7Z2z 9gqp nmMNNMW uvvwWuuw
  x×X .,·°;:¡!¿?`'‘’   ÄAÃÀ TODO
  "))
#+end_src

** The =nebkor-theme.el= section about font resizing (~variable-pitch-mode~)
:PROPERTIES:
:CUSTOM_ID: h:60d6aae2-6e4b-402c-b6a8-411fc49a6857
:ID:       01JGD333BH000DSVZXX8Q4965R
:END:

[ Watch Prot's video: [[https://protesilaos.com/codelog/2024-01-16-customize-emacs-fonts/][Customise Emacs fonts]] (2024-01-16) ]

#+begin_quote
The built-in ~variable-pitch-mode~ makes the current buffer use a
proportionately spaced font. In technical terms, it remaps the
~default~ face to ~variable-pitch~, so whatever applies to the latter
takes effect over the former. I take care of their respective font
families in my ~fontaine~ setup ([[#h:cb41fef0-41a5-4a85-9552-496d96290258][The =nebkor-theme.el= section about ~fontaine~]]).

I want to activate ~variable-pitch-mode~ in all buffers where I
normally focus on prose. The exact mode hooks are specified in the
variable =prot/enable-variable-pitch-in-hooks=. Exceptions to these
are major modes that I do not consider related to prose (and which in
my opinion should not be derived from ~text-mode~): these are excluded
in the function ~prot/enable-variable-pitch~.
#+end_quote

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-theme.el"
;;;;; `variable-pitch-mode' setup
(use-package face-remap
  :ensure nil
  :functions prot/enable-variable-pitch
  :bind ( :map ctl-x-x-map
          ("v" . variable-pitch-mode))
  :hook ((text-mode notmuch-show-mode elfeed-show-mode) . prot/enable-variable-pitch)
  :config
  ;; NOTE 2022-11-20: This may not cover every case, though it works
  ;; fine in my workflow.  I am still undecided by EWW.
  (defun prot/enable-variable-pitch ()
    (unless (derived-mode-p 'mhtml-mode 'nxml-mode 'yaml-mode 'prog-mode)
      (variable-pitch-mode 1)))
;;;;; Resize keys with global effect
  :bind
  ;; Emacs 29 introduces commands that resize the font across all
  ;; buffers (including the minibuffer), which is what I want, as
  ;; opposed to doing it only in the current buffer.  The keys are the
  ;; same as the defaults.
  (("C-x C-=" . global-text-scale-adjust)
   ("C-x C-+" . global-text-scale-adjust)
   ("C-x C-0" . global-text-scale-adjust)))
#+end_src

** Finally, we provide the =nebkor-theme.el= module
:PROPERTIES:
:CUSTOM_ID: h:bac0ce0a-db68-42e7-ba2c-f350f91f80ef
:ID:       01JGD333BQ000CWYYVX8CY9XAJ
:END:

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-theme.el"
(provide 'nebkor-theme)
#+end_src

* The =nebkor-essentials.el= module
:PROPERTIES:
:CUSTOM_ID: h:0ef52ed9-7b86-4329-ae4e-eff9ab8d07f2
:END:

** The =nebkor-essentials.el= block with basic configurations
:PROPERTIES:
:CUSTOM_ID: h:713ede33-3802-40c6-a8e3-7e1fc0d0a924
:ID:       01JGD333BW00026259X7RRF0TT
:END:

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-essentials.el" :mkdirp yes
  ;;; Essential configurations
  (use-package emacs
    :ensure nil
    :demand t
    :config
  ;;;; General settings and common custom functions
    (setq-default truncate-partial-width-windows nil)
    (setq column-number-mode t
          debug-on-error t
          echo-keystrokes-help t
          epa-keys-select-method 'minibuffer
          eval-expression-print-length nil
          find-file-visit-truename t
          find-library-include-other-files nil
          fringe-mode '(1 . 0)
          global-auto-revert-mode t
          global-display-line-numbers-mode nil
          help-window-select t
          inhibit-startup-message t
          initial-scratch-message nil
          kill-do-not-save-duplicates t
          mode-require-final-newline t
          next-error-recenter '(4) ; center of the window
          save-interprogram-paste-before-kill t
          scroll-error-top-bottom t
          tramp-connection-timeout (* 60 10))) ; seconds
#+end_src

** The =nebkor-essentials.el= section for fixing PATH on OSX (~exec-path-from-shell~)
:PROPERTIES:
:CUSTOM_ID: h:D4517B67-0D90-417E-97D7-60A08EABB3DA
:ID:       01JGD333C1000DW1VBA3PQGRYH
:END:

The ~PATH~ variable does not get set properly on Mac OSX and Windows machines, and due to this Emacs often does not find the right executables when calling external programs. ~exec-path-from-shell~ fixes this.

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-essentials.el"
  (use-package exec-path-from-shell
    :if (memq (window-system) '(mac ns))
    :ensure t
    :demand t
    :config
    (exec-path-from-shell-initialize))
#+end_src

** The =nebkor-essentials.el= configuration to track recently visited files (~recentf~)
:PROPERTIES:
:CUSTOM_ID: h:f9aa7523-d88a-4080-add6-073f36cb8b9a
:ID:       01JGD333C70005AM4ECT7FR4E1
:END:

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-essentials.el"
(use-package recentf
  :ensure nil
  :hook (elpaca-after-init . recentf-mode)
  :config
  (setq recentf-max-saved-items 100)
  (setq recentf-max-menu-items 25) ; I don't use the `menu-bar-mode', but this is good to know
  (setq recentf-save-file-modes nil)
  (setq recentf-keep nil)
  (setq recentf-auto-cleanup nil)
  (setq recentf-initialize-file-name-history nil)
  (setq recentf-filename-handlers nil)
  (setq recentf-show-file-shortcuts-flag nil))
#+end_src

** The =nebkor-essentials.el= configuration to track my place in visited files (~saveplace~)
:PROPERTIES:
:CUSTOM_ID: h:01DD3C5F-B871-408F-98F6-6B845921C541
:CREATED:  [2024-12-19 Thu 11:35]
:ID:       01JGD333CC0006A5AWJ6E9EKRC
:END:

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-essentials.el"
  (use-package saveplace
    :ensure nil
    :hook (elpaca-after-init . save-place-mode)
    :config
    (setq save-place-file (locate-user-emacs-file "saveplace")))
#+end_src

** The =nebkor-essentials.el= settings for bookmarks
:PROPERTIES:
:CUSTOM_ID: h:581aa0ff-b136-4099-a321-3b86edbfbccb
:ID:       01JGD333CJ000A5CTS5YD0HFCR
:END:

#+begin_quote
Bookmarks are compartments that store arbitrary information about a
file or buffer. The records are used to recreate that file/buffer
inside of Emacs. Put differently, we can easily jump back to a file or
directory (or anything that has a bookmark recorder+handler, really).
Use the ~bookmark-set~ command (=C-x r m= by default) to record a
bookmark and then visit one of your bookmarks with ~bookmark-jump~
(=C-x r b= by default).

Also see [[#h:5685df62-4484-42ad-a062-d55ab19022e3][the =nebkor-essentials.el= settings for registers]].
#+end_quote

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-essentials.el"
;;;; Built-in bookmarking framework (bookmark.el)
(use-package bookmark
  :ensure nil
  :commands (bookmark-set bookmark-jump bookmark-bmenu-list)
  :hook (bookmark-bmenu-mode . hl-line-mode)
  :config
  (setq bookmark-use-annotations nil)
  (setq bookmark-automatically-show-annotations nil)
  (setq bookmark-fringe-mark nil) ; Emacs 29 to hide bookmark fringe icon
  ;; Write changes to the bookmark file as soon as 1 modification is
  ;; made (addition or deletion).  Otherwise Emacs will only save the
  ;; bookmarks when it closes, which may never happen properly
  ;; (e.g. power failure).
  (setq bookmark-save-flag 1))
#+end_src

** The =nebkor-essentials.el= settings for registers
:PROPERTIES:
:CUSTOM_ID: h:5685df62-4484-42ad-a062-d55ab19022e3
:ID:       01JGD333CQ000796BXGRFT17V1
:END:

[ Watch Prot's video: [[https://protesilaos.com/codelog/2023-06-28-emacs-mark-register-basics/][Mark and register basics]] (2023-06-28). ]

#+begin_quote
Much like bookmarks, registers store data that we can reinstate
quickly ([[#h:581aa0ff-b136-4099-a321-3b86edbfbccb][The =nebkor-essentials.el= settings for bookmarks]]). A
common use-case is to write some text to a register and then insert
that text by calling the given register. This is much better than
relying on the ~kill-ring~, because registers are meant to be
overwritten by the user, whereas the ~kill-ring~ accumulates lots of
text that we do not necessarily need.

To me, registers are essential for keyboard macros. By default,
registers do not persist between Emacs sessions, though I do need to
re-use them from time to time, hence the arrangement to record them
with ~savehist-mode~ ([[#h:25765797-27a5-431e-8aa4-cc890a6a913a][The =nebkor-completion.el= settings for saving the history (~savehist-mode~)]]).
#+end_quote

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-essentials.el"
;;;; Registers (register.el)
(use-package register
  :ensure nil
  :defer t ; its commands are autoloaded, so this will be loaded then
  :config
  (setq register-preview-delay 0.8
        register-preview-function #'register-preview-default)

  (with-eval-after-load 'savehist
    (add-to-list 'savehist-additional-variables 'register-alist)))
#+end_src

** The =nebkor-essentials.el= settings for files
:PROPERTIES:
:ID:       01JGD333CW0009W0AE8Q6VNSFA
:END:

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-essentials.el"
  (use-package files
    :ensure nil
    :config
    (setq confirm-kill-emacs #'y-or-n-p)
    (setq require-final-newline t)
    (setq backup-directory-alist ;; Put the ~ files in tmp
          `(("." . ,(locate-user-emacs-file "temp-files/backups")))))
#+end_src

** The =nebkor-essentials.el= section for ~delete-selection-mode~
:PROPERTIES:
:CUSTOM_ID: h:d551b90d-d730-4eb5-976a-24b010fd4db3
:ID:       01JGD333D20000FVSM2MNMZ6WV
:END:

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-essentials.el"
;;;; Delete selection
(use-package delsel
  :ensure nil
  :hook (elpaca-after-init . delete-selection-mode))
#+end_src

** The =nebkor-essentials.el= settings for tooltips
:PROPERTIES:
:CUSTOM_ID: h:26afeb95-7920-45ed-8ff6-3648256c280b
:ID:       01JGD333D800059VVCWZ50M8SJ
:END:

#+begin_quote
With these settings in place, Emacs will use its own faces and frame
infrastructure to display tooltips. I prefer it this way because then
we can benefit from the text properties that can be added to these
messages (e.g. a different colour or a slant).
#+end_quote

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-essentials.el"
;;;; Tooltips (tooltip-mode)
(use-package tooltip
  :ensure nil
  :hook (elpaca-after-init . tooltip-mode)
  :config
  (setq tooltip-delay 0.5
        tooltip-short-delay 0.5
        x-gtk-use-system-tooltips t
        tooltip-frame-parameters
        '((name . "tooltip")
          (internal-border-width . 10)
          (border-width . 0)
          (no-special-glyphs . t))))
#+end_src

** The =nebkor-essentials.el= arrangement to run Emacs as a server
:PROPERTIES:
:CUSTOM_ID: h:7709b7e9-844f-49f3-badf-784aacec4bca
:ID:       01JGD333DD00072XGXC6TJPWHF
:END:

#+begin_quote
The "server" is functionally like the daemon, except it is run by the
first Emacs frame we launch. With a running server, we can connect to
it through a new ~emacsclient~ call. This is useful if we want to
launch new frames that share resources with the existing running
process.
#+end_quote

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-essentials.el"
;;;; Emacs server (allow emacsclient to connect to running session)
(use-package server
  :ensure nil
  :defer 1
  :config
  (setq server-client-instructions nil)
  (unless (server-running-p)
    (server-start)))
#+end_src

** The =nebkor-essentials.el= section about quick copying (~easy-kill~)
:PROPERTIES:
:CUSTOM_ID: h:891BA3F6-6229-45B5-B5E8-80FA4837662B
:ID:       01JGD333DJ000CP0TFT7PW7WB1
:END:

~easy-kill~ is a drop-in replacement for ~kill-ring-save~, letting me easily and quickly copy / kill anything I want.

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-essentials.el"
  (use-package easy-kill
    :ensure t
    :bind
    ("M-w" . easy-kill)) ; re-map kill-ring-save
#+end_src

** The =nebkor-essentials.el= section about auto management of treesit modules (~treesit-auto~)
:PROPERTIES:
:CUSTOM_ID: h:C9748AB2-AEFB-46E7-A3AD-0910D9CB153A
:CREATED:  [2024-12-10 Tue 13:45]
:ID:       01JGD333DQ0006TSDHEXBRAK54
:END:

~treesit-auto~ automatically downloads and installs tree-sitter
modules.

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-essentials.el"
  ;;; Install and use tree-sitter major modes where possible
    (use-package treesit-auto
      :ensure t
      :config
      (setq treesit-auto-install 'prompt)
      (treesit-auto-add-to-auto-mode-alist)
      (global-treesit-auto-mode))
#+end_src

** The =nebkor-essentials.el= section about using tree-sitter for marking (~expreg~)
:PROPERTIES:
:CUSTOM_ID: h:ceb193bf-0de3-4c43-8ab7-6daa50817754
:ID:       01JGD333DX000BE7CV17TXCMP5
:END:

#+begin_quote
The ~expreg~ package by Yuan Fu (aka casouri) uses the tree-sitter
framework to incrementally expand the region from the smallest to the
largest syntactic unit in the given context. This is a powerful
feature, though it (i) requires Emacs to be built with tree-sitter
support and (ii) for the user to be running a major mode that is
designed for tree-sitter (Lisp seems to work regardless).

The package offers the ~expreg-expand~ and ~expreg-contract~ commands.
#+end_quote

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-essentials.el"
  ;;; Mark syntactic constructs efficiently if tree-sitter is available (expreg)
  (use-package expreg
    :ensure t
    :functions (prot/expreg-expand prot/expreg-expand-dwim)
    :bind ("C-=" . prot/expreg-expand-dwim) ; overrides `mark-sexp'
    :config
    (defun prot/expreg-expand (n)
      "Expand to N syntactic units, defaulting to 1 if none is provided interactively."
      (interactive "p")
      (dotimes (_ n)
        (expreg-expand)))

    (defun prot/expreg-expand-dwim ()
      "Do-What-I-Mean `expreg-expand' to start with symbol or word.
  If over a real symbol, mark that directly, else start with a
  word.  Fall back to regular `expreg-expand'."
      (interactive)
      (let ((symbol (bounds-of-thing-at-point 'symbol)))
        (cond
         ((equal (bounds-of-thing-at-point 'word) symbol)
          (prot/expreg-expand 1))
         (symbol (prot/expreg-expand 2))
         (t (expreg-expand))))))
#+end_src

** The =nebkor-essentials.el= section for OSX changes
:PROPERTIES:
:ID:       01JGD333E200029PNFYCFH4JRK
:END:

These are modifications to basic configuration I use on my Mac OSX machine.

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-essentials.el"
  ;;;; Configuration on Mac OS X machine
  (when (eq system-type 'darwin)
    (use-package ns-win
      :ensure nil
      :config
      (defun copy-from-osx ()
        "Make cut and paste work with the OS X clipboard"
        (shell-command-to-string "pbpaste"))

      (defun paste-to-osx (text &optional push)
        "Make cut and paste work with the OS X clipboard"
        (let ((process-connection-type nil))
          (let ((proc (start-process "pbcopy" "*Messages*" "pbcopy")))
            (process-send-string proc text)
            (process-send-eof proc))))

      (setq mac-command-modifier 'meta)
      (setq mac-option-modifier 'alt)
      (setq interprogram-cut-function #'paste-to-osx)
      (setq interprogram-paste-function #'copy-from-osx)
      ;; Work around a bug on OS X where system-name is a fully qualified
      ;; domain name
      (setq system-name (car (split-string system-name "\\.")))
  ;;; Binaries
      (setq vc-git-program (or (executable-find "git") "/usr/local/bin/git"))
      (setq epg-gpg-program (or (executable-find "gpg") "/usr/local/bin/gpg"))))
#+end_src

** The =nebkor-essentials.el= section for ~simple.el~ changes
:PROPERTIES:
:CUSTOM_ID: h:6B18F988-DBAD-458C-97BE-129D1FF988F4
:ID:       01JGD333E8000D5AGKW1J2VJGK
:END:

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-essentials.el"
  (defun vedang/backward-kill-word-or-kill-region (&optional arg)
   "fancy C-w.
  If the region is selected, retain the original behaviour, otherwise call
  `backward-kill-word' instead.  ARG is passed to `backward-kill-word'."
    (interactive "p")
    (if (region-active-p)
        (kill-region (region-beginning) (region-end))
      (backward-kill-word arg)))

  (use-package simple
    :ensure nil
    :after vertico ;; so that we can bind to vertico-map
    :bind
    ;; Rebind `C-w' to work differently based on whether a region is
    ;; active.
    ( :map global-map
      ("C-w" . vedang/backward-kill-word-or-kill-region)
      :map vertico-map
      ("C-l" . vedang/backward-kill-word-or-kill-region))
    :hook
    ((before-save . delete-trailing-whitespace)
     (text-mode . turn-on-visual-line-mode))
    :config
    (setq column-number-mode t))
#+end_src

** The =nebkor-essentials.el= section for better help (~helpful~)
:PROPERTIES:
:CUSTOM_ID: h:ECAF81D8-4111-4C71-AB77-3C3D322B235F
:CREATED:  [2024-12-02 Mon 09:50]
:ID:       01JGD333ED000CFWD9GWED35XD
:END:

Helpful is a package that improves the default Emacs *Help* buffer. I don't want to replace what
Emacs provides with default, and I find myself using Helpful only as an Avy action ([[#h:4E8593F7-C065-4DFA-B513-98602EC2BA1A][The
=nebkor-search.el= settings for ~avy~ (jumping)]]). However, it's really useful in that context.

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-essentials.el"
  (use-package helpful
    :ensure t)
#+end_src

** The =nebkor-essentials= section for ~indent-tools~
:PROPERTIES:
:ID:       01JGD1ASTX0008J83XRB4TZW77
:END:

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-essentials.el"
  (use-package indent-tools
    :ensure t)
#+end_src

** The =nebkor-essentials= section for ~undo-tree~
:PROPERTIES:
:ID:       a980ab55-cc66-4e5a-99d4-cff449657e1c
:END:

Open a nodal history graph in a new window to navigate all previous histories with branches.

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-essentials.el"
  (use-package undo-tree
    :ensure t
    :config
    (defun nebkor/undo-tree ()
      (progn
        (global-undo-tree-mode)
        (blackout 'undo-tree-mode)))
    :hook (elpaca-after-init . nebkor/undo-tree)
    :bind (("C-z" . undo)
           ("C-S-z" . redo)))
#+end_src

** The =nebkor-essentials= section for fancy keyboard shortcuts (~key-chord~)
:PROPERTIES:
:ID:       01JGD1AZDG00072YN0C6Y1FFEG
:END:
#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-essentials.el"
  (use-package key-chord
    :ensure t
    :config
    (key-chord-mode +1)
    (setq key-chord-one-key-delay 0.185)           ; e.g. "jj", default 0.2
    (setq key-chord-two-keys-delay 0.1)          ; e.g. "jk", default 0.1
    (setq key-chord-safety-interval-backward 0.2) ; default 0.1 is too close to key delays
    (setq key-chord-safety-interval-forward 0.3) ; default 0.35 causes laggy experience

    (key-chord-define-global "VV" 'split-window-right)
    (key-chord-define-global "HH" 'split-window-below)
    (key-chord-define-global "BB" 'switch-to-buffer)
    (key-chord-define-global "CC" 'recenter)
    (key-chord-define-global "00" 'delete-window)
    (key-chord-define-global "11" 'delete-other-windows)
    (key-chord-define-global "WW" 'rotate-windows)
    )
#+end_src

The [[https://github.com/emacsorphanage/key-chord][key-chord package]] lets you define multi-key inputs, including double-taps of single keys, that
map to emacs commands. I use it for common window operations, like splitting, deleting, or rotating
them; the other major chord I use is =BB= which switches buffers.

** The =nebkor-essentials= section for ~which-key~
:PROPERTIES:
:ID:       01JGD1BCEX00019X4P0RCQXVQW
:END:

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-essentials.el"
  (use-package which-key
    :ensure t
    :config
    (which-key-mode t)
    (setq which-key-idle-delay 0.5)
    (setq which-key-sort-order 'which-key-description-order))
#+end_src

** The =nebkor-essentials= section for blackout and other random shit
:PROPERTIES:
:ID:       e810ad77-7720-4af4-98ee-9b5240c750f5
:END:

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-essentials.el"
  (use-package blackout
    :ensure t
    :demand t)
#+end_src

** Finally, we provide the =nebkor-essentials.el= module
:PROPERTIES:
:CUSTOM_ID: h:c8b2f021-fe5a-4f6b-944c-20340f764fb2
:ID:       01JGD333EP0004KBQ2VGJWV332
:END:

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-essentials.el"
(provide 'nebkor-essentials)
#+end_src

* The =nebkor-completion.el= module
:PROPERTIES:
:CUSTOM_ID: h:15edf2c3-4419-4101-928a-6e224958a741
:END:

** The =nebkor-completion.el= settings for completion styles
:PROPERTIES:
:CUSTOM_ID: h:14b09958-279e-4069-81e3-5a16c9b69892
:ID:       01JGD333EV0009D1WF55ZYS97Q
:END:

#+begin_quote
The ~completion-styles~ are pattern matching algorithms. They
interpret user input and match candidates accordingly.

- emacs22 :: Prefix completion that only operates on the text before
  point. If we are in =prefix|suffix=, with =|= representing the
  cursor, it will consider everything that expands =prefix= and then
  add back to it the =suffix=.

- basic :: Prefix completion that also accounts for the text after
  point. Using the above example, this one will consider patterns that
  match all of ~emacs22~ as well as anything that completes =suffix=.

- partial-completion :: This is used for file navigation. Instead of
  typing out a full path like =~/.local/share/fonts=, we do =~/.l/s/f=
  or variants thereof to make the matches unique such as =~/.l/sh/fon=.
  It is a joy to navigate the file system in this way.

- substring :: Matches the given sequence of characters literally
  regardless of where it is in a word. So =pro= will match
  =professional= as well as =reproduce=.

- flex :: Completion of an in-order subset of characters. It does not
  matter where the charactes are in the word, so long as they are
  encountered in the given order. The input =lad= will thus match
  ~list-faces-display~ as well as ~pulsar-highlight-dwim~.

- initials :: Completion of acronyms and initialisms. Typing =lfd=
  will thus match ~list-faces-display~. This completion style can also
  be used for file system navigation, though I prefer to only have
  ~partial-completion~ handle that task.

- orderless :: This is the only completion style I use which is not
  built into Emacs and which I tweak further in a separate section
  ([[#h:7cc77fd0-8f98-4fc0-80be-48a758fcb6e2][The =nebkor-completion.el= for the ~orderless~ completion style]]).
  It matches patterns out-of-order. Patterns are typically words
  separated by spaces, though they can also be regular expressions,
  and even styles that are the same as the aforementioned ~flex~ and
  ~initials~.

Now that you know about the completion styles I use, take a look at
the value of my ~completion-styles~. You will notice that ~orderless~,
which is the most powerful/flexible is placed last. I do this because
Emacs tries the styles in the given order from left to right, moving
the next one until it finds a match. As such, I usually want to start
with tight matches (e.g. =li-fa-di= for ~list-faces-display~) and only
widen the scope of the search as I need to. This is easy to do because
none of the built-in completion styles parses the empty space, so as
soon as I type a space after some characters I am using ~orderless~.
#+end_quote

(There are more details in Prot's file, for the interested reader)

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-completion.el" :mkdirp yes
;;; General minibuffer settings
(use-package minibuffer
  :ensure nil
  :config
;;;; Completion styles
  (setq completion-styles '(basic substring initials flex orderless)) ; also see `completion-category-overrides'
  (setq completion-pcm-leading-wildcard t) ; Emacs 31: make `partial-completion' behave like `substring'

  ;; Reset all the per-category defaults so that (i) we use the
  ;; standard `completion-styles' and (ii) can specify our own styles
  ;; in the `completion-category-overrides' without having to
  ;; explicitly override everything.
  (setq completion-category-defaults nil)

  ;; A non-exhaustve list of known completion categories:
  ;;
  ;; - `bookmark'
  ;; - `buffer'
  ;; - `charset'
  ;; - `coding-system'
  ;; - `color'
  ;; - `command' (e.g. `M-x')
  ;; - `customize-group'
  ;; - `environment-variable'
  ;; - `expression'
  ;; - `face'
  ;; - `file'
  ;; - `function' (the `describe-function' command bound to `C-h f')
  ;; - `info-menu'
  ;; - `imenu'
  ;; - `input-method'
  ;; - `kill-ring'
  ;; - `library'
  ;; - `minor-mode'
  ;; - `multi-category'
  ;; - `package'
  ;; - `project-file'
  ;; - `symbol' (the `describe-symbol' command bound to `C-h o')
  ;; - `theme'
  ;; - `unicode-name' (the `insert-char' command bound to `C-x 8 RET')
  ;; - `variable' (the `describe-variable' command bound to `C-h v')
  ;; - `consult-grep'
  ;; - `consult-isearch'
  ;; - `consult-kmacro'
  ;; - `consult-location'
  ;; - `embark-keybinding'
  ;;
  (setq completion-category-overrides
        ;; NOTE 2021-10-25: I am adding `basic' because it works better as a
        ;; default for some contexts.  Read:
        ;; <https://debbugs.gnu.org/cgi/bugreport.cgi?bug=50387>.
        ;;
        ;; `partial-completion' is a killer app for files, because it
        ;; can expand ~/.l/s/fo to ~/.local/share/fonts.
        ;;
        ;; If `basic' cannot match my current input, Emacs tries the
        ;; next completion style in the given order.  In other words,
        ;; `orderless' kicks in as soon as I input a space or one of its
        ;; style dispatcher characters.
        '((file (styles . (basic partial-completion orderless)))
          (bookmark (styles . (basic substring)))
          (library (styles . (basic substring)))
          (embark-keybinding (styles . (basic substring)))
          (imenu (styles . (basic substring orderless)))
          (consult-location (styles . (basic substring orderless)))
          (kill-ring (styles . (emacs22 orderless)))
          (eglot (styles . (emacs22 substring orderless))))))
#+end_src

** The =nebkor-completion.el= for the ~orderless~ completion style
:PROPERTIES:
:CUSTOM_ID: h:7cc77fd0-8f98-4fc0-80be-48a758fcb6e2
:ID:       01JGD333F10001KD5GP2BYZSY8
:END:

#+begin_quote
The ~orderless~ package by Omar Antolín Camarena provides one of the
completion styles that I use ([[#h:14b09958-279e-4069-81e3-5a16c9b69892][The =nebkor-completion.el= settings for completion styles]]).
It is a powerful pattern matching algorithm that parses user input and
interprets it out-of-order, so that =in pa= will cover ~insert-pair~
as well as ~package-install~. Components of the search are
space-separated, by default, though we can modify the user option
~orderless-component-separator~ to have something else (but I cannot
think of a better value). In the section about completion styles, I
explain how I use ~orderless~ and why its power does not result in
lots of false positives.
#+end_quote

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-completion.el"
  ;;; Orderless completion style
  (use-package orderless
    :ensure t
    :demand t
    :after minibuffer
    :config
    ;; Remember to check my `completion-styles' and the
    ;; `completion-category-overrides'.
    (setq orderless-matching-styles '(orderless-prefixes orderless-regexp))

    ;; SPC should never complete: use it for `orderless' groups.
    ;; The `?' is a regexp construct.
    :bind ( :map minibuffer-local-completion-map
            ("SPC" . nil)
            ("?" . nil)))
#+end_src

** The =nebkor-completion.el= settings to ignore letter casing
:PROPERTIES:
:CUSTOM_ID: h:7fe1787d-dba3-46fe-82a9-5dc5f8ea6217
:ID:       01JGD333F6000CVYYSRJ49BABE
:END:

#+begin_quote
I never really need to match letters case-sensitively in the
minibuffer. Let's have everything ignore casing by default.
#+end_quote

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-completion.el"
(setq completion-ignore-case t)
(setq read-buffer-completion-ignore-case t)
(setq-default case-fold-search t)   ; For general regexp
(setq read-file-name-completion-ignore-case t)
#+end_src

** The =nebkor-completion.el= settings for recursive minibuffers
:PROPERTIES:
:CUSTOM_ID: h:4299825a-db51-49fe-b415-fb1749eed289
:ID:       01JGD333FB000DN0A03K5M671B
:END:

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-completion.el"
(use-package mb-depth
  :ensure nil
  :hook (elpaca-after-init . minibuffer-depth-indicate-mode)
  :config
  (setq enable-recursive-minibuffers t))
#+end_src

** The =nebkor-completion.el= settings for default values
:PROPERTIES:
:CUSTOM_ID: h:aebbdd4c-6e5b-4773-9f0a-c69f0d3c7158
:ID:       01JGD333HJ0005YP3PGPPMDVH4
:END:

#+begin_quote
Minibuffer prompts often have a default value. This is used when the
user types =RET= without inputting anything. The out-of-the-box
behaviour of Emacs is to append informative text to the prompt like
=(default some-default-value)=. With the tweak to ~minibuffer-default-prompt-format~
we get a more compact style of =[some-default-value]=, which looks
better to me.
#+end_quote

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-completion.el"
  (use-package minibuf-eldef
    :ensure nil
    :config
    (setq minibuffer-default-prompt-format " [%s]"))
#+end_src

** The =nebkor-completion.el= settings for common interactions
:PROPERTIES:
:CUSTOM_ID: h:b640f032-ad11-413e-ad8f-63408671d500
:ID:       01JGD333HR000148M2B81S4H0H
:END:

#+begin_quote
Here I combine several small tweaks to improve the overall minibuffer
experience.

- The need to ~resize-mini-windows~ arises on some occasions where
  Emacs has to show text spanning multiple lines in the "mini
  windows". A common scenario for me is in Org mode buffers where I
  set the =TODO= keyword of a task with =C-c C-t= (=M-x org-todo=) and
  have this as my setting: ~(setq org-use-fast-todo-selection 'expert)~
  Otherwise, this is not an issue anyway and I may also like other
  options for ~org-use-fast-todo-selection~.

- The ~read-answer-short~ is complementary to ~use-short-answers~.
  This is about providing the shorter version to some confirmation
  prompt, such as =y= instead of =yes=.

- The ~echo-keystrokes~ is set to a low value to show in the echo area
  the incomplete key sequence I have just typed. This is especially
  helpful for demonstration purposes but also to double check that I
  did not mistype something (I cannot touch-type, so this happens a lot).

- The ~minibuffer-prompt-properties~ and advice to ~completing-read-multiple~
  make it so that (i) the minibuffer prompt is not accessible with
  regular motions to avoid mistakes and (ii) prompts that complete
  multiple targets show an indicator about this fact. With regard to
  the latter in particular, we have prompts like that of Org to set
  tags for a heading (with =C-c C-q= else =M-x org-set-tags-command=)
  where more than one candidate can be provided using completion,
  provided each candidate is separated by the ~crm-separator~ (a comma
  by default, though Org uses =:= in that scenario).

  Remember that when using completion in the minibuffer, you can hit
  =TAB= to expand the selected choice without exiting with it. For
  cases when multiple candidates can be selected, you select the
  candidate, =TAB=, then input the ~crm-separator~, and repeat until
  you are done selecting at which point you type =RET=.

- Finally the ~file-name-shadow-mode~ is a neat little feature to
  remove the "shadowed" part of a file prompt while using something
  like =C-x C-f= (=M-x find-file=). File name shadowing happens when
  we invoke ~find-file~ and instead of first deleting the contents of
  the minibuffer, we start typing out the file system path we wish to
  visit. For example, I am in =~/Git/Projects/= and type directly
  after it something like =~/.local/share/fonts/=, so Emacs displays
  =~/Git/Projects/~/.local/share/fonts/= with the original part greyed
  out. With ~file-name-shadow-mode~ the "shadowed" part is removed
  altogether. This is especially nice when combined with the
  completion style called ~partial-completion~
  ([[#h:14b09958-279e-4069-81e3-5a16c9b69892][The =nebkor-completion.el= settings for completion styles]]).
#+end_quote

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-completion.el"
  (use-package rfn-eshadow
    :ensure nil
    :hook (minibuffer-setup . cursor-intangible-mode)
    :config
    ;; Not everything here comes from rfn-eshadow.el, but this is fine.

    (setq resize-mini-windows t)
    (setq read-answer-short t) ; also check `use-short-answers' for Emacs28
    (setq echo-keystrokes 0.25)
    (setq kill-ring-max 60) ; Keep it small

    ;; Do not allow the cursor to move inside the minibuffer prompt.  I
    ;; got this from the documentation of Daniel Mendler's Vertico
    ;; package: <https://github.com/minad/vertico>.
    (setq minibuffer-prompt-properties
          '(read-only t cursor-intangible t face minibuffer-prompt))

    ;; Add prompt indicator to `completing-read-multiple'.  We display
    ;; [`completing-read-multiple': <separator>], e.g.,
    ;; [`completing-read-multiple': ,] if the separator is a comma.  This
    ;; is adapted from the README of the `vertico' package by Daniel
    ;; Mendler.  I made some small tweaks to propertize the segments of
    ;; the prompt.
    (defun crm-indicator (args)
      (cons (format "[`completing-read-multiple': %s]  %s"
                    (propertize
                     (replace-regexp-in-string
                      "\\`\\[.*?]\\*\\|\\[.*?]\\*\\'" ""
                      crm-separator)
                     'face 'error)
                    (car args))
            (cdr args)))

    (advice-add #'completing-read-multiple :filter-args #'crm-indicator)

    (file-name-shadow-mode 1))
#+end_src

** The =nebkor-completion.el= generic minibuffer UI settings
:PROPERTIES:
:CUSTOM_ID: h:de61a607-0bdf-462b-94cd-c0898319590e
:ID:       01JGD333HY00045FXEKVFX5AYN
:END:

These are some settings for the default completion user interface.

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-completion.el"
  (use-package minibuffer
    :ensure nil
    :demand t
    :config
    (setq completions-format 'one-column)
    (setq completion-show-help nil)
    (setq completion-auto-help 'always)
    (setq completion-auto-select t)
    (setq completions-detailed t)
    (setq completion-show-inline-help nil)
    (setq completions-max-height 10)
    (setq completions-header-format (propertize "%s candidates:\n" 'face 'bold-italic))
    (setq completions-highlight-face 'completions-highlight)
    (setq minibuffer-completion-auto-choose t)
    (setq minibuffer-visible-completions t)
    (setq completions-sort 'historical))
#+end_src

** The =nebkor-completion.el= settings for saving the history (~savehist-mode~)
:PROPERTIES:
:CUSTOM_ID: h:25765797-27a5-431e-8aa4-cc890a6a913a
:ID:       01JGD333J300040Q53S5C7TCZB
:END:

#+begin_quote
Minibuffer prompts can have their own history. When they do not, they
share a common history of user inputs. Emacs keeps track of that
history in the current session, but loses it as soon as we close it.
With ~savehist-mode~ enabled, all minibuffer histories are written to
a file and are restored when we start Emacs again.
#+end_quote

#+begin_quote
Since we are already recording minibuffer histories, we can instruct
~savehist-mode~ to also keep track of additional variables and restore
them next time we use Emacs. Hence ~savehist-additional-variables~. I
do this in a few of places:

- [[#h:804b858f-7913-47ef-aaf4-8eef5b59ecb4][The =nebkor-completion.el= for in-buffer completion popup and preview (~corfu~)]]
- [[#h:5685df62-4484-42ad-a062-d55ab19022e3][The =nebkor-essentials.el= settings for registers]]

Note that the user option ~history-length~ applies to each individual
history variable: it is not about all histories combined.

Overall, I am happy with this feature and benefit from it on a daily
basis.
#+end_quote

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-completion.el"
  ;;;; `savehist' (minibuffer and related histories)
  (use-package savehist
    :ensure nil
    :hook (elpaca-after-init . savehist-mode)
    :config
    (setq savehist-file (locate-user-emacs-file "savehist"))
    (setq history-length 100)
    (setq history-delete-duplicates t)
    (setq savehist-save-minibuffer-history t)
    (add-to-list 'savehist-additional-variables 'kill-ring))
#+end_src

** The =nebkor-completion.el= settings for dynamic text expansion (~dabbrev~)
:PROPERTIES:
:CUSTOM_ID: h:567bb00f-1d82-4746-93e5-e0f60721728a
:ID:       01JGD333J90004H93AMVCN8Y1Z
:END:

#+begin_quote
The built-in ~dabbrev~ package provides a text completion method that
reads the contents of a buffer and expands the text before the cursor
to match possible candidates...

The term "dabbrev" stands for "dynamic abbreviation". Emacs also has
static, user-defined abbreviations ([[#h:fd84b79a-351e-40f0-b383-bf520d77834b][Settings for static text expansion
(~abbrev~)]]).
#+end_quote

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-completion.el"
  (use-package dabbrev
    :ensure nil
    :commands (dabbrev-expand dabbrev-completion)
    :config
  ;;;; `dabbrev' (dynamic word completion (dynamic abbreviations))
    (setq dabbrev-abbrev-char-regexp "\\sw\\|\\s_")
    (setq dabbrev-abbrev-skip-leading-regexp "[$*/=~']")
    (setq dabbrev-backward-only nil)
    (setq dabbrev-case-distinction 'case-replace)
    (setq dabbrev-case-fold-search nil)
    (setq dabbrev-case-replace 'case-replace)
    (setq dabbrev-check-other-buffers t)
    (setq dabbrev-eliminate-newlines t)
    (setq dabbrev-upcase-means-case-search t)
    (setq dabbrev-ignored-buffer-modes
          '(archive-mode image-mode docview-mode pdf-view-mode)))
#+end_src

** The =nebkor-completion.el= settings for dynamic text expansion (~hippie~)
:PROPERTIES:
:ID:       01JGD333JE0005C8Y78JYDAJEF
:END:

Hippie is a built-in expansion mechanism that competes with dabbrev. I prefer hippie because of the simpler configuration and detailed expansion options it provides.

Hippie uses Dabbrev as one of the expansion sources, so all the dabbrev settings above are still important.

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-completion.el"
  (use-package hippie-ext
    :ensure nil
    :bind
    ;; Replace the default dabbrev
    ("M-/" . hippie-expand))
#+end_src

** The =nebkor-completion.el= for in-buffer completion popup (~corfu~ and ~cape~)
:PROPERTIES:
:CUSTOM_ID: h:804b858f-7913-47ef-aaf4-8eef5b59ecb4
:ID:       01JGD333JN000BY927DVDYYPG8
:END:

#+begin_quote
... the ~corfu~ package by Daniel Mendler: it handles the task
splendidly as it works with Emacs' underlying infrastructure for
~completion-at-point-functions~.

Completion is triggered with the =TAB= key, which produces a popup
where the cursor is. The companion ~corfu-popupinfo-mode~ will show a
secondary documentation popup if we move over a candidate but do not
do anything with it.

Also see [[#h:567bb00f-1d82-4746-93e5-e0f60721728a][the =nebkor-completion.el= settings for dynamic text expansion (~dabbrev~)]].
#+end_quote

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-completion.el"
  ;;; Corfu (in-buffer completion popup)
  (use-package corfu
    :ensure t
    :hook (elpaca-after-init . global-corfu-mode)
    ;; I also have (setq tab-always-indent 'complete) for TAB to complete
    ;; when it does not need to perform an indentation change.
    :bind (:map corfu-map ("<tab>" . corfu-complete))
    :config
    (setq corfu-auto t
          corfu-auto-delay 0.3
          corfu-auto-prefix 1
          corfu-count 15
          corfu-preview-current #'insert
          corfu-min-width 20
          corfu-preselect 'prompt
          corfu-on-exact-match nil
          corfu-popupinfo-delay '(2.0 . 1.0))
    (corfu-popupinfo-mode 1) ; shows documentation after `corfu-popupinfo-delay'

    ;; Sort by input history (no need to modify `corfu-sort-function').
    (with-eval-after-load 'savehist
      (corfu-history-mode 1)
      (add-to-list 'savehist-additional-variables 'corfu-history)))

  (use-package cape
    :ensure t
    :demand t
    ;; Press C-c p ? to for help.
    :bind ("C-c p" . cape-prefix-map)
    :hook
    (completion-at-point-functions . cape-dabbrev)
    (completion-at-point-functions . cape-dict)
    (completion-at-point-functions . cape-elisp-block)
    (completion-at-point-functions . cape-elisp-symbol)
    (completion-at-point-functions . cape-emoji)
    (completion-at-point-functions . cape-file))
#+end_src

** COMMENT The =nebkor-completion.el= for in-buffer completion using TAB (~smart-tab~)
:PROPERTIES:
:CUSTOM_ID: h:240488D1-B225-4877-86EE-40D5318B0A7E
:CREATED:  [2024-12-18 Wed 14:59]
:END:

~smart-tab~ is an old but extremely reliable package that gets TAB to
"do the right thing". Other packages somehow fail to replicate this
functionality correctly.

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-completion.el"
  ;;; smart-tab (TAB to do completion reliably)
  (use-package smart-tab
    :ensure (:repo "https://git.genehack.net/genehack/smart-tab.git" :branch "main")
    :after corfu
    :config
    (setq smart-tab-using-hippie-expand t)
    (setq smart-tab-expand-eolp nil)
    ;; (setq smart-tab-user-provided-completion-function #'corfu-complete)
    (setq smart-tab-user-provided-completion-function nil)
    (global-smart-tab-mode 1))
#+end_src

** The =nebkor-completion.el= settings for filtering, previewing candidates (~consult~)
:PROPERTIES:
:CUSTOM_ID: h:22e97b4c-d88d-4deb-9ab3-f80631f9ff1d
:ID:       01JGD333JV0008J1P5PCK30RP7
:END:

#+begin_quote
~consult~ is another wonderful package by Daniel Mendler. It provides
a number of commands that turbocharge the minibuffer with advanced
capabilities for filtering, asynchronous input, and previewing of the
current candidate's context.

- A case where filtering is in use is the ~consult-buffer~ command,
  which many users have as a drop-in replacement to the generic =C-x b=
  (=M-x switch-to-buffer=). It is a one-stop-shop for buffers,
  recently visited files (if ~recentf-mode~ is used---I don't),
  bookmarks ([[#h:581aa0ff-b136-4099-a321-3b86edbfbccb][The =nebkor-essentials.el= settings for bookmarks]]),
  and, in principle, anything else that defines a source for this
  interface. To filter those source, we can type at the empty
  minibuffer =b SPC=, which will insert a filter specific to buffers.
  Delete back to remove the =[Buffer]= filter and insert another
  filter. Available filters are displayed by typing =?= at the prompt
  (I define it this way to call the command ~consult-narrow-help~).
  Every multi-source command from ~consult~ relies on this paradigm.

- Asynchronous input pertains to the intersection between Emacs and
  external search programs. A case in point is ~consult-grep~, which
  calls the system's ~grep~ program. The prompt distinguishes between
  what is sent to the external program and what is only shown to Emacs
  by wrapping the former inside of =#=. So the input =#prot-#completion=
  will send =prot-= to the ~grep~ program and then use =completion=
  inside of the minibuffer to perform the subsequent pattern-matching
  (e.g. with help from ~orderless~ ([[#h:7cc77fd0-8f98-4fc0-80be-48a758fcb6e2][The =nebkor-completion.el= for the ~orderless~ completion style]]).
  The part that is sent to the external program does not block Emacs.
  It is handled asynchronously, so everything stays responsive.

- As for previewing, ~consult~ commands show the context of the
  current match and update the window as we move between completion
  candidates in the minibuffer. For example, the ~consult-line~
  command performs an in-buffer search and lets us move between
  matches in the minibuffer while seeing in the window above what the
  surrounding text looks like. This is an excellent feature when we
  are trying to find something and do not quite remember all the
  search terms to narrow down to it simply by typing at the minibuffer
  prompt.

Also check: [[#h:e0f9c30e-3a98-4479-b709-7008277749e4][The =nebkor-search.el= module]].
#+end_quote

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-completion.el"
  ;;; Enhanced minibuffer commands (consult.el)
  (use-package consult
    :ensure t
    :hook (completion-list-mode . consult-preview-at-point-mode)
    :bind
    ( :map global-map
      ;; Prot's bindings
      ("M-K" . consult-keep-lines)     ; M-S-k is similar to M-S-5 (M-%)
      ("M-F" . consult-focus-lines)    ; same principle
      ("M-s M-b" . consult-buffer)    ; Start opening anything from here
      ("M-s M-f" . consult-fd)
      ("M-s M-g" . consult-ripgrep)
      ("M-s M-h" . consult-history)
      ("M-s M-i" . consult-imenu)
      ("M-s M-l" . consult-line)
      ("M-s M-m" . consult-mark)
      ("M-s M-y" . consult-yank-pop)
      ("M-s M-s" . consult-outline)
      ;; Overriding defaults: C-x bindings in `ctl-x-map'
      ("C-x M-:" . consult-complex-command) ;; orig. repeat-complex-command
      ("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 b" . consult-bookmark)         ;; orig. bookmark-jump
      ("C-x p b" . consult-project-buffer) ;; orig. project-switch-to-buffer
      ;; Custom M-# bindings for fast register access
      ("M-#" . consult-register-load)
      ("M-'" . consult-register-store) ;; orig. abbrev-prefix-mark (unrelated)
      ("C-M-#" . consult-register)
      ;; Other custom bindings
      ("M-y" . consult-yank-pop) ;; orig. yank-pop
      ;; M-g bindings in `goto-map'
      ("M-g e" . consult-compile-error)
      ("M-g f" . consult-flymake)       ;; Alternative: consult-flycheck
      ("M-g g" . consult-goto-line)     ;; orig. goto-line
      ("M-g M-g" . consult-goto-line)   ;; orig. goto-line
      ("M-g o" . consult-outline) ;; Alternative: consult-org-heading
      ;; My bindings from my Helm workflow
      ("C-x c i" . consult-imenu)
      ("C-c s" . consult-ripgrep)
      :map consult-narrow-map
      ("?" . consult-narrow-help))
    :config
    (setq consult-line-numbers-widen t)
    ;; (setq completion-in-region-function #'consult-completion-in-region)
    (setq consult-async-min-input 3)
    (setq consult-async-input-debounce 0.5)
    (setq consult-async-input-throttle 0.8)
    (setq consult-narrow-key nil)
    (setq consult-find-args
          (concat "find . -not ( "
                  "-path */.git* -prune "
                  "-or -path */.cache* -prune )"))
    (setq consult-preview-key 'any)
      ;; the `imenu' extension is in its own file
    (require 'consult-imenu)
    (dolist (clj '(clojure-mode clojure-ts-mode))
      (add-to-list 'consult-imenu-config
                   `(,clj :toplevel "Functions"
                          :types
                          ((?f "Functions" font-lock-function-name-face)
                           (?m "Macros" font-lock-function-name-face)
                           (?p "Packages" font-lock-constant-face)
                           (?t "Types" font-lock-type-face)
                           (?v "Variables" font-lock-variable-name-face)))))
    (add-to-list 'consult-mode-histories '(vc-git-log-edit-mode . log-edit-comment-ring)))
#+end_src

** The =nebkor-completion.el= section to configure completion annotations (~marginalia~)
:PROPERTIES:
:CUSTOM_ID: h:bd3f7a1d-a53d-4d3e-860e-25c5b35d8e7e
:ID:       01JGD333K8000BX0JZS6CE3RQV
:END:

#+begin_quote
The ~marginalia~ package, co-authored by Daniel Mendler and Omar
Antolín Camarena, provides helpful annotations to the side of
completion candidates. We see its effect, for example, when we call =M-x=:
each command has a brief description next to it (taken from its doc
string) as well as a key binding, if it has one.
#+end_quote

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-completion.el"
  ;;; Detailed completion annotations (marginalia.el)
  (use-package marginalia
    :ensure t
    :hook (elpaca-after-init . marginalia-mode)
    :config
    (setq marginalia-max-relative-age 0)) ; absolute time
#+end_src

** The =nebkor-completion.el= section for vertical minibuffer layout (~vertico~)
:PROPERTIES:
:CUSTOM_ID: h:cff33514-d3ac-4c16-a889-ea39d7346dc5
:ID:       01JGD333KE0003C8GAJ69KR511
:END:

#+begin_quote
The ~vertico~ package by Daniel Mendler displays the minibuffer in a
vertical layout. Under the hood, it takes care to be responsive and to
handle even massive completion tables gracefully.
#+end_quote

I use ~vertico-repeat~ to mimic the functionality that ~helm-resume~ would provide. The configuration for that is also part of this section.

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-completion.el"
  ;;; Vertical completion layout (vertico)
  (use-package vertico
    :ensure t
    :hook (elpaca-after-init . vertico-mode)
    :config
    (setq vertico-scroll-margin 0)
    (setq vertico-count 8)
    (setq vertico-resize t)
    (setq vertico-cycle t)

    (with-eval-after-load 'rfn-eshadow
      ;; This works with `file-name-shadow-mode' enabled.  When you are in
      ;; a sub-directory and use, say, `find-file' to go to your home '~/'
      ;; or root '/' directory, Vertico will clear the old path to keep
      ;; only your current input.
      (add-hook 'rfn-eshadow-update-overlay-hook #'vertico-directory-tidy))
    )

  (use-package vertico-repeat
    :after vertico
    :bind ( :map global-map
            ("M-R" . vertico-repeat)
            :map vertico-map
            ("M-N" . vertico-repeat-next)
            ("M-P" . vertico-repeat-previous))
    :hook (minibuffer-setup . vertico-repeat-save))

  (use-package vertico-suspend
   :after vertico
    ;; Note: `enable-recursive-minibuffers' must be t
    :bind ( :map global-map
            ("M-S" . vertico-suspend)
            ("C-x c b" . vertico-suspend)))
#+end_src

** Finally, we provide the ~nebkor-completion.el~ module
:PROPERTIES:
:ID:       01JGCZHZSH0008MPA02ZJV43CY
:END:

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-completion.el"
(provide 'nebkor-completion)
#+end_src

* The =nebkor-functions.el= module
:PROPERTIES:
:ID:       3b74f636-b722-4306-b053-e4e51796e7e6
:END:

My old custom functions file.

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-functions.el" :mkdirp yes
  (defun diff-and-set-modified-p ()
    "Diff the current buffer with its associated file and set buffer modified status."
    (let* ((tmpfile (diff-file-local-copy (current-buffer)))
           (cmd (format "diff -q '%s' '%s'" (buffer-file-name) tmpfile))
           (result (call-process-shell-command cmd nil nil)))
      (set-buffer-modified-p (not (zerop result)))))

  (defun nebkor/kill-buffer ()
    (interactive)
    (diff-and-set-modified-p)
    (kill-buffer (current-buffer))
    )

  (defun sother-window ()
    "shift-other-window"
    (interactive)
    (other-window -1)
    )

  ;;; From:
  ;;; https://fluca1978.github.io/2022/04/13/EmacsPgFormatter.html, with
  ;;; minor modifications to add the function to a `before-save-hook`
  (defun pgformatter-on-region ()
    "A function to invoke pg_format as an external program."
    (interactive)
    (let ((b (if mark-active (min (point) (mark)) (point-min)))
          (e (if mark-active (max (point) (mark)) (point-max)))
          (pgfrm (executable-find "pg_format")))
      (when pgfrm
        (let ((p (point)))
          (progn
            (shell-command-on-region b e pgfrm (current-buffer) 1)
            (goto-char p)
            ))
        )
      )
    )

  (defun sql-format-buffer-on-save ()
    "When saving an SQL buffer, format it with pg_format."
    (add-hook 'before-save-hook #'pgformatter-on-region -10 t))

  (defun nebkor-ksuidgen-p (orig-fn id)
    "Check if an ID is a valid ksuid, and if not, return whatever ORIG-FN does."
    (or (string-match (rx bol (= 27 alnum) eol) id)
        (orig-fn id)))

  (defun nebkor-julid-p (orig-fn id)
    "Check if an ID is a valid Julid, and if not, return whatever ORIG-FN does."
    (or (string-match (rx bol (= 26 alnum) eol) id)
        (orig-fn id)))

  (defun rotate-windows (arg)
    "Rotate your windows; use the prefix argument to rotate the other direction"
    (interactive "P")
    (if (not (> (count-windows) 1))
        (message "You can't rotate a single window!")
      (let* ((rotate-times (prefix-numeric-value arg))
             (direction (if (or (< rotate-times 0) (equal arg '(4)))
                            'reverse 'identity)))
        (dotimes (_ (abs rotate-times))
          (dotimes (i (- (count-windows) 1))
            (let* ((w1 (elt (funcall direction (window-list)) i))
                   (w2 (elt (funcall direction (window-list)) (+ i 1)))
                   (b1 (window-buffer w1))
                   (b2 (window-buffer w2))
                   (s1 (window-start w1))
                   (s2 (window-start w2))
                   (p1 (window-point w1))
                   (p2 (window-point w2)))
              (set-window-buffer-start-and-point w1 b2 s2 p2)
              (set-window-buffer-start-and-point w2 b1 s1 p1)))))))

  (provide 'nebkor-functions)
#+end_src

* The =nebkor-search.el= module
:PROPERTIES:
:CUSTOM_ID: h:e0f9c30e-3a98-4479-b709-7008277749e4
:END:

[ Watch Prot's talk: [[https://protesilaos.com/codelog/2023-06-10-emacs-search-replace-basics/][Emacs: basics of search and replace]] (2023-06-10). ]

#+begin_quote
Emacs provides lots of useful facilities to search the contents of
buffers or files. The most common scenario is to type =C-s=
(~isearch-forward~) to perform a search forward from point or =C-r=
(~isearch-backward~) to do so in reverse. These commands pack a ton of
functionality and they integrate nicely with related facilities, such
as those of (i) permanently highlighting the thing being searched,
(ii) putting all results in a buffer that is useful for navigation
purposes, among others, and (iii) replacing the given matching items
with another term.

Here I summarise the functionality, though do check the video I did on
the basics of search and replace:

- =C-s= (~isearch-forward~) :: Search forward from point (incremental
  search); retype =C-s= to move forth.

- =C-r= (~isearch-backward~) :: Search backward from point
  (incremental); retype =C-r= to move back. While using either =C-s=
  and =C-r= you can move in the opposite direction with either of
  those keys when performing a repeat.

- =C-M-s= (~isearch-forward-regexp~) :: Same as =C-s= but matches a
  regular expression. The =C-s= and =C-r= motions are the same after
  matches are found.

- =C-M-r= (~isearch-backward-regexp~) :: The counterpart of the above
  =C-M-s= for starting in reverse.

- =C-s C-w= (~isearch-yank-word-or-char~) :: Search forward for
  word-at-point. Again, =C-s= and =C-r= move forth and back,
  respectively.

- =C-r C-w= (~isearch-yank-word-or-char~) :: Same as above, but
  backward.

- =M-s o= (~occur~) :: Search for the given regular expression
  throughout the buffer and collect the matches in an =*occur*=
  buffer. Also check what I am doing with this in my custom
  extensions: [[#h:b902e6a3-cdd2-420f-bc99-3d973c37cd20][The =nebkor-search.el= extras provided by the =prot-search.el= library]].

- =C-u 5 M-s o= (~occur~) :: Like the above, but give it N lines of
  context when N is the prefix numeric argument  (5 in
  this example).

- =C-s SEARCH= followed by =M-s o=  (~isearch-forward~ --> ~occur~) ::
  Like =C-s= but then put the matches in an *occur* buffer.

- =C-s SEARCH= followed by =C-u 5 M-s o= (~isearch-forward~ -->
  ~occur~) :: Same as above, but now with N lines of context (5 in
  this example).

- =M-%= (~query-replace~) :: Prompt for target to replace and then
  prompt for its replacement (see explanation)

- =C-M-%= (~query-replace-regexp~) :: Same as above, but for REGEXP

- =C-s SEARCH= followed by =M-%= (~isearch-forward~ --> ~query-replace~) :: Search
  with =C-s= and then perform a query-replace for the following
  matches.

- =C-M-s SEARCH M-%= (~isearch-forward-regexp~ -->
  ~query-replace-regexp~) :: As above, but regexp-aware.

- =C-s SEARCH C-M-%= (~isearch-forward~ --> ~query-replace-regexp~) :: Same
  as above.

- =M-s h r= (~highlight-regexp~) :: Prompt for a face (like
  ~hi-yellow~) to highlight the given regular expression.

- =M-s h u= (~unhighlight-regexp~) :: Prompt for an already
  highlighted regular expression to unhighlight (do it after the
  above).

For starters, just learn:

- =C-s=
- =C-r=
- =M-s o=
- =M-%=

Now on to the configurations.
#+end_quote

** The =nebkor-search.el= section for heading navigation (~imenu~)
:PROPERTIES:
:CUSTOM_ID: h:7151F001-75DB-4808-95CB-3BC6BEC6A8CA
:ID:       01JGD333KM000D9V344X6ZW0S9
:END:

~imenu~ is amazing and I use it as my primary tool for navigating any file. Here, we make a small
tweak to ensure that ~consult-imenu~ returns good ~imenu~ results to us.

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-search.el" :mkdirp yes
  (use-package imenu
    :ensure nil
    :config
    ;; Don't limit item length, this makes imenu less useful in
    ;; consult-imenu
    (setq imenu-max-item-length 'unlimited))
#+end_src

** The =nebkor-search.el= section on relaxed searching (~isearch~)
:PROPERTIES:
:CUSTOM_ID: h:95947b37-2071-4ee7-a201-8e19bf3322e9
:ID:       01JGD333KT0008M1W14VHW2GM3
:END:

#+begin_quote
The first thing I want to do for Isearch, is make it more convenient
for me to match words that occur in sequence but are not necessarily
following each other. By default, we can do that with something like
=C-M-s= (~isearch-forward-regexp~) followed by =one.*two=. Though it
is inconvenient to be a regexp-aware search mode when all we want is
to just type =one two= and have the space be interpreted as
"intermediate characters" rather than a literal space. The following
do exactly this for regular =C-s= (~isearch-forward~) and =C-r=
(~isearch-backward~).
#+end_quote

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-search.el"
  ;;; Isearch, occur, grep
  (use-package isearch
    :ensure nil
    :demand t
    :config
    (setq search-whitespace-regexp ".*?" ; one `setq' here to make it obvious they are a bundle
          isearch-lax-whitespace t
          isearch-regexp-lax-whitespace nil))
#+end_src

** The =nebkor-search.el= settings for highlighting search results (~isearch~)
:PROPERTIES:
:CUSTOM_ID: h:ed1307e7-f8a0-4b0a-8d91-2de9c1e2479c
:ID:       01JGD333KZ000BSK41M88SBRRC
:END:

#+begin_quote
Here I am just tweaking the delay that affects when deferred
highlights are applied. The current match is highlighted immediately.
The rest are done after ~lazy-highlight-initial-delay~ unless they are
longer in character count than ~lazy-highlight-no-delay-length~.
#+end_quote

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-search.el"
(use-package isearch
  :ensure nil
  :demand t
  :config
  (setq search-highlight t)
  (setq isearch-lazy-highlight t)
  (setq lazy-highlight-initial-delay 0.5)
  (setq lazy-highlight-no-delay-length 4))
#+end_src

** The =nebkor-search.el= section on showing search result count (~isearch~)
:PROPERTIES:
:CUSTOM_ID: h:acfdc17f-7ffb-48d3-90ff-49bd00463934
:ID:       01JGD333M50003ZSPQA5K0KAE3
:END:

#+begin_quote
I think the following options should be enabled by default. They
produce a counter next to the isearch prompt that shows the position
of the current match relative to the total count (like =5/20=). As we
move to the next/previous match, the counter is updated accordingly.
#+end_quote

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-search.el"
(use-package isearch
  :ensure nil
  :demand t
  :config
  (setq isearch-lazy-count t)
  (setq lazy-count-prefix-format "(%s/%s) ")
  (setq lazy-count-suffix-format nil))
#+end_src

** The =nebkor-search.el= tweaks for the search results in buffer (~occur~)
:PROPERTIES:
:CUSTOM_ID: h:85aca4da-b89b-4fbe-89e9-3ec536ad7b0d
:ID:       01JGD333MA00084NRNV8JWJ476
:END:

#+begin_quote
Here I am making some minor tweaks to =*occur*= buffer (remember to
read the introduction to this section ([[#h:e0f9c30e-3a98-4479-b709-7008277749e4][The =nebkor-search.el= module]])).
I always want (i) the cursor to be at the top of the buffer, (ii) the
current line to be highlighted, as it is easier for selection
purposes ...
#+end_quote

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-search.el"
(use-package isearch
  :ensure nil
  :demand t
  :config
  (setq list-matching-lines-jump-to-current-line nil) ; do not jump to current line in `*occur*' buffers
  (add-hook 'occur-mode-hook #'hl-line-mode))
#+end_src

** The =nebkor-search.el= section for search key bindings
:PROPERTIES:
:CUSTOM_ID: h:5ce6216d-f318-4191-9d4f-9681c92f7582
:ID:       01JGD333MG0003B2Q382PGA3FD
:END:

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-search.el"
(use-package isearch
  :ensure nil
  :demand t
  :bind
  ( :map minibuffer-local-isearch-map
    ("M-/" . isearch-complete-edit)
    :map occur-mode-map
    ("t" . toggle-truncate-lines)
    :map isearch-mode-map
    ("C-g" . isearch-cancel) ; instead of `isearch-abort'
    ("M-/" . isearch-complete)))
#+end_src

** The =nebkor-search.el= tweaks to (~xref~), (~re-builder~) and (~grep~)
:PROPERTIES:
:CUSTOM_ID: h:ceb286c5-a5f7-4cc8-b883-89d20a75ea02
:ID:       01JGD333MN000F5QC0CDHZ9EJ9
:END:

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-search.el"
  ;;; grep and xref
  (use-package re-builder
    :ensure nil
    :commands (re-builder regexp-builder)
    :config
    (setq reb-re-syntax 'read))

  (use-package xref
    :ensure nil
    :commands (xref-find-definitions xref-go-back)
    :config
    ;; All those have been changed for Emacs 28
    (setq xref-show-definitions-function #'xref-show-definitions-completing-read) ; for M-.
    (setq xref-show-xrefs-function #'xref-show-definitions-buffer) ; for grep and the like
    (setq xref-file-name-display 'project-relative)
    (setq xref-search-program (if (or (executable-find "rg")
                                      (executable-find "ripgrep"))
                                  'ripgrep
                                'grep)))

  (use-package grep
    :ensure nil
    :commands (grep lgrep rgrep)
    :config
    (setq grep-save-buffers nil)
    (setq grep-use-headings t) ; Emacs 30

    (let ((executable (or (executable-find "rg") "grep"))
          (rgp (string-match-p "rg" grep-program)))
      (setq grep-program executable)
      (setq grep-template
            (if rgp
                "rg -nH --null -e <R> <F>"
              "grep <X> <C> -nH --null -e <R> <F>"))
      (setq xref-search-program (if rgp 'ripgrep 'grep))))
#+end_src

** The =nebkor-search.el= setup for editable grep buffers (~grep-edit-mode~ or ~wgrep~)
:PROPERTIES:
:CUSTOM_ID: h:9a3581df-ab18-4266-815e-2edd7f7e4852
:ID:       01JGD333MV000F1CQFCAMH98BR
:END:

#+begin_quote
Starting with Emacs 31, buffers using ~grep-mode~ can now be edited
directly. The idea is to collect the results of a search in one place
and quickly apply a change across all or some of them. We have the
same concept with occur (=M-x occur=) as well as with Dired buffers
([[#h:1b53bc10-8b1b-4f68-bbec-165909761e43][The =nebkor-dired.el= section about ~wdired~ (writable Dired)]]).

For older versions of Emacs, we have the ~wgrep~ package by Masahiro
Hayashi. I configure it to have key bindings like those of the ~occur~
edit mode, which ~grep-edit-mode~ also uses.
#+end_quote

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-search.el"
;;; wgrep (writable grep)
;; See the `grep-edit-mode' for the new built-in feature.
(unless (>= emacs-major-version 31)
  (use-package wgrep
    :ensure t
    :after grep
    :bind
    ( :map grep-mode-map
      ("e" . wgrep-change-to-wgrep-mode)
      ("C-x C-q" . wgrep-change-to-wgrep-mode)
      ("C-c C-c" . wgrep-finish-edit))
    :config
    (setq wgrep-auto-save-buffer t)
    (setq wgrep-change-readonly-file t)))
#+end_src

** The =nebkor-search.el= settings for jumping (~avy~)
:PROPERTIES:
:CUSTOM_ID: h:4E8593F7-C065-4DFA-B513-98602EC2BA1A
:CREATED:  [2024-12-02 Mon 08:51]
:ID:       01JGD333N0000AHYGRX0BWJ960
:END:

Avy is my favorite package to jump around on an Emacs screen. This
section binds my primary entry-point to Avy (~avy-goto-char-timer~) to
=M-j=. Here are important things about ~avy~ that you should know:

- You can access a list of "actions" that you can perform with Avy by
  starting a selection (=M-j <type chars>=) and then pressing =?=
- When you jump anywhere on the screen using ~avy~, you can return to
  your starting point by using =C-x C-SPC= (~pop-global-mark~)

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-search.el"
  (use-package avy
    :ensure t
    :bind
    ("M-j" . avy-goto-char-timer)
    ("M-g SPC" . avy-goto-char-timer)
    :config
    ;; Mark text
    (defun avy-action-mark-to-char (pt)
      (activate-mark)
      (goto-char pt))

    (setf (alist-get ?  avy-dispatch-alist) 'avy-action-mark-to-char)

    (with-eval-after-load 'helpful
      (defun avy-action-helpful (pt)
        (save-excursion
          (goto-char pt)
          (helpful-at-point))
        (select-window
         (cdr (ring-ref avy-ring 0)))
        t)

      (setf (alist-get ?H avy-dispatch-alist) 'avy-action-helpful))

    (with-eval-after-load 'embark
      (defun avy-action-embark (pt)
        (unwind-protect
            (save-excursion
              (goto-char pt)
              (embark-act))
          (select-window
           (cdr (ring-ref avy-ring 0))))
        t)

      (setf (alist-get ?. avy-dispatch-alist) 'avy-action-embark)))
#+end_src
** Finally, we provide the =nebkor-search.el= module
:PROPERTIES:
:CUSTOM_ID: h:c8b2f021-fe5a-4f6b-944c-20340f764fb2
:ID:       01JGD333N6000AK30WV39XVTFZ
:END:

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-search.el"
(provide 'nebkor-search)
#+end_src

* The =nebkor-dired.el= module
:PROPERTIES:
:CUSTOM_ID: h:f8b08a77-f3a8-42fa-b1a9-f940348889c3
:END:

[ Watch Prot's talk: <https://protesilaos.com/codelog/2023-06-26-emacs-file-dired-basics/> (2023-06-26) ]

#+begin_quote
Dired is probably my favourite Emacs tool. It exemplifies how I see
Emacs as a whole: a layer of interactivity on top of Unix. The ~dired~
interface wraps---and puts to synergy---standard commands like ~ls~,
~cp~, ~mv~, ~rm~, ~mkdir~, ~chmod~, and related. All while granting
access to many other conveniences, such as (i) marking files to
operate on (individually, with a regexp, etc.), (ii) bulk renaming
files by making the buffer writable and editing it like a regular
file, (iii) showing only files you want, (iv) listing the contents of
any subdirectory, such as to benefit from the bulk-renaming
capability, (v) running a keyboard macro that edits file contents
while using Dired to navigate the file listing, (vi) open files in an
external application, and more.

Dired lets us work with our files in a way that still feels close to
the command-line, yet has more powerful interactive features than even
fully fledged, graphical file managers.
#+end_quote

** The =nebkor-dired.el= settings for common operations
:PROPERTIES:
:CUSTOM_ID: h:39fb0eab-54bb-4e5b-8e38-9443dbe5c5ee
:ID:       01JGD333NB00015DHSKF3MV4D0
:END:

#+begin_quote
I add two settings which make all copy, rename/move, and delete
operations more intuitive. I always want to perform those actions in a
recursive manner, as this is the intent I have when I am targeting
directories.

The ~delete-by-moving-to-trash~ is a deviation from the behaviour of
the ~rm~ program, as it sends the file into the virtual trash folder.
Depending on the system, files in the trash are either removed
automatically after a few days, or we still have to permanently delete
them manually. I prefer this extra layer of safety. Plus, we have the
~trashed~ package to navigate the trash folder in a Dired-like way
([[#h:2e005bd1-d098-426d-91f9-2a31a6e55caa][The =nebkor-dired.el= section about =trashed.el=]]).
#+end_quote

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-dired.el" :mkdirp yes
;;; Dired file manager and prot-dired.el extras
(use-package dired
  :ensure nil
  :commands (dired)
  :config
  (setq dired-recursive-copies 'always)
  (setq dired-recursive-deletes 'always)
  (setq delete-by-moving-to-trash t))
#+end_src

** The =nebkor-dired.el= switches for how files are listed (~ls~)
:PROPERTIES:
:CUSTOM_ID: h:679e4460-b306-450f-aa20-497243057e02
:ID:       01JGD333NH000CD2S51Z1KSVES
:END:

#+begin_quote
As I already explained, Dired is a layer of interactivity on top of
standard Unix tools ([[#h:f8b08a77-f3a8-42fa-b1a9-f940348889c3][The =nebkor-dired.el= module]]). We can see
this in how Dired produces the file listing and how we can affect it.
The ~ls~ program accepts an =-l= flag for a "long", detailed list of
files. This is what Dired uses. But we can pass more flags by setting
the value of ~dired-listing-switches~. Do =M-x man= and then search
for the ~ls~ manpage to learn about what I have here. In short:

- =-A= :: Show hidden files ("dotfiles"), such as =.bashrc=, but omit
  the implied =.= and =..= targets. The latter two refer to the
  present and parent directory, respectively.

- =-G= :: Do not show the group name in the long listing. Only show
  the owner of the file.

- =-F= :: Differentiate regular from special files by appending a
  character to them. The =*= is for executables, the =/= is for
  directories, the =|= is for a named pipe, the ~=~ is for a socket,
  the =@= and the =>= are for stuff I have never seen.

- =-h= :: Make file sizes easier to read, such as =555k= instead of
  =568024= (the size of =nebkor.org= as of this writing).

- =-l= :: Produce a long, detailed listing. Dired requires this.

- =-v= :: Sort files by version numbers, such that =file1=, =file2=,
  and =file10= appear in this order instead of 1, 10, 2. The latter is
  called "lexicograhic" and I have not found a single case where it is
  useful to me.

- =--group-directories-first= :: Does what it says to place all
  directories before files in the listing. I prefer this over a strict
  sorting that does not differentiate between files and directories.

- =--time-style=long-iso= :: Uses the international standard for time
  representation in the file listing. So we have something like
  =2023-12-30 06:38= to show the last modified time.
#+end_quote

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-dired.el"
  (use-package dired
    :ensure nil
    :commands (dired)
    :config
    (setq insert-directory-program
          (or (executable-find "ls") "/opt/homebrew/bin/gls"))
    (setq dired-listing-switches
          "-AGFhlv --group-directories-first --time-style=long-iso"))
#+end_src

** The =nebkor-dired.el= setting for dual-pane Dired
:PROPERTIES:
:CUSTOM_ID: h:8225364c-3856-48bc-bf64-60d40ddd3320
:ID:       01JGD333NP000FDB23RX1HP32F
:END:

#+begin_quote
I often have two Dired buffers open side-by-side and want to move
files between them. By setting ~dired-dwim-target~ to a ~t~ value,
we get the other buffer as the default target of the current rename or
copy operation. This is exactly what I want.

If there are more than two windows showing Dired buffers, the default
target is the previously visited window.

Note that this only affects how quickly we can access the default
value, as we can always type =M-p= (~previous-history-element~) and
=M-n= (~next-history-element~) to cycle through the minibuffer
history ([[#h:25765797-27a5-431e-8aa4-cc890a6a913a][The =nebkor-completion.el= settings for saving the history (~savehist-mode~)]]).
#+end_quote

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-dired.el"
(use-package dired
  :ensure nil
  :commands (dired)
  :config
  (setq dired-dwim-target t))
#+end_src

** The =nebkor-dired.el= miscellaneous tweaks
:PROPERTIES:
:CUSTOM_ID: h:6327e6ba-a468-416f-ad26-b530c32fe235
:ID:       01JGD333NW00067R4QF0AC2SGH
:END:

#+begin_quote
These are some minor tweaks that I do not really care about. The only
one which is really nice in my opinion is the hook that involves the
~dired-hide-details-mode~. This is the command that hides the noisy
output of the ~ls~ =-l= flag, leaving only the file names in the list
([[#h:679e4460-b306-450f-aa20-497243057e02][The =nebkor-dired.el= switches for ~ls~ (how files are listed)]]).
We can toggle this effect at any time with the =(= key, by default.

I disable the repetition of the =j= key as I do use ~repeat-mode~
([[#h:fbe6f9da-25ee-46a3-bb03-8fa7c1d48dab][The =nebkor-essentials.el= settings for ~repeat-mode~]]).
#+end_quote

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-dired.el"
(use-package dired
  :ensure nil
  :commands (dired)
  :config
  (setq dired-auto-revert-buffer #'dired-directory-changed-p) ; also see `dired-do-revert-buffer'
  (setq dired-make-directory-clickable t) ; Emacs 29.1
  (setq dired-free-space nil) ; Emacs 29.1
  (setq dired-mouse-drag-files t) ; Emacs 29.1

  (add-hook 'dired-mode-hook #'dired-hide-details-mode)
  (add-hook 'dired-mode-hook #'hl-line-mode)

  ;; In Emacs 29 there is a binding for `repeat-mode' which lets you
  ;; repeat C-x C-j just by following it up with j.  For me, this is a
  ;; problem as j calls `dired-goto-file', which I often use.
  (define-key dired-jump-map (kbd "j") nil))
#+end_src

** The =nebkor-dired.el= section for using multi-occur (~noccur~)
:PROPERTIES:
:CUSTOM_ID: h:6EA8DE69-4FA3-48CE-BAF7-066680675AD7
:CREATED:  [2024-12-29 Sun 20:51]
:ID:       01JGD333R20000VGT6DPM8C86F
:END:

~multi-occur~ is brilliant. But most of the times, I do not have all
the buffers I want to run open. The fastest way to search a small
subset of my Denote notes is to narrow down what I want in a ~dired~
buffer using ~denote-sort~, and then use ~noccur-dired~ on it. This
tiny function is super-handy, and it's taken from the ~noccur~ package
(because that's all I need from that package)

(see: [[#h:e86a66dc-7ef9-4f09-ad7e-946de2034e8d][The =nebkor-study.el= section for notes and file-naming (~denote~)]])

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-dired.el"
  ;;; Multi-occur in project and dired!
  (use-package dired
    :ensure nil
    :bind
    ( :map dired-mode-map
      ("M-s a m" . noccur-dired))
    :config
    (defun noccur-dired (regexp &optional nlines)
      "Perform `multi-occur' with REGEXP in all dired marked files.
  When called with a prefix argument NLINES, display NLINES lines before and after."
      (interactive (occur-read-primary-args))
      (multi-occur (mapcar #'find-file (dired-get-marked-files)) regexp nlines)))
#+end_src


** The =nebkor-dired.el= section about various conveniences
:PROPERTIES:
:CUSTOM_ID: h:6758bf16-e47e-452e-b39d-9d67c2b9aa4b
:ID:       01JGD333R80003W7TM8MHB2C93
:END:

#+begin_quote
The =dired-aux.el= and =dired-x.el= are two built-in libraries that
provide useful extras for Dired. The highlights from what I have here
are:

- the user option ~dired-create-destination-dirs~ and
  ~dired-create-destination-dirs-on-trailing-dirsep~, which offer to
  create the specified directory path if it is missing.

- the user options ~dired-clean-up-buffers-too~ and
  ~dired-clean-confirm-killing-deleted-buffers~ which cover the
  deletion of buffers related to files that we delete from Dired.

- the key binding for ~dired-do-open~, which opens the file or
  directory externally ([[#h:d7d5e619-3d50-4d9e-a951-7262462a60c9][The =nebkor-dired.el= settings to open files externally]]).
#+end_quote

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-dired.el"
(use-package dired-aux
  :ensure nil
  :after dired
  :bind
  ( :map dired-mode-map
    ("C-+" . dired-create-empty-file)
    ("M-s f" . nil)
    ("C-<return>" . dired-do-open) ; Emacs 30
    ("C-x v v" . dired-vc-next-action)) ; Emacs 28
  :config
  (setq dired-isearch-filenames 'dwim)
  (setq dired-create-destination-dirs 'ask) ; Emacs 27
  (setq dired-vc-rename-file t)             ; Emacs 27
  (setq dired-do-revert-buffer (lambda (dir) (not (file-remote-p dir)))) ; Emacs 28
  (setq dired-create-destination-dirs-on-trailing-dirsep t)) ; Emacs 29

(use-package dired-x
  :ensure nil
  :after dired
  :bind
  ( :map dired-mode-map
    ("I" . dired-info))
  :config
  (setq dired-clean-up-buffers-too t)
  (setq dired-clean-confirm-killing-deleted-buffers t)
  (setq dired-x-hands-off-my-keys t)    ; easier to show the keys I use
  (setq dired-bind-man nil)
  (setq dired-bind-info nil))
#+end_src

** The =nebkor-dired.el= section about subdirectory contents (~dired-subtree~)
:PROPERTIES:
:CUSTOM_ID: h:3a4a29bc-3491-4d01-9d64-1cef63b3116a
:ID:       01JGD333RE000AB6XV70AYHGEH
:END:

#+begin_quote
The ~dired-subtree~ package by Matúš Goljer provides the convenience
of quickly revealing the contents of the directory at point. We do not
have to insert its contents below the current listing, as we would
normally do in Dired, nor do we have to open it in another buffer just
to check if we need to go further.

I do not use this feature frequently, though I appreciate it when I do
need it.
#+end_quote

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-dired.el"
(use-package dired-subtree
  :ensure t
  :after dired
  :bind
  ( :map dired-mode-map
    ("<tab>" . dired-subtree-toggle)
    ("TAB" . dired-subtree-toggle)
    ("<backtab>" . dired-subtree-remove)
    ("S-TAB" . dired-subtree-remove))
  :config
  (setq dired-subtree-use-backgrounds nil))
#+end_src

** The =nebkor-dired.el= section about writable Dired (~wdired~)
:PROPERTIES:
:CUSTOM_ID: h:1b53bc10-8b1b-4f68-bbec-165909761e43
:ID:       01JGD333RM0006N67R3T2F0MNZ
:END:

#+begin_quote
As noted in the introduction, Dired can be made writable
([[#h:f8b08a77-f3a8-42fa-b1a9-f940348889c3][The =nebkor-dired.el= module]]). This way, we can quickly rename
multiple files using Emacs' panoply of editing capabilities.

Both of the variables I configure here have situational usage. I
cannot remember the last time I benefited from them.

Note that we have a variant of ~wdired~ for ~grep~ buffers
([[#h:9a3581df-ab18-4266-815e-2edd7f7e4852][The =nebkor-search.el= setup for editable grep buffers (~wgrep~)]]).
#+end_quote

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-dired.el"
(use-package wdired
  :ensure nil
  :commands (wdired-change-to-wdired-mode)
  :config
  (setq wdired-allow-to-change-permissions t)
  (setq wdired-create-parent-directories t))
#+end_src

** The =nebkor-dired.el= section about moving to Trash (~trashed~)
:PROPERTIES:
:CUSTOM_ID: h:2e005bd1-d098-426d-91f9-2a31a6e55caa
:ID:       01JGD333RS000DC92B8GQB57VC
:END:

The ~trashed~ package by Shingo Tanaka provides a Dired-like interface
to the system's virtual trash directory. The few times I need to
restore a file, I do =M-x trashed=, then type =r= to mark the file to be
restored (=M-x trashed-flag-restore=), and then type =x= (=M-x trashed-do-execute=)
to apply the effect.

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-dired.el"
  ;;; dired-like mode for the trash (trashed.el)
  (use-package trashed
    :ensure t
    :commands (trashed)
    :config
    (setq trashed-action-confirmer 'y-or-n-p)
    (setq trashed-use-header-line t)
    (setq trashed-sort-key '("Date deleted" . t))
    (setq trashed-date-format "%Y-%m-%d %H:%M:%S"))
#+end_src

** Finally, we provide the =nebkor-dired.el= module
:PROPERTIES:
:CUSTOM_ID: h:c8b2f021-fe5a-4f6b-944c-20340f764fb2
:ID:       01JGD333RY000FC7EQB90W9RSC
:END:

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-dired.el"
(provide 'nebkor-dired)
#+end_src

* The =nebkor-window.el= module
:PROPERTIES:
:CUSTOM_ID: h:b5fa481d-8549-4424-869e-91091cdf730b
:END:

#+begin_quote
This module is all about buffers and windows. How they are managed and
displayed.
#+end_quote

** The =nebkor-window.el= section about uniquifying buffer names
:PROPERTIES:
:CUSTOM_ID: h:cfbea29c-3290-4fd1-a02a-d7e887c15674
:ID:       01JGD333S40001750PN9KWYHGB
:END:

#+begin_quote
When a buffer name is reserved, Emacs tries to produce the new buffer
by finding a suitable variant of the original name. The doc string of
the variable ~uniquify-buffer-name-style~ does a good job at
explaining the various patterns:

#+begin_example
For example, the files ‘/foo/bar/mumble/name’ and ‘/baz/quux/mumble/name’
would have the following buffer names in the various styles:

  forward                       bar/mumble/name    quux/mumble/name
  reverse                       name\mumble\bar    name\mumble\quux
  post-forward                  name|bar/mumble    name|quux/mumble
  post-forward-angle-brackets   name<bar/mumble>   name<quux/mumble>
  nil                           name               name<2>
#+end_example

I use the =forward= style, which is the closest to the actual file
name.
#+end_quote

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-window.el"
;;; General window and buffer configurations
(use-package uniquify
  :ensure nil
  :config
;;;; `uniquify' (unique names for buffers)
  (setq uniquify-buffer-name-style 'forward)
  (setq uniquify-strip-common-suffix t)
  (setq uniquify-after-kill-buffer-p t))
#+end_src


#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-window.el"
  (use-package window
    :ensure nil
    :config
    (setq display-buffer-reuse-frames t)
    (setq recenter-positions '(top middle bottom)))
#+end_src

** The =nebkor-window.el= section about frame-oriented workflows (~beframe~)
:PROPERTIES:
:CUSTOM_ID: h:77e4f174-0c86-460d-8a54-47545f922ae9
:ID:       01JGD333T4000B6PCT3N2X2M84
:END:

[ Also see: [[#h:7dcbcadf-8af6-487d-b864-e4ce56d69530][The =nebkor-git.el= section about =project.el=]]. ]

#+begin_quote
My ~beframe~ package enables a frame-oriented Emacs workflow where
each frame has access to the list of buffers visited therein. In the
interest of brevity, we call buffers that belong to frames "beframed".
Check the video demo I did and note that I consider this one of the
best changes I ever did to boost my productivity:
<https://protesilaos.com/codelog/2023-02-28-emacs-beframe-demo/>.
#+end_quote

+ Package name (GNU ELPA): ~beframe~
+ Official manual: <https://protesilaos.com/emacs/beframe>
+ Change log: <https://protesilaos.com/emacs/beframe-changelog>
+ Git repositories:
  - GitHub: <https://github.com/protesilaos/beframe>
  - GitLab: <https://gitlab.com/protesilaos/beframe>
+ Backronym: Buffers Encapsulated in Frames Realise Advanced
  Management of Emacs.

Some notes on how I use beframe:

- I add ~beframe~ as a source for ~consult-buffers~.
  + This code comes from the ~beframe~ manual and lets me look at frame-specific buffers first when using ~consult-buffer~ (=M-s M-b=) ([[#h:22e97b4c-d88d-4deb-9ab3-f80631f9ff1d][The =nebkor-completion.el= settings for ~consult~]])
- I replace the default =C-x b= (~switch-to-buffer~) with the beframe'd version of the same command.
  + This lets me focus on the buffers in the current frame, which is what I generally want. When I need to access files or buffers which are not open yet, I use either:
    - The consult version of the command (=M-s M-b=)
    - ~project.el~ to find a file in the current project (=C-x p f=)
- When I am done with the frame, I delete all the buffers in the frame using ~beframe-kill-buffers-matching-regexp~ (=C-c b k=) with regex ~*~. This automatically kills the frame.

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-window.el"
  ;;; Frame-isolated buffers
  ;; Another package of mine.  Read the manual:
  ;; <https://protesilaos.com/emacs/beframe>.
  (use-package beframe
    :ensure t
    :hook (elpaca-after-init . beframe-mode)
    :bind
    ("C-x f" . other-frame-prefix)
    ("C-c b" . beframe-prefix-map)
    ;; Replace the generic `buffer-menu'.  With a prefix argument, this
    ;; commands prompts for a frame.  Call the `buffer-menu' via M-x if
    ;; you absolutely need the global list of buffers.
    ("C-x C-b" . beframe-buffer-menu)
    ("C-x b" . beframe-switch-buffer)
    ("C-x B" . select-frame-by-name)
    :config
    (setq beframe-functions-in-frames '(project-prompt-project-dir))

    (defvar consult-buffer-sources)
    (declare-function consult--buffer-state "consult")

    (with-eval-after-load 'consult
      (defface beframe-buffer
        '((t :inherit font-lock-string-face))
        "Face for `consult' framed buffers.")

      (defun my-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."
        (beframe-buffer-names frame :sort #'beframe-buffer-sort-visibility))

      (defvar beframe-consult-source
        `( :name     "Frame-specific buffers (current frame)"
           :narrow   ?F
           :category buffer
           :face     beframe-buffer
           :history  beframe-history
           :items    ,#'my-beframe-buffer-names-sorted
           :action   ,#'switch-to-buffer
           :state    ,#'consult--buffer-state))

      (add-to-list 'consult-buffer-sources 'beframe-consult-source)))
#+end_src

** The =nebkor-window.el= use of contextual header line (~breadcrumb~)
:PROPERTIES:
:CUSTOM_ID: h:29ced61b-4b5a-4f63-af00-fe311468d1cd
:END:

#+begin_quote
The ~breadcrumb~ package by João Távora lets us display contextual
information about the current heading or code definition in the header
line. The header line is displayed above the contents of each buffer
in the given window. When we are editing an Org file, for example, we
see the path to the file, followed by a reference to the tree that
leads to the current heading. Same idea for programming modes. Neat!
#+end_quote

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-window.el"
;;; Header line context of symbol/heading (breadcrumb.el)
(use-package breadcrumb
  :ensure t
  :functions (prot/breadcrumb-local-mode)
  :hook ((text-mode prog-mode) . prot/breadcrumb-local-mode)
  :config
  (setq breadcrumb-project-max-length 0.5)
  (setq breadcrumb-project-crumb-separator "/")
  (setq breadcrumb-imenu-max-length 1.0)
  (setq breadcrumb-imenu-crumb-separator " > ")

  (defun prot/breadcrumb-local-mode ()
    "Enable `breadcrumb-local-mode' if the buffer is visiting a file."
    (when buffer-file-name
      (breadcrumb-local-mode 1))))
#+end_src

** Finally, we provide the =nebkor-window.el= module
:PROPERTIES:
:CUSTOM_ID: h:2124c200-734d-49c4-aeb1-513caaf957ae
:ID:       01JGD333VG000B35SFF4PD7TFS
:END:

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-window.el"
(provide 'nebkor-window)
#+end_src

* The =nebkor-git.el= module
:PROPERTIES:
:CUSTOM_ID: h:65e3eff5-0bff-4e1f-b6c5-0d3aa1a0d232
:END:

[ Watch Prot's talk: [[https://protesilaos.com/codelog/2023-08-03-contribute-core-emacs/][Contribute to GNU Emacs core]] (2023-08-03). ]

#+begin_quote
This section covers my settings for version control per se, but more
widely for tools related to checking different versions of files and
working with so-called "projects".
#+end_quote

** The =nebkor-git.el= section about ediff
:PROPERTIES:
:CUSTOM_ID: h:89edea05-4d94-4ea1-b2a8-5ad01422618c
:ID:       01JGD333VP0003NXM8W068900N
:END:

[ Watch Prot's talk: [[https://protesilaos.com/codelog/2023-11-17-emacs-ediff-basics/][Emacs: ediff basics]] (2023-12-30) ]

#+begin_quote
The built-in ~ediff~ feature provides several commands that let us
compare files or buffers side-by-side. The defaults of ~ediff~ are bad,
in my opinion: it puts buffers one on top of the other and places the
"control panel" in a separate Emacs frame. The first time I tried to
use it, I thought I broke my setup because it is unlike anything we
normally interact with. As such, the settings I have for
~ediff-split-window-function~ and ~ediff-window-setup-function~ are
what I would expect Emacs maintainers to adopt as the new default. I
strongly encourage everyone to start with them.

In my workflow, the points of entry to the ~ediff~ feature are the
commands ~ediff-files~, ~ediff-buffers~. Sometimes I use the 3-way
variants with ~ediff-files3~ and ~ediff-buffers3~, though this is rare.
Do watch the video I link to in the beginning of this section, as it
covers the main functionality of this neat tool. I also show how it
integrates with ~magit~ ([[#h:b08af527-9ebf-4425-ac3a-24b4f371a4fd][The =nebkor-git.el= section about ~magit~ (great Git client)]]).
#+end_quote

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-git.el" :mkdirp yes
;;;; `ediff'
(use-package ediff
  :ensure nil
  :commands (ediff-buffers ediff-files ediff-buffers3 ediff-files3)
  :init
  (setq ediff-split-window-function 'split-window-horizontally)
  (setq ediff-window-setup-function 'ediff-setup-windows-plain)
  :config
  (setq ediff-keep-variants nil)
  (setq ediff-make-buffers-readonly-at-startup nil)
  (setq ediff-merge-revisions-with-ancestor t)
  (setq ediff-show-clashes-only t))
#+end_src

** The =nebkor-git.el= section about project management (~project~)
:PROPERTIES:
:CUSTOM_ID: h:7dcbcadf-8af6-487d-b864-e4ce56d69530
:ID:       01JGD333VV0001W3ABZ22GE6QE
:END:

#+begin_quote
In Emacs parlance, a "project" is a collection of files and/or
directories that share the same root. The root of a project is
identified by a special file or directory, with =.git/= being one of
the defaults as it is a version control system supported by the
built-in =vc.el= ([[#h:50add1d8-f0f4-49be-9e57-ab280a4aa300][The =nebkor-git.el= section about =vc.el= and related]]).

We can specify more project roots as a list of strings in the user
option ~project-vc-extra-root-markers~. I work exclusively with Git
repositories, so I just add there a =.project= file in case I ever
need to register a project without it being controlled by ~git~. In
that case, the =.project= file is just an empty file in a directory
that I want to treat as the root of this project.

The common way to switch to a project is to type =C-x p p=, which
calls the command ~project-switch-project~. It lists all registered
projects and also includes a =... (choose a dir)= option. By choosing
a new directory, we register it in our project list if it has a
recognisable root. Once we select a project, we are presented with a
list of common actions to start working on the project. These are
defined in the user option ~project-switch-commands~ and are activated
by the final key that accesses them from the =C-x p= prefix. As such,
do =M-x describe-keymap= and check the ~project-prefix-map~. For
example, I bind ~project-dired~ to =C-x p RET=, so =RET= accesses this
command after =C-x p p= as well.

If any of the =project.el= commands is called from outside a project,
it first prompts for a project and then carries out its action. For
example, ~project-find-file~ will ask for a project to use, then
switch to it, and then prompt for a file inside of the specified
project.

While inside a project, we have many commands that operate on the
project level. For example, =C-x p f= (~project-find-file~) searches
for a file across the project, while =C-x p b= (~project-switch-to-buffer~)
switches to a buffer that is specific to the project. Again, check the
~project-prefix-map~ for available commands.

If not inside a project, the project-related commands will first
prompt to select a project (same as typing =C-x p p=) and then carry
out their action.

I combine projects with my ~beframe~ package, so that when I switch to
a project I get a new frame that limits the buffers I visit there
limited to that frame ([[#h:77e4f174-0c86-460d-8a54-47545f922ae9][The =nebkor-window.el= section about ~beframe~]]).
#+end_quote

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-git.el"
  ;;;; `project'
  (use-package project
    :ensure nil
    :bind
    (("C-x p ." . project-dired)
     ("C-x p C-g" . keyboard-quit)
     ("C-x p <return>" . project-dired)
     ("C-x p <delete>" . project-forget-project))
    :config
    (setopt project-switch-commands
            '((project-find-file "Find file")
              (project-find-regexp "Find regexp")
              (project-find-dir "Find directory")
              (project-dired "Root dired")
              (project-vc-dir "VC-Dir")
              (project-shell "Shell")
              (keyboard-quit "Quit")))
    (setq project-vc-extra-root-markers '(".project")) ; Emacs 29
    (setq project-key-prompt-style t)) ; Emacs 30
#+end_src

** The =nebkor-git.el= section about diff management (~diff-mode~)
:PROPERTIES:
:CUSTOM_ID: h:8b426a69-e3cd-42ac-8788-f41f6629f879
:ID:       01JGD333W1000101YRR8RRSWV4
:END:

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-git.el"
;;;; `diff-mode'
(use-package diff-mode
  :ensure nil
  :defer t
  :config
  (setq diff-default-read-only t)
  (setq diff-advance-after-apply-hunk t)
  (setq diff-update-on-the-fly t)
  ;; The following are from Emacs 27.1
  (setq diff-refine nil) ; I do it on demand, with my `agitate' package (more below)
  (setq diff-font-lock-prettify t) ; I think nil is better for patches, but let me try this for a while
  (setq diff-font-lock-syntax 'hunk-also))
#+end_src

** The =nebkor-git.el= section about using Git (~magit~)
:PROPERTIES:
:CUSTOM_ID: h:b08af527-9ebf-4425-ac3a-24b4f371a4fd
:ID:       01JGD333W6000B0WS815JQZTE7
:END:

#+begin_quote
The ~magit~ package, maintained by Jonas Bernoulli, is the best
front-end to ~git~ I have ever used. Not only is it excellent at
getting the job done, it also helps you learn more about what ~git~
has to offer.

At the core of its interface is ~transient~. This is a library that
was originally developed as Magit-specific code that was then
abstracted away and ultimately incorporated into Emacs version 29.
With ~transient~, we get a window pop up with keys and commands
corresponding to them. The window is interactive, as the user can set
a value or toggle an option and have it take effect when the relevant
command is eventually invoked. For ~git~, in particular, this
interface is a genious way to surface the plethora of options.

To start, call the command ~magit-status~. It brings up a buffer that
shows information about the state of the repository. Sections include
an overview of the current =HEAD=, untracked files, unstaged changes,
staged changes, and recent commits. Each section's visibility state
can be cycled by pressing =TAB= (variations of this are available---remember
to do =C-h m= (~describe-mode~) in an unfamiliar major mode to get
information about its key bindings).

From the status buffer, we can perform all the usual version control
operations. By typing =?= (~magit-dispatch~), we bring up the main
~transient~ menu, with keys that then bring up their own submenus,
such as for viewing commit logs, setting the remotes, switching
branches, etc.
#+end_quote

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-git.el"
  ;;; Interactive and powerful git front-end (Magit)
  (use-package magit
    :ensure t
    :bind ("C-x g" . magit-status)
    :init
    (setq magit-define-global-key-bindings nil)
    ;; (setq magit-section-visibility-indicator '("⮧"))
    :config
    (setq git-commit-summary-max-length 50)
    ;; NOTE 2023-01-24: I used to also include `overlong-summary-line'
    ;; in this list, but I realised I do not need it.  My summaries are
    ;; always in check.  When I exceed the limit, it is for a good
    ;; reason.
    ;; (setq git-commit-style-convention-checks '(non-empty-second-line))

    (setq magit-diff-refine-hunk t))

  (use-package transient
    :ensure t
    :after magit
    :config
    (setq transient-show-popup 0.5))

  (use-package magit-repos
    :ensure nil ; part of `magit'
    :commands (magit-list-repositories)
    :init
    (setq magit-repository-directories
          '(("~/src/prototypes" . 1))))
#+end_src

** Finally, we provide the =nebkor-git.el= module
:PROPERTIES:
:CUSTOM_ID: h:4e7035c5-9350-4c51-be85-85f2539ed295
:ID:       01JGD333WC0000QX1SSCQEWGT7
:END:

Finally, we ~provide~ the module.

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-git.el"
(provide 'nebkor-git)
#+end_src

* The =nebkor-org.el= module
:PROPERTIES:
:CUSTOM_ID: h:d799c3c0-bd6a-40bb-bd1a-ba4ea5367840
:END:

Watch these talks by Prot:

- [[https://protesilaos.com/codelog/2023-12-18-emacs-org-advanced-literate-conf/][Advanced literate configuration with Org]] (2023-12-18)
- [[https://protesilaos.com/codelog/2023-05-23-emacs-org-basics/][Basics of Org mode]] (2023-05-23)
- [[https://protesilaos.com/codelog/2021-12-09-emacs-org-block-agenda/][Demo of my custom Org block agenda]] (2021-12-09)
- [[https://protesilaos.com/codelog/2020-02-04-emacs-org-capture-intro/][Primer on "org-capture"]] (2020-02-04)


#+begin_quote
At its core, Org is a plain text markup language. By "markup
language", we refer to the use of common characters to apply styling,
such as how a word wrapped in asterisks acquires strong emphasis.
Check the video I link to above on the basics of Org mode.

Though what makes Org powerful is not the markup per se, but the fact
that it has a rich corpus of Emacs Lisp code that does a lot with this
otherwise plain text notation. Some of the headline features:

- Cycle the visibility of any heading and its subheadings. This lets
  you quickly fold a section you do not need to see (or reveal the one
  you care about).
- Mix prose with code in a single document to either make the whole
  thing an actual program or to evaluate/demonstrate some snippets.
- Convert ("export") an Org file to a variety of formats, including
  HTML and PDF.
- Use LaTeX inside of Org files to produce a scientific paper without
  all the markup of LaTeX.
- Manage TODO lists and implement a concomitant methodology of
  labelling task states.
- Quickly shift a "thing" (heading, list item, paragraph, ...) further
  up or down in the file.
- Use tables with formulas as a lightweight alternative to spreadsheet
  software.
- Capture data or fleeting thoughts efficiently using templates.
- Maintain an agenda for all your date-bound activities.
- Clock in and out of tasks, to eventually track how you are spending
  your time.
- Link to files regardless of file type. This includes special links
  such as to an Info manual or an email, if you also have that running
  locally and integrated with Emacs ([[#h:755e195b-9471-48c7-963b-33055969b4e2][The =nebkor-email.el= module]]).

In other words, Org is highly capable and widely considered one of the
killer apps of Emacs.

This section covers the relevant configurations. You will notice that
it is not limited to Org, as some other built-in features are also
relevant here.
#+end_quote

** The =nebkor-org.el= section on (~calendar~)
:PROPERTIES:
:CUSTOM_ID: h:94d48381-1711-4d6b-8449-918bc1e3836c
:ID:       01JGD333WH0003P7D9T0K7JKQ4
:END:

#+begin_quote
The ~calendar~ is technically independent of Org, though it tightly
integrates with it. We witness this when we are setting timestamps,
such as while setting a =SCHEDULED= or =DEADLINE= entry for a given
heading. All I do here is set some stylistic preferences.

Note that Emacs also has a ~diary~ command. I used it for a while, but
Org is far more capable, so I switched to it completely.
#+end_quote

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-org.el" :mkdirp yes
;;; Calendar
(use-package calendar
  :ensure nil
  :commands (calendar)
  :config
  (setq calendar-mark-diary-entries-flag nil)
  (setq calendar-mark-holidays-flag t)
  (setq calendar-mode-line-format nil)
  (setq calendar-time-display-form
        '( 24-hours ":" minutes
           (when time-zone (format "(%s)" time-zone))))
  (setq calendar-week-start-day 1)      ; Monday
  (setq calendar-date-style 'iso)
  (setq calendar-time-zone-style 'numeric) ; Emacs 28.1

  (require 'solar)
  (setq calendar-latitude 35.17         ; Not my actual coordinates
        calendar-longitude 33.36)

  (require 'cal-dst)
  (setq calendar-standard-time-zone-name "+0700")
  (setq calendar-daylight-time-zone-name "+0800"))
#+end_src

** The =nebkor-org.el= section about appointment reminders (~appt~)
:PROPERTIES:
:CUSTOM_ID: h:bd4b0dcb-a925-4bd7-90db-6379a7ca6f5e
:ID:       01JGD333WQ000EYDFGKCTMGP6N
:END:

#+begin_quote
The built in =appt.el= defines functionality for handling
notifications about appointments. It is originally designed to work
with the generic diary feature (the =M-x diary= one, I mean), which I
do not use anymore, but also integrates nicely with the Org agenda
([[#h:7fe87b83-2815-4617-a5f9-d3417dd9d248][The =nebkor-org.el= Org agenda settings]]). I deepen this
integration further, such that after adding a task or changing its
state, the appointments mechanism re-reads my data to register new
notifications. This is done via a series of hooks and with the use of
the advice feature of Emacs Lisp.

Here I am setting some simple settings to keep appointment notifations
minimal. I do not need them to inform me about the contents of my next
entry on the agenda: just show text on the mode line telling me how
many minutes are left until the event.

In Org files, every heading can have an =APPT_WARNTIME= property: it takes
a numeric value representing minutes for a forewarning from =appt.el=.
I use this in tandem with ~org-capture~ for tasks that need to be
done at a specific time, such as coaching sessions ([[#h:f8f06938-0dfe-45c3-b4cf-996d36cba82d][The =nebkor-org.el= Org capture templates (~org-capture~)]]).
#+end_quote

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-org.el"
;;; Appt (appointment reminders which also integrate with Org agenda)
(use-package appt
  :ensure nil
  :commands (appt-activate)
  :config
  (setq appt-display-diary nil
        appt-display-format nil
        appt-display-mode-line t
        appt-display-interval 3
        appt-audible nil
        appt-warning-time-regexp "appt \\([0-9]+\\)" ; This is for the diary
        appt-message-warning-time 6)

  (with-eval-after-load 'org-agenda
    (appt-activate 1)

    ;; Create reminders for tasks with a due date when this file is read.
    (org-agenda-to-appt)))
#+end_src

** The =nebkor-org.el= section on paragraphs
:PROPERTIES:
:CUSTOM_ID: h:71AF485F-9C6B-4936-B50F-3A126663FFB0
:ID:       01JGD333WX0000S737A5GHEDRA
:END:

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-org.el"
  (use-package emacs
    :ensure nil
    :config
    (setq sentence-end-double-space nil))
#+end_src

** The =nebkor-org.el= section with basic Org settings
:PROPERTIES:
:CUSTOM_ID: h:e03df1e0-b43e-49b5-978e-6a511165617c
:ID:       01JGD333X300013BMYP0MPMW75
:END:

By default, Org looks for files in the =~/org= directory. If you want to use a location other than the default localtion for your ~org-mode~ files, set the environment variable ~ORG_DIRECTORY~.

#+begin_quote
Org, also known as "Org mode", is one of the potentially most useful
feature sets available to every Emacs user. At its core, Org is a
lightweight markup language: you can have headings and paragraphs,
mark a portion of text with emphasis, produce bullet lists, include
code blocks, and the like. Though what really sets Org apart from
other markup languages is the rich corpus of Emacs Lisp written around
it to do all sorts of tasks with this otherwise plain text format.

With Org you can write technical documents (e.g. the manuals of all my
Emacs packages), maintain a simple or highly sophisticated system for
task management, organise your life using the agenda, write tables
that can evaluate formulas to have spreadsheet functionality, have
embedded LaTeX, evaluate code blocks in a wide range of programming
languages and reuse their results for literate programming, include
the contents of other files into a singular file, use one file to
generate other files/directories with all their contents, and export
the Org document to a variety of formats like =.pdf= and =.odt=.
Furthermore, Org can be used as a lightweight, plain text database, as
each heading can have its own metadata. This has practical
applications in most of the aforementioned.

In short, if something can be done with plain text, Org probably does
it already or has all the elements for piecing it together. This
document, among many of my published works, is testament to Org's
sheer power, which I explained at greater length in a video
demonstration: [[https://protesilaos.com/codelog/2023-12-18-emacs-org-advanced-literate-conf/][Advanced literate configuration with Org]] (2023-12-18).

This being Emacs, everything is customisable and Org is a good example
of this. There are a lot of user options for us to tweak things to our
liking. I do as much, though know that Org is perfectly usable without
any configuration. The following sections contain further commentary
on how I use Org.
#+end_quote

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-org.el"
  ;;; Org-mode (personal information manager)
  (use-package org
    :ensure nil
    :init
    (let ((dir (string-trim (shell-command-to-string "echo $ORG_DIRECTORY"))))
      (when (not (string-empty-p dir))
        (setq org-directory (expand-file-name dir))))
    (setq org-imenu-depth 7)

    (add-to-list 'safe-local-variable-values '(org-hide-leading-stars . t))
    (add-to-list 'safe-local-variable-values '(org-hide-macro-markers . t))
    :bind
    ( :map global-map
      ("C-c l" . org-store-link)
      ("C-c o" . org-open-at-point-global)
      :map org-mode-map
      ;; I don't like that Org binds one zillion keys, so if I want one
      ;; for something more important, I disable it from here.
      ("C-'" . nil)
      ("C-," . nil)
      ("M-;" . nil)
      ("C-c M-l" . org-insert-last-stored-link)
      ("C-c C-M-l" . org-toggle-link-display)
      ("M-." . org-edit-special) ; alias for C-c ' (mnenomic is global M-. that goes to source)
      :map org-src-mode-map
      ("M-," . org-edit-src-exit) ; see M-. above
      )
    :config
  ;;;; general settings
    (setq org-ellipsis "...")
    (setq org-adapt-indentation nil)      ; No, non, nein, όχι!
    (setq org-special-ctrl-a/e 'reversed)
    (setq org-special-ctrl-k nil)
    (setq org-use-speed-commands t)
    (setq org-M-RET-may-split-line '((headline . nil)
                                     (item . t)
                                     (table . nil)
                                     (default . t)))
    (setq org-hide-emphasis-markers nil)
    (setq org-hide-macro-markers nil)
    (setq org-hide-leading-stars nil)
    (setq org-cycle-separator-lines 0)
    (setq 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")))
    (setq org-return-follows-link nil)
    (setq org-loop-over-headlines-in-active-region 'start-level)
    (setq org-modules '(ol-info ol-eww))
    (setq org-insert-heading-respect-content t)
    (setq org-read-date-prefer-future 'time)
    (setq org-highlight-latex-and-related nil) ; other options affect elisp regexp in src blocks
    (setq org-fontify-quote-and-verse-blocks t)
    (setq org-fontify-whole-block-delimiter-line t)
    (setq org-track-ordered-property-with-tag t))
#+end_src

** The =nebkor-org.el= section for archival settings
:PROPERTIES:
:CUSTOM_ID: h:119A0C7D-03E1-41D0-AC98-F14FD92245F1
:CREATED: [2024-11-28 Thu 11:46]
:ID:       01JGD333X9000CTWDYYA5XGGSR
:END:

I like all my archived files to sit in a single location, because I generally do not refer to archived notes unless I am specifically looking for something. Note that these settings depend on the ~org-directory~ being set ([[#h:e03df1e0-b43e-49b5-978e-6a511165617c][The =nebkor-org.el= section with basic Org settings]])

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-org.el"
  ;;;; archival, org-archive
  (use-package org
    :ensure nil
    :config
    ;; Setup directory and file paths for org
    (defvar org-archive-directory (concat org-directory "/archive")
      "Directory under which all archived content is stored.")
    (setq org-archive-location (concat org-archive-directory "/%s_archive::")))
#+end_src

** The =nebkor-org.el= section for narrowing and folding
:PROPERTIES:
:CUSTOM_ID: h:D11C6331-86BB-466D-AF65-A2A50F49A808
:CREATED: [2024-11-28 Thu 15:55]
:ID:       01JGD333XF0007SC5KTFAQ8KGV
:END:
Narrowing and folding are really powerful ways of working with org-mode, since we tend to create so many nodes and subtrees when we write documents in Org

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-org.el"
  ;;; Narrowing and Folding
  (use-package org
    :ensure nil
    :bind
    ( :map narrow-map
      ("b" . org-narrow-to-block)
      ("e" . org-narrow-to-element)
      ("s" . org-narrow-to-subtree)))

  (use-package org-fold
    :ensure nil
    :config
    (setq org-fold-catch-invisible-edits 'show-and-error))
#+end_src
** The =nebkor-org.el= Org to-do and refile settings
:PROPERTIES:
:CUSTOM_ID: h:024dd541-0061-4a10-b10b-b17dcd4794b9
:ID:       01JGD333XN000CM4ZSYQ6HA3E5
:END:

#+begin_quote
One of the many use-cases for Org is to maintain a plain text to-do
list. A heading that starts with a to-do keyword, such as =TODO= is
treated as a task and its state is considered not completed.

We can switch between the task states with shift and the left or right
arrow keys. Or we can select a keyword directly with =C-c C-t=, which
calls ~org-todo~ by default. I personally prefer the latter approach,
as it is more precise.

Whenever a task state changes, we can log that event in a special
=LOGBOOK= drawer. This is automatically placed right below the
heading, before any paragraph text. Logging data is an opt-in feature,
which I consider helpful ([[#h:0884658e-9eb5-47e3-9338-66e09004a1a0][The =nebkor-org.el= Org time/state logging]]).

Tasks can be associated with timestamps, typically a scheduled
date+time or a deadline+time. This can be helpful when we are
reviewing the source Org file, though it really shines in tandem with
the agenda. Any heading that has a timestamp and which belongs to a
file in the ~org-agenda-files~ will show up on the agenda in the given
date ([[#h:7fe87b83-2815-4617-a5f9-d3417dd9d248][The =nebkor-org.el= Org agenda settings]]).

By default, the ~org-todo-keywords~ are =TODO= and =DONE=. We can
write more keywords if we wish to implement a descriptive workflow.
For example, we can have a =WAIT= keyword for something that is to be
done but is not actionable yet. While the number of keywords is not
limited, the binary model is the same: we have words that represent
the incomplete state and those that count as the completion of the
task. For instance, both =CANCEL= and =DONE= mean that the task is not
actionable anymore and we move on to other things. As such, the extra
keywords are a way for the user to make tasks more descriptive and
easy to find. In the value of the ~org-todo-keywords~, we use the bar
character to separate the incomplete state to the left from the
completed one to the right.

One of the agenda's headiline features is the ability to produce a
view that lists headings with the given keyword. So having the right
terms can make search and retrieval of data more easy. On the
flip-side, too many keywords add cognitive load and require more
explicit search terms to yield the desired results. I used to work
with a more descriptive set of keywords, but ultimately decided to
keep things simple.

The refile mechanism is how we can reparent a heading, by moving it
from one place to another. We do this with the command ~org-refile~,
bound to =C-c C-w= by default. A common workflow where refiling is
essential is to have an "inbox" file or heading, where unprocessed
information is stored at, and periodically process its contents to
move the data where it belongs. Though it can also work fine without
any such inbox, in those cases where a heading should be stored
someplace else. The ~org-refile-targets~ specifies the files that are
available when we try to refile the current heading. With how I set it
up, all the agenda files plus the current file's headings up to level
2 are included as possible targets.

In terms of workflow, I have not done a refile in a very long time,
because my entries always stay in the same place as I had envisaged at
the capture phase ([[#h:f8f06938-0dfe-45c3-b4cf-996d36cba82d][The =nebkor-org.el= Org capture templates (~org-capture~)]]).
#+end_quote

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-org.el"
  ;;;; refile, todo
  (use-package org
    :ensure nil
    :bind
    ( :map org-mode-map
      ("C-c t" . org-todo))
    :config
    (setq org-refile-targets
          '((org-agenda-files . (:maxlevel . 2))
            (nil . (:maxlevel . 2))))
    (setq org-refile-use-outline-path 'file)
    (setq org-refile-allow-creating-parent-nodes 'confirm)
    (setq org-refile-use-cache t)
    (setq org-outline-path-complete-in-steps nil)
    (setq org-reverse-note-order nil)
    (setq org-todo-keywords
          '((sequence "TODO(t!)" "WORKING(w!)" "|" "CANCEL(c@)" "DONE(d!)")
            (sequence "PROJECT(p!)" "FOLLOWUP(f!)" "WAITING(a@/!)" "DELEGATED(e@/!)")
            (sequence "PROSPECT(P!)" "QUAL(q!)" "PROPOSAL(o!)" "WAITING(a@/!)" "NEGOTIATION(n!)" "CONTRACT(c!)" "|" "WON(W!)" "LOST(l@/!)")))

    (defface prot/org-bold-done
      '((t :inherit (bold org-done)))
      "Face for bold DONE-type Org keywords.")

    (setq org-todo-keyword-faces
          '(("CANCEL" . prot/org-bold-done)))
    (setq org-use-fast-todo-selection t)

    (setq org-fontify-done-headline nil)
    (setq org-fontify-todo-headline nil)
    (setq org-fontify-whole-heading-line nil)
    (setq org-enforce-todo-dependencies t)
    (setq org-enforce-todo-checkbox-dependencies t))
#+end_src

** The =nebkor-org.el= Org heading tags
:PROPERTIES:
:CUSTOM_ID: h:81de4e32-a1af-4e1f-9e10-90eb0c90afa2
:ID:       01JGD333ZY0005VCWP14FFR51P
:END:

#+begin_quote
Each Org heading can have one or more tags associated with it, while
all headings inherit any potential =#+FILETAGS=. We can add tags to a
heading when the cursor is over it by typing the ever flexible =C-c C-c=.
Though the more specific ~org-set-tags-command~ also gets the job
done, plus it does not require that the cursor is positioned on the
heading text.

Tagging is useful for searching and retrieving the data we store. The
Org agenda, in particular, provides commands to filter tasks by tag:

- [[#h:024dd541-0061-4a10-b10b-b17dcd4794b9][The =nebkor-org.el= Org to-do and refile settings]]
- [[#h:7fe87b83-2815-4617-a5f9-d3417dd9d248][The =nebkor-org.el= Org agenda settings]]
#+end_quote

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-org.el"
  ;;;; tags
  (use-package org
    :ensure nil
    :config
    (setq org-tag-alist
          '((:startgrouptag)
            ("TaskStatus" . ?S)
            (:grouptags)
            ("next" . ?x)
            ("waiting" . ?w)
            ("delegated" . ?d)
            (:endgrouptag)
            (:startgrouptag)
            ("Writing" . ?W)
            (:grouptags)
            ("notes" . ?n)
            ("sketch" . ?s)
            ("feedback" . ?f)
            ("actionitems" . ?a)
            ("noexport" . ?N)
            (:endgrouptag)
            (:startgrouptag)
            ("TaskType" . ?T)
            (:startgrouptag)
            ("joy" . ?j)
            ("errand" . ?e)
            ("bug" . ?b)
            ("habit" . ?h)
            ("goal" . ?g)
            (:endgrouptag)
            ("important" . ?i)
            ("refile" . ?r)
            ("future" . ?F)))
    (setq org-auto-align-tags nil)
    (setq org-tags-column 0))
#+end_src

** The =nebkor-org.el= Org priorities settings
:PROPERTIES:
:CUSTOM_ID: h:5D8AF9BA-E1D5-4908-834D-E21718199D88
:CREATED:  [2024-11-30 Sat 16:33]
:ID:       01JGD3340400041EGA7QK2YKN3
:END:

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-org.el"
  ;;; org-priorities
  (use-package org
    :ensure nil
    :config
    (setq org-highest-priority ?A)
    (setq org-lowest-priority ?C)
    (setq org-default-priority ?A)
    (setq org-priority-faces nil))
#+end_src
** The =nebkor-org.el= Org time/state logging
:PROPERTIES:
:CUSTOM_ID: h:0884658e-9eb5-47e3-9338-66e09004a1a0
:ID:       01JGD3340B0000D3SPSA10CMC8
:END:

#+begin_quote
Org can keep a record of state changes, such as when we set an entry
marked with the =TODO= keyword as =DONE= or when we reschedule an
appointment ([[#h:7fe87b83-2815-4617-a5f9-d3417dd9d248][The =nebkor-org.el= Org agenda settings]]). This data
is stored in a =LOGBOOK= drawer right below the heading. I choose to
keep track of this information, as it is sometimes useful to capture
mistakes or figure out intent in the absence of further clarification
(though I do tend to write why something happened).
#+end_quote

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-org.el"
  ;;;; log
  (use-package org
    :ensure nil
    :config
    (setq org-log-done 'time)
    (setq org-log-into-drawer t)
    (setq org-log-note-clock-out nil)
    (setq org-log-redeadline 'time)
    (setq org-log-reschedule 'time)
    ;; Persist clock across Emacs sessions
    (org-clock-persistence-insinuate))

  ;;; org-clock
  (use-package org-clock
    :ensure nil
    :bind
    ("C-<f11>" . org-clock-goto)
    :config
    (setq org-clock-history-length 20)
    (setq org-clock-in-resume t)
    (setq org-clock-into-drawer "CLOCK")
    (setq org-clock-out-remove-zero-time-clocks t)
    (setq org-clock-persist t)
    (setq org-clock-auto-clock-resolution 'when-no-clock-is-running)
    (setq org-clock-report-include-clocking-task t)

    ;; List of TODO states to clock-in
    (setq vm/todo-list '("TODO" "WAITING"))

    ;; Change task state to WORKING when clocking in
    (defun bh/clock-in-to-working (kw)
      "Switch task from TODO to WORKING when clocking in.
  Skips capture tasks and tasks with subtasks"
      (when (and (not (and (boundp 'org-capture-mode) org-capture-mode))
                 (member kw vm/todo-list))
        "WORKING"))

    (setq org-clock-in-switch-to-state 'bh/clock-in-to-working))
#+end_src

** The =nebkor-org.el= Org link settings
:PROPERTIES:
:CUSTOM_ID: h:da8ce883-7f21-4a6e-a41f-d668ad762b41
:ID:       01JGD3340H0002E974HHEPEXCW
:END:

#+begin_quote
One of the nice things about Org is its flexible linking mechanism. It
can produce links to a variety of file types or buffers and even
navigate to a section therein.

At its simplest form, we have the =file= link type, which points to a
file system path, with an optional extension for a match inside the
file, as documented in the manual. Evaluate this inside of Emacs:

#+begin_example emacs-lisp
(info "(org) Search Options")
#+end_example
#+end_quote

I bind ~org-store-link~ in main section of the Org configuration:
[[#h:e03df1e0-b43e-49b5-978e-6a511165617c][The =nebkor-org.el= section with basic Org settings]].

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-org.el"
;;;; links
(use-package org
  :ensure nil
  :config
  (setq org-link-context-for-files t)
  (setq org-link-keep-stored-after-insertion nil)
  (setq org-id-link-to-org-use-id 'create-if-interactive-and-no-custom-id))
#+end_src

** The =nebkor-org.el= Org list items settings
:PROPERTIES:
:ID:       01JGD3340Q0005C7RVKGBVTBY3
:END:
#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-org.el"
  ;;; org-list
  (use-package org-list
    :ensure nil
    :config
    (setq org-list-demote-modify-bullet
          '(("+" . "-") ("-" . "+") ("*" . "+"))))

  ;; From @suvrat to keep touch-typing when working with lists.
  (defun suv/org-move-item-or-tree ()
    (interactive)
    (message "Use f, b, n, p to move individual items. Use C-{f,b,n,p} for point movement.")
    (let ((map (make-sparse-keymap)))
      (define-key map (kbd "f") 'org-shiftmetaright)
      (define-key map (kbd "b") 'org-shiftmetaleft)
      (define-key map (kbd "n") 'org-metadown)
      (define-key map (kbd "p") 'org-metaup)
      (define-key map (kbd "C-f") 'forward-char)
      (define-key map (kbd "C-b") 'backward-char)
      (define-key map (kbd "C-n") 'next-line)
      (define-key map (kbd "C-p") 'previous-line)
      (set-transient-map map t)))

  (use-package org
    :ensure nil
    :config
    (define-key org-mode-map (kbd "C-c j") 'suv/org-move-item-or-tree))
#+end_src

** The =nebkor-org.el= Org code block settings
:PROPERTIES:
:CUSTOM_ID: h:1f5a0d46-5202-48dd-8048-b48ce17f3df8
:ID:       01JGD3340Y000D4V8FDJQDMT01
:END:

#+begin_quote
More generally, Org is capable of evaluating code blocks and passing
their return value to other code blocks. It is thus possible to write
a fully fledged program as an Org document. This paradigm is known as
"literate programming". In the case of an Emacs configuration, such as
mine, it is called a "literate configuration" or variants thereof. I
did a video about my setup: [[https://protesilaos.com/codelog/2023-12-18-emacs-org-advanced-literate-conf/][Advanced literate configuration with Org]] (2023-12-18).

Org can evaluate code blocks in many languages. This is known as "Org
Babel" and the files which implement support for a given language are
typically named =ob-LANG.el= where =LANG= is the name of the language.
We can load the requisite code for the languages we care about with
something like the following:

#+begin_example emacs-lisp
(require 'ob-python)

;; OR

(use-package ob-python)

;; OR for more control

(use-package ob-python
  :after org
  :config
  ;; Settings here
  )
#+end_example
#+end_quote

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-org.el"
  ;;;; code blocks
  (use-package org
    :ensure nil
    :config
    (setq org-confirm-babel-evaluate nil)
    (setq org-src-window-setup 'current-window)
    (setq org-edit-src-persistent-message nil)
    (setq org-src-fontify-natively t)
    (setq org-src-preserve-indentation nil)
    (setq org-src-tab-acts-natively t)
    (setq org-edit-src-content-indentation 2))
#+end_src

** The =nebkor-org.el= Org export settings
:PROPERTIES:
:CUSTOM_ID: h:bd11d4d8-6e9f-4536-87a4-4018783bf8f5
:ID:       01JGD334140000KCW78KV54YZ3
:END:

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-org.el"
  ;;;; export
  (use-package org
    :ensure nil
    :init
    ;; NOTE 2023-05-20: Must be evaluated before Org is loaded,
    ;; otherwise we have to use the Custom UI.  No thanks!
    (setq org-export-backends '(html texinfo md beamer))
    :config
    (setq org-export-with-toc t)
    (setq org-export-headline-levels 8)
    (setq org-export-dispatch-use-expert-ui nil)
    (setq org-html-htmlize-output-type nil)
    (setq org-html-head-include-default-style nil)
    (setq org-html-head-include-scripts nil)
    (setq org-use-sub-superscripts '{}))

  (use-package ox-latex
    :ensure nil
    :config
    (setq org-latex-packages-alist
          '(("capitalize" "cleveref" nil)
            ("" "booktabs" nil)
            ("" "svg" nil)
            ("" "fontspec" nil)))
    (when (executable-find "pygmentize")
      (add-to-list 'org-latex-packages-alist '("newfloat" "minted" nil))
      (setq org-latex-src-block-backend 'minted))
    (setq org-latex-reference-command "\\cref{%s}")
    (setq org-latex-tables-booktabs t)
    (setq org-latex-compiler "lualatex")
    (setq org-latex-hyperref-template
          "\\hypersetup{
   pdfauthor={%a},
   pdftitle={%t},
   pdfkeywords={%k},
   pdfsubject={%d},
   pdfcreator={%c},
   pdflang={%L},
   linktoc=all,
   colorlinks=true,
   linkcolor=blue,
   urlcolor=blue,
   citecolor=blue,
   pdfborder={0 0 1}
   }
  ")
    (when (executable-find "latexmk")
      (setq org-latex-pdf-process
            '("latexmk -f -pdf -%latex --jobname=%b  -file-line-error --synctex=1 -shell-escape -interaction=nonstopmode -output-directory=%o %f")))
    (setq org-image-actual-width nil))
#+end_src

** The =nebkor-org.el= Org capture templates (~org-capture~)
:PROPERTIES:
:CUSTOM_ID: h:f8f06938-0dfe-45c3-b4cf-996d36cba82d
:ID:       01JGD3341Z000B0WMK23B069EP
:END:

#+begin_quote
The ~org-capture~ command allows us to quickly store data in some
structured way. This is done with the help of a templating system
where we can, for example, record the date the entry was recorded,
prompt for user input, automatically use the email's subject as the
title of the task, and the like. The documentation string of
~org-capture-templates~ covers the technicalities.
#+end_quote

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-org.el"
  ;;;; org-capture
  (use-package org-capture
    :ensure nil
    :bind ("C-c c" . org-capture))
#+end_src

** The =nebkor-org.el= section on YASnippet
:PROPERTIES:
:CUSTOM_ID: h:75C672E5-4636-4CC0-A5ED-500EAFAB01DE
:ID:       01JGD3342T00034JXJBD1KCF9K
:END:

~yasnippet~ is the primary tool I use when I need templates of any kind. Since most of my templating is in ~org-mode~, it makes sense to keep YAS in this section.

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-org.el"
  ;;; YASnippets for daily life improvements
  (use-package yasnippet
    :ensure t
    :config
    (yas-global-mode 1)
    (add-to-list 'hippie-expand-try-functions-list
                 'yas-hippie-try-expand))

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

  ;; I use timestamp functions from ts.el in my snippets
  (use-package ts
    :ensure t
    :config
    (defun this-week-range (&optional week-num)
      "Return timestamps \(BEG . END\) spanning the WEEK-NUM calendar work week.
  If WEEK-NUM is not provided, use the current week."
      (let* ((now (ts-now))
             ;; Navigate to the date we need to
             (curr-week (string-to-number (ts-format "%W" now)))
             (days-to-adjust (if week-num (* 7 (- curr-week week-num)) 0))
             ;; We start by calculating the offsets for the beginning and
             ;; ending timestamps using the current day of the week.  Note
             ;; that the `ts-dow' slot uses the "%w" format specifier, which
             ;; counts from Sunday to Saturday as a number from 0 to 6.
             (adjust-beg-day (- (- (ts-dow now) 1)))
             (adjust-end-day (- 5 (ts-dow now)))
             ;; Make beginning/end timestamps based on `now', with adjusted
             ;; day and hour/minute/second values.  These functions return
             ;; new timestamps, so `now' is unchanged.
             (beg (thread-last now
                               ;; `ts-adjust' makes relative adjustments to timestamps.
                               (ts-adjust 'day (- adjust-beg-day days-to-adjust))
                               ;; `ts-apply' applies absolute values to timestamps.
                               (ts-apply :hour 0 :minute 0 :second 0)))
             (end (thread-last now
                               (ts-adjust 'day (- adjust-end-day days-to-adjust))
                               (ts-apply :hour 23 :minute 59 :second 59))))
        (cons beg end))))
#+end_src

** The =nebkor-org.el= Org agenda settings
:PROPERTIES:
:CUSTOM_ID: h:7fe87b83-2815-4617-a5f9-d3417dd9d248
:ID:       01JGD3343Q000FF721CKG7YFZ2
:END:

[ Watch Prot's talk: [[https://protesilaos.com/codelog/2021-12-09-emacs-org-block-agenda/][Demo of my custom Org block agenda]] (2021-12-09). It has
  changed a bit since then, but the idea is the same. ]

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-org.el"
  ;;;; agenda
  (use-package org-agenda
    :ensure nil
    :bind
    ("C-c a" . org-agenda)
    :config

  ;;;;; Basic agenda setup
    (setq org-agenda-files `(,org-directory))
    (setq org-agenda-span 'day)
    (setq org-agenda-start-on-weekday 1)  ; Monday
    (setq org-agenda-confirm-kill t)
    (setq org-agenda-show-all-dates t)
    (setq org-agenda-show-outline-path nil)
    ;; Show me only the Agenda window when I ask for Agenda
    (setq org-agenda-window-setup 'only-window)
    (setq org-agenda-skip-comment-trees t)
    (setq org-agenda-menu-show-matcher t)
    (setq org-agenda-menu-two-columns nil)
    ;; Don't recalculate agenda unless I explicitly say so.
    (setq org-agenda-sticky t)
    (setq org-agenda-custom-commands-contexts nil)
    (setq org-agenda-max-entries nil)
    (setq org-agenda-max-todos nil)
    (setq org-agenda-max-tags nil)
    (setq org-agenda-max-effort nil)

  ;;;;; General agenda view options
    (setq org-agenda-prefix-format
          '((agenda . " %i %-12:c%?-12t% s")
            (todo . " %i %-12:c")
            (tags . " %i %-12:c")
            (search . " %i %-12:c")))
    (setq org-agenda-sorting-strategy
          '(((agenda habit-down time-up priority-down category-keep)
             (todo priority-down category-keep)
             (tags priority-down category-keep)
             (search category-keep))))
    (setq org-agenda-breadcrumbs-separator "->")
    (setq org-agenda-todo-keyword-format "%-1s")
    (setq org-agenda-fontify-priorities 'cookies)
    (setq org-agenda-category-icon-alist nil)
    (setq org-agenda-remove-times-when-in-prefix nil)
    (setq org-agenda-remove-timeranges-from-blocks nil)
    (setq org-agenda-compact-blocks nil)

  ;;;;; Agenda diary entries
    (setq org-agenda-insert-diary-strategy 'date-tree)
    (setq org-agenda-insert-diary-extract-time nil)
    (setq org-agenda-include-diary nil)
    ;; I do not want the diary, but there is no way to disable it
    ;; altogether.  This creates a diary file in the /tmp directory.
    (setq diary-file (make-temp-file "emacs-diary-"))
    (setq org-agenda-diary-file 'diary-file) ; TODO 2023-05-20: review Org diary substitute

  ;;;;; Agenda follow mode
    (setq org-agenda-start-with-follow-mode nil)
    (setq org-agenda-follow-indirect t)

  ;;;;; Agenda multi-item tasks
    (setq org-agenda-dim-blocked-tasks t)
    (setq org-agenda-todo-list-sublevels t)

  ;;;;; Agenda filters and restricted views
    (setq org-agenda-persistent-filter nil)
    (setq org-agenda-restriction-lock-highlight-subtree t)

  ;;;;; Agenda items with deadline and scheduled timestamps
    (setq org-agenda-include-deadlines t)
    (setq org-deadline-warning-days 5)
    (setq org-agenda-skip-scheduled-if-done nil)
    (setq org-agenda-skip-scheduled-if-deadline-is-shown t)
    (setq org-agenda-skip-timestamp-if-deadline-is-shown t)
    (setq org-agenda-skip-deadline-if-done nil)
    (setq org-agenda-skip-deadline-prewarning-if-scheduled 'pre-scheduled)
    (setq org-agenda-skip-scheduled-delay-if-deadline nil)
    (setq org-agenda-skip-additional-timestamps-same-entry nil)
    (setq org-agenda-skip-timestamp-if-done nil)
    (setq org-agenda-search-headline-for-time nil)
    (setq org-scheduled-past-days 365)
    (setq org-deadline-past-days 365)
    (setq org-agenda-move-date-from-past-immediately-to-today t)
    (setq org-agenda-show-future-repeats t)
    (setq org-agenda-prefer-last-repeat nil)
    (setq org-agenda-timerange-leaders
          '("" "(%d/%d): "))
    (setq org-agenda-scheduled-leaders
          '("Scheduled: " "Sched.%2dx: "))
    (setq org-agenda-inactive-leader "[")
    (setq org-agenda-deadline-leaders
          '("Deadline:  " "In %3d d.: " "%2d d. ago: "))
    ;; Time grid
    (setq org-agenda-time-leading-zero t)
    (setq org-agenda-timegrid-use-ampm nil)
    (setq org-agenda-use-time-grid t)
    (setq org-agenda-show-current-time-in-grid t)
    (setq org-agenda-current-time-string (concat "Now " (make-string 70 ?.)))
    (setq org-agenda-time-grid
          '((daily today require-timed)
            ( 0500 0600 0700 0800 0900 1000
              1100 1200 1300 1400 1500 1600
              1700 1800 1900 2000 2100 2200)
            "" ""))
    (setq org-agenda-default-appointment-duration nil)

  ;;;;; Agenda global to-do list
    (setq org-agenda-todo-ignore-with-date t)
    (setq org-agenda-todo-ignore-timestamp t)
    (setq org-agenda-todo-ignore-scheduled t)
    (setq org-agenda-todo-ignore-deadlines t)
    (setq org-agenda-todo-ignore-time-comparison-use-seconds t)
    (setq org-agenda-tags-todo-honor-ignore-options nil)

  ;;;;; Agenda tagged items
    (setq org-agenda-show-inherited-tags t)
    (setq org-agenda-use-tag-inheritance
          '(todo search agenda))
    (setq org-agenda-hide-tags-regexp nil)
    (setq org-agenda-remove-tags nil)
    (setq org-agenda-tags-column -100)

  ;;;;; Agenda entry
    (setq org-agenda-start-with-entry-text-mode nil)
    (setq org-agenda-entry-text-maxlines 5)
    (setq org-agenda-entry-text-exclude-regexps nil)
    (setq org-agenda-entry-text-leaders "    > ")
    (setq org-agenda-text-search-extra-files '(agenda-archives))

  ;;;;; Agenda logging and clocking
    (setq org-agenda-log-mode-items '(clock))
    (setq org-agenda-log-mode-add-notes t)
    (setq org-agenda-start-with-log-mode nil)
    (setq org-agenda-start-with-clockreport-mode nil)
    (setq org-agenda-clockreport-parameter-plist '(:link t :maxlevel 3 :fileskip0 t :compact t :narrow 80))
    (setq org-agenda-search-view-always-boolean nil)
    (setq org-agenda-search-view-force-full-words nil)
    (setq org-agenda-search-view-max-outline-level 0)
    (setq org-agenda-search-headline-for-time t)
    (setq org-agenda-use-time-grid t)
    (setq org-agenda-cmp-user-defined nil)
    (setq org-agenda-sort-notime-is-late t)   ; Org 9.4
    (setq org-agenda-sort-noeffort-is-high t) ; Org 9.4

  ;;;;; Agenda column view
    ;; NOTE I do not use these, but may need them in the future.
    (setq org-agenda-view-columns-initially nil)
    (setq org-agenda-columns-show-summaries t)
    (setq org-agenda-columns-compute-summary-properties t)
    (setq org-agenda-columns-add-appointments-to-effort-sum nil)
    (setq org-agenda-auto-exclude-function nil)
    (setq org-agenda-bulk-custom-functions nil)

    ;; Note: This is a column format that's been useful a few times.
    ;; Noting it here so that I can use it when needed.
    ;; (setq org-columns-default-format
    ;;     "%50ITEM(Task) %5Effort(Effort){:} %5CLOCKSUM %3PRIORITY %20CLOSED %20DEADLINE %20SCHEDULED %20TIMESTAMP %TODO %CATEGORY(Category) %TAGS")
  ;;;;; Custom views for Agenda
    (setq org-agenda-custom-commands
          '(("g" "GTD Agenda"
             ((agenda ""
                      ((org-agenda-overriding-header
                        "Your Meetings today")
                       (org-agenda-entry-types '(:timestamp :sexp))
                       (org-agenda-repeating-timestamp-show-all t)
                       (org-agenda-time-grid
                        '((daily today require-timed)
                          (800 1000 1200 1400 1600 1800 2000 2200)
                          " ┄┄┄┄┄ " "┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄"))
                       (org-agenda-current-time-string
                        "◀── now ─────────────────────────────────────────────────")))
              (tags-todo "+important-notoday"
                         ((org-agenda-overriding-header
                           "These are your IMPORTANT Tasks")
                          (org-agenda-dim-blocked-tasks)
                          ;; Sorting is *really* slowing it down.

                          ;; @TODO: Figure out a way to speed this up,
                          ;; maybe by specifying certain files here and
                          ;; creating a separate custom agenda for all
                          ;; important tasks.
                          ;; (org-agenda-sorting-strategy
                          ;;  '(timestamp-down effort-up))
                          ))
              (agenda ""
                      ((org-agenda-overriding-header
                        "These are your URGENT Tasks")
                       (org-agenda-entry-types '(:deadline))
                       (org-deadline-warning-days 2)
                       (org-agenda-sorting-strategy
                        '(habit-down priority-down timestamp-down))))
              (tags-todo "+joy-notoday"
                         ((org-agenda-overriding-header
                           "These tasks bring JOY")
                          (org-agenda-dim-blocked-tasks)))
              (tags-todo "+notoday"
                         ((org-agenda-overriding-header
                           "I will NOT DO these today")
                          (org-agenda-dim-blocked-tasks)))))
            ("i" "Your IMPORTANT Tasks"
             ((tags-todo "+important-notoday"
                         ((org-agenda-overriding-header
                           "These are your IMPORTANT Tasks")
                          (org-agenda-dim-blocked-tasks)))))
            ("u" "Your URGENT Tasks"
             ((agenda ""
                      ((org-agenda-overriding-header
                        "These are your URGENT Tasks")
                       (org-agenda-entry-types '(:deadline))
                       (org-deadline-warning-days 2)
                       (org-agenda-sorting-strategy
                        '(habit-down priority-down timestamp-down))))))
            ("n" "Your NEXT Tasks" tags-todo "+next")
            ("h" "Your Habits" tags-todo "STYLE=\"habit\"")
            ("r" "Refile" tags "+refile"))))

  ;;;;; Agenda habits
  (use-package org-habit
    :ensure nil
    :config
    (setq org-habit-graph-column 50)
    (setq org-habit-preceding-days 9)
    (setq org-habit-show-all-today t))
#+end_src

** Finally, we provide the =nebkor-org.el= module
:PROPERTIES:
:CUSTOM_ID: h:62eb7ca3-2f79-45a6-a018-38238b486e98
:ID:       01JGD3344J0007E5E5DSRFDYH0
:END:

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-org.el"
(provide 'nebkor-org)
#+end_src

* The =nebkor-langs.el= module
:PROPERTIES:
:CUSTOM_ID: h:f44afb76-a1d7-4591-934d-b698cc79a792
:END:

** The =nebkor-langs.el= settings for TAB
:PROPERTIES:
:CUSTOM_ID: h:559713c8-0e1e-44aa-bca8-0caae01cc8bb
:ID:       01JGD3344R000523XVMWTAS946
:END:

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-langs.el" :mkdirp yes
  ;;;; Tabs, indentation, and the TAB key
  (use-package emacs
    :ensure nil
    :demand t
    :config
    (setq tab-always-indent 'complete)
    (setq tab-first-completion nil)
    (setq-default tab-width 4
                  indent-tabs-mode nil)
    (electric-pair-mode 1))
#+end_src

** The =nebkor-langs.el= settings highlighting parens (~show-paren-mode~)
:PROPERTIES:
:CUSTOM_ID: h:7cd21ea6-c5d8-4258-999d-ad94cac2d8bf
:ID:       01JGD3344X0000EJPQTZ9YSMFW
:END:

#+begin_quote
The built-in ~show-paren-mode~ highlights the parenthesis on the
opposite end of the current symbolic expression. It also highlights
matching terms of control flow in programming languages that are not
using parentheses like Lisp: for instance, in a ~bash~ shell script it
highlights the ~if~ and ~fi~ keywords. This mode also works for prose
and I use it globally. Simple and effective!
#+end_quote

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-langs.el"
  ;;;; Parentheses (show-paren-mode)
  (use-package paren
    :ensure nil
    :hook (prog-mode . show-paren-local-mode)
    :config
    (setq show-paren-style 'mixed)
    (setq show-paren-when-point-in-periphery nil)
    (setq show-paren-when-point-inside-paren nil)
    (setq show-paren-context-when-offscreen 'overlay)) ; Emacs 29
#+end_src

** The =nebkor-langs.el= settings for showing relevant documentation (~eldoc~)
:PROPERTIES:
:CUSTOM_ID: h:a5773a39-a78f-43fa-8feb-669492c1d5a9
:ID:       01JGD334530008VQ1KT64C77QG
:END:

#+begin_quote
The built-in ~eldoc~ feature is especially useful in programming
modes. While we are in a function call, it produces an indicator in
the echo area (where the minibuffer appears upon invocation) that
shows the name of the function, the arguments it takes, if any, and
highlights the current argument we are positioned at. This way, we do
not have to go back to review the signature of the function just to
remember its arity. Same principle for variables, where ~eldoc-mode~
puts the first line of their documentation string in the echo area.
#+end_quote

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-langs.el"
;;;; Eldoc (Emacs live documentation feedback)
(use-package eldoc
  :ensure nil
  :hook (prog-mode . eldoc-mode)
  :config
  (setq eldoc-message-function #'message)) ; don't use mode line for M-x eval-expression, etc.
#+end_src

** The =nebkor-langs.el= settings for connecting to LSP servers (~eglot~)
:PROPERTIES:
:CUSTOM_ID: h:92258aa8-0d8c-4c12-91b4-5f44420435ce
:ID:       01JGD33459000C9EVJ59KD31VP
:END:

#+begin_quote
The built-in ~eglot~ feature, developed and maintained by João Távora,
is Emacs' own client for the Language Server Protocol (LSP). The LSP
technology is all about enhancing the ability of a text editor to work
with a given programming language. This works by installing a
so-called "language server" on your computer, which the "LSP client"
(i.e. ~eglot~) will plug into. A typical language server provides the
following capabilities:

- Code completion :: This can be visualised for in-buffer
  automatic expansion of function calls, variables, and the like
  ([[#h:804b858f-7913-47ef-aaf4-8eef5b59ecb4][The =nebkor-completion.el= for in-buffer completion popup (~corfu~)]]).

- Code linting :: To display suggestions, warnings, or errors. These
  are highlighted in the buffer, usually with an underline, and can
  also be displayed in a standalone buffer with the commands
  ~flymake-show-buffer-diagnostics~, ~flymake-show-project-diagnostics~
  ([[#h:df6d1b52-0306-4ace-9099-17dded11fbed][The =nebkor-langs.el= settings for code linting (~flymake~)]]).

- Code navigation and cross-referencing :: While over a symbol, use a
  command to jump directly to its definition. The default key bindings
  for going forth and then back are =M-.= (~xref-find-definitions~)
  and =M-,= (~xref-go-back~).

...

Assuming the language server is installed, to start using the LSP
client in a given file, do =M-x eglot=. To make this happen
automatically for every newly visited file, add a hook like this:

(add-hook 'SOME-MAJOR-mode #'eglot-ensure)
#+end_quote

I use ~eglot~ as the main LSP entry point, and as such, I have key-bindings for the common functionality implemented by ~eglot~.

Note that demanding ~eglot~ is not a mistake. I want it loaded so that I can advice functions for different programming langauges. For example, Python uses ~emacs-pet~ for tracking the environment of each project, and this needs to load after ~eglot~ so that it can advice the necessary functions.

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-langs.el"
  ;;;; Eglot (built-in client for the language server protocol)
  (use-package eglot
    :ensure nil
    :demand t ;; Not a mistake, we need to load Eglot elisp code before
              ;; we open any Python file.
    :functions (eglot-ensure)
    :commands (eglot)
    :bind
    ( :map eglot-mode-map
      ("C-c e r" . eglot-rename)
      ("C-c e o" . eglot-code-action-organize-imports)
      ("C-c e d" . eldoc)
      ("C-c e c" . eglot-code-actions)
      ("C-c e f" . eglot-format)
      ;; Since eglot plugs into flymake anyway
      ("C-c e l" . flymake-show-buffer-diagnostics))
    :config
    (setq eglot-sync-connect nil)
    (setq eglot-autoshutdown t)
    (setq eglot-extend-to-xref t))
#+end_src

** The =nebkor-langs.el= settings for writing Markdown (~markdown-mode~)
:PROPERTIES:
:CUSTOM_ID: h:c9063898-07ae-4635-8853-bb5f4bbab421
:ID:       01JGD3345F0005PP8A5XVC3JNA
:END:

#+begin_quote
The ~markdown-mode~ lets us edit Markdown files. We get syntax
highlighting and several extras, such as the folding of headings and
navigation between them. The mode actually provides lots of added
functionality for GitHub-flavoured Markdown and to preview a Markdown
file's HTML representation on a web page. Though I only use it for
basic text editing.
#+end_quote

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-langs.el"
;;; Markdown (markdown-mode)
(use-package markdown-mode
  :ensure t
  :defer t
  :config
  (setq markdown-fontify-code-blocks-natively t))
#+end_src

** The =nebkor-langs.el= settings for dealing with CSV files (~csv-mode~)
:PROPERTIES:
:CUSTOM_ID: h:bae58479-86c1-410f-867e-c548def65b1c
:ID:       01JGD3345N0008XB6ATFE04Y30
:END:

#+begin_quote
The package ~csv-mode~ provides support for =.csv= files. I do need
this on occasion, even though my use-case is pretty basic. For me, the
killer feature is the ability to create a virtual tabulated listing
with the command ~csv-align-mode~: it hides the field delimiter (comma
or space) and shows a tab stop in its stead.
#+end_quote

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-langs.el"
;;; csv-mode
(use-package csv-mode
  :ensure t
  :commands (csv-align-mode))
#+end_src

** The =nebkor-langs.el= settings for spell checking (~flyspell~)
:PROPERTIES:
:CUSTOM_ID: h:115806c4-88b0-43c1-8db2-d9d8d20a5c17
:ID:       01JGD3345V000A2VNTSK2YQBGQ
:END:

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-langs.el"
  ;;; Flyspell
  (use-package flyspell
    :ensure nil
    :bind
    ( :map flyspell-mode-map
      ("C-;" . nil)
      :map flyspell-mouse-map
      ("<mouse-3>" . flyspell-correct-word))
    :config
    (setq flyspell-issue-message-flag nil)
    (setq flyspell-issue-welcome-flag nil)
    (setq ispell-program-name "aspell")
    (setq ispell-dictionary "en_GB"))
#+end_src

** The =nebkor-langs.el= settings for code linting (~flymake~)
:PROPERTIES:
:CUSTOM_ID: h:df6d1b52-0306-4ace-9099-17dded11fbed
:ID:       01JGD33461000BHZVSJV90Z8QZ
:END:

#+begin_quote
The built-in ~flymake~ feature defines an interface for viewing the
output of linter programs. A "linter" parses a file and reports
possible notes/warnings/errors in it. With ~flymake~ we get these
diagnostics in the form of a standalone buffer as well as inline
highlights (typically underlines combined with fringe indicators) for
the portion of text in question. The linter report is displayed with
the command ~flymake-show-buffer-diagnostics~, or ~flymake-show-project-diagnostics~.
Highlights are shown in the context of the file.

The built-in ~eglot~ feature uses ~flymake~ internally to handle the
LSP linter output ([[#h:92258aa8-0d8c-4c12-91b4-5f44420435ce][The =nebkor-langs.el= settings for ~eglot~]]).

As for what I have in this configuration block, the essentials for me
are the user options ~flymake-start-on-save-buffer~ and ~flymake-start-on-flymake-mode~
as they make the linter update its report when the buffer is saved and
when ~flymake-mode~ is started, respectively. Otherwise, we have to
run it manually, which is cumbersome.

The ~package-lint-flymake~ package by Steve Purcell adds the glue code
to make ~flymake~ report issues with Emacs Lisp files for the purposes
of packaging. I use it whenever I work on my numerous Emacs packages.
#+end_quote

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-langs.el"
  ;;; Flymake
  (use-package flymake
    :ensure nil
    :bind
    ( :map flymake-mode-map
      ("C-c ! s" . flymake-start)
      ("C-c ! l" . flymake-show-buffer-diagnostics)  ; Emacs28
      ("C-c ! L" . flymake-show-project-diagnostics) ; Emacs28
      ("C-c ! n" . flymake-goto-next-error)
      ("C-c ! p" . flymake-goto-prev-error))
    :hook
    (prog-mode . turn-on-flymake)
    :config
    (defun turn-on-flymake () (flymake-mode t))
    (setq flymake-fringe-indicator-position 'left-fringe)
    (setq flymake-suppress-zero-counters t)
    (setq flymake-no-changes-timeout nil)
    (setq flymake-start-on-flymake-mode t)
    (setq flymake-start-on-save-buffer t)
    (setq flymake-proc-compilation-prevents-syntax-check t)
    (setq flymake-wrap-around nil)
    (setq flymake-mode-line-format
          '("" flymake-mode-line-exception flymake-mode-line-counters))
    (setq flymake-mode-line-counter-format
          '("" flymake-mode-line-error-counter
            flymake-mode-line-warning-counter
            flymake-mode-line-note-counter ""))
    (setq flymake-show-diagnostics-at-end-of-line nil) ; Emacs 30
    (remove-hook 'flymake-diagnostic-functions #'flymake-proc-legacy-flymake))

  ;;; Elisp packaging requirements
  (use-package package-lint-flymake
    :ensure t
    :after flymake
    :config
    ;; We can't use `use-package' :hook because the hookname does not
    ;; end in -hook.
    (add-hook 'flymake-diagnostic-functions #'package-lint-flymake))
#+end_src

** The =nebkor-langs.el= settings for quick outlines (~outline-minor-mode~)
:PROPERTIES:
:CUSTOM_ID: h:ffff5f7b-a62b-4d4a-ae29-af75402e5c35
:ID:       01JGD33467000CA30X0721EZM9
:END:

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-langs.el"
;;; General configurations for prose/writing

;;;; `outline' (`outline-mode' and `outline-minor-mode')
(use-package outline
  :ensure nil
  :bind
  ("<f10>" . outline-minor-mode)
  :config
  (setq outline-minor-mode-highlight nil) ; emacs28
  (setq outline-minor-mode-cycle t) ; emacs28
  (setq outline-minor-mode-use-buttons nil) ; emacs29---bless you for the nil option!
  (setq outline-minor-mode-use-margins nil)) ; as above
#+end_src

** The =nebkor-langs.el= settings for definitions (~dictionary~)
:PROPERTIES:
:CUSTOM_ID: h:f91563d8-f176-4555-b45b-ece56de03279
:ID:       01JGD3346D0001F22XTMW8XNR8
:END:

Use the entry point ~M-x dictionary-search~


#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-langs.el"
;;;; `dictionary'
(use-package dictionary
  :ensure nil
  :config
  (setq dictionary-server "dict.org"
        dictionary-default-popup-strategy "lev" ; read doc string
        dictionary-create-buttons nil
        dictionary-use-single-buffer t))
#+end_src

** The =nebkor-langs.el= settings for paren matching (~paredit~)
:PROPERTIES:
:CUSTOM_ID: h:885F9DED-E9C9-4B5B-9FE0-1A33CBD23126
:ID:       01JGD3346J0006N6CC7T9H3J5P
:END:

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-langs.el"
  (use-package paredit
    :ensure t
    :bind ( :map paredit-mode-map
            ("C-o" . paredit-open-round)
            ("M-D" . paredit-splice-sexp)
            ("C-A-d" . paredit-forward-down)
            ("C-A-u" . paredit-backward-up)
            ;; Unbind things that I don't need
            ("M-s" . nil) ; used for search related keybindings
            ("M-?" . nil) ; `xref-find-references' uses it.
            ("RET" . nil)); `ielm-return' uses it.
    :hook ((lisp-data-mode lisp-mode clojure-mode clojure-ts-mode cider-repl-mode inferior-emacs-lisp-mode) . paredit-mode))
#+end_src

** The =nebkor-langs.el= settings for code formatting (~apheleia~)
:PROPERTIES:
:CUSTOM_ID: h:07B0E6F4-050E-4A7D-B489-E919E4887FF5
:ID:       01JGD3346R000F126SJG0CE7DC
:END:

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-langs.el"
  (use-package apheleia
    :ensure t
    :demand t
    :after blackout
    :config
    (apheleia-global-mode +1)
    (with-eval-after-load 'apheleia-formatters
      (push '(zprint . ("zprint")) apheleia-formatters))
    :hook
    (apheleia-mode . (lambda () (blackout 'apheleia-mode))))
#+end_src

** The =nebkor-langs.el= settings for changing many things (~multiple-cursors~)
:PROPERTIES:
:CUSTOM_ID: h:07712014-B63C-429E-8D81-2D0E21E04ECC
:CREATED:  [2024-12-18 Wed 14:13]
:ID:       01JGD3346Y000B94BQRJ5ZNFSG
:END:

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-langs.el"
  (use-package multiple-cursors
    :ensure t
    :bind
    ("C->" . mc/mark-next-like-this)
    ("C-<" . mc/mark-previous-like-this)
    ("C-c C->" . mc/mark-all-like-this))
#+end_src

** The =nebkor-langs.el= section for LSP
:PROPERTIES:
:ID:       f6046c17-ff41-4474-8b50-94f652da3b13
:END:

Some people like eglot, and it's fine. But it doesn't do semantic highlighting, and I like my pretty
colors (you can use tree-sitter to do *syntactic* highlighting, but that's different).

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-langs.el"
  (use-package lsp-mode
    :ensure t
    :demand t
    :commands lsp
    :init
    (defun my/lsp-mode-setup-completion ()
      (setf (alist-get 'styles (alist-get 'lsp-capf completion-category-defaults))
            '(flex))) ;; Configure flex
    :bind (("C-c C-c Q" . lsp-workspace-shutdown)
           ("C-c C-c q" . lsp-workspace-restart))
    :config
    (setq lsp-log-io nil
          lsp-semgrep-server-command nil
          lsp-completion-provider :none
          lsp-inlay-hint-enable t
          lsp-modeline-diagnostics-enable nil
          lsp-semantic-tokens-enable t
          lsp-semantic-tokens-warn-on-missing-face nil
          lsp-treemacs-error-list-current-project-only t
          lsp-ui-sideline-delay 0.1
          lsp-ui-sideline-diagnostic-max-line-length 50
          lsp-ui-sideline-enable nil
          lsp-ui-sideline-show-code-actions t
          lsp-ui-sideline-update-mode 'line
          lsp-eldoc-render-all t
          lsp-idle-delay 0.2
          lsp-enable-snippet t
          read-process-output-max (* 1024 1024))
    :hook
    (
     (lsp-mode . lsp-enable-which-key-integration)
     (lsp-mode . subword-mode)
     (lsp-completion-mode . my/lsp-mode-setup-completion)
     ;;(before-save . lsp-format-buffer)
     ))
#+end_src


** The =nebkor-langs.el= section for Rust
:PROPERTIES:
:ID:       4cc12328-6cca-4c79-b9ca-fb07492ba890
:END:

Rust is my primary development language; ~rustic~ is derived from ~rust-mode~.

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-langs.el"
  (use-package cargo
    :ensure t)

  (use-package rustic
    :ensure t
    :bind (:map rustic-mode-map
                ("M-?" . lsp-find-references)
                ("C-c C-c a" . lsp-execute-code-action)
                ("C-c C-c r" . lsp-rename)
                ("C-c C-c l" . flycheck-list-errors)
                ("C-c C-c s" . lsp-rust-analyzer-status))
    :hook rust-ts-mode
    :config
    (setq rustic-format-on-save t
          rust-indent-method-chain t
          rustic-format-trigger 'on-save
          rustic-lsp-format t
          rustic-lsp-client 'lsp)
    (setq-local buffer-save-without-query t)
    (setq-local lsp-semantic-tokens-enable t))
#+end_src

Most of the fancy IDE-like features are coming from rust-analyzer, and as I mentioned above in the
[[id:f6046c17-ff41-4474-8b50-94f652da3b13][LSP section]], I prefer to use ~lsp-mode~ as my LSP client. It has a lot of Rust-specific config.

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-langs.el"
  (use-package lsp-mode
    :ensure nil
    :defer t
    :config
    (setq lsp-rust-all-targets nil
          lsp-rust-analyzer-cargo-all-targets nil
          lsp-rust-analyzer-import-merge-behaviour "full"
          lsp-rust-analyzer-completion-auto-import-enable t
          lsp-rust-rustfmt-path "/home/ardent/.cargo/bin/rustfmt"
          lsp-rust-unstable-features t
          lsp-rust-analyzer-cargo-watch-command "clippy"
          lsp-rust-analyzer-server-display-inlay-hints t
          lsp-rust-analyzer-call-info-full t
          lsp-rust-analyzer-proc-macro-enable t
          lsp-rust-analyzer-display-lifetime-elision-hints-enable "skip_trivial"
          lsp-rust-analyzer-display-chaining-hints t
          lsp-rust-analyzer-display-lifetime-elision-hints-use-parameter-names t
          lsp-rust-analyzer-display-closure-return-type-hints t
          lsp-rust-analyzer-display-parameter-hints nil
          lsp-rust-analyzer-display-reborrow-hints t))
#+end_src

** The =nebkor-langs.el= section for Python
:PROPERTIES:
:CUSTOM_ID: h:EA5EA223-F97D-4EE9-8663-99822A037618
:CREATED: [2024-11-21 Thu 22:51]
:ID:       01JGD33499000EF11QDM5YWNDX
:END:

The built-in Python mode for Emacs goes a long way. I use the
following stack when programming Python:

- =uv= for package and venv management
- =pylsp= as the language server
- =ruff= as the linting and formatting tool

Run the following commands in every virtualenv environment to setup
the necessary developer tooling:

- =uv add ruff python-lsp-server python-lsp-ruff --group dev=
  + Ruff is an extremely fast Python linter, and code formatter,
    written in Rust. Ruff is also a langauge server, but it only
    provides functionality related to formating and linting. As it
    adds more over time (like go-to definition), I may make it my
    primary language server
  + Python LSP Server (provides the binary ~pylsp~) is the defacto
    language server for Python.
  + =python-lsp-ruff= provides tight integration between ~pylsp~ and
    ~ruff~, enabling the language server to use ruff for it's linting
    and formatting capabilities.
- =uv add pytest --group dev=

Uv takes care of setting up the venv properly, so if you replace the
default commands with uv versions, you are good to go. In practice,
this means:

- Use ~C-u C-c C-p~ command (=run-python=, with an argument) to start
  the Inferior Python Shell, instead of ~C-c C-p~.
  - This will prompt you for a command, with the default value being
    =python3 -i=. Change it to =uv run python3 -i=.
- Modify the ~C-c C-v~ command (=python-check=) to:
  =uv run ruff check <filename>=

NOTE: Exactly the same instructions also work for Poetry, just replace
~uv~ with ~poetry~ in any of the commands above.

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-langs.el"
  ;;;; Configuration for Python Programming

  (use-package python
    :ensure nil
  ;;; Uncomment this if you want Eglot to start automatically. I prefer
  ;;; calling `M-x eglot' myself.
  ;;  :hook ((python-base-mode . eglot-ensure))
    :config
    (setq python-shell-dedicated 'project)
    ;; Apheleia is an Emacs package for formatting code as you save
    ;; it. Here we are asking Apheleia to use Ruff for formatting our
    ;; Python code.
    (with-eval-after-load 'apheleia
      (setf (alist-get 'python-mode apheleia-mode-alist)
            '(ruff-isort ruff))
      (setf (alist-get 'python-ts-mode apheleia-mode-alist)
            '(ruff-isort ruff))))

  (use-package pet
    :ensure (:host github :repo "vedang/emacs-pet" :branch "fix-eglot-integration"
                   ;; :remotes (("upstream" :repo "wyuenho/emacs-pet" :branch "main"))
                   )
    :ensure-system-package (dasel sqlite3)
    :config
    ;; The -10 here is a way to define the priority of the function in
    ;; the list of hook functions. We want `pet-mode' to run before any
    ;; other configured hook function.
    (add-hook 'python-base-mode-hook #'pet-mode -10))
#+end_src

*** Tooling I have tried and rejected or failed to setup correctly
:PROPERTIES:
:CUSTOM_ID: h:AA769AC9-18D9-4971-81C9-8DF7AD920013
:END:

- Auto-virtualenv: For virtualenv setup and detection
- Pyvenv's WORKON_HOME: WORKON_HOME is useful if you keep all your venvs in the same directory. This is not generally what most people do.
  - Config: ~(setenv "WORKON_HOME" "~/.cache/venvs/")~

#+begin_src emacs-lisp
  (use-package pyvenv
    :ensure t
    :after python
    :commands (pyvenv-create pyvenv-workon pyvenv-activate pyvenv-deactivate)
    :hook
    ((python-base-mode . pyvenv-mode)))

  (use-package auto-virtualenv
    :ensure t
    :after python
    :config
    (setq auto-virtualenv-verbose t)
    (auto-virtualenv-setup))
#+end_src

** The =nebkor-langs.el= section for Ziglang (~zig-mode~)
:PROPERTIES:
:CUSTOM_ID: h:4C1D1E7E-7FA1-4D76-A6CF-6D89A10376B6
:CREATED: [2024-11-27 Wed 22:51]
:ID:       01JGD3349H0004FZXRQNFXMHDW
:END:

I use the Emacs major mode ~zig-mode~, along with the Ziglang langauge
server ~zls~ (via ~eglot~) for all my Zig programming requirements

To install ~zig~ and ~zls~ on MacOS:

=brew install zig zls=

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-langs.el"
  ;;;; Configuration for Zig Programming

  (use-package zig-mode
    :ensure t
  ;;; Uncomment this if you want Eglot to start automatically. I don't
  ;;; recommend it, but that's just me.
    ;; :hook ((zig-mode . eglot-ensure))
    )
#+end_src

** The =nebkor-langs.el= section for Clojure programming
:PROPERTIES:
:CUSTOM_ID: h:705CEAA5-00C4-4691-9425-7529981A8B18
:CREATED:  [2024-12-02 Mon 08:34]
:ID:       01JGD3349Q000DJDRF143PVW7N
:END:

Clojure is my favorite programming language, and it has been my bread
and butter language for well over a decade. I can only hope and pray
for this to continue.

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-langs.el"
  ;;; Configuration for Clojure programming
  (use-package clojure-mode
    :ensure t)

  ;;; `clojure-ts-mode' is not stable enough right now. In particular,
  ;;; it clashes with `paredit-mode' sometimes, leading to Paredit
  ;;; throwing unbalanced-expression errorsand being unusable. So
  ;;; keeping this disabled and experimenting with how to fix it, for
  ;;; them moment.

  (defvar enable-clojure-ts-mode nil)

  (when (and (treesit-available-p) enable-clojure-ts-mode)
    (use-package clojure-ts-mode
      :ensure t))
#+end_src

*** The =nebkor-langs.el= section for Clojure interactive development (~cider~)
:PROPERTIES:
:CUSTOM_ID: h:92A8163E-68DA-49D2-A40F-69853FD7E68A
:CREATED:  [2024-12-18 Wed 13:55]
:ID:       01JGD3349X000FNGH5GRV46PXR
:END:

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-langs.el"
  ;;;; Cider provides tooling over nREPL for Clojure programming
  (use-package cider
    :ensure t
    :after (:any clojure-mode clojure-ts-mode)
    :config
    (defun cider-repl-prompt-on-newline (ns)
      "Return a prompt string with newline.
  NS is the namespace information passed into the function by cider."
      (concat ns ">\n"))
    (setq cider-repl-prompt-function #'cider-repl-prompt-on-newline))
#+end_src

*** The =nebkor-langs.el= section for refactoring clojure code (~clj-refactor~)
:PROPERTIES:
:CUSTOM_ID: h:DCA5B96F-E543-4CB9-8ECC-F73A6531CCEA
:CREATED:  [2024-12-18 Wed 13:59]
:ID:       01JGD334A40006ZNWEPX38AWNM
:END:

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-langs.el"
  ;;;; clj-refactor enables smart refactoring of Clojure code
  (use-package clj-refactor
    :ensure t
    :after (:any clojure-mode clojure-ts-mode)
    :hook
    ((clojure-mode . clj-refactor-mode)
     (clojure-ts-mode . clj-refactor-mode))
    :config
    (cljr-add-keybindings-with-prefix "C-c m")
    ;; Don't magically add stuff to the namespace requires form (because
    ;; for big projects this operation is slow) it's easier to do this
    ;; by hand (=add-missing= operation) after you've typed out what you
    ;; wanted to.
    (setq cljr-magic-requires nil))
#+end_src

*** The =nebkor-langs.el= section for linting Clojure code (~flymake-kondor~)
:PROPERTIES:
:CUSTOM_ID: h:22939449-DC0E-4551-800D-396D5076C406
:CREATED:  [2024-12-18 Wed 14:03]
:ID:       01JGD334AA0007EE6J3C0B4CYW
:END:

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-langs.el"
  ;;;; flymake-kondor integrates flymake with clj-kondo, so that we get
  ;;;; great linting without needing to start a REPL or LSP server.
  (use-package flymake-kondor
    :ensure t
    :after (:any clojure-mode clojure-ts-mode flymake)
    :ensure-system-package (clj-kondo)
    :hook
    ((clojure-mode . flymake-kondor-setup)
     (clojure-ts-mode . flymake-kondor-setup)))
#+end_src

*** The =nebkor-langs.el= Clojure section for Clojure tools (~clojure-snippets~ and ~jet~)
:PROPERTIES:
:CUSTOM_ID: h:413C1A9A-BFC7-43B8-A062-3A81259DD794
:CREATED:  [2024-12-18 Wed 14:05]
:ID:       01JGD334AG0000SH5FDYZG10SH
:END:

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-langs.el"
  ;;;; clojure-snippets are handy yasnippets for fast coding
  (use-package clojure-snippets
    :ensure t
    :after clojure-mode)

  ;;;; jet is an external tool to convert between json, transit and edn
  (use-package jet
    :ensure t
    :config
    (defun json->edn ()
      "Convert the selected region, or entire file, from JSON to EDN."
      (interactive)
      (let ((b (if mark-active (region-beginning) (point-min)))
            (e (if mark-active (region-end) (point-max)))
            (jet (when (executable-find "jet")
                   "jet --pretty --keywordize keyword --from json --to edn")))
        (if jet
            (let ((p (point)))
              (shell-command-on-region b e jet (current-buffer) t)
              (goto-char p))
          (user-error "Could not find jet installed")))))
#+end_src

** The =nebkor-langs.el= section for Emacs Lisp
:PROPERTIES:
:CUSTOM_ID: h:54D8C607-5CF6-425A-BFBD-0602334BE199
:CREATED:  [2024-12-06 Fri 22:26]
:ID:       01JGD334AP000EWEHBZCRSKM2B
:END:

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-langs.el"
  ;;; Settings for Interaction mode for Emacs-Lisp
  (use-package ielm
    :ensure nil
    :bind
    ( :map ielm-map
      ("C-j" . newline-and-indent)))
#+end_src
** Finally, we provide the =nebkor-langs.el= module
:PROPERTIES:
:CUSTOM_ID: h:924224F1-61A0-4CCD-A3C3-4F230D3CF0A0
:ID:       01JGD334AW0002AX2598H8SQHW
:END:

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-langs.el"
(provide 'nebkor-langs)
#+end_src

* The =nebkor-study.el= module
:PROPERTIES:
:CUSTOM_ID: h:FE49DE29-DF99-450C-B6E8-AD64D877C518
:CREATED:  [2024-12-12 Thu 21:27]
:END:

This file contains configuration for everything I use to make Emacs my
go-to place for studying anything.

** The =nebkor-study.el= section for notes and file-naming (~denote~)
:PROPERTIES:
:CUSTOM_ID: h:e86a66dc-7ef9-4f09-ad7e-946de2034e8d
:CREATED:  [2024-12-12 Thu 21:33]
:ID:       01JGD334B4000DNEXGHP7T9FSS
:END:

Denote is my goto tool for any and all note-taking. This is what
powers my brain forest, and where anything and everything I write
starts from.

By default, Denote looks for files in the =~/Documents/notes/=
directory. If you want to use a location other than the default
localtion for your ~denote~ files, set the environment variable
~DENOTE_DIRECTORY~.

#+begin_quote
This is another one of my packages and is extended by my
~consult-denote~ package ([[#h:ee82e629-fb05-4c75-9175-48a760a25691][The =nebkor-study.el= integration between Consult and Denote (~consult-denote~)]]).

Denote is a simple note-taking tool for Emacs. It is based on the idea
that notes should follow a predictable and descriptive file-naming
scheme. The file name must offer a clear indication of what the note is
about, without reference to any other metadata. Denote basically
streamlines the creation of such files while providing facilities to
link between them.

Denote's file-naming scheme is not limited to "notes". It can be used
for all types of file, including those that are not editable in Emacs,
such as videos. Naming files in a consistent way makes their
filtering and retrieval considerably easier. Denote provides relevant
facilities to rename files, regardless of file type.
#+end_quote

Prot is the developer and maintainer of this package.

+ Package name (GNU ELPA): ~denote~
+ Official manual: <https://protesilaos.com/emacs/denote>
+ Change log: <https://protesilaos.com/emacs/denote-changelog>
+ Git repositories:
  - GitHub: <https://github.com/protesilaos/denote>
  - GitLab: <https://gitlab.com/protesilaos/denote>
+ Video demo: <https://protesilaos.com/codelog/2022-06-18-denote-demo/>
+ Backronyms: Denote Everything Neatly; Omit The Excesses.  Don't Ever
  Note Only The Epiphenomenal.

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-study.el" :mkdirp yes
  ;;; Denote (simple note-taking and file-naming)

  ;; Read the manual: <https://protesilaos.com/emacs/denote>.  This does
  ;; not include all the useful features of Denote.  I have a separate
  ;; private setup for those, as I need to test everything is in order.
  (use-package denote
    :ensure t
    :hook
    ;; If you use Markdown or plain text files you want to fontify links
    ;; upon visiting the file (Org renders links as buttons right away).
    ((text-mode . denote-fontify-links-mode-maybe)

     ;; Highlight Denote file names in Dired buffers.  Below is the
     ;; generic approach, which is great if you rename files Denote-style
     ;; in lots of places as I do.
     ;;
     ;; If you only want the `denote-dired-mode' in select directories,
     ;; then modify the variable `denote-dired-directories' and use the
     ;; following instead:
     ;;
     ;;  (dired-mode . denote-dired-mode-in-directories)
     (dired-mode . denote-dired-mode))
    :bind
    ;; Denote DOES NOT define any key bindings.  This is for the user to
    ;; decide.  Here I only have a subset of what Denote offers.
    ( :map global-map
      ("C-c d n" . denote-create-note)
      ("C-c d N" . denote-silo-extras-select-silo-then-command)
      ("C-c d o" . denote-open-or-create)
      ("C-c d O" . denote-silo-extras-open-or-create)
      ("C-c d l" . denote-link-or-create)
      ("C-c d L" . denote-link-after-creating-with-command)
      ;; Note that `denote-rename-file' can work from any context, not
      ;; just Dired buffers.  That is why we bind it here to the
      ;; `global-map'.
      ;;
      ;; Also see `denote-rename-file-using-front-matter' further below.
      ("C-c d r" . denote-rename-file)
      ;; If you intend to use Denote with a variety of file types, it is
      ;; easier to bind the link-related commands to the `global-map', as
      ;; shown here.  Otherwise follow the same pattern for
      ;; `org-mode-map', `markdown-mode-map', and/or `text-mode-map'.
      ("C-c d j" . denote-journal-extras-new-entry)
      ("C-c d s" . denote-sort-dired)
      ;; Bindings to personal functions (defined below)
      ("C-c d p m" . vedang/denote-publishing-extras-new-microblog-entry)
      ("C-c d p b" . vedang/denote-publishing-extras-new-blog-entry)
      :map text-mode-map
      ("C-c d B" . denote-backlinks)
      ("C-c d b" . denote-find-backlink)
      ;; Also see `denote-rename-file' further above.
      ("C-c d R" . denote-rename-file-using-front-matter)
      ("C-c d k" . denote-rename-file-keywords)
      :map org-mode-map
      ("C-c d h" . denote-org-extras-link-to-heading)
      ("C-c d d l" . denote-org-extras-dblock-insert-links)
      ("C-c d d b" . denote-org-extras-dblock-insert-backlinks)
      ("C-c d d m" . denote-org-extras-dblock-insert-missing-links)
      ;; Key bindings specifically for Dired.
      :map dired-mode-map
      ("C-c C-d C-i" . denote-dired-link-marked-notes)
      ("C-c C-d C-r" . denote-dired-rename-marked-files)
      ("C-c C-d C-k" . denote-dired-rename-marked-files-with-keywords)
      ("C-c C-d C-A" . denote-dired-rename-marked-files-add-keywords)
      ("C-c C-d C-K" . denote-dired-rename-marked-files-remove-keywords)
      ("C-c C-d C-f" . denote-dired-rename-marked-files-using-front-matter))
    :config
    (require 'denote-silo-extras)
    (require 'denote-journal-extras)
    (require 'denote-org-extras)

    ;; Remember to check the doc strings of those variables.
    (let ((dir (string-trim (shell-command-to-string "echo $DENOTE_DIRECTORY"))))
      (when (not (string-empty-p dir))
        (setq denote-directory (expand-file-name dir))
        (setq denote-journal-extras-directory (expand-file-name "journal" denote-directory))))
    (setq denote-infer-keywords t)
    (setq denote-sort-keywords t)
    (setq denote-excluded-directories-regexp "data") ; external data related to headings is stored in these directories (web archives)
    (setq denote-date-format nil)         ; read its doc string
    (setq denote-date-prompt-use-org-read-date t)
    (setq denote-prompts '(title keywords subdirectory signature))

    (setq denote-rename-confirmations nil) ; CAREFUL with this if you are not familiar with Denote!
    (setq denote-save-buffers t)
    (setq denote-rename-buffer-format "[D] %s %t%b")
    ;; Automatically rename Denote buffers when opening them so that
    ;; instead of their long file name they have a literal "[D]"
    ;; followed by the file's title.  Read the doc string of
    ;; `denote-rename-buffer-format' for how to modify this.
    (denote-rename-buffer-mode 1)

    (setq denote-buffer-has-backlinks-string " (<--->)")
    (setq denote-backlinks-show-context t)
    (setq denote-org-store-link-to-heading t)

    ;; Journal settings
    (setq denote-journal-extras-keyword "")

    ;; I use Yasnippet to expand these into a better template.
    (add-to-list 'denote-templates '(reference-note . "reference"))
    (add-to-list 'denote-templates '(morning . "morningpage"))
    (add-to-list 'denote-templates '(emotion . "emotion"))
    (add-to-list 'denote-templates '(insight . "insight"))
    (add-to-list 'denote-templates '(weekly_intentions . "weekint"))
    (add-to-list 'denote-templates '(weekly_report . "weekrpt"))
    (add-to-list 'denote-templates '(sketch . "sketch"))

    ;; Front-matter for Org files
    (setq denote-org-front-matter
          ":PROPERTIES:
  :ID: %4$s
  :CREATED: %2$s
  :END:
  ,#+title:      %1$s
  ,#+filetags:   %3$s
  ,#+date:       %2$s
  ,#+identifier: %4$s
  \n")

    (defun vedang/denote-publishing-extras-new-blog-entry (&optional date)
      "Create a new blog entry.

    With optional DATE as a prefix argument, prompt for a date.  If
    `denote-date-prompt-use-org-read-date' is non-nil, use the Org
    date selection module.

    When called from Lisp DATE is a string and has the same format as
    that covered in the documentation of the `denote' function.  It
    is internally processed by `denote-parse-date'."
      (interactive (list (when current-prefix-arg (denote-date-prompt))))
      (let ((internal-date (denote-parse-date date))
            (denote-directory (file-name-as-directory (expand-file-name "published" denote-directory))))
        (denote
         (denote-title-prompt)
         '("draft")
         nil nil date
         ;; See YASnippet
         "fullblog")))

    (defun vedang/denote-publishing-extras-new-microblog-entry (&optional date)
      "Create a new microblog entry.
    Set the title of the new entry according to the value of the user option
    `denote-journal-extras-title-format'.

    With optional DATE as a prefix argument, prompt for a date.  If
    `denote-date-prompt-use-org-read-date' is non-nil, use the Org
    date selection module.

    When called from Lisp DATE is a string and has the same format as
    that covered in the documentation of the `denote' function.  It
    is internally processed by `denote-parse-date'."
      (interactive (list (when current-prefix-arg (denote-date-prompt))))
      (let ((internal-date (denote-parse-date date))
            (denote-directory (file-name-as-directory (expand-file-name "published" denote-directory))))
        (denote
         (denote-journal-extras-daily--title-format internal-date)
         '("draft" "microblog")
         nil nil date
         ;; See YASnippet
         "microblog")))

    (defun vedang/denote-link-ol-get-id ()
      "Get the CUSTOM_ID of the current entry.

  If the entry already has a CUSTOM_ID, return it as-is, else create a new
  one.

  If we are creating a new ID, add a CREATED property with the current
  timestamp as well.

  This function is based on `denote-link-ol-get-id', with minor
  modifications."
      (interactive)
      (let* ((pos (point))
             (id (org-entry-get pos "CUSTOM_ID"))
             (created (org-entry-get pos "CREATED")))
        (if (and (stringp id) (string-match-p "\\S-" id))
            id
          (setq id (org-id-new "h"))
          (org-entry-put pos "CUSTOM_ID" id))
        (when (not created)
          (setq created (format-time-string (org-time-stamp-format t t) (current-time)))
          (org-entry-put pos "CREATED" created))
        id))

    (defun vedang/denote--split-luhman-sig (signature)
      "Split numbers and letters in Luhmann-style SIGNATURE string."
      (replace-regexp-in-string
       "\\([a-zA-Z]+?\\)\\([0-9]\\)" "\\1=\\2"
       (replace-regexp-in-string
        "\\([0-9]+?\\)\\([a-zA-Z]\\)" "\\1=\\2"
        signature)))

    (defun vedang/denote--pad-sig (signature)
      "Create a new signature with padded spaces for all components"
      (combine-and-quote-strings
       (mapcar
        (lambda (x)
          (string-pad x 5 32 t))
        (split-string (vedang/denote--split-luhman-sig signature) "=" t))
       "="))

    (defun vedang/denote-sort-for-signatures (sig1 sig2)
      "Return non-nil if SIG1 is smaller that SIG2.

    Perform the comparison with `string<'."
      (string< (vedang/denote--pad-sig sig1) (vedang/denote--pad-sig sig2)))

    (setq denote-sort-signature-comparison-function #'vedang/denote-sort-for-signatures))

#+end_src

*** The =nebkor-study.el= integration between Consult and Denote (~consult-denote~)
:PROPERTIES:
:CUSTOM_ID: h:ee82e629-fb05-4c75-9175-48a760a25691
:ID:       01JGD334BB000DF3EBFKMP10F4
:END:

#+begin_quote
This is another package of mine which extends my ~denote~ package
([[#h:e86a66dc-7ef9-4f09-ad7e-946de2034e8d][The =nebkor-study.el= settings for ~denote~ (notes and file-naming)]]).

This is glue code to integrate ~denote~ with Daniel Mendler's
~consult~ ([[#h:22e97b4c-d88d-4deb-9ab3-f80631f9ff1d][The =nebkor-completion.el= settings for ~consult~]]). The
idea is to enhance minibuffer interactions, such as by providing a
preview of the file-to-linked/opened and by adding more sources to the
~consult-buffer~ command.
#+end_quote

Prot is the developer of this package.

+ Package name (GNU ELPA): ~consult-denote~
+ Official manual: not available yet.
+ Git repositories:
  + GitHub: <https://github.com/protesilaos/consult-denote>

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-study.el"
  (use-package consult-denote
    :ensure t
    :bind
    (("C-c d f" . consult-denote-find)
     ("C-c d g" . consult-denote-grep))
    :config
    (consult-denote-mode 1))
#+end_src


** The =nebkor-study.el= section for reading and annotation of PDFs (~pdf-tools~)
:PROPERTIES:
:CUSTOM_ID: h:B662EABD-DC46-468A-BF59-E67AC48D2DDA
:CREATED:  [2024-12-12 Thu 21:35]
:ID:       01JGD334BH000APPCRC0660Z5C
:END:

PDF Tools is an absolute powerhouse for reading and annotating PDF
files. It is my goto tool for reading any academic papers.

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-study.el"
  ;;; PDF Tools for reading and annotating PDF files
  (use-package pdf-tools
    :ensure (:host github :repo "vedang/pdf-tools" :branch "master")
    :config
    (pdf-tools-install))
#+end_src

** The =nebkor-study.el= section for annotation of org and eww files (~org-remark~)
:PROPERTIES:
:CUSTOM_ID: h:814EC9C8-3182-4B86-ADD9-123096D144D4
:CREATED:  [2024-12-12 Thu 21:40]
:ID:       01JGD334BQ000DQSHQJKJX08V8
:END:

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-study.el"
  ;;; org-remark for annotating org and eww files
  (use-package org-remark
    :ensure t
    :init
    (setq org-remark-create-default-pen-set nil)
    :bind
    ( :map global-map
      ("C-c r m" . org-remark-mark)
      :map org-remark-mode-map
      ("C-c r e" . org-remark-mark-review)
      ("C-c r i" . org-remark-mark-important)
      ("C-c r o" . org-remark-open)
      ("C-c r n" . org-remark-view-next)
      ("C-c r p" . org-remark-view-prev)
      ("C-c r r" . org-remark-remove)
      ("C-c r d" . org-remark-delete)
      ("C-c r s" . org-remark-save)
      ("C-c r t" . org-remark-toggle)
      ("C-c r v" . org-remark-view))
    :config
    (defun vm/org-remark-notes ()
      (expand-file-name "brain/marginalia.org" org-directory))
    (setq org-remark-notes-file-name #'vm/org-remark-notes)
    ;; Create a pen set for specific kinds of highlights. NOTE: This
    ;; pen-set has been made for dark themes.

    ;; Creates `org-remark-mark-review'
    (org-remark-create "review"
                       ;; face: `dired-flagged'
                       '(:underline (:color "dark red" :style wave) :foreground "#f7143a")
                       '(CATEGORY "review" help-echo "Review this"))

    ;; Creates `org-remark-mark-important'
    (org-remark-create "important"
                       ;; face: `dired-broken-symlink'
                       '(:underline "gold" :background "red1" :foreground "yellow1" :weight bold)
                       '(CATEGORY "important"))

    (set-face-bold 'org-remark-highlighter t)

    (with-eval-after-load 'eww
      (org-remark-eww-mode +1))
    (with-eval-after-load 'info
      (org-remark-info-mode +1))
    (with-eval-after-load 'nov
      (org-remark-nov-mode +1)))
#+end_src

** The =nebkor-study.el= section for flashcards (~org-fc~)
:PROPERTIES:
:CUSTOM_ID: h:C63AE939-4082-4763-B0A4-A736869B7B41
:CREATED:  [2024-12-12 Thu 21:45]
:ID:       01JGD334BX0002JVAP3SEQF8YW
:END:

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-study.el"
  ;;; org-fc for flashcards and spaced repetition
  (use-package org-fc
    :ensure (:host github :repo "l3kn/org-fc" :branch "main")
    :ensure-system-package (gawk)
    :config
    (setq org-fc-directories `(,(concat org-directory "/notes/"))))
#+end_src

** The =nebkor-study.el= section for table of contents (~toc-org~)
:PROPERTIES:
:CUSTOM_ID: h:478591ED-2CEF-4E15-A5DF-77198A34C86A
:CREATED:  [2024-12-13 Fri 08:42]
:ID:       01JGD334C300070MC86G0PKCM4
:END:

~toc-org~ is a simple and nifty package for automatically creating a
table of contents in any ~org-mode~ file. It works by
creating/updating a table of contents under the first heading with the
tag =:TOC:=.

By default, it creates a version of links which export according to
the format expected by Github (so that the links work correctly for
someone reading on Github). But these links don't work in standard
~org-mode~ (without ~toc-org~ enabled).

Enabling ~toc-org-mode~ in the org file means that anytime I save the
buffer, the TOC will automatically be updated. Also, the links created
in the TOC will be navigable using standard ~org-mode~ shortcuts.

To enable ~toc-org~, run =M-x toc-org-mode= in your ~org-mode~ file
and create a heading with the tag =:TOC:= added to it.

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-study.el"
  ;;; toc-org for automatic Table of Contents
  (use-package toc-org
    :ensure t)
#+end_src

** The =nebkor-study.el= section for archiving web content (~org-board~)
:PROPERTIES:
:ID:       01JGD334C90006ECQX4W0Z7B3G
:END:

~org-board~ is a fantastic tool that lets me download content directly
from various webpages, and store it locally. I use this to download
every blogpost I enjoy, and this lets me reliably highlight the page
([[#h:814EC9C8-3182-4B86-ADD9-123096D144D4][The =nebkor-study.el= section for ~org-remark~ (annotation of org
and eww files)]]) and extract flashcards from it ([[#h:C63AE939-4082-4763-B0A4-A736869B7B41][The =nebkor-study.el=
section for ~org-fc~ (flashcards)]]).

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-study.el"
  ;;; Downloading and archiving webpages
  (use-package org-board
    :ensure t
    :bind-keymap
    ("C-c o" . org-board-keymap))
#+end_src

** Finally, we provide the =nebkor-study.el= module
:PROPERTIES:
:CUSTOM_ID: h:13AF346B-1721-47FC-87A9-16EEB7818521
:CREATED:  [2024-12-12 Thu 22:09]
:ID:       01JGD334CG000FA0TH5FCCW0CK
:END:

#+begin_src emacs-lisp :tangle "nebkor-modules/nebkor-study.el"
  (provide 'nebkor-study)
#+end_src

* Custom libraries
** The =prot-common.el= library
:PROPERTIES:
:CUSTOM_ID: h:3fccfadf-22e9-457f-b9fd-ed1b48600d23
:ID:       01JGD334CS000DEHYRMVG75GJH
:END:

#+begin_src emacs-lisp :tangle "custom-lisp/prot-common.el" :mkdirp yes
  ;;; prot-common.el --- Common functions for my dotemacs -*- lexical-binding: t -*-

  ;; Copyright (C) 2020-2024  Protesilaos Stavrou

  ;; Author: Protesilaos Stavrou <info@protesilaos.com>
  ;; URL: https://protesilaos.com/emacs/dotemacs
  ;; Version: 0.1.0
  ;; Package-Requires: ((emacs "30.1"))

  ;; 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:
  ;;
  ;; Common functions for my Emacs: <https://protesilaos.com/emacs/dotemacs/>.
  ;;
  ;; Remember that every piece of Elisp that I write is for my own
  ;; educational and recreational purposes.  I am not a programmer and I
  ;; do not recommend that you copy any of this if you are not certain of
  ;; what it does.

  ;;; Code:

  (eval-when-compile
    (require 'subr-x)
    (require 'cl-lib))

  (defgroup prot-common ()
    "Auxiliary functions for my dotemacs."
    :group 'editing)

  ;;;###autoload
  (defun prot-common-number-even-p (n)
    "Test if N is an even number."
    (if (numberp n)
        (= (% n 2) 0)
      (error "%s is not a number" n)))

  ;;;###autoload
  (defun prot-common-number-integer-p (n)
    "Test if N is an integer."
    (if (integerp n)
        n
      (error "%s is not an integer" n)))

  ;;;###autoload
  (defun prot-common-number-integer-positive-p (n)
    "Test if N is a positive integer."
    (if (prot-common-number-integer-p n)
        (> n 0)
      (error "%s is not a positive integer" n)))

  ;; Thanks to Gabriel for providing a cleaner version of
  ;; `prot-common-number-negative': <https://github.com/gabriel376>.
  ;;;###autoload
  (defun prot-common-number-negative (n)
    "Make N negative."
    (if (and (numberp n) (> n 0))
        (* -1 n)
      (error "%s is not a valid positive number" n)))

  ;;;###autoload
  (defun prot-common-reverse-percentage (number percent change-p)
    "Determine the original value of NUMBER given PERCENT.

  CHANGE-P should specify the increase or decrease.  For simplicity,
  nil means decrease while non-nil stands for an increase.

  NUMBER must satisfy `numberp', while PERCENT must be `natnump'."
    (unless (numberp number)
      (user-error "NUMBER must satisfy numberp"))
    (unless (natnump percent)
      (user-error "PERCENT must satisfy natnump"))
    (let* ((pc (/ (float percent) 100))
           (pc-change (if change-p (+ 1 pc) pc))
           (n (if change-p pc-change (float (- 1 pc-change)))))
      ;; FIXME 2021-12-21: If float, round to 4 decimal points.
      (/ number n)))

  ;;;###autoload
  (defun prot-common-percentage-change (n-original n-final)
    "Find percentage change between N-ORIGINAL and N-FINAL numbers.

  When the percentage is not an integer, it is rounded to 4
  floating points: 16.666666666666664 => 16.667."
    (unless (numberp n-original)
      (user-error "N-ORIGINAL must satisfy numberp"))
    (unless (numberp n-final)
      (user-error "N-FINAL must satisfy numberp"))
    (let* ((difference (float (abs (- n-original n-final))))
           (n (* (/ difference n-original) 100))
           (round (floor n)))
      ;; FIXME 2021-12-21: Any way to avoid the `string-to-number'?
      (if (> n round) (string-to-number (format "%0.4f" n)) round)))

  ;; REVIEW 2023-04-07 07:43 +0300: I just wrote the conversions from
  ;; seconds.  Hopefully they are correct, but I need to double check.
  (defun prot-common-seconds-to-minutes (seconds)
    "Convert a number representing SECONDS to MM:SS notation."
    (let ((minutes (/ seconds 60))
          (seconds (% seconds 60)))
      (format "%.2d:%.2d" minutes seconds)))

  (defun prot-common-seconds-to-hours (seconds)
    "Convert a number representing SECONDS to HH:MM:SS notation."
    (let* ((hours (/ seconds 3600))
           (minutes (/ (% seconds 3600) 60))
           (seconds (% seconds 60)))
      (format "%.2d:%.2d:%.2d" hours minutes seconds)))

  ;;;###autoload
  (defun prot-common-seconds-to-minutes-or-hours (seconds)
    "Convert SECONDS to either minutes or hours, depending on the value."
    (if (> seconds 3599)
        (prot-common-seconds-to-hours seconds)
      (prot-common-seconds-to-minutes seconds)))

  ;;;###autoload
  (defun prot-common-rotate-list-of-symbol (symbol)
    "Rotate list value of SYMBOL by moving its car to the end.
  Return the first element before performing the rotation.

  This means that if `sample-list' has an initial value of `(one
  two three)', this function will first return `one' and update the
  value of `sample-list' to `(two three one)'.  Subsequent calls
  will continue rotating accordingly."
    (unless (symbolp symbol)
      (user-error "%s is not a symbol" symbol))
    (when-let* ((value (symbol-value symbol))
                (list (and (listp value) value))
                (first (car list)))
      (set symbol (append (cdr list) (list first)))
      first))

  ;;;###autoload
  (defun prot-common-empty-buffer-p ()
    "Test whether the buffer is empty."
    (or (= (point-min) (point-max))
        (save-excursion
          (goto-char (point-min))
          (while (and (looking-at "^\\([a-zA-Z]+: ?\\)?$")
                      (zerop (forward-line 1))))
          (eobp))))

  ;;;###autoload
  (defun prot-common-minor-modes-active ()
    "Return list of active minor modes for the current buffer."
    (let ((active-modes))
      (mapc (lambda (m)
              (when (and (boundp m) (symbol-value m))
                (push m active-modes)))
            minor-mode-list)
      active-modes))

  ;;;###autoload
  (defun prot-common-truncate-lines-silently ()
    "Toggle line truncation without printing messages."
    (let ((inhibit-message t))
      (toggle-truncate-lines t)))

  ;; NOTE 2023-08-12: I tried the `clear-message-function', but it did
  ;; not work.  What I need is very simple and this gets the job done.
  ;;;###autoload
  (defun prot-common-clear-minibuffer-message (&rest _)
    "Print an empty message to clear the echo area.
  Use this as advice :after a noisy function."
    (message ""))

  ;;;###autoload
  (defun prot-common-disable-hl-line ()
    "Disable Hl-Line-Mode (for hooks)."
    (hl-line-mode -1))

  ;;;###autoload
  (defun prot-common-window-bounds ()
    "Return start and end points in the window as a cons cell."
    (cons (window-start) (window-end)))

  ;;;###autoload
  (defun prot-common-page-p ()
    "Return non-nil if there is a `page-delimiter' in the buffer."
    (or (save-excursion (re-search-forward page-delimiter nil t))
        (save-excursion (re-search-backward page-delimiter nil t))))

  ;;;###autoload
  (defun prot-common-window-small-p ()
    "Return non-nil if window is small.
  Check if the `window-width' or `window-height' is less than
  `split-width-threshold' and `split-height-threshold',
  respectively."
    (or (and (numberp split-width-threshold)
             (< (window-total-width) split-width-threshold))
        (and (numberp split-height-threshold)
             (> (window-total-height) split-height-threshold))))

  (defun prot-common-window-narrow-p ()
    "Return non-nil if window is narrow.
  Check if the `window-width' is less than `split-width-threshold'."
    (and (numberp split-width-threshold)
         (< (window-total-width) split-width-threshold)))

  ;;;###autoload
  (defun prot-common-three-or-more-windows-p (&optional frame)
    "Return non-nil if three or more windows occupy FRAME.
  If FRAME is non-nil, inspect the current frame."
    (>= (length (window-list frame :no-minibuffer)) 3))

  ;;;###autoload
  (defun prot-common-read-data (file)
    "Read Elisp data from FILE."
    (with-temp-buffer
      (insert-file-contents file)
      (read (current-buffer))))

  ;;;###autoload
  (defun prot-common-completion-category ()
    "Return completion category."
    (when-let* ((window (active-minibuffer-window)))
      (with-current-buffer (window-buffer window)
        (completion-metadata-get
         (completion-metadata (buffer-substring-no-properties
                               (minibuffer-prompt-end)
                               (max (minibuffer-prompt-end) (point)))
                              minibuffer-completion-table
                              minibuffer-completion-predicate)
         'category))))

  ;; Thanks to Omar Antolín Camarena for providing this snippet!
  ;;;###autoload
  (defun prot-common-completion-table (category candidates)
    "Pass appropriate metadata CATEGORY to completion CANDIDATES.

  This is intended for bespoke functions that need to pass
  completion metadata that can then be parsed by other
  tools (e.g. `embark')."
    (lambda (string pred action)
      (if (eq action 'metadata)
          `(metadata (category . ,category))
        (complete-with-action action candidates string pred))))

  ;;;###autoload
  (defun prot-common-completion-table-no-sort (category candidates)
    "Pass appropriate metadata CATEGORY to completion CANDIDATES.
  Like `prot-common-completion-table' but also disable sorting."
    (lambda (string pred action)
      (if (eq action 'metadata)
          `(metadata (category . ,category)
                     (display-sort-function . ,#'identity))
        (complete-with-action action candidates string pred))))

  ;; Thanks to Igor Lima for the `prot-common-crm-exclude-selected-p':
  ;; <https://github.com/0x462e41>.
  ;; This is used as a filter predicate in the relevant prompts.
  (defvar crm-separator)

  ;;;###autoload
  (defun prot-common-crm-exclude-selected-p (input)
    "Filter out INPUT from `completing-read-multiple'.
  Hide non-destructively the selected entries from the completion
  table, thus avoiding the risk of inputting the same match twice.

  To be used as the PREDICATE of `completing-read-multiple'."
    (if-let* ((pos (string-match-p crm-separator input))
              (rev-input (reverse input))
              (element (reverse
                        (substring rev-input 0
                                   (string-match-p crm-separator rev-input))))
              (flag t))
        (progn
          (while pos
            (if (string= (substring input 0 pos) element)
                (setq pos nil)
              (setq input (substring input (1+ pos))
                    pos (string-match-p crm-separator input)
                    flag (when pos t))))
          (not flag))
      t))

  ;; The `prot-common-line-regexp-p' and `prot-common--line-regexp-alist'
  ;; are contributed by Gabriel: <https://github.com/gabriel376>.  They
  ;; provide a more elegant approach to using a macro, as shown further
  ;; below.
  (defvar prot-common--line-regexp-alist
    '((empty . "[\s\t]*$")
      (indent . "^[\s\t]+")
      (non-empty . "^.+$")
      (list . "^\\([\s\t#*+]+\\|[0-9]+[^\s]?[).]+\\)")
      (heading . "^[=-]+"))
    "Alist of regexp types used by `prot-common-line-regexp-p'.")

  (defun prot-common-line-regexp-p (type &optional n)
    "Test for TYPE on line.
  TYPE is the car of a cons cell in
  `prot-common--line-regexp-alist'.  It matches a regular
  expression.

  With optional N, search in the Nth line from point."
    (save-excursion
      (goto-char (line-beginning-position))
      (and (not (bobp))
           (or (beginning-of-line n) t)
           (save-match-data
             (looking-at
              (alist-get type prot-common--line-regexp-alist))))))

  ;; The `prot-common-shell-command-with-exit-code-and-output' function is
  ;; courtesy of Harold Carr, who also sent a patch that improved
  ;; `prot-eww-download-html' (from the `prot-eww.el' library).
  ;;
  ;; More about Harold: <http://haroldcarr.com/about/>.
  (defun prot-common-shell-command-with-exit-code-and-output (command &rest args)
    "Run COMMAND with ARGS.
  Return the exit code and output in a list."
    (with-temp-buffer
      (list (apply 'call-process command nil (current-buffer) nil args)
            (buffer-string))))

  (defvar prot-common-url-regexp
    (concat
     "~?\\<\\([-a-zA-Z0-9+&@#/%?=~_|!:,.;]*\\)"
     "[.@]"
     "\\([-a-zA-Z0-9+&@#/%?=~_|!:,.;]+\\)\\>/?")
    "Regular expression to match (most?) URLs or email addresses.")

  (autoload 'auth-source-search "auth-source")

  ;;;###autoload
  (defun prot-common-auth-get-field (host prop)
    "Find PROP in `auth-sources' for HOST entry."
    (when-let* ((source (auth-source-search :host host)))
      (if (eq prop :secret)
          (funcall (plist-get (car source) prop))
        (plist-get (flatten-list source) prop))))

  ;;;###autoload
  (defun prot-common-parse-file-as-list (file)
    "Return the contents of FILE as a list of strings.
  Strings are split at newline characters and are then trimmed for
  negative space.

  Use this function to provide a list of candidates for
  completion (per `completing-read')."
    (split-string
     (with-temp-buffer
       (insert-file-contents file)
       (buffer-substring-no-properties (point-min) (point-max)))
     "\n" :omit-nulls "[\s\f\t\n\r\v]+"))

  (defun prot-common-ignore (&rest _)
    "Use this as override advice to make a function do nothing."
    nil)

  ;; NOTE 2023-06-02: The `prot-common-wcag-formula' and
  ;; `prot-common-contrast' are taken verbatim from my `modus-themes'
  ;; and renamed to have the prefix `prot-common-' instead of
  ;; `modus-themes-'.  This is all my code, of course, but I do it this
  ;; way to ensure that this file is self-contained in case someone
  ;; copies it.

  ;; This is the WCAG formula: <https://www.w3.org/TR/WCAG20-TECHS/G18.html>.
  (defun prot-common-wcag-formula (hex)
    "Get WCAG value of color value HEX.
  The value is defined in hexadecimal RGB notation, such #123456."
    (cl-loop for k in '(0.2126 0.7152 0.0722)
             for x in (color-name-to-rgb hex)
             sum (* k (if (<= x 0.03928)
                          (/ x 12.92)
                        (expt (/ (+ x 0.055) 1.055) 2.4)))))

  ;;;###autoload
  (defun prot-common-contrast (c1 c2)
    "Measure WCAG contrast ratio between C1 and C2.
  C1 and C2 are color values written in hexadecimal RGB."
    (let ((ct (/ (+ (prot-common-wcag-formula c1) 0.05)
                 (+ (prot-common-wcag-formula c2) 0.05))))
      (max ct (/ ct))))

  ;;;; EXPERIMENTAL macros (not meant to be used anywhere)

  ;; TODO 2023-09-30: Try the same with `cl-defmacro' and &key
  (defmacro prot-common-if (condition &rest consequences)
    "Separate the CONSEQUENCES of CONDITION semantically.
  Like `if', `when', `unless' but done by using `:then' and `:else'
  keywords.  The forms under each keyword of `:then' and `:else'
  belong to the given subset of CONSEQUENCES.

  - The absence of `:else' means: (if CONDITION (progn CONSEQUENCES)).
  - The absence of `:then' means: (if CONDITION nil CONSEQUENCES).
  - Otherwise: (if CONDITION (progn then-CONSEQUENCES) else-CONSEQUENCES)."
    (declare (indent 1))
    (let (then-consequences else-consequences last-kw)
      (dolist (elt consequences)
        (let ((is-keyword (keywordp elt)))
          (cond
           ((and (not is-keyword) (eq last-kw :then))
            (push elt then-consequences))
           ((and (not is-keyword) (eq last-kw :else))
            (push elt else-consequences))
           ((and is-keyword (eq elt :then))
            (setq last-kw :then))
           ((and is-keyword (eq elt :else))
            (setq last-kw :else)))))
      `(if ,condition
           ,(if then-consequences
                `(progn ,@(nreverse then-consequences))
              nil)
         ,@(nreverse else-consequences))))

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

** The =prot-embark.el= library
:PROPERTIES:
:CUSTOM_ID: h:fb034be5-c316-4c4f-a46f-cebcab332a47
:ID:       01JGD334D1000CBNZ67JQM8TEF
:END:

#+begin_src emacs-lisp :tangle "custom-lisp/prot-embark.el" :mkdirp yes
  ;;; prot-embark.el --- Custom Embark keymaps -*- lexical-binding: t -*-

  ;; Copyright (C) 2023-2024  Protesilaos Stavrou

  ;; Author: Protesilaos Stavrou <info@protesilaos.com>
  ;; URL: https://protesilaos.com/emacs/dotemacs
  ;; Version: 0.1.0
  ;; Package-Requires: ((emacs "30.1") (embark "0.23"))

  ;; 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:
  ;;
  ;; Remember that every piece of Elisp that I write is for my own
  ;; educational and recreational purposes.  I am not a programmer and I
  ;; do not recommend that you copy any of this if you are not certain of
  ;; what it does.

  ;;; Code:

  (require 'embark)

  (defvar-keymap prot-embark-general-map
    :parent embark-general-map
    "i" #'embark-insert
    "w" #'embark-copy-as-kill
    "E" #'embark-export
    "S" #'embark-collect
    "A" #'embark-act-all
    "DEL" #'delete-region)

  (defvar-keymap prot-embark-url-map
    :parent embark-general-map
    "b" #'browse-url
    "d" #'embark-download-url
    "e" #'eww)

  (defvar-keymap prot-embark-buffer-map
    :parent embark-general-map
    "k" #'prot-simple-kill-buffer
    "o" #'switch-to-buffer-other-window
    "e" #'ediff-buffers)

  (add-to-list 'embark-post-action-hooks (list 'prot-simple-kill-buffer 'embark--restart))

  (defvar-keymap prot-embark-file-map
    :parent embark-general-map
    "f" #'find-file
    "j" #'embark-dired-jump
    "c" #'copy-file
    "e" #'ediff-files)

  (defvar-keymap prot-embark-identifier-map
    :parent embark-general-map
    "h" #'display-local-help
    "." #'xref-find-definitions
    "o" #'occur)

  (defvar-keymap prot-embark-command-map
    :parent embark-general-map
    "h" #'describe-command
    "." #'embark-find-definition)

  (defvar-keymap prot-embark-expression-map
    :parent embark-general-map
    "e" #'pp-eval-expression
    "m" #'pp-macroexpand-expression)

  (defvar-keymap prot-embark-function-map
    :parent embark-general-map
    "h" #'describe-function
    "." #'embark-find-definition)

  (defvar-keymap prot-embark-package-map
    :parent embark-general-map
    "h" #'describe-package
    "i" #'package-install
    "d" #'package-delete
    "r" #'package-reinstall
    "u" #'embark-browse-package-url
    "w" #'embark-save-package-url)

  (defvar-keymap prot-embark-symbol-map
    :parent embark-general-map
    "h" #'describe-symbol
    "." #'embark-find-definition)

  (defvar-keymap prot-embark-variable-map
    :parent embark-general-map
    "h" #'describe-variable
    "." #'embark-find-definition)

  (defvar-keymap prot-embark-region-map
    :parent embark-general-map
    "a" #'align-regexp
    "D" #'delete-duplicate-lines
    "f" #'flush-lines
    "i" #'epa-import-keys-region
    "d" #'epa-decrypt-armor-in-region
    "r" #'repunctuate-sentences
    "s" #'sort-lines
    "u" #'untabify)

  ;; The minimal indicator shows cycling options, but I have no use
  ;; for those.  I want it to be silent.
  (defun prot-embark-no-minimal-indicator ())
  (advice-add #'embark-minimal-indicator :override #'prot-embark-no-minimal-indicator)

  (defun prot-embark-act-no-quit ()
    "Call `embark-act' but do not quit after the action."
    (interactive)
    (let ((embark-quit-after-action nil))
      (call-interactively #'embark-act)))

  (defun prot-embark-act-quit ()
    "Call `embark-act' and quit after the action."
    (interactive)
    (let ((embark-quit-after-action t))
      (call-interactively #'embark-act))
    (when (and (> (minibuffer-depth) 0)
               (derived-mode-p 'completion-list-mode))
      (abort-recursive-edit)))

  (provide 'prot-embark)
  ;;; prot-embark.el ends here
#+end_src

** The =prot-window.el= library
:PROPERTIES:
:CUSTOM_ID: h:35b8a0a5-c447-4301-a404-bc274596238d
:ID:       01JGD334D80000B5EYPJTPKNMQ
:END:

#+begin_src emacs-lisp :tangle "custom-lisp/prot-window.el" :mkdirp yes
;;; prot-window.el --- Display-buffer and window-related extensions for my dotemacs -*- lexical-binding: t -*-

;; Copyright (C) 2023-2024  Protesilaos Stavrou

;; Author: Protesilaos Stavrou <info@protesilaos.com>
;; URL: https://protesilaos.com/emacs/dotemacs
;; Version: 0.1.0
;; Package-Requires: ((emacs "30.1"))

;; 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:
;;
;; This covers my window and display-buffer extensions, for use in my
;; Emacs setup: https://protesilaos.com/emacs/dotemacs.
;;
;; Remember that every piece of Elisp that I write is for my own
;; educational and recreational purposes.  I am not a programmer and I
;; do not recommend that you copy any of this if you are not certain of
;; what it does.

;;; Code:

(require 'prot-common)

(defvar prot-window-window-sizes
  '( :max-height (lambda () (floor (frame-height) 3))
     :min-height 10
     :max-width (lambda () (floor (frame-width) 4))
     :min-width 20)
  "Property list of maximum and minimum window sizes.
The property keys are `:max-height', `:min-height', `:max-width',
and `:min-width'.  They all accept a value of either a
number (integer or floating point) or a function.")

(defun prot-window--get-window-size (key)
  "Extract the value of KEY from `prot-window-window-sizes'."
  (when-let* ((value (plist-get prot-window-window-sizes key)))
    (cond
     ((functionp value)
      (funcall value))
     ((numberp value)
      value)
     (t
      (error "The value of `%s' is neither a number nor a function" key)))))

(defun prot-window-select-fit-size (window)
  "Select WINDOW and resize it.
The resize pertains to the maximum and minimum values for height
and width, per `prot-window-window-sizes'.

Use this as the `body-function' in a `display-buffer-alist' entry."
  (select-window window)
  (fit-window-to-buffer
   window
   (prot-window--get-window-size :max-height)
   (prot-window--get-window-size :min-height)
   (prot-window--get-window-size :max-width)
   (prot-window--get-window-size :min-width))
  ;; If we did not use `display-buffer-below-selected', then we must
  ;; be in a lateral window, which has more space.  Then we do not
  ;; want to dedicate the window to this buffer, because we will be
  ;; running out of space.
  (when (or (window-in-direction 'above) (window-in-direction 'below))
    (set-window-dedicated-p window t)))

(defun prot-window--get-display-buffer-below-or-pop ()
  "Return list of functions for `prot-window-display-buffer-below-or-pop'."
  (list
   #'display-buffer-reuse-mode-window
   (if (or (prot-common-window-small-p)
           (prot-common-three-or-more-windows-p))
       #'display-buffer-below-selected
     #'display-buffer-pop-up-window)))

(defun prot-window-display-buffer-below-or-pop (&rest args)
  "Display buffer below current window or pop a new window.
The criterion for choosing to display the buffer below the
current one is a non-nil return value for
`prot-common-window-small-p'.

Apply ARGS expected by the underlying `display-buffer' functions.

This as the action function in a `display-buffer-alist' entry."
  (let ((functions (prot-window--get-display-buffer-below-or-pop)))
    (catch 'success
      (dolist (fn functions)
        (when (apply fn args)
          (throw 'success fn))))))

(defun prot-window-shell-or-term-p (buffer &rest _)
  "Check if BUFFER is a shell or terminal.
This is a predicate function for `buffer-match-p', intended for
use in `display-buffer-alist'."
  (when (string-match-p "\\*.*\\(e?shell\\|v?term\\).*" (buffer-name (get-buffer buffer)))
    (with-current-buffer buffer
      ;; REVIEW 2022-07-14: Is this robust?
      (and (not (derived-mode-p 'message-mode 'text-mode))
           (derived-mode-p 'eshell-mode 'shell-mode 'comint-mode 'fundamental-mode)))))

(defun prot-window-remove-dedicated (&rest _)
  "Remove dedicated window parameter.
Use this as :after advice to `delete-other-windows' and
`delete-window'."
  (when (one-window-p :no-mini)
    (set-window-dedicated-p nil nil)))

(mapc
 (lambda (fn)
   (advice-add fn :after #'prot-window-remove-dedicated))
 '(delete-other-windows delete-window))

(defmacro prot-window-define-full-frame (name &rest args)
  "Define command to call ARGS in new frame with `display-buffer-full-frame' bound.
Name the function prot-window- followed by NAME.  If ARGS is nil,
call NAME as a function."
  (declare (indent 1))
  `(defun ,(intern (format "prot-window-%s" name)) ()
     ,(format "Call `prot-window-%s' in accordance with `prot-window-define-full-frame'." name)
     (interactive)
     (let ((display-buffer-alist '((".*" (display-buffer-full-frame)))))
       (with-selected-frame (make-frame)
         ,(if args
              `(progn ,@args)
            `(funcall ',name))
         (modify-frame-parameters nil '((buffer-list . nil)))))))

(defun prot-window--get-shell-buffers ()
  "Return list of `shell' buffers."
  (seq-filter
   (lambda (buffer)
     (with-current-buffer buffer
       (derived-mode-p 'shell-mode)))
   (buffer-list)))

(defun prot-window--get-new-shell-buffer ()
  "Return buffer name for `shell' buffers."
  (if-let* ((buffers (prot-window--get-shell-buffers))
            (buffers-length (length buffers))
            ((>= buffers-length 1)))
      (format "*shell*<%s>" (1+ buffers-length))
    "*shell*"))

;;;###autoload (autoload 'prot-window-shell "prot-window")
(prot-window-define-full-frame shell
  (let ((name (prot-window--get-new-shell-buffer)))
    (shell name)
    (set-frame-name name)
    (when-let* ((buffer (get-buffer name)))
      (with-current-buffer buffer
        (add-hook
         'delete-frame-functions
         (lambda (_)
           ;; FIXME 2023-09-09: Works for multiple frames (per
           ;; `make-frame-command'), but not if the buffer is in two
           ;; windows in the same frame.
           (unless (> (safe-length (get-buffer-window-list buffer nil t)) 1)
             (let ((kill-buffer-query-functions nil))
               (kill-buffer buffer))))
         nil
         :local)))))

;;;###autoload (autoload 'prot-window-coach "prot-window")
(prot-window-define-full-frame coach
  (let ((buffer (get-buffer-create "*scratch for coach*")))
    (with-current-buffer buffer
      (funcall initial-major-mode))
    (display-buffer buffer)
    (set-frame-name "Coach")))

;; REVIEW 2023-06-25: Does this merit a user option?  I don't think I
;; will ever set it to the left.  It feels awkward there.
(defun prot-window-scroll-bar-placement ()
  "Control the placement of scroll bars."
  (when scroll-bar-mode
    (setq default-frame-scroll-bars 'right)
    (set-scroll-bar-mode 'right)))

(add-hook 'scroll-bar-mode-hook #'prot-window-scroll-bar-placement)

(defun prot-window-no-minibuffer-scroll-bar (frame)
  "Remove the minibuffer scroll bars from FRAME."
  (set-window-scroll-bars (minibuffer-window frame) nil nil nil nil :persistent))

(add-hook 'after-make-frame-functions 'prot-window-no-minibuffer-scroll-bar)

;;;; Run commands in a popup frame (via emacsclient)

(defun prot-window-delete-popup-frame (&rest _)
  "Kill selected selected frame if it has parameter `prot-window-popup-frame'.
Use this function via a hook."
  (when (frame-parameter nil 'prot-window-popup-frame)
    (delete-frame)))

(defmacro prot-window-define-with-popup-frame (command)
  "Define function which calls COMMAND in a new frame.
Make the new frame have the `prot-window-popup-frame' parameter."
  `(defun ,(intern (format "prot-window-popup-%s" command)) ()
     ,(format "Run `%s' in a popup frame with `prot-window-popup-frame' parameter.
Also see `prot-window-delete-popup-frame'." command)
     (interactive)
     (let ((frame (make-frame '((prot-window-popup-frame . t)))))
       (select-frame frame)
       (switch-to-buffer " prot-window-hidden-buffer-for-popup-frame")
       (condition-case nil
           (call-interactively ',command)
         ((quit error user-error)
          (delete-frame frame))))))

(declare-function org-capture "org-capture" (&optional goto keys))
(defvar org-capture-after-finalize-hook)

;;;###autoload (autoload 'prot-window-popup-org-capture "prot-window")
(prot-window-define-with-popup-frame org-capture)

(declare-function tmr "tmr" (time &optional description acknowledgep))
(defvar tmr-timer-created-functions)

;;;###autoload (autoload 'prot-window-popup-tmr "prot-window")
(prot-window-define-with-popup-frame tmr)

(provide 'prot-window)
;;; prot-window.el ends here
#+end_src


** The =nebkor-personal.el= module
:PROPERTIES:
:CUSTOM_ID: h:0EAB9FBA-98AD-4E0E-BC01-71D1EC920B8D
:END:

I want this section to contain all the personal configuration that
anyone using this repo should modify. But I don't know how to do this
properly at the moment, in a way such that it's part of the repo, and
yet only exported for me. I'll update this section as and when I
figure it out.

*** My personal user settings
:PROPERTIES:
:CUSTOM_ID: h:9757A878-D383-47F4-94FD-98374954F83A
:CREATED:  [2024-12-31 Tue]
:ID:       01JGD334DE0000F5Z5HSR7XYRX
:END:

#+begin_src emacs-lisp :tangle "custom-lisp/nebkor-personal.el" :mkdirp yes
  (global-set-key [C-tab] #'other-window)
  (global-set-key [C-S-tab] #'sother-window)
  (global-set-key [C-iso-lefttab] #'sother-window)
  (global-set-key [f4] #'nebkor/kill-buffer)
  (global-set-key [f7] 'revert-buffer)

  (setq-default auto-fill-function 'do-auto-fill)
  (setq-default fill-column 100)
  (turn-on-auto-fill)
  (add-hook 'prog-mode-hook (lambda () (auto-fill-mode -1)))
  (add-hook 'before-save-hook #'delete-trailing-whitespace)
  (fset 'yes-or-no-p 'y-or-n-p)
  (global-set-key (kbd "C-1")
                  "//-************************************************************************
  //
  //-************************************************************************")
#+end_src

*** Finally, we provide the =nebkor-personal.el= module
:PROPERTIES:
:CUSTOM_ID: h:29100A9B-3142-4458-8CE1-43798EB9AA13
:CREATED:  [2024-12-05 Thu 16:47]
:ID:       01JGD334GK0005ME4V4H9MVAHY
:END:

#+begin_src emacs-lisp :tangle "custom-lisp/nebkor-personal.el"
  (provide 'nebkor-personal)
#+end_src

** COMMENT Local Variables

We use ~toc-org-mode~ in this file, to generate titles which are
compliant with GitHub. Here, we enable ~toc-org-mode~ for this file
specifically, so that others who open the file in Emacs Org-mode can
navigate the TOC properly.

# Local Variables:
# eval: (toc-org-mode 1)
# End: