Skip to content
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

add support for virtual text or padding in textprop #7553

Closed
prabirshrestha opened this issue Dec 26, 2020 · 40 comments
Closed

add support for virtual text or padding in textprop #7553

prabirshrestha opened this issue Dec 26, 2020 · 40 comments

Comments

@prabirshrestha
Copy link

I would like vim to support virtual text. Neovim does support one but only at the end of the line. Here is an example of how it looks like in neovim. Notice the diagnostics error that is displayed at the end by vim-lsp.

image

This also seems like quite a popular request based on the 49 likes on the poll. #3573 (comment)

I'm primarily interested in implementing codelens support for language server but there are tons of other use cases. Notice "Run" and "Debug".

image

Another usage would be to show types that was implicitly discovered. Notice how I only type let result = a + b; but vscode with lsp automatically adds u32 i.e. let result: u32 = a + b;

image

Few ways it could be implemented.

  1. Use the api same as neovim's virtual text - [RFC] support "virtual text" annotations (currently after EOL only) neovim/neovim#8180.
call nvim_buf_set_virtual_text(bufnr, namespace_id, linenr, ["more text", "Comment"]], {})
  1. Extend textprops to add padding.
call prop_type_add('lspcodelens', { 'highlight': 'Comment' })
call prop_add(11, 12, { length:3, type: 'lspcodelens', 'padding': [0,0,0,5] })

Then we can get the coordinates of the textprop padding using a new api and use popup to render text on the padding space.

1 would be easier to implement as well as use from a plugin author but 2 would be very powerful allowing us to support neovim's end of line virtual text or even above and below.

Not sure about perf for 2 but another option would be to have something similar to sign_define() api and support for inserting a virtual line and another api for virtual text at EOL similar to neovim. This might be performant but will not support inline types but is still better than having nothing. If textprop apis is better and powerful it might be better to incrementally support this as in we can only support padding top first.

The reason I suggested textprop api with popup window support is that we might in the future want to focus inside this virtual text and perform actions such as Run Tests or Debug Tests. For now I wanted it to do the simplest task that is give me whitespace so I can use popup to render text. When popup supports focusing we can then interact with it.

Another example of where virtual text can be used is to detect the node_modules bundle file size. ref: https://devtips.theanubhav.com/posts/cost-of-import

import-cost

VS Code calls this decorations and is similar to vim's textprop. You can read more about it on their official website including lot of other extensions that takes advantage of it.

image
image

VS Code use after and before instead of padding to accomplish this.

const decorationType = vscode.window.createTextEditorDecorationType({});
const decorationOptions: vscode.DecorationOptions = {
	range: new vscode.Range(0, 0, 0, 0),
	renderOptions: {
		after: {
			contentText: 'blah',
                        width: '200px',
                        height: '50px'
                       // full options can be found here. https://code.visualstudio.com/api/references/vscode-api#ThemableDecorationAttachmentRenderOptions
		},
	},
};
editor.setDecorations(decorationType, [decorationOptions]);
@lacygoill
Copy link

I think you could get something similar using :h popup-textprop-pos:

vim9script

def AddVirtualText()
    var lines =<< trim END
        fn main() {
            println!("Hello, world!");
        }

        fn add(a: u32, b: u32) -> u32 {
            a + b
        }

        fn sub(a: u32, b: u32) -> u32 {
            a - b
        }
    END
    setline(1, lines)

    for lnum in [5, 9]
        prop_type_add('markendofline' .. lnum, {})
        prop_add(lnum, col([lnum, '$']), {
            type: 'markendofline' .. lnum,
            })
        var text = {
            5: 'function is never used: `add`^@`#[warn(dead_code)]` on by default',
            9: 'function is never used: `sub`',
            }[lnum]
        popup_create(text, {
            textprop: 'markendofline' .. lnum,
            highlight: 'Comment',
            line: -1,
            col: 2,
            zindex: 49,
            })
    endfor
enddef
AddVirtualText()

But I guess that's not exactly what you want. For example, if you add a split to the right of a window displaying virtual text, I assume you don't want the virtual text to overflow on the latter.

@prabirshrestha
Copy link
Author

Wasn't aware of popup-textprop-pos. Seems like that will fix one of the asks I had earlier.

