38

The latest version of Vim, 8.2, has a new feature called "popups" or "popup windows". They sound interesting but I'm not quite sure what they're good for. Can you enlighten me...preferably with some examples?

2
  • 1
    @MartinTournoij I just noticed the large bounty you've attached. My understanding of bounties was pretty one-dimensional (i.e. it's about attracting additional and/or better answers) but now that I've done some research I think I understand your actual intent (as indicated by your chosen reason and the chosen waiting period) to which I say, "Thanks! That's very generous of you." :)
    – B Layer
    Apr 10, 2020 at 0:09
  • Ya, I just wanted to add it to your answer. For some reason you still have to wait 24 hours when you fill in "reward existing answer" 🤷‍♂️ I got way too much reputation anyway just because I was active in the early days; I figured I might as well attach them to some exceptional answers as I come across them. Apr 10, 2020 at 12:02

1 Answer 1

60
+500

Background: I have a plugin for navigating sections in markup documents and wanted to show the section hierarchy in a popup. I distilled what I learned while implementing this down to the following introduction in hopes of getting others up and running quickly...

Overview

Vim 8.2's popup windows allow Vimscript authors and plugin developers to create one or more floating, modal windows that, among other things, can be used for tool tips, notifications/alerts, dialog boxes and transient menus.

Window and Buffer

