6657 lines
258 KiB
Org Mode
6657 lines
258 KiB
Org Mode
#+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:
|