Skip to content

Conversation

@girishji
Copy link
Contributor

@girishji girishji commented Apr 6, 2025

This change adds two new values to the 'complete' ('cpt') option:

  • f – invokes the function specified by the 'completefunc' option
  • f{func} – invokes a specific function {func} (can be a string or Funcref)

These new flags extend keyword completion behavior (e.g., via <C-N> / <C-P>) by allowing function-based sources to participate in standard keyword completion.

Key behaviors:

  • Multiple f{func} values can be specified, and all will be called in order.
  • Functions should follow the interface defined in :help complete-functions.
  • When using f{func}, escaping is required for spaces (with \) and commas (with \\) in Funcref names.
  • If a function sets 'refresh' to 'always', it will be re-invoked on every change to the input text. Otherwise, Vim will attempt to reuse and filter existing matches as the input changes, which matches the default behavior of other completion sources.
  • Matches are inserted at the keyword boundary for consistency with other completion methods.
  • If finding matches is time-consuming, complete_check() can be used to maintain responsiveness.
  • Completion matches are gathered in the sequence defined by the 'cpt' option, preserving source priority.

This feature increases flexibility of standard completion mechanism and may reduce the need for external completion plugins for many users.

Examples:

Complete matches from LSP client. Notice the use of refresh: always and function().

set cpt+=ffunction("g:LspCompletor"\\,\ [5]). # maxitems = 5

def! g:LspCompletor(maxitems: number, findstart: number, base: string): any
    if findstart == 1
        return g:LspOmniFunc(findstart, base)
    endif
    return {words: g:LspOmniFunc(findstart, base)->slice(0, maxitems), refresh: 'always'}
enddef
autocmd VimEnter * g:LspOptionsSet({ autoComplete: false, omniComplete: true })

Complete matches from :iabbrev.

set cpt+=fAbbrevCompletor

