Advertisement
cutecoder

Simple file reader using termios and ANSI escape codes

Sep 5th, 2023
61
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C 5.71 KB | Source Code | 0 0
  1. #include <ctype.h>
  2. #include <errno.h>
  3. #include <fcntl.h>
  4. #include <locale.h>
  5. #include <stdlib.h>
  6. #include <stdio.h>
  7. #include <string.h>
  8. #include <sys/ioctl.h>
  9. #include <sys/types.h>
  10. #include <termios.h>
  11. #include <unistd.h>
  12.  
  13. #define MIN(a, b) ({ \
  14.     __auto_type _a = (a); \
  15.     __auto_type _b = (b); \
  16.     _a < _b ? _a : _b; \
  17. })
  18.  
  19. #define CTRL_KEY(k) ((k) & 0x1f)
  20.  
  21. enum {
  22.     KEY_BACKSPACE = 0x7f,
  23.     KEY_LEFT = 0400,
  24.     KEY_RIGHT,
  25.     KEY_UP,
  26.     KEY_DOWN,
  27.     KEY_DEL,
  28.     KEY_HOME,
  29.     KEY_END,
  30.     KEY_PAGEUP,
  31.     KEY_PAGEDN,
  32. };
  33.  
  34. struct termios orig_term;
  35. int term_lines;
  36. int term_cols;
  37. int term_tabsize = 8;
  38.  
  39. struct line {
  40.     int indent;
  41.     char *text;
  42.     size_t ntext;
  43. };
  44.  
  45. struct buffer {
  46.     struct line *lines;
  47.     size_t nlines;
  48.     size_t nbytes;
  49. };
  50.  
  51. struct buffer buf;
  52.  
  53. int view_width, view_height;
  54. size_t v_scroll;
  55.  
  56. int line_append(struct line *line, char ch)
  57. {
  58.     char *newtext;
  59.  
  60.     if (line->ntext == 0 && isblank(ch)) {
  61.         line->indent += ch == ' ' ? 1 : 8;
  62.         return 0;
  63.     }
  64.     newtext = realloc(line->text, sizeof(*line->text) * (line->ntext + 1));
  65.     if (newtext == NULL)
  66.         return -1;
  67.     line->text = newtext;
  68.     line->text[line->ntext++] = ch;
  69.     return 0;
  70. }
  71.  
  72. struct line *buf_newline(void)
  73. {
  74.     struct line *newlines;
  75.  
  76.     newlines = realloc(buf.lines, sizeof(*buf.lines) * (buf.nlines + 1));
  77.     if (newlines == NULL)
  78.         return NULL;
  79.     buf.lines = newlines;
  80.     newlines += buf.nlines;
  81.     memset(newlines, 0, sizeof(*newlines));
  82.     buf.nlines++;
  83.     return newlines;
  84. }
  85.  
  86. int buf_read_file(const char *file)
  87. {
  88.     int fd;
  89.     char b[1024];
  90.     struct line *line;
  91.     ssize_t r;
  92.  
  93.     if ((fd = open(file, O_RDONLY)) < 0)
  94.         return -1;
  95.  
  96.     buf.nlines = 0;
  97.     buf.nbytes = 0;
  98.     if ((line = buf_newline()) == NULL) {
  99.         close(fd);
  100.         return -1;
  101.     }
  102.     while ((r = read(fd, b, sizeof(b))) > 0)
  103.         for(const char *p = b; r; r--, p++) {
  104.             if (*p == '\n' && ((line = buf_newline()) == NULL))
  105.                 goto sudden_out_of_mem;
  106.             if (*p != '\n' && line_append(line, *p) < 0)
  107.                 goto sudden_out_of_mem;
  108.             buf.nbytes++;
  109.         }
  110.  
  111. /* fall through */
  112. sudden_out_of_mem:
  113.  
  114.     close(fd);
  115.     return 0;
  116. }
  117.  
  118. void handle_char(int c)
  119. {
  120.     switch (c) {
  121.     /*case KEY_RESIZE:
  122.         view_width = term_cols;
  123.         view_height = MIN((size_t) (term_lines - 1), buf.nlines);
  124.         break;*/
  125.     case KEY_DOWN:
  126.     case 'j':
  127.         if (v_scroll == buf.nlines - view_height)
  128.             break;
  129.         v_scroll++;
  130.         break;
  131.     case KEY_UP:
  132.     case 'k':
  133.         if (v_scroll == 0)
  134.             break;
  135.         v_scroll--;
  136.         break;
  137.     case KEY_HOME:
  138.     case 'g':
  139.         v_scroll = 0;
  140.         break;
  141.     case KEY_END:
  142.     case 'G':
  143.         v_scroll = buf.nlines - view_height;
  144.         break;
  145.     }
  146. }
  147.  
  148. int enable_raw_mode(void)
  149. {
  150.     struct termios raw;
  151.  
  152.     if (tcgetattr(STDIN_FILENO, &orig_term) == -1)
  153.         return -1;
  154.  
  155.     raw = orig_term;
  156.     raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
  157.     raw.c_oflag &= ~(OPOST);
  158.     raw.c_cflag |= CS8;
  159.     raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);
  160.     raw.c_cc[VMIN] = 1;
  161.     raw.c_cc[VTIME] = 0;
  162.     /* For non blocking */
  163.     //raw.c_cc[VMIN] = 0;
  164.     //raw.c_cc[VTIME] = 1;
  165.  
  166.     if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw) == -1)
  167.         return -1;
  168.     return 0;
  169. }
  170.  
  171. int get_window_size(void)
  172. {
  173.     struct winsize ws;
  174.  
  175.     if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == -1)
  176.         return 0;
  177.     term_lines = ws.ws_row;
  178.     term_cols = ws.ws_col;
  179.     return 0;
  180. }
  181.  
  182. int get_key(void)
  183. {
  184.     int c;
  185.  
  186.     if ((c = getchar()) != 0x1b)
  187.         return c;
  188.     if ((c = getchar()) == '[') {
  189.         c = getchar();
  190.         if (isdigit(c)) {
  191.             c = getchar();
  192.             if (c == '~') {
  193.                 switch (c) {
  194.                     case '1': return KEY_HOME;
  195.                     case '3': return KEY_DEL;
  196.                     case '4': return KEY_END;
  197.                     case '5': return KEY_PAGEUP;
  198.                     case '6': return KEY_PAGEDN;
  199.                     case '7': return KEY_HOME;
  200.                     case '8': return KEY_END;
  201.                 }
  202.             }
  203.         } else {
  204.             switch (c) {
  205.                 case 'A': return KEY_UP;
  206.                 case 'B': return KEY_DOWN;
  207.                 case 'C': return KEY_RIGHT;
  208.                 case 'D': return KEY_LEFT;
  209.                 case 'H': return KEY_HOME;
  210.                 case 'F': return KEY_END;
  211.             }
  212.         }
  213.     } else if (c == 'O') {
  214.         c = getchar();
  215.         switch (c) {
  216.             case 'H': return KEY_HOME;
  217.             case 'F': return KEY_END;
  218.         }
  219.     }
  220.     return -1;
  221. }
  222.  
  223. int main(int argc, char **argv)
  224. {
  225.     if (argc < 2) {
  226.         fprintf(stderr, "usage: %s <file name>\n", argv[0]);
  227.         return -1;
  228.     }
  229.  
  230.     setlocale(LC_ALL, "");
  231.  
  232.     /* make stdout unbuffered */
  233.     setbuf(stdout, NULL);
  234.  
  235.     if (enable_raw_mode() == -1) {
  236.         fprintf(stderr, "couldn't enabel raw mode\n");
  237.         return -1;
  238.     }
  239.     if (get_window_size() == -1) {
  240.         tcsetattr(STDIN_FILENO, TCSAFLUSH, &orig_term);
  241.         fprintf(stderr, "unable to get terminal size\n");
  242.         return -1;
  243.     }
  244.  
  245.     if (buf_read_file(argv[1]) == -1) {
  246.         tcsetattr(STDIN_FILENO, TCSAFLUSH, &orig_term);
  247.         fprintf(stderr, "error reading file '%s'\n", argv[1]);
  248.         return -1;
  249.     }
  250.     printf("\x1b[?25l"); /* hide the cursor */
  251.  
  252.     /* Initial size */
  253.     view_width = term_cols;
  254.     view_height = MIN((size_t) (term_lines - 1), buf.nlines);
  255.  
  256.     /* Main loop */
  257.     while (1) {
  258.         printf("\x1b[0;0H");
  259.         for (size_t i = 0, e = MIN((size_t) view_height, buf.nlines);
  260.                 i < e; i++) {
  261.             const struct line *const line = buf.lines +
  262.                 v_scroll + i;
  263.             write(STDIN_FILENO, line->text, line->ntext);
  264.             printf("\x1b[K\r\n");
  265.         }
  266.         printf("\x1b[7m"); /* reverse video */
  267.         printf("%4zu-%4zu/%4zu (%3d%%) %zub (view=%dx%d) (term=%dx%d)",
  268.             v_scroll + 1, v_scroll + view_height,
  269.             buf.nlines,
  270.             buf.nlines == (size_t) view_height ? 100 :
  271.                 (int) (100 *
  272.                     v_scroll / (buf.nlines - view_height)),
  273.             buf.nbytes,
  274.             view_width, view_height,
  275.             term_cols, term_lines);
  276.         printf("\x1b[0m"); /* reset attributes */
  277.  
  278.         const int c = get_key();
  279.         if (c == CTRL_KEY('C') || c == 'q')
  280.             break;
  281.         handle_char(c);
  282.     }
  283.  
  284.     for (size_t i = 0; i < buf.nlines; i++)
  285.         free(buf.lines[i].text);
  286.     free(buf.lines);
  287.  
  288.     tcsetattr(STDIN_FILENO, TCSAFLUSH, &orig_term);
  289.     return 0;
  290. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement