Vimpulse and vim-mode
Frank Fischer
frank.fischer at mathematik.tu-chemnitz.de
Mon Feb 28 10:02:15 CET 2011
Am Samstag, 26. Februar 2011 schrieb Vegard Øye:
> On 2011-02-25 22:20 +0100, Frank Fischer wrote:
> > Gitorious should be okay.
>
> Then Gitorious it is! :)
yuk ;)
> A big benefit of recording the keys is that it removes the need for
> repeat code in the commands themselves. For example, I have written
> some custom insertion commands for dealing with Lisp code (e.g.,
> "Insert after S-exp"); if I want them to repeat correctly under
> Viper, I have to edit the repeat history from within the commands,
> which is /extremely/ cumbersome. By contrast, a repeat system which
> monitors keystrokes and the current state would simply take note that
> I have entered Insert state, and update the repeat history
> accordingly.
>
> As you point out, some keystrokes are troublesome: "M-/", which calls
> `dabbrev-expand', may expand to different words in different places
> in the buffer. One solution may be to define the repeat history as a
> collection of keystrokes and buffer insertions, e.g.,
>
> ([a b c] "inserted text" [d e])
>
> Most commands are remembered as keystrokes (vectors), but certain
> ones, like `dabbrev-expand', are remembered as insertions (strings)
> instead. The repeat system would use a "blacklist" of unsafe commands
> like `dabbrev-expand'; if the current command is listed, the system
> creates a marker at point (in `pre-command-hook'), lets the command
> execute, and then (in `post-command-hook') extracts the text between
> point and the marker, which goes into the repeat history like above.
I wonder if it is that easy. What kind of repeation is intended?
Sometimes you want to repeat the inserted text, but another person
wants to repeat the expansion context sensitive. I really have to think
about a good compromise (of course dabbrev-expand may yield different
results - but are they intendend? does this happend regularly or is it
just a rare case?).
Some further comments. First using markers does not work in all cases,
only if point is placed right after the inserted text. Other insertion
packages may place point, e.g., somewhere in middle of the inserted
text. Propably it's better to use after-change-functions. Second in
general I do not like blacklists. The reason is that there are many
Emacs packages out there each may define its own commands. Furthermore
each new Emacs version could introduce new commands and we have to keep
the blacklist up-to-date. Of course, each user can modify this list and
adjust it to the packages she uses. But I think this may be cumbersome
for the average user. And sometimes it may be quite difficult to find
out which key-sequence to drop - I thing of auto-completion packages
where during completion the user executes several commands and only few
of them (the final RET?) really inserts some text, and this completions
are often triggered by a timer, not by an explicit command.
I would prefer some good default rule which works in most cases
(blacklists are okay for rare cases). This does not mean I have a good
solution for these problems just a few things to consider (and I often
run into the auto-completion problem in programming, so this is not a
rare case).
> > - undo, vim usually undos everything done in insert-mode at once
> > while emacs undos insertion in steps.
>
> This is not that difficult in itself, but becomes more complex when
> loading a custom undo package like undo-tree.el. (I would really like
> to use undo-tree.el in place of redo.el; it features a graphical
> interface which totally outclasses Vim's :undolist.)
I never used :undolist so I can't say anything useful to it. For me redo
has always been okay, so this is up to you ;)
>
> There is a case for making Vim-like "bulk undoing" toggleable:
> Emacs' fine-grained undoing can be valuable in some circumstances.
> The default should be Vim-like behavior, of course.
>
> > - catching of commands and motions. In each state the execution of
> > commands and motions has different effects and therefore some
> > special function has to be executed in each mode to deal with
> > commands and motions. In general I see two possibilities how this
> > special function can be executed. Either one hooks into pre-
> > and/or post-command hooks and checks whether the command/motion
> > about to be executed is a Vim operator and in this case do whatever
> > is required according to the current state. Or each command and
> > motion is defined in a special way in order to call the state
> > specific handler when it is executed. vim-mode uses the latter one
> > because I try to avoid pre- and post-command hooks if possible.
>
> Ah, the relationship between operators and motions. This is an area
> where Vimpulse and vim-mode differ somewhat. In vim-mode, as I
> understand it, motions are passed to operators by parameter (or the
> operator reads a motion from the keyboard when called interactively).
> The operator executes the received motion to determine what text to
> act on. The resulting pair of buffer positions is used internally.
That's wrong. Vim-mode almost behaves like what described below. Perhaps
it's time to tell you how the things work in vim-mode (and I think this
is not too bad, but certainly can be improved).
Motions in vim-mode are represented by the vim:motion struct. This
struct contains the two buffer positions along with the intended motion
type (inclusive, exclusive, linewise, block). It is this data-structure
that is passed to operators. So the single motion argument for
operators in vim-mode is rather equivalent to the two parameters BEG
END in vimpulse plus the type of the motion. Usually operators use the
motion type to behave differently (but this is not required). If we had
CLOS-like methods one would propably dispatch on the motion type.
A motion in vim-mode is function that returns a vim:motion structure.
This can be done either implicit or explicit. Explicit means that the
function body really creates the structure and returns it. This is
usually the case for text-objects (they are just usual motions in
vim-mode) or some special commands like , or ; which return motions of
different types depending on the previous f F t T command. Implicit
means that the function does not return an explicit vim:motion
structure. In this case it is enough to move point. Then the magic
within vim:defmotion ensures that a vim:motion structure is
automatically created depending on the default type of the motion and
the movement of point. Implicit is therefore enough for almost all
simple motions what makes them one-liners. Note that from the caller's
point of view it makes no different to call an implicit or explicit
returning motion - the caller always receives the vim:motion structure.
When executing an operator the operator is not executed immediately.
Instead the execution of its body is postponed until a motion has been
selected. When the motion has been selected, the handler of
operator-pending-mode first executes the motion to create the
vim:motion structure and then passes this structure to the operator
which is executed afterwards. In visual-mode the vim:motion structure
is not created by a motion but w.r.t. to the current visual selection.
All in all, the representation of a motion is very simple, just three
values, BEG END and TYPE.
I decided to use keyword arguments because in vim-mode commands and
motions may take a different set of arguments. E.g., a command may take
- count
- motion
- register
- additional argument (char, text, file, buffer, ...) whereas the
latter are only usefull for ex-commands
- force (only for ex)
Also note that in vim-mode each command can be bound in ex-mode AND
normal-mode/visual-mode. In my philosophy ex-mode is just another mode
with own keybindings, although they are read in a different way.
> In Vimpulse, the buffer positions /are/ the parameters (BEG and END).
> Operators work like region commands (i.e., (interactive "r")) in that
> they either receive the positions from the caller, or figure them out
> for themselves when called interactively. (Vimpulse also possesses
> some facilities for "passing a motion to an operator", which executes
> the specified motion and feeds its range to the operator. This is
> only used by the repeat system, though, and it would be totally
> superfluous if a keystroke-based system was in place.)
I really like the idea of making operators as close to usual emacs
commands as possible. This is certainly possible for character motions,
but how do you tell which kind of motion the region belongs to? Call
different commands depending on the type? Modify point and mark? The
latter is possible for character and linewise motions, but what about
block? Furthermore modifying point and mark may be problematic. Several
commands have rules where to place point after execution depending on
the original position of point (before modification to respect the
visual region).
I can think of defining operators with arguments
(BEG END &optional (type 'char) register)
and an apropriate interactive specification. This would enable them
to behave like normal region-operations but gives the additional
possibilities to pass further important information. But then there is
the problem how to pass the additional arguments interactively. But it
sounds as if you have a good idea how to do this ;)
> For the sake of reusability, I recommend defining operator commands
> in the same way as region commands: with two parameters, BEG and END,
> and an `interactive' specification for determining their values by
> reading a motion from the keyboard.
>
> > - What to do with non-vim commands and motions? There should be a
> > simple rule so they work with vim in most cases as expected, but
> > if no meta-information is provided a non-vim command may always be
> > problematic (e.g., in operator-pending mode).
>
> Well, we will likely have one macro for defining operators, and
> another for defining motions. Both can be set up to index all defined
> operators/motions for later lookup, which will give us some
> information to go on. By the way, I also think that all of Emacs'
> standard movement commands should be regarded as motions.
As above, this would require a blacklist and have to keep the list
up-to-date. And other packages may introduce new motions. So in all
cases we need very good defaults so noone has to modify that list in
almost all cases.
> We thus get a large list of commands which move the cursor, and can
> restrict Operator-Pending state to those commands. Alternatively, we
> can maintain a "blacklist" of Emacs commands which should /not/ be
> executed. In any case, I don't think it's overly problematic if a
> nonsensical command should get called; any damage will be undoable,
> after all. It's probably better to err on the side of allowing too
> many commands than too few.
>
> > - What to do with commands that change the current buffer? This is
> > problematic if some buffer-local variables are involved.
>
> Right, which means that the repeat system cannot be buffer-local.
> I think it would just be confusing if it was, anyway.
Of course it is not, but there may be other buffer-local variables. In
vim-mode a tackle this problem by saving the new-buffer when a command
returns, switching back to the original buffer, do the necessary work
and switch to new buffer at the end. This seems to work quite well but
can certainly be improved.
> > - How to switch states and how to represent states (usually as
> > minor-modes, perhaps with some further meta-data like cursor,
> > info-message, ...)
>
> We'll have a macro for defining those too, of course. :)
>
> I suggest that each state's toggle function be a command; e.g., in
> Vimpulse's development code, vi state defines the command
> `vimpulse-vi-state' for entering that state, emacs state defines
> `vimpulse-emacs-state', and so on. The command also has an optional
> argument which, if negative, disables the state; this is used
> internally when switching from one state to another.
Should we use standard minor-modes for this? I do this in vim-mode and
it seems to be okay, but I wonder if there are any downsides.
> > - What is a motion? There are a few fundamental differences between
> > buffer-coordinates of Vim and Emacs. Vim known line/column, Emacs
> > knows just the offset. The choice how to represent coordinates
> > can be crucial. And of course there is that funny newline
> > character, which is usually invisible in Vim except for empty
> > lines. Because a motion is a important concept for many parts
> > including operators and visual-mode.
>
> I think all of Emacs' regular movement commands should be valid
> motions. Furthermore, all of Emacs' selection commands should be
> valid text objects. In Vimpulse, "y C-x h" and "C-x h y" are equally
> valid ways of copying the whole buffer, and "d M-e" works just as
> well for deleting a sentence as does "d)".
Hm I think d M-e is okay besides the fact that a sentence is slightly
different in Emacs and Vim (I think), but never mind. The first example
seems to be very special case. But if I understand correctly you just
want a function which changes point and mark and toggle
transient-mark-mode to behave like a text-object ... or to enable
visual-mode. Sounds reasonable to me although I'm always afraid of
compatibility issue between Emacs regions and visual-regions. Is it
easy to detect that a certain command behaves like this or is it
another blacklist variant? ;)
> Given the above, the only thing which separates Emacs' movement
> commands from the other motions is their lack of a /type/. For
> example, the motion "j" has a type of `line', which means that "dj"
> deletes two whole lines. What a type is, then, is a way to transform
> two buffer positions to two other positions -- in this case, to two
> whole lines.
>
> > Probably a motion is represented by some abstract data
> > type and this data-type is an important part of the interface a
> > user must understand in order to write new motions and commands.
> > Therefore it should be well-defined from the beginning because it
> > cannot be changed easily afterwards.
>
> I don't think it needs to be very abstract. I would rather try to
> keep things from getting too abstract, actually. One of the design
> rules I laid down in Vimpulse's new "TODO" list was that it should
> require as few packages as possible, and not invent new stuff unless
> it's absolutely necessary.
I think we both have the same feeling of it (see above). In fact,
vim:motion in vim-mode comes with a couple of functions which do
exactly this (returning appropriate buffer positions w.r.t. the motion
type).
> The way I understand motions, they are just regular movement command
> plus a type. The type can be stored as a symbol property, e.g.,
> (put 'motion 'type 'line). The type itself, of course, must also be
> defined; for this, a minimal system may be warranted, since both
> Operator-Pending state and Visual state use types. I've pushed a
> currently unused "type system" to "development":
>
> http://gitorious.org/vimpulse/vimpulse/blobs/development/vimpulse-typ
>es.el
>
> Now, it's a bit more involved than it has to be; the routines for
> reselecting (e.g., two lines anywhere in the buffer) are unnecessary
> given a keystroke-based repeat system. The advantages of a systematic
> approach is that it can be reused by Operator-Pending state and
> Visual state, making the overall code simpler. The only heavy lifting
> Visual state ends up doing (once initialized) is highlight the
> different types, which is rather trivial (with the exception of
> `block' ...).
>
> > In fact, there are only two (or three) difficult commands: yank,
> > delete and paste, but I hope the implementation in vim-mode is
> > quite good know (it uses yank-handlers for all three kinds of
> > yanking and also provides "yank-pop" stuff).
>
> Yes, this is exactly the right way to do it. I think the actual yank
> handlers should be specified by the type system, so that `line' has
> one handler, `block' has another, and so on. That way, we avoid
> hard-coding these associations into the "yank" command.
At this point I completely disaggree. I don't like the idea of coding
behaviours of yank/delete and so on into the type system for two
reasons. First of all, yank/delete should not be different from any
other command. A special handling of them in the type system seems to
be confusing to me. The other reason is that there may be even more
commands that have different behaviour depending on the motion type,
not just yank/delete, even commands that we cannot think of because we
want *extensibility*.
I prefer a type to be as simple as possible and to have as less
additional functions and responsibilities as possible. The type should
deal with its buffer positions. Not more. Anything else like handling
of the visual-region or commands like yank/delete do not belong here.
I think the main difference here is that you want to stick with the
standard interface of Emacs for operators (only BEG END) while I want
to provide additional information (especially motion type). So in my
world it would be the responsibility of the command to behave
differently on different motion types. Of course then your commands
need to be sensitive to the motion types - but you have to deal with
this sensitivity anyway, nothing prevents you from that. The question
is where.
Of course it would be nice if operators also behave like usual Emacs
commands when used in such a context. This was the reason of my
suggestion above (BEG END &optional type ...).
In fact, what I would need is a dispatch on the motion type, i.e., (yank
beg end 'char) calls another function than (yank beg end 'line). I do
not know of any such functionality in Emacs so propably it ends up with
manual dispatch (case type (line (yank-line ...)))) and so on. I do not
think this is too bad and certainly not more complicate than you
suggestion. But I think it provides a better separation of code.
Note that it is okay for me to have certain functions with update visual
region according to a motion type. But since visual-mode is the only
place where this is required it should go there. Of course in a
structured way.
> > What could be useful are frameworks or perhaps only some functions
> > which help defining more involved motions like text-objects. We
> > should provide helper-functions for typical text-objects like
> > parentheses, blocks, whatever. vim-mode (and vimpulse, too)
> > contains a few of those functions which are again independent.
>
> Agreed; we should have a macro for defining text objects, too.
>
> To recap, we now have the following define macros:
>
> * `define-state'
> * `define-type'
> * `define-motion'
> * `define-text-object'
> * `define-operator'
>
> Do we need more than these?
Well there are also other commands that are not operators. E.g. paste
requires at least two arguments, the count and the register. Other
commands require additional character arguments like "r" or motions
like "f". Of course those commands can read that character themselves.
But I prefer passing the character as an argument because then this
command can also be used non-interactively (useful, e.g., for commands
like , and ;).
> >> http://gitorious.org/vimpulse/vimpulse/blobs/development/vimpulse-
> >>states.el
> >
> > I read your comments and they sound reasonable. Adding auxiliary
> > keymaps should not be that difficult. As I remember correctly,
> > local keymaps were the biggest problem in vim-mode, but major and
> > minor-mode specific should be relatively easy. Of course this is
> > very important and should be done as one of the first things.
>
> Actually, the current code already implements auxiliary keymaps,
> although I've not tested it thoroughly.
>
> > Note that vim-mode arranges keymaps in another hierarchy, too,
> > which allows to enable only few keybindings in certain major modes,
> > e.g., only movement and window-commands in info-mode (somehow like
> > viper-on-more-modes I think). The difference is that many
> > standard-keybindings are not available in this case, e.g.,
> > insert-mode.
>
> Yes, this is very sensible. Motions and text objects can be bound in
> their own keymap (or state), and that keymap is inherited by vi
> state, Visual state, and so on. Modes which do not work well in vi
> state, can come up in the motion state instead. Any shortcomings can
> be alleviated on a per-mode basis with mode-specific state bindings.
>
> >> Regarding test frameworks, I hear Christian Ohler's ert.el is now
> >> included with Emacs trunk, so we could go with that:
> >
> > I never used any of those libraries but we should try. The main
> > problem is to cover all those funny special cases that arise (what
> > happens at the end of the buffer, on empty lines, ...) - most bugs
> > found in vim-mode are of this kind.
>
> This can be tested by executing a keyboard macro in a temporary
> buffer. A lot of Vimpulse's early tests do this, e.g.,
>
> (deftest test-change-undo
> "Change a word and undo."
> (execute-kbd-macro "wcwfoo^[u")
> (assert-string=
> (buffer-substring 1 51)
> ";; This buffer is for notes you don't want to save"))
Note that executing a keyboard macro makes testing certain aspects very
difficult, namely those that behave differently when executed in a
keyboard-macro or not. I think of the repeating system which must take
care of keyboard-macros.
> > This is another point which is currently very bad in vim-mode. How
> > to deal with errors, should there be a unique signalling scheme,
> > where to handle them, ...
>
> In "indirect" cases, like when executing a motion to get a text
> range, I think any errors should be suppressed. When executing the
> motions by themselves, however, we should signal any problems with
> `error'.
I think errors should never be suppressed. The operator should just be
canceled but the error should be shown. It is always dangerous to
suppress errors (why does this not work? There is no error, right?)
Note that there are other error-symbols in use like end-of-buffer and
the like. I think we should use them whenever possible. But should we
use special error-symbols for errors that occur in our code?
> > Btw, Emacs 23 contains visual-line-mode which is quite useful. But
> > most commands work on buffer lines not on screen lines. Should we
> > try to support visual-lines, too (optionally)? If yes this has to
> > be considered for motions and commands, especially for
> > operator-stuff, too, and we should think of it from the beginning,
> > because many commands have to be implemented differently for
> > visual-lines.
>
> Yes, we should definitely support screen lines; I have "j" and "k"
> bound to `next-line' and `previous-line' because they work better in
> visual-line-mode. In Vim, one uses "gj" and "gk" to move by screen
> lines. I think this is very inefficient and would prefer an option
> for the regular commands. It may of course be disabled by default.
Yes it is definitely a difference for commands to work on screen-lines
or visual-lines. Note that deleting a visual line may have funny
results because the other lines may change a little bit, too (long
words deleted which caused line-breaks).
Furthermore block-motions do not really make sense for visual lines. I
would definitely not use visual-line movements as defaults but document
very well how it can be configured, because dealing with visual lines
(as a user) may be very inconsistend.
> > Another thing somehow related to tests is documentation. We should
> > not only write developer-documentation but also user-documentation
> > right from the start. This should contain instructions for all
> > important concepts and also examples for new motions and commands
> > of all kind. Where should we put this documentation? EmacsWiki?
> > Hosting site? Perhaps as tex-info?
>
> Texinfo, definitely. I've had my share of arbitrary wiki syntax and
> never-ending comment blocks. A text file under version control is
> wiki-like enough for me.
Very good, texinfo allows for nice inline documentation via :help ;)
> > Hm, I think a new cool name would be good. I prefer short names or
> > at least names that allow short prefixes for function names ;)
>
> For the sake of brainstorming, I've grepped through /usr/share/dict
> for anything containing "vi". There isn't much, unfortunately, so
> I'll start with some acronyms comprising three letters:
>
> * avi, evi (augmented/extensible vi)
> * via, vie (vi augmented/extended)
> * vil (vi layer)
> * xvi (vi plus a cool letter)
> * yvi (yet a(nother) vi)
>
> Things get slightly more descriptive when we allow four letters:
>
> * evil (extensible vi layer; this almost works too well)
> * nuvi (new (unlimited) vi)
> * vibe (vi is beautiful?)
> * vici (veni, vidi ...)
> * vimu (vi(m) emulation; vim unlimited; vi made usable)
> * vini (vi new and improved)
> * viva (viva la vi!)
> * yavi (yet another vi)
>
"evil" really gave me a smile ;)
I thought of something like "VeM" but have no good idea for its
interpretation. "vimu" sounds good, too.
Bye,
Frank
More information about the implementations-list
mailing list