3 ms·
Write your own terminal
- Instantnoodl 3y agoI wrote a small terminal emulator a while ago to have a portable terminal for my terminal based game. It's very specific but I had great fun with it. https://github.com/bigjk/crt https://github.com/bigjk/crt
- clemailacct1 3y agoThat terminal and even the associated game look incredible!
- deleted 3y ago[deleted]
- sotix 3y agoThis is awesome! Excited to see what you do with the project. Definitely keep us updated.
- Instantnoodl 3y agoThanks for the kind words :) It's currently in a bit of a hiatus. While the games "engine" is in a workable state it lacks content. Unfortunately I like building systems far more than actually creating game content. Might need to find a few people that are interested in helping with that at some point
- sotix 3y agoWell lucky for you I like building content but lack experience in building engines (something I’d love to learn). Happy to contribute when I have some time. Email is in my profile if you feel like reaching out.
- Instantnoodl 3y agoWhat a coincidence! In case you are also using discord there is a lonely server linked in the README. Feel free to hop in and don't worry about time. There are no strings attached. I'm in no rush and this is a fun-first project :)
- billconan 3y agoI wanted to write a terminal emulator, the biggest hurdle is understanding the escape sequences. all documents seem to be unreadable, including those mentioned in the post, and those often referenced in projects, like https://vt100.net/emu/dec_ansi_parser https://vt100.net/emu/dec_ansi_parser the second difficulty is handling reflow. a real terminal can't resize its screen, but an emulator can. how to implement that correctly with cursor movements? the third difficulty is handling font fallback and rendering emojis and other combinatory glyphs correctly.
- dilap 3y agohaven't tried, but i'd guess chatgpt (especially 4) could help a ton w/ this.
- SoftTalker 3y agoSome real terminals had a few different row/column modes, e.g. 80x24 or 132x40 (IIRC). I don't recall if text was reflowed when switching.
- jws 3y agoI've written ANSI terminal handling before. The original VT100 paper manual that came with the terminal is a nice start, because the complexity hadn't really happened yet and people used to write useful documentation. With that as a starting point for understanding it isn't hard to extend into handling the full spec. That diagram you linked is actually quite nice, but visually intimidating. If you think of it as a handful of regular expressions exploded into a state diagram that helps. For instance, the entire left half of the diagram is just the CSI code acceptor, see "CSI (Control Sequence Introducer) sequences" on the "ANSI escape code" wikipedia page. You can write a regular expression to match a CSI and carry on. This is 2023 and you aren't using an 8080 with 3k of RAM. (probably) The only tiny trick is that you have to handle the "incomplete trailing regex" and wait for more data to arrive and try again. As for handling reflow. I wouldn't call that an "implementation" problem. I'd call it a "specification" problem. I'd approach it by seeing what Apple's Terminal program does, write that down, and call that my specification.
- er4hn 3y agoMitchell Hashimoto, Hashicorps longest serving IC, has been working on his own terminal emulator as a side project: https://mitchellh.com/ghostty https://mitchellh.com/ghostty . It's been interesting to read through his logs and see how it develops along with the gnarly bugs he gets to work through.
- mtlynch 3y agoAgreed! I started reading not understanding anything about terminal emulators, and it's been interesting following his progress. >Mitchell Hashimoto, Hashicorps longest serving IC Small correction: I don't think this is right. He only became an IC two years ago.[0] [0] https://www.hashicorp.com/blog/mitchell-s-new-role-at-hashicorp https://www.hashicorp.com/blog/mitchell-s-new-role-at-hashic...
- er4hn 3y agoThat's just my bad attempt at a joke. "Longest serving code writer" may be more accurate, but IC gets lumped in as that so frequently and he publicly took that title back on. This is getting less funny the more I try to explain it..
- keithwinstein 3y agoFWIW, I wouldn't try to parse escape sequences directly from the input bytestream -- it's easy to end up with annoying edge cases. :-/ In my experience you'll thank yourself if you can separate the logic into something like: - First step (for a UTF-8-input terminal) is interpreting the input bytestream as UTF-8 and "lexing" into a stream of Unicode Scalar Values (https://www.unicode.org/versions/Unicode15.1.0/ch03.pdf#P.127 https://www.unicode.org/versions/Unicode15.1.0/ch03.pdf#P.12... ; https://github.com/mobile-shell/mosh/blob/master/src/terminal/parser.cc#L74 https://github.com/mobile-shell/mosh/blob/master/src/termina...). - Second step is "parsing" the scalar values by running them through the DEC parser/state machine. This is independent of the escape sequences (https://vt100.net/emu/dec_ansi_parser https://vt100.net/emu/dec_ansi_parser ; https://github.com/mobile-shell/mosh/blob/master/src/terminal/parserstate.cc https://github.com/mobile-shell/mosh/blob/master/src/termina...). - And then the third step is for the terminal to execute the dispatch/execute/etc. actions coming from the parser, which is where the escape sequences and control chars get implemented (https://www.vt100.net/docs/vt220-rm/ https://www.vt100.net/docs/vt220-rm/ ; https://invisible-island.net/xterm/ctlseqs/ctlseqs.html https://invisible-island.net/xterm/ctlseqs/ctlseqs.html ; https://github.com/mobile-shell/mosh/blob/master/src/terminal/terminalfunctions.cc https://github.com/mobile-shell/mosh/blob/master/src/termina...). Without this separation, it's easier to end up with bugs where, e.g., a UTF-8 sequence or an ANSI escape sequence is treated differently if it's split between read() calls (https://bugs.chromium.org/p/chromium/issues/detail?id=212702 https://bugs.chromium.org/p/chromium/issues/detail?id=212702), or invalid input isn't correctly recovered-from, etc.
- duskwuff 3y ago> - First step (for a UTF-8-input terminal) is interpreting the input bytestream as UTF-8 > - Second step is "parsing" the scalar values by running them through the DEC parser/state machine. Unfortunately, you may need to intermingle some logic between these two steps. While VT100 style control sequences are usually introduced with an ESC, they can also be represented as a C1 control sequence, e.g. 0x84 instead of ESC + D, 0x9b instead of ESC + [. These sequences are raw bytes, not Unicode codepoints, and their encoding collides unpleasantly with UTF-8 continuation characters. Further documentation: https://vt100.net/docs/vt220-rm/chapter4.html https://vt100.net/docs/vt220-rm/chapter4.html Since there's no standard which specifies how UTF-8 should interact with the terminal parser, you're a little bit on your own here. But probably the simplest fix is to introduce a special case into the UTF-8 decoder which allows stray continuation characters to be passed through to the DEC parser, rather than transforming them to replacement characters immediately.
- azinman2 3y agoThe comments here don’t seem to reflect what I think is the most interesting point here: quick loops of satisfaction. So much of programming often takes forever to get any real utility or see progress. That can really be depressing, especially for a side project. That’s what I love about cooking or sewing; you quickly see the process come together. I wish programming was like that more often.
- vidarh 3y agoYeah, I'm using my own terminal, and my own editor. The terminal also relies on a font-engine I have heavily modified (I converted the original from C to Ruby). So I "control" the whole pipeline from the editor to the actual pixels, and on one hand it has all kinds of quirks I wouldn't wish on someone else, on the other hand they all have bits and pieces that are custom-written to fit exactly what I want, and which features gets implemented are decided almost entirely based on which little change feels like it'll immediately improve my life right now (and I'm not joking - I spend enough time in front of my terminal that fixing small aspects of the terminal or my editor does feel like it is making an actual improvement in my happiness).
- wnolens 3y agoWhat do you do? That all sounds like so so much effort, I'm very curious. I'm in a terminal most of the day but never have I ever thought of once writing my own. Installing ohmyzsh was a huge step - I'm good for life now.
- 8n4vidtmkvmk 3y agoI'm constantly disappointed by my terminal. It always feels sluggish, especially with something like ohmyzsh. Boggles my mind why the whole pipeline is synchronous too.
- vidarh 3y agoDevops and development. It is effort, but it's also relaxing. I'll start by saying I don't think this is for everyone and it is far easier if you like minimalist environments. E.g. to me Visual Studio Code for example is an utter nightmare of visual overload. I want my editor to at most show a minimal one-line or less status area and maybe, optionally line numbers. Nothing more. I actively don't want it to pop up auto-completion options or documentation. And so on. A lot of my requirements makes things simpler rather than harder, and that's not necessarily the case for others. I started with the editor when I realised my Emacs config was several thousand lines and I though "I can write a whole editor in less". As it happens, a basic editor can be extremely small even in a language like C (e.g. see Antirez' "kilo"), but important: It can be extra small when you write it only for you. I can overlook bugs that doesn't bother me, and only implement functions I care about, and make assumptions about the environment others can't. E.g. my editor presumes you're running under bspwm. It can run without it, but requires adapting helper scripts, because e.g. "split horizontal" and "split vertical" are implemented by opening a container and starting a new instance of the editor that will fill that area on screen and attach to the same view (the buffers are all held in a server, similar to if you use Emacs in client server mode). It also contains no code for picking files, because it spawns an external one-liner which uses rofi to select files (same for themes etc.). After a while, I started thinking about minor nuisances as a result of my terminal. E.g. I like a high degree of translucency, but that makes text hard to read unless there's an outline, so I started hacking outlines into st and kitty, but found other things that annoyed me, and before you knew it I had a "I bet I could replace this" moment. I don't really care about packing up these for others as-is because so much is specific to how I want things, but I am slowly working my way through and pulling out generic components to release, and then I'll clean up the pase so it's at least there to look at. EDIT: Just to add that e.g. for the terminal, the entire codebase, including packaging up all dependences other than the standard Ruby library, is currently ~6.5k lines. That includes ca. 1.5k lines for the Toml parser for the config files, for example, so I think it can be trimmed down significantly. For comparison `st` is around 8k lines, excluding dependencies like Xlib, freetype etc, and Xterm is around 88k lines. That's not to crow about how tiny mine is, because a lot of it I get "for free" thanks to Ruby, and a lot of it is down to just not implementing features I don't care about. But it is an illustration of how you can start really small and make something usable when you're free to keep a really tight focus. "Just" the terminal codebase itself is about 1.5k lines. But it will fail to correctly render a bunch of apps that I don't care about because there are still escapes I haven't bothered to implement. That's fine for me, but not for a terminal for general use.
- norir 3y ago> It’s also possible to write a terminal in a terminal, like tmux, but I’d save this for my second attempt. It’s very helpful to have a place to dump logging info that’s not also the screen we’re writing to. I don't fully understand what the author is saying here or precisely what they mean by writing a terminal in a terminal. From my perspective though, it is easier to write a hosted terminal that runs inside of an existing terminal. Writing the full thing from scratch is a much harder problem. A terminal has many subproblems that are best attacked separately in my opinion. At its heart, a terminal reads formatted text from standard input and writes formatted text to standard output. It is essentially a REPL. So the first step is to write a (R)ead function. Then you pass the result of read to the (E)valuate function which will process the input and finally pass it in to the (P)rint function. If you start with a hosted terminal, the read and print functions can be modeled with posix read and write so you can devote most of your time to the evaluate function. Once you have a good evaluate function and the terminal works as you like in the hosted environment, then it makes sense to go back and write new implementations of read and write that target a new host environment. This is when it makes sense to switch to QT or opengl: when you have already implemented the core logic of the terminal and want better io performance. But it also might make sense to target html/js for maximum portability. You can either reuse or rewrite the backend that was used in the bootstrap terminal depending on how it was written. Even if you are changing languages, the rewrite should be much easier than the initial implementation since you already know what functionality is necessary and how to do it. If you start with QT or opengl, you might never even get to a useful terminal because you get so bogged down in the incidental details. What I am describing is essentially quite similar to bootstrapping a new programming language. The initial implementation should be done in the most convenient language/environment possible for the author. People commonly make the mistake of implementing a bootstrap compiler in a low level language, which is almost always a premature optimization and forces you to take on accidental complexity (such as memory management) that is secondary to your primary goals. Remember Fred Brook's advice to plan to throw the first implementation away. It is so much easier to do something that you have already done before than something new.
- vidarh 3y agoThe entire backend rendering to raw X11 calls for my personal terminals is ~160 lines of Ruby, and that includes support for oddities like double width/double height, and optimizations you can drop at first like scroll up/down (as opposed to taking the slow approach of redrawing, which is enough for a first approximation). You need very little to do the bare minimum graphical output.
- winstonrc 3y agoI built a fake terminal on my website[0]. I've been planning on building an actual one that is compiled to WASM, but it was fun building the little features such as a memory of entered commands that can be navigated by pressing up and down with the arrow keys. This looks like a great resource for me to take it to the next level. Are there any concerns I should be aware of if I were to deploy a working terminal on a website? [0] https://www.winstoncooke.com/terminal https://www.winstoncooke.com/terminal
- c-smile 3y agoJust in case, Sciter has built-in element <terminal> that can be used for various purposes. Escape codes are supported, see: https://sciter.com/wp-content/uploads/2022/10/terminal.png https://sciter.com/wp-content/uploads/2022/10/terminal.png Docs/API: https://docs.sciter.com/docs/behaviors/behavior-terminal https://docs.sciter.com/docs/behaviors/behavior-terminal
- kragen 3y agomine emulates an adm-3a, which is probably about the simplest thing already in termcap/terminfo that can support vi and curses https://gitlab.com/kragen/bubbleos/-/blob/master/yeso/admu_shell.png https://gitlab.com/kragen/bubbleos/-/blob/master/yeso/admu_s... it's 96 lines of c, which ought to remove any intimidation factor from the task; if i can do it so can you https://gitlab.com/kragen/bubbleos/-/blob/master/yeso/admu.h https://gitlab.com/kragen/bubbleos/-/blob/master/yeso/admu.h https://gitlab.com/kragen/bubbleos/-/blob/master/yeso/admu.c https://gitlab.com/kragen/bubbleos/-/blob/master/yeso/admu.c then all the crap to interface with unix braindamage and draw glyphs on the screen, without any actual terminal emulation, is another 232 lines of c https://gitlab.com/kragen/bubbleos/-/blob/master/yeso/admu-shell.c https://gitlab.com/kragen/bubbleos/-/blob/master/yeso/admu-s... yeso makes it pretty easy to get pixels on the screen (opposite extreme from 'OpenGL code is very heavy on boiler plate and repetition'), but its text handling capabilities are limited at best
- wkat4242 3y agoWhy would I want to? Terminal emulators are one of the categories of software with the most options available already. And understanding terminal codes which are historically a bunch of kludges on kludges is a guarantee for bugs. I don't think there's any terminal emulator that does it perfectly and the quirks of the most prolific ones like xterm have simply become the ad-hoc standard.
- kragen 3y agoall that is true, but writing your own terminal emulator is the first step towards fixing it
- jmmv 3y agoI can confirm that writing a terminal is fun, for the reasons mentioned in the article: it’s easy to get “self-hosting”, but then the possibilities are endless :) In my case, this was about creating the terminal for EndBASIC (https://www.endbasic.dev/ https://www.endbasic.dev/). I wanted to mix text and graphics in the same console, so I had to ditch Xterm.js and create my own thing. It was really exciting to see graphics rendering mix with text “just fine” when I was able to render the first line.
- qweqwe14 3y agoCheck out st[1] for a minimal terminal implementation. They also have user-submitted patches that you can apply to add desired functionality. [1] https://st.suckless.org https://st.suckless.org
- ishuah_ 3y agoShameless plug: I wrote an article on building a terminal emulator in 100 lines of code - https://ishuah.com/2021/03/10/build-a-terminal-emulator-in-100-lines-of-go/ https://ishuah.com/2021/03/10/build-a-terminal-emulator-in-1...
- kragen 3y agothis is cool! golang definitely seems to make this simpler than c, and less error-prone too you sure did have to import a lot of libraries, tho