-
Notifications
You must be signed in to change notification settings - Fork 1.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Feature request] Tooltip support #3465
Comments
Ability for plugins to draw tooltips is something I brought up in the discussion about LSP clients (#3231). I think it would be a great addition to the editor. |
Great! What @glupi-borna showed in #3231 looked a lot more finished than what I've made so far, so I suppose his implementation would be the most sensible one to actually try and merge at some point. |
Honestly, I'd love to work on getting something like this implemented. I doubt merging my fork would work, cause it's based on a very outdated version of micro, and it has a bunch of other unrelated changes (and it's hacky as all hell, on top of that). I also didn't bother to design the API very well, cause I just needed it to work well enough for my own purposes. It's a good amount of effort, though, so I'd like to know that the maintainers would even consider merging such a change before I start working :) |
I don't see a problem in case you can provide the most of the logic as a plugin. Tell us which Lua or exposed API is currently insufficient for this feature and we can discuss what is possible. |
As I implemented it, it would be a new API 'endpoint' similar to I've not yet fully implemented a multiple choice dropdown for autocompletion, but I imagine it would be something akin to This way, the lsp-plugin (or any other plugin for that matter), just need to forward its results to this new tooltip drawing API, implemented in the editor itself. |
To start, I would suggest we implement something more primitive than specific components -- simple tooltips, autocompletes, etc., are neat, but they are also limiting in many ways, and implementing and maintaining different UI components for different use cases would increase the burden on the maintainers significantly. Instead, I propose overlays. As the name suggests, overlays are UI elements that are drawn on top of the rest of the UI. This would be achieved by exposing a few simple rendering primitives (functions) that allow plugin authors to experiment with novel UI ideas and implement complex extension UIs with no additional burden for the maintainers. These primitives would be thin wrappers around screen.SetContent() and a few other useful functions like config.StringToStyle and friends. Something like: // DrawRect draws a colored rectangle to the screen
func DrawRect(x, y, w, h int, style tcell.Style)
// DrawText draws text at the given coordinates, clipped by the provided maximum
// width and height.
func DrawText(text string, x, y, w, h int, style tcell.Style) Overlay positioningI have discovered that you generally want to position overlays in one of three ways:
Of these, 1. is easiest to implement - the overlay is simply drawn on top of everything else, at fixed coordinates. These could be used to, for example, supplant the infobar with a stack of hovering notifications, or to implement a hovering command palette (like in VSCode, SublimeText, etc.), or for some sort of "guided tutorial" (you can imagine small explanation blurbs popping up in the appropriate place when the user interacts with certain features of the editor, etc). Overlays with this positioning only need to be repositioned when the whole viewport (most commonly, the terminal window) is resized. Next is 2., which is very similar to 1., but is drawn on top of a specific BufPane. This means that the overlay must be repositioned when the BufPane is moved, resized, hidden (tabbed away from), etc. Care needs to be taken that these overlays can not spill outside of the BufPane and into other bufpanes, the infobar, the statusbar area, etc. The last (3.) type of positioning is also drawn on top of a specific BufPane, but is bound to a specific buffer Location. This means that everything from 2. still applies, but these also need to be repositioned when the view is scrolled or modified. Interestingly, if we provide a couple of helper functions (getting the screen-space bounds of a BufPane, the screen-space location of a specific Location in a buffer, and perhaps one or two more that I'm not thinking of right now), we can only implement positioning 1. and let plugin authors do the rest. The only drawback is that we would probably be redrawing parts of the UI too often, but maybe that's not a big deal? Overlay renderingDepending on how positioning is implemented, overlay rendering can be more or less involved. If we go with implementing each of 1., 2. and 3. in micro, then we're on the hook for ensuring that all rendering operations that an overlay attempts are clipped to the area defined by the positioning. However, if we go with the simpler method, then overlay rendering is as simple as calling a Overlay event handlingThis is not as important for informational overlays, but more advanced interactive overlays will want to do some level of event handling. For example, an autocomplete overlay will want to intercept and filter keyboard events before they are passed to the underlying bufpane. The simplest way to enable this behavior (in my opinion): overlays can optionally supply a Obviously, this means that we need to expose some way of binding an overlay to a Buffer/BufPane. On the other hand, event handling can also be left up to plugin maintainers - the onAction/preAction hooks should be enough to do all of this, albeit in a perhaps less ergonomic way. TL;DRWe implement overlays - small bundles of stuff (at the minimum, a Draw() function that micro would call every frame) that enable plugins to draw to the screen on top of micro. Depending on how hands-on we would want to be, we can implement more or less features. At the bare minimum, a few new plugin API functions are needed (creating/destroying an overlay, getting the screen-space positions of a BufPanes and buffer locations, a thin wrapper or two around screen.SetContent and a few other utility functions for creating tcell styles). To have something more concrete to discuss, here is a minimal set of extensions to the plugin API: // Note: OverlayHandle is just an opaque handle. Could be as simple as an integer
// that gets incremented every time CreateOverlay is called. All that matters is
// that it uniquely identifies the overlay, so that we can properly call
// DestroyOverlay later.
func CreateOverlay(draw func()) OverlayHandle
func DestroyOverlay(overlay OverlayHandle)
func DrawRect(x, y, w, h int, style tcell.Style)
func DrawText(text string, x, y, w, h int, style tcell.Style)
// Note: Rect is just a struct with X,Y,W,H
func BufPaneScreenRect(bp *BufPane) Rect
func BufPaneScreenLoc(bp *BufPane, loc Loc) Loc
func StringToStyle(str string) tcell.Style Thoughts? |
Doesn't sound bad so far. 👍 |
I implemented a Lua module that creates tooltips and made a plugin to show autocompletions in a tooltip under the cursor. You can check it here: https://github.com/usfbih8u/micro-autocomplete-tooltip To make it look like a real tooltip, I had to fake the background with padded text. In this plugin, it's not a big deal, but I made another plugin to navigate and show the gutter messages in a tooltip (I will release it this week), and there, the formatting becomes a little too cumbersome. Having a real background in micro, a colorscheme per buffer, or a full border will help with readability and make it easier to create tooltips for plugins. The use of a custom syntax and colorscheme is mandatory. |
@usfbih8u wow, that's pretty cool! I didn't have time to take a detailed look at the code yet - are you achieving this by writing directly to the buffer? BTW, I'm planning to take a stab at implementing what's been laid out in my earlier comment sometime soon, it's just that work has kept me very busy lately. Would an API as described above be helpful for you? |
TLDRHaving almost full access to Go structs inside Lua is a blessing. However, closing that access with an overlay API and losing control over the buffer itself and the events associated with it may not be an improvement. The API creates a rectangle with a color and text without syntax, similar to the image at the top(?). This is the idea I’m getting from your post. Please, correct me if I misunderstood. I believe using a buffer is the best approach, but we need to prioritize addressing the readability issue and the padding situation (more on that later). I'm not sure how easy it would be to implement this. Perhaps you could create a NOTE: I would like to refine these plugins a bit more, as I think the event handling can be simplified. I will work on this throughout the week, so if you’re busy, let me clean up the code a little, and then you can take a deep look at it. Problems that I encounter during development
Readability issues and padding shenanigansLacking a solid background in Micro, a color scheme per buffer, or a full border forces you to pad the text with spaces and use custom syntax and color schemes to make it readable. For instance, if you look at the
To highlight the current selection, I added a non-visible Unicode character at the end of the suggestion so that the syntax can detect it as a different item and display it in another color. Now, regarding the other plugin,
All of these shenanigans arise from the lack of a proper background. NOTE: In my color scheme, if the current line is a wrapped line, you can see what happens with the highlight and the background. The highlight ends after the last word of the wrapped line, and only the last line extends to the end. A similar issue occurs if you don't pad the text with spaces, resulting in a "stair-step" appearance. Thus, avoiding all of this padding with spaces and having a proper background is, in my opinion, a top priority. Resize and Split issues
Not detecting some events breaks the layoutIn the To achieve this, I used the Things I need in Micro
As you can see, the major problems are general Micro issues and not directly related to tooltips. I think @dmaluka and @JoeKar, being the main developers, should provide their insights on all of this. |
Not really. Let me explain: The Overlay API I proposed is supposed to be a very low-level, immediate mode API that would allow plugins to draw anything to the terminal character grid (aka the screen), on top of micro. The reasoning for providing such a low-level API is to lessen the maintenance burden, while supporting a wide number of use cases that would be hard to predict, implement and maintain otherwise. I intended the drawing commands ( To help with positioning:
The Finally, supporting functions from the So, in terms of your main concerns:
Does that make sense? |
I think we are proposing different approaches. You want a low-level API with access to tcells, and I want floating BufPanes and all its functionalities. Losing access to the BufPane functionalities (you cannot write on it, select text, you don't have syntaxes, terminal, etc.) feels like a wrong decision. I gain too much control in areas that I don't care about and lose all the functionality that I could use. What would be useful for my use case is having floating BufPanes. Having a real background and being able to change it to make contrast with the user color scheme, and not much more (some events1 and other UI things2) would be a great improvement. Footnotes
|
Adding the ability to draw tooltips at the cursor for autocompletes, LSP documentation, etc. is really the only feature that I miss in micro compared to Emacs, Neovim, etc.
Its implementation should not be too disruptive to the codebase either, only an optional API that can be adopted picewise by plugins.
Has anyone worked on this previously? I've whipped up a small test implementation and modified the LSP hover command to use it here: https://github.com/ImFstAsFckBoi/micro/tree/feat/tooltip. If anyone already has a better implementation, then maybe that one should be used instead, but if no one want to work on it, I would be happy to do it.
The text was updated successfully, but these errors were encountered: