Skip to content

Conversation

@girishji
Copy link
Contributor

@girishji girishji commented May 29, 2025

This change improves the scheduling behavior of async user-defined completion functions (such as F{func}, F, or 'o' values in the 'complete' option), particularly benefiting LSP clients.

Currently, these user functions are invoked twice:

  1. First with findstart = 1 to determine the completion start position.
  2. Then with findstart = 0 to retrieve the actual matches.

Previously, both calls were executed back-to-back. With this change, the first call (findstart = 1) is performed earlier—before any matches are gathered from other sources.

This adjustment gives event-driven completion sources (e.g., LSP clients) more time to send their requests while Vim concurrently collects matches from other sources like the current buffer.

Not sure about the real-world performance gains, but this approach should, in theory, improve responsiveness and reduce latency for asynchronous completions.

Improvement on #17065


To try yegappan LSP client:

set cpt+=o^10
autocmd VimEnter * g:LspOptionsSet({ autoComplete: false, omniComplete: true })

If you prefer to use 'native' auto-completion (without plugins), try the following configuration:

set cot=menuone,popup,noselect,nearest
autocmd TextChangedI * InsComplete()
def InsComplete()
  if getcharstr(1) == '' && getline('.')->strpart(0, col('.') - 1) =~ '\k$'
    SkipTextChangedI()
    feedkeys("\<c-n>", "n")
  endif
enddef
inoremap <silent> <c-e> <c-r>=<SID>SkipTextChangedI()<cr><c-e>
inoremap <silent> <c-y> <c-r>=<SID>SkipTextChangedI()<cr><c-y>
def SkipTextChangedI(): string
  set eventignore+=TextChangedI
  timer_start(1, (_) => {
    set eventignore-=TextChangedI
  })
  return ''
enddef
inoremap <silent><expr> <tab> pumvisible() ? "\<c-n>" : "\<tab>"
inoremap <silent><expr> <s-tab> pumvisible() ? "\<c-p>" : "\<s-tab>"

This change improves the scheduling behavior of async user-defined completion
functions (such as `F{func}`, `F`, or `'o'` values in the `'complete'`
option), particularly benefiting LSP clients.

Currently, these user functions are invoked twice:

1. First with `findstart = 1` to determine the completion start position.
2. Then with `findstart = 0` to retrieve the actual matches.

Previously, both calls were executed back-to-back. With this change, the first
call (`findstart = 1`) is performed earlier—before any matches are gathered
from other sources.

This adjustment gives event-driven completion sources (e.g., LSP clients) more
time to send their requests while Vim concurrently collects matches from other
sources like the current buffer.

Not sure about the real-world performance gains, but this approach should, in
theory, improve responsiveness and reduce latency for asynchronous
completions.
girishji added a commit to girishji/vim that referenced this pull request May 29, 2025
vim#17394

Rebased on vim#17396

M  src/insexpand.c
M  src/testdir/test_ins_complete.vim
@chrisbra
Copy link
Member

chrisbra commented Jun 1, 2025

It seems hard to test this properly. So I'll merge it. Thanks.

@chrisbra chrisbra closed this in 98c29db Jun 1, 2025
zeertzjq added a commit to zeertzjq/neovim that referenced this pull request Jun 2, 2025
Problem:  scheduling of complete function can be improved
Solution: call user completion functions earlier when just determining
          the insertion column (Girish Palya)

This change improves the scheduling behavior of async user-defined
completion functions (such as `F{func}`, `F`, or `'o'` values in the
`'complete'` option), particularly benefiting LSP clients.

Currently, these user functions are invoked twice:

1. First with `findstart = 1` to determine the completion start
   position.
2. Then with `findstart = 0` to retrieve the actual matches.

Previously, both calls were executed back-to-back. With this change, the
first call (`findstart = 1`) is performed earlier—before any matches are
gathered from other sources.

This adjustment gives event-driven completion sources (e.g., LSP
clients) more time to send their requests while Vim concurrently
collects matches from other sources like the current buffer.

