code / ascii-table

Lines448 C398 make30 Markdown20
(482 lines)
1 /**
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)
10 */
12 #include <curses.h>
13 #include <ncurses.h>
14 #include <poll.h>
15 #include <stdio.h>
16 #include <stdlib.h>
17 #include <string.h>
18 #include <unistd.h>
20 typedef enum {BROWSE_MODE, INSERT_MODE, SEARCH_MODE} Mode;
22 #define DELETE_KEY 127
23 #define ESCAPE_KEY 27
25 // The descriptive names of the ASCII characters
26 const char *NAMES[128][2] = {
27 {"NUL", "null"}, // 0
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
34 {"BEL", "bell"}, // 7
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
59 {" ", "space"}, // 32
60 [127] = {"DEL", "delete"}
61 };
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;
76 int W = 0;
77 int H = 0;
79 static inline int min(int a, int b) {
80 return a < b ? a : b;
83 static inline int max(int a, int b) {
84 return a >= b ? a : b;
87 // Shift the cursor
88 static inline void shift(int dy, int dx) {
89 int x, y;
90 getyx(stdscr, y, x);
91 move(y+dy, x+dx);
94 // Same as printw, except it doesn't do text wrapping, and it sets attributes
95 #define printwattrs(_attrs, ...) \
96 { char _buf[W+1];\
97 chtype _bufch[W+1];\
98 sprintf(_buf, __VA_ARGS__);\
99 for (int i = 0; i == 0 || _buf[i-1]; i++) _bufch[i] = _buf[i] | _attrs;\
100 addchstr(_bufch);\
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;
110 move(y, x);
111 printwattrs(DECIMAL_COLORS | attrs, "%3d ", i);
112 printwattrs(HEX_COLORS | attrs, " 0x%02X ", i);
113 if (NAMES[i][0]) {
114 printwattrs(CHAR_COLORS | attrs, " %-3s ", NAMES[i][0]);
115 } else {
116 printwattrs(CHAR_COLORS | attrs, " %c ", i);
118 if (NAMES[i][1]) {
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);
124 } else {
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
137 erase();
138 for (int i = 0; i < 128; i++) {
139 draw_entry(i, scrollx, i == selected ? A_REVERSE : 0);
141 last_scrollx = scrollx;
142 last_H = H;
143 last_W = W;
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')
161 return c;
162 // Only do full match for search strings longer than 1
163 if (searchbuf[1]) {
164 if (NAMES[c][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);
170 } else {
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)
174 return c;
177 return fallback;
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()
184 timeout(0);
185 do {
186 // Wait 200ms
187 usleep(2e5);
188 // Peek if there's another resize event
189 int k = getch();
190 if (k == KEY_RESIZE) continue;
191 if (k > 0) ungetch(k);
192 } while (0);
193 // Back to blocking input
194 timeout(-1);
195 W = getmaxx(stdscr);
196 H = getmaxy(stdscr);
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");
203 return 0;
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)");
216 exit(EXIT_FAILURE);
218 SCREEN *screen = newterm(term_type, stderr, term_in);
219 set_term(screen);
222 W = getmaxx(stdscr);
223 H = getmaxy(stdscr);
224 noecho();
225 set_escdelay(5);
226 keypad(stdscr, 1);
227 curs_set(2);
228 start_color();
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
249 struct pollfd desc;
250 desc.fd = STDIN_FILENO;
251 desc.events = POLLIN;
252 int ret = poll(&desc, 1, 50);
253 if (ret > 0) {
254 size_t consumed;
255 while ((consumed = fread(&outbuf[buf_i], 1, buf_chunk, stdin))) {
256 buf_i += consumed;
257 if (consumed == buf_chunk) {
258 buf_chunk *= 2;
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
270 // the output buffer
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) {
275 buf_size *= 2;
276 outbuf = realloc(outbuf, buf_size);
280 Mode mode = BROWSE_MODE;
281 int prev_selected = 0, selected = 0;
282 while (1) {
283 redraw(selected);
284 // Title bar
285 move(0,0);
286 char *title;
287 int title_colors;
288 switch (mode) {
289 case INSERT_MODE:
290 title = "ASCII TABLE (typing, 'esc' to browse)";
291 title_colors = INSERT_MODE_COLORS;
292 break;
293 case SEARCH_MODE:
294 title = "ASCII TABLE (searching, 'esc' to browse)";
295 title_colors = SEARCH_MODE_COLORS;
296 break;
297 case BROWSE_MODE:
298 title = "ASCII TABLE (browsing, 'esc' to exit)";
299 title_colors = BROWSE_MODE_COLORS;
300 break;
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);
309 addch(' ');
310 printwattrs(OUTPUT_CHAR_COLORS, "%s", searchbuf);
311 } else {
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;
318 else space -= chlen;
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);
323 addch(' ');
324 for (char *c = visible; c < &outbuf[buf_i]; c++) {
325 if (' ' <= *c && *c <= '~') { // printable range
326 printwattrs(OUTPUT_CHAR_COLORS, "%c", *c);
327 } else {
328 printwattrs(OUTPUT_ESCAPE_COLORS, "\\x%02X", *c);
332 int y, x;
333 getyx(stdscr, y, x);
334 // Clear to end of line
335 for (int x2 = x; x2 < W; x2++) mvaddch(y, x2, ' ');
336 move(y, x);
338 refresh();
340 int key;
341 next_input:
342 key = getch();
343 switch (mode) {
344 case INSERT_MODE:
345 switch (key) {
346 case ESCAPE_KEY:
347 mode = BROWSE_MODE;
348 break;
349 case DELETE_KEY:
350 if (buf_i > 0) buf_i--;
351 break;
352 case KEY_RESIZE:
353 handle_resize();
354 break;
355 default:
356 if (0 <= key && key < 127) {
357 outbuf[buf_i++] = (char)key;
358 if (buf_i >= buf_size) {
359 buf_size *= 2;
360 outbuf = realloc(outbuf, buf_size);
362 break;
363 } else goto next_input; // skip redraw
365 break;
368 case SEARCH_MODE:
369 switch (key) {
370 case ESCAPE_KEY: // Escape
371 selected = prev_selected;
372 searchbuf[0] = '\0';
373 searchlen = 0;
374 mode = BROWSE_MODE;
375 break;
377 case KEY_ENTER: case '\n':
378 mode = BROWSE_MODE;
379 break;
381 case DELETE_KEY:
382 if (searchlen > 0) {
383 searchbuf[searchlen--] = '\0';
384 selected = find_ascii_match(prev_selected, prev_selected, 1, searchbuf);
386 break;
388 case KEY_RESIZE:
389 handle_resize();
390 break;
392 default:
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);
399 break;
400 } else goto next_input; // skip redraw
402 break;
405 case BROWSE_MODE:
406 switch (key) {
407 case 'j': case KEY_DOWN:
408 selected++;
409 break;
411 case 'J':
412 selected += 10;
413 break;
415 case 'k': case KEY_UP:
416 selected--;
417 break;
419 case 'K':
420 selected -= 10;
421 break;
423 case 'l': case 'L': case KEY_RIGHT: case KEY_SRIGHT:
424 selected += H-2;
425 break;
427 case 'h': case 'H': case KEY_LEFT: case KEY_SLEFT:
428 selected -= H-2;
429 break;
431 case 'i': case 'a':
432 mode = INSERT_MODE;
433 break;
435 case '/':
436 mode = SEARCH_MODE;
437 prev_selected = selected;
438 searchbuf[0] = '\0';
439 searchlen = 0;
440 break;
442 case 'n':
443 selected = find_ascii_match(selected+1, selected, 1, searchbuf);
444 break;
446 case 'N':
447 selected = find_ascii_match(selected-1, selected, -1, searchbuf);
448 break;
450 case KEY_RESIZE:
451 handle_resize();
452 break;
454 case KEY_ENTER: case '\n':
455 outbuf[buf_i++] = (char)selected;
456 if (buf_i >= buf_size) {
457 buf_size *= 2;
458 outbuf = realloc(outbuf, buf_size);
460 break;
462 case KEY_BACKSPACE: case KEY_DC: case DELETE_KEY:
463 if (buf_i > 0) buf_i--;
464 break;
466 case 'q': case 'Q': case KEY_CANCEL: case KEY_CLOSE: case KEY_EXIT: case ESCAPE_KEY:
467 endwin();
468 write(STDOUT_FILENO, outbuf, buf_i);
469 return 0;
471 default:
472 goto next_input; // skip redraw
474 break;
476 if (selected < 0) selected = 0;
477 if (selected > 127) selected = 127;
480 endwin();
481 return 0;