Each popup consists of a Vim window and an associated buffer. The window and buffer have a number of unique characteristics including:

  • Popup windows will be positioned on top of regular Vim windows.
  • Popups can be stacked on each other with stacking order determined by the relative values of their zindex attributes.
  • The buffer is 'bufhidden', is not 'buflisted' (it won't show up in a buffer listing), and has a 'buftype' of "popup".
  • There is no cursor in a popup. (Oddly, if the cursor in the underlying window is below the popup it will remain visible.)
  • The buffer has no swapfile or undo capability and is not editable by normal means but the content can be modified post-creation with popup_settext().
  • A popup's initial contents are specified via a parameter to one of the create functions. The content can be in a string or a list of strings. Alternatively, you can pass the number of an existing buffer containing your content. (To create a buffer solely for the popup see bufadd().)
  • The window/buffer look and feel is highly customizable.

Creation

There are six different functions for creating popup windows. That's a lot but five of them are basically wrappers around popup_create() that provide "pre-packaged" configurations for particular purposes. Here they all are with brief descriptions taken from :h popup-functions:

  • popup_create() - centered in the screen
  • popup_atcursor() - just above the cursor position, closes on mouse move
  • popup_beval() - at pos indicated by v:beval_ variables, closes on mouse move
  • popup_notification() - show a notification for three seconds
  • popup_dialog() - centered with padding and border
  • popup_menu() - prompt for selecting an item from a list

Customization

Text appearance can be controlled with syntax highlighting or using text properties (another major new feature in Vim 8.2...like syntax highlighting except it sticks to associated text as it moves in the buffer). There are also a number of attributes or "options" that control certain other look-and-feel characteristics. This is a sampling of those options.

  • Placement/Size: line, col, pos (e.g. "botright"), resize (with mouse), drag (with mouse), minwidth*
  • Appearance: padding, border, borderchars, highlight (a la :hi), title, mask (see example below)
  • Behavior: time (until close), callback (function), filter (input processing)

Option values can be defined on creation of a popup window or afterwards through the popup_setoptions() function.

* It's worth noting that popup window dimensions are, by default, dictated by the contained text. You can override this behavior, within limits, using minwidth, maxwidth, minheight, maxheight.

Interaction

A popup's lifetime can be controlled several ways including by timer, upon mouse movement or user input or programmatically with popup_close(). The nuclear option, popup_clear(), will dismiss any and all existing popups.

How a popup processes user input can be controlled with so-called "filters". There are a couple of builtin filters including the "yesno" filter demonstrated in the examples below.

Each popup can have an assigned callback function. When the popup is closed this function is called with the "result". This is most applicable for popups acting as menus. The result, in that case, is usually just the number of the row that the user selected. We'll see this in action, too, below.

Examples

Finally, we get to the good stuff!

Notification

For a time-limited notification popup:

call popup_notification("Get the hell out of Dodge!", 
     \ #{ line: 5, col: 10, highlight: 'WildMenu', } )

enter image description here

As noted above this will show for three seconds then close.

Menu

Now for a simple menu:

func! MenuCB(id, result)
    echo "You chose item #".a:result
endfunc

call popup_menu(['The quick fox...', '...jumped over...', '...the lazy dogs!'], 
     \ #{ title: "Well? Pick one", callback: 'MenuCB', line: 25, col: 40, 
     \ highlight: 'Question', border: [], close: 'click',  padding: [1,1,0,1]} )

enter image description here

Note the padding option which I used to put a little space between the menu items and the top and side borders.

Choosing the last row results in...

enter image description here

Yes/No Dialog

In this example we'll use the builtin filter popup_filter_yesno. This will wait until the user has clicked a close key (x or <Esc>) or one of Y, y, N, or n. Yes replies cause the callback to be called with result 1. No sends result 0.

func! YayOrNay(id, result)
    if a:result | echo "You said 'yay'" | endif
endfunc

call popup_dialog('[y]ay or [n]ay?', #{ filter: 'popup_filter_yesno', callback: 'YayOrNay'})

enter image description here

If I choose Y or y the message ("You said 'yay'") will show on the status line.

If you specify your own filter function then you can intercept a subset of key presses, handling them in whatever way you choose and pass the rest on to the generic filter, popup_filter_menu(). There's an example at :h popup-examples.

The Base Case

Let's try the primary function, popup_create(), with no options...

call popup_create(poplist, #{ })

poplist is a list of strings representing a hierarchical menu, with appropriate numbering and spacing included.

enter image description here

So by default we get a popup window positioned in the middle of the main window, having no borders or padding and with dimensions determined by the content.

The Z-Index and Masking

Finally, let's see the z-index functionality and, for amusement mostly, demonstrate the mask attribute. With 'mask' you can specify popup coordinates and those sections of the popup will be rendered as transparent. I've yet to think of a practical use for this so I just cut off a couple corners and poked a couple big holes in the thing.

call popup_create(poplist, #{ zindex: 20, line: 5, col: 20,
     \ mask: [[1, 10, 1, 3], [-10, -4, 3, 5], [10, 23, -5, -2], [-10, -1, -3, -1]] })

enter image description here

Normally one would expect additional popups to be placed over existing ones but by specifying a lower value for the zindex attribute....

call popup_create(poplist, #{ zindex: 10, line: 7, col: 25, highlight: 'CursorLine' })

...we can make it a pop-under:

enter image description here

The garish pink? That's the default color for popups in many color schemes. (Completion menu users will likely have seen it before! :P) Override with the new highlight groups Popup and PopupSel.

Other Features

Well, I hope that gives you a good sense of what popups are capable of. There are certainly some features I haven't covered or only mentioned briefly including:

  • Asynchronous content fill-in
  • Popup manipulation functions (e.g. popup_move(), popup_setoptions())
  • User-defined filters
  • Key mappings
  • Building a popup from a user-created buffer.
  • Running a terminal in a popup window

To learn more about those and everything else popup related visit :h popup for a thousand-plus lines of juicy popup goodness.

20
  • 2
    That markdown outline jumping thing sounds useful... i’d love to stay posted on its development
    – D. Ben Knoble
    Apr 5, 2020 at 14:21
  • 3
    @D.BenKnoble I created it for myself but I can share it if it reaches an appropriate level of stability/usability. Note, though, that I said markUP. Specifically, I use asciidoc(tor) which is similar to but much more powerful than markdown.
    – B Layer
    Apr 5, 2020 at 22:37
  • 2
    Awesome answer! Good to see so many examples
    – mowwwalker
    Apr 6, 2020 at 17:45
  • 1
    @MaximKim Looks like we're working on similar things. Add scrollbar: 1 to the option map when you call popup_create and a scrollbar will be there if the text doesn't fit. I haven't tried them yet because even my largest asciidoctor doc, which would probably render out to 150 printed pages (I should write a book) has "only" 19 top-level sections. I do have a huge monitor and use tall windows, to be fair.
    – B Layer
    Apr 8, 2020 at 20:02
  • 1
    @D.BenKnoble The markup section navigator thingy works nicely with popups. I had to add custom key handler so numbers can be used to select sections (by default you get nothing more than Up/Down or j/k which sucks with 40 or more sections) I'm digging it and use it every day. Probably a while yet until I share it...todo list is still long...but I almost certainly will.
    – B Layer
    May 10, 2020 at 14:14

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.