function! g:TestVirtualText() abort
  call setline(1, ['hello', 'world'])
  call cursor(2, 1)
  call prop_type_add('codelens', {})
  call prop_add(2, 1, { 'type': 'codelens' })
  call popup_create("Run", {
    \   'textprop': 'codelens',
    \   'higlight': 'Comment',
    \   'line': -2,
    \   'col': 1,
    \   'zindex': 49,
    \ })
endfunction
call g:TestVirtualText()

This doesn't seem to quite work. Notice how Run is overlapping hello.

image.

I would want it to be something like. Notice how it is 3 lines but when I navigate down and up it should only go to hello and world.

hello
Run
world

What I would need is adding padding top in prop_type_add.

call prop_type_add('codelens', { 'padding': [1, 0, 0, 0] })

Another thing I noticed is how do you close the popup? Once the text have been deleted I still see the popup window.
image

If I use dd it does close the popup correctly.

@lacygoill
Copy link

lacygoill commented Dec 28, 2020

I would want it to be something like. Notice how it is 3 lines but when I navigate down and up it should only go to hello and world.

OK, I understand better now. You want the ability to add a virtual line between 2 lines of text and use it to display some arbitrary information. I don't think that's currently possible; at least I don't know how to do that. As you said, maybe a new padding property in a prop_*() function would help.

Another thing I noticed is how do you close the popup? Once the text have been deleted I still see the popup window.

You need to give a non-zero length to the text property.

Suppose your text looks like this:

aaa bbb ccc

And you want your trailing virtual text to be closed when bbb is deleted. Then, you need to:

  • apply your text property on bbb
  • position your popup next to the text property
  • add some left padding to the text in the popup
  • make the padding transparent so that the real text below the popup remains visible

For the last bullet point, see :h popup-mask.

As an example:

vim9script

def AddVirtualText()
    var lines =<< trim END
        fn main() {
            println!("Hello, world!");
        }

        fn add(a: u32, b: u32) -> u32 {
            a + b
        }

        fn sub(a: u32, b: u32) -> u32 {
            a - b
        }
    END
    setline(1, lines)

    for [lnum, col, length, virtual_text]
        in [[5, 4, 3, 'function is never used: `add`^@`#[warn(dead_code)]` on by default'],
            [9, 4, 3, 'function is never used: `sub`']]
        prop_type_add('virtualText' .. lnum, {})
        prop_add(lnum, col, {
            type: 'virtualText' .. lnum,
            length: length,
            })
        var offset = 2
        var left_padding = col([lnum, '$']) - col + length + offset
        popup_create(virtual_text, {
            textprop: 'virtualText' .. lnum,
            highlight: 'Comment',
            line: -1,
            col: offset,
            zindex: 50 - 1,
            wrap: false,
            mask: [[1, left_padding, 1, 1]],
            padding: [0, 0, 0, left_padding],
            pos: 'topright',
            })
    endfor
enddef
AddVirtualText()

Tested on 8.2.2230, Ubuntu 16.04:

gif

@prabirshrestha
Copy link
Author

@lacygoill That looks great.

I noticed the length is hard-coded to 3, so if you change add to multiplication popup will be on top.

@lacygoill
Copy link

lacygoill commented Jan 17, 2021

I noticed the length is hard-coded to 3, so if you change add to multiplication popup will be on top.

Yes, the code had issues. This one seems better:

vim9
var lines: list<string> =<< trim END
    I met a traveller from an antique land,
    Who said—“Two vast and trunkless legs of stone
    Stand in the desert. . . . Near them, on the sand,
    Half sunk a shattered visage lies, whose frown,
    And wrinkled lip, and sneer of cold command,
    Tell that its sculptor well those passions read
    Which yet survive, stamped on these lifeless things,
    The hand that mocked them, and the heart that fed;
    And on the pedestal, these words appear:
    My name is Ozymandias, King of Kings;
    Look on my Works, ye Mighty, and despair!
    Nothing beside remains. Round the decay
    Of that colossal Wreck, boundless and bare
    The lone and level sands stretch far away.”
END
setline(1, lines)

var lnum2popup_id: dict<dict<number>> = {}

