Printf TUI Debugging
I’ve been working on my TUI spreadsheet program, neoscim quite a bit recently. In this process I had to do some debugging, as you would with any program. However since this is a TUI application, it throws a wrench in the whole “throw a print statement in there” method of debugging. “Why” You may ask? This is because when we are running a normal CLI or GUI application we have multiple output mediums. For a CLI, there is no visual output, so everything goes to the terminal, causing no issues with “printf” debugging. For a GUI application, you have your UI - as the name implies - graphically rendered to another window, leaving your terminal open for printf debugging.
Now the fun 3rd option: TUIs.
TUI (Terminal User Interface) is like a CLI (Command Line Interface) in that it only takes a terminal to run (something as simple as a tty (the thing that you get to when pressing Ctrl Alt F1-7 on most Unix machines)).
You may use common ones such as alsamixer or nmtui for managing audio or networks on your Linux machine.
However, when you want to write some debug statements, how do you do that?
The UI is the terminal, so if you place a print!("{}") statement (neoscim is written in Rust), it will mangle the UI.
We could send the logs to a server like Loki, but that would be too complex for some simple debugging.
We can’t even do a eprint!("{}") (print on stderr), since stdout and stderr both go to the terminal.
If only there was another way…
Guess what! There’s another way! We can use some cool techniques learned in my other post to move data streams all around the system. I’ll show you how:
What we had before:
# Run the program (simple)
$ neoscim
But since the program is put into stdout which is rendered together with stderr, they can mangle each other. Step 1 of this solution is putting our debug statements into stderr. This way debug messages go to stderr, and the UI goes to stdout. Now we can run the program with some redirection:
$ neoscim 2> file.log
This fixes the mangling issue!
But we sill have a problem.
I’m not getting my log statements in real time.
We can now implement a fancy thing called a fifo, aka a First-In-First-Out queue.
This is actually really simple in Linux (since everything is a file!)
$ mkfifo queue
$ ls
queue
$ neoscim 2> queue
As you can see here; mkfifo creates a “file” (named queue) which is actually a fifo queue.
Now when neoscim runs, it will take a hold of stdout to render the UI and stderr will get redirected into the “file” queue.
However, this will prevent neoscim from launching!
Why?
Because writing to the fifo will block the writing program until the queue also a a reader.
So we then open another terminal and type:
$ cat queue
And now the messages start flowing!