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

API/UI: ui_event_extmark #15674

Merged
merged 1 commit into from
May 3, 2022
Merged

API/UI: ui_event_extmark #15674

merged 1 commit into from
May 3, 2022

Conversation

yatli
Copy link
Contributor

@yatli yatli commented Sep 15, 2021

No description provided.

@justinmk
Copy link
Member

ui_call_win_extmarks(..) in win_update seems noisy. Also seems a bit special case. Some alternative ideas:

  • clients/subscribers can "attach" a grid, analogous to nvim_buf_attach()
  • or make it a global event like nvim_error_event.
    • this will also be noisy, so we will need to introduce a subscription/opt-in mechanism for all clients. (Precedent: nvim_ui_attach())

@bfredl
Copy link
Member

bfredl commented Sep 16, 2021

@justinmk not doing it in win_update would completely defeat the purpose of this PR though. The UI wants to know at ui_flush() that the extmark positions is in sync with the grid positions, so it can present a correct screen state to the user directly, including extmark tagged elements (and not have extmarks glitch around whenever a secondary event stream is flushed).

Also this is the first WIP draft from someone who is relatively new to the C code (and a pretty good one, for that). Let's take some time to clean it up and simplify some things (like the "lookless" stuff should just be reduced into ordinary decorations), before passing it off as too special and noisy and attempting a different implementation from scratch, which are bound to have other complexities, like re-calculating all positions which simply are just there for us for free inside the win_update code paths.

src/nvim/decoration.c Outdated Show resolved Hide resolved
@justinmk

This comment has been minimized.

@yatli
Copy link
Contributor Author

yatli commented Sep 16, 2021

@justinmk initially I proposed exactly that ext_extmark to bfredl :p
The reason for choosing the current path is that, the UI may not be interested in all extmarks so it can choose to opt-in fine-grained on some namespaces. e.g. one namespace for pictures, one for collaborative editing guest cursors, and so on, but not codelens, in-line git blame, lsp error msgs etc. which are handled by other plugins.

@justinmk
Copy link
Member

justinmk commented Sep 16, 2021

initially I proposed exactly that ext_extmark to bfredl :p
The reason for choosing the current path is that, the UI may not be interested in all extmarks so it can choose to opt-in fine-grained on some namespaces. e.g. one namespace for pictures, one for collaborative editing guest cursors,

Makes sense. Keeping it mind that nothing I say is blocking this PR, I wonder if ...

  • we should not introduce "watch" as a concept since we already have "attach". IOW, rename "watch" to "attach" here?
    • or use "subscribe" or "listen" instead of "watch".
    • or rename nvim_buf_attach to nvim_buf_watch? (Or explain what is the difference?)
  • are we actually watching a namespace, not extmarks? Thus nvim_attach_namespace ?
  • should nvim_ui_watch_extmark be channel-agnostic (not only for UIs)?

Thus the signature might be:

/// Subscribes a channel or Lua callbacks to events in namespace {ns_id}.
///
/// @param ns_id Namespace to subscribe to.
/// @param ui If true, data is published in the UI redraw.win_extmark, redraw.win_highlight events.
/// @param opt option keys:
///            - events: list of events in namespace {ns_id} to listen to, any
///              of: "extmark", "highlight"
///            - on_extmark: Lua callback invoked on namespaced extmark updates.
///            - on_highlight: Lua callback invoked on namespaced highlight updates.
/// @return false if ns_id is invalid, else true
void nvim_attach_namespace(uint64_t channel_id, Integer ns_id, Dictionary opt) 

@yatli
Copy link
Contributor Author

yatli commented Sep 16, 2021

In my mind, "watch" is for a specific type of message, and "attach" is like a whole-sale solution -- attach to buffer means to receive everything related to the buffer.
We can say "nvim_attach_namespace" but it's a different scope than what I have done here.
(or, just use lua/vimL on CursorHold/TextChanged etc. to list the extmarks and problem solved)

Also, this is ui-specific (hence nvim_ui_watch_extmarks) because I'm all interested in the calculated on-screen grid positions of marks, not just the buffer positions -- this information cannot be obtained anywhere else.
Letting non-ui plugins to receive such messages probably goes across the abstraction layers (does the non-ui plugins ever handle anything "grid"?).

Edit: "watch" doesn't carry the idea that we want grid cell positions either... Thoughts?

@yatli
Copy link
Contributor Author

yatli commented Sep 16, 2021

