Implementation of tutorial for kilo text editor: https://viewsourcecode.org/snaptoken/kilo/index.html
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1469 lines
24KB

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