2 ASCII Table Utility by Bruce Hill
4 A simple utility that uses ncurses to display the ASCII table and lets you
5 manually output ASCII characters one byte at a time. Use arrow keys or
6 h/j/k/l to move, 'enter' to add a character to the output, '/' to search,
7 and 'q' or escape to quit and print the output.
9 Licensed under the MIT License (see LICENSE file for details)
20 typedef enum {BROWSE_MODE, INSERT_MODE, SEARCH_MODE} Mode;
22 #define DELETE_KEY 127
25 // The descriptive names of the ASCII characters
26 const char *NAMES[128][2] = {
28 {"SOH", "start of heading"}, // 1
29 {"STX", "start of text"}, // 2
30 {"ETX", "end of text"}, // 3
31 {"EOT", "end of transmission"}, // 4
32 {"ENQ", "enquiry"}, // 5
33 {"ACK", "acknowledge"}, // 6
35 {"BS", "backspace"}, // 8
36 {"TAB", "horizontal tab"}, // 9
37 {"LF", "NL line feed, new line"}, // 10
38 {"VT", "vertical tab"}, // 11
39 {"FF", "NP form feed, new page"}, // 12
40 {"CR", "carriage return"}, // 13
41 {"SO", "shift out"}, // 14
42 {"SI", "shift in"}, // 15
43 {"DLE", "data link escape"}, // 16
44 {"DC1", "device control 1"}, // 17
45 {"DC2", "device control 2"}, // 18
46 {"DC3", "device control 3"}, // 19
47 {"DC4", "device control 4"}, // 20
48 {"NAK", "negative acknowledge"}, // 21
49 {"SYN", "synchonous idle"}, // 22
50 {"ETB", "end of transmission block"}, // 23
51 {"CAN", "cancel"}, // 24
52 {"EM", "end of medium"}, // 25
53 {"SUB", "substitute"}, // 26
54 {"ESC", "escape"}, // 27
55 {"FS", "file separator"}, // 28
56 {"GS", "group separator"}, // 29
57 {"RS", "record separator"}, // 30
58 {"US", "unit separator"}, // 31
60 [127] = {"DEL", "delete"}
63 static int new_colorpair(int fg, int bg)
65 static int next_colornum = 1;
66 init_pair(next_colornum, fg, bg);
67 return COLOR_PAIR(next_colornum++);
70 int DECIMAL_COLORS, HEX_COLORS, CHAR_COLORS, DESCRIPTION_SPECIAL_COLORS,
71 DESCRIPTION_LETTER_COLORS, DESCRIPTION_NUMBER_COLORS, DESCRIPTION_SYMBOL_COLORS,
72 OUTPUT_CHAR_COLORS, OUTPUT_ESCAPE_COLORS, OUTPUT_LABEL_COLORS, BROWSE_MODE_COLORS,
73 INSERT_MODE_COLORS, SEARCH_MODE_COLORS;
75 const int COL_WIDTH = 42;
79 static inline int min(int a, int b) {
83 static inline int max(int a, int b) {
84 return a >= b ? a : b;
88 static inline void shift(int dy, int dx) {
94 // Same as printw, except it doesn't do text wrapping, and it sets attributes
95 #define printwattrs(_attrs, ...) \
98 sprintf(_buf, __VA_ARGS__);\
99 for (int i = 0; i == 0 || _buf[i-1]; i++) _bufch[i] = _buf[i] | _attrs;\
101 shift(0, strlen(_buf));}
104 // Draw the information for one character
105 static inline void draw_entry(int i, int scrollx, int attrs)
107 int x = (i / (H-2)) * COL_WIDTH - scrollx;
108 if (x > W || x < 0) return;
109 int y = (i % (H-2)) + 1;
111 printwattrs(DECIMAL_COLORS | attrs, "%3d ", i);
112 printwattrs(HEX_COLORS | attrs, " 0x%02X ", i);
114 printwattrs(CHAR_COLORS | attrs, " %-3s ", NAMES[i][0]);
116 printwattrs(CHAR_COLORS | attrs, " %c ", i);
119 printwattrs(DESCRIPTION_SPECIAL_COLORS | attrs, " %-25s", NAMES[i][1]);
120 } else if (('a' <= i && i <= 'z') || ('A' <= i && i <= 'Z')) {
121 printwattrs(DESCRIPTION_LETTER_COLORS | attrs, " the letter %c ", i);
122 } else if ('0' <= i && i <= '9') {
123 printwattrs(DESCRIPTION_NUMBER_COLORS | attrs, " the number %c ", i);
125 printwattrs(DESCRIPTION_SYMBOL_COLORS | A_BOLD | attrs, " the %c symbol ", i);
129 // Redraw the parts of the ASCII table necessary for the selected item
130 static void redraw(int selected)
132 static int last_selected = -1, last_scrollx = -1, last_W = -1, last_H = -1;
133 int selectedx = (selected / (H-2)) * COL_WIDTH;
134 int scrollx = (selectedx + COL_WIDTH < W) ? 0 : (((selectedx + COL_WIDTH) - W)/COL_WIDTH+1)*COL_WIDTH;
135 if (last_selected == -1 || last_scrollx != scrollx || last_W != W || last_H != H) {
136 // Redraw all the entries
138 for (int i = 0; i < 128; i++) {
139 draw_entry(i, scrollx, i == selected ? A_REVERSE : 0);
141 last_scrollx = scrollx;
144 } else if (selected != last_selected) {
145 // Otherwise, we can lazily just redraw 2 entries
146 draw_entry(selected, scrollx, A_REVERSE);
147 draw_entry(last_selected, scrollx, 0);
149 last_selected = selected;
152 // Find the ascii character starting at `start`, going in `direction`, matching
153 // `searchbuff`, and return `fallback` if nothing matches.
154 static int find_ascii_match(int start, int fallback, int direction, char *searchbuf)
156 if (searchbuf[0] == '\0') return fallback;
157 char tmp[COL_WIDTH+6];
158 for (int i = 0; i <= 127; i++) {
159 int c = (start + i*direction) % 128; // relative to starting point
160 if (searchbuf[0] == c && searchbuf[1] == '\0')
162 // Only do full match for search strings longer than 1
165 sprintf(tmp, "%d 0x%02X 0x%02x %s %s", c, c, c, NAMES[c][0] ? NAMES[c][0] : "", NAMES[c][1]);
166 } else if (('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z')) {
167 sprintf(tmp, "%d 0x%02X 0x%02x %s the letter %c", c, c, c, NAMES[c][0] ? NAMES[c][0] : "", c);
168 } else if ('0' <= c && c <= '9') {
169 sprintf(tmp, "%d 0x%02X 0x%02x %s the number %c", c, c, c, NAMES[c][0] ? NAMES[c][0] : "", c);
171 sprintf(tmp, "%d 0x%02X 0x%02x %s the %c character", c, c, c, NAMES[c][0] ? NAMES[c][0] : "", c);
173 if (strstr(tmp, searchbuf) != NULL)
180 // To prevent wasted work, wait until 200ms has elapsed with no additional
181 // resizes before doing anything, then set the correct W/H values.
182 static void handle_resize()
188 // Peek if there's another resize event
190 if (k == KEY_RESIZE) continue;
191 if (k > 0) ungetch(k);
193 // Back to blocking input
199 int main(int argc, char *argv[])
201 if (argc > 1 && (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-h") == 0)) {
202 printf("ASCII table viewer/editor, Usage: ascii [N...]\n");
206 // Instead of initscr(), set up the terminal this way to play nicely with
207 // the unix pipeline:
209 char* term_type = getenv("TERM");
210 if (term_type == NULL || *term_type == '\0') {
211 term_type = "unknown";
213 FILE* term_in = fopen("/dev/tty", "r");
214 if (term_in == NULL) {
215 perror("fopen(/dev/tty)");
218 SCREEN *screen = newterm(term_type, stderr, term_in);
230 DECIMAL_COLORS = new_colorpair(COLOR_YELLOW, COLOR_BLACK) | A_BOLD;
231 HEX_COLORS = new_colorpair(COLOR_GREEN, COLOR_BLACK) | A_BOLD;
232 CHAR_COLORS = new_colorpair(COLOR_MAGENTA, COLOR_BLACK) | A_BOLD;
233 DESCRIPTION_SPECIAL_COLORS = new_colorpair(COLOR_RED, COLOR_BLACK) | A_BOLD;
234 DESCRIPTION_LETTER_COLORS = new_colorpair(COLOR_BLUE, COLOR_BLACK) | A_BOLD;
235 DESCRIPTION_NUMBER_COLORS = new_colorpair(COLOR_CYAN, COLOR_BLACK) | A_BOLD;
236 DESCRIPTION_SYMBOL_COLORS = new_colorpair(COLOR_WHITE, COLOR_BLACK) | A_BOLD;
237 OUTPUT_CHAR_COLORS = new_colorpair(COLOR_BLACK, COLOR_WHITE) | A_BOLD;
238 OUTPUT_ESCAPE_COLORS = new_colorpair(COLOR_RED, COLOR_WHITE) | A_BOLD;
239 OUTPUT_LABEL_COLORS = new_colorpair(COLOR_BLACK, COLOR_YELLOW);
240 BROWSE_MODE_COLORS = new_colorpair(COLOR_BLACK, COLOR_YELLOW);
241 INSERT_MODE_COLORS = new_colorpair(COLOR_WHITE, COLOR_RED) | A_BOLD;
242 SEARCH_MODE_COLORS = new_colorpair(COLOR_BLACK, COLOR_BLUE);
244 size_t buf_chunk = 1024;
245 size_t buf_size = buf_chunk, buf_i = 0;
246 char *outbuf = calloc(buf_size, sizeof(char));
248 { // Read stdin if anything was piped in
250 desc.fd = STDIN_FILENO;
251 desc.events = POLLIN;
252 int ret = poll(&desc, 1, 50);
255 while ((consumed = fread(&outbuf[buf_i], 1, buf_chunk, stdin))) {
257 if (consumed == buf_chunk) {
259 buf_size += buf_chunk;
260 outbuf = realloc(outbuf, buf_size);
266 char searchbuf[1024] = {'\0'};
267 size_t searchlen = 0;
269 // If any command line args, treat them as bytes to append to
271 for (int i = 1; i < argc; i++) {
272 long c = strtol(argv[i], NULL, 0);
273 outbuf[buf_i++] = (char)c;
274 if (buf_i >= buf_size) {
276 outbuf = realloc(outbuf, buf_size);
280 Mode mode = BROWSE_MODE;
281 int prev_selected = 0, selected = 0;
290 title = "ASCII TABLE (typing, 'esc' to browse)";
291 title_colors = INSERT_MODE_COLORS;
294 title = "ASCII TABLE (searching, 'esc' to browse)";
295 title_colors = SEARCH_MODE_COLORS;
298 title = "ASCII TABLE (browsing, 'esc' to exit)";
299 title_colors = BROWSE_MODE_COLORS;
302 int spacer = W/2-strlen(title)/2;
303 printwattrs(title_colors, "%*s%s%*s", spacer, "", title, spacer+1, "");
305 if (mode == SEARCH_MODE) {
306 attron(SEARCH_MODE_COLORS);
307 mvprintw(H-1, 0, " Search:");
308 attroff(SEARCH_MODE_COLORS);
310 printwattrs(OUTPUT_CHAR_COLORS, "%s", searchbuf);
312 // Bar at the bottom of the screen showing the output text:
313 // left-truncate output so the end of it fits on screen:
314 char *visible = &outbuf[buf_i];
315 for (int space = W-strlen(" Output: ")-1-5; space > 0 && visible > outbuf; visible--) {
316 int chlen = (' ' <= *visible && *visible <= '~') ? 1 : 4; // printable range
317 if (chlen > space) break;
320 attron(mode == INSERT_MODE ? INSERT_MODE_COLORS : OUTPUT_LABEL_COLORS);
321 mvprintw(H-1, 0, " Output:");
322 attroff(mode == INSERT_MODE ? INSERT_MODE_COLORS : OUTPUT_LABEL_COLORS);
324 for (char *c = visible; c < &outbuf[buf_i]; c++) {
325 if (' ' <= *c && *c <= '~') { // printable range
326 printwattrs(OUTPUT_CHAR_COLORS, "%c", *c);
328 printwattrs(OUTPUT_ESCAPE_COLORS, "\\x%02X", *c);
334 // Clear to end of line
335 for (int x2 = x; x2 < W; x2++) mvaddch(y, x2, ' ');
350 if (buf_i > 0) buf_i--;
356 if (0 <= key && key < 127) {
357 outbuf[buf_i++] = (char)key;
358 if (buf_i >= buf_size) {
360 outbuf = realloc(outbuf, buf_size);
363 } else goto next_input; // skip redraw
370 case ESCAPE_KEY: // Escape
371 selected = prev_selected;
377 case KEY_ENTER: case '\n':
383 searchbuf[searchlen--] = '\0';
384 selected = find_ascii_match(prev_selected, prev_selected, 1, searchbuf);
393 if (0 <= key && key < 127) {
394 if (searchlen < sizeof(searchbuf)-1) {
395 searchbuf[searchlen++] = (char)key;
396 searchbuf[searchlen] = '\0';
398 selected = find_ascii_match(prev_selected, prev_selected, 1, searchbuf);
400 } else goto next_input; // skip redraw
407 case 'j': case KEY_DOWN:
415 case 'k': case KEY_UP:
423 case 'l': case 'L': case KEY_RIGHT: case KEY_SRIGHT:
427 case 'h': case 'H': case KEY_LEFT: case KEY_SLEFT:
437 prev_selected = selected;
443 selected = find_ascii_match(selected+1, selected, 1, searchbuf);
447 selected = find_ascii_match(selected-1, selected, -1, searchbuf);
454 case KEY_ENTER: case '\n':
455 outbuf[buf_i++] = (char)selected;
456 if (buf_i >= buf_size) {
458 outbuf = realloc(outbuf, buf_size);
462 case KEY_BACKSPACE: case KEY_DC: case DELETE_KEY:
463 if (buf_i > 0) buf_i--;
466 case 'q': case 'Q': case KEY_CANCEL: case KEY_CLOSE: case KEY_EXIT: case ESCAPE_KEY:
468 write(STDOUT_FILENO, outbuf, buf_i);
472 goto next_input; // skip redraw
476 if (selected < 0) selected = 0;
477 if (selected > 127) selected = 127;