@bfredl a few thoughts for dc0e7fa:

I understand that non-text marks should be filtered as early as possible, so I moved it out from draw_virt_text to decor_add.
Since DecorRange actually copies out the Decoration, it's easy to do the filtering, and conditionally set a new field ui_watched to indicate that the ui client is interested in this mark.

"lookless" stuff has been merged with ordinary decors. Debugged this a few rounds, the "lookless marks" do not have altpos (set to -1). The decor adding predicates are updated accordingly.

TODO: can get even earlier? -- put ui_watched in ExtMarkItem and compute this on mark creation/update, and watch updates.

src/nvim/api/ui_events.in.h Outdated Show resolved Hide resolved
src/nvim/screen.c Outdated Show resolved Hide resolved
src/nvim/screen.c Outdated Show resolved Hide resolved
@yatli
Copy link
Contributor Author

yatli commented Sep 18, 2021

@bfredl 9875086 fixes corner cases mentioned above.
Specifically, if we are redrawing the whole window, we are not sure about the previous extmarks state and a redraw-all is not a scroll, so the client cannot infer correctly -- win_extmarks_clear should be sent.

@yatli
Copy link
Contributor Author

yatli commented Sep 18, 2021

one more edge case fixed -- when the viewport scrolls up but there's no text updates (all empty lines), invalidate extmarks.

@yatli
Copy link
Contributor Author

yatli commented Sep 18, 2021

but there are still more. Higher order ones 🙀
Like, have two windows of the same buffer, insert <cr> to push a mark out of both viewports.
The focused one gets clear marks event, the other one doesn't, until going back to normal mode 😅
@bfredl could you help?

@yatli
Copy link
Contributor Author

yatli commented Sep 24, 2021

my conclusion is that it's not appropriate to send win_extmarks_clear in win_update because it's incremental and does not have the full information of the window.

@bfredl
Copy link
Member

bfredl commented Sep 24, 2021

that's fine. as extmarks are anchored to cell grids, they will be cleared when the line is redrawn, and should also scroll with the line (i e it should be treated as an extra attribute to the cell).

@yatli
Copy link
Contributor Author

yatli commented Sep 24, 2021

A frontend can implement something like this:

yatli/fvim@6619106

so there are two sets of tracked extmarks -- one is for those associated with the on-screen cells, and the other is for scrolled-out ones which can be tracked with win_viewport.

This allows image display clipped by window top.

@yatli
Copy link
Contributor Author

yatli commented Sep 26, 2021

@bfredl rebased on virtual lines.

@yatli yatli force-pushed the ui_event_extmark branch 2 times, most recently from 9783934 to ac368e8 Compare September 28, 2021 19:14
@yatli yatli force-pushed the ui_event_extmark branch 3 times, most recently from 5c29ad6 to cd2690f Compare October 12, 2021 05:28
@yatli yatli changed the title [wip] ui_event_extmark [RDY] ui_event_extmark Nov 3, 2021
src/nvim/decoration.c Outdated Show resolved Hide resolved
runtime/doc/ui.txt Outdated Show resolved Hide resolved
src/nvim/screen.c Outdated Show resolved Hide resolved
@bfredl
Copy link
Member

bfredl commented Apr 25, 2022

@yatli would you mind write a simple test or two? I'm happy to give guidance as needed.

@yatli
Copy link
Contributor Author

yatli commented Apr 25, 2022

Wilco. I was waiting for the shape of this PR to settle down before moving to tests, and it looks like the time is now.

@yatli
Copy link
Contributor Author

yatli commented Apr 26, 2022

@bfredl how do I receive events from a screen(ui)?
helpers.expect_msg_seq?

I see there's also screen:expect_unchanged(..., request_cb)
Also session:run(...)

Not sure which one to use.

Edit:
Hmm screen:expect has its own notification_cb that handles redraw. Not a good idea to intercept.

@yatli look here in case you forget:

test/functional/ui/screen.lua:L477 function Screen:_wait(check, flags)
test/functional/ui/screen.lua:L516 local function notification_cb(method, args)
test/functional/ui/screen.lua:L617 function Screen:_redraw(updates)

@bfredl
Copy link
Member

bfredl commented Apr 26, 2022

@yatli indeed. instead of trying to "intercept" screen.lua, we have to implement support there, perhaps not too unlike how a "real" UI would. so we should write a function like Screen:_handle_win_extmarks (actually should be singular: win_extmark ) and save state in a new self variable. then we can assert the value after event processing is done, like for other extended attributes.

