diff --git a/apps/more.c b/apps/more.c new file mode 100644 index 00000000..2ba55297 --- /dev/null +++ b/apps/more.c @@ -0,0 +1,192 @@ +#include +#include +#include +#include +#include +#include +#include + +#include + +static int term_width = 80; +static int term_height = 25; + +static int term_x = 0; +static int term_yish = 1; + +static int to_eight(uint32_t codepoint, char * out) { + memset(out, 0x00, 7); + + if (codepoint < 0x0080) { + out[0] = (char)codepoint; + } else if (codepoint < 0x0800) { + out[0] = 0xC0 | (codepoint >> 6); + out[1] = 0x80 | (codepoint & 0x3F); + } else if (codepoint < 0x10000) { + out[0] = 0xE0 | (codepoint >> 12); + out[1] = 0x80 | ((codepoint >> 6) & 0x3F); + out[2] = 0x80 | (codepoint & 0x3F); + } else if (codepoint < 0x200000) { + out[0] = 0xF0 | (codepoint >> 18); + out[1] = 0x80 | ((codepoint >> 12) & 0x3F); + out[2] = 0x80 | ((codepoint >> 6) & 0x3F); + out[3] = 0x80 | ((codepoint) & 0x3F); + } else if (codepoint < 0x4000000) { + out[0] = 0xF8 | (codepoint >> 24); + out[1] = 0x80 | (codepoint >> 18); + out[2] = 0x80 | ((codepoint >> 12) & 0x3F); + out[3] = 0x80 | ((codepoint >> 6) & 0x3F); + out[4] = 0x80 | ((codepoint) & 0x3F); + } else { + out[0] = 0xF8 | (codepoint >> 30); + out[1] = 0x80 | ((codepoint >> 24) & 0x3F); + out[2] = 0x80 | ((codepoint >> 18) & 0x3F); + out[3] = 0x80 | ((codepoint >> 12) & 0x3F); + out[4] = 0x80 | ((codepoint >> 6) & 0x3F); + out[5] = 0x80 | ((codepoint) & 0x3F); + } + + return strlen(out); +} + +static void char_draw(int c) { + if (c == '\t') { + int count = 8 - (term_x % 8); + for (int i = 0; i < count; ++i) { + printf(" "); + } + } else if (c < 32 || c == 0x7F) { + printf("\033[7m^%c\033[0m", (c < 32) ? ('@' + c) : '?'); + } else if (c > 0x7f && c < 0xa0) { + printf("\033[7m<%02x>\033[0m", c); + } else if (c == 0xa0) { + printf("\033[7m \033[0m"); + } else if (c > 127) { + if (wcwidth(c) >= 1) { + char tmp[8] = {0}; + to_eight(c,tmp); + printf("%s", tmp); + } else { + if (c < 0x10000) { + printf("\033[7m[U+%04x]\033[0m", c); + } else { + printf("\033[7m[U+%06x]\033[0m", c); + } + } + } else { + printf("%c", c); + } +} + +static int char_width(int c) { + if (c == '\t') { + return 8 - (term_x % 8); + } else if (c < 32 || c == 0x7F) { + return 2; /* ^@ */ + } else if (c > 0x7f && c < 0xa0) { + return 4; /* */ + } else if (c == 0xa0) { + return 1; /* nbsp */ + } else if (c > 127) { + int out = wcwidth(c); + if (out >= 1) return out; + return (c < 0x10000) ? 8 : 10; + } + return 1; +} + +static struct termios old; +static void get_initial_termios(void) { + tcgetattr(STDOUT_FILENO, &old); +} + +static void set_unbuffered(void) { + struct termios new = old; + new.c_iflag &= (~ICRNL) & (~IXON); + new.c_lflag &= (~ICANON) & (~ECHO); + tcsetattr(STDOUT_FILENO, TCSAFLUSH, &new); +} + +static void set_buffered(void) { + tcsetattr(STDOUT_FILENO, TCSAFLUSH, &old); +} + +static void next_line(void) { + term_yish++; + if (term_yish < term_height) { + printf("\n"); + term_x = 0; + } else { + printf("\n\033[7m--More--\033[0m"); + fflush(stdout); + do { + char buf[1]; + read(STDERR_FILENO, buf, 1); + char c = buf[0]; + if (c == '\n' || c == '\r') { + printf("\r\033[K"); + fflush(stdout); + term_x = 0; + return; + } else if (c == 'q') { + printf("\r\033[K"); + fflush(stdout); + set_buffered(); + exit(0); + } + } while (1); + } +} + +static void do_file(char * name, FILE * f) { + if (!f) { + printf("\033[7m`%s`: %s\033[0m", name, strerror(errno)); + next_line(); + return; + } + uint32_t code, state = 0; + while (!feof(f)) { + int c = fgetc(f); + if (c < 0) break; + if (!decode(&state, &code, c)) { + if (code == '\n') next_line(); + else { + int width = char_width(code); + if (term_x + width > term_width) { + next_line(); + } + char_draw(code); + term_x += width; + } + } else if (state == UTF8_REJECT) { + state = 0; + } + } +} + +int main(int argc, char * argv[]) { + if (argc < 2 && isatty(STDIN_FILENO)) { + fprintf(stderr, "usage: %s file...\n", argv[0]); + return 1; + } + + struct winsize w; + ioctl(STDOUT_FILENO, TIOCGWINSZ, &w); + term_width = w.ws_col; + term_height = w.ws_row; + get_initial_termios(); + set_unbuffered(); + + if (argc < 2) { + do_file("stdin",stdin); + } + + for (int i = 1; i < argc; ++i) { + FILE * f = fopen(argv[1], "r"); + do_file(argv[1], f); + fclose(f); + } + + set_buffered(); + return 0; +}