Tuesday | 23 APR 2024
[ previous ]
[ next ]

Notes on using xterm.js

Title:
Date: 2022-11-07
Tags:  

Xterm.js is a easy to use terminal library in javascript. This way you can quickly get a terminal emulator going in the browser. This library is used by quite a few shell in the web type applications and the one I'm using, ttyd.

One big downside of using it though with ttyd is that sending commands via buttons is a bit of a pain in the ass. Even worse is trying to parse the output of the command that you just ran. Due to javascript and the event loop, you can't just send something to the terminal and then read the output. You need to somehow only read the terminal for data when there is data to be read.

Things are asynchronous, so there is no easy way of knowing when the output is read to be read.

I originally used setTimeout to get around the issue, I basically had a 100ms gap for every command I ran and the parsing of the output. This however makes the code much harder to reason about and nesting things becomes painful and unwieldy.

I found a solution I like more but still hacky which is to use promises. I found a javascript sleep function that you can use with async await and that solves the issue of waiting for output to appear after a command is run.

This is what the runCommand function now looks like.

const sleep = ms => new Promise(r => setTimeout(r, ms));

let termFrame = document.getElementById('terminal-frame');
setTimeout(function() {
    term = termFrame.contentWindow.term;
},500);

async function runCommand(command) {
    let output = [];
    let currentLine = term.buffer.normal.viewportY + term.buffer.normal.cursorY;

    term.paste(`${command}\n`);
    await sleep(100);

    let newLine = term.buffer.normal.viewportY + term.buffer.normal.cursorY;

    for (let i = currentLine + 1; i < newLine; i++) {
        output.push(term.buffer.normal.getLine(i).translateToString().trim());
    }

    return output.join("\n");
}

I have my terminal window in a iframe and so it requires a bit more hackery than usual.

This version of the runCommand now returns the result output concatenated. I used new line characters as I'll need to do the split further on depending on which command I ran. If the command returned newlines then I can split on that, otherwise it was all really just one line and I can split on colons.

This lets me now write some straightforward code but it still doesn't feel right. I feel like there should be some sort of event that gets triggered when the terminal updates with output. The xtermjs docs are pretty bad and require reading the actual source code for hints. I'll need to take a deeper dive into it at some point but for now it's fine.

This is all for a project to make a GUI debugger for UniVerse BASIC. The core of it is working but now its just getting more of the debugger commands wired up.