@yatli
Copy link
Contributor Author

yatli commented Apr 27, 2022

@bfredl 224bbc2

nothing goes into self._grid_win_extmarks... ?
The handler is not reached.

@bfredl
Copy link
Member

bfredl commented Apr 27, 2022

@yatli try screen:redraw_debug() before the expect. Then we can see the exact stream of events (if the event is missing or malformatted, or something)

@yatli
Copy link
Contributor Author

yatli commented Apr 27, 2022

...
{ "win_viewport", { 2, {
      id = 1000,
      <metatable> = <1>{
        __index = <table 1>,
        new = <function 1>
      }
    }, 0, 3, 1, 14, 2 } }
{ "grid_line", { 1, 0, 0, { { "n", 0 }, { "o" }, { "n" }, { " " }, { "u" }, { "i" }, { "-" }, { "w" }, { "a" }, { "t" }, { "c" }, { "h" }, { "e" }, { "d" }, { " " }, { "l" }, { "i" }, { "n" }, { "e" } } }, { 1, 1, 0, { { "u", 0 }, { "i" }, { "-" }, { "w" }, { "a" }, { "t" }, { "c" }, { "h" }, { "e" }, { "d" }, { " " }, { "l" }, { "i" }, { "n" }, { "e" } } }, { 1, 2, 0, { { "~", 7 }, { " ", 7, 19 } } }, { 1, 3, 0, { { " ", 1, 20 } } } }
{ "win_viewport", { 2, {
      id = 1000,
      <metatable> = <1>{
        __index = <table 1>,
        new = <function 1>
      }
    }, 0, 3, 1, 14, 2 } }
{ "grid_cursor_goto", { 1, 1, 14 } }
{ "flush", {} }

screen:expect{grid=[[
  non ui-watched line |
  ui-watched lin^e     |
  {1:~                   }|
                      |
]], attr_ids={
  [1] = {foreground = Screen.colors.Blue1, bold = true};
}}

{ "mode_change", { "normal", 0 } }
{ "mouse_off", {} }
{ "flush", {} }

screen:expect{grid=[[
  non ui-watched line |
  ui-watched lin^e     |
  {1:~                   }|
                      |
]], attr_ids={
  [1] = {foreground = Screen.colors.Blue1, bold = true};
}}

{ "win_extmark", { 2, {
      id = 1000,
      <metatable> = <1>{
        __index = <table 1>,
        new = <function 1>
      }
    }, 1, 1, 0, 20 } }
{ "win_viewport", { 2, {
      id = 1000,
      <metatable> = <1>{
        __index = <table 1>,
        new = <function 1>
      }
    }, 0, 3, 1, 14, 2 } }
{ "flush", {} }

screen:expect{grid=[[
  non ui-watched line |
  ui-watched lin^e     |
  {1:~                   }|
                      |
]], attr_ids={
  [1] = {foreground = Screen.colors.Blue1, bold = true};
}}

The event is present.
I tried to add error('hey') in _handle_win_extmark but that didn't trigger an error.

@yatli
Copy link
Contributor Author

yatli commented Apr 27, 2022

oka removed table.remove and added redraw_debug or sleep and the msg loop is pumped correctly.

@yatli
Copy link
Contributor Author

yatli commented Apr 27, 2022

@bfredl tests ready

@yatli yatli force-pushed the ui_event_extmark branch 2 times, most recently from 235b669 to 612c3fe Compare April 27, 2022 16:55
@yatli
Copy link
Contributor Author

yatli commented May 3, 2022

Done the changes.
The errors (of not having sleep and unchanged) are gone now.

Is that what you mean by "checked multiple times"? That the error should not break the execution flow.

@bfredl
Copy link
Member

bfredl commented May 3, 2022

yes. it will check against the conditions each time there is a "flush" event. so it must be prepared to check state multiple times. than screen:except can work "async" all by itself.

src/nvim/screen.c Outdated Show resolved Hide resolved
@bfredl bfredl merged commit 4df1146 into neovim:master May 3, 2022
@bfredl
Copy link
Member

bfredl commented May 3, 2022

Thanks, merged!

We might want more docs/tests for how to properly interact with grid_scroll et al, though that could be a follow up. This is good enough for UI:s to start using it.

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

Successfully merging this pull request may close these issues.

None yet

4 participants