kilo

Kilo: minimal text editor written in C
git clone git://git.dimitrijedobrota.com/kilo.git
Log | Files | Refs

kilo.c (29680B)


      1 /*** includes ***/
      2 
      3 #define _DEFAULT_SOURCE
      4 #define _BSD_SOURCE
      5 #define _GNU_SOURCE
      6 
      7 #include <ctype.h>
      8 #include <errno.h>
      9 #include <fcntl.h>
     10 #include <limits.h>
     11 #include <stdarg.h>
     12 #include <stdio.h>
     13 #include <stdlib.h>
     14 #include <string.h>
     15 #include <sys/ioctl.h>
     16 #include <sys/types.h>
     17 #include <termios.h>
     18 #include <time.h>
     19 #include <unistd.h>
     20 
     21 /*** utils ***/
     22 
     23 #define MIN(x, y) ((x) < (y) ? (x) : (y))
     24 #define MAX(x, y) ((x) > (y) ? (x) : (y))
     25 
     26 #define RCLAMP(a, x, y)    (MAX((x), MIN((y), (a))))
     27 #define RCLAMP_LEFT(a, x)  (MAX((a), (x)))
     28 #define RCLAMP_RIGHT(a, x) (MIN((a), (x)))
     29 
     30 #define CLAMP(a, x, y)    ((a) = RCLAMP(a, x, y))
     31 #define CLAMP_LEFT(a, x)  ((a) = RCLAMP_LEFT(a, x))
     32 #define CLAMP_RIGHT(a, x) ((a) = RCLAMP_RIGHT(a, x))
     33 
     34 int numberOfDigits(int n) {
     35   if (n < 0) n = (n == INT_MIN) ? INT_MAX : -n;
     36   if (n < 10) return 1;
     37   if (n < 100) return 2;
     38   if (n < 1000) return 3;
     39   if (n < 10000) return 4;
     40   if (n < 100000) return 5;
     41   if (n < 1000000) return 6;
     42   if (n < 10000000) return 7;
     43   if (n < 100000000) return 8;
     44   if (n < 1000000000) return 9;
     45   return 10;
     46 }
     47 
     48 /*** defines ***/
     49 
     50 #define KILO_VERSION    "0.0.1"
     51 #define KILO_TAB_STOP   8
     52 #define KILO_TAP_SOFT   1
     53 #define KILO_QUIT_TIMES 3
     54 
     55 #define KILO_NUM_RELATIVE 1
     56 #define KILO_MARGIN_LEFT  1
     57 
     58 #define CTRL_KEY(k) ((k)&0x1f)
     59 
     60 enum editorKey {
     61   BACKSPACE = 127,
     62   ARROW_LEFT = 1000,
     63   ARROW_RIGHT,
     64   ARROW_UP,
     65   ARROW_DOWN,
     66   DEL_KEY,
     67   HOME_KEY,
     68   END_KEY,
     69   PAGE_UP,
     70   PAGE_DOWN
     71 };
     72 
     73 enum editorHighlight {
     74   HL_NORMAL = 0,
     75   HL_COMMENT,
     76   HL_MLCOMMENT,
     77   HL_KEYWORD1,
     78   HL_KEYWORD2,
     79   HL_KEYWORD3,
     80   HL_KEYWORD4,
     81   HL_MATCH,
     82   HL_NUMBER,
     83   HL_OPERATORS,
     84   HL_STRING,
     85   HL_ABRAKETS,
     86 
     87   HL_BRACKETS,
     88   HL_SBRACKETS,
     89   HL_CBRACKETS,
     90   HL_SEMICOLON,
     91   HL_COMMA,
     92 
     93   HL_LINEN_CRNT,
     94   HL_LINEN,
     95 };
     96 
     97 #define HL_HIGHLIGHT_NUMBERS   (1 << 0)
     98 #define HL_HIGHLIGHT_STRINGS   (1 << 1)
     99 #define HL_HIGHLIGHT_ABRACKETS (1 << 2)
    100 #define HL_HIGHLIGHT_BRACKETS  (1 << 3)
    101 #define HL_HIGHLIGHT_CBRACKETS (1 << 4)
    102 #define HL_HIGHLIGHT_SBRACKETS (1 << 5)
    103 #define HL_HIGHLIGHT_SEMICOLON (1 << 6)
    104 #define HL_HIGHLIGHT_COMMA     (1 << 7)
    105 
    106 /*** data ***/
    107 
    108 struct editorSyntax {
    109   char *filetype;
    110   char **filematch;
    111   char **keywords;
    112   char **operators;
    113   char *singleline_comment_start;
    114   char *multiline_comment_start;
    115   char *multiline_comment_end;
    116   int flags;
    117 };
    118 
    119 typedef struct erow {
    120   int idx;
    121   int size;
    122   int rsize;
    123   char *chars;
    124   char *render;
    125   unsigned char *hl;
    126   int hl_open_comment;
    127 } erow;
    128 
    129 struct editorConfig {
    130   int cx, cy;
    131   int rx;
    132   int rowoff;
    133   int coloff;
    134   int screenrows;
    135   int screencols;
    136   int numrows;
    137   int row_digits;
    138   erow *row;
    139   int dirty;
    140   char *filename;
    141   char statusmsg[80];
    142   time_t statusmsg_time;
    143   struct editorSyntax *syntax;
    144   struct termios orig_termios;
    145 };
    146 
    147 struct editorConfig E;
    148 
    149 /*** filetypes ***/
    150 
    151 char *C_HL_extensions[] = {".c", ".h", ".cpp", NULL};
    152 
    153 /* clang-format off */ 
    154 char *C_HL_keywords[] = {
    155     "auto",   "break",    "case",   "const",    "continue", "default",
    156     "do",     "else",     "enum",   "extern",   "for",      "goto",
    157     "if",     "register", "return", "sizeof",   "static",   "struct",
    158     "switch", "typedef",  "union",  "volatile", "while",
    159 
    160     "int|",  "long|",     "double|", "float|",
    161     "char|", "unsigned|", "signed|", "void|",
    162 
    163     "#include#", "#error#",  "#define#", "#undef#",
    164     "#ifdef#",   "#ifndef#", "#endif#",
    165 
    166     "__FILE__*", "__LINE__*",         "__DATE__*",        "__TIME__*",
    167     "__STDC__*", "__STDC_VERSION__*", "__STDC_HOSTED__*", "NULL*", NULL};
    168 /* clang-format on */
    169 
    170 char *C_HL_operators[] = {
    171     "...", "<=>", "<<=", ">>=", "++", "--", "==", "!=", ">=", "<=", "&&",
    172     "||",  "<<",  ">>",  "+=",  "-=", "*=", "/=", "%=", "&=", "|=", "^=",
    173     "->",  "::",  "**",  "-",   "*",  "/",  "%",  ">",  "<",  "!",  "~",
    174     "&",   "|",   "^",   "=",   ".",  "+",  "?",  ":",  NULL};
    175 
    176 struct editorSyntax HLDB[] = {
    177     {"c", C_HL_extensions, C_HL_keywords, C_HL_operators, "//", "/*", "*/",
    178      HL_HIGHLIGHT_NUMBERS | HL_HIGHLIGHT_STRINGS | HL_HIGHLIGHT_ABRACKETS |
    179      HL_HIGHLIGHT_BRACKETS | HL_HIGHLIGHT_CBRACKETS |
    180      HL_HIGHLIGHT_SBRACKETS | HL_HIGHLIGHT_COMMA | HL_HIGHLIGHT_SEMICOLON},
    181 };
    182 
    183 #define HLDB_ENTRIES (sizeof(HLDB) / sizeof(HLDB[0]))
    184 
    185 /*** prototypes ***/
    186 
    187 void editorSetStatusMessage(const char *fmt, ...);
    188 void editorRefreshScreen();
    189 char *editorPrompt(char *prompt, void (*callback)(char *, int));
    190 
    191 /*** terminal ***/
    192 
    193 void die(const char *s) {
    194   write(STDOUT_FILENO, "\x1b[2J", 4);
    195   write(STDOUT_FILENO, "\x1b[H", 3);
    196 
    197   perror(s);
    198   exit(1);
    199 }
    200 
    201 void disableRawMode() {
    202   if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &E.orig_termios) == -1)
    203     die("tcsetattr");
    204 }
    205 
    206 void enableRawMode() {
    207   if (tcgetattr(STDIN_FILENO, &E.orig_termios) == -1) die("tcgetattr");
    208   atexit(disableRawMode);
    209 
    210   struct termios raw = E.orig_termios;
    211 
    212   raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
    213   raw.c_oflag &= ~(OPOST);
    214   raw.c_cflag |= ~(CS8);
    215   raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);
    216   raw.c_cc[VMIN] = 0;
    217   raw.c_cc[VTIME] = 1;
    218 
    219   if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw) == -1) die("tcsetattr");
    220 }
    221 
    222 int editorReadKey() {
    223   int nread;
    224   char c;
    225   while ((nread = read(STDIN_FILENO, &c, 1)) != 1) {
    226     if (nread == -1 && errno != EAGAIN) die("read");
    227   }
    228 
    229   if (c == '\x1b') {
    230     char seq[3];
    231 
    232     if (read(STDIN_FILENO, &seq[0], 1) != 1) return '\x1b';
    233     if (read(STDIN_FILENO, &seq[1], 1) != 1) return '\x1b';
    234 
    235     if (seq[0] == '[') {
    236       if (seq[1] >= '0' && seq[1] <= '9') {
    237         if (read(STDIN_FILENO, &seq[2], 1) != 1) return '\x1b';
    238         if (seq[2] == '~') {
    239           switch (seq[1]) {
    240           case '1': return HOME_KEY;
    241           case '3': return DEL_KEY;
    242           case '4': return END_KEY;
    243           case '5': return PAGE_UP;
    244           case '6': return PAGE_DOWN;
    245           case '7': return HOME_KEY;
    246           case '8': return END_KEY;
    247           }
    248         }
    249       } else {
    250         switch (seq[1]) {
    251         case 'A': return ARROW_UP;
    252         case 'B': return ARROW_DOWN;
    253         case 'C': return ARROW_RIGHT;
    254         case 'D': return ARROW_LEFT;
    255         case 'H': return HOME_KEY;
    256         case 'F': return END_KEY;
    257         }
    258       }
    259     } else if (seq[0] == 'O') {
    260       switch (seq[1]) {
    261       case 'H': return HOME_KEY;
    262       case 'F': return END_KEY;
    263       }
    264     }
    265     return '\x1b';
    266   }
    267 
    268   return c;
    269 }
    270 
    271 int getCursorPosition(int *rows, int *cols) {
    272   char buf[32];
    273   unsigned int i = 0;
    274 
    275   if (write(STDOUT_FILENO, "\x1b[6n", 4) != 4) return -1;
    276 
    277   while (i < sizeof(buf) - 1) {
    278     if (read(STDIN_FILENO, &buf[i], 1) != 1) break;
    279     if (buf[i] == 'R') break;
    280     i++;
    281   }
    282   buf[i] = '\0';
    283 
    284   if (buf[0] != '\x1b' || buf[1] != '[') return -1;
    285   if (sscanf(&buf[2], "%d;%d", rows, cols) != 2) return -1;
    286 
    287   return 0;
    288 }
    289 
    290 int getWindowSize(int *rows, int *cols) {
    291   struct winsize ws;
    292 
    293   if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == -1 || ws.ws_col == 0) {
    294     if (write(STDOUT_FILENO, "\x1b[999C\x1b[999B", 12) != 12) return -1;
    295     return getCursorPosition(rows, cols);
    296   } else {
    297     *cols = ws.ws_col;
    298     *rows = ws.ws_row;
    299   }
    300   return 0;
    301 }
    302 
    303 /*** syntax highlighting ***/
    304 
    305 int is_separator(int c) {
    306   return isspace(c) || c == '\0' || strchr(",.()+-/*=~%<>[]{};", c) != NULL;
    307 }
    308 
    309 void editorUpdateSyntax(erow *row) {
    310   row->hl = realloc(row->hl, row->rsize);
    311   memset(row->hl, HL_NORMAL, row->rsize);
    312 
    313   if (E.syntax == NULL) return;
    314 
    315   char **keywords = E.syntax->keywords;
    316   char **operators = E.syntax->operators;
    317 
    318   char *scs = E.syntax->singleline_comment_start;
    319   char *mcs = E.syntax->multiline_comment_start;
    320   char *mce = E.syntax->multiline_comment_end;
    321 
    322   int scs_len = scs ? strlen(scs) : 0;
    323   int mcs_len = mcs ? strlen(mcs) : 0;
    324   int mce_len = mce ? strlen(mce) : 0;
    325 
    326   int prev_sep = 1, in_string = 0, in_abrackets = 0;
    327   int in_comment = (row->idx > 0 && E.row[row->idx - 1].hl_open_comment);
    328   int i = 0;
    329   while (i < row->rsize) {
    330     char c = row->render[i];
    331     unsigned char prev_hl = (i > 0) ? row->hl[i - 1] : HL_NORMAL;
    332 
    333     if (scs_len && !in_string && !in_comment) {
    334       if (!strncmp(&row->render[i], scs, scs_len)) {
    335         memset(&row->hl[i], HL_COMMENT, row->rsize - i);
    336         break;
    337       }
    338     }
    339 
    340     if (mcs_len && mce_len && !in_string) {
    341       if (in_comment) {
    342         row->hl[i] = HL_MLCOMMENT;
    343         if (!strncmp(&row->render[i], mce, mce_len)) {
    344           memset(&row->hl[i], HL_MLCOMMENT, mce_len);
    345           i += mce_len;
    346           in_comment = 0;
    347           prev_sep = 1;
    348           continue;
    349         } else {
    350           i++;
    351           continue;
    352         }
    353       } else if (!strncmp(&row->render[i], mcs, mcs_len)) {
    354         memset(&row->hl[i], HL_MLCOMMENT, mcs_len);
    355         i += mcs_len;
    356         in_comment = 1;
    357         continue;
    358       }
    359     }
    360 
    361     if (E.syntax->flags & HL_HIGHLIGHT_STRINGS) {
    362       if (in_string) {
    363         row->hl[i] = HL_STRING;
    364         if (c == '\\' && i + 1 < row->rsize) {
    365           row->hl[i + 1] = HL_STRING;
    366           i += 2;
    367           continue;
    368         }
    369         if (c == in_string) in_string = 0;
    370         i++;
    371         prev_sep = 1;
    372         continue;
    373       } else {
    374         if (c == '"' || c == '\'') {
    375           in_string = c;
    376           row->hl[i] = HL_STRING;
    377           i++;
    378           continue;
    379         }
    380       }
    381     }
    382 
    383     if (E.syntax->flags & HL_HIGHLIGHT_ABRACKETS) {
    384       if (in_abrackets) {
    385         row->hl[i++] = HL_ABRAKETS;
    386         if (c == '>') in_abrackets = 0;
    387         prev_sep = 1;
    388         continue;
    389       } else {
    390         if (c == '<' && strrchr(row->chars, '>')) {
    391           in_abrackets = 1;
    392           row->hl[i++] = HL_ABRAKETS;
    393           continue;
    394         }
    395       }
    396     }
    397 
    398     if (E.syntax->flags & HL_HIGHLIGHT_NUMBERS) {
    399       if ((isdigit(c) && (prev_sep || prev_hl == HL_NUMBER)) ||
    400           ((c == '.' || c == 'x' || c == 'l' || c == 'u' || c == 'f') &&
    401            prev_hl == HL_NUMBER)) {
    402         row->hl[i] = HL_NUMBER;
    403         i++;
    404         prev_sep = 0;
    405         continue;
    406       }
    407     }
    408 
    409     if (prev_sep) {
    410       int j;
    411       for (j = 0; keywords[j]; j++) {
    412         int klen = strlen(keywords[j]);
    413 
    414         enum editorHighlight hl = HL_KEYWORD1;
    415         switch (keywords[j][--klen]) {
    416         case '|': hl = HL_KEYWORD2; break;
    417         case '#': hl = HL_KEYWORD3; break;
    418         case '*': hl = HL_KEYWORD4; break;
    419         default: klen++;
    420         }
    421 
    422         if (!strncmp(&row->render[i], keywords[j], klen) &&
    423             is_separator(row->render[i + klen])) {
    424           memset(&row->hl[i], hl, klen);
    425           i += klen;
    426           prev_sep = 0;
    427           goto next_iteration;
    428         }
    429       }
    430     }
    431 
    432     {
    433       int j;
    434       for (j = 0; operators[j]; j++) {
    435         int olen = strlen(operators[j]);
    436         if (!strncmp(&row->render[i], operators[j], olen)) {
    437           memset(&row->hl[i], HL_OPERATORS, olen);
    438           i += olen;
    439           prev_sep = 1;
    440           goto next_iteration;
    441         }
    442       }
    443     }
    444 
    445     switch (c) {
    446     case '(':
    447     case ')':
    448       if (E.syntax->flags & HL_HIGHLIGHT_BRACKETS) row->hl[i] = HL_BRACKETS;
    449       break;
    450     case '[':
    451     case ']':
    452       if (E.syntax->flags & HL_HIGHLIGHT_SBRACKETS) row->hl[i] = HL_SBRACKETS;
    453       break;
    454     case '{':
    455     case '}':
    456       if (E.syntax->flags & HL_HIGHLIGHT_CBRACKETS) row->hl[i] = HL_CBRACKETS;
    457       break;
    458     case ';':
    459       if (E.syntax->flags & HL_HIGHLIGHT_SEMICOLON) row->hl[i] = HL_SEMICOLON;
    460       break;
    461     case ',':
    462       if (E.syntax->flags & HL_HIGHLIGHT_COMMA) row->hl[i] = HL_COMMA;
    463       break;
    464     }
    465 
    466     // if ((prev_sep = is_separator(c))) row->hl[i] = HL_KEYWORD4;
    467     prev_sep = is_separator(c);
    468     i++;
    469   next_iteration:;
    470   }
    471 
    472   int changed = (row->hl_open_comment != in_comment);
    473   row->hl_open_comment = in_comment;
    474   if (changed && row->idx + 1 < E.numrows)
    475     editorUpdateSyntax(&E.row[row->idx + 1]);
    476 }
    477 
    478 int editorSyntaxToColor(int hl) {
    479   switch (hl) {
    480   case HL_COMMENT:
    481   case HL_MLCOMMENT: return 36;
    482   case HL_STRING: return 35;
    483   case HL_KEYWORD1: return 33;
    484   case HL_KEYWORD2: return 31;
    485   case HL_KEYWORD3: return 91;
    486   case HL_KEYWORD4: return 96;
    487   case HL_OPERATORS: return 94;
    488   case HL_NUMBER: return 32;
    489   case HL_MATCH: return 34;
    490   case HL_ABRAKETS: return 93;
    491 
    492   case HL_BRACKETS: return 91;
    493   case HL_SBRACKETS: return 37;
    494   case HL_CBRACKETS: return 93;
    495   case HL_SEMICOLON: return 93;
    496   case HL_COMMA: return 91;
    497 
    498   case HL_LINEN_CRNT: return 91;
    499   case HL_LINEN: return 37;
    500 
    501   default: return 37;
    502   }
    503 }
    504 
    505 void editorSelectSyntaxHighlight() {
    506   E.syntax = NULL;
    507   if (E.filename == NULL) return;
    508 
    509   char *ext = strrchr(E.filename, '.');
    510 
    511   for (unsigned int j = 0; j < HLDB_ENTRIES; j++) {
    512     struct editorSyntax *s = &HLDB[j];
    513     unsigned int i = 0;
    514     while (s->filematch[i]) {
    515       int is_ext = (s->filematch[i][0] == '.');
    516       if ((is_ext && ext && !strcmp(ext, s->filematch[i])) ||
    517           (!is_ext && strstr(E.filename, s->filematch[i]))) {
    518         E.syntax = s;
    519 
    520         for (int filerow = 0; filerow < E.numrows; filerow++) {
    521           editorUpdateSyntax(&E.row[filerow]);
    522         }
    523 
    524         return;
    525       }
    526       i++;
    527     }
    528   }
    529 }
    530 
    531 /*** row operations ***/
    532 
    533 int editorRowIndent(erow *row) {
    534   char *p = row->chars;
    535   while (isspace(*p++))
    536     ;
    537   return p - row->chars - 1;
    538 
    539   // on blank line return 0
    540   // return *p ? p - row->chars - 1 : 0;
    541 }
    542 
    543 int editorRowCxToRx(erow *row, int cx) {
    544   int rx = 0;
    545   for (int j = 0; j < cx; j++) {
    546     if (row->chars[j] == '\t') rx += (KILO_TAB_STOP - 1) - (rx % KILO_TAB_STOP);
    547     rx++;
    548   }
    549   return rx;
    550 }
    551 
    552 int editorRowRxtoCx(erow *row, int rx) {
    553   int cur_rx = 0, cx;
    554   for (cx = 0; cx < row->size; cx++) {
    555     if (row->chars[cx] == '\t')
    556       cur_rx += (KILO_TAB_STOP - 1) - (cur_rx % KILO_TAB_STOP);
    557     cur_rx++;
    558     if (cur_rx > rx) return cx;
    559   }
    560   return cx;
    561 }
    562 
    563 void editorUpdateRow(erow *row) {
    564   int tabs = 0;
    565   for (int j = 0; j < row->size; j++)
    566     if (row->chars[j] == '\t') tabs++;
    567 
    568   free(row->render);
    569   row->render = malloc(row->size + tabs * (KILO_TAB_STOP - 1) + 1);
    570 
    571   int idx = 0;
    572   for (int j = 0; j < row->size; j++) {
    573     if (row->chars[j] == '\t') {
    574       row->render[idx++] = ' ';
    575       while (idx % KILO_TAB_STOP != 0) row->render[idx++] = ' ';
    576     } else {
    577       row->render[idx++] = row->chars[j];
    578     }
    579   }
    580   row->render[idx] = '\0';
    581   row->rsize = idx;
    582 
    583   editorUpdateSyntax(row);
    584 }
    585 
    586 void editorInsertRow(int at, char *s, size_t len, int indent) {
    587   if (at < 0 || at > E.numrows) return;
    588 
    589   E.row = realloc(E.row, sizeof(erow) * (E.numrows + 1));
    590   memmove(&E.row[at + 1], &E.row[at], sizeof(erow) * (E.numrows - at));
    591   for (int j = at + 1; j <= E.numrows; j++) E.row[j].idx++;
    592 
    593   E.row[at].idx = at;
    594 
    595   int tlen = indent + len;
    596   E.row[at].size = tlen;
    597   E.row[at].chars = malloc(tlen + 1);
    598   memcpy(E.row[at].chars, E.row[at - 1].chars, indent);
    599   memcpy(E.row[at].chars + indent, s, len);
    600   E.row[at].chars[tlen] = '\0';
    601 
    602   E.row[at].rsize = 0;
    603   E.row[at].render = NULL;
    604   E.row[at].hl = NULL;
    605   E.row[at].hl_open_comment = 0;
    606 
    607   editorUpdateRow(&E.row[at]);
    608 
    609   E.numrows++;
    610   E.row_digits = numberOfDigits(E.numrows);
    611   E.dirty++;
    612 }
    613 
    614 void editorFreeRow(erow *row) {
    615   free(row->render);
    616   free(row->chars);
    617   free(row->hl);
    618 }
    619 
    620 void editorDelRow(int at) {
    621   if (at < 0 || at >= E.numrows) return;
    622   editorFreeRow(&E.row[at]);
    623   memmove(&E.row[at], &E.row[at + 1], sizeof(erow) * (E.numrows - at - 1));
    624   for (int j = at; j < E.numrows - 1; j++) E.row[j].idx--;
    625 
    626   E.numrows--;
    627   E.row_digits = numberOfDigits(E.numrows);
    628   E.dirty++;
    629 }
    630 
    631 void editorRowInsertChar(erow *row, int at, int c) {
    632   if (at < 0 || at > row->size) at = row->size;
    633   row->chars = realloc(row->chars, row->size + 2);
    634   memmove(&row->chars[at + 1], &row->chars[at], row->size - at + 1);
    635   row->size++;
    636   row->chars[at] = c;
    637   editorUpdateRow(row);
    638   E.dirty++;
    639 }
    640 
    641 void editorRowAppendString(erow *row, char *s, size_t len) {
    642   row->chars = realloc(row->chars, row->size + len + 1);
    643   memcpy(&row->chars[row->size], s, len);
    644   row->size += len;
    645   row->chars[row->size] = '\0';
    646   editorUpdateRow(row);
    647   E.dirty++;
    648 }
    649 
    650 void editorRowDelChar(erow *row, int at) {
    651   if (at < 0 || at >= row->size) return;
    652   memmove(&row->chars[at], &row->chars[at + 1], row->size - at);
    653   row->size--;
    654   editorUpdateRow(row);
    655   E.dirty++;
    656 }
    657 
    658 /*** editor operations ***/
    659 
    660 void editorInsertChar(int c) {
    661   if (E.cy == E.numrows) { editorInsertRow(E.numrows, "", 0, 0); }
    662 
    663   if (KILO_TAP_SOFT && c == '\t') {
    664     for (int i = 0; i < KILO_TAB_STOP; i++)
    665       editorRowInsertChar(&E.row[E.cy], E.cx++, ' ');
    666     return;
    667   }
    668 
    669   editorRowInsertChar(&E.row[E.cy], E.cx++, c);
    670 }
    671 
    672 void editorInsertNewline() {
    673   if (E.cx == 0) {
    674     editorInsertRow(E.cy, "", 0, 0);
    675   } else {
    676     int indent = editorRowIndent(&E.row[E.cy]);
    677     erow *row = &E.row[E.cy];
    678     editorInsertRow(E.cy + 1, &row->chars[E.cx], row->size - E.cx, indent);
    679     row = &E.row[E.cy]; // reassign in case realloc changes it
    680     row->size = E.cx;
    681     row->chars[row->size] = '\0';
    682     editorUpdateRow(row);
    683     E.cx = indent;
    684   }
    685   E.cy++;
    686 }
    687 
    688 void editorDelChar() {
    689   if (E.cy == E.numrows) return;
    690   if (E.cx == 0 && E.cy == 0) return;
    691 
    692   erow *row = &E.row[E.cy];
    693   if (E.cx > 0) {
    694     editorRowDelChar(row, E.cx - 1);
    695     E.cx--;
    696   } else {
    697     E.cx = E.row[E.cy - 1].size;
    698     editorRowAppendString(&E.row[E.cy - 1], row->chars, row->size);
    699     editorDelRow(E.cy);
    700     E.cy--;
    701   }
    702 }
    703 
    704 /*** file i/o ***/
    705 
    706 char *editorRowsToString(int *buflen) {
    707   int totlen = 0;
    708   for (int j = 0; j < E.numrows; j++) totlen += E.row[j].size + 1;
    709   *buflen = totlen;
    710 
    711   char *buf = malloc(totlen), *p = buf;
    712   for (int j = 0; j < E.numrows; j++) {
    713     memcpy(p, E.row[j].chars, E.row[j].size);
    714     p += E.row[j].size;
    715     *p = '\n';
    716     p++;
    717   }
    718   return buf;
    719 }
    720 
    721 void editorOpen(char *filename) {
    722   free(E.filename);
    723   E.filename = strdup(filename);
    724 
    725   editorSelectSyntaxHighlight();
    726 
    727   FILE *fp = fopen(filename, "r");
    728   if (!fp) die("fopen");
    729 
    730   char *line = NULL;
    731   size_t linecap = 0;
    732   ssize_t linelen;
    733   while ((linelen = getline(&line, &linecap, fp)) != -1) {
    734     while (linelen > 0 &&
    735            (line[linelen - 1] == '\n' || line[linelen - 1] == '\r'))
    736       linelen--;
    737 
    738     editorInsertRow(E.numrows, line, linelen, 0);
    739   }
    740   free(line);
    741   fclose(fp);
    742   E.dirty = 0;
    743 }
    744 
    745 void editorSave() {
    746   if (E.filename == NULL) {
    747     E.filename = editorPrompt("Save as: %s", NULL);
    748     if (E.filename == NULL) {
    749       editorSetStatusMessage("Save aborted");
    750       return;
    751     }
    752     editorSelectSyntaxHighlight();
    753   }
    754 
    755   int len;
    756   char *buf = editorRowsToString(&len);
    757 
    758   int fd = open(E.filename, O_RDWR | O_CREAT, 0644);
    759   if (fd != -1) {
    760     if (ftruncate(fd, len) != -1) {
    761       if (write(fd, buf, len) == len) {
    762         close(fd);
    763         free(buf);
    764         E.dirty = 0;
    765         editorSetStatusMessage("%d bytes written to disk", len);
    766         return;
    767       }
    768     }
    769     close(fd);
    770   }
    771   free(buf);
    772   editorSetStatusMessage("Can't save! I/O error: %s", strerror(errno));
    773 }
    774 
    775 /*** find ***/
    776 
    777 void editorFindCallback(char *query, int key) {
    778   static int last_match = -1;
    779   static int direction = 1;
    780   static int saved_hl_line;
    781   static char *saved_hl = NULL;
    782 
    783   if (saved_hl) {
    784     memcpy(E.row[saved_hl_line].hl, saved_hl, E.row[saved_hl_line].rsize);
    785     free(saved_hl);
    786     saved_hl = NULL;
    787   }
    788 
    789   if (key == '\r' || key == '\x1b') {
    790     last_match = -1;
    791     direction = 1;
    792     return;
    793   } else if (key == ARROW_RIGHT || key == ARROW_DOWN) {
    794     direction = 1;
    795   } else if (key == ARROW_LEFT || key == ARROW_UP) {
    796     direction = -1;
    797   } else {
    798     last_match = -1;
    799     direction = 1;
    800   }
    801 
    802   if (last_match == -1) direction = 1;
    803 
    804   int current = last_match;
    805   for (int i = 0; i < E.numrows; i++) {
    806     current += direction;
    807     if (current == -1)
    808       current = E.numrows - 1;
    809     else if (current == E.numrows)
    810       current = 0;
    811 
    812     erow *row = &E.row[current];
    813     char *match = strstr(row->render, query);
    814     if (match) {
    815       last_match = current;
    816       E.cy = current;
    817       E.cx = editorRowRxtoCx(row, match - row->render);
    818       E.rowoff = E.numrows;
    819 
    820       saved_hl_line = current;
    821       saved_hl = malloc(row->rsize);
    822       memcpy(saved_hl, row->hl, row->rsize);
    823       memset(&row->hl[match - row->render], HL_MATCH, strlen(query));
    824       break;
    825     }
    826   }
    827 }
    828 
    829 void editorFind() {
    830   int saved_cx = E.cx;
    831   int saved_cy = E.cy;
    832   int saved_coloff = E.coloff;
    833   int saved_rowoff = E.rowoff;
    834 
    835   char *query =
    836       editorPrompt("Search: %s (Use ESC/Arrows/Enter)", editorFindCallback);
    837   if (query) {
    838     free(query);
    839   } else {
    840     E.cx = saved_cx;
    841     E.cy = saved_cy;
    842     E.coloff = saved_coloff;
    843     E.rowoff = saved_rowoff;
    844   }
    845 }
    846 
    847 /*** append buffer ***/
    848 
    849 struct abuf {
    850   char *b;
    851   int len;
    852 };
    853 
    854 #define ABUF_INIT                                                              \
    855   { NULL, 0 }
    856 
    857 void abAppend(struct abuf *ab, const char *s, int len) {
    858   char *new = realloc(ab->b, ab->len + len);
    859 
    860   if (new == NULL) return;
    861   memcpy(&new[ab->len], s, len);
    862   ab->b = new;
    863   ab->len += len;
    864 }
    865 
    866 void abFree(struct abuf *ab) { free(ab->b); }
    867 
    868 /*** input ***/
    869 
    870 char *editorPrompt(char *prompt, void (*callback)(char *, int)) {
    871   size_t bufsize = 128, buflen;
    872   char *buf = malloc(bufsize);
    873   buf[buflen = 0] = '\0';
    874 
    875   while (1) {
    876     editorSetStatusMessage(prompt, buf);
    877     editorRefreshScreen();
    878 
    879     int c = editorReadKey();
    880 
    881     if (c == DEL_KEY || c == CTRL_KEY('h') || c == BACKSPACE) {
    882       if (buflen != 0) buf[--buflen] = '\0';
    883     } else if (c == '\x1b') {
    884       editorSetStatusMessage("");
    885       if (callback) callback(buf, c);
    886       free(buf);
    887       return NULL;
    888     } else if (c == '\r') {
    889       if (buflen != 0) {
    890         editorSetStatusMessage("");
    891         if (callback) callback(buf, c);
    892         return buf;
    893       }
    894     } else if (!iscntrl(c) && c < 128) {
    895       if (buflen == bufsize - 1) {
    896         bufsize *= 2;
    897         buf = realloc(buf, bufsize);
    898       }
    899       buf[buflen++] = c;
    900       buf[buflen] = '\0';
    901     }
    902 
    903     if (callback) callback(buf, c);
    904   }
    905 }
    906 
    907 void editorMoveCursor(int key) {
    908   erow *row = (E.cy >= E.numrows) ? NULL : &E.row[E.cy];
    909 
    910   switch (key) {
    911   case ARROW_LEFT:
    912     if (E.cx != 0) {
    913       E.cx--;
    914     } else if (E.cy > 0) {
    915       E.cy--;
    916       E.cx = E.row[E.cy].size;
    917     }
    918     break;
    919   case ARROW_RIGHT:
    920     if (row && E.cx < row->size) {
    921       E.cx++;
    922     } else if (row && E.cx == row->size) {
    923       E.cy++;
    924       E.cx = 0;
    925     }
    926     break;
    927   case ARROW_UP:
    928     if (E.cy != 0) { E.cy--; }
    929     break;
    930   case ARROW_DOWN:
    931     if (E.cy < E.numrows) { E.cy++; }
    932     break;
    933   }
    934 
    935   row = (E.cy >= E.numrows) ? NULL : &E.row[E.cy];
    936   int rowlen = row ? row->size : 0;
    937   CLAMP_RIGHT(E.cx, rowlen);
    938 }
    939 
    940 void editorProcessKeypress() {
    941   static int quit_times = KILO_QUIT_TIMES;
    942 
    943   int c = editorReadKey();
    944 
    945   switch (c) {
    946   case '\r': editorInsertNewline(); break;
    947 
    948   case CTRL_KEY('q'):
    949     if (E.dirty && quit_times > 0) {
    950       editorSetStatusMessage("WARNING!!! File has unsaved changes. "
    951                              "Press Ctrl-Q %d more times to quit.",
    952                              quit_times);
    953       quit_times--;
    954       return;
    955     }
    956     write(STDOUT_FILENO, "\x1b[2J", 4);
    957     write(STDOUT_FILENO, "\x1b[H", 3);
    958     exit(0);
    959     break;
    960 
    961   case CTRL_KEY('s'): editorSave(); break;
    962 
    963   case HOME_KEY: E.cx = 0; break;
    964 
    965   case END_KEY:
    966     if (E.cy < E.numrows) E.cx = E.row[E.cy].size;
    967     break;
    968 
    969   case CTRL_KEY('f'): editorFind(); break;
    970 
    971   case BACKSPACE:
    972   case CTRL_KEY('h'):
    973   case DEL_KEY:
    974     if (c == DEL_KEY) editorMoveCursor(ARROW_RIGHT);
    975     editorDelChar();
    976     break;
    977 
    978   case PAGE_UP:
    979   case PAGE_DOWN: {
    980     if (c == PAGE_UP) {
    981       E.cy = E.rowoff;
    982     } else if (c == PAGE_DOWN) {
    983       E.cy = E.rowoff + E.screenrows - 1;
    984       if (E.cy > E.numrows) E.cy = E.numrows;
    985     }
    986 
    987     int times = E.screenrows;
    988     while (times--) editorMoveCursor(c == PAGE_UP ? ARROW_UP : ARROW_DOWN);
    989     break;
    990   }
    991 
    992   case ARROW_UP:
    993   case ARROW_DOWN:
    994   case ARROW_LEFT:
    995   case ARROW_RIGHT: editorMoveCursor(c); break;
    996 
    997   case CTRL_KEY('l'):
    998   case '\x1b': break;
    999 
   1000   default: editorInsertChar(c); break;
   1001   }
   1002 
   1003   quit_times = KILO_QUIT_TIMES;
   1004 }
   1005 
   1006 /*** output ***/
   1007 
   1008 void editorScroll() {
   1009   E.rx = 0;
   1010   if (E.cy < E.numrows) { E.rx = editorRowCxToRx(&E.row[E.cy], E.cx); }
   1011   CLAMP_RIGHT(E.rowoff, E.cy);
   1012   if (E.cy >= E.rowoff + E.screenrows) { E.rowoff = E.cy - E.screenrows + 1; }
   1013   CLAMP_RIGHT(E.coloff, E.rx);
   1014   if (E.rx >= E.coloff + E.screencols) { E.coloff = E.rx - E.screencols + 1; }
   1015 }
   1016 
   1017 void editorDrawRows(struct abuf *ab) {
   1018   int margin_size = E.row_digits + KILO_MARGIN_LEFT;
   1019   char margin_buf[margin_size + 1];
   1020   char color_buf[16];
   1021 
   1022   for (int y = 0; y < E.screenrows; y++) {
   1023     int filerow = y + E.rowoff;
   1024     if (filerow >= E.numrows) {
   1025       if (E.numrows == 0 && y == E.screenrows / 3) {
   1026         char welcome[80];
   1027         int welcomelen = snprintf(welcome, sizeof(welcome),
   1028                                   "Kilo editor -- version %s", KILO_VERSION);
   1029         if (welcomelen > E.screencols) welcomelen = E.screencols;
   1030         int padding = (E.screencols - welcomelen) / 2;
   1031         if (padding) {
   1032           abAppend(ab, "~", 1);
   1033           padding--;
   1034         }
   1035         while (padding--) abAppend(ab, " ", 1);
   1036         abAppend(ab, welcome, welcomelen);
   1037       } else {
   1038         abAppend(ab, "~", 1);
   1039       }
   1040     } else {
   1041       int clen, padding;
   1042 
   1043       if (E.cy - E.rowoff == y) {
   1044         padding = sprintf(margin_buf, "%d%*s", filerow + 1,
   1045                           margin_size - numberOfDigits(y + 1) + 1, "");
   1046         clen = snprintf(color_buf, sizeof(color_buf), "\x1b[%dm",
   1047                         editorSyntaxToColor(HL_LINEN_CRNT));
   1048       } else {
   1049         int row = KILO_NUM_RELATIVE ? abs(y - (E.cy - E.rowoff)) : filerow + 1;
   1050         padding = sprintf(margin_buf, "%*d ", margin_size, row);
   1051         clen = snprintf(color_buf, sizeof(color_buf), "\x1b[%dm",
   1052                         editorSyntaxToColor(HL_LINEN));
   1053       }
   1054 
   1055       abAppend(ab, color_buf, clen);
   1056       abAppend(ab, margin_buf, padding);
   1057       abAppend(ab, "\x1b[39m", 5);
   1058 
   1059       int len = E.row[filerow].rsize - E.coloff;
   1060       CLAMP(len, 0, E.screencols - padding);
   1061       char *c = &E.row[filerow].render[E.coloff];
   1062       unsigned char *hl = &E.row[filerow].hl[E.coloff];
   1063       int current_color = -1;
   1064       for (int j = 0; j < len; j++) {
   1065         if (iscntrl(c[j])) {
   1066           char sym = (c[j] <= 26) ? '@' + c[j] : '?';
   1067           abAppend(ab, "\x1b[7m", 4);
   1068           abAppend(ab, &sym, 1);
   1069           abAppend(ab, "\x1b[m", 3);
   1070 
   1071           // restore current color as <esc>[m turns off all text formatting,
   1072           // including colors
   1073           if (current_color != -1) {
   1074             int clen = snprintf(color_buf, sizeof(color_buf), "\x1b[%dm",
   1075                                 current_color);
   1076             abAppend(ab, color_buf, clen);
   1077           }
   1078         } else if (hl[j] == HL_NORMAL) {
   1079           if (current_color != -1) {
   1080             abAppend(ab, "\x1b[39m", 5);
   1081             current_color = -1;
   1082           }
   1083           abAppend(ab, &c[j], 1);
   1084         } else {
   1085           int color = editorSyntaxToColor(hl[j]);
   1086           if (color != current_color) {
   1087             current_color = color;
   1088             int clen =
   1089                 snprintf(color_buf, sizeof(color_buf), "\x1b[%dm", color);
   1090             abAppend(ab, color_buf, clen);
   1091           }
   1092           abAppend(ab, &c[j], 1);
   1093         }
   1094       }
   1095       abAppend(ab, "\x1b[39m", 5);
   1096     }
   1097     abAppend(ab, "\x1b[K", 3); // clear current row
   1098     abAppend(ab, "\r\n", 2);
   1099   }
   1100 }
   1101 
   1102 void editorDrawStatusBar(struct abuf *ab) {
   1103   abAppend(ab, "\x1b[7m", 4);
   1104   char status[80], rstatus[80];
   1105   int len = snprintf(status, sizeof(status), "%.20s - %d lines %s",
   1106                      E.filename ? E.filename : "[No Name]", E.numrows,
   1107                      E.dirty ? "(modified)" : "");
   1108   int rlen =
   1109       snprintf(rstatus, sizeof(rstatus), "%s | %d/%d",
   1110                E.syntax ? E.syntax->filetype : "no ft", E.cy + 1, E.numrows);
   1111   CLAMP_RIGHT(len, E.screencols);
   1112   abAppend(ab, status, len);
   1113   while (len < E.screencols) {
   1114     if (E.screencols - len == rlen) {
   1115       abAppend(ab, rstatus, rlen);
   1116       break;
   1117     } else {
   1118       abAppend(ab, " ", 1);
   1119       len++;
   1120     }
   1121   }
   1122   abAppend(ab, "\x1b[m", 3);
   1123   abAppend(ab, "\r\n", 2);
   1124 }
   1125 
   1126 void editorDrawMessageBar(struct abuf *ab) {
   1127   abAppend(ab, "\x1b[K", 3);
   1128   int msglen = strlen(E.statusmsg);
   1129   CLAMP_RIGHT(msglen, E.screencols);
   1130   if (msglen && time(NULL) - E.statusmsg_time < 5)
   1131     abAppend(ab, E.statusmsg, msglen);
   1132 }
   1133 
   1134 void editorRefreshScreen() {
   1135   editorScroll();
   1136 
   1137   struct abuf ab = ABUF_INIT;
   1138 
   1139   abAppend(&ab, "\x1b[?25l", 6); // hide cursor
   1140   abAppend(&ab, "\x1b[H", 3);
   1141 
   1142   editorDrawRows(&ab);
   1143   editorDrawStatusBar(&ab);
   1144   editorDrawMessageBar(&ab);
   1145 
   1146   char buf[32];
   1147   snprintf(buf, sizeof(buf), "\x1b[%d;%dH", (E.cy - E.rowoff) + 1,
   1148            (E.rx - E.coloff) + E.row_digits + 2 + KILO_MARGIN_LEFT);
   1149   abAppend(&ab, buf, strlen(buf));
   1150 
   1151   abAppend(&ab, "\x1b[?25h", 6); // show cursor
   1152 
   1153   write(STDOUT_FILENO, ab.b, ab.len);
   1154   abFree(&ab);
   1155 }
   1156 
   1157 void editorSetStatusMessage(const char *fmt, ...) {
   1158   va_list ap;
   1159   va_start(ap, fmt);
   1160   vsnprintf(E.statusmsg, sizeof(E.statusmsg), fmt, ap);
   1161   va_end(ap);
   1162   E.statusmsg_time = time(NULL);
   1163 }
   1164 
   1165 /*** init ***/
   1166 
   1167 void initEditor() {
   1168   E.cx = 0;
   1169   E.cy = 0;
   1170   E.rx = 0;
   1171   E.rowoff = 0;
   1172   E.coloff = 0;
   1173   E.numrows = 0;
   1174   E.row_digits = 1;
   1175   E.row = NULL;
   1176   E.dirty = 0;
   1177   E.filename = NULL;
   1178   E.statusmsg[0] = '\0';
   1179   E.statusmsg_time = 0;
   1180   E.syntax = NULL;
   1181 
   1182   if (getWindowSize(&E.screenrows, &E.screencols) == -1) die("getWindowSize");
   1183   E.screenrows -= 2;
   1184 }
   1185 
   1186 int main(int argc, char *argv[]) {
   1187   enableRawMode();
   1188   initEditor();
   1189   if (argc >= 2) { editorOpen(argv[1]); }
   1190 
   1191   editorSetStatusMessage("HELP: Ctrl-S = save | Ctrl-Q = quit | Ctrl-F = find");
   1192 
   1193   while (1) {
   1194     editorRefreshScreen();
   1195     editorProcessKeypress();
   1196   }
   1197   return 0;
   1198 }