const FONT_WIDTH = 8 # glyph width in pixels const FONT_HEIGHT = 16 # glyph height in pixels (8x16 vga bitmap font) # base16 from lung const BASE00 = 0x101010 # background const BASE03 = 0x505050 # bright black const BASE05 = 0xd0d0d0 # default foreground const BASE07 = 0xfafafa # bright white const BASE08 = 0xab4642 # red const BASE09 = 0xdc9656 # orange const BASE0A = 0xf7ca88 # yellow const BASE0B = 0xa1b56c # green const BASE0C = 0x86c1b9 # cyan const BASE0D = 0x7cafc2 # blue const BASE0E = 0xba8baf # magenta const FG_COLOR = BASE05 const BG_COLOR = BASE00 const CURSOR_COLOR = BASE0B global wd = { path = "/" } global shell_status = { code = 0 } global prompt = fn() do let mid = "" unless (shell_status.code == 0) do mid = "\x1b[31m#{shell_status.code} " end return "\x1b[33m#{wd.path} #{mid}\x1b[97m$\x1b[0m " end fn parse_int(text) do let value = 0 for index in 0..text:len() do const digit = text[index]:ascii() if (digit >= 48 and digit <= 57) do value = value * 10 + (digit - 48) end end return value end fn sgr_color(code, current) do if (code == 0 or code == 39) return BASE05 if code == 30 return BASE00 if code == 90 return BASE03 if (code == 31 or code == 91) return BASE08 if (code == 32 or code == 92) return BASE0B if (code == 33 or code == 93) return BASE0A if (code == 34 or code == 94) return BASE0D if (code == 35 or code == 95) return BASE0E if (code == 36 or code == 96) return BASE0C if code == 37 return BASE05 if code == 97 return BASE07 return current end # runtime font bs fn embolden(byte, row) do return (byte bor (byte shl 1)) band 0xFF end fn slant(byte, row) do return (byte shl ((FONT_HEIGHT - 1 - row) shr 2)) band 0xFF end fn remap_glyphs(transform) do let out = {} for glyph_index in 0..FONT:len() do const glyph = FONT[glyph_index] let rows = {} for row_index in 0..FONT_HEIGHT do rows:push(transform(glyph[row_index], row_index)) end out:push(rows) end return out end global reverse8 = fn(byte) do let out = 0 for bit in 0..8 do if (byte shr bit) band 1 == 1 do out = out bor (1 shl (7 - bit)) end end return out end global fonts = {} global add_font = fn(name, glyphs, width, height, kind, path, size) do for index in 0..fonts:len() do if fonts[index].name == name do fonts[index].glyphs = glyphs fonts[index].width = width fonts[index].height = height fonts[index].kind = kind fonts[index].path = path fonts[index].size = size return :nil end end fonts:push({ name = name, glyphs = glyphs, width = width, height = height, kind = kind, path = path, size = size }) end global find_font = fn(name) do for index in 0..fonts:len() do if fonts[index].name == name return fonts[index] end return :nil end add_font("vga", FONT, FONT_WIDTH, FONT_HEIGHT, :bitmap, :nil, 0) add_font("bold", remap_glyphs(embolden), FONT_WIDTH, FONT_HEIGHT, :bitmap, :nil, 0) add_font("italic", remap_glyphs(slant), FONT_WIDTH, FONT_HEIGHT, :bitmap, :nil, 0) add_font("bolditalic", remap_glyphs(fn(byte, row) do return slant(embolden(byte, row), row) end), FONT_WIDTH, FONT_HEIGHT, :bitmap, :nil, 0) global console = { column = 0, row = 0, scale = 1, shown = :false, cursor_x = 0, cursor_y = 0, blink = 0, font = FONT, font_width = FONT_WIDTH, font_height = FONT_HEIGHT, font_entry = find_font("vga"), fg = FG_COLOR, ansi = 0, ansi_param = "", # this is now font dependent cell_width = fn(self) self.font_width * self.scale, cell_height = fn(self) self.font_height * self.scale, columns = fn(self) fb_width() / self:cell_width(), rows = fn(self) fb_height() / self:cell_height(), set_font = fn(self, entry) do self.font = entry.glyphs self.font_width = entry.width self.font_height = entry.height self.font_entry = entry # a ttf renders at its true pixel size so we js bitmap zoom scale if entry.kind == :ttf do self.scale = 1 end self:clear() end, draw_glyph = fn(self, column, row, character) do const origin_x = column * self:cell_width() const origin_y = row * self:cell_height() fill_rect(origin_x, origin_y, self:cell_width(), self:cell_height(), BG_COLOR) # clear the cell const code = character:ascii() if code < 32 return :nil if code > 127 return :nil const glyph = self.font[code - 32] for glyph_row in 0..self.font_height do const bits = glyph[glyph_row] for glyph_col in 0..self.font_width do if (bits shr glyph_col) band 1 == 1 fill_rect(origin_x + glyph_col * self.scale, origin_y + glyph_row * self.scale, self.scale, self.scale, self.fg) end end end, newline = fn(self) do self.column = 0 self.row += 1 if self.row >= self:rows() do fb_scroll(self:cell_height(), BG_COLOR) self.row = self:rows() - 1 end end, # we dont do shift/cntrl, thats a job for future me lmao # NOTE: future me kinda delt with it, i was mad drunk apply_sgr = fn(self, param) do if param:len() == 0 do self.fg = BASE05 return :nil end const parts = param:split(";") for index in 0..parts:len() do self.fg = sgr_color(parse_int(parts[index]), self.fg) end end, putc = fn(self, character) do const code = character:ascii() if self.ansi == 1 do if code == 91 do self.ansi = 2 self.ansi_param = "" end else do self.ansi = 0 end return :nil end if self.ansi == 2 do if (code >= 48 and code <= 57) or code == 59 do self.ansi_param = self.ansi_param ~ character return :nil end if code == 109 do self:apply_sgr(self.ansi_param) end self.ansi = 0 return :nil end if code == 27 do self.ansi = 1 return :nil end # ESC, start a sequence if code == 10 do self:newline() return :nil end # \n if code == 13 do self.column = 0 return :nil end # \r if code == 8 do # backspace: move left if self.column > 0 self.column -= 1 return :nil end if code < 32 return :nil self:draw_glyph(self.column, self.row, character) self.column += 1 if self.column >= self:columns() self:newline() end, clear = fn(self) do fill_rect(0, 0, fb_width(), fb_height(), BG_COLOR) self.column = 0 self.row = 0 end, cursor_clear = fn(self) do if self.shown do fill_rect(self.cursor_x * self:cell_width(), self.cursor_y * self:cell_height(), self:cell_width(), self:cell_height(), BG_COLOR) self.shown = :false end end, cursor_show = fn(self) do self.cursor_x = self.column self.cursor_y = self.row fill_rect(self.cursor_x * self:cell_width(), self.cursor_y * self:cell_height(), self:cell_width(), self:cell_height(), CURSOR_COLOR) self.shown = :true end, # render every byte the serial mirror has buffered drain = fn(self) do let byte = serial_next() while byte >= 0 do self:putc(string.of_ascii(byte)) byte = serial_next() end end, } global puts = fn(text) serial_puts(text) global fail = fn(message) do puts(message) shell_status.code = 1 end global flush_console = fn() do console:cursor_clear() console:drain() console.blink = 0 console:cursor_show() end global on_tick = fn(ticks) do let byte = serial_next() if byte >= 0 do console:cursor_clear() console:putc(string.of_ascii(byte)) console:drain() console.blink = 0 console:cursor_show() return :nil end if proc_exited() do console:cursor_clear() puts(prompt()) console:drain() console.blink = 0 console:cursor_show() return :nil end console.blink += 1 if console.blink >= 50 do console.blink = 0 if console.shown console:cursor_clear() else console:cursor_show() end end