TheClapp/ blog/ tags/ vim mode

This feed contains pages in the "vim_mode" category.

lw-vim-mode v8 posted

v8 of lw-vim-mode has been posted to common-lisp.net.

Download: darcs get http://common-lisp.net/project/lw-vim-mode/darcs/lw-vim-mode.

For further announcements, please subscribe to the lw-vim-mode-announce mailing list.

Posted Fri 13 Jun 2008 08:17:00 AM EDT Tags: vim mode
lw-vim-mode project at common-lisp.net

I got approved for a project at common-lisp.net. I haven't uploaded my source tree yet, but I have done a tarball.

Also, there are two mailing lists (one for developers and one for announcements), and even a wiki and bug tracking page (which I can't currently log in to; I've emailed the c-l.net admin about that).

Update 30 May 2008: Gary King fixed it (trac) on Memorial Day and dropped me an email. Thanks, Gary! Also, I uploaded a source tree and put it under darcs. See here.

Posted Thu 22 May 2008 10:40:45 PM EDT Tags: vim mode
lw-vim-mode project interest

I got no replies (so far) or emails about my announcement yesterday, but I got a lot of hits on the website. I'm going to look on the bright side and hope that people are interested and not pointing and laughing and/or shaking their heads in disbelief. :)

So I requested a project at http://www.common-lisp.net with darcs and trac and mailing lists and everything. More updates as events warrant.

Posted Tue 20 May 2008 08:23:17 AM EDT Tags: vim mode
Intro to lw-vim-mode

I've written a partial vi-mode for the Lispworks editor.

Questions

Why did I publicize it?

Beyond a certain point, working in a vacuum sucks. I'd like to know if anyone else is interested in it.

If nobody is, I'll keep working on it at my current widely variable pace, for my own edification and instruction. But if other people want to use it too, then I'll do all the usual stuff for an Open Source software project, like make my Wiki actually editable by others, create a mailing list, use an actual distributed revision control system instead of RCS on my local box, that sort of thing.

Doing all that takes a certain amount of time, work, and effort, though, and if all I hear is crickets, then I won't bother. :)

Why did I write it?

Mostly 'cause I like the vi input model and because I've used it for circa 18 years.

Other reasons, in varying degrees of usefulness:

Download?

Here.

Documentation?

Here.

ASDF / ASDF-install?

No, just a load file at the moment. Not even a Lispworks project file. See the documentation link above.

Changelog?

Here.

Blog?

You're looking at it. See also here.

Mailing lists?

None yet. If and only if people are interested, I'll set one up here or get a project page at cl.net. For now, post in the forum [update: disabled], reply to the thread on c.l.l, email me at larry at theclapp dot org, or start something on programming.reddit.com. In particular, please do not discuss this on the Lispworks HUG mailing list.

Prominently missing features

I'm most interested in "visual mode", but that requires an Ex mode to do much of anything useful with, and marks and registers too, so it'll have to wait.

Of course the Emacs emulation has a kind of visual mode, and registers, and yank/put, and lots of other stuff, I just haven't wired in the vi-ish analogs.

In part I posted everywhere about it to find out what people would want next.

"What the heck are you talking about?"

If the first you're hearing about this is via a subscription to my blog, I published a very brief notice on the Lispworks HUG and slim-vim mailing lists, and also on comp.lang.lisp and comp.editors.

Posted Mon 19 May 2008 09:07:35 AM EDT Tags: vim mode
lw-vim-mode v7 posted

lw-vim-mode v7 posted here. Changelog:

v7, 5/19/2008

General changes:

Mappings changed:

Mappings added:

Not much to show for 2.5 months work, but on the other hand most of it was actually in the last few weeks. :)

Posted Mon 19 May 2008 08:26:44 AM EDT Tags: vim mode
lw-vim-mode v6 posted

lw-vim-mode v6 posted here. Changelog:

Mappings changed

Mappings Added

Posted Wed 27 Feb 2008 09:21:30 PM EST Tags: vim mode
DSL: cursor movement in Vim-mode

I haven't had a lot of time for blogging lately, what with work and the chorus I'm in and trying to absorb the Getting Things Done methodology that I picked up over New Years. So I thought I'd try to jump back in and talk about a small DSL I wrote for moving the cursor around in Vim-mode.

