2

So far, I managed to implement a function that adds a delay after "putting" each character contained in a register to the screen:

function! AnimateInsertion() abort
    " split contents of last modified reg in individual chars
    let s:line = split(getreg(v:register), '\zs')
    " iterate through each character
    for s:char in s:line
        sleep 100m
        exe 'normal!a'.s:char
        redraw
    endfor
endfunction

enter image description here

However, this approach treats the contents of the register as a literal string. In other words, this implementation prints an 'i' instead of actually treating it as a command that makes Vim go into insert mode. How could I execute the contents of the register as commands (just like @<reg> does) and add a delay after each command is executed?

UPDATE: I encountered this 3-year-old question yesterday. It asks pretty much the same as this one. Unfortunately nobody has been able to come up with a solution yet.

  • Hard to tell how to modify your code to do what you're asking if you don't show your code... – filbranden Apr 16 at 19:20
  • 1
    Having said that, take a look at feedkeys(). – filbranden Apr 16 at 19:21
  • @filbranden I added the code. I didn't do so initially because I assumed that the solution requires a very different approach. But you are right, for completeness is better to show it. – mroavi Apr 16 at 19:30
  • @filbranden thanks for the reference to the feedkeys() function. It does pretty much what I need. However, I couldn't understand what the term typeahead means. Typing :h typeahead didn't throw any results either. – mroavi Apr 16 at 20:24
  • Typeahead is a queue of keystrokes/characters to process. The main problem I had with feedkeys() is that it enqueues all the commands and executes them once the mapping is finished... You can pass feedkeys() a second argument 'x' or 'x!' to have it consume the key, but that was a problem when the command was a multi-character command (such as dd or y}, etc.) Not sure if splitting the string into multiple separate commands would be enough (I'll try that.) Even then, it's hard to do that programatically from a string... – filbranden Apr 16 at 20:27
2

This is fun:

com -nargs=+ Type call s:type(<q-args>)

let g:type_interval='200ms'

function s:type(cmd) abort

  " spawn a timer_start for every char, the inner lambda call feedkeys with
  " captured current char.
  call map(split(a:cmd, '\zs'),
        \ { i,v -> timer_start( g:type_interval * i, { t -> feedkeys(v) } ) })
endfunction

exe "Type Gohello\<esc>oworld\<esc>ciwvim\<esc>dddd"

source it, it will jump to end of file, typing hello world, then change world to vim, then delete lines one by one. If you type in the middle, you will mess it, I hope that's not a problem.

This works like every character is typed one by one, it doesn't care about mapping delay and keycode delay, something like this will never wrok:

" turn on timeout, make sure timeoutlen is greater than type interval
set timeout
let &timeoutlen=1000
let g:type_interval='100ms'

nnoremap abc dd
Type abc

The a never get it's bc, can't tell why. It also breaks if you use multi bytes keycodes (such as "\<c-right>").

Update to address comment

If you want to place the command in a register and hide the execution part:

let @x = "Type Gohello\<esc>oworld\<esc>ciwvim\<esc>dddd"
nnoremap <silent> <leader>x :exe @x<cr>

To support abort

We can't use command to abort, the animation will mess our typing, we can only use single key map to do it, we need to create map in all mods:

noremap <f4> <c-\><c-n>:call timer_stopall()<cr>
noremap! <f4> <c-\><c-n>:call timer_stopall()<cr>
tnoremap <f4> <c-\><c-n>:call timer_stopall()<cr>

I use timer_stopall() for simplicity here. If you want to be precise, you can record it in the s:type and loop them one by one to stop it.

| improve this answer | |
  • Oh wow this is great. It works just as you describe. My goal is to use this to automate live coding sessions (to avoid typos and speed up the typing). The only thing I'm missing is to be able to this transparently, for example, store Gohello\<esc>oworld\<esc>ciwvim\<esc>dddd in a register and use @ to run it. I tried doing this with your example but the invisible characters don't get correctly interpreted. – mroavi Apr 20 at 9:18
  • @mroavi Could you be specific ? There has no invisible character except esc in the example, I changed it to call from register in the update, it works fine. – dedowsdi Apr 20 at 9:33
  • In your example, to start the animation, I have to type exe @q in the command line. I would like to start the animation just by typing @q (just like a normal macro). – mroavi Apr 20 at 9:44
  • The idea behind this is that I only want students to see the animation and not me starting the animation. Maybe this could be done more easily with a mapping? – mroavi Apr 20 at 9:54
  • @mroavi You can store the :exe ... part in another register, see update for example. map of course works too. – dedowsdi Apr 20 at 10:06

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.