def AddVirtualText(props: dict<any>): number
    var lnum: number = props.lnum
    var col: number = props.col
    var length: number = props.length
    var text: string = props.text
    var highlight_text: string = has_key(props, 'highlight_text')
        ? props.highlight_text
        : 'Normal'
    var highlight_virtualtext: string = has_key(props, 'highlight_virtualtext')
        ? props.highlight_virtualtext
        : 'Normal'
    if has_key(props, 'highlight_virtualtext')
        highlight_virtualtext = props.highlight_virtualtext
    endif

    var buf: number = bufnr('%')
    if prop_type_list({bufnr: buf})->index('virtualText' .. lnum) == -1
        prop_type_add('virtualText' .. lnum, {bufnr: buf, highlight: highlight_text})
    endif
    if has_key(lnum2popup_id, buf) && has_key(lnum2popup_id[buf], lnum)
        popup_close(lnum2popup_id[buf][lnum])
    endif
    prop_add(lnum, col, {
        type: 'virtualText' .. lnum,
        length: length,
        bufnr: buf,
        })
    var left_padding: number = col([lnum, '$'])
    var popup_id: number = popup_create(text, {
        textprop: 'virtualText' .. lnum,
        highlight: highlight_virtualtext,
        line: -1,
        zindex: 50 - 1,
        wrap: false,
        mask: [[1, left_padding, 1, 1]],
        padding: [0, 0, 0, left_padding],
        pos: 'topright',
        })
    if !has_key(lnum2popup_id, buf)
        extend(lnum2popup_id, {[buf->string()]: {}})
    endif
    extend(lnum2popup_id[buf], {[lnum->string()]: popup_id})
    return popup_id
enddef

var ozymandias_pos: list<number> = searchpos('Ozymandias')
AddVirtualText({
    lnum: ozymandias_pos[0],
    col: ozymandias_pos[1],
    length: strchars('Ozymandias'),
    text: 'Greek name for Ramesses II, pharaoh of Egypt',
    highlight_text: 'Search',
    highlight_virtualtext: 'MoreMsg',
    })

gif


But it still has some issue. If we insert text on the line where there is virtual text, some real text at the end of the line gets hidden by the popup.

gif

That's because left_padding needs to be updated. We would need to re-invoke the function every time we insert or remove text on a line where there is virtual text. We could try an autocmd:

au TextChanged,TextChangedI,TextChangedP * AddVirtualText({
    \ lnum: ozymandias_pos[0],
    \ col: ozymandias_pos[1],
    \ length: strchars('Ozymandias'),
    \ text: 'Greek name for Ramesses II, pharaoh of Egypt',
    \ highlight_text: 'Search',
    \ highlight_virtualtext: 'MoreMsg',
    \ })

But this one is wrong; because ozymandias_pos might also need to be updated.
Maybe AddVirtualText() should be called as a callback via listener_add(). When I have more time, I'll see if I can improve the code.

@lacygoill
Copy link

lacygoill commented Jan 17, 2021

I think that the issue could be fixed when we insert something before the text to which a popup is attached. For this, we would need to use the col key when invoking popup_create(). According to the help, the latter is relative to the text property:

vim/runtime/doc/popup.txt

Lines 607 to 608 in e2edc2e

When using "textprop" the number is relative to the
text property and can be negative.

Unfortunately, it seems to be ignored (contrary to line which works as expected). It could be a Vim bug. I'll make more tests; if it is a bug, I'll report it.

But even then, it wouldn't fix the issue when we insert something after the text to which a popup is attached.