Vim has many commands for moving the cursor around by what it calls "words" and "WORDS", which I call :words and :bigwords.

According to the Vim help file:

A word consists of a sequence of letters, digits and underscores, or a sequence of other non-blank characters, separated with white space (spaces, tabs, <EOL>). This can be changed with the 'iskeyword' option. An empty line is also considered to be a word.

A WORD consists of a sequence of non-blank characters, separated with white space. An empty line is also considered to be a WORD.

The basic core of this kind of movement is, search (forward or backwards) until you find a character that is (or isn't) whitespace or a keyword.

My approach to coding is to get it to work and then make it pretty, so I wrote functions to a) figure out if the current character is in a certain class (e.g. :whitespace or (:not :keyword :whitespace)), b) search (forward or backwards) for characters in a given list of classes, and c) write direct calls to these to move the cursor around.

The first one looks like this:

(defmethod vim-char-attribute ((type symbol) &optional (point (current-point)))
  (member (next-character point) (gethash type b-vim-char-attributes)))
(defmethod vim-char-attribute ((types list) &optional (point (current-point)))
  (let* ((invert-p (eql (car types) :not))
         (types (if invert-p (cdr types) types)))
    (loop for type in types
          if (vim-char-attribute type point)
          return (not invert-p)
          finally return invert-p)))

the second one looks like this:

(defun vim-find-attribute (forward attributes &optional (point (current-point)))
  (with-point ((started-at point))
    (let ((offset (if forward 1 -1)))
      (loop while (and (not (vim-char-attribute attributes point))
                       (character-offset point offset)))
      (and (point/= started-at point)
           (vim-char-attribute attributes point)))))

and I long since deleted the third one and rewrote it, so I don't have an example of that one. Suffice to say, it was pretty unreadable. In retrospect, it reminds me of what Kent Pitman (I think) has said about printed output in Lisp, before the advent of format -- the stuff you actually wanted to print (or the cursor movements I wanted to make) got lost in the rest of the code.

So once you get that out of the way, you find that the next layer is basically finding boundaries, which means skipping the current kind of character and then leaving the cursor in the right place. You skip the current kind of character by figuring out what kind of character you're currently on (i.e. :whitespace, :keyword, or (:not :whitespace :keyword)), and then moving until you run out of that kind of character.

Vim has several movement commands, both forwards and backwards, that leave the cursor at the beginning or end of the :word (or :bigword).

w                       [count] words forward.

W                       [count] WORDS forward.

e                       Forward to the end of word [count]

E                       Forward to the end of WORD [count]

b                       [count] words backward.

B                       [count] WORDS backward.

ge                      Backward to the end of word [count]

gE                      Backward to the end of WORD [count]

One really interesting part of the process came when I realized that the algorithm for w and ge are the same, just going different directions, and similarly for e and b. So if you're going forward to the front of the next word (w), or you're going backwards to the end of the previous word (ge), you're doing the same thing: find a boundary, and then skip whitespace.

So I wrote a couple of methods to move the cursor by one or more :words, or one or more :bigwords. Here's the former, along with the defgeneric:

(defgeneric vim-offset (n type forward point &key &allow-other-keys))
(defmethod vim-offset (count (type (eql :word)) forward point &key end (word-type :keyword))
  (setf count (or count 1))
  (loop for n below count
        while
        ; This code highlights that e & b are inverses of each other, and
        ; w and ge are inverses of each other.  That is, e & b do the same
        ; things in opposite directions; same for w and ge.
        (with-move (forward point :word-type word-type)
          (cond ((xor forward end)
                 (boundary)
                 (skip :whitespace))
                (t (bump)
                   (skip :whitespace)
                   (boundary :end))))
        finally return (= n count)))

And now we get to the meat of it: with-move. with-move defines a context where you can easily move forwards or backwards, turn around, bump the cursor one character one way or the other, and so on, and not have to keep repeating the movement direction or variable name with the cursor in it.

The core subfunctions are

There are also some auxiliary subfunctions, among them

