In insert mode, CTRL-P autocompletes a word from earlier in the buffer.

A subsequent CTRL-X CTRL-P will insert the word that followed that word.

canFail :: Either Error Success

canAlsoFail :: Ei█
  • CTRL-P autocompletes "Either"     canAlsoFail :: Either█
  • CTRL-X CTRL-P inserts "Error"       canAlsoFail :: Either Error█
  • CTRL-X CTRL-P inserts "Success" canAlsoFail :: Either Error Success█

When dealing with lines that start differently but end the same (like the above example), it would be convenient to complete the rest of the line with a single keystroke, rather than mashing CTRL-X CTRL-P repeatedly.

I'm going to try to implement this, but I can see it getting hairy in a hurry. Does anyone know of:

  • a plugin that offers this functionality?
  • a built-in feature I've overlooked?
  • a robust approach to implementing this?
    • does Vim expose any data about the location it's completing from?

You could try and find a simpler keysequence. As an example, assuming you don't need C-z to suspend Vim:

ino  <expr><silent>  <c-z>  <sid>easy_c_x_c_p()

fu! s:easy_c_x_c_p() abort
    let cot_save = &cot
    set cot-=noinsert
    call timer_start(0, {-> execute('let &cot = '.string(cot_save))})
    return "\<c-x>\<c-p>"

You could install a submode in which you only need to press C-p:

let g:submode_keep_leaving_key = 1

call submode#enter_with('my-c_x-c_p', 'i', '', '<c-x><c-p>', '<c-x><c-p>')
call submode#map(       'my-c_x-c_p', 'i', '',      '<c-p>', '<c-x><c-p>')

The submode#enter_with() and submode#map() functions are provided by the vim-submode plugin.

You could try a custom mapping which would:

  1. look for the complete word before the cursor
  2. look for the previous line containing this word
  3. look for the text on this line, after this word
  4. insert this text

Something like this (it uses the keysequence C-x C-z):

ino  <expr>  <c-x><c-z>  <sid>complete_until_eol()

fu! s:complete_until_eol() abort
    let pat   = '.*\zs\<\k*\s*\%'.col('.').'c'
    let word  = matchstr(getline('.'), pat)
    let lines = reverse(getline(1, line('.')-1))

    let idx = match(lines, word)
    if idx == -1
        return ''

    let text = matchstr(lines[idx], word.'\zs.*')
    return text

You would need to press C-p or C-x C-p to complete the first word, then C-x C-z to complete until the end of the line.

You could write a custom completion function which would suggest all end of lines starting from the word before the cursor:

ino  <silent>  <c-x><c-z>  <c-r>=<sid>complete_to_eol()<cr>

fu! s:complete_to_eol() abort
    let word = matchstr(getline('.'), '\k\+\%(\s*\)\?\%'.col('.').'c')

    let lines = getline(1,line('.')-1) + getline(line('.')+1, '$')
    call filter(lines, {i,v -> v =~# word})
    call map(lines, {i,v -> matchstr(v, word.'.*$')})

    let from_where = col('.') - len(word)
    if !empty(lines)
        call complete(from_where, lines)
    return ''

I haven't tried it, but you could have a look at the vim-story plugin.

  • 1
    Many good suggestions. I dug around in the help files and edit.c and it doesn't look like the data you'd need to really implement this is exposed. Your suggestions are a good compromise though. I think the submode approach is best, and I'm using submode already for some other stuff, so I'll give it a try.
    – ivan
    Jan 28 '18 at 19:04

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.