Skip to content

Conversation

@girishji
Copy link
Contributor

@girishji girishji commented Apr 7, 2025

Problem: During insert-mode completion, the most relevant match is often the one closest to the cursor—frequently just above the current line. However, both <C-N> and <C-P> tend to rank candidates from the current buffer that appear above the cursor near the bottom of the completion menu, rather than near the top. This ordering can feel unintuitive, especially when noselect is active, as it doesn't prioritize the most contextually relevant suggestions.

Solution: This change introduces a new option value nearest for the 'completeopt' ('cot') setting. When enabled, matches from the current buffer are prioritized based on their proximity to the cursor position, improving the relevance of suggestions during completion.

Key Details:

  • Option: "nearest" added to 'completeopt'
  • Applies to: Matches from the current buffer only
  • Effect: Sorts completion candidates by their distance from the cursor
  • Interaction with other options:
    • Has no effect if the fuzzy option is also present

This feature is helpful especially when working within large buffers where multiple similar matches may exist at different locations.


Edit:

You can test this feature with auto-completion using the snippet below. Try it in a large file like vim/src/insexpand.c, where you'll encounter many potential matches. You'll notice that the popup menu now typically surfaces the most relevant matches—those closest to the cursor—at the top. Sorting by spatial proximity (i.e., contextual relevance) often produces more useful matches than sorting purely by lexical distance ("fuzzy").

Another way to sort matches is by recency, using an LRU (Least Recently Used) cache—essentially ranking candidates based on how recently they were used. However, this is often overkill in practice, as spatial proximity (as provided by the "nearest" option) is usually sufficient to surface the most relevant matches.

set cot=menuone,popup,noselect,nearest inf

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

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>

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

During insert-mode completion, the most relevant match is often the one
closest to the cursor—frequently just above the current line. However, both
`<C-N>` and `<C-P>` tend to place candidates from above the cursor near the
bottom of the completion menu, rather than the top. This ordering can feel
unintuitive, especially when `noselect` is active, as it doesn't prioritize
the most contextually relevant suggestions.

This change introduces a new option value `nearest` for the `'completeopt'`
(`'cot'`) setting. When enabled, matches from the current buffer are
prioritized based on their proximity to the cursor position, improving the
relevance of suggestions during completion.

Key Details:
- Option: "nearest" added to 'completeopt'
- Applies to: Matches from the current buffer only
- Effect: Sorts completion candidates by their distance from the cursor
- Interaction with other options:
  - Has no effect if the `fuzzy` option is also present

This feature allows users to fine-tune the ordering of completion candidates,
especially when working within large buffers where multiple similar matches
may exist at different locations.

M  runtime/doc/options.txt
M  src/insexpand.c
M  src/option.h
M  src/optionstr.c
M  src/testdir/test_ins_complete.vim
@girishji girishji changed the title Add proximity-based sorting of completion matches Feature: Add proximity-based sorting of completion matches Apr 7, 2025
@chrisbra
Copy link
Member

chrisbra commented Apr 7, 2025

Hm, not being a completion user myself much, but I'd think this sorting method could cause quite a bit of confusion about the ordering in the popup menu, no? I don't think I ever had a use case for such sorting.

girishji added 2 commits April 7, 2025 21:00
M  src/insexpand.c
M  src/testdir/test_ins_complete.vim
Copy link
Member

@glepnir glepnir left a comment

Choose a reason for hiding this comment

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

This can be done in a script. Don't make it too bloated here.

@girishji
Copy link
Contributor Author

girishji commented Apr 9, 2025

Hm, not being a completion user myself much, but I'd think this sorting method could cause quite a bit of confusion about the ordering in the popup menu, no? I don't think I ever had a use case for such sorting.

