Vimpulse and vim-mode

Frank Fischer frank.fischer at mathematik.tu-chemnitz.de
Mon Feb 28 21:45:42 CET 2011


On Mon, Feb 28, 2011 at 04:23:21PM +0100, Vegard Øye wrote:
> >> 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.
> >
> > 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.
> 
> No, it is definitely not easy. :) But it might help to extend the
> "inserted text"/"actual keystrokes" split to the changes themselves.
> On one hand, there are "linear changes", which are changes for which
> one doesn't need a keystrokes-based system at all (the insertion-based
> approach of Vim would work equally well). 

Well, but must "linear changes" are very easily covered by the
keystrokes version (e.g., insertion of "abc" is just "abc"). Others
are problematic (note that vim-mode's repeat system is keystroke based
and linear changes have never been problems).

> > 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.
> 
> If it is possible to reliably distinguish linear changes from
> non-linear ones in `after-change-functions', we can choose the
> appropriate representation accordingly. Any invocation of, e.g.,
> `self-insert-command' will be represented as a linear change,
> while anything that doesn't fit the linear mold is represented
> as keystrokes.

See above, self-insert-command is the easy case.
 
> > 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.
> 
> Provided the completion does work linearly -- i.e., it only changes
> the text immediately before point -- the keystrokes shouldn't matter.

Exactly that's the problem. I think of packages like company or
auto-complete. They show up possible completions after a certain
amount of time. Then some popup window occurs and the user can select
a possible completion. Obviously those keystrokes should not be
repeated (without waiting the time until the popup occurs something
totally different would happen and repeating keystrokes is, of course,
far too fast).

I think almost all packages that are direct consequences of keystrokes
can be handled very well (or at least reasonable well) by the
keystrokes approach. The difficult packages are those other packages
like those described above.

Perhaps we should just think of some generic approach, some kind of
programmable repeation. The default would be keystrokes but some
commands or events can trigger a special behaviour. But I have
currently no real idea how this could be done w.r.t. to those nasty
packages.

> > 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.
> 
> I see. We might have use of such a structure when passing values
> around internally, but I think the commands themselves should do
> things "out in the open" -- i.e., the first three arguments should be
> (BEG END &optional TYPE), and maybe a fourth one for the register.
> The define macros should let one specify further arguments, though.

Perhaps the order of arguments depends on the order of the interactive
specification.
 
> > 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.
> 
> Yeah, all of Vimpulse's motions and text objects are implicit. Text
> objects are implemented as selection commands (since in Visual state,
> they /are/ selection commands). I experimented with return values at
> one point, but didn't find much use for it.

Well it worked for me because of the different system how motions and
commands were composed.

Btw, what about commands like , and ;. The do not have a specific
type. Their type may be either explicit or implicit depending on the
previous search command. 

> > 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 ;)
> 
> I can think of two approaches:
> 
>     1. The `with-register' command (bound to ") reads a register from
>        the keyboard, reads an operator, reads a motion (if
>        appropriate), runs the motion to get buffer positions, and
>        passes the positions and the register to the operator.
> 
>     2. The current register is stored in a global variable which is
>        reset in `post-command-hook'. The `with-register' command sets
>        the variable, reads an operator from the keyboard, and executes
>        the operator with `call-interactively'. The operator, as part
>        of its interactive behavior, sets its REGISTER argument to the
>        global variable.

Hm I don't care, I don't think it makes much a difference.

I tell a little bit more about how I handle registers in vim-mode in
my next post, not to convince to use that approach, just to give an
explanation for my silly questions ;)

> Yes. There is one case where I cannot see any other solution than a
> blacklist, though: normalizing Emacs' region to the Visual selection.
> We have to do this /before/ the next command is called (so we can't
> inspect its behavior), and we don't want to do it if the next command
> is a motion. 

I do not understand. You can inspect the command in a pre-command hook
and normalize the region accordingly or what do you mean? Of course,
this only works if the commands use the interactive statement nicely.

> So we can list either motions or non-motions, and
> normalize or don't normalize based on that. I went with listing
> motions because I figured the user is more likely to write a new
> region command than a new motion -- and Vimpulse does, after all,
> supply its own motions. If the documentation can convince the user to
> always use the `define-motion' macro for defining new motions, there
> shouldn't be much of a problem.

I think more of motions already defined by other packages. You're
right that usually people won't define own motions. Assuming commands
are operations by default seems okay to me, vim-mode just used the
other way.

Perhaps we should just give define-motion not only the ability to
define new motions but also the extend old motions with the necessary
meta-data?

> > 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.
> 
> I can't think of any. I think Viper solved this problem well enough.

It also works in vim-mode, just a question ;)
 
> Fair point. I agree that types should only specify a transformation
> and the reverse transformation if possible, plus perhaps a function
> for textual descriptions (for Viper-esque messages like
> "Deleted 2 lines"). A definition of the `inclusive' type could be:
> 
>     (define-type inclusive
>       :expand (lambda (beg end)
>                 (list beg (min (1+ end) (point-max))))
>       :contract (lambda (beg end)
>                   (list beg (max beg (1- end))))
>       :describe (lambda (beg end)
>                   (let ((width (- end beg)))
>                     (format "%s characters" width))))
> 
> The words "expand" and "contract" refer to the fact that Vim's
> "inclusive" selections are larger than Emacs' "exclusive" ones.
> I'd prefer a more general pair of terms, though: maybe
> "transform"/"inverse-transform", or "transform"/"inverse",
> or "normalize"/"reverse", or something. Suggestions?

Expand/contract is okay.

> Oh, and you /can/ step through this thing with Edebug, which is
> actually quite good at handling macros provided one tells it how
> (with `declare'). But then there's cl.el. Vimpulse used cl.el when I
> started out, but after various issues with `destructuring-bind' and
> Edebug (among other considerations), I decided to revert to "vanilla"
> Emacs Lisp. Would this be inconvenient to you? Are there some cl.el
> macros which would be valuable when solving the kinds of problems
> we're discussing here, or can we avoid this dependency?

Well, I never cared about cl dependencies, I often used macros like
when, unless, lexical-let and defun*. Imo cl is not really a bad
dependency at least at compile time.

> > 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.
> 
> Yeah ... hm. Maybe we could invent our own variant of
> `execute-kbd-macro' for testing purposes.

Sounds okay.

> > 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?
> 
> We could always create our own function for error reporting. For
> example, less serious errors could be logged in a hidden buffer
> (starting with a space), whence they're brought forth by calling a
> special command. This could prove useful when helping users: "Please
> do `M-x show-error-log' and post the output". The logging level is
> the same, but the "noise level" is adjustable.

Well, it is not so important where to report the error ... as long as
they are reported. The standard error mechanism of Emacs is okay and
usually error signals should not start the debugger.
 
> > 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).
> 
> Yeah, I experimented a little bit with selecting screen lines in
> Visual state, but called it off because of pasting issues. It might
> work if one had a special yank-handler for such lines.
> 
> > Very good, texinfo allows for nice inline documentation via :help ;)
> 
> Good point. Perhaps :help could open our documentation, while :info
> would go to the main Info index?

Should be no problem.


Bye,
Frank



More information about the implementations-list mailing list