Need advice on how separate render the UI on lua plugin development

Hello guys!

I need some advice on how I can properly separate the UI and the logic of my plugin. I’m a bit confused on how I can do this. I’ll give a example of what I’m trying to do, a file manager plugin:

  1. Get the list of entries from the file system
  2. Show de entries on the screen rendering it using some decorations (like git files, directories, etc)
  3. Allow user to select entries (and update the UI with a decoration)
  4. Allow user to create, rename, delete entries right on the UI buffer

This are some features I’m trying to do. The logic of get files, rename, etc is already really good. But I’m having some problem separating the logic from the UI.

  • I need to couple then or there is a some better way to do it?
  • I track a table with all current entries and render it on the screen and, when the user do some action, render it again?
  • Just render everything and do all operations based on the current cursor location getting the content of that line?
  • Any other advice?

Thanks in advance for the help. I’m pretty happy doing some code in lua, really nice integration.

I think you’re mostly right on track.

Most plugins end up implementing some kind of mapping between their internal data structures and in which lines they got rendered.

Once a user wants to trigger an action on a line, you look up what entry is supposed to be at that line and then execute the associated logic.

That approach works fairly well if you control all the rendering and if it’s not possible for users to insert other content to the buffer.

Whether you re-render everything or only changed parts shouldn’t matter too much if you’re not drawing thousands of lines. I’d opt for whatever is easier in your particular use-case, both is feasible.

If content could be inserted outside of your control and you still want to keep track of where your structures got rendered you could utilize extmarks. extmarks allow you to get a mark_id for a region in a buffer. This “marked” region will move with text changes and you can either get back the original mark_id based on the region or you can find the region based on the mark_id. This allows you to create some kind of mapping between your structures and the rendered text - and compared to tracking via line numbers it has the advantage that it is resilient against changes made to the buffer.
(And therefore makes a partial re-render a lot easier)

See :help nvim_buf_set_extmark() and :help nvim_buf_get_extmarks()

In nvim-dap I make use of that and built an abstraction on top that is basically some kind of buf_set_lines, except that it works with arbitrary lua tables and a render function and lets you attach some arbitrary context to each rendered line. This context could include lua functions for example

If you’re curious and want to take a look at the code:

Some test cases illustrate how it’s used:

Maybe it helps to get some inspiration

There is also a tree abstraction built on top that allows to render hierarchical structures and provides expand and collapse functionality

2 Likes

you can check my code on

the best thing i do on that to made it live reload by separate state ui and actions.

1 Like