Skip to content

jack-faller/ebangs

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

55 Commits
 
 
 
 
 
 

Repository files navigation

What is it?

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.

An example:

;; ~~# ! '(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.

Usage:

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.

Selection:

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 just VAR if none is supplied), using the common lisp loop accumulator ACCUMULATOR.
  • INDEX is a key from ebangs-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.

Defining your own:

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.

File Tracking

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.

Other examples:

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)"

About

Index important locations in files

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published