The Motivation
The motivation, as touched on earlier, was to move around HTML and other
buffer types via the % key. Moving around otherwise with Evil
can be quite cumbersome, and at the time I had not known of
sgml-skip-tag-backward . Even if I knew of that function, or
others similar, I would prefer them bound to what I am used to when I use
VIM or similar. Hence, I had to alter the behaviour
of Evil's % with advice-add.
What is EMACS's Advice system?
In simple terms, advice lets you run code around another piece of code,
ultimately replacing the entrypoint in the symbol table. You can add a piece
of code that runs :before, :after, and
:around.
I ultimately just use :around, because I don't know what I want,
and usually want to know what the code path is. I also usually replace the
code path entirely.
The following code is likely best used with a :before, but I use
an :around.
;;; MANAGE MESSAGE BUFFER
(defun clear-message-buffer ()
(interactive)
(with-current-buffer (get-buffer "*Messages*")
(named-let lp ((it buffer-read-only))
(when it
(read-only-mode -1)
(lp (not it)))
(delete-region (point-min) (point-max))
(read-only-mode))))
(defvar message-size-threshold 5000)
(advice-add 'message :around
(lambda (fn format-string &rest args)
(with-current-buffer (get-buffer "*Messages*")
(let ((size (buffer-size (current-buffer))))
(when (> size message-size-threshold)
(clear-message-buffer))))
(apply fn (cl-list* format-string args)))
'((name . "memory-mgr")))
;;(advice-remove 'message "memory-mgr")
The purpose of the previous code is so that I do not accumulate hours worth of
messages from EMACS over a course of a hacking session.
This would have been more applicable to older systems, where a few megabytes
of RAM being wasted on debug info would have been costly. This is more for
legibility of trying to find out what happened in the last half hour.
What would I need to do to make % extensible?
Essentially, what one needs is some way of testing for a piece of lexical
syntax, and then dispatching a piece of code to move the editor's
point around.
I have no idea how one would do this without a table.
If this were like, smalltalk, one would still have a table, but then you don't need
funcall and rely on polymorphism instead.
le
JumpTable>>determineAndRun [
self caseOf: table :: value: universalArgument.
].
Because of the fact that one needs to be able to add to this at runtime, you
cannot forgo using a table.
I am open to suggestions, but I am doubtful of this
Hence, we come to this.
(defun evil-extensible-jump-item/advice (fn &optional count)
"Advice for enabling the extensible jumping behaviour."
(let (do)
(if (cl-some (lambda (x)
(let ((tst (funcall (car x))))
(when tst
(setf do (cdr x))
tst)))
evil-extensible-jump-item/*table*)
(funcall do count)
;; fallthrough to original function
(apply fn count))))
(advice-add 'evil-jump-item :around
#'evil-extensible-jump-item/advice
'((name . "evil-extensible-jump-item")))
One immediate concern would be, how would I share data between functions?
How would I avoid recomputing nodes in a treesitter tree that got made for
the test function?
The shrimple answer is this: Let over Lambda.
I initially only used it because of the shotgun regex parsing I had with a
bash pipeline movement function, where I computed a pipeline's tree from the
test function and returning true if the node-at-point was within
it.
But I came to use it in every example of the SML movement functions.
So when I determined I was within a case expression, I kept the node in a
unbound let slot that was shared amoung the tester and mover
function. I could then determine the next MRULE node
within that case, and hence move to it, without re-walking the tree to get a
case node, wasting cycles[1].
Caveats
This overwhelmingly relies on treesitter.
This also relies on treesitter parses being similar.
Hence, if theres a difference in parsing, one function could do unexpected
things when being ran along side another treesitter grammar.
Another caveat is that of load order, and
test-function clobbering.
z03-extjmp/sml-patfun-test, in particular, if it did not test
for parent nodes not being that of CASE_EXP, and was
added to the table earlier: then it would run inside the pattern of a case
expression, and return to the beginning of the function or go to the other
pattern entry point of the function.
This is less of a caveat and more of a deliberate decision, but you
require the use of mode hooks to register functions in the table.
Otherwise, EVERY function related would be in the
table at the same time, bloating it and possibly doing the "clobbering" I
mentioned earlier.
This is a feature of EMACS called "buffer local
variables".
I could provide a macro for this, similar to my add-named-hook
macro, but I don't have enough of these to warrant doing so. It would just
be a (progn (defun ... () ...) (add-hook ... ...)), so.