├── LICENSE ├── Makefile ├── README.md └── kilo.c /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016, Salvatore Sanfilippo 2 | 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright notice, 9 | this list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 19 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 22 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | kilo: kilo.c 2 | $(CC) kilo.c -o kilo -Wall -Wextra -pedantic -std=c99 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This repo contains the entire source code for each step of 2 | [kilo-tutorial](https://github.com/snaptoken/kilo-tutorial). Each step is 3 | represented by a commit. Each step name (found in the upper-right corner of 4 | each step diff in the [tutorial](http://viewsourcecode.org/snaptoken/kilo)) is 5 | a ref to that step's commit. 6 | 7 | If you want to compare your version of `kilo.c` with the version in this repo 8 | for a particular step, say `keypresses`, you could do it like this: 9 | 10 | $ git clone https://github.com/snaptoken/kilo-src 11 | $ cd kilo-src 12 | $ git checkout keypresses 13 | $ git diff --no-index -b ../path/to/your/kilo.c kilo.c 14 | 15 | `--no-index` lets you use `git diff` as an ordinary diff tool, and `-b` ignores 16 | differences in whitespace, which is important if you use a different indent 17 | style than the one in the tutorial. 18 | 19 | -------------------------------------------------------------------------------- /kilo.c: -------------------------------------------------------------------------------- 1 | /*** includes ***/ 2 | 3 | #define _DEFAULT_SOURCE 4 | #define _BSD_SOURCE 5 | #define _GNU_SOURCE 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | /*** defines ***/ 21 | 22 | #define KILO_VERSION "0.0.1" 23 | #define KILO_TAB_STOP 8 24 | #define KILO_QUIT_TIMES 3 25 | 26 | #define CTRL_KEY(k) ((k) & 0x1f) 27 | 28 | enum editorKey { 29 | BACKSPACE = 127, 30 | ARROW_LEFT = 1000, 31 | ARROW_RIGHT, 32 | ARROW_UP, 33 | ARROW_DOWN, 34 | DEL_KEY, 35 | HOME_KEY, 36 | END_KEY, 37 | PAGE_UP, 38 | PAGE_DOWN 39 | }; 40 | 41 | enum editorHighlight { 42 | HL_NORMAL = 0, 43 | HL_COMMENT, 44 | HL_MLCOMMENT, 45 | HL_KEYWORD1, 46 | HL_KEYWORD2, 47 | HL_STRING, 48 | HL_NUMBER, 49 | HL_MATCH 50 | }; 51 | 52 | #define HL_HIGHLIGHT_NUMBERS (1<<0) 53 | #define HL_HIGHLIGHT_STRINGS (1<<1) 54 | 55 | /*** data ***/ 56 | 57 | struct editorSyntax { 58 | char *filetype; 59 | char **filematch; 60 | char **keywords; 61 | char *singleline_comment_start; 62 | char *multiline_comment_start; 63 | char *multiline_comment_end; 64 | int flags; 65 | }; 66 | 67 | typedef struct erow { 68 | int idx; 69 | int size; 70 | int rsize; 71 | char *chars; 72 | char *render; 73 | unsigned char *hl; 74 | int hl_open_comment; 75 | } erow; 76 | 77 | struct editorConfig { 78 | int cx, cy; 79 | int rx; 80 | int rowoff; 81 | int coloff; 82 | int screenrows; 83 | int screencols; 84 | int numrows; 85 | erow *row; 86 | int dirty; 87 | char *filename; 88 | char statusmsg[80]; 89 | time_t statusmsg_time; 90 | struct editorSyntax *syntax; 91 | struct termios orig_termios; 92 | }; 93 | 94 | struct editorConfig E; 95 | 96 | /*** filetypes ***/ 97 | 98 | char *C_HL_extensions[] = { ".c", ".h", ".cpp", NULL }; 99 | char *C_HL_keywords[] = { 100 | "switch", "if", "while", "for", "break", "continue", "return", "else", 101 | "struct", "union", "typedef", "static", "enum", "class", "case", 102 | 103 | "int|", "long|", "double|", "float|", "char|", "unsigned|", "signed|", 104 | "void|", NULL 105 | }; 106 | 107 | struct editorSyntax HLDB[] = { 108 | { 109 | "c", 110 | C_HL_extensions, 111 | C_HL_keywords, 112 | "//", "/*", "*/", 113 | HL_HIGHLIGHT_NUMBERS | HL_HIGHLIGHT_STRINGS 114 | }, 115 | }; 116 | 117 | #define HLDB_ENTRIES (sizeof(HLDB) / sizeof(HLDB[0])) 118 | 119 | /*** prototypes ***/ 120 | 121 | void editorSetStatusMessage(const char *fmt, ...); 122 | void editorRefreshScreen(); 123 | char *editorPrompt(char *prompt, void (*callback)(char *, int)); 124 | 125 | /*** terminal ***/ 126 | 127 | void die(const char *s) { 128 | write(STDOUT_FILENO, "\x1b[2J", 4); 129 | write(STDOUT_FILENO, "\x1b[H", 3); 130 | 131 | perror(s); 132 | exit(1); 133 | } 134 | 135 | void disableRawMode() { 136 | if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &E.orig_termios) == -1) 137 | die("tcsetattr"); 138 | } 139 | 140 | void enableRawMode() { 141 | if (tcgetattr(STDIN_FILENO, &E.orig_termios) == -1) die("tcgetattr"); 142 | atexit(disableRawMode); 143 | 144 | struct termios raw = E.orig_termios; 145 | raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON); 146 | raw.c_oflag &= ~(OPOST); 147 | raw.c_cflag |= (CS8); 148 | raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG); 149 | raw.c_cc[VMIN] = 0; 150 | raw.c_cc[VTIME] = 1; 151 | 152 | if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw) == -1) die("tcsetattr"); 153 | } 154 | 155 | int editorReadKey() { 156 | int nread; 157 | char c; 158 | while ((nread = read(STDIN_FILENO, &c, 1)) != 1) { 159 | if (nread == -1 && errno != EAGAIN) die("read"); 160 | } 161 | 162 | if (c == '\x1b') { 163 | char seq[3]; 164 | 165 | if (read(STDIN_FILENO, &seq[0], 1) != 1) return '\x1b'; 166 | if (read(STDIN_FILENO, &seq[1], 1) != 1) return '\x1b'; 167 | 168 | if (seq[0] == '[') { 169 | if (seq[1] >= '0' && seq[1] <= '9') { 170 | if (read(STDIN_FILENO, &seq[2], 1) != 1) return '\x1b'; 171 | if (seq[2] == '~') { 172 | switch (seq[1]) { 173 | case '1': return HOME_KEY; 174 | case '3': return DEL_KEY; 175 | case '4': return END_KEY; 176 | case '5': return PAGE_UP; 177 | case '6': return PAGE_DOWN; 178 | case '7': return HOME_KEY; 179 | case '8': return END_KEY; 180 | } 181 | } 182 | } else { 183 | switch (seq[1]) { 184 | case 'A': return ARROW_UP; 185 | case 'B': return ARROW_DOWN; 186 | case 'C': return ARROW_RIGHT; 187 | case 'D': return ARROW_LEFT; 188 | case 'H': return HOME_KEY; 189 | case 'F': return END_KEY; 190 | } 191 | } 192 | } else if (seq[0] == 'O') { 193 | switch (seq[1]) { 194 | case 'H': return HOME_KEY; 195 | case 'F': return END_KEY; 196 | } 197 | } 198 | 199 | return '\x1b'; 200 | } else { 201 | return c; 202 | } 203 | } 204 | 205 | int getCursorPosition(int *rows, int *cols) { 206 | char buf[32]; 207 | unsigned int i = 0; 208 | 209 | if (write(STDOUT_FILENO, "\x1b[6n", 4) != 4) return -1; 210 | 211 | while (i < sizeof(buf) - 1) { 212 | if (read(STDIN_FILENO, &buf[i], 1) != 1) break; 213 | if (buf[i] == 'R') break; 214 | i++; 215 | } 216 | buf[i] = '\0'; 217 | 218 | if (buf[0] != '\x1b' || buf[1] != '[') return -1; 219 | if (sscanf(&buf[2], "%d;%d", rows, cols) != 2) return -1; 220 | 221 | return 0; 222 | } 223 | 224 | int getWindowSize(int *rows, int *cols) { 225 | struct winsize ws; 226 | 227 | if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == -1 || ws.ws_col == 0) { 228 | if (write(STDOUT_FILENO, "\x1b[999C\x1b[999B", 12) != 12) return -1; 229 | return getCursorPosition(rows, cols); 230 | } else { 231 | *cols = ws.ws_col; 232 | *rows = ws.ws_row; 233 | return 0; 234 | } 235 | } 236 | 237 | /*** syntax highlighting ***/ 238 | 239 | int is_separator(int c) { 240 | return isspace(c) || c == '\0' || strchr(",.()+-/*=~%<>[];", c) != NULL; 241 | } 242 | 243 | void editorUpdateSyntax(erow *row) { 244 | row->hl = realloc(row->hl, row->rsize); 245 | memset(row->hl, HL_NORMAL, row->rsize); 246 | 247 | if (E.syntax == NULL) return; 248 | 249 | char **keywords = E.syntax->keywords; 250 | 251 | char *scs = E.syntax->singleline_comment_start; 252 | char *mcs = E.syntax->multiline_comment_start; 253 | char *mce = E.syntax->multiline_comment_end; 254 | 255 | int scs_len = scs ? strlen(scs) : 0; 256 | int mcs_len = mcs ? strlen(mcs) : 0; 257 | int mce_len = mce ? strlen(mce) : 0; 258 | 259 | int prev_sep = 1; 260 | int in_string = 0; 261 | int in_comment = (row->idx > 0 && E.row[row->idx - 1].hl_open_comment); 262 | 263 | int i = 0; 264 | while (i < row->rsize) { 265 | char c = row->render[i]; 266 | unsigned char prev_hl = (i > 0) ? row->hl[i - 1] : HL_NORMAL; 267 | 268 | if (scs_len && !in_string && !in_comment) { 269 | if (!strncmp(&row->render[i], scs, scs_len)) { 270 | memset(&row->hl[i], HL_COMMENT, row->rsize - i); 271 | break; 272 | } 273 | } 274 | 275 | if (mcs_len && mce_len && !in_string) { 276 | if (in_comment) { 277 | row->hl[i] = HL_MLCOMMENT; 278 | if (!strncmp(&row->render[i], mce, mce_len)) { 279 | memset(&row->hl[i], HL_MLCOMMENT, mce_len); 280 | i += mce_len; 281 | in_comment = 0; 282 | prev_sep = 1; 283 | continue; 284 | } else { 285 | i++; 286 | continue; 287 | } 288 | } else if (!strncmp(&row->render[i], mcs, mcs_len)) { 289 | memset(&row->hl[i], HL_MLCOMMENT, mcs_len); 290 | i += mcs_len; 291 | in_comment = 1; 292 | continue; 293 | } 294 | } 295 | 296 | if (E.syntax->flags & HL_HIGHLIGHT_STRINGS) { 297 | if (in_string) { 298 | row->hl[i] = HL_STRING; 299 | if (c == '\\' && i + 1 < row->rsize) { 300 | row->hl[i + 1] = HL_STRING; 301 | i += 2; 302 | continue; 303 | } 304 | if (c == in_string) in_string = 0; 305 | i++; 306 | prev_sep = 1; 307 | continue; 308 | } else { 309 | if (c == '"' || c == '\'') { 310 | in_string = c; 311 | row->hl[i] = HL_STRING; 312 | i++; 313 | continue; 314 | } 315 | } 316 | } 317 | 318 | if (E.syntax->flags & HL_HIGHLIGHT_NUMBERS) { 319 | if ((isdigit(c) && (prev_sep || prev_hl == HL_NUMBER)) || 320 | (c == '.' && prev_hl == HL_NUMBER)) { 321 | row->hl[i] = HL_NUMBER; 322 | i++; 323 | prev_sep = 0; 324 | continue; 325 | } 326 | } 327 | 328 | if (prev_sep) { 329 | int j; 330 | for (j = 0; keywords[j]; j++) { 331 | int klen = strlen(keywords[j]); 332 | int kw2 = keywords[j][klen - 1] == '|'; 333 | if (kw2) klen--; 334 | 335 | if (!strncmp(&row->render[i], keywords[j], klen) && 336 | is_separator(row->render[i + klen])) { 337 | memset(&row->hl[i], kw2 ? HL_KEYWORD2 : HL_KEYWORD1, klen); 338 | i += klen; 339 | break; 340 | } 341 | } 342 | if (keywords[j] != NULL) { 343 | prev_sep = 0; 344 | continue; 345 | } 346 | } 347 | 348 | prev_sep = is_separator(c); 349 | i++; 350 | } 351 | 352 | int changed = (row->hl_open_comment != in_comment); 353 | row->hl_open_comment = in_comment; 354 | if (changed && row->idx + 1 < E.numrows) 355 | editorUpdateSyntax(&E.row[row->idx + 1]); 356 | } 357 | 358 | int editorSyntaxToColor(int hl) { 359 | switch (hl) { 360 | case HL_COMMENT: 361 | case HL_MLCOMMENT: return 36; 362 | case HL_KEYWORD1: return 33; 363 | case HL_KEYWORD2: return 32; 364 | case HL_STRING: return 35; 365 | case HL_NUMBER: return 31; 366 | case HL_MATCH: return 34; 367 | default: return 37; 368 | } 369 | } 370 | 371 | void editorSelectSyntaxHighlight() { 372 | E.syntax = NULL; 373 | if (E.filename == NULL) return; 374 | 375 | char *ext = strrchr(E.filename, '.'); 376 | 377 | for (unsigned int j = 0; j < HLDB_ENTRIES; j++) { 378 | struct editorSyntax *s = &HLDB[j]; 379 | unsigned int i = 0; 380 | while (s->filematch[i]) { 381 | int is_ext = (s->filematch[i][0] == '.'); 382 | if ((is_ext && ext && !strcmp(ext, s->filematch[i])) || 383 | (!is_ext && strstr(E.filename, s->filematch[i]))) { 384 | E.syntax = s; 385 | 386 | int filerow; 387 | for (filerow = 0; filerow < E.numrows; filerow++) { 388 | editorUpdateSyntax(&E.row[filerow]); 389 | } 390 | 391 | return; 392 | } 393 | i++; 394 | } 395 | } 396 | } 397 | 398 | /*** row operations ***/ 399 | 400 | int editorRowCxToRx(erow *row, int cx) { 401 | int rx = 0; 402 | int j; 403 | for (j = 0; j < cx; j++) { 404 | if (row->chars[j] == '\t') 405 | rx += (KILO_TAB_STOP - 1) - (rx % KILO_TAB_STOP); 406 | rx++; 407 | } 408 | return rx; 409 | } 410 | 411 | int editorRowRxToCx(erow *row, int rx) { 412 | int cur_rx = 0; 413 | int cx; 414 | for (cx = 0; cx < row->size; cx++) { 415 | if (row->chars[cx] == '\t') 416 | cur_rx += (KILO_TAB_STOP - 1) - (cur_rx % KILO_TAB_STOP); 417 | cur_rx++; 418 | 419 | if (cur_rx > rx) return cx; 420 | } 421 | return cx; 422 | } 423 | 424 | void editorUpdateRow(erow *row) { 425 | int tabs = 0; 426 | int j; 427 | for (j = 0; j < row->size; j++) 428 | if (row->chars[j] == '\t') tabs++; 429 | 430 | free(row->render); 431 | row->render = malloc(row->size + tabs*(KILO_TAB_STOP - 1) + 1); 432 | 433 | int idx = 0; 434 | for (j = 0; j < row->size; j++) { 435 | if (row->chars[j] == '\t') { 436 | row->render[idx++] = ' '; 437 | while (idx % KILO_TAB_STOP != 0) row->render[idx++] = ' '; 438 | } else { 439 | row->render[idx++] = row->chars[j]; 440 | } 441 | } 442 | row->render[idx] = '\0'; 443 | row->rsize = idx; 444 | 445 | editorUpdateSyntax(row); 446 | } 447 | 448 | void editorInsertRow(int at, char *s, size_t len) { 449 | if (at < 0 || at > E.numrows) return; 450 | 451 | E.row = realloc(E.row, sizeof(erow) * (E.numrows + 1)); 452 | memmove(&E.row[at + 1], &E.row[at], sizeof(erow) * (E.numrows - at)); 453 | for (int j = at + 1; j <= E.numrows; j++) E.row[j].idx++; 454 | 455 | E.row[at].idx = at; 456 | 457 | E.row[at].size = len; 458 | E.row[at].chars = malloc(len + 1); 459 | memcpy(E.row[at].chars, s, len); 460 | E.row[at].chars[len] = '\0'; 461 | 462 | E.row[at].rsize = 0; 463 | E.row[at].render = NULL; 464 | E.row[at].hl = NULL; 465 | E.row[at].hl_open_comment = 0; 466 | editorUpdateRow(&E.row[at]); 467 | 468 | E.numrows++; 469 | E.dirty++; 470 | } 471 | 472 | void editorFreeRow(erow *row) { 473 | free(row->render); 474 | free(row->chars); 475 | free(row->hl); 476 | } 477 | 478 | void editorDelRow(int at) { 479 | if (at < 0 || at >= E.numrows) return; 480 | editorFreeRow(&E.row[at]); 481 | memmove(&E.row[at], &E.row[at + 1], sizeof(erow) * (E.numrows - at - 1)); 482 | for (int j = at; j < E.numrows - 1; j++) E.row[j].idx--; 483 | E.numrows--; 484 | E.dirty++; 485 | } 486 | 487 | void editorRowInsertChar(erow *row, int at, int c) { 488 | if (at < 0 || at > row->size) at = row->size; 489 | row->chars = realloc(row->chars, row->size + 2); 490 | memmove(&row->chars[at + 1], &row->chars[at], row->size - at + 1); 491 | row->size++; 492 | row->chars[at] = c; 493 | editorUpdateRow(row); 494 | E.dirty++; 495 | } 496 | 497 | void editorRowAppendString(erow *row, char *s, size_t len) { 498 | row->chars = realloc(row->chars, row->size + len + 1); 499 | memcpy(&row->chars[row->size], s, len); 500 | row->size += len; 501 | row->chars[row->size] = '\0'; 502 | editorUpdateRow(row); 503 | E.dirty++; 504 | } 505 | 506 | void editorRowDelChar(erow *row, int at) { 507 | if (at < 0 || at >= row->size) return; 508 | memmove(&row->chars[at], &row->chars[at + 1], row->size - at); 509 | row->size--; 510 | editorUpdateRow(row); 511 | E.dirty++; 512 | } 513 | 514 | /*** editor operations ***/ 515 | 516 | void editorInsertChar(int c) { 517 | if (E.cy == E.numrows) { 518 | editorInsertRow(E.numrows, "", 0); 519 | } 520 | editorRowInsertChar(&E.row[E.cy], E.cx, c); 521 | E.cx++; 522 | } 523 | 524 | void editorInsertNewline() { 525 | if (E.cx == 0) { 526 | editorInsertRow(E.cy, "", 0); 527 | } else { 528 | erow *row = &E.row[E.cy]; 529 | editorInsertRow(E.cy + 1, &row->chars[E.cx], row->size - E.cx); 530 | row = &E.row[E.cy]; 531 | row->size = E.cx; 532 | row->chars[row->size] = '\0'; 533 | editorUpdateRow(row); 534 | } 535 | E.cy++; 536 | E.cx = 0; 537 | } 538 | 539 | void editorDelChar() { 540 | if (E.cy == E.numrows) return; 541 | if (E.cx == 0 && E.cy == 0) return; 542 | 543 | erow *row = &E.row[E.cy]; 544 | if (E.cx > 0) { 545 | editorRowDelChar(row, E.cx - 1); 546 | E.cx--; 547 | } else { 548 | E.cx = E.row[E.cy - 1].size; 549 | editorRowAppendString(&E.row[E.cy - 1], row->chars, row->size); 550 | editorDelRow(E.cy); 551 | E.cy--; 552 | } 553 | } 554 | 555 | /*** file i/o ***/ 556 | 557 | char *editorRowsToString(int *buflen) { 558 | int totlen = 0; 559 | int j; 560 | for (j = 0; j < E.numrows; j++) 561 | totlen += E.row[j].size + 1; 562 | *buflen = totlen; 563 | 564 | char *buf = malloc(totlen); 565 | char *p = buf; 566 | for (j = 0; j < E.numrows; j++) { 567 | memcpy(p, E.row[j].chars, E.row[j].size); 568 | p += E.row[j].size; 569 | *p = '\n'; 570 | p++; 571 | } 572 | 573 | return buf; 574 | } 575 | 576 | void editorOpen(char *filename) { 577 | free(E.filename); 578 | E.filename = strdup(filename); 579 | 580 | editorSelectSyntaxHighlight(); 581 | 582 | FILE *fp = fopen(filename, "r"); 583 | if (!fp) die("fopen"); 584 | 585 | char *line = NULL; 586 | size_t linecap = 0; 587 | ssize_t linelen; 588 | while ((linelen = getline(&line, &linecap, fp)) != -1) { 589 | while (linelen > 0 && (line[linelen - 1] == '\n' || 590 | line[linelen - 1] == '\r')) 591 | linelen--; 592 | editorInsertRow(E.numrows, line, linelen); 593 | } 594 | free(line); 595 | fclose(fp); 596 | E.dirty = 0; 597 | } 598 | 599 | void editorSave() { 600 | if (E.filename == NULL) { 601 | E.filename = editorPrompt("Save as: %s (ESC to cancel)", NULL); 602 | if (E.filename == NULL) { 603 | editorSetStatusMessage("Save aborted"); 604 | return; 605 | } 606 | editorSelectSyntaxHighlight(); 607 | } 608 | 609 | int len; 610 | char *buf = editorRowsToString(&len); 611 | 612 | int fd = open(E.filename, O_RDWR | O_CREAT, 0644); 613 | if (fd != -1) { 614 | if (ftruncate(fd, len) != -1) { 615 | if (write(fd, buf, len) == len) { 616 | close(fd); 617 | free(buf); 618 | E.dirty = 0; 619 | editorSetStatusMessage("%d bytes written to disk", len); 620 | return; 621 | } 622 | } 623 | close(fd); 624 | } 625 | 626 | free(buf); 627 | editorSetStatusMessage("Can't save! I/O error: %s", strerror(errno)); 628 | } 629 | 630 | /*** find ***/ 631 | 632 | void editorFindCallback(char *query, int key) { 633 | static int last_match = -1; 634 | static int direction = 1; 635 | 636 | static int saved_hl_line; 637 | static char *saved_hl = NULL; 638 | 639 | if (saved_hl) { 640 | memcpy(E.row[saved_hl_line].hl, saved_hl, E.row[saved_hl_line].rsize); 641 | free(saved_hl); 642 | saved_hl = NULL; 643 | } 644 | 645 | if (key == '\r' || key == '\x1b') { 646 | last_match = -1; 647 | direction = 1; 648 | return; 649 | } else if (key == ARROW_RIGHT || key == ARROW_DOWN) { 650 | direction = 1; 651 | } else if (key == ARROW_LEFT || key == ARROW_UP) { 652 | direction = -1; 653 | } else { 654 | last_match = -1; 655 | direction = 1; 656 | } 657 | 658 | if (last_match == -1) direction = 1; 659 | int current = last_match; 660 | int i; 661 | for (i = 0; i < E.numrows; i++) { 662 | current += direction; 663 | if (current == -1) current = E.numrows - 1; 664 | else if (current == E.numrows) current = 0; 665 | 666 | erow *row = &E.row[current]; 667 | char *match = strstr(row->render, query); 668 | if (match) { 669 | last_match = current; 670 | E.cy = current; 671 | E.cx = editorRowRxToCx(row, match - row->render); 672 | E.rowoff = E.numrows; 673 | 674 | saved_hl_line = current; 675 | saved_hl = malloc(row->rsize); 676 | memcpy(saved_hl, row->hl, row->rsize); 677 | memset(&row->hl[match - row->render], HL_MATCH, strlen(query)); 678 | break; 679 | } 680 | } 681 | } 682 | 683 | void editorFind() { 684 | int saved_cx = E.cx; 685 | int saved_cy = E.cy; 686 | int saved_coloff = E.coloff; 687 | int saved_rowoff = E.rowoff; 688 | 689 | char *query = editorPrompt("Search: %s (Use ESC/Arrows/Enter)", 690 | editorFindCallback); 691 | 692 | if (query) { 693 | free(query); 694 | } else { 695 | E.cx = saved_cx; 696 | E.cy = saved_cy; 697 | E.coloff = saved_coloff; 698 | E.rowoff = saved_rowoff; 699 | } 700 | } 701 | 702 | /*** append buffer ***/ 703 | 704 | struct abuf { 705 | char *b; 706 | int len; 707 | }; 708 | 709 | #define ABUF_INIT {NULL, 0} 710 | 711 | void abAppend(struct abuf *ab, const char *s, int len) { 712 | char *new = realloc(ab->b, ab->len + len); 713 | 714 | if (new == NULL) return; 715 | memcpy(&new[ab->len], s, len); 716 | ab->b = new; 717 | ab->len += len; 718 | } 719 | 720 | void abFree(struct abuf *ab) { 721 | free(ab->b); 722 | } 723 | 724 | /*** output ***/ 725 | 726 | void editorScroll() { 727 | E.rx = 0; 728 | if (E.cy < E.numrows) { 729 | E.rx = editorRowCxToRx(&E.row[E.cy], E.cx); 730 | } 731 | 732 | if (E.cy < E.rowoff) { 733 | E.rowoff = E.cy; 734 | } 735 | if (E.cy >= E.rowoff + E.screenrows) { 736 | E.rowoff = E.cy - E.screenrows + 1; 737 | } 738 | if (E.rx < E.coloff) { 739 | E.coloff = E.rx; 740 | } 741 | if (E.rx >= E.coloff + E.screencols) { 742 | E.coloff = E.rx - E.screencols + 1; 743 | } 744 | } 745 | 746 | void editorDrawRows(struct abuf *ab) { 747 | int y; 748 | for (y = 0; y < E.screenrows; y++) { 749 | int filerow = y + E.rowoff; 750 | if (filerow >= E.numrows) { 751 | if (E.numrows == 0 && y == E.screenrows / 3) { 752 | char welcome[80]; 753 | int welcomelen = snprintf(welcome, sizeof(welcome), 754 | "Kilo editor -- version %s", KILO_VERSION); 755 | if (welcomelen > E.screencols) welcomelen = E.screencols; 756 | int padding = (E.screencols - welcomelen) / 2; 757 | if (padding) { 758 | abAppend(ab, "~", 1); 759 | padding--; 760 | } 761 | while (padding--) abAppend(ab, " ", 1); 762 | abAppend(ab, welcome, welcomelen); 763 | } else { 764 | abAppend(ab, "~", 1); 765 | } 766 | } else { 767 | int len = E.row[filerow].rsize - E.coloff; 768 | if (len < 0) len = 0; 769 | if (len > E.screencols) len = E.screencols; 770 | char *c = &E.row[filerow].render[E.coloff]; 771 | unsigned char *hl = &E.row[filerow].hl[E.coloff]; 772 | int current_color = -1; 773 | int j; 774 | for (j = 0; j < len; j++) { 775 | if (iscntrl(c[j])) { 776 | char sym = (c[j] <= 26) ? '@' + c[j] : '?'; 777 | abAppend(ab, "\x1b[7m", 4); 778 | abAppend(ab, &sym, 1); 779 | abAppend(ab, "\x1b[m", 3); 780 | if (current_color != -1) { 781 | char buf[16]; 782 | int clen = snprintf(buf, sizeof(buf), "\x1b[%dm", current_color); 783 | abAppend(ab, buf, clen); 784 | } 785 | } else if (hl[j] == HL_NORMAL) { 786 | if (current_color != -1) { 787 | abAppend(ab, "\x1b[39m", 5); 788 | current_color = -1; 789 | } 790 | abAppend(ab, &c[j], 1); 791 | } else { 792 | int color = editorSyntaxToColor(hl[j]); 793 | if (color != current_color) { 794 | current_color = color; 795 | char buf[16]; 796 | int clen = snprintf(buf, sizeof(buf), "\x1b[%dm", color); 797 | abAppend(ab, buf, clen); 798 | } 799 | abAppend(ab, &c[j], 1); 800 | } 801 | } 802 | abAppend(ab, "\x1b[39m", 5); 803 | } 804 | 805 | abAppend(ab, "\x1b[K", 3); 806 | abAppend(ab, "\r\n", 2); 807 | } 808 | } 809 | 810 | void editorDrawStatusBar(struct abuf *ab) { 811 | abAppend(ab, "\x1b[7m", 4); 812 | char status[80], rstatus[80]; 813 | int len = snprintf(status, sizeof(status), "%.20s - %d lines %s", 814 | E.filename ? E.filename : "[No Name]", E.numrows, 815 | E.dirty ? "(modified)" : ""); 816 | int rlen = snprintf(rstatus, sizeof(rstatus), "%s | %d/%d", 817 | E.syntax ? E.syntax->filetype : "no ft", E.cy + 1, E.numrows); 818 | if (len > E.screencols) len = E.screencols; 819 | abAppend(ab, status, len); 820 | while (len < E.screencols) { 821 | if (E.screencols - len == rlen) { 822 | abAppend(ab, rstatus, rlen); 823 | break; 824 | } else { 825 | abAppend(ab, " ", 1); 826 | len++; 827 | } 828 | } 829 | abAppend(ab, "\x1b[m", 3); 830 | abAppend(ab, "\r\n", 2); 831 | } 832 | 833 | void editorDrawMessageBar(struct abuf *ab) { 834 | abAppend(ab, "\x1b[K", 3); 835 | int msglen = strlen(E.statusmsg); 836 | if (msglen > E.screencols) msglen = E.screencols; 837 | if (msglen && time(NULL) - E.statusmsg_time < 5) 838 | abAppend(ab, E.statusmsg, msglen); 839 | } 840 | 841 | void editorRefreshScreen() { 842 | editorScroll(); 843 | 844 | struct abuf ab = ABUF_INIT; 845 | 846 | abAppend(&ab, "\x1b[?25l", 6); 847 | abAppend(&ab, "\x1b[H", 3); 848 | 849 | editorDrawRows(&ab); 850 | editorDrawStatusBar(&ab); 851 | editorDrawMessageBar(&ab); 852 | 853 | char buf[32]; 854 | snprintf(buf, sizeof(buf), "\x1b[%d;%dH", (E.cy - E.rowoff) + 1, 855 | (E.rx - E.coloff) + 1); 856 | abAppend(&ab, buf, strlen(buf)); 857 | 858 | abAppend(&ab, "\x1b[?25h", 6); 859 | 860 | write(STDOUT_FILENO, ab.b, ab.len); 861 | abFree(&ab); 862 | } 863 | 864 | void editorSetStatusMessage(const char *fmt, ...) { 865 | va_list ap; 866 | va_start(ap, fmt); 867 | vsnprintf(E.statusmsg, sizeof(E.statusmsg), fmt, ap); 868 | va_end(ap); 869 | E.statusmsg_time = time(NULL); 870 | } 871 | 872 | /*** input ***/ 873 | 874 | char *editorPrompt(char *prompt, void (*callback)(char *, int)) { 875 | size_t bufsize = 128; 876 | char *buf = malloc(bufsize); 877 | 878 | size_t buflen = 0; 879 | buf[0] = '\0'; 880 | 881 | while (1) { 882 | editorSetStatusMessage(prompt, buf); 883 | editorRefreshScreen(); 884 | 885 | int c = editorReadKey(); 886 | if (c == DEL_KEY || c == CTRL_KEY('h') || c == BACKSPACE) { 887 | if (buflen != 0) buf[--buflen] = '\0'; 888 | } else if (c == '\x1b') { 889 | editorSetStatusMessage(""); 890 | if (callback) callback(buf, c); 891 | free(buf); 892 | return NULL; 893 | } else if (c == '\r') { 894 | if (buflen != 0) { 895 | editorSetStatusMessage(""); 896 | if (callback) callback(buf, c); 897 | return buf; 898 | } 899 | } else if (!iscntrl(c) && c < 128) { 900 | if (buflen == bufsize - 1) { 901 | bufsize *= 2; 902 | buf = realloc(buf, bufsize); 903 | } 904 | buf[buflen++] = c; 905 | buf[buflen] = '\0'; 906 | } 907 | 908 | if (callback) callback(buf, c); 909 | } 910 | } 911 | 912 | void editorMoveCursor(int key) { 913 | erow *row = (E.cy >= E.numrows) ? NULL : &E.row[E.cy]; 914 | 915 | switch (key) { 916 | case ARROW_LEFT: 917 | if (E.cx != 0) { 918 | E.cx--; 919 | } else if (E.cy > 0) { 920 | E.cy--; 921 | E.cx = E.row[E.cy].size; 922 | } 923 | break; 924 | case ARROW_RIGHT: 925 | if (row && E.cx < row->size) { 926 | E.cx++; 927 | } else if (row && E.cx == row->size) { 928 | E.cy++; 929 | E.cx = 0; 930 | } 931 | break; 932 | case ARROW_UP: 933 | if (E.cy != 0) { 934 | E.cy--; 935 | } 936 | break; 937 | case ARROW_DOWN: 938 | if (E.cy < E.numrows) { 939 | E.cy++; 940 | } 941 | break; 942 | } 943 | 944 | row = (E.cy >= E.numrows) ? NULL : &E.row[E.cy]; 945 | int rowlen = row ? row->size : 0; 946 | if (E.cx > rowlen) { 947 | E.cx = rowlen; 948 | } 949 | } 950 | 951 | void editorProcessKeypress() { 952 | static int quit_times = KILO_QUIT_TIMES; 953 | 954 | int c = editorReadKey(); 955 | 956 | switch (c) { 957 | case '\r': 958 | editorInsertNewline(); 959 | break; 960 | 961 | case CTRL_KEY('q'): 962 | if (E.dirty && quit_times > 0) { 963 | editorSetStatusMessage("WARNING!!! File has unsaved changes. " 964 | "Press Ctrl-Q %d more times to quit.", quit_times); 965 | quit_times--; 966 | return; 967 | } 968 | write(STDOUT_FILENO, "\x1b[2J", 4); 969 | write(STDOUT_FILENO, "\x1b[H", 3); 970 | exit(0); 971 | break; 972 | 973 | case CTRL_KEY('s'): 974 | editorSave(); 975 | break; 976 | 977 | case HOME_KEY: 978 | E.cx = 0; 979 | break; 980 | 981 | case END_KEY: 982 | if (E.cy < E.numrows) 983 | E.cx = E.row[E.cy].size; 984 | break; 985 | 986 | case CTRL_KEY('f'): 987 | editorFind(); 988 | break; 989 | 990 | case BACKSPACE: 991 | case CTRL_KEY('h'): 992 | case DEL_KEY: 993 | if (c == DEL_KEY) editorMoveCursor(ARROW_RIGHT); 994 | editorDelChar(); 995 | break; 996 | 997 | case PAGE_UP: 998 | case PAGE_DOWN: 999 | { 1000 | if (c == PAGE_UP) { 1001 | E.cy = E.rowoff; 1002 | } else if (c == PAGE_DOWN) { 1003 | E.cy = E.rowoff + E.screenrows - 1; 1004 | if (E.cy > E.numrows) E.cy = E.numrows; 1005 | } 1006 | 1007 | int times = E.screenrows; 1008 | while (times--) 1009 | editorMoveCursor(c == PAGE_UP ? ARROW_UP : ARROW_DOWN); 1010 | } 1011 | break; 1012 | 1013 | case ARROW_UP: 1014 | case ARROW_DOWN: 1015 | case ARROW_LEFT: 1016 | case ARROW_RIGHT: 1017 | editorMoveCursor(c); 1018 | break; 1019 | 1020 | case CTRL_KEY('l'): 1021 | case '\x1b': 1022 | break; 1023 | 1024 | default: 1025 | editorInsertChar(c); 1026 | break; 1027 | } 1028 | 1029 | quit_times = KILO_QUIT_TIMES; 1030 | } 1031 | 1032 | /*** init ***/ 1033 | 1034 | void initEditor() { 1035 | E.cx = 0; 1036 | E.cy = 0; 1037 | E.rx = 0; 1038 | E.rowoff = 0; 1039 | E.coloff = 0; 1040 | E.numrows = 0; 1041 | E.row = NULL; 1042 | E.dirty = 0; 1043 | E.filename = NULL; 1044 | E.statusmsg[0] = '\0'; 1045 | E.statusmsg_time = 0; 1046 | E.syntax = NULL; 1047 | 1048 | if (getWindowSize(&E.screenrows, &E.screencols) == -1) die("getWindowSize"); 1049 | E.screenrows -= 2; 1050 | } 1051 | 1052 | int main(int argc, char *argv[]) { 1053 | enableRawMode(); 1054 | initEditor(); 1055 | if (argc >= 2) { 1056 | editorOpen(argv[1]); 1057 | } 1058 | 1059 | editorSetStatusMessage( 1060 | "HELP: Ctrl-S = save | Ctrl-Q = quit | Ctrl-F = find"); 1061 | 1062 | while (1) { 1063 | editorRefreshScreen(); 1064 | editorProcessKeypress(); 1065 | } 1066 | 1067 | return 0; 1068 | } 1069 | --------------------------------------------------------------------------------