internal Term_Viewer make_term_viewer() { Term_Viewer viewer {}; viewer.col_key = ACol_Green; viewer.col_page_start = ACol_Bright_Magenta; viewer.col_checksum = ACol_Yellow; viewer.col_highlight = ACol_Bright_White; viewer.col_section[Sec_RNTuple_Anchor] = ACol_Bright_Yellow; viewer.col_section[Sec_RNTuple_Header] = ACol_Cyan; viewer.col_section[Sec_RNTuple_Footer] = ACol_Blue; viewer.col_section[Sec_TFile_Header] = ACol_White; viewer.col_section[Sec_TFile_Object] = ACol_Grey; viewer.col_section[Sec_TFile_Info] = ACol_Red; viewer.col_section[Sec_TFile_FreeList] = ACol_Bright_Yellow; viewer.col_section[Sec_Page] = ACol_Magenta; viewer.col_section[Sec_Page_List] = ACol_Bright_Cyan; viewer.col_section[Sec_TKey_List] = ACol_Bright_Green; return viewer; } internal Ansi_Color ansi_color_for_offset(u64 off, App_State &app, const Term_Viewer &viewer) { off += app.base_display_addr; Section section = find_section(app, off); if (section.highlighted) return viewer.col_highlight; if (section.id == Sec_Page && off == section.range.start) return viewer.col_page_start; if (off < section.range.start) return viewer.col_key; if (section.range.end() - section.post_size <= off && off < section.range.end()) return viewer.col_checksum; return viewer.col_section[section.id]; } internal String8 render_legend_to_string(Arena *arena, const Term_Viewer &viewer, const App_State &app) { Temp scratch = scratch_begin(&arena, 1); defer { scratch_end(scratch); }; struct String8_Node { String8_Node *next; String8 str; } *head = nullptr, *tail = nullptr; String8 color_none = ansi_color_table[ACol_None]; u64 tot_len = 0; for (u64 section = 1; section < Sec_COUNT; ++section) { Ansi_Color color = viewer.col_section[section]; String8 color_str = ansi_color_table[color]; String8 sec_name = section_names[section]; Byte_Range range = get_section_range(app, static_cast(section)); String8 s; if (range.len) s = push_str8f(scratch.arena, "%s%20s %s0x%lX - 0x%lX [%lu B]\n", color_str.c(), sec_name.c(), color_none.c(), range.start, range.end(), range.len); else if (section == Sec_Page) s = push_str8f(scratch.arena, "%s%20s %s(%lu) [%lu B] \n", color_str.c(), sec_name.c(), color_none.c(), app.rndata.n_pages, app.rndata.tot_page_size); else if (section == Sec_Page_List) s = push_str8f(scratch.arena, "%s%20s %s(%lu) [%lu B]\n", color_str.c(), sec_name.c(), color_none.c(), app.rndata.n_cluster_groups, app.rndata.tot_page_list_size); String8_Node *node = arena_push(scratch.arena); node->str = s; if (!tail) { head = node; } else { tail->next = node; } tail = node; tot_len += s.size; } String8 legend { arena_push_array_nozero(arena, tot_len + 1), tot_len + 1 }; legend.str[tot_len] = 0; u64 cur_size = 0; for (String8_Node *node = head; node; node = node->next) { memcpy(legend.str + cur_size, node->str.str, node->str.size); cur_size += node->str.size; } return legend; } internal String8 render_range_to_string(Arena *arena, App_State &app, u64 len, u64 n_cols) { Term_Viewer viewer = make_term_viewer(); String8 legend = render_legend_to_string(arena, viewer, app); // scratch must be created after `render_legend_to_string` Temp scratch = scratch_begin(&arena, 1); defer { scratch_end(scratch); }; String8 ntpl_desc = rntuple_description(scratch.arena, app.rndata); String8 header_str = push_str8f(scratch.arena, "RNTuple '%s' (%s) from file \"%s\"", app.ntpl_name.c(), ntpl_desc.c(), app.inspected_file.name.c()); if (n_cols == 0) n_cols = 32; u64 n_lines = len / n_cols; u64 max_addr = app.inspected_file.size; // Calculate how much space do we need to fix line addr numbers. // The biggest address is gonna take up the most space const f32 RECP_LOG_16 = 0.360673760222f; // NOTE: +1 for the ":" u64 max_addr_len = 1 + ceilf(logf(max_addr + 1) * RECP_LOG_16); const u64 n_spaces_after_line_addr = 3; u64 line_addr_size = (max_addr_len + n_spaces_after_line_addr) * n_lines; String8 line_addr_fmt = push_str8f(scratch.arena, "%%0%dlX:", max_addr_len - 1); // NOTE: +3 because we need 2 chars for each byte + 1 whitespace. u64 buf_size = (ACOL_MAX_LEN + 3) * len; buf_size += n_lines; // one newline per line buf_size += n_lines * ACOL_MAX_LEN; // reset color every line buf_size += line_addr_size; buf_size += 1; // initial newline buf_size += header_str.size; buf_size += 2; // two newlines after header buf_size += 2; // two newlines before legend buf_size += legend.size; buf_size += 1; // trailing zero char *buf = arena_push_array_nozero(arena, buf_size); buf[len] = 0; u64 start = app.base_display_addr; // We need to properly initialize this before calling mem_edit_bg_color_fn! app.last_pinfo = &invalid_pinfo; const u8 *data = app.inspected_file.mem; assert(start <= max_addr); len = min(len, max_addr - start); char *cur = buf; *cur++ = '\n'; // header memcpy(cur, header_str.str, header_str.size); cur += header_str.size; *cur++ = '\n'; *cur++ = '\n'; // first line addr cur += sprintf(cur, line_addr_fmt.c(), start); for (u32 i = 0; i < n_spaces_after_line_addr; ++i) *cur++ = ' '; String8 col_none = ansi_color_table[ACol_None]; for (u64 i = 0; i < len; ++i) { u64 off = start + i; /// Select color Ansi_Color acol = ansi_color_for_offset(i, app, viewer); String8 acol_str = ansi_color_table[acol]; memcpy(cur, acol_str.str, acol_str.size); cur += acol_str.size; /// Write the human-readable byte cur += sprintf(cur, "%02X ", data[off]); // new line if ((i + 1) % n_cols == 0) { *cur++ = '\n'; memcpy(cur, col_none.c(), col_none.size); cur += col_none.size; cur += sprintf(cur, line_addr_fmt.c(), off + 1); for (u32 i = 0; i < n_spaces_after_line_addr; ++i) *cur++ = ' '; } assert((u64)(cur - buf) < (u64)buf_size); } *cur++ = '\n'; *cur++ = '\n'; memcpy(cur, legend.str, legend.size); cur += legend.size; *cur++ = 0; u64 len_used = (u64)(cur - buf); return { (u8 *)buf, len_used }; }