annalist.el
annalist.el copied to clipboard
Record and display information such as keybindings
#+TITLE: Annalist User Manual #+AUTHOR: Fox Kiester #+LANGUAGE: en #+TEXINFO_DIR_CATEGORY: Emacs #+TEXINFO_DIR_TITLE: Annalist: (annalist). #+TEXINFO_DIR_DESC: Record and display information such as keybindings.
NOTE: if you are viewing this in org-mode, it is recommended that you install and enable [[https://github.com/snosov1/toc-org][toc-org]], so that all internal links open correctly.
[[https://melpa.org/#/annalist][file:https://melpa.org/packages/annalist-badge.svg]] [[https://travis-ci.org/noctuid/annalist.el][https://travis-ci.org/noctuid/annalist.el.svg?branch=master]]
#+begin_quote Incessant wind sweeps the plain. It murmurs on across grey stone, carrying dust from far climes to nibble eternally at the memorial pillars. There are a few shadows out there still but they are the weak and the timid and the hopelessly lost.
It is immortality of a sort.
Memory is immortality of a sort.
In the night, when the wind dies and silence rules the place of glittering stone, I remember. And they all live again. #+end_quote
=annalist.el= is a library that can be used to record information and later print that information using =org-mode= headings and tables. It allows defining different types of things that can be recorded (e.g. keybindings, settings, hooks, and advice) and supports custom filtering, sorting, and formatting. =annalist= is primarily intended for use in other packages like =general= and =evil-collection=, but it can also be used directly in a user's configuration.
[[file:https://user-images.githubusercontent.com/4250696/63480582-64e2cb00-c460-11e9-9571-706b5b96992c.png]]
- Table of Contents :noexport:TOC:
- [[#usage][Usage]]
- [[#disabling-annalist][Disabling Annalist]]
- [[#terminology][Terminology]]
- [[#settings][Settings]]
- [[#defining-new-types][Defining New Types]]
- [[#type-top-level-settings][Type Top-level Settings]]
- [[#type-item-settings][Type Item Settings]]
- [[#record-update-preprocess-and-postprocess-settings-argument][=:record-update=, =:preprocess=, and =:postprocess= Settings Argument]]
- [[#defining-views][Defining Views]]
- [[#view-top-level-settings][View Top-level Settings]]
- [[#view-item-settings][View Item Settings]]
- [[#recording][Recording]]
- [[#describing][Describing]]
- [[#helper-functions][Helper Functions]]
- [[#list-helpers][List Helpers]]
- [[#formatting-helpers][Formatting Helpers]]
- [[#format-helpers][=:format= Helpers]]
- [[#formatting-emacs-lisp-source-blocks][Formatting Emacs Lisp Source Blocks]]
- [[#sorting-helpers][Sorting Helpers]]
- [[#builtin-types][Builtin Types]]
- [[#keybindings-type][Keybindings Type]]
- Usage ** Disabling Annalist #+begin_quote What fool always has his nose in everywhere because he thinks he has to know so he can record it in his precious Annals? #+end_quote
If you use a library that uses =annalist= (e.g. =evil-collection= or =general=) but don't need it's functionality during init or at all, you can set =annalist-record= to nil to shave some milliseconds off of your init time (especially if you have a lot of keybindings). Alternatively, if you only want to prevent =annalist= from recording certain things or have it only record certain things, you can configure =annalist-record-blacklist= or =annalist-record-whitelist= respectively.
** Terminology
- item - an individual recorded item; may be displayed as a heading or as a table column entry (e.g. a key such as =C-c=)
- record - a list of related, printable items corresponding to one piece of information (e.g. a single keybinding: a list of a keymap, key, and definition)
- metadata - a plist of information about a data list that should not be printed; appears as the last item in a record
- tome - a collection of records of a specific type
** Settings Annalist provides =annalist-describe-hook= which is run in annalist description buffers after they have been populated but before they are marked read-only: #+begin_src emacs-lisp (add-hook 'annalist-describe-hook (lambda () (visual-fill-column-mode -1))) #+end_src
** Defining New Types #+begin_quote Three huge tomes bound in worn, cracked dark leather rested on a large, long stone lectern, as though waiting for three speakers to step up and read at the same time. #+end_quote
Annalist provides the function ~annalist-define-tome~ for defining new types of tomes: #+begin_src emacs-lisp (annalist-define-tome 'battles '(:primary-key (year name) :table-start-index 1 year name casualties ...)) #+end_src
At minimum, a type definition must include =:primary-key=, =:table-start-index=, and a symbol for each item records should store. Items should be defined in the order they should appear in org headings and then in the table.
*** Type Top-level Settings These settings apply to the entirety of the recorded information.
- =:table-start-index= - the index of the first item to be printed in an org table; previous items are printed as headings (default: none)
- =:primary-key= - the item or list of items that uniquely identifies the record; used with the =:test= values for those items to check for an old record that should be replaced/updated (default: none)
- =:record-update= - a function used to update a record before recording it; this can be used to, for example, set the value of an item to store the previous value of another item; the function is called with =old-record= (nil if none), =new-record=, and =settings=; see ~annalist--update-keybindings~ for an example of how to create such a function (default: none)
- =:preprocess= - a function used to alter a record before doing anything with it; it is passed =record= and =settings= and should return the altered record; see the default keybindings type for an example (default: none)
- =:test= - test function used for comparing the primary key (as a list of each item in the order it appears in the definition); you will need to create the test with ~define-hash-table-test~ if it does not exist (default: ~equal~; generally should be unnecessary to change)
- =:defaults= - a plist of default item settings; see below for valid item settings (default: none)
*** Type Item Settings Item settings only apply to a specific item. Defaults for items that don't explicitly specify a setting can be set using the top-level =:defaults= keyword.
- =:test= - test function used for comparing items; only applicable to heading items; you will need to create the test with ~define-hash-table-test~ if it does not exist (default: ~equal~; generally should be unnecessary to change)
*** =:record-update=, =:preprocess=, and =:postprocess= Settings Argument The settings plist past to the =:record-update= function contains all information for both the tome type and view. The information is converted into a valid plist and some extra keywords are added. Here is an example: #+begin_src emacs-lisp '(:table-start-index 2 :primary-key (keymap state key) ;; the following keywords are generated for convenience :type keybindings :key-indices (2 1 0) :final-index 4 :metadata-index 5 ;; item settings can be accessed by their symbol or their index keymap (:name keymap :index 0 :format annalist-code) 0 (:name keymap :index 0 :format annalist-code) ...) #+end_src
** Defining Views #+begin_quote In those days the company was in service to… #+end_quote
Views contain settings for formatting and displaying recorded information. Settings from the type definition cannot be changed later. On the other hand, views are for all settings that a user may want to change for a particular ~annalist-describe~ call. They are defined using the same format as tome types: #+begin_src emacs-lisp (annalist-define-view 'battles 'default '(:defaults (:format capitalize) year name (casualties :title "Deaths") ...)) #+end_src
The =default= view is what ~annalist-describe~ will use if no view name is explicitly specified. To prevent naming conflicts, external packages that create views should prefix the views with their symbol (e.g. =general-alternate-view=).
*** View Top-level Settings These settings apply to the entirety of the recorded information.
- =:predicate= - a function that is passed the entire record and returns non-nil if the record should be printed (default: none)
- =:sort= - a function used to sort records in each printed table; the function is passed two records and and should return non-nil if the first record should come first (default: none; tables are printed in recorded order)
- =:hooks= - a function or a list of functions to run in the describe buffer after printing all headings and tables before making the buffer readonly; these run before =annalist-describe-hook= (default: none)
- =:postprocess= - a function used to alter a record just before printing it; it is passed =record= and =settings= and should return the altered record; an example use case would be to alter the record using its metadata (e.g. by replacing a keybinding definition with a which-key description, if one exists) (default: none)
- =:defaults= - a plist of default item settings; see below for valid item settings (default: none)
There is also a special =:inherit= keyword that can be used to create a new type of tome that is based on another type: #+begin_src emacs-lisp (annalist-define-view 'keybindings 'alternate ;; override title for key column '((key :title "Keybinding") ...) :inherit 'keybindings) #+end_src
*** View Item Settings Item settings only apply to a specific item. Defaults for items that don't explicitly specify a setting can be set using the top-level =:defaults= keyword. #+begin_src emacs-lisp (annalist-define-view 'keybindings 'my-view '(:defaults (:format #'capitalize) ;; surround key with = instead of capitalizing (key :format #'annalist-verbatim) ;; perform no formatting on definition (definition :format nil))) #+end_src
Sorting/filtering (only for items displayed in headings):
- =:predicate= - a function that is passed the item and returns non-nil if it should be printed; only applicable to heading items (default: none)
- =:prioritize= - list of items that should be printed before any others; only applicable to heading items (default: none)
- =:sort= - a function used to sort records; only applicable to heading items; the function is passed two items and and should return non-nil if the first item should come first (default: none; printed in recorded order)
Formatting:
- =:title= - a description of the item; used as the column title (default: capitalize the symbol name; local only)
- =:format= - function to run on the item value before it is printed (e.g. ~#'capitalize~, ~#'annalist-code~, ~#'annalist-verbatim~, etc.); note that this is run on the item as-is if it has not been truncated, so the function may need to convert the item to a string first; has no effect if the item is extracted to a footnote/source block (default: none)
- =:max-width= - the max character width for an item; note that this is compared to the item as-is before any formatting (default: 50)
- =:extractp= - function to determine whether to extract longer entries into footnotes instead of truncating them; (default: ~listp~)
- =:src-block-p= function to determine whether to extract to a source block when the =:extractp= function returns non-nil (default: ~listp~)
** Recording #+begin_quote The Lady said, “I wanted you to see this, Annalist.” […] “What is about to transpire. So that it is properly recorded in at least one place.” #+end_quote
~annalist-record~ is used to record information. It requires three arguments: =annalist= =type= =record=. The =annalist= argument will usually be the same as the package prefix that is recording the data. =annalist= and any other names prefixed by =annalist= are reserved for this package. =type= is the type of data to record, and =record= is the actual data. Optionally, the user can also specify metadata that won't be printed after the final item. Buffer-local records should additionally specify =:local t=. Here is an example: #+begin_src emacs-lisp (annalist-record 'me 'keybindings (list ;; keymap state key definition previous-definition 'global-map nil (kbd "C-+") #'text-scale-increase nil ;; metadata can be specified after final item (list :zoom-related-binding t)))
;; alternatively, record using plist instead of ordered list (annalist-record 'me 'keybindings (list 'keymap 'global-map 'state nil 'key (kbd "C-+") 'definition #'text-scale-increase ;; metadata can be specified with `t' key t (list :zoom-related-binding t)) :plist t) #+end_src
Some items can potentially be recorded as nil. In the previous example, the evil =state= is recorded as nil (which will always be the case for non-evil users). When a heading item is nil, the heading at that level will just be skipped/not printed.
** Describing #+begin_quote Once each month, in the evening, the entire Company assembles so the Annalist can read from his predecessors. #+end_quote
~annalist-describe~ is used to describe information. It takes three arguments: =name= =type view=. =view= is optional (defaults to =default=). For example: #+begin_src emacs-lisp (annalist-describe 'me 'keybindings) #+end_src
It is possible to have custom filtering/sorting behavior by using a custom view: #+begin_src emacs-lisp (annalist-define-view 'keybindings 'active-keybindings-only '((keymap ;; only show keys bound in active keymaps :predicate #'annalist--active-keymap ;; sort keymaps alphabetically :sort #'annalist--string-<)))
(annalist-describe 'my 'keybindings 'active-keybindings-only) #+end_src
=annalist-org-startup-folded= will determine what =org-startup-folded= setting to use (defaults to nil; all headings will be unfolded).
** Helper Functions *** List Helpers ~annalist-plistify-record~ can be used to convert a record that is an ordered list to a plist. ~annalist-listify-record~ can be used to do the opposite. This is what the =:plist= argument for ~annalist-record~ uses internally. These functions can be useful, for example, inside a =:record-update= function, so that you can get record items by their name instead of by their index. However, if there will be a lot of data recorded for a type during Emacs initialization time, the extra time to convert between list types can add up, so it's recommended that you don't use these functions or =:plist= in such cases.
*** Formatting Helpers **** =:format= Helpers Annalist provides ~annalist-verbatim~ (e.g. ~=verbatim text=~), ~annalist-code~ (e.g. =~my-function~=), and ~annalist-capitalize~. There is also an ~annalist-compose~ helper for combining different formatting functions.
**** Formatting Emacs Lisp Source Blocks By default, Emacs Lisp extracted into source blocks will just be one long line. You can add ~annalist-multiline-source-blocks~ to a view's =:hooks= keyword or to =annalist-describe-hook= to autoformat org source blocks if lispy is installed. By default, it uses ~lispy-alt-multiline~. To use ~lispy-multiline~ instead, customize ~annalist-multiline-function~.
The builtin types have ~annlist-multiline-source-blocks~ in their =:hooks= setting by default.
Here is an example of what this looks like:
[[file:https://user-images.githubusercontent.com/4250696/62338313-1025e300-b4a6-11e9-845f-179c02abef35.png]]
*** Sorting Helpers Annalist provides ~annalist-string-<~ and ~annalist-key-<~ (e.g. ~(kbd "C-c a")~ vs ~(kbd "C-c b")~).
** Builtin Types *** Keybindings Type Annalist provides a type for recording keybindings that is used by =evil-collection= and =general=. When recording a keybinding, the keymap must be provided as a symbol. Here is an example: #+begin_src emacs-lisp (annalist-record 'annalist 'keybindings (list 'org-mode-map nil (kbd "C-c g") #'counsel-org-goto)) #+end_src
In addition to the default view, it has a =valid= to only show keybindings for keymaps/states that exist (since some keybindings may be in a ~with-eval-after-load~). It also has an =active= view to only show keybindings that are currently active.