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 }