Back to the schedule
Previous: Tree-edit: Structural editing for Java, Python, C, and beyond!
Next: Moldable Emacs, a step towards sustainable software

Yak-shaving to a UI framework

Erik Anderson

Q&A: live Q&A or Etherpad
Duration: 9:28

If you have questions and the speaker has not indicated public contact information on this page, please feel free to e-mail us at emacsconf-submit@gnu.org and we'll forward your question to the speaker.

Talk

Q&A

Description

Name pronunciation: ERR-ick ANN-dur-sun
Pronouns: he/him
Homepage: https://github.com/ebpa/tui.el
Preferred contact info: erik@ebpa.link

Tui.el is a textual User Interface (UI) framework for Emacs Lisp modeled after the popular JavaScript 'React' framework. This package implements React Component API's with the goal of simplifying development of interactive UI's for all Emacs users- regardless of their prior experience with React or web programming. Components provide a useful functional unit for constructing complex interfaces declaratively and also eliminate much of the burden associated with updating textual content as application state changes. This talk will cover use of the tui.el API and its operation in a textual environment by implementing some basic UI's.

Discussion

Pad:

  • Q1: A common issue I have with Magit status buffers is that focus get lost easily when staging hunks since scroll gets lost during re-render (Magit attempts at recovering). Are we getting magit-tui?
    • A: It is certainly possible and compatible.
    • I am interested in tui.el but haven't looked at it too closely yet. Have been entertaining the idea of something like this for a long time now. -- jonas (magit maintainer)
  • Q2:We can update images as well?! Like SVG, or the comics you shown. This is awesome!
    • A: Yes, that's possible.
  • Q3:Have you tried to display any diagram? Like UML sequence diagrams
  • Q4: So does tui implement some sort of DOM model?
    • A: Yes.
  • Q5: How does performance compare with some other libraries, like EWOC, magit-section, tabulated-list?  e.g. to render a view with thousands of elements (and thank you for your work on this, it's very exciting for Emacs's future)
    • A: In general EWOC and tabulated-list should perform better, and tui still needs some optimization. TUI has the potential to be better, but it needs some work.
  • Q6: Are you planning to contribute tui.el to emacs core?
    • A: Eventually, once its polished and more robust.
  • Q7: What is the memory overhead like, e.g. I guess values are hashed to detect whether items need to be re-rendered?
    • A: Haven't done any memory profiling, but memory overhead could probably be an issue.
  • Q8: Awesome. Would lack of concurrency/multi-threading in Emacs be an issue?

BBB:

  • like in dogears.el readme
  • So I'm really interested in potentially using tui for Ement.el
    1. For the room buffers, showing events in the chat rooms. That sometimes has thousands of events, so that's why I asked about performance for that case.
  • It seems like it could be very helpful for re-rendering some events when their content changes, e.g. when messages are edited, when coalescing adjacent join/leave events...
  • EWOC does work for that to some extent, but I've been unable to get nested EWOCs to work correctly so far, so TUI is an interesting alternative
  • yeah, EWOC uses markers too AFAIK, and it seems to perform well enough even with 2000-3000 events in a buffer
  • oh yeah, your grid idea
  • yes, sorting and filtering, temporarily hiding elements!
  • like "show all messages from this user or mentioning that user in this room"
  • and then press a key and all the others are shown again
  • expanding larger images from thumbnails, captions for files, etc
  • like Element.io but in Emacs with TUI, that would be great
  • that's the official Matrix Web client
  • Sounds great! well thanks for the presentation, I really look forward to TUI's progress! maybe someday I can help with it, in the distant future... I have too many Emacs projects already :)
  • hmm, a TUI library for taxy.el... more ideas!
  • TUI would be like a natural frontend for taxy.el as a backend
  • are you on Matrix by any chance?
  • I'm bad with email, but when I have time to check out TUI in more detail, I look forward to it!

IRC:

  • I'm trying the run your demos of tui... it seems that (add-to-list 'load-path "~/usrc/tui.el/") is not enough, I have to either add the subdirectories by hand or to run a standard function - whose name I don't know - to add the subdirs...
  • hey, I'm trying to run your demos of tui... I had to add the subdirectories to the load-path manually to make (require 'tui-tic-tac-toe) work. my notes are here: https://0x0.st/-7dV.txt
  • tui.el is very exciting, should open up a new era of more advanced UI in Emacs
  • seems like we can get some really cool emacs ui going in combination with svg.el
  • combine with the magit approach to menus (transient etc) and something very nice is coming!
  • I think anything you can show in a buffer should work with this, so images, text, whatever.
  • tui.el is just too cool: I am going to try it for sure :D

IRC feedback: - I like the bird mascot on the repo readme :) - FYI if you would want it to show at the side of the readme, you can see the Org markup I use to accomplish that in some of my readmes

Outline

  • 5-10 minutes:
    • Problem space: UI implementation complexity.
    • API introduction: Displaying content, Components.
    • Visual taste of dashboards and applications built with tui.

Transcript

