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:
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.
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.
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.
—
Reply to this email directly, view it on GitHub.
You are receiving this because you are subscribed to this thread.
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.
Closed #10440.
—
Reply to this email directly, view it on GitHub.
You are receiving this because you are subscribed to this thread.
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.
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.
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.
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.