Here’s the rationale: Imagine you’ve just defined a function foo_function(int fbar). In the next line, you want to check fbar with if (fbar .... When attempting to complete fbar, here’s how the scenarios differ:

  • With this change, fbar appears as the second item in the completion menu when using <c-n>, which is quite ideal.
Screenshot 2025-04-09 at 11 47 13 PM
  • Without this change, fbar does not appear when using either <c-n> or <c-p>, which is problematic.
Screenshot 2025-04-09 at 11 46 46 PM Screenshot 2025-04-09 at 11 46 27 PM
  • Even with the "fuzzy" matching option, fbar still does not appear, despite fuzzy reordering of the matches.
Screenshot 2025-04-09 at 11 48 26 PM

This change ensures that fbar is prioritized and visible in the completion menu. Completion is often spatially local, meaning the most relevant matches are typically those closest to the current context, such as nearby variables or parameters.


I used the following settings on vim/src/insexpand.c file above:

set cot=menuone,popup,noselect,nearest inf cpt-=t,i
" vs:
set cot=menuone,popup,noselect inf cpt-=t,i
" vs:
set cot=menuone,popup,noselect,fuzzy inf cpt-=t,i

@glepnir
Copy link
Member

glepnir commented Apr 10, 2025

If you complete fbar with lsp you should have the highest sortText. It is first. I can't reproduce your problem

image

@girishji
Copy link
Contributor Author

girishji commented Apr 10, 2025

If you complete fbar with lsp you should have the highest sortText. It is first. I can't reproduce your problem

What does LSP got anything to do with this PR?

Open vim/src/insexpand.c file and try these settings:

set cot=menuone,popup,noselect,nearest inf cpt-=t,i
" vs:
set cot=menuone,popup,noselect inf cpt-=t,i
" vs:
set cot=menuone,popup,noselect,fuzzy inf cpt-=t,i

@glepnir
Copy link
Member

glepnir commented Apr 10, 2025

? lsp provides sort so why do we need something extra like this? And can't you implement it in your completion plugin even if you don't use lsp? This is not in the planned todo and there is no issue. I don't think this PR brings any obvious benefits.

time complexity of reposition_match is O(n) growth factor of n is the file size and the options that cpt needs to handle. Doesn't it get slower when n is larger? Currently, the efficiency of keyword itself needs to be improved on large files. I don't think it will be more complicated before refactoring.

Copy link
Member

@chrisbra chrisbra left a comment

Choose a reason for hiding this comment

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

okay fair. I have a few minor comments.

@chrisbra chrisbra closed this in b156588 Apr 15, 2025
zeertzjq added a commit to zeertzjq/neovim that referenced this pull request Apr 16, 2025
…ursor

Problem:  During insert-mode completion, the most relevant match is often
          the one closest to the cursor—frequently just above the current line.
          However, both `<C-N>` and `<C-P>` tend to rank candidates from the
          current buffer that appear above the cursor near the bottom of the
          completion menu, rather than near the top. This ordering can feel
          unintuitive, especially when `noselect` is active, as it doesn't
          prioritize the most contextually relevant suggestions.

Solution: This change introduces a new sub-option value "nearest" for the
          'completeopt' setting. When enabled, matches from the current buffer
          are prioritized based on their proximity to the cursor position,
          improving the relevance of suggestions during completion
          (Girish Palya).

Key Details:
- Option: "nearest" added to 'completeopt'
- Applies to: Matches from the current buffer only
- Effect: Sorts completion candidates by their distance from the cursor
- Interaction with other options:
  - Has no effect if the `fuzzy` option is also present

This feature is helpful especially when working within large buffers where
multiple similar matches may exist at different locations.

You can test this feature with auto-completion using the snippet below. Try it
in a large file like `vim/src/insexpand.c`, where you'll encounter many
potential matches. You'll notice that the popup menu now typically surfaces the
most relevant matches—those closest to the cursor—at the top. Sorting by
spatial proximity (i.e., contextual relevance) often produces more useful
matches than sorting purely by lexical distance ("fuzzy").

Another way to sort matches is by recency, using an LRU (Least Recently Used)
cache—essentially ranking candidates based on how recently they were used.
However, this is often overkill in practice, as spatial proximity (as provided
by the "nearest" option) is usually sufficient to surface the most relevant
matches.

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

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

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>

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

closes: vim/vim#17076

vim/vim@b156588

Co-authored-by: Girish Palya <girishji@gmail.com>
zeertzjq added a commit to neovim/neovim that referenced this pull request Apr 16, 2025
…ursor (#33491)

Problem:  During insert-mode completion, the most relevant match is often
          the one closest to the cursor—frequently just above the current line.
          However, both `<C-N>` and `<C-P>` tend to rank candidates from the
          current buffer that appear above the cursor near the bottom of the
          completion menu, rather than near the top. This ordering can feel
          unintuitive, especially when `noselect` is active, as it doesn't
          prioritize the most contextually relevant suggestions.

Solution: This change introduces a new sub-option value "nearest" for the
          'completeopt' setting. When enabled, matches from the current buffer
          are prioritized based on their proximity to the cursor position,
          improving the relevance of suggestions during completion
          (Girish Palya).

Key Details:
- Option: "nearest" added to 'completeopt'
- Applies to: Matches from the current buffer only
- Effect: Sorts completion candidates by their distance from the cursor
- Interaction with other options:
  - Has no effect if the `fuzzy` option is also present

This feature is helpful especially when working within large buffers where
multiple similar matches may exist at different locations.

You can test this feature with auto-completion using the snippet below. Try it
in a large file like `vim/src/insexpand.c`, where you'll encounter many
potential matches. You'll notice that the popup menu now typically surfaces the
most relevant matches—those closest to the cursor—at the top. Sorting by
spatial proximity (i.e., contextual relevance) often produces more useful
matches than sorting purely by lexical distance ("fuzzy").

Another way to sort matches is by recency, using an LRU (Least Recently Used)
cache—essentially ranking candidates based on how recently they were used.
However, this is often overkill in practice, as spatial proximity (as provided
by the "nearest" option) is usually sufficient to surface the most relevant
matches.

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

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

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>

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

closes: vim/vim#17076

vim/vim@b156588

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

glepnir commented Apr 16, 2025

Why can't we add a fractional factor for the visible area? Would this also affect the sorting when fuzzy? merger was too hasty.

@girishji girishji deleted the pr_nearest branch April 16, 2025 21:13
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.

3 participants