Two Wrongs

Ceasing Short-Lived Maintenance of Emacs Versor

Ceasing Short-Lived Maintenance of Emacs Versor

I really like the idea of Emacs Versor (as in Versatile Cursors) for semantic editing and navigation, but I had to stop using it. I’ve moved back to Smartparens’ strict mode and regular Emacs navigation commands now, simply because those packages Just Work™, and I’m a busy man now so I need stuff that just works.

About Versor

The idea of Versor is that if you look at the arrow keys, they actually represent movement in two arbitrary dimensions.

What if you could reassign these keys such that the left and right keys didn’t move by column, and the up and down keys didn’t move by line? Maybe you’d like the left and right keys to move by word, and the up and down keys by paragraph. Or the left and right keys by expression, and the up and down keys by statement? You’d get semantic text navigation at relatively little cost.

What if you then add some language-aware editing tools to this, such that key editing operations are performed not on a character-by-character basis, but rather on whatever unit defines the “small” dimension (i.e. the one traversed with the left and right keys)? So if you press <DEL> when the left and right keys navigate across words, entire words will be deleted at a time. That’s what Languide 11 Languide as in Language Guided Editing. I think., the companion to Versor, provides. Very neat stuff.

Unfortunately, the author of both Versor and Languide, John Sturdy, does not respond to emails, and the last update to the code was back in 2008.

I took it upon myself to “maintain” this package until someone better shows up, but it just hasn’t been worth the effort. I say that with a slight smile, because it’s not like anyone other than myself uses it. As far as I know, I’m the only one who uses it. But fixing the bugs I encounter every now and then has proven to be too much of a hassle, even with myself as the sole user.

My Two Patches

In case anyone else wants to pick up the torch, there are the two bug fixes I have made that should probably make it into the general codebase22 The others are probably more specific to my own workflow and not of general interest.:

  1. This is a rather silly bug, actually, and after I had torn half of my hair out I found it somewhat amusing. Apparently I’m not the only one who makes this kind of mistake!

    --- a/lisp/versor-commands.el
    +++ b/lisp/versor-commands.el
    @@ -252,8 +252,8 @@ comment and doing \(funcall function 1) until not in a comment."
                     major-mode))
           (if (and (interactive-p)
                   versor-reversed)
    -         (decf versor-meta-level)
    -       (incf versor-meta-level))
    +         (incf versor-meta-level)
    +       (decf versor-meta-level))
           (versor-trim-meta-level))
         (versor-trim-level)
         (versor-set-status-display
    
  2. This was more convoluted, and I’m sure the code sucks. I was still kind of new to Emacs Lisp when I wrote it as a quick fix for annoying errors I encountered.

    --- a/lisp/versor-dimensions.el
    +++ b/lisp/versor-dimensions.el
    @@ -553,15 +553,28 @@ With optional LEVEL-OFFSET, add that to the level first."
           (setq versor-level
                (if versor-level-wrap max 1)))))
    
    +(defun versor-meta-level-bounds ()
    +  "Find the first and last valid meta level in the current major mode."
    +  (let ((first -1)
    +        (last -1))
    +    (dotimes (meta-level (length moves-moves))
    +      (when (versor-meta-dimension-valid-for-mode
    +             (aref (aref moves-moves meta-level) 0)
    +             major-mode)
    +        (when (= last -1) (setq first meta-level))
    +        (setq last meta-level)))
    +    (cons first last)))
    +
     (defun versor-trim-meta-level ()
       "Ensure that `versor-meta-level' is in range."
    -  (let ((max (1- (length moves-moves))))
    +  (let ((min (car (versor-meta-level-bounds)))
    +        (max (cdr (versor-meta-level-bounds))))
         (when (> versor-meta-level max)
           (setq versor-meta-level
    -           (if versor-meta-level-wrap 1 max)))
    -    (when (< versor-meta-level 1)
    +           (if versor-meta-level-wrap min max)))
    +    (when (< versor-meta-level min)
           (setq versor-meta-level
    -           (if versor-meta-level-wrap max 1)))))
    +           (if versor-meta-level-wrap max min)))))
    
     (defvar versor-meta-dimensions-valid-for-modes
       '(((emacs-lisp-mode lisp-mode scheme-mode lisp-interaction-mode)
    

    I had to manually edit this diff to get rid of some debug prints I accidentally commited, but I’m fairly sure it still applies cleanly.