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


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

Make a good plan of your data structures. This is usually the stem from which the rest of the code spreads.

I develop some plugins myself, and I’ve been doing this successfully since 2019. I’m now using a glance of classical software engineering and I’m sure you would benefit from some ideas I’ve been using. Here is one, use specific files meant for you and other developers, in which you will describe in detail the aspects of the logic, as well as the data structures. Take a look at this.

This file contains the information about the data structure I’m currently using for one of my plugins. I wrote it back in 2019, and because of it, I was able to resume my work after a whole year halt because of pandemics.

Besides, after the code is done, I usually use a lot of its contents to write the user documentation, so It’s definitely worth a shot.

I hope this helps,
Itamar Soares