package tcom import rt "base:runtime" import hm "core:container/handle_map" import "core:log" import "core:testing" Cell :: struct { r: rune, attribs: Cell_Attribs, // TODO: Colors } Cell_Attrib :: enum { Bold, Dim, Italic, Underline, } Cell_Attribs :: bit_set[Cell_Attrib] Pane :: struct { cells: []Cell, // owned dims: TTY_Pos, cursor: Pane_Cursor, handle: Pane_Handle, allocator: rt.Allocator, child: Sys_Child_Info, } Pane_Cursor :: struct { using pos: [2]u16, attribs: Cell_Attribs, } pane_init :: proc(cmd: []string, dims: TTY_Pos, allocator: rt.Allocator) -> (h: Pane_Handle, err: rt.Allocator_Error) { return hm.add(&g_panes, Pane{ cells = make_slice([]Cell, dims.x * dims.y, allocator) or_return, dims = dims, allocator = allocator, child = sys_child_start(cmd, dims, allocator) or_return, }) } pane_init_headless :: proc(dims: TTY_Pos, allocator: rt.Allocator) -> (h: Pane_Handle, err: rt.Allocator_Error) { return hm.add(&g_panes, Pane{ cells = make_slice([]Cell, dims.x * dims.y, allocator) or_return, dims = dims, allocator = allocator, }) } pane_fini :: proc(pane_handle: Pane_Handle) { pane, ok := hm.get(&g_panes, pane_handle) if !ok { log.errorf("pane_fini called when pane does not exist.") return } delete(pane.cells, pane.allocator) hm.remove(&g_panes, pane_handle) } pane_set_dims :: proc(h: Pane_Handle, dims: TTY_Pos) -> (ok: bool) { pane := hm.get(&g_panes, h) or_return sys_term_set_dims(dims, pane.child) return true } pane_handle_event :: proc(h: Pane_Handle, event: TTY_Event) -> (ok: bool) { p := hm.get(&g_panes, h) or_return switch e in event { case TTY_Ctrl: return _pane_handle_ctrl(p, e) case TTY_Resized: log.error("TTY_Resize is unimplemented.") // TODO: case TTY_Text: return _pane_handle_text(p, e) } return true } pane_draw :: proc(h: Pane_Handle) -> (ok: bool) { pane := hm.get(&g_panes, h) or_return log.debug("DRAW:", pane) // TODO: return true } // --------------------------------------------------------------------------------------------------------------------- // INTERNALS _pane_advance_cursor :: proc(p: ^Pane) { p.cursor.x = (p.cursor.x + 1) % p.dims.x if p.cursor.x == 0 { p.cursor.y += 1 } } _pane_get_cursor_cell :: proc(p: ^Pane) -> ^Cell { i := (p.cursor.y * p.dims.x) + p.cursor.x // log.debug("cursor offset calc:", p.cursor.pos, i) return &p.cells[i] } _pane_handle_ctrl :: proc(p: ^Pane, ctrl: TTY_Ctrl) -> (ok: bool) { switch ctrl { // C0 control codes case .Bell: case .Backspace: case .Tab: case .Line_Feed: case .Form_Feed: case .Carriage_Return: // CSI commands case .Reset_All: p.cursor.attribs = {} case .Bold: p.cursor.attribs += {.Bold} case .Dim: p.cursor.attribs += {.Dim} case .Italic: p.cursor.attribs += {.Italic} case .Underline: p.cursor.attribs += {.Underline} } return true } _pane_handle_text :: proc(p: ^Pane, r: rune) -> (ok: bool) { cell := _pane_get_cursor_cell(p) cell.r = r cell.attribs = p.cursor.attribs _pane_advance_cursor(p) return true } // --------------------------------------------------------------------------------------------------------------------- // TESTS @test pane_test_cursor_attribs :: proc(t: ^testing.T) { panes_init(context.allocator) defer panes_fini() h, err := pane_init_headless({600, 400}, context.allocator) testing.expect(t, err == nil) defer pane_fini(h) // The pane pointer will be stable throughout this test. pane, ok := hm.get(&g_panes, h) testing.expect(t, ok) pane_handle_event(h, .Bold) testing.expect(t, .Bold in pane.cursor.attribs) pane_handle_event(h, .Dim) testing.expect(t, .Dim in pane.cursor.attribs) pane_handle_event(h, .Italic) testing.expect(t, .Italic in pane.cursor.attribs) pane_handle_event(h, .Underline) testing.expect(t, .Underline in pane.cursor.attribs) pane_handle_event(h, .Reset_All) testing.expect(t, pane.cursor.attribs == {}) }