10

I'm trying to define a macro which quickly generates a list of random-ish words.
I want the macro to turn this into a list like

a
b
c
d
e

into this

arezzo
bywords
czars
détente
eyrie

What I have so far:

  • put cursor on letter a
  • start recording macro qq
  • a enter insert mode
  • <C-x><C-k> - perform dictionary completion on the character before cursor - in this case the lettera`
  • <C-p> select the last item in the completion list that appears, on my system its the word arezzo. (N.B. I have longest included in my 'completeopt' setting, which is required for <C-p> to work in this way during completion: 'set completeopt=longest,menuone,preview').
  • <C-y> accept the completion
  • ^[^[ exit insert mode
  • j move down to the next line, ready to do the same operation on the next line
  • q stop recording

when I create the macro and go through the steps manually, everything works fine, however when I execute the macro, I see a message flashing in the command line something like:

scanning dictionary file

and nothing happens, of the commands in the macro only j is run, i.e. the cursor moves down the line, but none of the completion commands run.
Is it possible to use insert mode completions in a macro, and if so, how can I do it?

  • I do not understand your <C-p> step... when I do that in my vim, it just gives the message back at original - doesn't select the last item.. I used up arrow key twice to select last word (my case azures), pressed enter and then Esc+j+q .. it then works quite fine on executing macro – Sundeep Jul 26 '16 at 1:28
  • 1
    @spasic ah good point, I should have mentioned, but I'm using set completeopt=longest,menuone,preview its the longest option which causes <C-p> to immediately insert. If I remove longest from the completeopt string, I get the same behaviour as you. Also, yes the arrow keys worked ! thanks (I forgot you can use them to navigate the completion list) – the_velour_fog Jul 26 '16 at 1:41
  • Maybe the popup-menu is interfering. Perhaps try :set completeopt= and a<c-x><c-k><esc>j as your macro. – Antony Jul 26 '16 at 9:26
  • @Antony I'm not sure why the <C-p> wasn't working, but the directional arrow keys worked. – the_velour_fog Jul 26 '16 at 9:32
2

EDIT: Really you are after dictionary completion; you don't necessarily need insert mode (Ctrl + X mode) to do this. As you discovered, it makes working with macros a real pain. The below script can be used to do the dictionary completion without insert mode, so that you can easily repeat it inside of a macro.

Add the below code to your .vimrc file, close and reopen any vim windows to source the new function, and then record yourself alternately pressing <C-k> to do the completion and j to move down to the next line. I added a check for the longest option and if it is disabled, it picks a random entry using system random time. If it finds the longest option, it manually searches for the longest matching entry from the dictionary, and inserts that.

If you don't like the <C-k> mapping, feel free to change it as you see fit. It should work with any mapping you like.

Also, the command to return the expression for the mapping to evaluate adds an \<esc> character to the end so that we leave insert mode automatically after each insertion, and an 0cw at the front to enter insert mode to begin with and make sure we overwrite the entire word correctly. Depending on the exact behavior you want (if your words aren't nicely at the front of each line) you could use bcw instead to find the actual word start. However, then you will see some oddities when going back to words that do begin the line. There's probably a more advanced command to ignore the line ending and do both at once, but I couldn't find one.

map <expr> <C-k> CompleteDictionary()
fun! CompleteDictionary()
    let cword = expand(“<cword>”)
    let res = []
    for m in readfile(&dictionary)
        if m =~ '^' . cword
            call add(res, m)
        endif
    endfor
    if &completeopt =~ 'longest'
        let max = 0
        let maxindex = 0
        let index = 0
        for item in res
            if strlen(item) > max
                let max = strlen(item)
                let maxindex = index
            endif
            let index = index + 1
        endfor
        return "0cw" . res[maxindex] . "\<esc>"
    else
        return "0cw" . res[system('@echo %RANDOM%') % len(res)] . "\<esc>"
    endif
endfun

In addition, it would not be difficult to extend the logic here to create alternate mappings (using, for example, function parameters) that perform any function the insert mode provides. I'll be happy to assist with any such requirements and update this script as I have time.

  • This sounds like a plausible explanation of the issue, but I tried both your suggested solutions, and neither seemed to help. – Rich Nov 13 '17 at 11:41
  • Did you try the sleep command as well? – Tim Nov 13 '17 at 15:33
  • Yes, although only with your suggested value of 5 seconds. Have you managed to get either solution to work? Could you post a complete list of steps? – Rich Nov 13 '17 at 15:44
  • I am not the person who wrote the question. However, I can reproduce the issue: you have to add longest to completeopt as described in the comments (again, not by me). edit: Oops, 'longest' is required for the ctrl-p step: not ctrl-y. Still the steps described by the question-asker work for me in vim -Nu NONE with completeopt edited. – Rich Nov 13 '17 at 20:41
  • I think I've found a solution, give me a few minutes to update my answer with it. – Tim Nov 13 '17 at 20:47

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.