Right now, the textprop key which we can use when invoking popup_create() controls two things:

  • it determines where the popup should be created
  • it determines when it should be visible
    (i.e. when the text to which the property is attached is deleted, the popup is automatically closed; when it's restored, the popup is automatically re-opened)

I think what you would need is the ability to use 2 different text properties:

  • the first one would determine where the popup should be created
  • the second one would determine when it should be visible

We could then tell Vim that we want the popup to be positioned at the end of the line (by adding a text property there), and only stay visible while a different text on the line has not been deleted (by adding another text property on the latter).

It wouldn't fulfill your other requirement, which is the ability to display virtual lines (BTW, someone else is interested in the same feature), but it would already be very helpful.

@prabirshrestha
Copy link
Author

Besides when and where I would also need to specify padding so I know the popup is not not overriding other text.

Ideally it would be better if vim supports it by default similar to how there are already bunch of helpers for different style of popup.

This would encourage more people to use the feature instead of having a custom one.

https://github.com/prabirshrestha/vim-lsp/blob/51a566c87824b4fe214d5ff63d2271254f22d461/autoload/lsp/internal/diagnostics/virtual_text.vim#L125-L134

In neovim it is as simple as calling one function. My guess is that it was easier to implement and hence virtual text in neovim only works for end of line.

  call nvim_buf_set_virtual_text(a:bufnr, s:namespace_id, l:line,
            \ [[g:lsp_diagnostics_virtual_text_prefix . l:item['message'], l:hl_name]], {})

Related: neovim/neovim#13661

My thought was something like this.

call prop_add(l:start_line, l:start_col, {
                    \ 'end_lnum': l:end_line,
                    \ 'end_col': l:end_col,
                    \ 'bufnr': a:bufnr,
                    \ 'type': 'virtualtext',
                    \ 'padding_eol': 10
                    \ })

padding_eol would give support for neovim's virtual text support. One open question would be if the padding needs to be in the start line or end line. padding_endline_eol padding_startline_eol.

padding_endcol would give support for type hints for implicit known types. This would always pad right of endcol.
padding_startline would give support for code lens support i.e. Run | Debug. This would always pad top of startline.

@bfredl
Copy link
Contributor

bfredl commented Jan 18, 2021

we (neovim) plan to support virtual text anywhere eventually but it is quite a bit trickier than just adding it at the end, basically for the same reason that conceal is tricky (including things like visual cursor pos and mouse clicks).

@brammool
Copy link
Contributor

brammool commented Jan 18, 2021 via email

@lacygoill
Copy link

lacygoill commented Jan 21, 2021

Ideally it would be better if vim supports it by default similar to how there are already bunch of helpers for different style of popup.

This would encourage more people to use the feature instead of having a custom one.

Oh yes, I completely agree. Even if we can currently achieve something with builtin functions, a new helper function would make sense for this use case, similar to popup_atcursor(), popup_dialog(), popup_menu(), and popup_notification().


Unfortunately, it seems to be ignored (contrary to line which works as expected). It could be a Vim bug. I'll make more tests; if it is a bug, I'll report it.

I was wrong, it's not ignored. But the code made it look like so, because it wrongly used pos: 'topright', which changed how col was interpreted. Once it's removed, the popups are correctly moved when we insert text before a text property. But not when we insert text afterward. For that, I think we need a listener callback to update the padding and mask options of the popups.

Here is an updated version of the previous code:

vim9

var lnum2popup_id: dict<dict<number>> = {}

def AddVirtualText(props: dict<any>): number
    var lnum: number = props.lnum
    var col: number = props.col
    var length: number = props.length
    var text: string = props.text
    var highlight_text: string = has_key(props, 'highlight_text')
        ? props.highlight_text
        : 'Normal'
    var highlight_virtualtext: string = has_key(props, 'highlight_virtualtext')
        ? props.highlight_virtualtext
        : 'Normal'
    if has_key(props, 'highlight_virtualtext')
        highlight_virtualtext = props.highlight_virtualtext
    endif

    var buf: number = bufnr('%')

    if !lnum2popup_id->has_key(buf)
        listener_add(UpdatePadding, buf)
    endif

    if prop_type_list({bufnr: buf})->index('virtualText' .. lnum) == -1
        prop_type_add('virtualText' .. lnum, {
            bufnr: buf,
            highlight: highlight_text
            })
    endif
    if has_key(lnum2popup_id, buf) && has_key(lnum2popup_id[buf], lnum)
        popup_close(lnum2popup_id[buf][lnum])
    endif
    prop_add(lnum, col, {
        type: 'virtualText' .. lnum,
        length: length,
        bufnr: buf,
        })

    var left_padding: number = col([lnum, '$']) - length - col + 1

    var popup_id: number = popup_create(text, {
        line: -1,
        padding: [0, 0, 0, left_padding],
        mask: [[1, left_padding, 1, 1]],
        textprop: 'virtualText' .. lnum,
        highlight: highlight_virtualtext,
        fixed: true,
        wrap: false,
        zindex: 50 - 1,
        })
    if !has_key(lnum2popup_id, buf)
        extend(lnum2popup_id, {[buf->string()]: {}})
    endif
    extend(lnum2popup_id[buf], {[lnum->string()]: popup_id})

    return popup_id
enddef

def UpdatePadding(
    buffer: number,
    start: number,
    ...l: any
    )
    if start > line('$')
        return
    endif
    var prop_list: list<dict<any>> = start->prop_list()
    var i: number = prop_list->match('virtualText')
    if i == -1
        return
    endif
    var textprop: dict<any> = prop_list[i]
    var left_padding: number = col([start, '$']) - textprop.length - textprop.col + 1

    if !has_key(lnum2popup_id, buffer) || !has_key(lnum2popup_id[buffer], start)
        return
    endif
    var popup_id: number = lnum2popup_id[buffer][start]
    popup_setoptions(popup_id, {
        padding: [0, 0, 0, left_padding],
        mask: [[1, left_padding, 1, 1]]
        })
enddef

var lines: list<string> =<< trim END
    I met a traveller from an antique land,
    Who said—“Two vast and trunkless legs of stone
    Stand in the desert. . . . Near them, on the sand,
    Half sunk a shattered visage lies, whose frown,
    And wrinkled lip, and sneer of cold command,
    Tell that its sculptor well those passions read
    Which yet survive, stamped on these lifeless things,
    The hand that mocked them, and the heart that fed;
    And on the pedestal, these words appear:
    My name is Ozymandias, King of Kings;
    Look on my Works, ye Mighty, and despair!
    Nothing beside remains. Round the decay
    Of that colossal Wreck, boundless and bare
    The lone and level sands stretch far away.”
END
setline(1, lines)

var shattered_pos: list<number> = searchpos('shattered', 'n')
AddVirtualText({
    lnum: shattered_pos[0],
    col: shattered_pos[1],
    length: strchars('shattered'),
    text: 'broken into many pieces',
    highlight_text: 'Search',
    highlight_virtualtext: 'MoreMsg',
    })

var sneer_pos: list<number> = searchpos('sneer', 'n')
AddVirtualText({
    lnum: sneer_pos[0],
    col: sneer_pos[1],
    length: strchars('sneer'),
    text: 'a contemptuous or mocking smile, remark, or tone',
    highlight_text: 'Search',
    highlight_virtualtext: 'MoreMsg',
    })

var ozymandias_pos: list<number> = searchpos('Ozymandias', 'n')
AddVirtualText({
    lnum: ozymandias_pos[0],
    col: ozymandias_pos[1],
    length: strchars('Ozymandias'),
    text: 'Greek name for Ramesses II, pharaoh of Egypt',
    highlight_text: 'Search',
    highlight_virtualtext: 'MoreMsg',
    })

var wreck_pos: list<number> = searchpos('Wreck', 'n')
AddVirtualText({
    lnum: wreck_pos[0],
    col: wreck_pos[1],
    length: strchars('Wreck'),
    text: 'something that has been badly damaged or destroyed',
    highlight_text: 'Search',
    highlight_virtualtext: 'MoreMsg',
    })

gif


If I find other issues, I'll try to fix them in this gist.

@vim-ml
Copy link

vim-ml commented Jan 21, 2021 via email

@poetaman
Copy link

poetaman commented Jun 8, 2021

@lacygoill @brammool Is virtual-text support going to be added in Vim or is it decided to remain as a separate plugin? https://github.com/lacygoill/vim-virtual-text

@jclsn
Copy link

jclsn commented Apr 17, 2022

Vim really needs this feature. Tim Pope is writing a Github Copilot plugin and it would be so awesome if this worked in Vim as well and not just Neovim

https://github.com/github/copilot.vim

@CoelacanthusHex
Copy link

As mentioned in several of the above duplicate issues, this feature is very helpful for language development with automatic type inference like rust and haskell, mainly used to annotate the variables and return and parameter type of functions.
The inlay hint has now been promoted as part of the LSP standard by rust-analyzer, which means that the full implementation of LSP functionality requires the support of this feature.
As many other IDEs do, neovim has provided this functionality for rust users for a long time.
As a vim user, I want this feature to be part of vim and make vim better.

@brammool
Copy link
Contributor

There are references to "this feature", but I'm not sure what that is about. Please be specific.

As suggested above (more than a year ago) using a text property and attaching a popup window already comes very close.
See the help for popup-textprop-pos.
It seems the only thing needed is to position the popup not right next to the text property, but after the end of the line.
That works fine if there is enough white space, but when there isn't it gets tricky.
Does the text in the popup get truncated when there is not enough space?
When it is truncated (perhaps to just a couple of characters or a placeholder) should it show all the text on hover?
What does Neovim do when there is not enough space?
Also, does that popup overlap another window? Or perhaps only when the window is the current window?

All these questions need to be answered to be able to implement this.
I don't think a hack that ignores these questions is acceptable, later changes will then cause plugins to break.

@CoelacanthusHex
Copy link

There are references to "this feature", but I'm not sure what that is about. Please be specific.

add some virttext right next to text property or at the end of the text, The former can be used to show the type of a variable, and the latter can be used to show what type the function at the end of the line will return. The former looks as described in #9155 and the latter looks as described in this issue begin.
This is how a friend of mine wrote rust using this feature of neovim (he uses a post-version of this feature).
IMG_20220417_210819_530

As suggested above (more than a year ago) using a text property and attaching a popup window already comes very close.
See the help for popup-textprop-pos.
It seems the only thing needed is to position the popup not right next to the text property, but after the end of the line.

Whether implementing an API based on an existing API using viml, or implementing an API using C, in any case, we need a direct API instead of doing all the work ourselves.

That works fine if there is enough white space, but when there isn't it gets tricky.
Does the text in the popup get truncated when there is not enough space?
When it is truncated (perhaps to just a couple of characters or a placeholder) should it show all the text on hover?
What does Neovim do when there is not enough space?
Also, does that popup overlap another window? Or perhaps only when the window is the current window?

These issues are very necessary to discuss, and we should list them here in detail and directly to discuss and solve them together. Only when a problem is pointed out can it be solved.
And according to my personal opinion, I prefer to maintain the same behavior as normal text, that is, if normal text is to be truncated, then virttext is also truncated, and if normal text is folded, then virttext is also folded.

All these questions need to be answered to be able to implement this. I don't think a hack that ignores these questions is acceptable, later changes will then cause plugins to break.

I'm not trying to rush, I'm just stating the necessity of this thing: a lot of things are ready to rely on such a feature.

@Avimitin
Copy link

Does the text in the popup get truncated when there is not enough space?

The inlay hint in neovim work like trailing comment text. When the line is too long and user set the wrap option, the inlay hint will still attach at the end of the code.

image

It just works like the developer write a comment for the type at the end, but it is done by the LSP and the plugin.

@91khr
Copy link
Contributor

91khr commented Apr 17, 2022

@CoelacanthusHex I think maybe virttext can be treated in the same way as normal text, because most times it's used to insert some hints or to provide some information, in a more... fusing way than a popup window, i.e. appear as if they are normal text. As the name 'virtual text' indicates, they should be able to be inserted directly like normal text does, overlapping nothing, and have its own place in the text buffer.

If virttexts are rendered in a much different way than normal text, virttext's advancement that they can mix more naturally with normal text would lose, and if so, it should be enough to add some features to popup windows, e.g. utilities to simplify the control to some subtle behaviors like truncating, and some more features to allow it to be wrapped like virttext in nvim.

So I think most render facilities in Vim should treat virttext basically the same way as normal text, e.g. wrap them in the same way how normal text is wrapped, and virttext should be able to be inserted directly into the highlighting stream.

An already-existed example is the parameter name hint in IDEA:
image
Here, the definition: is the parameter name hint; when it's hard to determine the meaning of the argument, like in this screenshot, or in a more evil function invocation like CreateWindow(800, 600, CENTER, CENTER, "Hello"), the hint is useful, and apparently the hints should be inserted before the arguments, otherwise, they would make no sense.

As another example --- I don't know if there's any editors/IDEs that implement this --- a Rust language client may render the source code as:

fn f(&'_ self) -> T {
    let v : Vec<{unknown}> = vec![];
    // ...
}

where the lifetime annotation '_ and the type annotation : Vec<{unknown}> are both virttext, and they can be highlighted just like normal Rust code, except that the {unknown} marker may be processed specially.

And back to virttext itself, there are some more questions and possibilities. Virttexts may be able to be concealed in insert mode, and there may be a new option for concealing that allows virttext be concealed only if insert cursor gets very close to them (the definition of 'close' may also be able to be controlled through some options), so that users can view the hint without intentionally moving to another line or leaving insert mode, and won't get confused when editing text close to the virttext. And maybe users can be allowed to interact directly with virttexts, e.g. edit them, or they can be anchors indicating a location where a jump-to command would take the user to.

So the discussion above may be the reason why popup windows aren't enough --- they're not a part of the current buffer by design, and behave differently from normal text; and there's need for something similar to normal buffer text.

@jclsn
Copy link

jclsn commented Apr 17, 2022

There are references to "this feature", but I'm not sure what that is about. Please be specific.

Tbh I can't really say. I just read that this feature was missing on the page of the Github Copilot plugin, which is written by Tim Pope, who has been a long-time Vim plugin developer. If he says that only Neovim is supported, I believe him. I am currently still waiting for my Copilot license and could probably describe it better, when I see it. This issue just sounds very much like it. I didn't want to create a duplicate issue.

Ah the others already described it.

@chrisbra
Copy link
Member

perhaps @tpope wants to comment?

@tpope
Copy link
Contributor

tpope commented Apr 17, 2022

The reference in question is to virtual lines. The defining characteristic is that text isn't obscured: Displaying a virtual line after line 4 causes line 5, 6, etc to shift down.

@brammool
Copy link
Contributor

brammool commented Apr 17, 2022 via email

@CoelacanthusHex
Copy link

So the discussion above may be the reason why popup windows aren't enough --- they're not a part of the current buffer by design, and behave differently from normal text; and there's need for something similar to normal buffer text.

You are right, most of the time we want to treat virttext as text. But I don't think it's a good idea to let users edit virttext directly, what we need is a tool that shows the information implicit in the text itself, obviously, it should be derived using some kind of automated tool, such as linter and LSP, instead of us modifying it manually. I'd like it to be displayed as normal text (of course, in most cases differentiated by special styles (like darker)), such as truncation and line wrapping that "look"; but when processing text, like move or search and text replace, I want it as if it doesn't exist. The former is why it is called text, and the latter is why it is virt.

I also hesitate to show text that isn't actually in the file in a way
that it looks like it's part of the file. It should be obvious to the
user that this is not an actual text line from the file.

In fact here, vim just provides a mechanism. Users should know what effects the plugins they install or the code they write will do, right? In other words, when the user encounters an indistinguishable problem, he should go to the plugin to change, which is the plugin's bug, not vim itself.
If we had to consider vim's responsibility in this matter, then we probably shouldn't have added popup either, which would also allow for an indistinguishable "virt" text.
The people who make the knives are not accomplices of the murderer.

@CoelacanthusHex
Copy link

And, looking at the whole feature request, depending on the requirements, this feature can be divided into two parts, the short message used by linter, and the LSP and git-blame (which generally avoid triggering line breaks) and copilot, which will insert a large paragraph of text and will face a lot of line breaks and truncation options, as well as serious performance problems.
So maybe we can split this feature instead of implementing the full feature at once?

@91khr
Copy link
Contributor

91khr commented Apr 18, 2022

As I mentioned before, inserting screen lines without actual text is a
lot of work to get right. When adding gaps for diff mode it took quite
a while to find all places where a change was needed.

In this issue there are two kinds of virttext discussing: one for inlay hints like type/parameter name hint or git blame that would not require extra logic lines, which may cause the physical line to overflow that can already be handled well by current wrapping mechanic, and they are expected to behave in a similar way as normal buffer text; one for features such as codelens and copilot, which need extra screen lines and require gaps between buffer lines, like diff mode.

At least there may be less performance concerns for the prior one. And even if so, those who decide to use the mechanic should be ready to afford the cost; as long as there is little performance lose when the feature is not used, I think the cost may be acceptable.

As I mentioned, adding some text after the line,
be it a popup or virtual text, has problems when there is not enough
space. Lots of corner cases to take care of. Nevertheless, for quite a
few purposes, such as displaying a variable type, adding a screenline is
not the solution.

This is true, but those who calls for virttext that adds extra lines are not focusing on these purposes, but on copilot and codelens; 'inline' virttext is enough for hints, holes in proof assistants, etc., and as illustrated above, 'inline' virttext can be rendered in the same way normal text is rendered.

I also hesitate to show text that isn't actually in the file in a way
that it looks like it's part of the file. It should be obvious to the
user that this is not an actual text line from the file.

New highlight groups can be added to highlight virttext differently from normal text in the buffer. With proper color scheme, virttext can be obviously different to the user.

Personally, I think it's OK to allow plugins to change virttext's highlight, even if this would confuse users, because after all, plugins have the same privilege as the user, and if a plugin do want to do harm to users, confusing users with virttext may be the last thing to concern.

@CoelacanthusHex
Copy link

At least there may be less performance concerns for the prior one. And even if so, those who decide to use the mechanic should be ready to afford the cost; as long as there is little performance lose when the feature is not used, I think the cost may be acceptable.

From what I've seen in the neovim community and my friends who use neovim, few people complain about performance issues with simple inline virttext.

Personally, I think it's OK to allow plugins to change virttext's highlight, even if this would confuse users, because after all, plugins have the same privilege as the user, and if a plugin do want to do harm to users, confusing users with virttext may be the last thing to concern.

If a plugin deliberately confuses users, it should be reported to their code host (eg GitHub), rather than complaining that vim provides this avenue. It's important to know that tools are not sinful, it is the people who use them to do bad things that are sinful.

@91khr
Copy link
Contributor

91khr commented Apr 18, 2022 via email

@bfredl
Copy link
Contributor

bfredl commented Apr 18, 2022

But compatibility with old machines may still be a goal of vim, while nvim seems to give up this.
After all, VSCode, which uses a browser to render its editor
and provides much more features adding decorations and controls to the editor,
runs well on most modern machine.
Regretfully, I have no knowledge to vim's rendering,
thus I can't tell if the feature, even if not used, would add much overhead.

The neovim implementation makes sure, that when virtual lines are not used at all, there is no extra overhead.

@brammool
Copy link
Contributor

brammool commented Apr 18, 2022 via email

@brammool
Copy link
Contributor

brammool commented Apr 18, 2022 via email

@jclsn
Copy link

jclsn commented May 2, 2022

Once Vim9 is done I might have a go at that.

I am looking forward to it! The Gitlab Copilot plugin will give Vim code completion features of modern IDEs like CLion. I really don't think that this will work with popups or without shifting text: https://www.youtube.com/watch?v=lAYSPU2swAg

@lukepass
Copy link

I second this request, as @jclsn wrote Copilot plugin (which is very quick and useful) only works with Neovim because of the missing ghost text feature:

Copilot

Thanks.

@brammool
Copy link
Contributor

The problem with mouse positioning after concealed text should be fixed now.
Work on virtual text started, but it will take a long time before it's fully working.

@jclsn
Copy link

jclsn commented Jul 26, 2022

@brammool Cool, thank you :)

