Newer
Older
tree-os / src / kernel / drivers / textMode / terminal.c
#include <cursor.h>
#include <terminal.h>
#include <util.h>

uint16_t *framebuffer = (uint16_t *)FRAMEBUFFER_LOCATION;
uint8_t currentFormat = 0x0A;

void setTextStyle(uint8_t style) { currentFormat = style; }

void setCharAtOffset(uint16_t offset, char c, uint8_t format) {
    *((uint16_t *)FRAMEBUFFER_LOCATION + offset) = format << 8 | c;
}

uint16_t getOffset(uint8_t x, uint8_t y) { return VIDEO_WIDTH * y + x; }

void clearScreen() {
    setCursorOffset(0);
    for (int y = 0; y < VIDEO_HEIGHT; y++) {
        for (int x = 0; x < VIDEO_WIDTH; x++) {
            setCharAtOffset(getOffset(x, y), ' ', currentFormat);
        }
    }
}

void shiftUp() {
    for (int y = 1; y < VIDEO_HEIGHT; y++) {
        for (int x = 0; x < VIDEO_WIDTH; x++) {
            uint16_t old = *(uint16_t *)(uintptr_t)(FRAMEBUFFER_LOCATION +
                                                    getOffset(x, y) * 2);
            setCharAtOffset(getOffset(x, y - 1), old, old >> 8);
        }
    }
    for (int x = 0; x < VIDEO_WIDTH; x++) {
        setCharAtOffset(getOffset(x, VIDEO_HEIGHT - 1), ' ', currentFormat);
    }
}

void shiftDown() {
    for (int y = VIDEO_HEIGHT - 1; y >= 0; y--) {
        for (int x = 0; x < VIDEO_WIDTH; x++) {
            uint16_t old = *(uint16_t *)(uintptr_t)(FRAMEBUFFER_LOCATION +
                                                    getOffset(x, y) * 2);
            setCharAtOffset(getOffset(x, y + 1), old, old >> 8);
        }
    }
    for (int x = 0; x < VIDEO_WIDTH; x++) {
        setCharAtOffset(x, ' ', currentFormat);
    }
}

uint16_t cursorOffset = 0;

void newLine() {
    uint8_t x = cursorOffset % VIDEO_WIDTH;
    uint8_t y = cursorOffset / VIDEO_WIDTH;
    x = 0;
    y++;
    if (y >= VIDEO_HEIGHT) {
        shiftUp();
        y--;
    }
    cursorOffset = getOffset(x, y);
    setCursorOffset(cursorOffset);
}

void clearLine() {
    uint8_t y = cursorOffset / VIDEO_WIDTH;
    for (int x = 0; x < VIDEO_WIDTH; x++) {
        setCharAtOffset(getOffset(x, y), ' ', currentFormat);
    }
}

typedef enum { STANDARD, ANSI_ESCAPE } States;

typedef enum { BRACKET_OPEN, BUFFER } AnsiEscapeStates;

States currentState = STANDARD;

AnsiEscapeStates currentAnsiEscapeState = BRACKET_OPEN;

uint32_t ansiEscapeBuffer1 = 0;
uint32_t ansiEscapeBuffer2 = 0;

uint32_t *currentBuffer = &ansiEscapeBuffer1;

uint32_t savedCursor = 0;

#define addBuffer(i)                                                           \
    case i + '0':                                                              \
        *currentBuffer *= 10;                                                  \
        *currentBuffer += i;                                                   \
        break;

#define forCurrentBuffer(func)                                                 \
    for (uint16_t i = 0; i < max(*currentBuffer, 1); i++)                      \
    func

void writeChar(char c) {
    if (c == 0)
        return;
    switch (currentState) {
    case STANDARD:
        if (c == '\x1B') {
            currentState = ANSI_ESCAPE;
            currentAnsiEscapeState = BRACKET_OPEN;
        }
        break;
    case ANSI_ESCAPE:
        switch (currentAnsiEscapeState) {
        case BRACKET_OPEN:
            currentAnsiEscapeState = BUFFER;
            currentBuffer = &ansiEscapeBuffer1;
            ansiEscapeBuffer1 = 0;
            ansiEscapeBuffer2 = 0;
            return;
        case BUFFER:
            switch (c) {
                addBuffer(0);
                addBuffer(1);
                addBuffer(2);
                addBuffer(3);
                addBuffer(4);
                addBuffer(5);
                addBuffer(6);
                addBuffer(7);
                addBuffer(8);
                addBuffer(9);
            case 'H':
                cursorOffset = 0;
                setCursorOffset(0);
                currentState = STANDARD;
                return;
            case 'J':
                clearScreen();
                currentState = STANDARD;
                return;
            case 'A':
                forCurrentBuffer({
                    if (cursorOffset < VIDEO_WIDTH) {
                        shiftDown();
                    } else {
                        cursorOffset -= VIDEO_WIDTH;
                    }
                });
                setCursorOffset(cursorOffset);
                currentState = STANDARD;
                return;
            case 'B':
                forCurrentBuffer({
                    if (cursorOffset / VIDEO_WIDTH + 1 >= VIDEO_HEIGHT) {
                        shiftUp();
                    } else {
                        cursorOffset += VIDEO_WIDTH;
                        setCursorOffset(cursorOffset);
                    }
                });
                currentState = STANDARD;
                return;
            case 'C':
                forCurrentBuffer({
                    if ((cursorOffset + 1) / VIDEO_WIDTH >= VIDEO_HEIGHT) {
                        newLine();
                    } else {
                        cursorOffset++;
                        setCursorOffset(cursorOffset);
                    }
                });
                currentState = STANDARD;
                return;
            case 'D':
                forCurrentBuffer({
                    if (cursorOffset == 0) {
                        cursorOffset = VIDEO_WIDTH - 1;
                        shiftDown();
                    } else {
                        cursorOffset--;
                    }
                });
                setCursorOffset(cursorOffset);
                currentState = STANDARD;
                return;
            case 's':
                savedCursor = cursorOffset;
                currentState = STANDARD;
                return;
            case 'u':
                cursorOffset = savedCursor;
                setCursorOffset(cursorOffset);
                currentState = STANDARD;
                return;
            case 'k':
                setCursorOffset(getOffset(0, cursorOffset / VIDEO_WIDTH));
                clearLine();
                currentState = STANDARD;
                return;
            }
        }
    }
    if (currentState != STANDARD) {
        return;
    }
    switch (c) {
    case '\n':
        newLine();
        return;
    case '\b':
        // savely move cursor left via ANSI-escape codes
        writeChar(0x1B);
        writeChar('[');
        writeChar('D');
        setCharAtOffset(cursorOffset, ' ', currentFormat);
        return;
    }
    setCharAtOffset(cursorOffset++, c, currentFormat);
    setCursorOffset(cursorOffset);
}