0

I am trying to add autocompletion, with suggestions from the currently edited buffer when reading input from the user for a search string:

function! Testinput()
    return input("Enter search string: ", 'default', 'syntax')
endfun
call Testinput()

In the above snippet, syntax is the {completion} argument, from the help:

  The optional {completion} argument specifies the type of
  completion supported for the input.  Without it completion is
  not performed.  The supported completion types are the same as
  that can be supplied to a user-defined command using the
  "-complete=" argument.  Refer to |:command-completion| for
  more information.  Example: >
      let fname = input("File: ", "", "file")

I looked though the list available :help command-completion, the closest matches I could find are: syntax, tags (error about not having ctags), i.e. neither one worked. I tested it with file and I indeed had file autocompletion.

Surely its quite a common use case to auto complete using the buffere's auto complete (or the text in the buffer)? I tried to figure out what auto complete function is currently in use and could not find it (e.g. omnifunc is set to nothing), and the help shows one how to make an autocomplete function.

2 Answers 2

3

"tags" is probably the closest built-in, if you want to complete project contents, but you'd need a tags file. There doesn't seem to be a method for buffer contents. We can write a custom one:

function! CompleteWords(ArgLead, CmdLine, ...)
  return getline(1, '$')->join(' ')->split('\s\+')
        \ ->filter({_,x->match(x, '^\h\w\+$') > -1})
        \ ->filter({_,x->match(x, '^' . a:CmdLine) > -1})
        \ ->sort()->uniq()
endfunction

call input('file: ', '', 'customlist,CompleteWords')

This function just gets the current buffer's text, and splits it word-by-word, filters to the current entered text, and then unique-sorts.

It is not efficient if your buffer is huge, but for moderate size buffers it should work.

5
  • Thank you for the great answer but it gives me an error: Using a list as a string, and then Invalid expression: getline(..... I modified it below to not use the chaining you have, and then it works. Is that some sort of lambda expression, where its passed in as the first argument to the next item in the chain? If you get your answer working I would like to mark it as the correct solution. Apr 30, 2020 at 12:04
  • You could probably eliminate the second filter() by replacing customlist with custom and adding a ->join("\n") at the end. It could also be more efficient.
    – user938271
    Apr 30, 2020 at 12:52
  • 1
    @run_the_race It's a new syntax construct which makes the code more readable when it contains deeply nested function evaluations. It was first introduced in v8.1.1803 for a few functions, then progressively extended to more (look for more functions can be used as a method in Vim's commit messages). See :h method. No one has ported the patches to Nvim yet.
    – user938271
    Apr 30, 2020 at 12:57
  • 1
    A string expression may also help improve the performance. All in all, the code could be rewritten like this, or like this if (N)Vim doesn't support the method call operator.
    – user938271
    Apr 30, 2020 at 13:07
  • With regards to eliminating the 2nd filter.... "Vim will filter the candidates with its regexp engine after function return, and this is probably more efficient in most cases." Im guessing that is the reason why. Its giving up some control for speed. I quite like knowing it willl show only the result I have calculated, but it is worth testing, thanks! Apr 30, 2020 at 13:08
0

I'm using Neovim if that makes a difference. @Mass's answer did not work for me until I modified it to:

function! CompleteWords(ArgLead, CmdLine, ...)
    let str = getline(1, '$')
    let str  = join(str, ' ')
    let slist = split(str, '\s\+')
    call filter(slist, {_, x -> match(x, '^\h\w\+$') > -1})
    call filter(slist, {_, x -> match(x, '^' . a:CmdLine) > -1})
    call sort(slist)
    call uniq(slist)
    return slist
endfunction
call input('file: ', '', 'customlist,CompleteWords')

Additionally it was not finding some of the matches, so I made it a bit smarter:

function! CompleteFromBufferWords(ArgLead, CmdLine, ...)
    let str = getline(1, '$')
    let str  = join(str, ' ')
    " Split on whitespace and unusual characters (excludes - and _)
    let slist = split(str, '[ \t~!@#$%^&*+=()<>{}[\];:|,.?"\\/'']\+')
    call filter(slist, {_, x -> len(x) > 2 && match(x, '^[a-zA-Z_]\+') > -1})
    " If must start with the word:
    "call filter(slist, {_, x -> match(x, '^' . a:CmdLine) > -1})
    " If must only contain the word:
    call filter(slist, {_, x -> match(x, a:CmdLine) > -1})
    call sort(slist)
    call uniq(slist)
    return slist
endfunction
call input('file: ', '', 'customlist,CompleteFromBufferWords')

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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