And one final feature: if any subfunction fails (i.e. you've come to the beginning or end of the buffer), quit immediately. This is acheived by wrapping most movement in a local must macro, that says basically "do all the movement in order; if any fail, exit with-move immediately".

(macrolet ((must (&rest x)
              `(let ((res (and ,@x)))
                 (if res res (return-from with-move nil)))))
  [...])

And finally, here's the macro itself:

(defmacro with-move ((forward point &key (word-type :keyword)) &body body)
  (rebinding (forward point)
    `(block with-move
       (macrolet ((must (&rest x)
                    `(let ((res (and ,@x)))
                       (if res res (return-from with-move nil)))))
         (labels ((set-point (new-point)
                    (setf ,point new-point))
                  (set-direction (new-direction)
                    (setf ,forward new-direction))
                  (go-forward ()
                    (setf ,forward t))
                  (u-turn ()
                    (setf ,forward (not ,forward)))
                  (invert (list)
                    (if (eql (car list) :not)
                      (cdr list)
                      (cons :not list)))
                  (skip-current ()
                    (loop for attrib in (list '(:whitespace)
                                              (list ,word-type)
                                              (list :not :whitespace ,word-type))
                          until
                          (vim-char-attribute attrib ,point)
                          finally return
                          (must (vim-find-attribute ,forward (invert attrib) ,point))))
                  (fix-endp (endp)
                    (if endp
                      (must (character-offset ,point (if ,forward -1 1)))
                      t))
                  (boundary (&optional endp)
                    (must (skip-current)
                          (fix-endp endp)))
                  (skip (&rest attribute)
                    (let ((inverted-attribute (invert attribute)))
                      (or (vim-char-attribute inverted-attribute ,point)
                          (must (vim-find-attribute ,forward inverted-attribute ,point)))))
                  (bump ()
                    (must (character-offset ,point (if ,forward 1 -1))))
                  (unbump ()
                    (must (character-offset ,point (if ,forward -1 1))))
                  (find (attribute &optional endp)
                    (skip (invert attribute))
                    (fix-endp endp)))
           ,@body)))))

I'm not completely happy with this macro. It defines 12 new functions every time you use it. (Lispworks may be smart enough to compile-away any you don't use in any given invocation, but it still bugs me.) I'm thinking about changing most of those local subfunctions to be global functions that take defaulted optional arguments, and changing with-move to just establish a context that sets the defaults once so you can ignore them. I've run out of time just now or I'd try to give an example of what I mean, but that'll have to wait for later.

In the meantime, happy Lisping!

Posted Sat 19 Jan 2008 09:31:25 AM EST Tags: vim mode
Architecture of Vim-mode global, buffer, and window variables

I thought I'd take a few minutes and touch on a piece of my Vim-mode code that I rather like, the specification of Vim variables. (This is all about Vim variables used in the code so far, not about Vim-user-level variables, which I don't even have yet, but if/when I do they may well just use this same framework.) Knowing a little bit about Vim or vi will come in handy in this discussion, but hopefully I can explain most of it.

Background: Vim-mode operations happen in (at least) three contexts: global, buffer, and window. For example, it's a global option whether you want to highlight all the matches of the last thing you searched for. (This corresponds to Vim's hlsearch option.)1 Second, Vim allows you to perform a command, and then a movement, and have the command apply to the moved-over text. To facilitate this, I store the before and after character positions in buffer-local variables -- each buffer has its very own before and after variables. Last, Vim allows you to preceed most commands with a count just by starting to type in some digits, and you can be in the middle of typing a count in one window and doing something else in another window, so I have a window-local flag that tells me whether I should consider a digit as part of a count, or part of some other command. (This is particularly important to process 0 (zero) correctly -- 0 normally moves to the beginning of the line.)

So to store and keep track of all this, I have four things:

The class looks like this, and has a name, a type, a value, an initialization function, and a doc string:

(defclass vim-var ()
  ((name :initarg :name :accessor name-of)
   (var-type :initarg :type :accessor var-type-of)
   (values :initarg :values :accessor values-of)
   (init-func :initarg :init-func :accessor init-func-of)
   (doc :initarg :doc :accessor doc-of)))

The values slot is not the value of the variable, it's actually a hash table storing the value (or values) of the variable in all the appropriate contexts, e.g. for buffer variables, a value for each buffer.

The hash table that stores all the variable records is keyed on the name of the variable and its type. I could just key on name, and look up the type from there, but Vim lets you have same-named user-level variables in different contexts, so I wanted to allow that.

Since each kind of variable (global, buffer, window) needs to know its context (for example a buffer variable needs to know the currently active buffer), first you need to define each kind of variable, and specify how to find out the context. I call the thing that defines the context the "selector", and I store the selector in a hash table keyed on the type.

So accessing a variable is a three step process: given its name and type, look up the vim-var instance that has all the values for variables of that name and type, then look up and call the selector for that type, then use the selector value to look up the actual value of the variable in the current context. Here's the code to do all that:

(defun vim-var-key (name type)
  (cons name type))
(defun vim-var-lookup (name type #| &optional create |#)
  (let* ((key (vim-var-key name type))
         (var (gethash key *vim-vars*)))
    (cond (var var)
          #+nil
          (create (setf (gethash key *vim-vars*)
                        (make-instance 'vim-var :name name :type type)))
          (t (error "vim-var-lookup: vim-var ~S of type ~S not found" name type)))))

(defun vim-var-selector (type)
  (multiple-value-bind (selector present) (gethash type *vim-var-selectors*)
    (if present
      (funcall selector)
      (error "vim-var-selector: unknown selector: ~S" type))))

(defun vim-var (name type)
  (let ((var (vim-var-lookup name type)))
    (if var
      (let ((selector (vim-var-selector type)))
        (multiple-value-bind (selected-val present)
            (gethash selector (values-of var))
          (if present
            selected-val
            (setf (gethash selector (values-of var))
                  (funcall (init-func-of var))))))
      (error "vim-var: vim-var ~S of type ~S not found" name type))))

I also have a function to set a variable, of course, and the defsetf that allows you to use setf instead:

(defun vim-var-set (name type value)
  (setf (gethash (vim-var-selector type)
                 (values-of (vim-var-lookup name type)))
        value))
(defsetf vim-var vim-var-set)

I also define a macro per context that makes it easy to define variables of that type. The definer macro creates an instance of vim-var, puts it in the hash table, and (here's the handy part) creates a symbol macro so access to the variable -- which is actually via a function call -- looks like a normal variable reference.

Last, defining all this by hand would be a drag, so I have a macro that defines a new context, which sets up the selector for the type and defines a macro to define instances of the type:

(defmacro def-vim-var-definer (definer-name type selector)
  `(progn
     (setf (gethash ,type *vim-var-selectors*) (lambda () ,selector))
     (setup-indent ',definer-name 2)
     (defmacro ,definer-name (name init-code &optional (doc "Vim variable"))
       `(progn
          (setf (gethash (vim-var-key ',name ,,type) *vim-vars*)
                (make-instance 'vim-var
                               :name ',name
                               :type ,,type
                               :values (make-hash-table)
                               :init-func (lambda () ,init-code)
                               :doc ,doc))
          (eval-when (:compile-toplevel :load-toplevel :execute)
            (define-symbol-macro ,name
              (vim-var ',name ,,type)))))))

This may be my first macro-defining-macro. :)

Here's the definition of buffer-local variables:

(def-vim-var-definer def-vim-buffer-var :buffer (current-buffer))

This means that to define a buffer-local variable, call def-vim-buffer-var, and to use a buffer-local variable, figure out what the buffer is by calling current-buffer.

Here's the definition of a buffer-local variable:

(def-vim-buffer-var b-vim-point-before-movement (copy-point (current-point))
  "The point before a movement command.")

This defines b-vim-point-before-movement as a buffer-local variable, and says that when you first access b-vim-point-before-movement in a new buffer, use (copy-point (current-point)) to initialize the value for that buffer.


note 1: I don't actually use this option yet, but I will eventually.

Posted Sat 29 Dec 2007 02:03:57 AM EST Tags: architecture vim mode
lw-vim-mode v5 posted

lw-vim-mode v5 posted here. Changelog here.

Posted Sat 29 Dec 2007 01:28:57 AM EST Tags: vim mode
Some things I'd like to do with Lispworks's editor, a scratchpad

Originally posted Saturday, 17-Nov-2007

Posted Wed 26 Dec 2007 04:24:06 PM EST Tags: lispworks vim mode