hun

terminal text editor
git clone git://moonbender.net/hun
Log | Files | Refs | README

hun.c (5028B)


      1 #include <stdlib.h>
      2 #include <unistd.h>
      3 #include <termios.h>
      4 #include <ctype.h>
      5 #include <stdio.h>
      6 #include <errno.h>
      7 #include <sys/ioctl.h>
      8 #include <string.h>
      9 
     10 #define CTRL_KEY(k) ((k) & 0x1f)
     11 
     12 #define NUN_VERSION "1"
     13 
     14 typedef struct
     15 {
     16     int cx, cy;
     17     struct termios termios_original;
     18     int screenrows;
     19     int screencols;
     20 } EditorConfig;
     21 
     22 EditorConfig ed_state;
     23 
     24 void die(const char *s)
     25 {
     26     write(STDOUT_FILENO, "\x1b[2J", 4);
     27     write(STDOUT_FILENO, "\x1b[H", 3);
     28 
     29     perror(s);
     30     exit(1);
     31 }
     32 
     33 void raw_mode_disable(void)
     34 {
     35     if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &ed_state.termios_original) == -1)
     36         die("tcsetattr in raw_mode_disable");
     37 }
     38 
     39 void raw_mode_enable(void)
     40 {
     41     struct termios raw;
     42     if (tcgetattr(STDIN_FILENO, &ed_state.termios_original) == -1) die("tcgetattr");
     43     atexit(raw_mode_disable);
     44 
     45     raw = ed_state.termios_original;
     46     raw.c_iflag &= ~(ICRNL | IXON | BRKINT | INPCK | ISTRIP);
     47     raw.c_oflag &= ~(OPOST);
     48     raw.c_cflag |= ~(CS8);
     49     raw.c_lflag &= ~(ECHO | ICANON | ISIG | IEXTEN);
     50     raw.c_cc[VMIN] = 0;
     51     raw.c_cc[VTIME] = 1;
     52 
     53     if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw) == -1) die("tcsetattr");
     54 }
     55 
     56 char editor_read_key(void)
     57 {
     58     int nread;
     59     char ch;
     60 
     61     while ((nread = read(STDIN_FILENO, &ch, 1)) != 1)
     62     {
     63         if (nread == -1 && errno != EAGAIN) die("read");
     64     }
     65     return ch;
     66 }
     67 
     68 int get_cursor_position(int *rows, int *cols)
     69 {
     70     char buf[32];
     71     unsigned int i = 0;
     72 
     73     if (write(STDOUT_FILENO, "\x1b[6n", 4) != 4) return -1;
     74 
     75     while (i < sizeof(buf) - 1)
     76     {
     77         if (read(STDIN_FILENO, &buf[i], 1) != 1) break;
     78         if (buf[i] == 'R') break;
     79         i++;
     80     }
     81     buf[i] = '\0';
     82 
     83     if (buf[0] != '\x1b' || buf[1] != '[') return -1;
     84     if (sscanf(&buf[2], "%d;%d", rows, cols) != 2) return -1;
     85 
     86     return 0;
     87 }
     88 
     89 int get_window_size(int *rows, int *cols)
     90 {
     91     struct winsize ws;
     92 
     93     if(ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == -1 || ws.ws_col == 0)
     94     {
     95         if (write(STDOUT_FILENO, "\x1b[999C\x1b[999B", 12) != 12) return -1;
     96         return get_cursor_position(rows, cols);
     97     }
     98     else
     99     {
    100         *cols = ws.ws_col;
    101         *rows = ws.ws_row;
    102         return 0;
    103     }
    104 }
    105 
    106 /*** append buffer ***/
    107 
    108 typedef struct {
    109     char *b;
    110     size_t len;
    111 } AppendBuf;
    112 
    113 #define ABUF_INIT {NULL, 0}
    114 
    115 void abuf_append(AppendBuf *ab, const char *s, int len)
    116 {
    117     char *new = realloc(ab->b, ab->len + len);
    118 
    119     if (new == NULL) return;
    120     memcpy(&new[ab->len], s, len);
    121     ab->b = new;
    122     ab->len += len;
    123 }
    124 
    125 void abuf_free(AppendBuf *ab)
    126 {
    127     free(ab->b);
    128 }
    129 
    130 /*** output ***/
    131 
    132 void editor_draw_rows(AppendBuf *ab)
    133 {
    134     int y;
    135     for (y = 0; y < ed_state.screenrows; y++)
    136     {
    137         if (y == ed_state.screenrows / 3)
    138         {
    139             char welcome[80];
    140             int welcomelen;
    141             int padding;
    142             welcomelen = snprintf(welcome, sizeof(welcome), "Honey Text Editor -- Version %s", NUN_VERSION);
    143             if (welcomelen > ed_state.screencols)
    144                 welcomelen = ed_state.screencols;
    145             padding = (ed_state.screencols - welcomelen) / 2;
    146             if (padding)
    147             {
    148                 abuf_append(ab, "~", 1);
    149                 padding--;
    150             }
    151             while (padding--) abuf_append(ab, " ", 1);
    152             abuf_append(ab, welcome, welcomelen);
    153         }
    154         else
    155         {
    156             abuf_append(ab, "~", 1);
    157         }
    158 
    159         abuf_append(ab, "\x1b[K", 3);
    160         if (y < ed_state.screenrows - 1)
    161             abuf_append(ab, "\r\n", 2);
    162     }
    163 }
    164 
    165 void editor_refresh_screen(void)
    166 {
    167     char buf[32];
    168     AppendBuf ab = ABUF_INIT;
    169 
    170     abuf_append(&ab, "\x1b[?25l", 6);
    171     abuf_append(&ab, "\x1b[H", 3);
    172 
    173     editor_draw_rows(&ab);
    174 
    175     snprintf(buf, sizeof(buf), "\x1b[%d;%dH", ed_state.cy + 1, ed_state.cx + 1);
    176     abuf_append(&ab, buf, strlen(buf));
    177 
    178     abuf_append(&ab, "\x1b[?25h", 6);
    179 
    180     write(STDOUT_FILENO, ab.b, ab.len);
    181     abuf_free(&ab);
    182 }
    183 
    184 /*** input ***/
    185 
    186 void editor_move_cursor(char key)
    187 {
    188     switch (key)
    189     {
    190         case 'n':
    191             ed_state.cy++;
    192             break;
    193         case 'e':
    194             ed_state.cy--;
    195             break;
    196         case 'y':
    197             ed_state.cx--;
    198             break;
    199         case 'o':
    200             ed_state.cx++;
    201             break;
    202     }
    203 }
    204 
    205 void editor_process_keypress(void)
    206 {
    207     char ch = editor_read_key();
    208 
    209     switch(ch)
    210     {
    211         case CTRL_KEY('q'):
    212             write(STDOUT_FILENO, "\x1b[2J", 4);
    213             write(STDOUT_FILENO, "\x1b[H", 3);
    214             exit(0);
    215             break;
    216 
    217         case 'n':
    218         case 'e':
    219         case 'y':
    220         case 'o':
    221             editor_move_cursor(ch);
    222             break;
    223     }
    224 } 
    225 
    226 /*** init ***/
    227 
    228 void editor_init(void)
    229 {
    230     ed_state.cx = 0;
    231     ed_state.cy = 0;
    232     if (get_window_size(&ed_state.screenrows, &ed_state.screencols) == -1) die("get_window_size");
    233 }
    234 
    235 int main(void)
    236 {
    237     raw_mode_enable();
    238     editor_init();
    239     while (1)
    240     {
    241         editor_refresh_screen();
    242         editor_process_keypress();
    243     }
    244     return 0;
    245 }