Har du noen gang vært nysgjerrig på hvordan en terminal-emulator fungerer? Ikke? Det er fullstendig forståelig. Jeg, derimot, jeg har klart å la meg fange i en verden som kan spores tilbake til langt før den moderne datamaskinen så dagens lys, der enkle konsepter kombineres for å lage avanserte verktøy!
Hva er prosjektet?
En terminal-emulator. Altså en software-implementasjon av terminalene som ble brukt på 1980-tallet, eller det vi populært refererer til som "terminalen", "shellet", "CLIet", og så videre i dag. Det verktøyet mange av oss bruker til å komme gjennom hverdagen som utvikler.
Hva inspirerte deg til å lage det?
Definitivt ikke mangel på eksisterende alternativer i alle fall. Å lage en terminal-emulator er ganske langt i fra nybrotsarbeid eller noen form for entreprenørvirksomhet, de finnes i alle ulike former og størrelser fra før av. Til og med av den mer moderne sorten med innebygget AI.
Så hva er det da som inspirerer meg? Det koker nok ned til nysgjerrighet. Terminalen er nærmest et slags paradox for meg: til tross for stadig kraftigere maskiner, mer avanserte operativsystem, nyvinninger innen grensesnitt, og en teknologiverden som raser avgårde i 200 på autobahn, så spiller fortsatt terminalen en helt sentral rolle i mange utvikleres hverdag. Jeg bruker terminalen til å håndtere filer, jobbe med versjonskontroll, bygge kode, starte utviklingsmiljøer og programmer, snakke med servere, rulle ut kode, og ikke minst for å skrive denne bloggposten. For å nevne noe. Hvis jeg kan løse en oppgave med terminalen, så velger jeg veldig ofte å gjøre det. Det er nok dette regnestykket som jeg ikke klarer helt å få til å gå opp som gjør at jeg blir så fascinert av terminalen.
Som utvikler elsker jeg å forstå ting. Så hva er vel da mer naturlig enn å lage en terminal-emulator fra bunnen av for å klare å forstå hvordan det hele henger sammen?
Hva er tech-stacken?
La meg først og fremst forklare litt nærmere hvilken del av det vi ser på som terminalen jeg lager. Når vi starter en terminal vil den bestå av flere ulike deler, som hver har sitt eget ansvarsområde:
- Den grafiske delen – altså den du ser på – som stort sett består av farget tekst. Av og til lar den oss skrive inn kommandoer og spytter tilbake resultatet, og av og til tar den over hele vinduet og blir et slags interaktivt program. Det er denne delen som kalles for en terminal-emulator
- Shellet, som kanskje best kan beskrives som "backenden", eller "motoren" som utfører oppgaver basert på teksten vi sender inn og tastene vi trykker på.
- En kommunikasjonskanal som ligger mellom terminal-emulatoren og shellet, og sender signaler mellom dem slik at de kan fungere sammen. Det er dette som gjør at ulike terminaler kan fungere med ulike shell. Den er opprettet og styres av operativsystemet.
Jeg lager utelukkende den første delen: en terminal-emulator. Ikke et shell, og ikke "limet" mellom disse to.
Teknologimessig kan det vel nesten ikke engang kalles en stack, for den består utelukkende av Rust. Jeg bruker noen lavnivå bibliotek for å gjøre systemkall. Når man starter en ny terminal ber man operativsystemet om å få opprettet en ny PTY ("kommunikasjonskanalen") og gjør innstillinger på den gjennom en rekke systemkall. For å lage det grafiske grensesnittet bruker jeg et Rust-bibliotek som heter iced. Dette er ett av helt utrolig mange GUI-rammeverk som prøver å vise muskler i Rust-sfæren. Det er flere andre rammeverk som har både mer traction og er mer modent, men iced fanget oppmerksomheten min da de reklamerte med at de forsøker å gjenskape The Elm Architecture for å bygge grafiske grensesnitt i Rust.
Og det er stort sett det. Utenom dette er det meste skrevet selv, for hånd, ved å lese dokumentasjon og diskutere med Claude.ai.
Hva er en ting du har lært?
Tro det eller ei, men det er helt vanvittig mye! Det hele har vært et prosjekt som startet med at jeg ikke kunne Rust, hadde veldig lite erfaring med systemprogrammering, og på ingen måte var utlært om the inner workings of terminaler. Men for å holde det hele litt kort og konsist, her er noen av de viktigste punktene jeg sitter igjen med så langt
- Rust. Jeg har lært meg å bli glad i Rust. Selvtilliten kompilatoren gir meg på at koden fungerer er helt uovertruffen alt annet jeg har vært borti. En kraftull kompilator gir meg byggetidsgarantier for at koden jeg har skrevet fungerer og passer på at jeg ikke gjør operasjoner som kan føre til kjøretidsfeil. Jeg merker at jeg savner denne hjelpen kompilatoren gir meg når jeg til daglig koder i Kotlin og TypeScript!
- Arkeologi. Terminal-emulatorer er nok det nærmeste vi kommer arkeologi i data-verdenen, med tanke på at de fysiske terminalene var en slags forløper til, og på mange måter har formet, datamaskinene vi bruker i dag. Det er nok å dra det langt å si at dette er veldig nyttig kunnskap som hjelper meg i hverdagen, men om ikke annet får man meg seg mye fun facts underveis!
- ANSI-sekvenser. Helt klart noe av det snevreste her, men også noe av det jeg synes er mest interessant! Jeg har tidligere skrevet at "protokollen" mellom en terminal og shellet er ren tekst. Eller bytes, da. Det viser seg at i disse bytesene som flyter fram og tilbake er det kodet inn ANSI-sekvenser, eller bestemte sekvenser av tegn som tolkes som operasjoner i stedet for synlig tekst. Ønsker du å skrive "Hei" med rød tekst? Da kan du f.eks. kjøre kommandoen
echo "\e[31mHei"
i terminalen. Det stopper ikke her. Det finnes slike sekvenser for å flytte markøren rundt på skjermen, fjerne tekst som allerede er synlig på skjermen, gjemme markøren, endre hvordan input tolkes, endre stil på markøren, lytte på museklikk, og helt ekstremt mye annet. De er heldigvis godt dokumenterte, og her har også ChatGPT (eller Claude, som jeg har falt mer og mer for) vist seg å være en enormt god ressurs for å forstå hvordan de bør implementeres.
Hele koden er tilgjengelig på Github, men jeg lover ingen former for best practice eller forståelig kode!