root/src/apps/terminal/TermParse.cpp
/*
 * Copyright 2001-2024, Haiku, Inc. All rights reserved.
 * Copyright (c) 2003-4 Kian Duffy <myob@users.sourceforge.net>
 * Parts Copyright (C) 1998,99 Kazuho Okui and Takashi Murai.
 * Distributed under the terms of the MIT license.
 *
 * Authors:
 *              Kian Duffy, myob@users.sourceforge.net
 *              Simon South, simon@simonsouth.net
 *              Siarzhuk Zharski, zharik@gmx.li
 */


//! Escape sequence parse and character encoding.


#include "TermParse.h"

#include <ctype.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <string.h>
#include <unistd.h>

#include <Autolock.h>
#include <Beep.h>
#include <Catalog.h>
#include <Locale.h>
#include <Message.h>
#include <UTF8.h>

#include "Colors.h"
#include "TermConst.h"
#include "TerminalBuffer.h"
#include "VTparse.h"


extern int gUTF8GroundTable[];          /* UTF8 Ground table */
extern int gISO8859GroundTable[];       /* ISO8859 & EUC Ground table */
extern int gWinCPGroundTable[];         /* Windows cp1252, cp1251, koi-8r */
extern int gSJISGroundTable[];          /* Shift-JIS Ground table */

extern int gEscTable[];                         /* ESC */
extern int gCsiTable[];                         /* ESC [ */
extern int gDecTable[];                         /* ESC [ ? */
extern int gScrTable[];                         /* ESC # */
extern int gIgnoreTable[];                      /* ignore table */
extern int gIesTable[];                         /* ignore ESC table */
extern int gEscIgnoreTable[];           /* ESC ignore table */

extern const char* gLineDrawGraphSet[]; /* may be used for G0, G1, G2, G3 */

#define DEFAULT -1
#define NPARAM 10               // Max parameters


//! Get char from pty reader buffer.
inline uchar
TermParse::_NextParseChar()
{
        if (fParserBufferOffset >= fParserBufferSize) {
                // parser buffer empty
                status_t error = _ReadParserBuffer();
                if (error != B_OK)
                        throw error;
        }

#ifdef USE_DEBUG_SNAPSHOTS
        fBuffer->CaptureChar(fParserBuffer[fParserBufferOffset]);
#endif

        return fParserBuffer[fParserBufferOffset++];
}


TermParse::TermParse(int fd)
        :
        fFd(fd),
        fParseThread(-1),
        fReaderThread(-1),
        fReaderSem(-1),
        fReaderLocker(-1),
        fBufferPosition(0),
        fReadBufferSize(0),
        fParserBufferSize(0),
        fParserBufferOffset(0),
        fBuffer(NULL),
        fQuitting(true)
{
        memset(fReadBuffer, 0, READ_BUF_SIZE);
        memset(fParserBuffer, 0, ESC_PARSER_BUFFER_SIZE);
}


TermParse::~TermParse()
{
        StopThreads();
}


status_t
TermParse::StartThreads(TerminalBuffer *buffer)
{
        if (fBuffer != NULL)
                return B_ERROR;

        fQuitting = false;
        fBuffer = buffer;

        status_t status = _InitPtyReader();
        if (status < B_OK) {
                fBuffer = NULL;
                return status;
        }

        status = _InitTermParse();
        if (status < B_OK) {
                _StopPtyReader();
                fBuffer = NULL;
                return status;
        }

        return B_OK;
}


status_t
TermParse::StopThreads()
{
        if (fBuffer == NULL)
                return B_ERROR;

        fQuitting = true;

        _StopPtyReader();
        _StopTermParse();

        fBuffer = NULL;

        return B_OK;
}


//! Initialize and spawn EscParse thread.
status_t
TermParse::_InitTermParse()
{
        if (fParseThread >= 0)
                return B_ERROR; // we might want to return B_OK instead ?

        fParseThread = spawn_thread(_escparse_thread, "EscParse",
                B_DISPLAY_PRIORITY, this);

        if (fParseThread < 0)
                return fParseThread;

        resume_thread(fParseThread);

        return B_OK;
}


//! Initialize and spawn PtyReader thread.
status_t
TermParse::_InitPtyReader()
{
        if (fReaderThread >= 0)
                return B_ERROR; // same as above

        fReaderSem = create_sem(0, "pty_reader_sem");
        if (fReaderSem < 0)
                return fReaderSem;

        fReaderLocker = create_sem(0, "pty_locker_sem");
        if (fReaderLocker < 0) {
                delete_sem(fReaderSem);
                fReaderSem = -1;
                return fReaderLocker;
        }

        fReaderThread = spawn_thread(_ptyreader_thread, "PtyReader",
                B_NORMAL_PRIORITY, this);
        if (fReaderThread < 0) {
                delete_sem(fReaderSem);
                fReaderSem = -1;
                delete_sem(fReaderLocker);
                fReaderLocker = -1;
                return fReaderThread;
        }

        resume_thread(fReaderThread);

        return B_OK;
}


void
TermParse::_StopTermParse()
{
        if (fParseThread >= 0) {
                status_t dummy;
                wait_for_thread(fParseThread, &dummy);
                fParseThread = -1;
        }
}


void
TermParse::_StopPtyReader()
{
        if (fReaderSem >= 0) {
                delete_sem(fReaderSem);
                fReaderSem = -1;
        }
        if (fReaderLocker >= 0) {
                delete_sem(fReaderLocker);
                fReaderLocker = -1;
        }

        if (fReaderThread >= 0) {
                suspend_thread(fReaderThread);

                status_t status;
                wait_for_thread(fReaderThread, &status);

                fReaderThread = -1;
        }
}


//! Get data from pty device.
int32
TermParse::PtyReader()
{
        int32 bufferSize = 0;
        int32 readPos = 0;
        while (!fQuitting) {
                // If Pty Buffer nearly full, snooze this thread, and continue.
                while (READ_BUF_SIZE - bufferSize < MIN_PTY_BUFFER_SPACE) {
                        status_t status;
                        do {
                                status = acquire_sem(fReaderLocker);
                        } while (status == B_INTERRUPTED);
                        if (status < B_OK)
                                return status;

                        bufferSize = fReadBufferSize;
                }

                // Read PTY
                uchar buf[READ_BUF_SIZE];
                ssize_t nread = read(fFd, buf, READ_BUF_SIZE - bufferSize);
                if (nread <= 0) {
                        fBuffer->NotifyQuit(errno);
                        return B_OK;
                }

                // Copy read string to PtyBuffer.

                int32 left = READ_BUF_SIZE - readPos;

                if (nread >= left) {
                        memcpy(fReadBuffer + readPos, buf, left);
                        memcpy(fReadBuffer, buf + left, nread - left);
                } else
                        memcpy(fReadBuffer + readPos, buf, nread);

                bufferSize = atomic_add(&fReadBufferSize, nread);
                if (bufferSize == 0)
                        release_sem(fReaderSem);

                bufferSize += nread;
                readPos = (readPos + nread) % READ_BUF_SIZE;
        }

        return B_OK;
}


void
TermParse::DumpState(int *groundtable, int *parsestate, uchar c)
{
        static const struct {
                int *p;
                const char *name;
        } tables[] = {
#define T(t) \
        { t, #t }
                T(gUTF8GroundTable),
                T(gISO8859GroundTable),
                T(gWinCPGroundTable),
                T(gSJISGroundTable),
                T(gEscTable),
                T(gCsiTable),
                T(gDecTable),
                T(gScrTable),
                T(gIgnoreTable),
                T(gIesTable),
                T(gEscIgnoreTable),
                { NULL, NULL }
        };
        int i;
        fprintf(stderr, "groundtable: ");
        for (i = 0; tables[i].p; i++) {
                if (tables[i].p == groundtable)
                        fprintf(stderr, "%s\t", tables[i].name);
        }
        fprintf(stderr, "parsestate: ");
        for (i = 0; tables[i].p; i++) {
                if (tables[i].p == parsestate)
                        fprintf(stderr, "%s\t", tables[i].name);
        }
        fprintf(stderr, "char: 0x%02x (%d)\n", c, c);
}


int *
TermParse::_GuessGroundTable(int encoding)
{
        switch (encoding) {
                case B_ISO1_CONVERSION:
                case B_ISO2_CONVERSION:
                case B_ISO3_CONVERSION:
                case B_ISO4_CONVERSION:
                case B_ISO5_CONVERSION:
                case B_ISO6_CONVERSION:
                case B_ISO7_CONVERSION:
                case B_ISO8_CONVERSION:
                case B_ISO9_CONVERSION:
                case B_ISO10_CONVERSION:
                case B_ISO13_CONVERSION:
                case B_ISO14_CONVERSION:
                case B_ISO15_CONVERSION:
                case B_EUC_CONVERSION:
                case B_EUC_KR_CONVERSION:
                case B_JIS_CONVERSION:
                case B_BIG5_CONVERSION:
                        return gISO8859GroundTable;

                case B_KOI8R_CONVERSION:
                case B_MS_WINDOWS_1251_CONVERSION:
                case B_MS_WINDOWS_CONVERSION:
                case B_MAC_ROMAN_CONVERSION:
                case B_MS_DOS_866_CONVERSION:
                case B_GBK_CONVERSION:
                case B_MS_DOS_CONVERSION:
                        return gWinCPGroundTable;

                case B_SJIS_CONVERSION:
                        return gSJISGroundTable;

                case M_UTF8:
                default:
                        break;
        }

        return gUTF8GroundTable;
}


int32
TermParse::EscParse()
{
        int top = 0;
        int bottom = 0;

        char cbuf[4] = { 0 };
        char dstbuf[4] = { 0 };

        int currentEncoding = -1;

        int param[NPARAM];
        int nparam = 1;
        for (int i = 0; i < NPARAM; i++)
                param[i] = DEFAULT;

        int row = 0;
        int column = 0;

        // default encoding system is UTF8
        int *groundtable = gUTF8GroundTable;
        int *parsestate = gUTF8GroundTable;

        // handle alternative character sets G0 - G4
        const char** graphSets[4] = { NULL, NULL, NULL, NULL };
        int curGL = 0;
        int curGR = 0;

        BAutolock locker(fBuffer);

        while (!fQuitting) {
                try {
                        uchar c = _NextParseChar();

                        //DumpState(groundtable, parsestate, c);

                        if (currentEncoding != fBuffer->Encoding()) {
                                // Change coding, change parse table.
                                groundtable = _GuessGroundTable(fBuffer->Encoding());
                                parsestate = groundtable;
                                currentEncoding = fBuffer->Encoding();
                        }

        //debug_printf("TermParse: char: '%c' (%d), parse state: %d\n", c, c, parsestate[c]);
                        int32 srcLen = 0;
                        int32 dstLen = sizeof(dstbuf);
                        int32 dummyState = 0;

                        switch (parsestate[c]) {
                                case CASE_PRINT:
                                {
                                        int curGS = c < 128 ? curGL : curGR;
                                        const char** curGraphSet = graphSets[curGS];
                                        if (curGraphSet != NULL) {
                                                int offset = c - (c < 128 ? 0x20 : 0xA0);
                                                if (offset >= 0 && offset < 96
                                                        && curGraphSet[offset] != 0) {
                                                        fBuffer->InsertChar(curGraphSet[offset]);
                                                        break;
                                                }
                                        }
                                        fBuffer->InsertChar((char)c);
                                        break;
                                }
                                case CASE_PRINT_GR:
                                {
                                        /* case iso8859 gr character, or euc */
                                        switch (currentEncoding) {
                                                case B_EUC_CONVERSION:
                                                case B_EUC_KR_CONVERSION:
                                                case B_JIS_CONVERSION:
                                                case B_BIG5_CONVERSION:
                                                        cbuf[srcLen++] = c;
                                                        c = _NextParseChar();
                                                        cbuf[srcLen++] = c;
                                                        break;

                                                case B_GBK_CONVERSION:
                                                        cbuf[srcLen++] = c;
                                                        do {
                                                                // GBK-compatible codepoints are 2-bytes long
                                                                c = _NextParseChar();
                                                                cbuf[srcLen++] = c;

                                                                // GB18030 extends GBK with 4-byte codepoints
                                                                // using 2nd byte from range 0x30...0x39
                                                                if (srcLen == 2 && (c < 0x30 || c > 0x39))
                                                                        break;
                                                        } while (srcLen < 4);
                                                        break;

                                                default: // ISO-8859-1...10 and MacRoman
                                                        cbuf[srcLen++] = c;
                                                        break;
                                        }

                                        if (srcLen > 0) {
                                                int encoding = currentEncoding == B_JIS_CONVERSION
                                                        ? B_EUC_CONVERSION : currentEncoding;

                                                convert_to_utf8(encoding, cbuf, &srcLen,
                                                                dstbuf, &dstLen, &dummyState, '?');

                                                fBuffer->InsertChar(UTF8Char(dstbuf, dstLen));
                                        }
                                        break;
                                }

                                case CASE_LF:
                                        fBuffer->InsertLF();
                                        break;

                                case CASE_CR:
                                        fBuffer->InsertCR();
                                        break;

                                case CASE_INDEX:
                                        fBuffer->InsertLF();
                                        parsestate = groundtable;
                                        break;

                                case CASE_NEXT_LINE:
                                        fBuffer->NextLine();
                                        parsestate = groundtable;
                                        break;

                                case CASE_SJIS_KANA:
                                        cbuf[srcLen++] = c;
                                        convert_to_utf8(currentEncoding, cbuf, &srcLen,
                                                        dstbuf, &dstLen, &dummyState, '?');
                                        fBuffer->InsertChar(UTF8Char(dstbuf, dstLen));
                                        break;

                                case CASE_SJIS_INSTRING:
                                        cbuf[srcLen++] = c;
                                        c = _NextParseChar();
                                        cbuf[srcLen++] = c;

                                        convert_to_utf8(currentEncoding, cbuf, &srcLen,
                                                        dstbuf, &dstLen, &dummyState, '?');
                                        fBuffer->InsertChar(UTF8Char(dstbuf, dstLen));
                                        break;

                                case CASE_UTF8_2BYTE:
                                        cbuf[srcLen++] = c;
                                        c = _NextParseChar();
                                        if (groundtable[c] != CASE_UTF8_INSTRING)
                                                break;
                                        cbuf[srcLen++] = c;

                                        fBuffer->InsertChar(UTF8Char(cbuf, srcLen));
                                        break;

                                case CASE_UTF8_3BYTE:
                                        cbuf[srcLen++] = c;

                                        do {
                                                c = _NextParseChar();
                                                if (groundtable[c] != CASE_UTF8_INSTRING) {
                                                        srcLen = 0;
                                                        break;
                                                }
                                                cbuf[srcLen++] = c;

                                        } while (srcLen != 3);

                                        if (srcLen > 0)
                                                fBuffer->InsertChar(UTF8Char(cbuf, srcLen));
                                        break;

                                case CASE_SCS_STATE:
                                {
                                        int set = -1;
                                        switch (c) {
                                                case '(':
                                                        set = 0;
                                                        break;
                                                case ')':
                                                case '-':
                                                        set = 1;
                                                        break;
                                                case '*':
                                                case '.':
                                                        set = 2;
                                                        break;
                                                case '+':
                                                case '/':
                                                        set = 3;
                                                        break;
                                                default:
                                                        break;
                                        }

                                        if (set > -1) {
                                                char page = _NextParseChar();
                                                switch (page) {
                                                        case '0':
                                                                graphSets[set] = gLineDrawGraphSet;
                                                                break;
                                                        default:
                                                                graphSets[set] = NULL;
                                                                break;
                                                }
                                        }

                                        parsestate = groundtable;
                                        break;
                                }

                                case CASE_GROUND_STATE:
                                        /* exit ignore mode */
                                        parsestate = groundtable;
                                        break;

                                case CASE_BELL:
                                        beep();
                                        break;

                                case CASE_BS:
                                        fBuffer->MoveCursorLeft(1);
                                        break;

                                case CASE_TAB:
                                        fBuffer->InsertTab();
                                        break;

                                case CASE_ESC:
                                        /* escape */
                                        parsestate = gEscTable;
                                        break;

                                case CASE_IGNORE_STATE:
                                        /* Ies: ignore anything else */
                                        parsestate = gIgnoreTable;
                                        break;

                                case CASE_IGNORE_ESC:
                                        /* Ign: escape */
                                        parsestate = gIesTable;
                                        break;

                                case CASE_IGNORE:
                                        /* Ignore character */
                                        break;

                                case CASE_LS1:
                                        /* select G1 into GL */
                                        curGL = 1;
                                        parsestate = groundtable;
                                        break;

                                case CASE_LS0:
                                        /* select G0 into GL */
                                        curGL = 0;
                                        parsestate = groundtable;
                                        break;

                                case CASE_SCR_STATE:    // ESC #
                                        /* enter scr state */
                                        parsestate = gScrTable;
                                        break;

                                case CASE_ESC_IGNORE:
                                        /* unknown escape sequence */
                                        parsestate = gEscIgnoreTable;
                                        break;

                                case CASE_ESC_DIGIT:    // ESC [ number
                                        /* digit in csi or dec mode */
                                        if ((row = param[nparam - 1]) == DEFAULT)
                                                row = 0;
                                        param[nparam - 1] = 10 * row + (c - '0');
                                        break;

                                case CASE_ESC_SEMI:             // ESC ;
                                        /* semicolon in csi or dec mode */
                                        if (nparam < NPARAM)
                                                param[nparam++] = DEFAULT;
                                        break;

                                case CASE_CSI_SP: // ESC [N q
                                        // part of change cursor style DECSCUSR
                                        if (nparam < NPARAM)
                                                param[nparam++] = ' ';
                                        break;

                                case CASE_CSI_EXCL: // ESC [N p
                                        // part of soft reset DECSTR
                                        if (nparam < NPARAM)
                                                param[nparam++] = '!';
                                        break;

                                case CASE_DEC_DOL: // ESC [? N p
                                        // part of change cursor style DECRQM
                                        if (nparam < NPARAM)
                                                param[nparam++] = '$';
                                        break;

                                case CASE_DEC_STATE:
                                        /* enter dec mode */
                                        parsestate = gDecTable;
                                        break;

                                case CASE_ICH:          // ESC [@ insert charactor
                                        /* ICH */
                                        if ((row = param[0]) < 1)
                                                row = 1;
                                        fBuffer->InsertSpace(row);
                                        parsestate = groundtable;
                                        break;

                                case CASE_CUU:          // ESC [A cursor up, up arrow key.
                                        /* CUU */
                                        if ((row = param[0]) < 1)
                                                row = 1;
                                        fBuffer->MoveCursorUp(row);
                                        parsestate = groundtable;
                                        break;

                                case CASE_CUD:          // ESC [B cursor down, down arrow key.
                                        /* CUD */
                                        if ((row = param[0]) < 1)
                                                row = 1;
                                        fBuffer->MoveCursorDown(row);
                                        parsestate = groundtable;
                                        break;

                                case CASE_CUF:          // ESC [C cursor forword
                                        /* CUF */
                                        if ((row = param[0]) < 1)
                                                row = 1;
                                        fBuffer->MoveCursorRight(row);
                                        parsestate = groundtable;
                                        break;

                                case CASE_CUB:          // ESC [D cursor backword
                                        /* CUB */
                                        if ((row = param[0]) < 1)
                                                row = 1;
                                        fBuffer->MoveCursorLeft(row);
                                        parsestate = groundtable;
                                        break;

                                case CASE_CUP:          // ESC [...H move cursor
                                        /* CUP | HVP */
                                        if ((row = param[0]) < 1)
                                                row = 1;
                                        if (nparam < 2 || (column = param[1]) < 1)
                                                column = 1;

                                        fBuffer->SetCursor(column - 1, row - 1 );
                                        parsestate = groundtable;
                                        break;

                                case CASE_ED:           // ESC [ ...J clear screen
                                        /* ED */
                                        switch (param[0]) {
                                                case DEFAULT:
                                                case 0:
                                                        fBuffer->EraseBelow();
                                                        break;

                                                case 1:
                                                        fBuffer->EraseAbove();
                                                        break;

                                                case 2:
                                                        fBuffer->EraseAll();
                                                        break;

                                                case 3:
                                                        fBuffer->EraseScrollback();
                                                        break;
                                        }
                                        parsestate = groundtable;
                                        break;

                                case CASE_EL:           // ESC [ ...K delete line
                                        /* EL */
                                        switch (param[0]) {
                                                case DEFAULT:
                                                case 0:
                                                        fBuffer->DeleteColumns();
                                                        break;

                                                case 1:
                                                        fBuffer->EraseCharsFrom(0, fBuffer->Cursor().x + 1);
                                                        break;

                                                case 2:
                                                        fBuffer->DeleteColumnsFrom(0);
                                                        break;
                                        }
                                        parsestate = groundtable;
                                        break;

                                case CASE_IL:
                                        /* IL */
                                        if ((row = param[0]) < 1)
                                                row = 1;
                                        fBuffer->InsertLines(row);
                                        parsestate = groundtable;
                                        break;

                                case CASE_DL:
                                        /* DL */
                                        if ((row = param[0]) < 1)
                                                row = 1;
                                        fBuffer->DeleteLines(row);
                                        parsestate = groundtable;
                                        break;

                                case CASE_DCH:
                                        /* DCH */
                                        if ((row = param[0]) < 1)
                                                row = 1;
                                        fBuffer->DeleteChars(row);
                                        parsestate = groundtable;
                                        break;

                                case CASE_SET:
                                        /* SET */
                                        if (param[0] == 4)
                                                fBuffer->SetInsertMode(MODE_INSERT);
                                        parsestate = groundtable;
                                        break;

                                case CASE_RST:
                                        /* RST */
                                        if (param[0] == 4)
                                                fBuffer->SetInsertMode(MODE_OVER);
                                        parsestate = groundtable;
                                        break;

                                case CASE_SGR:
                                {
                                        /* SGR */
                                        Attributes attributes = fBuffer->GetAttributes();
                                        for (row = 0; row < nparam; ++row) {
                                                switch (param[row]) {
                                                        case DEFAULT:
                                                        case 0: /* Reset attribute */
                                                                attributes.Reset();
                                                                break;

                                                        case 1: /* Bold     */
                                                        case 5:
                                                                attributes |= BOLD;
                                                                break;

                                                        case 4: /* Underline    */
                                                                if ((row + 1) < nparam) {
                                                                        row++;
                                                                        switch (param[row]) {
                                                                                case 0:
                                                                                        attributes.UnsetUnder();
                                                                                        break;
                                                                                case 1:
                                                                                        attributes.SetUnder(SINGLE_UNDERLINE);
                                                                                        break;
                                                                                case 2:
                                                                                        attributes.SetUnder(DOUBLE_UNDERLINE);
                                                                                        break;
                                                                                case 3:
                                                                                        attributes.SetUnder(CURLY_UNDERLINE);
                                                                                        break;
                                                                                case 4:
                                                                                        attributes.SetUnder(DOTTED_UNDERLINE);
                                                                                        break;
                                                                                case 5:
                                                                                        attributes.SetUnder(DASHED_UNDERLINE);
                                                                                        break;
                                                                                default:
                                                                                        row = nparam; // force exit of the parsing
                                                                                        break;
                                                                        }
                                                                } else
                                                                        attributes.SetUnder(SINGLE_UNDERLINE);
                                                                break;

                                                        case 7: /* Inverse      */
                                                                attributes |= INVERSE;
                                                                break;

                                                        case 8: /* Hidden       */
                                                                attributes |= HIDDEN;
                                                                break;

                                                        case 21:        /* Double Underline     */
                                                                attributes.SetUnder(DOUBLE_UNDERLINE);
                                                                break;

                                                        case 22:        /* Not Bold     */
                                                                attributes &= ~BOLD;
                                                                break;

                                                        case 24:        /* Not Underline        */
                                                                attributes.UnsetUnder();
                                                                break;

                                                        case 27:        /* Not Inverse  */
                                                                attributes &= ~INVERSE;
                                                                break;

                                                        case 28:        /* Not Hidden   */
                                                                attributes &= ~HIDDEN;
                                                                break;

                                                        case 53:        /* Overline     */
                                                                attributes |= OVERLINE;
                                                                break;

                                                        case 55:        /* Not Overline */
                                                                attributes &= ~OVERLINE;
                                                                break;

                                                        case 90:
                                                        case 91:
                                                        case 92:
                                                        case 93:
                                                        case 94:
                                                        case 95:
                                                        case 96:
                                                        case 97:
                                                                param[row] -= 60;
                                                        case 30:
                                                        case 31:
                                                        case 32:
                                                        case 33:
                                                        case 34:
                                                        case 35:
                                                        case 36:
                                                        case 37:
                                                                attributes.SetIndexedForeground(param[row] - 30);
                                                                break;

                                                        case 38:
                                                        {
                                                                if (nparam >= 3 && param[row+1] == 5) {
                                                                        attributes.SetIndexedForeground(param[row+2]);
                                                                        row += 2;
                                                                } else if (nparam >= 5 && param[row+1] == 2) {
                                                                        attributes.SetDirectForeground(param[row+2], param[row+3], param[row+4]);
                                                                        row += 4;
                                                                } else {
                                                                        row = nparam; // force exit of the parsing
                                                                }

                                                                break;
                                                        }

                                                        case 39:
                                                                attributes.UnsetForeground();
                                                                break;

                                                        case 100:
                                                        case 101:
                                                        case 102:
                                                        case 103:
                                                        case 104:
                                                        case 105:
                                                        case 106:
                                                        case 107:
                                                                param[row] -= 60;
                                                        case 40:
                                                        case 41:
                                                        case 42:
                                                        case 43:
                                                        case 44:
                                                        case 45:
                                                        case 46:
                                                        case 47:
                                                                attributes.SetIndexedBackground(param[row] - 40);
                                                                break;

                                                        case 48:
                                                        {
                                                                if (nparam >= 3 && param[row+1] == 5) {
                                                                        attributes.SetIndexedBackground(param[row+2]);
                                                                        row += 2;
                                                                } else if (nparam >= 5 && param[row+1] == 2) {
                                                                        attributes.SetDirectBackground(param[row+2], param[row+3], param[row+4]);
                                                                        row += 4;
                                                                } else {
                                                                        row = nparam; // force exit of the parsing
                                                                }

                                                                break;
                                                        }

                                                        case 49:
                                                                attributes.UnsetBackground();
                                                                break;

                                                        case 58:
                                                        {
                                                                if (nparam >= 3 && param[row+1] == 5) {
                                                                        attributes.SetIndexedUnderline(param[row+2]);
                                                                        row += 2;
                                                                } else if (nparam >= 5 && param[row+1] == 2) {
                                                                        attributes.SetDirectUnderline(param[row+2], param[row+3], param[row+4]);
                                                                        row += 4;
                                                                } else {
                                                                        row = nparam; // force exit of the parsing
                                                                }

                                                                break;
                                                        }

                                                        case 59:
                                                                attributes.UnsetUnderline();
                                                                break;
                                                }
                                        }
                                        fBuffer->SetAttributes(attributes);
                                        parsestate = groundtable;
                                        break;
                                }

                                case CASE_CPR:
                                // Q & D hack by Y.Hayakawa (hida@sawada.riec.tohoku.ac.jp)
                                // 21-JUL-99
                                _DeviceStatusReport(param[0]);
                                parsestate = groundtable;
                                break;

                                case CASE_DA1:
                                // DA - report device attributes
                                if (param[0] < 1) {
                                        // claim to be a VT102
                                        write(fFd, "\033[?6c", 5);
                                }
                                parsestate = groundtable;
                                break;

                                case CASE_DECSTBM:
                                /* DECSTBM - set scrolling region */

                                if ((top = param[0]) < 1)
                                        top = 1;

                                if (nparam < 2)
                                        bottom = fBuffer->Height();
                                else
                                        bottom = param[1];

                                top--;
                                        bottom--;

                                        if (bottom > top)
                                                fBuffer->SetScrollRegion(top, bottom);

                                        parsestate = groundtable;
                                        break;

                                case CASE_DECSCUSR_ETC:
                                // DECSCUSR - set cursor style VT520
                                        if (nparam == 2 && param[1] == ' ') {
                                                bool blinking = (param[0] & 0x01) != 0;
                                                int style = -1;
                                                switch (param[0]) {
                                                        case 0:
                                                                blinking = true;
                                                        case 1:
                                                        case 2:
                                                                style = BLOCK_CURSOR;
                                                                break;
                                                        case 3:
                                                        case 4:
                                                                style = UNDERLINE_CURSOR;
                                                                break;
                                                        case 5:
                                                        case 6:
                                                                style = IBEAM_CURSOR;
                                                                break;
                                                }

                                                if (style != -1) {
                                                        fBuffer->SetCursorStyle(style);
                                                        if (blinking)
                                                                fBuffer->SetMode(MODE_CURSOR_BLINKING);
                                                        else
                                                                fBuffer->ResetMode(MODE_CURSOR_BLINKING);
                                                }
                                        }
                                        parsestate = groundtable;
                                        break;

                                case CASE_DECREQTPARM:
                                        // DEXREQTPARM - request terminal parameters
                                        _DecReqTermParms(param[0]);
                                        parsestate = groundtable;
                                        break;

                                case CASE_DECSET:
                                        /* DECSET */
                                        for (int i = 0; i < nparam; i++)
                                                _DecPrivateModeSet(param[i]);
                                        parsestate = groundtable;
                                        break;

                                case CASE_DECRST:
                                        /* DECRST */
                                        for (int i = 0; i < nparam; i++)
                                                _DecPrivateModeReset(param[i]);
                                        parsestate = groundtable;
                                        break;

                                case CASE_DECALN:
                                        /* DECALN */
                                        {
                                                Attributes attr;
                                                fBuffer->FillScreen(UTF8Char('E'), attr);
                                                parsestate = groundtable;
                                        }
                                        break;

                                        //      case CASE_GSETS:
                                        //              screen->gsets[scstype] = GSET(c) | cs96;
                                        //              parsestate = groundtable;
                                        //              break;

                                case CASE_DECSC:
                                        /* DECSC */
                                        fBuffer->SaveCursor();
                                        parsestate = groundtable;
                                        break;

                                case CASE_DECRC:
                                        /* DECRC */
                                        fBuffer->RestoreCursor();
                                        parsestate = groundtable;
                                        break;

                                case CASE_HTS:
                                        /* HTS */
                                        fBuffer->SetTabStop(fBuffer->Cursor().x);
                                        parsestate = groundtable;
                                        break;

                                case CASE_TBC:
                                        /* TBC */
                                        if (param[0] < 1)
                                                fBuffer->ClearTabStop(fBuffer->Cursor().x);
                                        else if (param[0] == 3)
                                                fBuffer->ClearAllTabStops();
                                        parsestate = groundtable;
                                        break;

                                case CASE_RI:
                                        /* RI */
                                        fBuffer->InsertRI();
                                        parsestate = groundtable;
                                        break;

                                case CASE_SS2:
                                        /* SS2 */
                                        parsestate = groundtable;
                                        break;

                                case CASE_SS3:
                                        /* SS3 */
                                        parsestate = groundtable;
                                        break;

                                case CASE_CSI_STATE:
                                        /* enter csi state */
                                        nparam = 1;
                                        param[0] = DEFAULT;
                                        parsestate = gCsiTable;
                                        break;

                                case CASE_OSC:
                                        {
                                                /* Operating System Command: ESC ] */
                                                uchar params[512];
                                                // fill the buffer until BEL, ST or something else.
                                                bool isParsed = false;
                                                int32 skipCount = 0; // take care about UTF-8 characters
                                                for (uint i = 0; !isParsed && i < sizeof(params); i++) {
                                                        params[i] = _NextParseChar();

                                                        if (skipCount > 0) {
                                                                skipCount--;
                                                                continue;
                                                        }

                                                        skipCount = UTF8Char::ByteCount(params[i]) - 1;
                                                        if (skipCount > 0)
                                                                continue;

                                                        switch (params[i]) {
                                                                // BEL
                                                                case 0x07:
                                                                        isParsed = true;
                                                                        break;
                                                                // 8-bit ST
                                                                case 0x9c:
                                                                        isParsed = true;
                                                                        break;
                                                                // 7-bit ST is "ESC \"
                                                                case '\\':
                                                                // hm... Was \x1b replaced by 0 during parsing?
                                                                        if (i > 0 && params[i - 1] == 0) {
                                                                                isParsed = true;
                                                                                break;
                                                                        }
                                                                default:
                                                                        if (!isprint(params[i] & 0x7f))
                                                                                break;
                                                                        continue;
                                                        }
                                                        params[i] = '\0';
                                                }

                                                // watchdog for the 'end of buffer' case
                                                params[sizeof(params) - 1] = '\0';

                                                if (isParsed)
                                                        _ProcessOperatingSystemControls(params);

                                                parsestate = groundtable;
                                                break;
                                        }

                                case CASE_RIS:          // ESC c ... Reset terminal.
                                        break;

                                case CASE_LS2:
                                        /* select G2 into GL */
                                        curGL = 2;
                                        parsestate = groundtable;
                                        break;

                                case CASE_LS3:
                                        /* select G3 into GL */
                                        curGL = 3;
                                        parsestate = groundtable;
                                        break;

                                case CASE_LS3R:
                                        /* select G3 into GR */
                                        curGR = 3;
                                        parsestate = groundtable;
                                        break;

                                case CASE_LS2R:
                                        /* select G2 into GR */
                                        curGR = 2;
                                        parsestate = groundtable;
                                        break;

                                case CASE_LS1R:
                                        /* select G1 into GR */
                                        curGR = 1;
                                        parsestate = groundtable;
                                        break;

                                case CASE_VPA:          // ESC [...d move cursor absolute vertical
                                        /* VPA (CV) */
                                        if ((row = param[0]) < 1)
                                                row = 1;

                                        // note beterm wants it 1-based unlike usual terminals
                                        fBuffer->SetCursorY(row - 1);
                                        parsestate = groundtable;
                                        break;

                                case CASE_HPA:          // ESC [...G move cursor absolute horizontal
                                        /* HPA (CH) */
                                        if ((column = param[0]) < 1)
                                                column = 1;

                                        // note beterm wants it 1-based unlike usual terminals
                                        fBuffer->SetCursorX(column - 1);
                                        parsestate = groundtable;
                                        break;

                                case CASE_SU:   // scroll screen up
                                        if ((row = param[0]) < 1)
                                                row = 1;
                                        fBuffer->ScrollBy(row);
                                        parsestate = groundtable;
                                        break;

                                case CASE_SD:   // scroll screen down
                                        if ((row = param[0]) < 1)
                                                row = 1;
                                        fBuffer->ScrollBy(-row);
                                        parsestate = groundtable;
                                        break;


                                case CASE_ECH:  // erase characters
                                        if ((column = param[0]) < 1)
                                                column = 1;
                                        fBuffer->EraseChars(column);
                                        parsestate = groundtable;
                                        break;

                                case CASE_CBT:  // cursor back tab
                                        if ((column = param[0]) < 1)
                                                column = 1;
                                        fBuffer->InsertCursorBackTab(column);
                                        parsestate = groundtable;
                                        break;

                                case CASE_CFT:  // cursor forward tab
                                        if ((column = param[0]) < 1)
                                                column = 1;
                                        for (int32 i = 0; i < column; ++i)
                                                fBuffer->InsertTab();
                                        parsestate = groundtable;
                                        break;

                                case CASE_CNL:  // cursor next line
                                        if ((row = param[0]) < 1)
                                                row = 1;
                                        fBuffer->SetCursorX(0);
                                        fBuffer->MoveCursorDown(row);
                                        parsestate = groundtable;
                                        break;

                                case CASE_CPL:  // cursor previous line
                                        if ((row = param[0]) < 1)
                                                row = 1;
                                        fBuffer->SetCursorX(0);
                                        fBuffer->MoveCursorUp(row);
                                        parsestate = groundtable;
                                        break;

                                case CASE_REP:          // ESC [...b repeat last graphic char
                                {
                                        int repetitions = param[0];
                                        if (repetitions < 1)
                                                repetitions = 1;
                                        int maxRepetitions = fBuffer->Width() * fBuffer->Height();
                                        if (repetitions > maxRepetitions)
                                                repetitions = maxRepetitions;
                                        for (int i = 0; i < repetitions; i++)
                                                fBuffer->InsertLastChar();
                                        parsestate = groundtable;
                                        break;
                                }

                                case CASE_DECRQM:       // DECRQM - request mode to terminal
                                        if (nparam == 2 && param[1] == '$') {
                                                _DecPrivateModeRequest(param[0]);
                                        }
                                        parsestate = groundtable;
                                        break;

                                case CASE_DECSTR:       // DECSTR - soft terminal reset
                                        if (nparam == 2 && param[1] == '!') {
                                                Attributes attributes;
                                                fBuffer->SetAttributes(attributes);
                                        }
                                        parsestate = groundtable;
                                        break;

                                default:
                                        break;
                        }
                } catch (...) {
                        break;
                }
        }

        return B_OK;
}


/*static*/ int32
TermParse::_ptyreader_thread(void *data)
{
        return reinterpret_cast<TermParse *>(data)->PtyReader();
}


/*static*/ int32
TermParse::_escparse_thread(void *data)
{
        return reinterpret_cast<TermParse *>(data)->EscParse();
}


status_t
TermParse::_ReadParserBuffer()
{
        // We have to unlock the terminal buffer while waiting for data from the
        // PTY. We don't have to unlock when we don't need to wait, but we do it
        // anyway, so that TermView won't be starved when trying to synchronize.
        fBuffer->Unlock();

        // wait for new input from pty
        if (atomic_get(&fReadBufferSize) == 0) {
                status_t status = B_OK;
                while (atomic_get(&fReadBufferSize) == 0 && status == B_OK) {
                        do {
                                status = acquire_sem(fReaderSem);
                        } while (status == B_INTERRUPTED);

                        // eat any sems that were released unconditionally
                        int32 semCount;
                        if (get_sem_count(fReaderSem, &semCount) == B_OK && semCount > 0)
                                acquire_sem_etc(fReaderSem, semCount, B_RELATIVE_TIMEOUT, 0);
                }

                if (status < B_OK) {
                        fBuffer->Lock();
                        return status;
                }
        }

        int32 toRead = atomic_get(&fReadBufferSize);
        if (toRead > ESC_PARSER_BUFFER_SIZE)
                toRead = ESC_PARSER_BUFFER_SIZE;

        for (int32 i = 0; i < toRead; i++) {
                // TODO: This could be optimized using memcpy instead and
                // calculating space left as in the PtyReader().
                fParserBuffer[i] = fReadBuffer[fBufferPosition];
                fBufferPosition = (fBufferPosition + 1) % READ_BUF_SIZE;
        }

        int32 bufferSize = atomic_add(&fReadBufferSize, -toRead);

        // If the pty reader thread waits and we have made enough space in the
        // buffer now, let it run again.
        if (bufferSize > READ_BUF_SIZE - MIN_PTY_BUFFER_SPACE
                        && bufferSize - toRead <= READ_BUF_SIZE - MIN_PTY_BUFFER_SPACE) {
                release_sem(fReaderLocker);
        }

        fParserBufferSize = toRead;
        fParserBufferOffset = 0;

        fBuffer->Lock();
        return B_OK;
}


void
TermParse::_DeviceStatusReport(int n)
{
        char sbuf[16] ;
        int len;

        switch (n) {
                case 5:
                        {
                                // Device status report requested
                                // reply with "no malfunction detected"
                                const char* toWrite = "\033[0n";
                                write(fFd, toWrite, strlen(toWrite));
                                break ;
                        }
                case 6:
                        // Cursor position report requested
                        len = snprintf(sbuf, sizeof(sbuf),
                                "\033[%" B_PRId32 ";%" B_PRId32 "R",
                                fBuffer->Cursor().y + 1,
                                fBuffer->Cursor().x + 1);
                        write(fFd, sbuf, len);
                        break ;
                default:
                        return;
        }
}


void
TermParse::_DecReqTermParms(int value)
{
        // Terminal parameters report:
        //   type (2 or 3);
        //   no parity (1);
        //   8 bits per character (1);
        //   transmit speed 38400bps (128);
        //   receive speed 38400bps (128);
        //   bit rate multiplier 16 (1);
        //   no flags (0)
        char parms[] = "\033[?;1;1;128;128;1;0x";

        if (value < 1)
                parms[2] = '2';
        else if (value == 1)
                parms[2] = '3';
        else
                return;

        write(fFd, parms, strlen(parms));
}


void
TermParse::_DecPrivateModeSet(int value)
{
        switch (value) {
                case 1:
                        // Application Cursor Keys (whatever that means).
                        // Not supported yet.
                        break;
                case 5:
                        // Reverse Video (inverses colors for the complete screen
                        // -- when followed by normal video, that's shortly flashes the
                        // screen).
                        // Not supported yet.
                        break;
                case 6:
                        // Set Origin Mode.
                        fBuffer->SetOriginMode(true);
                        break;
                case 9:
                        // Set Mouse X and Y on button press.
                        fBuffer->SetMode(MODE_REPORT_X10_MOUSE_EVENT);
                        break;
                case 12:
                        // Start Blinking Cursor.
                        fBuffer->SetMode(MODE_CURSOR_BLINKING);
                        break;
                case 25:
                        // Show Cursor.
                        fBuffer->ResetMode(MODE_CURSOR_HIDDEN);
                        break;
                case 47:
                        // Use Alternate Screen Buffer.
                        fBuffer->UseAlternateScreenBuffer(false);
                        break;
                case 1000:
                        // Send Mouse X & Y on button press and release.
                        fBuffer->SetMode(MODE_REPORT_NORMAL_MOUSE_EVENT);
                        break;
                case 1002:
                        // Send Mouse X and Y on button press and release, and on motion
                        // when the mouse enter a new cell
                        fBuffer->SetMode(MODE_REPORT_BUTTON_MOUSE_EVENT);
                        break;
                case 1003:
                        // Use All Motion Mouse Tracking
                        fBuffer->SetMode(MODE_REPORT_ANY_MOUSE_EVENT);
                        break;
                case 1006:
                        // Enable extended mouse coordinates with SGR scheme
                        fBuffer->SetMode(MODE_EXTENDED_MOUSE_COORDINATES);
                        break;
                case 1034:
                        // Interpret "meta" key, sets eighth bit.
                        fBuffer->SetMode(MODE_INTERPRET_META_KEY);
                        break;
                case 1036:
                        // Send ESC when Meta modifies a key
                        fBuffer->SetMode(MODE_META_KEY_SENDS_ESCAPE);
                        break;
                case 1039:
                        // TODO: Send ESC when Alt modifies a key
                        // Not supported yet.
                        break;
                case 1049:
                        // Save cursor as in DECSC and use Alternate Screen Buffer, clearing
                        // it first.
                        fBuffer->SaveCursor();
                        fBuffer->UseAlternateScreenBuffer(true);
                        break;
                case 2004:
                        // Enable bracketed paste mode
                        fBuffer->SetMode(MODE_BRACKETED_PASTE);
                        break;
        }
}


void
TermParse::_DecPrivateModeReset(int value)
{
        switch (value) {
                case 1:
                        // Normal Cursor Keys (whatever that means).
                        // Not supported yet.
                        break;
                case 3:
                        // 80 Column Mode.
                        // Not supported yet.
                        break;
                case 4:
                        // Jump (Fast) Scroll.
                        // Not supported yet.
                        break;
                case 5:
                        // Normal Video (Leaves Reverse Video, cf. there).
                        // Not supported yet.
                        break;
                case 6:
                        // Reset Origin Mode.
                        fBuffer->SetOriginMode(false);
                        break;
                case 9:
                        // Disable Mouse X and Y on button press.
                        fBuffer->ResetMode(MODE_REPORT_X10_MOUSE_EVENT);
                        break;
                case 12:
                        // Stop Blinking Cursor.
                        fBuffer->ResetMode(MODE_CURSOR_BLINKING);
                        break;
                case 25:
                        // Hide Cursor
                        fBuffer->SetMode(MODE_CURSOR_HIDDEN);
                        break;
                case 47:
                        // Use Normal Screen Buffer.
                        fBuffer->UseNormalScreenBuffer();
                        break;
                case 1000:
                        // Don't send Mouse X & Y on button press and release.
                        fBuffer->ResetMode(MODE_REPORT_NORMAL_MOUSE_EVENT);
                        break;
                case 1002:
                        // Don't send Mouse X and Y on button press and release, and on motion
                        // when the mouse enter a new cell
                        fBuffer->ResetMode(MODE_REPORT_BUTTON_MOUSE_EVENT);
                        break;
                case 1003:
                        // Disable All Motion Mouse Tracking.
                        fBuffer->ResetMode(MODE_REPORT_ANY_MOUSE_EVENT);
                        break;
                case 1006:
                        // Disable extended mouse coordinates with SGR scheme
                        fBuffer->ResetMode(MODE_EXTENDED_MOUSE_COORDINATES);
                        break;
                case 1034:
                        // Don't interpret "meta" key.
                        fBuffer->ResetMode(MODE_INTERPRET_META_KEY);
                        break;
                case 1036:
                        // Don't send ESC when Meta modifies a key
                        fBuffer->ResetMode(MODE_META_KEY_SENDS_ESCAPE);
                        break;
                case 1039:
                        // TODO: Don't send ESC when Alt modifies a key
                        // Not supported yet.
                        break;
                case 1049:
                        // Use Normal Screen Buffer and restore cursor as in DECRC.
                        fBuffer->UseNormalScreenBuffer();
                        fBuffer->RestoreCursor();
                        break;
                case 2004:
                        // Disable bracketed paste mode
                        fBuffer->ResetMode(MODE_BRACKETED_PASTE);
                        break;
        }
}


void
TermParse::_DecPrivateModeRequest(int value)
{
        BString reply;
        switch (value) {
                case 12:
                        // Request cursor blinking mode
                        reply.SetToFormat("\033[?12;%u$y\033\\",
                                fBuffer->IsMode(MODE_CURSOR_BLINKING) ? 1 : 2);
                        break;
                case 1006:
                        // Request extended mouse coordinates with SGR scheme
                        reply.SetToFormat("\033[?1006;%u$y\033\\",
                                fBuffer->IsMode(MODE_EXTENDED_MOUSE_COORDINATES) ? 1 : 2);
                        break;
                case 2004:
                        // Request bracketed paste mode
                        reply.SetToFormat("\033[?2004;%u$y\033\\",
                                fBuffer->IsMode(MODE_BRACKETED_PASTE) ? 1 : 2);
                        break;
                default:
                        return;
        }
        _WriteReply(reply);
}


void
TermParse::_ProcessOperatingSystemControls(uchar* params)
{
        int mode = 0;
        for (uchar c = *params; c != ';' && c != '\0'; c = *(++params)) {
                mode *= 10;
                mode += c - '0';
        }

        // eat the separator
        if (*params == ';')
                params++;

        static uint8 indexes[kTermColorCount];
        static rgb_color colors[kTermColorCount];

        switch (mode) {
                case 0: // icon name and window title
                case 2: // window title
                        fBuffer->SetTitle((const char*)params);
                        break;
                case 4: // set colors (0 - 255)
                case 104: // reset colors (0 - 255)
                        {
                                bool reset = (mode / 100) == 1;

                                // colors can be in "idx1:name1;...;idxN:nameN;" sequence too!
                                uint32 count = 0;
                                char* p = strtok((char*)params, ";");
                                while (p != NULL && count < kTermColorCount) {
                                        indexes[count] = atoi(p);

                                        if (!reset) {
                                                p = strtok(NULL, ";");
                                                if (p == NULL)
                                                        break;

                                                if (gXColorsTable.LookUpColor(p, &colors[count]) == B_OK)
                                                        count++;
                                        } else
                                                count++;

                                        p = strtok(NULL, ";");
                                };

                                if (count > 0) {
                                        if (!reset)
                                                fBuffer->SetColors(indexes, colors, count);
                                        else
                                                fBuffer->ResetColors(indexes, count);
                                }
                        }
                        break;
                // set dynamic colors (10 - 19)
                case 10: // text foreground
                case 11: // text background
                case 12: // cursor back
                        {
                                int32 offset = mode - 10;
                                int32 count = 0;
                                if (strcmp((char*)params, "?") == 0) {
                                        fBuffer->GetColor(mode);
                                        break;
                                }
                                char* p = strtok((char*)params, ";");
                                do {
                                        if (gXColorsTable.LookUpColor(p, &colors[count]) != B_OK) {
                                                // dyna-colors are pos-sensitive - no chance to continue
                                                break;
                                        }

                                        indexes[count] = 10 + offset + count;
                                        count++;
                                        p = strtok(NULL, ";");

                                } while (p != NULL && (offset + count) < 10);

                                if (count > 0) {
                                        fBuffer->SetColors(indexes, colors, count, true);
                                }
                        }
                        break;
                // reset dynamic colors (10 - 19)
                case 110: // text foreground
                case 111: // text background
                case 112: // cursor back
                        {
                                indexes[0] = mode;
                                fBuffer->ResetColors(indexes, 1, true);
                        }
                        break;
                // handle hyperlinks
                case 8:
                {
                        char* id = NULL;
                        char* start = (char*)params;
                        char* end = strpbrk(start, ";:");
                        for (; end != NULL; start = end + 1) {
                                end = strpbrk(start, ";:");
                                if (end == NULL)
                                        break;
                                if (end - start > 3 && strncmp(start, "id=", 3) == 0)
                                        id = strndup(start + 3, end - start + 3);
                                if (*end == ';')
                                        break;
                        }
                        if (end == NULL)
                                break;
                        BString uri(end + 1);
                        Attributes attributes = fBuffer->GetAttributes();
                        if (uri.IsEmpty()) {
                                attributes.SetHyperlink(0);
                        } else {
                                attributes.SetHyperlink(fBuffer->PutHyperLink(id, uri));
                        }
                        free(id);
                        fBuffer->SetAttributes(attributes);
                        break;
                }
                default:
                //      printf("%d -> %s\n", mode, params);
                        break;
        }
}


void
TermParse::_WriteReply(BString &reply)
{
        write(fFd, reply.String(), reply.Length());
}