tve.c - ve - a minimal text editor (work in progress)
 (HTM) git clone git://src.adamsgaard.dk/ve
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) README
 (DIR) LICENSE
       ---
       tve.c (24211B)
       ---
            1 /* see LICENSE for license details */
            2 
            3 #include <sys/types.h>
            4 #include <sys/ioctl.h>
            5 
            6 #include <ctype.h>
            7 #include <errno.h>
            8 #include <fcntl.h>
            9 #include <stdarg.h>
           10 #include <stdlib.h>
           11 #include <stdio.h>
           12 #include <stdlib.h>
           13 #include <string.h>
           14 #include <termios.h>
           15 #include <time.h>
           16 #include <unistd.h>
           17 
           18 #include "arg.h"
           19 
           20 /* MACROS */
           21 #define ABUF_INIT {NULL, 0}
           22 #define CTRL_KEY(k) ((k) & 0x1f)
           23 
           24 
           25 /* TYPES */
           26 struct abuf {
           27         char *b;
           28         int len;
           29 };
           30 
           31 /* editor row: stores a row of text */
           32 typedef struct eRow {
           33         char *chars;          /* text read from file */
           34         char *rchars;         /* text to render */
           35         int size;             /* length of chars */
           36         int rsize;            /* length of rchars */
           37         unsigned char *hl;    /* array of rsize storing highlight type per rchar */
           38 } eRow;
           39 
           40 struct editor_config {
           41         int cursor_x, cursor_y, cursor_rx;
           42         int screen_rows, screen_columns;
           43         int num_rows;
           44         eRow *row;
           45         int row_offset, column_offset;
           46         char *filename;
           47         struct termios orig_termios;
           48         int mode; /* 0: normal, 1: insert, 2: visual */
           49         int show_status;
           50         char status_msg[80];
           51         time_t status_msg_time;
           52         int file_changed;
           53         char *find_query;
           54         int find_direction;
           55 };
           56 
           57 /* color scheme types */
           58 enum { ColorNormal, ColorDigit, ColorComment, ColorKeyword };
           59 enum { FG, BG, STYLE };
           60 
           61 /* configuration, allows nested code to access above variables */
           62 #include "config.h"
           63 
           64 
           65 /* FUNCTION DECLARATIONS */
           66 char* editor_prompt(char *prompt);
           67 void editor_set_status_message(const char *fmt, ...);
           68 
           69 
           70 /* GLOBAL VARIABLES */
           71 struct editor_config E;
           72 char *argv0;
           73 
           74 /* FUNCTION DEFINITIONS */
           75 
           76 /** TERMINAL **/
           77 void
           78 die(const char *s)
           79 {
           80         /* clear screen on exit */
           81         write(STDOUT_FILENO, "\x1b[2J", 4);
           82         write(STDOUT_FILENO, "\x1b[H", 3);
           83         perror(s);
           84         exit(1);
           85 }
           86 
           87 void
           88 disable_raw_mode()
           89 {
           90         if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &E.orig_termios) == -1)
           91                 die("tcsetattr in disable_raw_mode()");
           92 }
           93 
           94 void
           95 enable_raw_mode()
           96 {
           97         struct termios raw;
           98 
           99         if (tcgetattr(STDIN_FILENO, &E.orig_termios) == -1)
          100                 die("tcgetattr in enable_raw_mode()");
          101         atexit(disable_raw_mode);
          102 
          103         /* fix modifier keys, set 8 bits per char, ignore interrupts */
          104         raw = E.orig_termios;
          105         raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
          106         raw.c_oflag &= ~(OPOST);
          107         raw.c_cflag |= (CS8);
          108         raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);
          109 
          110         /* set read() timeout in tenths of seconds */
          111         raw.c_cc[VMIN] = 0;
          112         raw.c_cc[VTIME] = 1;
          113 
          114         /* apply terminal settings */
          115         if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw) == -1)
          116                 die("tcsetattr in enable_raw_mode()");
          117 }
          118 
          119 char
          120 editor_read_key()
          121 {
          122         int nread;
          123         char c;
          124         while ((nread = read(STDIN_FILENO, &c, 1)) != 1) {
          125                 if (nread == -1 && errno != EAGAIN)
          126                         die("read in editor_read_key()");
          127         }
          128         return c;
          129 }
          130 
          131 /* get screen size by moving cursor and get current position */
          132 int
          133 get_cursor_position(int *rows, int *cols) {
          134         char buf[32];
          135         unsigned int i;
          136 
          137         if (write(STDOUT_FILENO, "\x1b[6n", 4) != 4)
          138                 return -1;
          139 
          140         i = 0;
          141         while (i < sizeof(buf) - 1) {
          142                 if (read(STDIN_FILENO, &buf[i], 1) != 1)
          143                         break;
          144                 if (buf[i] == 'R')
          145                         break;
          146                 ++i;
          147         }
          148         buf[i] = '\0';
          149 
          150         if (buf[0] != '\x1b' || buf[1] != '[')
          151                 return -1;
          152         if (sscanf(&buf[2], "%d;%d", rows, cols) != 2)
          153                 return -1;
          154         return 0;
          155 }
          156 
          157 int
          158 get_window_size(int *rows, int *cols)
          159 {
          160         struct winsize ws;
          161 
          162         if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == -1 || ws.ws_col == 0) {
          163                 /* fallback screen size detection */
          164                 if (write(STDOUT_FILENO, "\x1b[999C\x1b[999B", 12) != 12)
          165                         return -1;
          166                 return get_cursor_position(rows, cols);
          167         } else {
          168                 *cols = ws.ws_col;
          169                 *rows = ws.ws_row;
          170                 return 0;
          171         }
          172 }
          173 
          174 
          175 /** SYNTAX HIGHLIGHTING **/
          176 
          177 void
          178 editor_update_syntax(eRow *row)
          179 {
          180         int i;
          181         row->hl = realloc(row->hl, row->rsize);
          182         memset(row->hl, ColorNormal, row->rsize);
          183         for (i=0; i<row->rsize; ++i) {
          184                 if (isdigit(row->rchars[i]))
          185                         row->hl[i] = ColorDigit;
          186         }
          187 }
          188 
          189 
          190 /** ROW OPERATIONS **/
          191 
          192 /* navigate over tab-representative string as one character */
          193 int
          194 editor_row_cursor_x_to_rx(eRow *row, int cursor_x)
          195 {
          196         int rx, j;
          197         rx = 0;
          198         for (j=0; j<cursor_x; ++j) {
          199                 if (row->chars[j] == '\t')
          200                         rx += (tab_width - 1) - (rx % tab_width);
          201                 rx++;
          202         }
          203         return rx;
          204 }
          205 
          206 /* translate on-screen position to data position */
          207 int
          208 editor_row_cursor_rx_to_x(eRow *row, int cursor_rx)
          209 {
          210         int cur_rx, cx;
          211         cur_rx = 0;
          212         for (cx=0; cx<row->size; ++cx) {
          213                 if (row->chars[cx] == '\t')
          214                         cur_rx += (tab_width - 1) - (cur_rx % tab_width);
          215                 cur_rx++;
          216 
          217                 if (cur_rx > cursor_rx)
          218                         return cx;
          219         }
          220         return cx;
          221 }
          222 
          223 /* translate tabs before display */
          224 void
          225 editor_row_update(eRow* row)
          226 {
          227         int j, idx, tabs;
          228 
          229         free(row->rchars);
          230         row->rchars = malloc(row->size + 1);
          231 
          232         tabs = 0;
          233         for (j=0; j<row->size; ++j)
          234                 if (row->chars[j] == '\t')
          235                         tabs++;
          236 
          237         free(row->rchars);
          238         row->rchars = malloc(row->size + tabs*(tab_width - 1) + 1);
          239 
          240         idx = 0;
          241         for (j=0; j<row->size; ++j) {
          242                 if (row->chars[j] == '\t') {
          243                         row->rchars[idx++] = '>';
          244                         while (idx % tab_width != 0)
          245                                 row->rchars[idx++] = ' ';
          246                 } else {
          247                         row->rchars[idx++] = row->chars[j];
          248                 }
          249         }
          250         row->rchars[idx] = '\0';
          251         row->rsize = idx;
          252         editor_update_syntax(row);
          253 }
          254 
          255 /* add row to buffer */
          256 void
          257 editor_row_insert(int i, char *s, size_t len)
          258 {
          259         if (i<0 || i>E.num_rows)
          260                 return;
          261 
          262         E.row = realloc(E.row, sizeof(eRow) * (E.num_rows + 1));
          263         memmove(&E.row[i+1], &E.row[i], sizeof(eRow) * (E.num_rows - i));
          264 
          265         E.row[i].size = len;
          266         E.row[i].chars = malloc(len + 1);
          267         memcpy(E.row[i].chars, s, len);
          268         E.row[i].chars[len] = '\0';
          269 
          270         E.row[i].rsize = 0;
          271         E.row[i].rchars = NULL;
          272         E.row[i].hl = NULL;
          273         editor_row_update(&E.row[i]);
          274 
          275         ++E.num_rows;
          276         E.file_changed = 1;
          277 }
          278 
          279 /* insert character to row, making sure to allocate memory accordingly */
          280 void
          281 editor_row_insert_char(eRow *row, int i, int c)
          282 {
          283         if (i<0 || i>row->size)
          284                 i = row->size;
          285         row->chars = realloc(row->chars, row->size + 2);
          286         memmove(&row->chars[i+1], &row->chars[i], row->size - i+1);
          287         row->size++;
          288         row->chars[i] = c;
          289         editor_row_update(row);
          290         E.file_changed = 1;
          291 }
          292 
          293 /* append a string to the end of a row */
          294 void
          295 editor_row_append_string(eRow *row, char *s, size_t len)
          296 {
          297         row->chars = realloc(row->chars, row->size + len + 1);
          298         memcpy(&row->chars[row->size], s, len);
          299         row->size += len;
          300         row->chars[row->size] = '\0';
          301         editor_row_update(row);
          302         E.file_changed = 1;
          303 }
          304 
          305 void
          306 editor_row_delete_char(eRow *row, int i)
          307 {
          308         if (i<0 || i>=row->size)
          309                 return;
          310         memmove(&row->chars[i], &row->chars[i+1], row->size - i);
          311         row->size--;
          312         editor_row_update(row);
          313         E.file_changed = 1;
          314 }
          315 
          316 void
          317 editor_row_free(eRow *row)
          318 {
          319         free(row->chars);
          320         free(row->rchars);
          321         free(row->hl);
          322 }
          323 
          324 void
          325 editor_row_delete(int i)
          326 {
          327         if (i<0 || i>=E.num_rows)
          328                 return;
          329         editor_row_free(&E.row[i]);
          330         memmove(&E.row[i], &E.row[i+1], sizeof(eRow)*(E.num_rows - i - 1));
          331         E.num_rows--;
          332         E.file_changed = 1;
          333 }
          334 
          335 void
          336 editor_insert_char(int c)
          337 {
          338         if (E.cursor_y == E.num_rows)
          339                 editor_row_insert(E.num_rows, "", 0);
          340         editor_row_insert_char(&E.row[E.cursor_y], E.cursor_x, c);
          341         E.cursor_x++;
          342 }
          343 
          344 void
          345 editor_insert_new_line()
          346 {
          347         eRow *row;
          348         if (E.cursor_x == 0) {
          349                 editor_row_insert(E.cursor_y, "", 0);
          350         } else {
          351                 row = &E.row[E.cursor_y];
          352                 editor_row_insert(E.cursor_y + 1, &row->chars[E.cursor_x],
          353                                   row->size - E.cursor_x);
          354                 row = &E.row[E.cursor_y];
          355                 row->size = E.cursor_x;
          356                 row->chars[row->size] = '\0';
          357                 editor_row_update(row);
          358         }
          359         E.cursor_y++;
          360         E.cursor_x = 0;
          361 }
          362 
          363 /* delete a character before the cursor, and allow for deletion across rows */
          364 void
          365 editor_delete_char_left()
          366 {
          367         eRow *row;
          368         if (E.cursor_y == E.num_rows || (E.cursor_x == 0 && E.cursor_y == 0))
          369                 return;
          370         
          371         row = &E.row[E.cursor_y];
          372         if (E.cursor_x > 0) {
          373                 editor_row_delete_char(row, E.cursor_x - 1);
          374                 E.cursor_x--;
          375         } else {
          376                 E.cursor_x = E.row[E.cursor_y - 1].size;
          377                 editor_row_append_string(&E.row[E.cursor_y - 1],
          378                                          row->chars, row->size);
          379                 editor_row_delete(E.cursor_y);
          380                 E.cursor_y--;
          381         }
          382 }
          383 
          384 void
          385 editor_delete_char_right()
          386 {
          387         if (E.cursor_y == E.num_rows)
          388                 return;
          389         eRow *row = &E.row[E.cursor_y];
          390         editor_row_delete_char(row, E.cursor_x);
          391 }
          392 
          393 /* convert rows to one long char array of length buflen */
          394 char*
          395 editor_concatenate_rows(int *buflen)
          396 {
          397         int totlen, j;
          398         char *buf, *p;
          399 
          400         totlen = 0;
          401         for (j=0; j<E.num_rows; ++j)
          402                 totlen += E.row[j].size + 1;  /* add space for newline char */
          403         *buflen = totlen;
          404 
          405         buf = malloc(totlen);
          406         p = buf;
          407         for (j=0; j<E.num_rows; ++j) {
          408                 memcpy(p, E.row[j].chars, E.row[j].size);
          409                 p += E.row[j].size;
          410                 *p = '\n';
          411                 p++;
          412         }
          413         return buf;
          414 }
          415 
          416 
          417 /** FILE IO **/
          418 
          419 /* open file and read lines into memory */
          420 void
          421 file_open(char *filename)
          422 {
          423         free(E.filename);
          424         E.filename = strdup(filename);
          425 
          426         FILE *fp;
          427         char *line;
          428         size_t linecap;
          429         ssize_t linelen;
          430         
          431         fp = fopen(filename, "r");
          432         if (!fp)
          433                 die("fopen in file_open");
          434 
          435         line = NULL;
          436         linecap = 0;
          437         while ((linelen = getline(&line, &linecap, fp)) != -1) {
          438                 while (linelen > 0 && (line[linelen - 1] == '\n' ||
          439                                        line[linelen - 1] == '\r'))
          440                         linelen--;
          441                 editor_row_insert(E.num_rows, line, linelen);
          442         }
          443         free(line);
          444         fclose(fp);
          445         E.file_changed = 0;
          446 }
          447 
          448 void
          449 file_save(char *filename)
          450 {
          451         int len, fd;
          452         char *buf;
          453 
          454         if (filename == NULL) {
          455                 filename = editor_prompt("save as: %s");
          456                 if (filename == NULL) {
          457                         editor_set_status_message("save aborted");
          458                         return;
          459                 }
          460         }
          461 
          462         buf = editor_concatenate_rows(&len);
          463 
          464         fd = open(filename, O_RDWR | O_CREAT, 0644);
          465         if (fd != -1) {
          466                 if (ftruncate(fd, len) != -1) {
          467                         if (write(fd, buf, len) == len) {
          468                                 close(fd);
          469                                 free(buf);
          470                                 E.filename = filename;
          471                                 E.file_changed = 0;
          472                                 editor_set_status_message("%d bytes written to disk", len);
          473                                 return;
          474                         }
          475                 }
          476                 close(fd);
          477         }
          478         free(buf);
          479         editor_set_status_message("error: can't save! I/O error %s",
          480                                   strerror(errno));
          481 }
          482 
          483 
          484 /** FIND **/
          485 
          486 /* reverse of strstr (3) */
          487 char*
          488 strrstr(char *haystack, char *needle,
          489         size_t haystack_length, size_t needle_length)
          490 {
          491   char *cp;
          492   for (cp = haystack + haystack_length - needle_length;
          493        cp >= haystack;
          494        cp--) {
          495     if (strncmp(cp, needle, needle_length) == 0)
          496         return cp;
          497   }
          498   return NULL;
          499 } 
          500 
          501 /* find E.find_query from current cursor position moving in E.direction 
          502  * if opposite_direction = 0, and opposite E.direction if
          503  * opposite_direction = 1 */
          504 void
          505 editor_find_next_occurence(int opposite_direction)
          506 {
          507         int y, y_inc, x_offset, query_len;
          508         eRow *row;
          509         char *match;
          510 
          511         if (!E.find_query)
          512                 return;
          513 
          514         if ((E.find_direction && !opposite_direction) ||
          515                 (!E.find_direction && opposite_direction))
          516                 y_inc = +1;
          517         else
          518                 y_inc = -1;
          519 
          520         x_offset = 0;
          521         query_len = strlen(E.find_query);
          522         /* from cursor until end of document */
          523         for (y = E.cursor_y;
          524              y != ((y_inc == +1) ? E.num_rows : -1);
          525              y += y_inc) {
          526                 row = &E.row[y];
          527 
          528                 if (y == E.cursor_y)
          529                         x_offset = y_inc + E.cursor_x;
          530                 else
          531                         x_offset = 0;
          532 
          533                 if (y_inc == +1) {
          534                         match = strstr(row->chars + x_offset, E.find_query);
          535                         if (match)
          536                                 break;
          537                 } else {
          538                         match = strrstr(row->chars, E.find_query,
          539                                         (y == E.cursor_y) ? E.cursor_x : row->size,
          540                                         query_len);
          541                         if (match)
          542                                 break;
          543                 }
          544         }
          545 
          546         if (match) {
          547                 E.cursor_y = y;
          548                 E.cursor_x = match - row->chars;
          549                 /* E.row_offset = E.num_rows; */ /* put line to top of screen */
          550 
          551         } else {
          552                 /* from other end of file until cursor */
          553                 for (y = (y_inc == +1) ? 0 : E.num_rows - 1;
          554                      y != E.cursor_y + y_inc;
          555                      y += y_inc) {
          556                         row = &E.row[y];
          557                         match = strstr(row->chars, E.find_query);
          558                         if (match)
          559                                 break;
          560                 }
          561                 if (match) {
          562                         E.cursor_y = y;
          563                         E.cursor_x = editor_row_cursor_rx_to_x(row, match - row->chars);
          564                 }
          565         }
          566 }
          567 
          568 /* direction = 1 is forward, 0 is backward */
          569 void
          570 editor_find(int direction)
          571 {
          572         char *query;
          573 
          574         if (direction)
          575                 query = editor_prompt("/%s");
          576         else
          577                 query = editor_prompt("?%s");
          578         if (query == NULL)
          579                 return;
          580 
          581         E.find_direction = direction;
          582         free(E.find_query);
          583         E.find_query = strdup(query);
          584         editor_find_next_occurence(0);
          585         free(query);
          586 }
          587 
          588 
          589 /** OUTPUT **/
          590 
          591 void
          592 editor_update_screen_size()
          593 {
          594         if (get_window_size(&E.screen_rows, &E.screen_columns) == -1)
          595                 die("get_window_size in main()");
          596         E.screen_rows--;
          597         if (E.show_status)
          598                 E.screen_rows--;
          599 }
          600 
          601 /* reallocate append buffer to hold string s */
          602 void
          603 ab_append(struct abuf *ab, const char *s, int len)
          604 {
          605         char *new;
          606         new = realloc(ab->b, ab->len + len);
          607 
          608         if (new == NULL)
          609                 return;
          610         memcpy(&new[ab->len], s, len);
          611         ab->b = new;
          612         ab->len += len;
          613 }
          614 
          615 void
          616 ab_free(struct abuf *ab) {
          617         free(ab->b);
          618 }
          619 
          620 int
          621 show_status_message()
          622 {
          623         if (E.show_status &&
          624             strlen(E.status_msg) &&
          625             time(NULL) - E.status_msg_time < status_message_timeout) {
          626                 return 1;
          627         } else {
          628                 if (E.show_status) {
          629                         E.show_status = 0;
          630                         E.screen_rows++;
          631                 }
          632                 return 0;
          633         }
          634 }
          635 
          636 /* draw status line consisting of left and right components.
          637  * if the screen is narrower than both parts, just show the left status, 
          638  * truncated, if necessary. */
          639 void
          640 editor_draw_status(struct abuf *ab)
          641 {
          642         char left_status[512], right_status[512];
          643         int left_status_len, right_status_len, padding;
          644         int percentage;
          645 
          646         left_status_len = 0;
          647         switch (E.mode) {
          648                 case 1:
          649                         left_status_len = snprintf(left_status, sizeof(left_status),
          650                                                    "INSERT ");
          651                         break;
          652                 case 2:
          653                         left_status_len = snprintf(left_status, sizeof(left_status),
          654                                                    "VISUAL ");
          655                         break;
          656         }
          657         left_status_len += snprintf(left_status + left_status_len,
          658                                     sizeof(left_status),
          659                                     "%.20s %s",
          660                                     E.filename ? E.filename : "[unnamed]",
          661                                     E.file_changed ? "[+] " : "");
          662 
          663         percentage = (int)((float)(E.cursor_y)/(E.num_rows-1)*100);
          664         if (percentage < 0) percentage = 0;
          665         right_status_len = snprintf(right_status, sizeof(right_status),
          666                                     "%d%% < %d, %d",
          667                                     percentage,
          668                                     E.cursor_x+1, E.cursor_y+1);
          669 
          670         if (left_status_len > E.screen_columns)
          671                 left_status_len = E.screen_columns;
          672 
          673         padding = E.screen_columns - left_status_len - right_status_len;
          674         if (padding < 0) {
          675                 if (left_status_len < E.screen_columns)
          676                         ab_append(ab, left_status, left_status_len);
          677                 else
          678                         ab_append(ab, left_status, E.screen_columns);
          679         } else {
          680                 ab_append(ab, left_status, left_status_len);
          681                 while (padding--)
          682                         ab_append(ab, " ", 1);
          683                 ab_append(ab, right_status, right_status_len);
          684         }
          685 
          686         if (show_status_message()) {
          687                 ab_append(ab, "\x1b[m", 3);
          688                 ab_append(ab, "\r\n", 2);
          689         }
          690 }
          691 
          692 /* draw status message if as long as it is specified and it is not too old */
          693 void
          694 editor_draw_status_message(struct abuf *ab)
          695 {
          696         int msglen;
          697         ab_append(ab, "\x1b[K", 3);
          698         msglen = strlen(E.status_msg);
          699         if (msglen > E.screen_columns)
          700                 msglen = E.screen_columns;
          701         if (show_status_message())
          702                 ab_append(ab, E.status_msg, msglen);
          703 }
          704 
          705 /* set vertical offset between file and screen when hitting the boundaries */
          706 void
          707 editor_scroll()
          708 {
          709         E.cursor_rx = 0;
          710         if (E.cursor_y < E.num_rows)
          711                 E.cursor_rx = editor_row_cursor_x_to_rx(&E.row[E.cursor_y],
          712                                                         E.cursor_x);
          713 
          714         if (E.cursor_y < E.row_offset)
          715                 E.row_offset = E.cursor_y;
          716         else if (E.cursor_y >= E.row_offset + E.screen_rows)
          717                 E.row_offset = E.cursor_y - E.screen_rows + 1;
          718 
          719         if (E.cursor_rx < E.column_offset)
          720                 E.column_offset = E.cursor_rx;
          721         else if (E.cursor_rx >= E.column_offset + E.screen_columns)
          722                 E.column_offset = E.cursor_rx - E.screen_columns + 1;
          723 }
          724 
          725 /* draw editor screen.
          726  * show tilde characters after EOF, and show status on last line */
          727 void
          728 editor_draw_rows(struct abuf *ab)
          729 {
          730         int y, j, len, file_row;
          731         char *c;
          732         unsigned char *hl;
          733         char buf[16];
          734         for (y = 0; y < E.screen_rows; ++y) {
          735                 file_row = y + E.row_offset;
          736                 if (file_row < E.num_rows) {
          737                         len = E.row[file_row].rsize - E.column_offset;
          738                         if (len < 0)
          739                                 len = 0;
          740                         if (len > E.screen_columns)
          741                                 len = E.screen_columns;
          742                         c = &E.row[file_row].rchars[E.column_offset];
          743                         hl = &E.row[file_row].hl[E.column_offset];
          744                         for (j = 0; j < len; ++j) {
          745                                 if (hl[j] == ColorDigit) {
          746                                         snprintf(buf, sizeof(buf),
          747                                                  "\x1b[3%dm", colors[ColorDigit][FG]); 
          748                                         ab_append(ab, buf, strlen(buf));
          749                                         /* ab_append(ab, "\x1b[31m", 5); */
          750                                         ab_append(ab, &c[j], 1);
          751                                         ab_append(ab, "\x1b[39m", 5);
          752                                 } else {
          753                                         ab_append(ab, &c[j], 1);
          754                                 }
          755                         }
          756                 } else {
          757                         ab_append(ab, "~", 1);
          758                 }
          759 
          760                 ab_append(ab, "\x1b[K", 3); /* erase to end of line */
          761                 ab_append(ab, "\r\n", 2);
          762         }
          763 }
          764 
          765 /* fill output append buffer and write to terminal.
          766  * move cursor to left before repaint, and to cursor position after.
          767  * hide the cursor when repainting.
          768  * VT100 escape sequences:
          769  * - http://vt100.net/docs/vt100-ug/chapter3.html
          770  * - http://vt100.net/docs/vt100-ug/chapter3.html#CUP */
          771 void
          772 editor_refresh_screen()
          773 {
          774         char buf[32];
          775         struct abuf ab = ABUF_INIT;
          776 
          777         show_status_message();
          778         editor_scroll();
          779 
          780         ab_append(&ab, "\x1b[?25l", 6); /* hide cursor */
          781         ab_append(&ab, "\x1b[H", 3);    /* cursor to home */
          782         
          783         editor_draw_rows(&ab);
          784         editor_draw_status(&ab);
          785         editor_draw_status_message(&ab);
          786 
          787         snprintf(buf, sizeof(buf), "\x1b[%d;%dH",
          788                 (E.cursor_y - E.row_offset)+1,
          789                 (E.cursor_rx - E.column_offset)+1);
          790         ab_append(&ab, buf, strlen(buf));
          791 
          792         ab_append(&ab, "\x1b[?25h", 6); /* show cursor */
          793 
          794         write(STDOUT_FILENO, ab.b, ab.len);
          795         ab_free(&ab);
          796 }
          797 
          798 void
          799 editor_place_cursor(int cx, int cy)
          800 {
          801         char buf[128];
          802         snprintf(buf, sizeof(buf), "\x1b[%d;%dH", cy, cx);
          803         write(STDOUT_FILENO, buf, strlen(buf));
          804 }
          805 
          806 /* set status message text, uses same format as printf */
          807 void
          808 editor_set_status_message(const char *fmt, ...)
          809 {
          810         va_list ap;
          811         va_start(ap, fmt);
          812         vsnprintf(E.status_msg, sizeof(E.status_msg), fmt, ap);
          813         va_end(ap);
          814         E.status_msg_time = time(NULL);
          815 
          816         if (!E.show_status) {
          817                 E.screen_rows--;
          818                 E.show_status = 1;
          819         }
          820 }
          821 
          822 
          823 /** INPUT **/
          824 
          825 /* prompt is expected to be a format string containing a %s */
          826 char*
          827 editor_prompt(char *prompt)
          828 {
          829         size_t bufsize, buflen;
          830         char *buf;
          831         int c;
          832         
          833         bufsize = 128;
          834         buflen = 0;
          835         buf = malloc(bufsize);
          836         buf[0] = '\0';
          837 
          838         while (1) {
          839                 editor_update_screen_size();
          840                 editor_set_status_message(prompt, buf);
          841                 editor_refresh_screen();
          842                 editor_place_cursor(strlen(prompt) - 1 + strlen(buf),
          843                                     E.screen_rows + 2);
          844 
          845                 c = editor_read_key();
          846                 if (c == CTRL_KEY('h') || c == 127) { /* detect backspace */
          847                         if (buflen != 0)
          848                                 buf[--buflen] = '\0';
          849                 } else if (c == '\x1b' || (c == '\r' && !buflen)) { /* detect escape */
          850                         editor_set_status_message("");
          851                         free(buf);
          852                         return NULL;
          853                 } else if (c == '\r') {
          854                         if (buflen != 0) {
          855                                 editor_set_status_message("");
          856                                 return buf;
          857                         }
          858                 } else if (!iscntrl(c) && c < 128) {
          859                         if (buflen >= bufsize - 1) {
          860                                 bufsize *= 2;
          861                                 buf = realloc(buf, bufsize);
          862                         }
          863                         buf[buflen++] = c;
          864                         buf[buflen] = '\0';
          865                 }
          866         }
          867 }
          868 
          869 /* move cursor according to screen, file, and line limits */
          870 void
          871 editor_move_cursor(char key)
          872 {
          873         int row_len;
          874         eRow *row;
          875         
          876         switch(key) {
          877                 case 'h':
          878                         if (E.cursor_x != 0) {
          879                                 E.cursor_x--;
          880                         } else if (E.cursor_y > 0) {
          881                                 E.cursor_y--;
          882                                 E.cursor_x = E.row[E.cursor_y].size;
          883                         }
          884                         break;
          885                 case 'j':
          886                         if (E.cursor_y < E.num_rows - 1)
          887                                 E.cursor_y++;
          888                         break;
          889                 case 'k':
          890                         if (E.cursor_y != 0)
          891                                 E.cursor_y--;
          892                         break;
          893                 case 'l':
          894                         row = (E.cursor_y >= E.num_rows) ? NULL : &E.row[E.cursor_y];
          895                         if (row && E.cursor_x < row->size - 1) {
          896                                 E.cursor_x++;
          897                         } else if (row && E.cursor_x == row->size &&
          898                                    E.cursor_y < E.num_rows - 1) {
          899                                 E.cursor_y++;
          900                                 E.cursor_x = 0;
          901                         }
          902                         break;
          903         }
          904 
          905         /* do not allow navigation past EOL by vertical navigation */
          906         row = (E.cursor_y >= E.num_rows) ? NULL : &E.row[E.cursor_y];
          907         row_len = row ? row->size : 0;
          908         if (E.cursor_x > row_len)
          909                 E.cursor_x = row_len;
          910 }
          911 
          912 void
          913 editor_read_command()
          914 {
          915         char* query;
          916         query = editor_prompt(":%s");
          917 
          918         editor_set_status_message("you typed: '%s'", query);
          919         free(query);
          920 }
          921 
          922 /* first word in query is the command, the remaining are interpreted as args */
          923 void
          924 editor_command(char *query)
          925 {
          926         switch(query) {
          927 
          928         }
          929 }
          930 
          931 
          932 void
          933 editor_process_keypress()
          934 {
          935         char c;
          936         int i;
          937         
          938         c = editor_read_key();
          939 
          940         if (E.mode == 0) {  /* normal mode */
          941                 switch (c) {
          942                         case 'i':
          943                                 E.mode = 1;
          944                                 break;
          945                         case 'a':
          946                                 editor_move_cursor('l');
          947                                 E.mode = 1;
          948                                 break;
          949 
          950                         case 'h':
          951                         case 'j':
          952                         case 'k':
          953                         case 'l':
          954                                 editor_move_cursor(c);
          955                                 break;
          956 
          957                         case ':':
          958                                 editor_read_command();
          959                                 break;
          960 
          961                         case LEADER:
          962                                 c = editor_read_key();
          963                                 switch (c) {
          964                                         case 'w':
          965                                                 file_save(E.filename);
          966                                                 break;
          967                                         case 'q':
          968                                                 if (E.file_changed) {
          969                                                         editor_set_status_message(
          970                                                                 "error: file has unsaved changes! "
          971                                                                 "Press :q! to confirm quit");
          972                                                         break;
          973                                                 } else {
          974                                                         /* clear screen */
          975                                                         write(STDOUT_FILENO, "\x1b[2J", 4); 
          976                                                         write(STDOUT_FILENO, "\x1b[H", 3);
          977                                                         exit(0);
          978                                                         break;
          979                                                 }
          980                                 }
          981                                 break;
          982 
          983                         case CTRL_KEY('f'):
          984                                 i = E.screen_rows;
          985                                 while (i--)
          986                                         editor_move_cursor('j');
          987                                 break;
          988                         case CTRL_KEY('b'):
          989                                 i = E.screen_rows;
          990                                 while (i--)
          991                                         editor_move_cursor('k');
          992                                 break;
          993 
          994                         case CTRL_KEY('d'):
          995                                 i = E.screen_rows/2;
          996                                 while (i--)
          997                                         editor_move_cursor('j');
          998                                 break;
          999                         case CTRL_KEY('u'):
         1000                                 i = E.screen_rows/2;
         1001                                 while (i--)
         1002                                         editor_move_cursor('k');
         1003                                 break;
         1004 
         1005                         case '0':
         1006                                 E.cursor_x = 0;
         1007                                 break;
         1008                         case '$':
         1009                                 if (E.cursor_y < E.num_rows)
         1010                                         E.cursor_x = E.row[E.cursor_y].size - 1;
         1011                                 break;
         1012 
         1013                         case 'g':
         1014                                 c = editor_read_key();
         1015                                 if (c == 'g') {
         1016                                         E.cursor_x = 0;
         1017                                         E.cursor_y = 0;
         1018                                 }
         1019                                 break;
         1020                         case 'G':
         1021                                 E.cursor_x = 0;
         1022                                 E.cursor_y = E.num_rows - 1;
         1023                                 break;
         1024 
         1025                         case 'x':
         1026                                 editor_delete_char_right();
         1027                                 break;
         1028                         case 'd':
         1029                                 c = editor_read_key();
         1030                                 if (c == 'd') {
         1031                                         editor_row_delete(E.cursor_y);
         1032                                         editor_move_cursor('h');
         1033                                 }
         1034                                 break;
         1035 
         1036                         case 'o':
         1037                                 if (E.cursor_y < E.num_rows)
         1038                                         E.cursor_x = E.row[E.cursor_y].size;
         1039                                 editor_insert_new_line();
         1040                                 E.mode = 1;
         1041                                 break;
         1042                         case 'O':
         1043                                 E.cursor_x = 0;
         1044                                 editor_insert_new_line();
         1045                                 editor_move_cursor('k');
         1046                                 E.mode = 1;
         1047                                 break;
         1048 
         1049                         case 'I':
         1050                                 E.cursor_x = 0;
         1051                                 E.mode = 1;
         1052                                 break;
         1053                         case 'A':
         1054                                 if (E.cursor_y < E.num_rows)
         1055                                         E.cursor_x = E.row[E.cursor_y].size;
         1056                                 E.mode = 1;
         1057                                 break;
         1058 
         1059                         case '/':
         1060                                 editor_find(1);
         1061                                 break;
         1062                         case '?':
         1063                                 editor_find(0);
         1064                                 break;
         1065                         case 'n':
         1066                                 editor_find_next_occurence(0);
         1067                                 break;
         1068                         case 'N':
         1069                                 editor_find_next_occurence(1);
         1070                                 break;
         1071                 }
         1072         } else if (E.mode == 1) {  /* insert mode */
         1073                 switch (c) {
         1074                         case CTRL_KEY('c'):
         1075                         case '\x1b':  /* escape */
         1076                                 E.mode = 0;
         1077                                 break;
         1078                         
         1079                         case CTRL_KEY('\r'): /* enter */
         1080                                 editor_insert_new_line();
         1081                                 break;
         1082 
         1083                         case 127:  /* backspace */
         1084                         case CTRL_KEY('h'):
         1085                                 editor_delete_char_left();
         1086                                 break;
         1087 
         1088                         case CTRL_KEY('l'):
         1089                                 break;
         1090 
         1091                         default:
         1092                                 editor_insert_char(c);
         1093                                 break;
         1094                 }
         1095         }
         1096 }
         1097 
         1098 
         1099 /** INIT **/
         1100 
         1101 void
         1102 deinit_editor() {
         1103         int i;
         1104         free(E.filename);
         1105         free(E.find_query);
         1106         for (i=0; i<E.num_rows; ++i)
         1107                 editor_row_free(&E.row[i]);
         1108         free(E.row);
         1109 }
         1110 
         1111 /* set editor state variables, make room for status */
         1112 void
         1113 init_editor()
         1114 {
         1115         E.cursor_x = 0;
         1116         E.cursor_y = 0;
         1117         E.cursor_rx = 0;
         1118         E.mode = 0;
         1119         E.num_rows = 0;
         1120         atexit(deinit_editor);
         1121         E.row = NULL;
         1122         E.row_offset = 0;
         1123         E.column_offset = 0;
         1124         E.filename = NULL;
         1125         E.status_msg[0] = '\0';
         1126         E.status_msg_time = 0;
         1127         E.show_status = 0;
         1128         E.file_changed = 0;
         1129         E.find_query = NULL;
         1130 }
         1131 
         1132 void
         1133 usage()
         1134 {
         1135         printf("%s: %s [OPTIONS] [FILES]\n"
         1136                "\nOptional arguments:\n"
         1137                " -h        show this message\n"
         1138                " -v        show version information\n",
         1139                __func__, PROGNAME);
         1140 }
         1141 
         1142 void
         1143 version()
         1144 {
         1145         printf("%s version %s\n"
         1146                "Licensed under the GNU Public License, v3+\n"
         1147                "written by Anders Damsgaard, anders@adamsgaard.dk\n"
         1148                "https://gitlab.com/admesg/ve\n",
         1149                PROGNAME, VERSION);
         1150 }
         1151 
         1152 /** MAIN **/
         1153 int
         1154 main(int argc, char *argv[])
         1155 {
         1156         ARGBEGIN {
         1157         case 'h':
         1158                 usage();
         1159                 return 0;
         1160         case 'v':
         1161                 version();
         1162                 return 0;
         1163         default:
         1164                 usage();
         1165         } ARGEND;
         1166 
         1167         enable_raw_mode();
         1168         init_editor();
         1169 
         1170         editor_set_status_message("%s %s", PROGNAME, VERSION);
         1171 
         1172         if (argv[0])
         1173                 file_open(argv[0]);
         1174 
         1175         while (1) {
         1176                 editor_update_screen_size();
         1177                 editor_refresh_screen();
         1178                 editor_process_keypress();
         1179         }
         1180         return 0;
         1181 }