Not sure about the real-world performance gains, but this approach
should, in theory, improve responsiveness and reduce latency for
asynchronous completions.

To test, try using yegappan LSP client:

```vim
set cpt+=o^10
autocmd VimEnter * g:LspOptionsSet({ autoComplete: false, omniComplete: true })
```

If you prefer to use 'native' auto-completion (without plugins), try the
following configuration:

```vim
set cot=menuone,popup,noselect,nearest
autocmd TextChangedI * InsComplete()
def InsComplete()
  if getcharstr(1) == '' && getline('.')->strpart(0, col('.') - 1) =~ '\k$'
    SkipTextChangedI()
    feedkeys("\<c-n>", "n")
  endif
enddef
inoremap <silent> <c-e> <c-r>=<SID>SkipTextChangedI()<cr><c-e>
inoremap <silent> <c-y> <c-r>=<SID>SkipTextChangedI()<cr><c-y>
def SkipTextChangedI(): string
  set eventignore+=TextChangedI
  timer_start(1, (_) => {
    set eventignore-=TextChangedI
  })
  return ''
enddef
inoremap <silent><expr> <tab> pumvisible() ? "\<c-n>" : "\<tab>"
inoremap <silent><expr> <s-tab> pumvisible() ? "\<c-p>" : "\<s-tab>"
```

closes: vim/vim#17396

vim/vim@98c29db

Co-authored-by: Girish Palya <girishji@gmail.com>
@girishji
Copy link
Contributor Author

girishji commented Jun 2, 2025

Thanks!

zeertzjq added a commit to zeertzjq/neovim that referenced this pull request Jun 2, 2025
Problem:  scheduling of complete function can be improved
Solution: call user completion functions earlier when just determining
          the insertion column (Girish Palya)

This change improves the scheduling behavior of async user-defined
completion functions (such as `F{func}`, `F`, or `'o'` values in the
`'complete'` option), particularly benefiting LSP clients.

Currently, these user functions are invoked twice:

1. First with `findstart = 1` to determine the completion start
   position.
2. Then with `findstart = 0` to retrieve the actual matches.

Previously, both calls were executed back-to-back. With this change, the
first call (`findstart = 1`) is performed earlier—before any matches are
gathered from other sources.

This adjustment gives event-driven completion sources (e.g., LSP
clients) more time to send their requests while Vim concurrently
collects matches from other sources like the current buffer.

Not sure about the real-world performance gains, but this approach
should, in theory, improve responsiveness and reduce latency for
asynchronous completions.

To test, try using yegappan LSP client:

```vim
set cpt+=o^10
autocmd VimEnter * g:LspOptionsSet({ autoComplete: false, omniComplete: true })
```

If you prefer to use 'native' auto-completion (without plugins), try the
following configuration:

```vim
set cot=menuone,popup,noselect,nearest
autocmd TextChangedI * InsComplete()
def InsComplete()
  if getcharstr(1) == '' && getline('.')->strpart(0, col('.') - 1) =~ '\k$'
    SkipTextChangedI()
    feedkeys("\<c-n>", "n")
  endif
enddef
inoremap <silent> <c-e> <c-r>=<SID>SkipTextChangedI()<cr><c-e>
inoremap <silent> <c-y> <c-r>=<SID>SkipTextChangedI()<cr><c-y>
def SkipTextChangedI(): string
  set eventignore+=TextChangedI
  timer_start(1, (_) => {
    set eventignore-=TextChangedI
  })
  return ''
enddef
inoremap <silent><expr> <tab> pumvisible() ? "\<c-n>" : "\<tab>"
inoremap <silent><expr> <s-tab> pumvisible() ? "\<c-p>" : "\<s-tab>"
```

closes: vim/vim#17396

vim/vim@98c29db

Co-authored-by: Girish Palya <girishji@gmail.com>
@girishji girishji deleted the func_improve branch July 1, 2025 04:15
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.

2 participants