@brammool
Copy link
Contributor

brammool commented Aug 5, 2022

first version of virtual text, using text properties, has been implemented. What I tried works, but it's likely some problems will be uncovered when actually using it. Anyway, this issue can be closed. If something doesn't work well, please create a specific issue for that.

@brammool brammool closed this as completed Aug 5, 2022
@jclsn
Copy link

jclsn commented Aug 5, 2022

Awesome!

@tpope Go for it!

@lukepass
Copy link

lukepass commented Aug 5, 2022

Thanks a lot @brammool! Do you think that the Copilot plugin needs to be adjusted by @tpope or it will work automatically? Thanks again to Tim Pope too.

EDIT: I think that the plugin will require changes because it checks for Neovim:

https://github.com/github/copilot.vim/blob/release/autoload/copilot.vim#L8

@jclsn
Copy link

jclsn commented Aug 5, 2022

@lukepass I doubt that that the API is the same as in Neovim. So copilot won't work out of the box.

@lukepass
Copy link

It seems that now Copilot supports Vim's virtual text. Many thanks to @tpope and @brammool for being so active in the Vim community!

github/copilot.vim@da286d8

https://github.com/github/copilot.vim/blob/release/autoload/copilot.vim#L9

@jclsn
Copy link

jclsn commented Aug 26, 2022 via email

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests