Like the start of a bash file a bang is a short sequence of text proceeded by some more text. Ebangs lets you define these sequences then persistently stores all occurrences of them.
;; ~~# ! '(todo (text "some stuff"))
Here the bang is ~~#
, !
is a base 94 ID that can be used to reference that specific bang.
Finally an elisp form is at the end, it evaluates to a list whose car is the bang type and cdr is a list of additional properties.
While ~~#
is a bang that comes included with Ebangs, you could easily make your own which takes other “arguments” for other reasons.
(ebangs-select i => (ebangs-get 'text i) :from (file buffer-file-name)
(eq (ebangs-get 'type i) 'todo))
The above code will then give you back a list of all the todos in the current file.
The default bang ~~#
also gave us other properties; you could ask for the line numbers or positions in the buffer by replacing 'text
with 'position
or 'line-number
.
For a better example: see the actual implementation of ebangs-show-file-todos
and ebangs-show-todos
.
Put ebangs.el in your load path then in your init.el:
(require 'ebangs)
(ebangs-global-minor-mode)
Type out the bang (eg ~~#
), then M-x complete-bang (you may wish to bind this to
a key). It will insert the body of the bang, and start watching the file or
buffer for changes. The instanced bangs persist between sessions, and will
always be up to date when they are queried.
All selections come down to the ebangs-loop
macro.
(ebangs-loop ACCUMULATOR VAR [=> COLLECTION-FORM] ; optional parts are in square brackets
[:from INDEX]
or [:from (INDEX VALUE)]
or [:from (INDEX VAR :where CONDITIONS...)]
BODY...)
- It loops
VAR
over all of the bangs instance. - Only when all of the
BODY
forms return true is the value collected. - The
=> COLLECTION-FORM
is for the value collected (or justVAR
if none is supplied), using the common lisp loop accumulatorACCUMULATOR
. INDEX
is a key fromebangs-get
. Indexed keys are faster to lookup than regular ones, by default only'file
,'id
, and'type
are indexed.
The ebangs-select
you saw earlier is just a shorthand for loop with collect
passed as the accumulator.
The ~~#
bang is defined as follows:
(ebangs-set-type
(concat "~~" "#") ; I use concat here to avoid getting errors when the definition is picked up as a bang.
(lambda (beg)
(let* ((id (ebangs-read-number))
(exp (ebangs-read-sexp))
(items (eval exp t))
type (table (make-hash-table)))
(unless (and (listp items) (car items))
(error "Expected link expression to evaluate to list with a type, got:\n%S from \n%S" items exp))
(setf type (car items))
(dolist (i (cdr items))
(puthash (car i) (cadr i) table))
(puthash 'id id table)
(puthash 'position beg table)
(puthash 'line-number (line-number-at-pos beg) table)
(ebangs-make-instance type table (list id)))))
(ebangs-set-completer
(concat "~~" "#")
(lambda (_)
(insert " " (int->base94 (ebangs-get-number)) " '()")
(cl-decf (point))))
The first defines a way to read a type of bang from the buffer, the second a way to complete it.
Only buffers with related files can be tracked for changes.
Files without bangs aren’t watched for updates until one is inserted by ebangs-complete
, this is why I can write bangs in this file without them being picked up.
If a file isn’t active (you can check with ebangs-check-active
), you can call ebangs-activate
in the buffer to start tracking it.
If a file is moved, the new location won’t be tracked, so you should activate it quickly to avoid clashing IDs and re-instance the bangs.
Ebangs isn’t just todos, you could define a bang for anything you need. You might start off just putting some properties in a default bang, then define a new bang and an entirely new set of functions to work with it. For instance you might define a flashcard using the following default bang:
~~# a `(flashcard (front ,(ebangs-get-paragraph "front")) (back ,(ebangs-get-paragraph "back"))) Begin front: some text for the front End front. Begin back: some back text End back.
ebangs-get-paragraph
is just a function which returns a string of the lines between the next Begin:/End. pair.
You could also use a bang to link to others:
~~> a "Look at my cool flashcard! (and this link to it)"