def! g:AbbrevCompletor(findstart: number, base: string): any
    if findstart > 0
        var prefix = getline('.')->strpart(0, col('.') - 1)->matchstr('\S\+$')
        if prefix->empty()
            return -2
        endif
        return col('.') - prefix->len() - 1
    endif
    var lines = execute('ia', 'silent!')
    if lines =~? gettext('No abbreviation found')
        return v:none  # Suppresses warning message
    endif
    var items = []
    for line in lines->split("\n")
        var m = line->matchlist('\v^i\s+\zs(\S+)\s+(.*)$')
        if m->len() > 2 && m[1]->stridx(base) == 0
            items->add({ word: m[1], info: m[2], dup: 1 })
        endif
    endfor
    return items->empty() ? v:none :
        items->sort((v1, v2) => v1.word < v2.word ? -1 : v1.word ==# v2.word ? 0 : 1)
enddef

Auto-completion:

Vim's standard completion frequently checks for user input while searching for new matches. It is responsive irrespective of file size. This makes it well-suited for smooth auto-completion. You can try with above examples:

set cot=menuone,popup,noselect inf

autocmd TextChangedI * InsComplete()

def InsComplete()
    if getcharstr(1) == '' && getline('.')->strpart(0, col('.') - 1) =~ '\k$'
        SkipTextChangedIEvent()
        feedkeys("\<c-n>", "n")
    endif
enddef

inoremap <silent> <c-e> <c-r>=<SID>SkipTextChangedIEvent()<cr><c-e>

def SkipTextChangedIEvent(): string
    set eventignore+=TextChangedI  # Suppress next event caused by <c-e> (or <c-n> when no matches found)
    timer_start(1, (_) => {
        set eventignore-=TextChangedI
    })
    return ''
enddef

# Optional
inoremap <silent><expr> <tab> pumvisible() ? "\<c-n>" : "\<tab>"
inoremap <silent><expr> <s-tab> pumvisible() ? "\<c-p>" : "\<s-tab>"

girishji added 4 commits April 6, 2025 10:16
This change adds two new values to the `'complete'` (`'cpt'`) option:
- `f` – invokes the function specified by the `'completefunc'` option
- `f{func}` – invokes a specific function `{func}` (can be a string or
  `Funcref`)

These new flags extend keyword completion behavior (e.g., via `<C-N>` /
`<C-P>`) by allowing function-based sources to participate in standard keyword
completion.

Key behaviors:
- Multiple `f{func}` values can be specified, and all will be called in order.
- Functions should follow the interface defined in `:help complete-functions`.
- When using `f{func}`, escaping is required for spaces (with `\`) and commas
  (with `\\`) in `Funcref` names.
- If a function sets `'refresh'` to `'always'`, it will be re-invoked on every
  change to the input text. Otherwise, Vim will attempt to reuse and filter
  existing matches as the input changes, which matches the default behavior of
  other completion sources.
- Matches are inserted at the keyword boundary for consistency with other
  completion methods.
- If finding matches is time-consuming, `complete_check()` can be used to
  maintain responsiveness.
- Completion matches are gathered in the sequence defined by the `'cpt'`
  option, preserving source priority.

This feature increases flexibility of standard completion mechanism and may
reduce the need for external completion plugins for many users.

M  runtime/doc/insert.txt
M  runtime/doc/options.txt
M  src/insexpand.c
M  src/optionstr.c
M  src/testdir/test_ins_complete.vim
M  src/testdir/test_options.vim
M  src/insexpand.c
M  src/insexpand.c
M  src/insexpand.c
@chrisbra
Copy link
Member

chrisbra commented Apr 7, 2025

Thanks, that sounds like a very interesting enhancement. @glepnir what's your thought about this?

girishji added 4 commits April 7, 2025 20:53
M  src/insexpand.c
M  runtime/doc/options.txt
M  src/insexpand.c
M  src/insexpand.c
M  src/insexpand.c
@glepnir
Copy link
Member

glepnir commented Apr 8, 2025

related.#9005 , adding omnifunc to cpt may be better ? extra flags may add burden. Use the existing omniufunc to extend keyword completion. And neovim can also benefit from it.

Also this is in our todo

-   Add a flag to 'complete' to be able to do omni completion with CTRL-N (and
    mix it with other kinds of completion).

@girishji
Copy link
Contributor Author

girishji commented Apr 8, 2025

What is this MacOS failure about: https://github.com/vim/vim/actions/runs/14326056546/job/40151616766?pr=17065 ?

@glepnir
Copy link
Member

glepnir commented Apr 8, 2025

random failed i have rerun job

@chrisbra
Copy link
Member

chrisbra commented Apr 9, 2025

I think it is fine to use this for the 'complete' option. But how does this handle the return values? From :h complete-functions

 Negative return values:
     -2   To cancel silently and stay in completion mode.
     -3   To cancel silently and leave completion mode.
     Another negative value: completion starts at the cursor column

If there are several f{func} defined, I would expect that at least for the -3 case no other completion function is called?

@girishji
Copy link
Contributor Author

girishji commented Apr 9, 2025

I think it is fine to use this for the 'complete' option. But how does this handle the return values? From :h complete-functions

 Negative return values:
     -2   To cancel silently and stay in completion mode.
     -3   To cancel silently and leave completion mode.
     Another negative value: completion starts at the cursor column

If there are several f{func} defined, I would expect that at least for the -3 case no other completion function is called?

Great question. The behavior follows the documentation: a return value of -2 from the function will cause it to be invoked again during the same session (when the leader changes). A return value of -3 will cause the function to be ignored for the remainder of the session. A negative return value from one function will not affect the behavior of other completion sources. Please review the test Test_cpt_func_refresh_always_fail() in the file test_ins_complete.vim for further clarification.

In the minimal case—when the 'complete' (cpt) option contains only a single function and no other sources—the behavior aligns with what is described in the documentation.

M  runtime/doc/options.txt
…nction

M  runtime/doc/options.txt
M  src/insexpand.c
M  src/optionstr.c
M  src/testdir/test_ins_complete.vim
M  src/testdir/test_options.vim
girishji and others added 3 commits April 11, 2025 08:58
Co-authored-by: Christian Brabandt <cb@256bit.org>
Co-authored-by: Christian Brabandt <cb@256bit.org>
M  src/optionstr.c
@chrisbra chrisbra requested a review from Copilot April 12, 2025 16:11
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot reviewed 3 out of 6 changed files in this pull request and generated 1 comment.

Files not reviewed (3)
  • runtime/doc/insert.txt: Language not supported
  • runtime/doc/options.txt: Language not supported
  • src/testdir/test_options.vim: Language not supported
Comments suppressed due to low confidence (1)

src/optionstr.c:1589

  • [nitpick] The token validation distinguishes between tokens that may have additional characters (e.g. 'f') and those that cannot (e.g. 'o'), but this behavior is not self-documenting. Consider adding a comment to clarify the intent behind the use of different allowed token sets.
if (vim_strchr((char_u *)".wbuksid]tUfo", *buffer) == NULL)

girishji and others added 2 commits April 13, 2025 19:36
Co-authored-by: Christian Brabandt <cb@256bit.org>
Removed static variable, and code associated with it.
Easier to read now. However, function name is not visible
inside callback during debugging.

M  src/insexpand.c
Co-authored-by: zeertzjq <zeertzjq@outlook.com>
girishji and others added 6 commits April 14, 2025 13:42
To increase coverage

M  src/testdir/test_ins_complete.vim
M  src/testdir/test_options.vim
Co-authored-by: glepnir <glephunter@gmail.com>
Co-authored-by: glepnir <glephunter@gmail.com>
Co-authored-by: glepnir <glephunter@gmail.com>
Co-authored-by: glepnir <glephunter@gmail.com>
Co-authored-by: glepnir <glephunter@gmail.com>
@chrisbra
Copy link
Member

thanks

@chrisbra chrisbra closed this in cbe5319 Apr 14, 2025
@girishji
Copy link
Contributor Author

thank you!

@chrisbra
Copy link
Member

Coverity complains:

   static void
6425cpt_compl_refresh(void)
6426{
6427#ifdef FEAT_COMPL_FUNC
6428    char_u      *cpt;
6429    char_u      *p;
       1. var_decl: Declaring variable cb without initializer.
6430    callback_T  *cb;
6431
6432    // Make the completion list linear (non-cyclic)
6433    ins_compl_make_linear();
6434    // Make a copy of 'cpt' in case the buffer gets wiped out
6435    cpt = vim_strsave(curbuf->b_p_cpt);
6436
6437    cpt_value_idx = 0;
       2. Condition *p, taking true branch.
6438    for (p = cpt; *p; cpt_value_idx++)
6439    {
       3. Condition *p == 44, taking true branch.
       5. Condition *p == 44, taking true branch.
       7. Condition *p == 44, taking false branch.
       8. Condition *p == 32, taking true branch.
       10. Condition *p == 44, taking false branch.
       11. Condition *p == 32, taking false branch.
6440        while (*p == ',' || *p == ' ') // Skip delimiters
       4. Jumping back to the beginning of the loop.
       6. Jumping back to the beginning of the loop.
       9. Jumping back to the beginning of the loop.
6441            p++;
6442
       12. Condition cpt_func_refresh_always[cpt_value_idx], taking true branch.
6443        if (cpt_func_refresh_always[cpt_value_idx])
6444        {
       13. Condition *p == 111, taking false branch.
6445            if (*p == 'o')
6446                cb = &curbuf->b_ofu_cb;
       14. Condition *p == 102, taking false branch.
6447            else if (*p == 'f')
6448                cb = (*(p + 1) != ',' && *(p + 1) != NUL)
6449                    ? get_cpt_func_callback(p + 1) : &curbuf->b_cfu_cb;
      
CID 1646489: (#1 of 1): Uninitialized pointer read (UNINIT)
15. uninit_use: Using uninitialized value cb.
6450            if (cb)
6451            {
6452                compl_curr_match = remove_old_matches();
6453                get_cpt_func_completion_matches(cb);
6454            }
6455        }
6456

I suppose we can just initialize cb = NULL?

@girishji
Copy link
Contributor Author

girishji commented Apr 15, 2025

Yes, that should fix it.

@chrisbra
Copy link
Member

fixed in d2079cf

64-bitman pushed a commit to 64-bitman/vim that referenced this pull request Apr 15, 2025
…h 'complete'

Problem:  completion: cannot configure completion functions with
          'complete'
Solution: add support for setting completion functions using the f and o
          flag for 'complete' (Girish Palya)

This change adds two new values to the `'complete'` (`'cpt'`) option:
- `f` – invokes the function specified by the `'completefunc'` option
- `f{func}` – invokes a specific function `{func}` (can be a string or `Funcref`)

These new flags extend keyword completion behavior (e.g., via `<C-N>` /
`<C-P>`) by allowing function-based sources to participate in standard keyword
completion.

**Key behaviors:**

- Multiple `f{func}` values can be specified, and all will be called in order.
- Functions should follow the interface defined in `:help complete-functions`.
- When using `f{func}`, escaping is required for spaces (with `\`) and commas
  (with `\\`) in `Funcref` names.
- If a function sets `'refresh'` to `'always'`, it will be re-invoked on every
  change to the input text. Otherwise, Vim will attempt to reuse and filter
  existing matches as the input changes, which matches the default behavior of
  other completion sources.
- Matches are inserted at the keyword boundary for consistency with other completion methods.
- If finding matches is time-consuming, `complete_check()` can be used to
  maintain responsiveness.
- Completion matches are gathered in the sequence defined by the `'cpt'`
  option, preserving source priority.

This feature increases flexibility of standard completion mechanism and may
reduce the need for external completion plugins for many users.

**Examples:**

Complete matches from [LSP](https://github.com/yegappan/lsp) client. Notice the use of `refresh: always` and `function()`.

```vim
set cpt+=ffunction("g:LspCompletor"\\,\ [5]). # maxitems = 5

def! g:LspCompletor(maxitems: number, findstart: number, base: string): any
    if findstart == 1
        return g:LspOmniFunc(findstart, base)
    endif
    return {words: g:LspOmniFunc(findstart, base)->slice(0, maxitems), refresh: 'always'}
enddef
autocmd VimEnter * g:LspOptionsSet({ autoComplete: false, omniComplete: true })
```

Complete matches from `:iabbrev`.

```vim
set cpt+=fAbbrevCompletor

def! g:AbbrevCompletor(findstart: number, base: string): any
    if findstart > 0
        var prefix = getline('.')->strpart(0, col('.') - 1)->matchstr('\S\+$')
        if prefix->empty()
            return -2
        endif
        return col('.') - prefix->len() - 1
    endif
    var lines = execute('ia', 'silent!')
    if lines =~? gettext('No abbreviation found')
        return v:none  # Suppresses warning message
    endif
    var items = []
    for line in lines->split("\n")
        var m = line->matchlist('\v^i\s+\zs(\S+)\s+(.*)$')
        if m->len() > 2 && m[1]->stridx(base) == 0
            items->add({ word: m[1], info: m[2], dup: 1 })
        endif
    endfor
    return items->empty() ? v:none :
        items->sort((v1, v2) => v1.word < v2.word ? -1 : v1.word ==# v2.word ? 0 : 1)
enddef
```

**Auto-completion:**

Vim's standard completion frequently checks for user input while searching for
new matches. It is responsive irrespective of file size. This makes it
well-suited for smooth auto-completion. You can try with above examples:

```vim
set cot=menuone,popup,noselect inf

autocmd TextChangedI * InsComplete()

def InsComplete()
    if getcharstr(1) == '' && getline('.')->strpart(0, col('.') - 1) =~ '\k$'
        SkipTextChangedIEvent()
        feedkeys("\<c-n>", "n")
    endif
enddef

inoremap <silent> <c-e> <c-r>=<SID>SkipTextChangedIEvent()<cr><c-e>

def SkipTextChangedIEvent(): string
    # Suppress next event caused by <c-e> (or <c-n> when no matches found)
    set eventignore+=TextChangedI
    timer_start(1, (_) => {
        set eventignore-=TextChangedI
    })
    return ''
enddef
```

closes: vim#17065

Co-authored-by: Christian Brabandt <cb@256bit.org>
Co-authored-by: zeertzjq <zeertzjq@outlook.com>
Co-authored-by: glepnir <glephunter@gmail.com>
Signed-off-by: Girish Palya <girishji@gmail.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
@girishji girishji deleted the pr_func_compl branch April 16, 2025 21:13
Yamagi added a commit to Yamagi/vimrc that referenced this pull request May 7, 2025
This will make use of Vims brand new completion functions. This is just
the bare framework without any completor. It triggers on buffer change
events in insert mode. Suggestions can be navigated with <tab>.

Heavily based on the examples in vim/vim#17065
zeertzjq added a commit to zeertzjq/neovim that referenced this pull request May 31, 2025
… with 'complete'

Problem:  completion: cannot configure completion functions with
          'complete'
Solution: add support for setting completion functions using the f and o
          flag for 'complete' (Girish Palya)

This change adds two new values to the `'complete'` (`'cpt'`) option:
- `f` – invokes the function specified by the `'completefunc'` option
- `f{func}` – invokes a specific function `{func}` (can be a string or `Funcref`)

These new flags extend keyword completion behavior (e.g., via `<C-N>` /
`<C-P>`) by allowing function-based sources to participate in standard keyword
completion.

**Key behaviors:**

- Multiple `f{func}` values can be specified, and all will be called in order.
- Functions should follow the interface defined in `:help complete-functions`.
- When using `f{func}`, escaping is required for spaces (with `\`) and commas
  (with `\\`) in `Funcref` names.
- If a function sets `'refresh'` to `'always'`, it will be re-invoked on every
  change to the input text. Otherwise, Vim will attempt to reuse and filter
  existing matches as the input changes, which matches the default behavior of
  other completion sources.
- Matches are inserted at the keyword boundary for consistency with other completion methods.
- If finding matches is time-consuming, `complete_check()` can be used to
  maintain responsiveness.
- Completion matches are gathered in the sequence defined by the `'cpt'`
  option, preserving source priority.

This feature increases flexibility of standard completion mechanism and may
reduce the need for external completion plugins for many users.

**Examples:**

Complete matches from [LSP](https://github.com/yegappan/lsp) client. Notice the use of `refresh: always` and `function()`.

```vim
set cpt+=ffunction("g:LspCompletor"\\,\ [5]). # maxitems = 5

def! g:LspCompletor(maxitems: number, findstart: number, base: string): any
    if findstart == 1
        return g:LspOmniFunc(findstart, base)
    endif
    return {words: g:LspOmniFunc(findstart, base)->slice(0, maxitems), refresh: 'always'}
enddef
autocmd VimEnter * g:LspOptionsSet({ autoComplete: false, omniComplete: true })
```

Complete matches from `:iabbrev`.

```vim
set cpt+=fAbbrevCompletor

def! g:AbbrevCompletor(findstart: number, base: string): any
    if findstart > 0
        var prefix = getline('.')->strpart(0, col('.') - 1)->matchstr('\S\+$')
        if prefix->empty()
            return -2
        endif
        return col('.') - prefix->len() - 1
    endif
    var lines = execute('ia', 'silent!')
    if lines =~? gettext('No abbreviation found')
        return v:none  # Suppresses warning message
    endif
    var items = []
    for line in lines->split("\n")
        var m = line->matchlist('\v^i\s+\zs(\S+)\s+(.*)$')
        if m->len() > 2 && m[1]->stridx(base) == 0
            items->add({ word: m[1], info: m[2], dup: 1 })
        endif
    endfor
    return items->empty() ? v:none :
        items->sort((v1, v2) => v1.word < v2.word ? -1 : v1.word ==# v2.word ? 0 : 1)
enddef
```

**Auto-completion:**

Vim's standard completion frequently checks for user input while searching for
new matches. It is responsive irrespective of file size. This makes it
well-suited for smooth auto-completion. You can try with above examples:

```vim
set cot=menuone,popup,noselect inf

autocmd TextChangedI * InsComplete()

def InsComplete()
    if getcharstr(1) == '' && getline('.')->strpart(0, col('.') - 1) =~ '\k$'
        SkipTextChangedIEvent()
        feedkeys("\<c-n>", "n")
    endif
enddef

inoremap <silent> <c-e> <c-r>=<SID>SkipTextChangedIEvent()<cr><c-e>

def SkipTextChangedIEvent(): string
    # Suppress next event caused by <c-e> (or <c-n> when no matches found)
    set eventignore+=TextChangedI
    timer_start(1, (_) => {
        set eventignore-=TextChangedI
    })
    return ''
enddef
```

closes: vim/vim#17065

vim/vim@cbe5319

Temporarily remove bufname completion with #if 0 to make merging easier.

Co-authored-by: Girish Palya <girishji@gmail.com>
Co-authored-by: Christian Brabandt <cb@256bit.org>
Co-authored-by: glepnir <glephunter@gmail.com>
zeertzjq added a commit to zeertzjq/neovim that referenced this pull request Jun 2, 2025
… with 'complete'

Problem:  completion: cannot configure completion functions with
          'complete'
Solution: add support for setting completion functions using the f and o
          flag for 'complete' (Girish Palya)

This change adds two new values to the `'complete'` (`'cpt'`) option:
- `f` – invokes the function specified by the `'completefunc'` option
- `f{func}` – invokes a specific function `{func}` (can be a string or `Funcref`)

These new flags extend keyword completion behavior (e.g., via `<C-N>` /
`<C-P>`) by allowing function-based sources to participate in standard keyword
completion.

**Key behaviors:**

- Multiple `f{func}` values can be specified, and all will be called in order.
- Functions should follow the interface defined in `:help complete-functions`.
- When using `f{func}`, escaping is required for spaces (with `\`) and commas
  (with `\\`) in `Funcref` names.
- If a function sets `'refresh'` to `'always'`, it will be re-invoked on every
  change to the input text. Otherwise, Vim will attempt to reuse and filter
  existing matches as the input changes, which matches the default behavior of
  other completion sources.
- Matches are inserted at the keyword boundary for consistency with other completion methods.
- If finding matches is time-consuming, `complete_check()` can be used to
  maintain responsiveness.
- Completion matches are gathered in the sequence defined by the `'cpt'`
  option, preserving source priority.

This feature increases flexibility of standard completion mechanism and may
reduce the need for external completion plugins for many users.

**Examples:**

Complete matches from [LSP](https://github.com/yegappan/lsp) client. Notice the use of `refresh: always` and `function()`.

```vim
set cpt+=ffunction("g:LspCompletor"\\,\ [5]). # maxitems = 5

def! g:LspCompletor(maxitems: number, findstart: number, base: string): any
    if findstart == 1
        return g:LspOmniFunc(findstart, base)
    endif
    return {words: g:LspOmniFunc(findstart, base)->slice(0, maxitems), refresh: 'always'}
enddef
autocmd VimEnter * g:LspOptionsSet({ autoComplete: false, omniComplete: true })
```

Complete matches from `:iabbrev`.

```vim
set cpt+=fAbbrevCompletor

def! g:AbbrevCompletor(findstart: number, base: string): any
    if findstart > 0
        var prefix = getline('.')->strpart(0, col('.') - 1)->matchstr('\S\+$')
        if prefix->empty()
            return -2
        endif
        return col('.') - prefix->len() - 1
    endif
    var lines = execute('ia', 'silent!')
    if lines =~? gettext('No abbreviation found')
        return v:none  # Suppresses warning message
    endif
    var items = []
    for line in lines->split("\n")
        var m = line->matchlist('\v^i\s+\zs(\S+)\s+(.*)$')
        if m->len() > 2 && m[1]->stridx(base) == 0
            items->add({ word: m[1], info: m[2], dup: 1 })
        endif
    endfor
    return items->empty() ? v:none :
        items->sort((v1, v2) => v1.word < v2.word ? -1 : v1.word ==# v2.word ? 0 : 1)
enddef
```

**Auto-completion:**

Vim's standard completion frequently checks for user input while searching for
new matches. It is responsive irrespective of file size. This makes it
well-suited for smooth auto-completion. You can try with above examples:

```vim
set cot=menuone,popup,noselect inf

autocmd TextChangedI * InsComplete()

def InsComplete()
    if getcharstr(1) == '' && getline('.')->strpart(0, col('.') - 1) =~ '\k$'
        SkipTextChangedIEvent()
        feedkeys("\<c-n>", "n")
    endif
enddef

inoremap <silent> <c-e> <c-r>=<SID>SkipTextChangedIEvent()<cr><c-e>

def SkipTextChangedIEvent(): string
    # Suppress next event caused by <c-e> (or <c-n> when no matches found)
    set eventignore+=TextChangedI
    timer_start(1, (_) => {
        set eventignore-=TextChangedI
    })
    return ''
enddef
```

closes: vim/vim#17065

vim/vim@cbe5319

Temporarily remove bufname completion with #if 0 to make merging easier.

Co-authored-by: Girish Palya <girishji@gmail.com>
Co-authored-by: Christian Brabandt <cb@256bit.org>
Co-authored-by: glepnir <glephunter@gmail.com>
zeertzjq added a commit to zeertzjq/neovim that referenced this pull request Jun 2, 2025
… with 'complete'

Problem:  completion: cannot configure completion functions with
          'complete'
Solution: add support for setting completion functions using the f and o
          flag for 'complete' (Girish Palya)

This change adds two new values to the `'complete'` (`'cpt'`) option:
- `f` – invokes the function specified by the `'completefunc'` option
- `f{func}` – invokes a specific function `{func}` (can be a string or `Funcref`)

These new flags extend keyword completion behavior (e.g., via `<C-N>` /
`<C-P>`) by allowing function-based sources to participate in standard keyword
completion.

**Key behaviors:**

- Multiple `f{func}` values can be specified, and all will be called in order.
- Functions should follow the interface defined in `:help complete-functions`.
- When using `f{func}`, escaping is required for spaces (with `\`) and commas
  (with `\\`) in `Funcref` names.
- If a function sets `'refresh'` to `'always'`, it will be re-invoked on every
  change to the input text. Otherwise, Vim will attempt to reuse and filter
  existing matches as the input changes, which matches the default behavior of
  other completion sources.
- Matches are inserted at the keyword boundary for consistency with other completion methods.
- If finding matches is time-consuming, `complete_check()` can be used to
  maintain responsiveness.
- Completion matches are gathered in the sequence defined by the `'cpt'`
  option, preserving source priority.

This feature increases flexibility of standard completion mechanism and may
reduce the need for external completion plugins for many users.

**Examples:**

Complete matches from [LSP](https://github.com/yegappan/lsp) client. Notice the use of `refresh: always` and `function()`.

```vim
set cpt+=ffunction("g:LspCompletor"\\,\ [5]). # maxitems = 5

def! g:LspCompletor(maxitems: number, findstart: number, base: string): any
    if findstart == 1
        return g:LspOmniFunc(findstart, base)
    endif
    return {words: g:LspOmniFunc(findstart, base)->slice(0, maxitems), refresh: 'always'}
enddef
autocmd VimEnter * g:LspOptionsSet({ autoComplete: false, omniComplete: true })
```

Complete matches from `:iabbrev`.

```vim
set cpt+=fAbbrevCompletor

def! g:AbbrevCompletor(findstart: number, base: string): any
    if findstart > 0
        var prefix = getline('.')->strpart(0, col('.') - 1)->matchstr('\S\+$')
        if prefix->empty()
            return -2
        endif
        return col('.') - prefix->len() - 1
    endif
    var lines = execute('ia', 'silent!')
    if lines =~? gettext('No abbreviation found')
        return v:none  # Suppresses warning message
    endif
    var items = []
    for line in lines->split("\n")
        var m = line->matchlist('\v^i\s+\zs(\S+)\s+(.*)$')
        if m->len() > 2 && m[1]->stridx(base) == 0
            items->add({ word: m[1], info: m[2], dup: 1 })
        endif
    endfor
    return items->empty() ? v:none :
        items->sort((v1, v2) => v1.word < v2.word ? -1 : v1.word ==# v2.word ? 0 : 1)
enddef
```

**Auto-completion:**

Vim's standard completion frequently checks for user input while searching for
new matches. It is responsive irrespective of file size. This makes it
well-suited for smooth auto-completion. You can try with above examples:

```vim
set cot=menuone,popup,noselect inf

autocmd TextChangedI * InsComplete()

def InsComplete()
    if getcharstr(1) == '' && getline('.')->strpart(0, col('.') - 1) =~ '\k$'
        SkipTextChangedIEvent()
        feedkeys("\<c-n>", "n")
    endif
enddef

inoremap <silent> <c-e> <c-r>=<SID>SkipTextChangedIEvent()<cr><c-e>

def SkipTextChangedIEvent(): string
    # Suppress next event caused by <c-e> (or <c-n> when no matches found)
    set eventignore+=TextChangedI
    timer_start(1, (_) => {
        set eventignore-=TextChangedI
    })
    return ''
enddef
```

closes: vim/vim#17065

vim/vim@cbe5319

Temporarily remove bufname completion with #if 0 to make merging easier.

Co-authored-by: Girish Palya <girishji@gmail.com>
Co-authored-by: Christian Brabandt <cb@256bit.org>
Co-authored-by: glepnir <glephunter@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants