#+build linux #+vet explicit-allocators package tcom import "core:bytes" import "core:io" import "core:log" import "core:strconv" TTY_Ctrl :: enum { Bell, Backspace, Tab, Line_Feed, Form_Feed, Carriage_Return, // CSI Reset_All, Bold, Dim, Italic, Underline, // OSC } TTY_Text :: rune TTY_Resized :: distinct TTY_Pos TTY_Event :: union { TTY_Ctrl, TTY_Resized, TTY_Text, } TTY_Error_Unknown_Escape_Seq :: struct { bytes: []byte, } TTY_Error :: union { TTY_Error_Unknown_Escape_Seq, io.Error, } TTY_Pos :: [2]u16 TTY_Reader :: struct { buf: []byte, br: bytes.Reader, state: TTY_Reader_State, start_i: i64, } TTY_Reader_State :: enum { Normal, Escape_Code, } tty_reader_init :: proc(r: ^TTY_Reader, buf: []byte) { r.buf = buf bytes.reader_init(&r.br, buf) } tty_reader_has_data :: proc(r: ^TTY_Reader) -> bool { return r.br.i < bytes.reader_size(&r.br) } tty_reader_get_event :: proc(r: ^TTY_Reader) -> (e: TTY_Event, err: TTY_Error) { if !tty_reader_has_data(r) { return } br := &r.br r.start_i = br.i for { // TODO: Peek and do things based on the peeked rune (numeric == reader_number) ru, _, b_err := bytes.reader_read_rune(br) // TODO: Emit some kind of error if we end in a premature state. if b_err == .EOF { return } else if b_err != nil { err = b_err return } e, err = _tty_reader_process_rune(r, ru) if e != nil || err != nil { return } } } _tty_reader_process_rune :: proc(r: ^TTY_Reader, ru: rune) -> (e: TTY_Event, err: TTY_Error) { switch r.state { case .Normal: return _tty_reader_process_rune_normal(r, ru) case .Escape_Code: return _tty_reader_process_rune_escape_code(r, ru) } return } _tty_reader_process_rune_normal :: proc(r: ^TTY_Reader, ru: rune) -> (e: TTY_Event, err: TTY_Error) { switch ru { // C0 control codes case '\a': return .Bell, nil case '\b': return .Backspace, nil case '\t': return .Tab, nil case '\n': return .Line_Feed, nil case '\f': return .Form_Feed, nil case '\r': return .Carriage_Return, nil // Escape code case '\e': // FIXME: We need to distinguish between "the user pressed escape" and a possible escape sequence. r.state = .Escape_Code return } return TTY_Text(ru), nil } // https://en.wikipedia.org/wiki/ANSI_escape_code /* For Control Sequence Introducer, or CSI, commands, the ESC [ (written as \e[, \x1b[ or \033[ in several programming languages) is followed by any number (including none) of "parameter bytes" in the range 0x30–0x3F (ASCII 0–9:;<=>?), then by any number of "intermediate bytes" in the range 0x20–0x2F (ASCII space and !"#$%&'()*+,-./), then finally by a single "final byte" in the range 0x40–0x7E (ASCII @A–Z[\]^_`a–z{|}~). */ _tty_reader_process_rune_escape_code :: proc(r: ^TTY_Reader, ru: rune) -> (e: TTY_Event, err: TTY_Error) { // TODO: Actually do the thing! r.state = .Normal // if ru >= '0' && ru <= '9' { // bytes.reader_unread_rune(&r.br) or_return // log.debug(string(r.buf[r.br.i:])) // n: int // arg, ok := strconv.parse_uint(string(r.buf[r.br.i:]), n=&n) // if !ok { // err = TTY_Error_Unknown_Escape_Seq{} // return // } // r.esc_code.args[r.esc_code.args_len] = arg // r.esc_code.args_len += 1 // bytes.reader_seek(&r.br, auto_cast n, .Current) // } return } // tty_parse_next :: proc(br: ^bytes.Reader, is_stdin: bool) -> (e: Sys_Event) { // if br.i >= bytes.reader_size(br) { // return // } // start_i := int(br.i) // did_first_iter: bool // for { // // TODO: s is unused // r, s, err := bytes.reader_read_rune(br) // if err != nil && err != .EOF { // end_i := int(br.i) // return Sys_Event_IO_Error{br.s[start_i:end_i], start_i, end_i, err} // } // if err == .EOF { // break // } // if r == '\e' { // if did_first_iter { // // End of sequence of "standard" characters. // err = bytes.reader_unread_rune(br) // if err != nil { // end_i := int(br.i) // return Sys_Event_IO_Error{br.s[start_i:end_i], start_i, end_i, err} // } // break // } else { // esc_err: TTY_Parse_Escape_Code_Error // e, esc_err = tty_parse_escape_code(br, &s) // if esc_err != nil { // log.error(err) // TODO: Return to business logic. // } else { // return e // } // } // } // did_first_iter = true // } // end_i := int(br.i) // if end_i > start_i { // return Sys_Event_TTY_Text{string(br.s[start_i:end_i])} // } // return // } // TTY_Unknown_Escape_Code_Error :: struct {} // TTY_Parse_Escape_Code_Error :: union { // TTY_Unknown_Escape_Code_Error, // io.Error, // } // tty_parse_escape_code :: proc(br: ^bytes.Reader, s: ^int) -> (e: Sys_Event, err: TTY_Parse_Escape_Code_Error) { // // When this function is called, the caller has already seen the escape character, so we will proceed assuming that // // it existed. // // TODO: This is terrible no-good awful. // switch _read(br, s) or_return { // case '[': // switch _read(br, s) or_return { // case '0': // switch _read(br, s) or_return { // case 'm': // e = Sys_Event_TTY_Control{.Reset_All} // return // case: // err = TTY_Unknown_Escape_Code_Error{} // return // } // case '1': // switch _read(br, s) or_return { // case 'm': // e = Sys_Event_TTY_Control{.Bold} // return // case: // err = TTY_Unknown_Escape_Code_Error{} // return // } // case '2': // switch _read(br, s) or_return { // case 'm': // e = Sys_Event_TTY_Control{.Dim} // return // case: // err = TTY_Unknown_Escape_Code_Error{} // return // } // case '3': // switch _read(br, s) or_return { // case 'm': // e = Sys_Event_TTY_Control{.Italic} // return // case: // err = TTY_Unknown_Escape_Code_Error{} // return // } // case '4': // switch _read(br, s) or_return { // case 'm': // e = Sys_Event_TTY_Control{.Underline} // return // case: // err = TTY_Unknown_Escape_Code_Error{} // return // } // case: // err = TTY_Unknown_Escape_Code_Error{} // return // } // case: // err = TTY_Unknown_Escape_Code_Error{} // return // } // }