punity logo large

PuniTY

PuniTY, an editor extension that offers an integrated terminal for Unity3D on Windows, Mac and Linux. To me, one of the great missing featutes in the Unity3D editor. It’s a work in progress project in an attempt to add a pseudo-terminal (PTY) interface into a Unity3D EditorWindow. Let’s see, just how deep, this rabbit hole goes…

Rationale

The idea for PuniTY, which is a play-on-words for Unity and pseudo-terminal (PTY), has been on my mind for a very, very long time. I’m one of those guys who tries to use the terminal for anything and everything, which of course includes Git. The ‘problem’ however is that, to use Git, I need to context switch between the Unity3D Editor and my terminal interface.

This has been bugging me for a long time and thus I set out to create my own pseudo-terminal (pty) implementation just like modern IDE’s have. For example; in JetBrains’ IDE’s you can access the terminal directly from within the IDE environment – that’s a pty, VSCode also has this feature. Furthermore, a very well known pty is Putty, hence the name 😉

So, I went on this quest to implement my very own pty and… Oh Boy, that escalated quickly 😀

I spend the good time of a year working on this thing. This might sound like a very long time but in reality I can only spend about an hour per week on my passion projects due to other commitments.

PuniTY is far from done but still I want to document up to the point I’m at now. So, join me on this journey to the center of the earth…

DevLog

As I mentioned, this all started when I wanted to write some Git commands directly in Unity3D. Usually I use the pty provided in my IDE (JetBrains Rider) for interacting with Git. Writing some sort of git integration always seemed like fun little side project to me but why stop there? Having a fully phledged terminal interface in Unity3D seemed far more interesting.

So I started experimenting by starting a `Bash` process through a very simple `System.Diagnostics.Process`. This of course, works perfectly fine. I could send commands to this process pretty easily by redirecting I/O. Very easy… I only needed to add a simple UI around it and I was done. Perfect.

But then… These previous commands were all very simple, one-off, fire and forget, kind of commands. What about some more interactive scenario’s like rebasing? So, I tried that and this wasn’t really successful. I could chain commands but not really setup an interactive flow.

Another scenario I tried was using VIM or Nano. Starting the process is very simple yet how do we exit such process? Well for Nano you need to use the CTRL key as a modifier. How do we send such combination over to the process? Well, actually pretty easily. You can send the ascii encoded string over to the terminal as described here. This works fine, but the System.Diagnostics.Process will throw a very lovely exception saying:`something something cannot use as terminal`. So this is where I hit a wall. I need the terminal to be interactive, otherwise, what’s the point? It’s not usable without such functionality.

This is where the event-horizon of the rabit hole begins…

rabithole AI

Before I knew, I found myself up to my neck in the multi-verse of the classic VT100 and ANSI control sequences. ANSI control sequences are basically specific strings of text to send commands to a terminal, which inherently is designed to be controlled by text. Such sequences control for example text color, scroll-regions, screen buffers and cursor movements.

I used the specs from the xterm terminal as reference material for implementing PuniTY. They can be found here. Yes, this specification is HUGE!!! I implemented most of the sequences listed there although, I’m not done just yet. Some sequences toggle certain functionality in the terminal like enable printing of the screen, which I have not implemented yet. But the command is being parsed and the sequence gets executed but its just a stub method without implementation.

So how then does PuniTY work exactly? Well I found this neat little project called VS-PTY which is used to implement the integrated terminal emulator in VSCode. I tried to get this running, cross-platform… However there are some limitations in Mono that don’t support it. Luckily, Modern DotNet allows for building AOT compiled executables which was perfect for me.

So I basically strapped a very thin TCP layer in front of the terminal over which the client (Unity editor extension) can communicate to the server, the PuniTY terminal. This way I’m able to send streams of byte[] across. And so, I’m able to run commands from Unity, on the terminal emulator.

When the client, Unity3D implementation, receives a response from the server, PuniTY terminal I pass the byte[] through my Ansi Control sequence parser and act upon the events that get executed. That’s more or less how it works…

The Ansi Control sequence parser consists out of a number of main components;

  • EscapeCharacterDecoder: which decodes a stream of bytes, detects Ansi escape codes, converts them into commands + parameters
  • Sequences: Sequences are mapped to the commands + parameters and based on the control character the command is executed with the given parameters
  • Screen: The screen is an abstraction that handles all screen related sequences like, adding characters, cursor movements and colors
  • Input: an aggregate for the Mouse (pointer) and keyboard to pass along input from the UI->client->server

These components make up what I call the AnsiContext which can be used as a means for dependency injection to downstream components. There are some other less important components but I wont describe them here.

Then of course there is the UI part which is just a UI-Toolkit based Editor window.

The terminal is currently not really functional but I’ve written over 600 tests to prove the functionality that is there, works as expected. Well, as far as I can understand it and my ever so friendly AI assistents can help me. Yes, certainly…

I’m looking forward to further develop PuniTY in the near future, but for now I would like to work on several smaller passion projects I have been pushing on my backlog for the past year.

To be continued…