[vim/vim] `:help popup-examples` needs more interesting examples (Issue #10440)

79 views
Skip to first unread message

lacygoill

unread,
May 17, 2022, 10:49:30 PM5/17/22
to vim/vim, Subscribed

There is a TODO item at :help popup-examples:

TODO: more interesting examples

How about adding an example to document how one could implement a simple fuzzy file selector?

diff --git a/runtime/doc/popup.txt b/runtime/doc/popup.txt
index 51413e796..63d080c8a 100644
--- a/runtime/doc/popup.txt
+++ b/runtime/doc/popup.txt
@@ -1091,5 +1091,65 @@ this example simulated with a timer callback: >
 	  let s:winid = popup_beval(s:balloonText, #{mousemoved: 'word'})
 	endfunc
 <
+					*popup_fuzzy_select-example*
+Let the user select a file to edit, by prompting for a pattern matched against
+the paths in `v:oldfiles` with a fuzzy algorithm: >
 
+	vim9script
+
+	def Callback(winid: number, choice: number)
+	  if choice == -1
+	    return
+	  endif
+	  execute $'split {winid->winbufnr()->getbufline(1, '$')->get(choice - 1, '')}'
+	enddef
+
+	var win: number = v:oldfiles->popup_create({
+	  border: [],
+	  callback: Callback,
+	  cursorline: true,
+	  minheight: 10, maxheight: 10,
+	  minwidth: 80, maxwidth: 80,
+	  scrollbar: false,
+	})
+	redraw
+
+	augroup FuzzyOldfiles | autocmd!
+	  autocmd CmdlineChanged @ MatchFuzzy(win)
+	  autocmd CmdlineLeave @ TearDown()
+	augroup END
+
+	prop_type_add('FuzzyOldfiles', {bufnr: win->winbufnr(), highlight: 'WarningMsg'})
+	def MatchFuzzy(winid: number)
+	  var look_for: string = getcmdline()
+	  if look_for == ''
+	    popup_settext(winid, v:oldfiles) | redraw
+	    return
+	  endif
+	  var matches: list<list<any>> = v:oldfiles->matchfuzzypos(look_for)
+	  var pos: list<list<number>> = matches->get(1, [])
+	  var text: list<dict<any>> = matches->get(0, [])
+	    ->map((i: number, match: string) => ({
+	      text: match,
+	      props: pos[i]->copy()->map((_, col: number) => ({
+	        col: col + 1,
+	        length: 1,
+	        type: 'FuzzyOldfiles'
+	    }))}))
+	  popup_settext(winid, text) | redraw
+	  win_execute(winid, 'normal! 1Gzt')
+	enddef
+
+	cnoremap <buffer><nowait> <Down> <ScriptCmd>popup_filter_menu(win, 'j')<Bar>redraw<CR>
+	cnoremap <buffer><nowait> <Up> <ScriptCmd>popup_filter_menu(win, 'k')<Bar>redraw<CR>
+	def TearDown()
+	  autocmd! FuzzyOldfiles
+	  augroup! FuzzyOldfiles
+	  cunmap <buffer> <Down>
+	  cunmap <buffer> <Up>
+	enddef
+
+	input('look for: ') | echo ''
+	popup_filter_menu(win, "\<CR>")
+<
  vim:tw=78:ts=8:noet:ft=help:norl:

In legacy:

diff --git a/runtime/doc/popup.txt b/runtime/doc/popup.txt
index 51413e796..654f021a6 100644
--- a/runtime/doc/popup.txt
+++ b/runtime/doc/popup.txt
@@ -1091,5 +1091,63 @@ this example simulated with a timer callback: >
 	  let s:winid = popup_beval(s:balloonText, #{mousemoved: 'word'})
 	endfunc
 <
+					*popup_fuzzy_select-example*
+Let the user select a file to edit, by prompting for a pattern matched against
+the paths in `v:oldfiles` with a fuzzy algorithm: >
 
+	function s:Callback(winid, choice) abort
+	  if a:choice == -1
+	    return
+	  endif
+	  execute $'split {a:winid->winbufnr()->getbufline(1, '$')->get(a:choice - 1, '')}'
+	endfunction
+
+	let s:win = v:oldfiles->popup_create(#{
+	  \   border: [],
+	  \   callback: function('s:Callback'),
+	  \   cursorline: v:true,
+	  \   minheight: 10, maxheight: 10,
+	  \   minwidth: 80, maxwidth: 80,
+	  \   scrollbar: v:false,
+	  \ })
+	redraw
+
+	augroup FuzzyOldfiles | autocmd!
+	  autocmd CmdlineChanged @ call s:MatchFuzzy(s:win)
+	  autocmd CmdlineLeave @ call s:TearDown()
+	augroup END
+
+	call prop_type_add('FuzzyOldfiles', #{bufnr: s:win->winbufnr(), highlight: 'WarningMsg'})
+	function s:MatchFuzzy(winid) abort
+	  let look_for = getcmdline()
+	  if look_for == ''
+	    call popup_settext(a:winid, v:oldfiles) | redraw
+	    return
+	  endif
+	  let matches = v:oldfiles->matchfuzzypos(look_for)
+	  let pos = matches->get(1, [])
+	  let text = matches->get(0, [])
+	    \ ->map({i, match -> #{
+	    \   text: match,
+	    \   props: pos[i]->copy()->map({_, col -> #{
+	    \     col: col + 1,
+	    \     length: 1,
+	    \     type: 'FuzzyOldfiles'
+	    \ }})}})
+	  call popup_settext(a:winid, text) | redraw
+	  call win_execute(a:winid, 'normal! 1Gzt')
+	endfunction
+
+	cnoremap <buffer><nowait> <Down> <ScriptCmd>call popup_filter_menu(s:win, 'j')<Bar>redraw<CR>
+	cnoremap <buffer><nowait> <Up> <ScriptCmd>call popup_filter_menu(s:win, 'k')<Bar>redraw<CR>
+	function s:TearDown() abort
+	  autocmd! FuzzyOldfiles
+	  augroup! FuzzyOldfiles
+	  cunmap <buffer> <Down>
+	  cunmap <buffer> <Up>
+	endfunction
+
+	call input('look for: ') | echo ''
+	call popup_filter_menu(s:win, "\<CR>")
+<
  vim:tw=78:ts=8:noet:ft=help:norl:

Here is how the results looks like:

gif


BTW, not sure whether that's possible, but it would be nice to get some popup option to let us interact with a popup window from a prompt. Right now, we have to install temporary buffer-local mappings to select a neighbor line with <Down>/<Up>, which looks clumsy. We also have to install autocmds to listen to the changes of the command-line and update the contents of the popup accordingly.

An input option would be useful:

popup_create(..., {
    ...
    input: {prompt: 'some text', ch_cb: ChangeCallback},
    ...
})

This would tell Vim to ask the user for some input, using "some text" for the text prompt, and calling the ChangeCallback user-function whenever the input changes. When the user presses Enter to validate their input, the existing callback option would be used.

Ideally, the input would be drawn in a 2nd popup window, right below or above the main one, but the regular command-line would be enough for a start.


Reply to this email directly, view it on GitHub.
You are receiving this because you are subscribed to this thread.Message ID: <vim/vim/issues/10440@github.com>

Romain Lafourcade

unread,
May 18, 2022, 6:27:41 AM5/18/22
to vim/vim, Subscribed

That's certainly an interesting example, but also a pretty heavy one with zero explanation of anything. Something less involved, perhaps, and better documented would be more useful.


Reply to this email directly, view it on GitHub.

You are receiving this because you are subscribed to this thread.Message ID: <vim/vim/issues/10440/1129839888@github.com>

bfrg

unread,
May 18, 2022, 7:44:56 AM5/18/22
to vim/vim, Subscribed

BTW, not sure whether that's possible, but it would be nice to get some popup option to let us interact with a popup window from a prompt. Right now, we have to install temporary buffer-local mappings to select a neighbor line with /, which looks clumsy. We also have to install autocmds to listen to the changes of the command-line and update the contents of the popup accordingly.

I agree. It would be nice to have a new function like popup_prompt({prompt}, {options}) (or alternatively popup_input()) that prompts the user for input and invokes a callback function whenever the input changes similar to input() but inside a popup window.

In your example you can avoid the temporary buffer-local mappings with a popup filter:

def Filter(winid: number, key: string): bool
    if key ==# "\<c-n>"
        popup_filter_menu(winid, 'j')
        redraw
        return true
    elseif key ==# "\<c-p>"
        popup_filter_menu(winid, 'k')
        redraw
        return true
    else
        return false
    endif
enddef

var win: number = v:oldfiles->popup_create
({
    border: [],
    callback: Callback,
    cursorline: true,
    minheight: 10,
    maxheight: 10,
    minwidth: 80,
    maxwidth: 80,
    scrollbar: false,
    mapping: false,
    filtermode: 'c',
    filter: Filter
})

The rest is the same as your example.


Reply to this email directly, view it on GitHub.

You are receiving this because you are subscribed to this thread.Message ID: <vim/vim/issues/10440/1129906031@github.com>

Bram Moolenaar

unread,
May 18, 2022, 8:13:58 AM5/18/22
to vim/vim, Subscribed


> There is a TODO item at [`:help popup-examples`](https://vimhelp.org/popup.txt.html#popup-examples):

>
> TODO: more interesting examples
>
> How about adding an example to document how one could implement a
> simple fuzzy file selector?

This is too long. An example here should be short and to-the-point,
concentrating on how to use a popup window.


--
hundred-and-one symptoms of being an internet addict:
226. You sit down at the computer right after dinner and your spouse
says "See you in the morning."

/// Bram Moolenaar -- ***@***.*** -- http://www.Moolenaar.net \\\
/// \\\
\\\ sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ ///
\\\ help me help AIDS victims -- http://ICCF-Holland.org ///


Reply to this email directly, view it on GitHub.

You are receiving this because you are subscribed to this thread.Message ID: <vim/vim/issues/10440/1129930784@github.com>

lacygoill

unread,
May 18, 2022, 8:15:52 AM5/18/22
to vim/vim, Subscribed

This is too long. An example here should be short and to-the-point,
concentrating on how to use a popup window.

Understood. Closing then.


Reply to this email directly, view it on GitHub.

You are receiving this because you are subscribed to this thread.Message ID: <vim/vim/issues/10440/1129932449@github.com>

lacygoill

unread,
May 18, 2022, 8:15:52 AM5/18/22
to vim/vim, Subscribed

Closed #10440.


Reply to this email directly, view it on GitHub.

You are receiving this because you are subscribed to this thread.Message ID: <vim/vim/issue/10440/issue_event/6631832730@github.com>

D. Ben Knoble

unread,
May 18, 2022, 9:24:02 AM5/18/22
to vim/vim, Subscribed

On the chance that folks stumble across this later looking for more examples, I'll leave a pretty shameless plug for https://github.com/benknoble/popsikey, which uses popups in for a "HUD" effect for (user-defined) mappings. It's also too long and probably under-documented.

I also learned a little about popups from https://github.com/b-layer/musecnav and https://vi.stackexchange.com/a/24463/10604, the latter of which has several short examples.


Reply to this email directly, view it on GitHub.

You are receiving this because you are subscribed to this thread.Message ID: <vim/vim/issues/10440/1130013295@github.com>

lacygoill

unread,
May 22, 2022, 7:16:51 AM5/22/22
to vim/vim, Subscribed

In your example you can avoid the temporary buffer-local mappings with a popup filter:

It works in this example, but it didn't work when I tried here.

I guess the filter is not working when input() is called from the filter itself. And I guess this is working as intended, to prevent some kind of never-ending loop.


Reply to this email directly, view it on GitHub.

You are receiving this because you are subscribed to this thread.Message ID: <vim/vim/issues/10440/1133873156@github.com>

bfrg

unread,
May 22, 2022, 2:50:45 PM5/22/22
to vim/vim, Subscribed

It doesn't work because input() consumes all keys and won't return until you hit <CR>. So you're temporarily stuck in the popup-filter where you called input().

I don't know how well it works when you invoke input() with a timer but it's certainly not easier than using some cnoremap. You'd also have to check the current mode() in the popup-filter, and if in command-line mode return false so that input() can consume all keys (except for <C-N>, <C-P>, <Down> and <Up> which you handle with the popup-filter). It's too complicated and just shows that we need something like a popup-prompt. Or maybe popup_fuzzy_menu()?


Reply to this email directly, view it on GitHub.

You are receiving this because you are subscribed to this thread.Message ID: <vim/vim/issues/10440/1133952817@github.com>

lacygoill

unread,
May 23, 2022, 12:51:22 AM5/23/22
to vim/vim, Subscribed

It doesn't work because input() consumes all keys and won't return until you hit .

Ah that makes sense. The keyword I was missing was "consume".

I don't know how well it works when you invoke input() with a timer

I remember trying everything: a timer, temporarily removing the filter, ... nothing worked. The only solution was these temporary buffer-local mappings.

It's too complicated and just shows that we need something like a popup-prompt. Or maybe popup_fuzzy_menu()?

Yes, I agree. As for the name, I would avoid "fuzzy", because someone might need this feature for something else, and then reading "fuzzy" in their code would look confusing.


Reply to this email directly, view it on GitHub.

You are receiving this because you are subscribed to this thread.Message ID: <vim/vim/issues/10440/1134177795@github.com>

Reply all
Reply to author
Forward
0 new messages