Hi! I'm Erik Anderson, and I'll be talking about tui, a user interface framework that I've written in Emacs Lisp. First, I want to talk a bit about the problem space of user interface development, specifically, I want to quickly illustrate some of the complexities involved with UI implementation in Emacs. In Emacs, we have the ubiquitous buffer object type that forms the container for most content in Emacs. Most interfaces we interact with consist of character based content in a buffer that's presented in a window in frame. Although the underlying content may be textual, Emacs has capable APIs to present rich content. The pervasiveness of buffers affords us wonderful flexibility. This presentation, for instance, is running in an Emacs buffer. Using Emacs's built-in basic button library, we can insert an interactive button that shows a message in the minibuffer when clicked. What about UIs that express application state? Most applications don't have a static UI. As application state changes, the UI should change to display the desired content. One simplifying strategy is to simply re-render the entire UI upon any change. First erase the contents of the buffer, and then reinsert your UI again with desired changes, and restore things like point and region. Basic composition is possible with this approach. Simply insert the elements of the UI in sequence. Complex elements can be composed of multiple sub-elements. UIs can be made extensible, and expose this composition, for example, with insertion hooks like magit's status sections hook. This generally relies on elements being well-behaved inserting themselves, not affecting the rest of the buffer. If we find ourselves with complex UIs, large buffers, long lines, or poor rendering performance, we might consider partial UI updates rather than re-rendering completely. In that case, the complexity for maintaining the UI quickly increases. As accessible as buffers are, we don't have high level abstractions for managing portions of a UI rendered to a buffer. (It) is left up to the programmers to track and update UI state. This is generally done by one of two methods, reflection, searching for strings or text properties within the buffer, or tracking segments of a UI buffer manually using numeric offsets, or marker, or overlay objects. Here we have a basic timer component that shows elapsed time after it's inserted. It works, but has several problems. It doesn't restore the user's point or mark, so it snaps back after every render, after every update. It relies on singleton global state, so isn't designed to coexist with other instances of itself. It doesn't use a marker, so it's sensitive to content proceeding it, that's following it, changing in the buffer. The update logic doesn't even consider which buffer it's trying to update. If I switch buffers, it will insert into another buffer, or even the minibuffer. It can't remove itself, or re-render if it gets corrupted, as you see. All in all, it's not a readily composable component. Addressing these components within this logic further increases the implementation complexity of this component, and still this component would likely have various subtle differences with other components implemented by other authors. For those of you unfamiliar with this term Yak Shaving, that is a quite technical term for any seemingly pointless activity, which is actually necessary to solve a problem, which solves a problem, which, several levels of recursion later, solves the real problem you're working on. The itch that led to this project was the desire to display a dense summary of local Git repository statuses. Encountering various implementation complexity for building UI elements, it led to the yak shaving endeavor that produced tui. When I wrote the library, I had recently played with a popular UI framework called React, and had an interest in learning about the internal architecture of React. So, rather than implement a string caching layer on top of tabulated list mode, I was rather inclined to go down the path of implementing the React API for Emacs Lisp. I'll offer a brief view of the tui Emacs Lisp API. Inserting component content is pretty straightforward. You take a component tree and render it in an Emacs buffer. If any elements in that tree are updated, their respective content on the tree is updated automatically. Here's a basic re-implementation of the straw man timer from earlier, using a macro for syntactic trigger. You'll notice that the signature includes its own object reference, arguments, and state. Associating arguments in the state with a component instance out of the box, makes it easy to design reusable components, and forms the basis for partial UI updates. The component rendering anchors are durable, so content can be added and removed surrounding content, or even within the region of the component, and replaced when it re-renders. Components will also cleanly remove themselves from a buffer when instructed to. tui contains the core implementation of the React API, so components, their constituent props, state, and all of the lifecycle methods associated with them, as well as keys, refs, and the fundamental reconciliation algorithm of React. A variety of other React APIs that haven't been implemented yet. It contains some useful features so far, such as hot reloading, reflection, and various debugging tools, and some reconciliation logging. Lastly, I'd like to give you some quick visual taste of components built with tui. The grid view that motivated my development of this package is very similar to Magit's list repositories functionality. Essentially, tabulated list mode but portable and has a separated model and presentation layers. Here's a basic xkcd comic viewer showing a couple classics. A long-standing React tutorial is building a tic-tac-toe game as a bit of a gimmick, and I'm not quite satisfied with the buffering direction, but it got me thinking about layout engines with text, so it was interesting. And here's a small Unicode character viewer capable of showing a bunch of characters. If this piques your interest, I would encourage you to check it out. tui should be usable by anyone with some basic Elisp familiarity, no prior knowledge about JavaScript or React is necessary. I'd absolutely love to talk with people about the tui package, textual user interfaces in general, and really anything in Emacs. If you have any ideas, feedback, or want to contribute, please reach out. Thank you all for listening. captions by bhavin192 (Bhavin Gandhi)

Back to the schedule
Previous: Tree-edit: Structural editing for Java, Python, C, and beyond!
Next: Moldable Emacs, a step towards sustainable software