Advertisement
Guest User

Untitled

a guest
Jun 24th, 2017
103
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 36.02 KB | None | 0 0
  1. /* Kilogram - Very lightweight and minimal version of Kilo text editor
  2. *
  3. * Keys:
  4. * CTRL-S: Save
  5. * CTRL-Q: Quit
  6. * CTRL-F: Find text in current buffer (ESC to exit search, arrow keys to
  7. * navigate)
  8. */
  9.  
  10. #define KILOGRAM_VERSION "1.7.4"
  11.  
  12. /* Feature test macro (Satisfies getline function. */
  13. #define _BSD_SOURCE
  14. #define _GNU_SOURCE
  15.  
  16. /* Includes */
  17. #include <termios.h>
  18. #include <stdlib.h>
  19. #include <stdio.h>
  20. #include <errno.h>
  21. #include <string.h>
  22. #include <stdlib.h>
  23. #include <ctype.h>
  24. #include <sys/types.h>
  25. #include <sys/ioctl.h>
  26. #include <sys/time.h>
  27. #include <unistd.h>
  28. #include <stdarg.h>
  29. #include <fcntl.h>
  30.  
  31. /* Syntax highlight types */
  32. #define HL_NORMAL 0
  33. #define HL_NONPRINT 1
  34. #define HL_COMMENT 2 /* Single line comment. */
  35. #define HL_MLCOMMENT 3 /* Multi-line comment. */
  36. #define HL_KEYWORD1 4
  37. #define HL_KEYWORD2 5
  38. #define HL_STRING 6
  39. #define HL_NUMBER 7
  40. #define HL_MATCH 8 /* Search match. */
  41.  
  42. #define HL_HIGHLIGHT_STRINGS (1<<0)
  43. #define HL_HIGHLIGHT_NUMBERS (1<<1)
  44.  
  45. struct editorSyntax {
  46. char **filematch;
  47. char **keywords;
  48.  
  49. char singleline_comment_start[2];
  50.  
  51. char multiline_comment_start[3];
  52. char multiline_comment_end[3];
  53.  
  54. int flags;
  55. };
  56.  
  57. /* This structure represents a single line of the file we are editing. */
  58. typedef struct erow {
  59. int idx; /* Row index in the file, zero-based. */
  60. int size; /* Size of the row, excluding the null term. */
  61. int rsize; /* Size of the rendered row. */
  62. char *chars; /* Row content. */
  63. char *render; /* Row content "rendered" for screen (for TABs). */
  64. unsigned char *hl; /* Syntax highlight type for each character in render.*/
  65. int hl_oc; /* Row had open comment at end in last syntax highlight
  66. check. */
  67. } erow;
  68.  
  69. typedef struct hlcolor {
  70. int r, g, b;
  71. } hlcolor;
  72.  
  73. struct editorConfig {
  74. int cx, cy; /* Cursor x and y position in characters. */
  75. int rowoff; /* Offset of row displayed. */
  76. int coloff; /* Offset of column displayed. */
  77. int screenrows; /* Number of rows that we can show. */
  78. int screencols; /* Number of cols that we can show. */
  79. int numrows; /* Number of rows. */
  80. erow *row; /* Rows */
  81. int rawmode; /* Is terminal raw mode enabled? */
  82. int dirty; /* File modified but not saved. */
  83. char *filename; /* Currently open filename. */
  84. char statusmsg[80]; /* Editor status message */
  85. time_t statusmsg_time;
  86. struct editorSyntax *syntax; /* Current syntax highlight, or NULL. */
  87. };
  88.  
  89. static struct editorConfig E;
  90.  
  91. enum KEY_ACTION{
  92. KEY_NULL = 0, /* NULL */
  93. CTRL_C = 3, /* CTRL-C */
  94. CTRL_D = 4, /* CTRL-D */
  95. CTRL_F = 6, /* CTRL-F */
  96. CTRL_H = 8, /* CTRL-H */
  97. TAB = 9, /* TAB */
  98. CTRL_L = 12, /* CTRL+L */
  99. ENTER = 13, /* Enter */
  100. CTRL_Q = 17, /* CTRL-Q */
  101. CTRL_S = 19, /* CTRL-S */
  102. CTRL_U = 21, /* CTRL-U */
  103. ESC = 27, /* Escape */
  104. BACKSPACE = 127, /* Backspace */
  105. /* The following are just soft codes, not really reported by the
  106. * terminal directly. */
  107. ARROW_LEFT = 1000,
  108. ARROW_RIGHT,
  109. ARROW_UP,
  110. ARROW_DOWN,
  111. DEL_KEY,
  112. HOME_KEY,
  113. END_KEY,
  114. PAGE_UP,
  115. PAGE_DOWN
  116. };
  117.  
  118. void editorSetStatusMessage(const char *fmt, ...);
  119.  
  120. /* =========================== Syntax highlights DB =========================
  121. *
  122. * In order to add a new syntax, define two arrays with a list of file name
  123. * matches and keywords. The file name matches are used in order to match
  124. * a given syntax with a given file name: if a match pattern starts with a
  125. * dot, it is matched as the last past of the filename, for example ".c".
  126. * Otherwise the pattern is just searched inside the filenme, like "Makefile").
  127. *
  128. * The list of keywords to highlight is just a list of words, however if they
  129. * a trailing '|' character is added at the end, they are highlighted in
  130. * a different color, so that you can have two different sets of keywords.
  131. *
  132. * Finally add a stanza in the HLDB global variable with two two arrays
  133. * of strings, and a set of flags in order to enable highlighting of
  134. * comments and numbers.
  135. *
  136. * The characters for single and multi line comments must be exactly two
  137. * and must be provided as well (see the C language example).
  138. *
  139. * There is no support to highlight patterns currently. */
  140.  
  141. /* C / C++ */
  142. char *C_HL_extensions[] = {".c", ".cpp", NULL};
  143. char *C_HL_keywords[] = {
  144. /* A few C / C++ keywords */
  145. "switch", "if", "while", "for", "break", "continue", "return", "else",
  146. "struct", "union", "typedef", "static", "enum", "class",
  147. /* C types */
  148. "int|", "long|", "double|", "float|", "char|", "unsigned|", "signed|",
  149. "void|", NULL
  150. };
  151. /* Python */
  152. char *PY_HL_extensions[] = {".py"};
  153. char *PY_HL_keywords[] = {
  154. /* Python keywords */
  155. "and", "as", "assert", "break", "class", "continue", "def", "del", "elif",
  156. "else", "except", "exec", "finally", "for", "from", "global", "if", "import",
  157. "in", "is", "lambda", "not", "or", "pass", "print", "raise", "return", "try",
  158. "while", "width", "yield",
  159. /* Python types */
  160. "boolean|", "int|", "long|", "float|", "complex|", "|str", "|bytes", "|tuple",
  161. "|list", "|dict", "|set", NULL
  162. };
  163.  
  164. /* Here we define an array of syntax highlights by extensions, keywords,
  165. * comments delimiters and flags. */
  166. struct editorSyntax HLDB[] = {
  167. {
  168. /* C / C++ */
  169. C_HL_extensions,
  170. C_HL_keywords,
  171. "//","/*","*/",
  172. /* Python */
  173. PY_HL_extensions,
  174. PY_HL_keywords,
  175. "#", "'''",
  176.  
  177. HL_HIGHLIGHT_STRINGS | HL_HIGHLIGHT_NUMBERS
  178. }
  179. };
  180.  
  181. #define HLDB_ENTRIES (sizeof(HLDB) / sizeof(HLDB[0]))
  182.  
  183.  
  184. /* ======================= Low-level Terminal Handling ====================== */
  185.  
  186. static struct termios orig_termios; /* In order to restore at exit.*/
  187.  
  188. void disableRawMode(int fd) {
  189. /* Don't even check the return value as it's too late. */
  190. if (E.rawmode) {
  191. tcsetattr(fd, TCSAFLUSH, &orig_termios);
  192. E.rawmode = 0;
  193. }
  194. }
  195.  
  196. /* Called at exit to avoid remaining in raw mode.
  197. * If you hate yourself, you're free to remove it. */
  198. void editorAtExit(void) {
  199. disableRawMode(STDIN_FILENO);
  200. }
  201.  
  202. /* Raw mode: 1960 magic shit.
  203. * It reads stdin, but doesn't echoes keys to screen.*/
  204. int enableRawMode(int fd) {
  205. struct termios raw;
  206.  
  207. if (E.rawmode) return 0; /* Already enabled. */
  208. if (!isatty(STDIN_FILENO)) goto fatal;
  209. atexit(editorAtExit);
  210. if (tcgetattr(fd, &orig_termios) == -1) goto fatal;
  211.  
  212. raw = orig_termios; /* Modify the original mode. */
  213. /* Input modes: no break, no CR to NL, no parity check, no strip char,
  214. * no start/stop output control. */
  215. raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
  216. /* Output modes - disable post processing. */
  217. raw.c_oflag &= ~(OPOST);
  218. /* Control modes - set 8 bit chars. */
  219. raw.c_cflag |= (CS8);
  220. /* Local modes - choing off, canonical off, no extended functions,
  221. * no signal chars (^Z, ^C). */
  222. raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);
  223. /* Control chars - set return condition: min number of bytes and timer. */
  224. raw.c_cc[VMIN] = 0; /* Return each byte, or zero for timeout. */
  225. raw.c_cc[VTIME] = 1; /* 100 ms timeout (unit is tens of second). */
  226.  
  227. /* Put terminal in raw mode after flushing. */
  228. if (tcsetattr(fd, TCSAFLUSH, &raw) < 0) goto fatal;
  229. E.rawmode = 1;
  230. return 0;
  231.  
  232. fatal:
  233. errno = ENOTTY;
  234. return -1;
  235. }
  236.  
  237. /* Read a key from the terminal put in raw mode, trying to handle
  238. * escape sequences. */
  239. int editorReadKey(int fd) {
  240. int nread;
  241. char c, seq[3];
  242. while ((nread = read(fd, &c, 1)) == 0);
  243. if (nread == -1) exit(1);
  244.  
  245. while(1) {
  246. switch(c) {
  247. case ESC: /* Escape sequence */
  248. /* If this is just an ESC, we'll timeout here. */
  249. if (read(fd,seq,1) == 0) return ESC;
  250. if (read(fd,seq+1,1) == 0) return ESC;
  251.  
  252. /* ESC [ sequences (cool ones). */
  253. if (seq[0] == '[') {
  254. if (seq[1] >= '0' && seq[1] <= '9') {
  255. /* Extended escape, read additional byte. */
  256. if (read(fd, seq + 2, 1) == 0) return ESC;
  257. if (seq[2] == '~') {
  258. switch(seq[1]) {
  259. case '3': return DEL_KEY;
  260. case '5': return PAGE_UP;
  261. case '6': return PAGE_DOWN;
  262. }
  263. }
  264. } else {
  265. switch(seq[1]) {
  266. case 'A': return ARROW_UP;
  267. case 'B': return ARROW_DOWN;
  268. case 'C': return ARROW_RIGHT;
  269. case 'D': return ARROW_LEFT;
  270. case 'H': return HOME_KEY;
  271. case 'F': return END_KEY;
  272. }
  273. }
  274. }
  275.  
  276. /* ESC O sequences. */
  277. else if (seq[0] == 'O') {
  278. switch(seq[1]) {
  279. case 'H': return HOME_KEY;
  280. case 'F': return END_KEY;
  281. }
  282. }
  283. break;
  284. default:
  285. return c;
  286. }
  287. }
  288. }
  289.  
  290. /* Use the ESC [6n escape sequence to query the horizontal cursor position
  291. * and return it. On error -1 is returned, on success the position of the
  292. * cursor is stored at *rows and *cols and 0 is returned. */
  293. int getCursorPosition(int ifd, int ofd, int *rows, int *cols) {
  294. char buf[32];
  295. unsigned int i = 0;
  296.  
  297. /* Report cursor location */
  298. if (write(ofd, "\x1b[6n", 4) != 4) return -1;
  299.  
  300. /* Read the response: ESC [ rows ; cols R */
  301. while (i < sizeof(buf) - 1) {
  302. if (read(ifd, buf + i, 1) != 1) break;
  303. if (buf[i] == 'R') break;
  304. i++;
  305. }
  306. buf[i] = '\0';
  307.  
  308. /* Parse it. */
  309. if (buf[0] != ESC || buf[1] != '[') return -1;
  310. if (sscanf(buf + 2, "%d;%d", rows, cols) != 2) return -1;
  311.  
  312. return 0;
  313. }
  314.  
  315. /* Try to get the number of columns in the current terminal. If the ioctl()
  316. * call fails the function will try to query the terminal itself.
  317. * Returns 0 on success, -1 on error. */
  318. int getWindowSize(int ifd, int ofd, int *rows, int *cols) {
  319. struct winsize ws;
  320.  
  321. if (ioctl(1, TIOCGWINSZ, &ws) == -1 || ws.ws_col == 0) {
  322. /* ioctl() failed. Try to query the terminal itself. */
  323. int orig_row, orig_col, retval;
  324.  
  325. /* Get the initial position so we can restore it later. */
  326. retval = getCursorPosition(ifd, ofd, &orig_row, &orig_col);
  327. if (retval == -1) goto failed;
  328.  
  329. /* Go to right-bottom margin and get position. */
  330. if (write(ofd, "\x1b[999C\x1b[999B", 12) != 12) goto failed;
  331. retval = getCursorPosition(ifd, ofd, rows, cols);
  332. if (retval == -1) goto failed;
  333.  
  334. /* Restore position. */
  335. char seq[32];
  336. snprintf(seq, 32, "\x1b[%d;%dH", orig_row, orig_col);
  337. if (write(ofd,seq,strlen(seq)) == -1) {
  338. /* Can't recover.. Sorry user.*/
  339. }
  340. return 0;
  341. } else {
  342. *cols = ws.ws_col;
  343. *rows = ws.ws_row;
  344. return 0;
  345. }
  346.  
  347. failed:
  348. return -1;
  349. }
  350.  
  351. /* ====================== Syntax Highlight Color Scheme ==================== */
  352.  
  353. int is_separator(int c) {
  354. return c == '\0' || isspace(c) || strchr(",.()+-/*=~%[];", c) != NULL;
  355. }
  356.  
  357. /* Return true if the specified row last char is part of a multi line comment
  358. * that starts at this row or at one before, and does not end at the end
  359. * of the row but spawns to the next row. */
  360. int editorRowHasOpenComment(erow *row) {
  361. if (row->hl && row->rsize && row->hl[row->rsize-1] == HL_MLCOMMENT &&
  362. (row->rsize < 2 || (row->render[row->rsize-2] != '*' ||
  363. row->render[row->rsize-1] != '/'))) return 1;
  364. return 0;
  365. }
  366.  
  367. /* Set every byte of row->hl (that corresponds to every character in the line)
  368. * to the right syntax highlight type (HL_* defines). */
  369. void editorUpdateSyntax(erow *row) {
  370. row->hl = realloc(row->hl, row->rsize);
  371. memset(row->hl, HL_NORMAL, row->rsize);
  372.  
  373. if (E.syntax == NULL) return; /* No syntax, everything is HL_NORMAL. */
  374.  
  375. int i, prev_sep, in_string, in_comment;
  376. char *p;
  377. char **keywords = E.syntax->keywords;
  378. char *scs = E.syntax->singleline_comment_start;
  379. char *mcs = E.syntax->multiline_comment_start;
  380. char *mce = E.syntax->multiline_comment_end;
  381.  
  382. /* Point to the first non-space char. */
  383. p = row->render;
  384. i = 0; /* Current char offset */
  385. while(*p && isspace(*p)) {
  386. p++;
  387. i++;
  388. }
  389.  
  390. prev_sep = 1; /* Tell the parser if 'i' points to start of word. */
  391. in_string = 0; /* Are we inside "" or '' ? */
  392. in_comment = 0; /* Are we inside multi-line comment? */
  393.  
  394. /* If the previous line has an open comment, this line starts
  395. * with an open comment state. */
  396. if (row->idx > 0 && editorRowHasOpenComment(&E.row[row->idx-1]))
  397. in_comment = 1;
  398.  
  399. while(*p) {
  400. /* Handle single line comments. */
  401. if (prev_sep && *p == scs[0] && *(p+1) == scs[1]) {
  402. /* From here to end is a comment */
  403. memset(row->hl+i, HL_COMMENT, row->size-i);
  404. return;
  405. }
  406.  
  407. /* Handle multi line comments. */
  408. if (in_comment) {
  409. row->hl[i] = HL_MLCOMMENT;
  410. if (*p == mce[0] && *(p+1) == mce[1]) {
  411. row->hl[i+1] = HL_MLCOMMENT;
  412. p += 2; i += 2;
  413. in_comment = 0;
  414. prev_sep = 1;
  415. continue;
  416. } else {
  417. prev_sep = 0;
  418. p++; i++;
  419. continue;
  420. }
  421. } else if (*p == mcs[0] && *(p+1) == mcs[1]) {
  422. row->hl[i] = HL_MLCOMMENT;
  423. row->hl[i+1] = HL_MLCOMMENT;
  424. p += 2; i += 2;
  425. in_comment = 1;
  426. prev_sep = 0;
  427. continue;
  428. }
  429.  
  430. /* Handle "" and '' */
  431. if (in_string) {
  432. row->hl[i] = HL_STRING;
  433. if (*p == '\\') {
  434. row->hl[i+1] = HL_STRING;
  435. p += 2; i += 2;
  436. prev_sep = 0;
  437. continue;
  438. }
  439. if (*p == in_string) in_string = 0;
  440. p++; i++;
  441. continue;
  442. } else {
  443. if (*p == '"' || *p == '\'') {
  444. in_string = *p;
  445. row->hl[i] = HL_STRING;
  446. p++; i++;
  447. prev_sep = 0;
  448. continue;
  449. }
  450. }
  451.  
  452. /* Handle non printable chars. */
  453. if (!isprint(*p)) {
  454. row->hl[i] = HL_NONPRINT;
  455. p++; i++;
  456. prev_sep = 0;
  457. continue;
  458. }
  459.  
  460. /* Handle numbers */
  461. if ((isdigit(*p) && (prev_sep || row->hl[i-1] == HL_NUMBER)) ||
  462. (*p == '.' && i > 0 && row->hl[i-1] == HL_NUMBER)) {
  463. row->hl[i] = HL_NUMBER;
  464. p++; i++;
  465. prev_sep = 0;
  466. continue;
  467. }
  468.  
  469. /* Handle keywords and lib calls */
  470. if (prev_sep) {
  471. int j;
  472. for (j = 0; keywords[j]; j++) {
  473. int klen = strlen(keywords[j]);
  474. int kw2 = keywords[j][klen-1] == '|';
  475. if (kw2) klen--;
  476.  
  477. if (!memcmp(p, keywords[j], klen) && is_separator(*(p+klen))) {
  478. /* Keyword */
  479. memset(row->hl+i, kw2 ? HL_KEYWORD2 : HL_KEYWORD1, klen);
  480. p += klen;
  481. i += klen;
  482. break;
  483. }
  484. }
  485. if (keywords[j] != NULL) {
  486. prev_sep = 0;
  487. continue; /* We had a keyword match */
  488. }
  489. }
  490.  
  491. /* Not special chars */
  492. prev_sep = is_separator(*p);
  493. p++; i++;
  494. }
  495.  
  496. /* Propagate syntax change to the next row if the open commen
  497. * state changed. This may recursively affect all the following rows
  498. * in the file. */
  499. int oc = editorRowHasOpenComment(row);
  500. if (row->hl_oc != oc && row->idx+1 < E.numrows)
  501. editorUpdateSyntax(&E.row[row->idx+1]);
  502. row->hl_oc = oc;
  503. }
  504.  
  505. /* Maps syntax highlight token types to terminal colors. */
  506. int editorSyntaxToColor(int hl) {
  507. switch(hl) {
  508. case HL_COMMENT:
  509. case HL_MLCOMMENT: return 31; /* gray */
  510. case HL_KEYWORD1: return 32; /* white */
  511. case HL_KEYWORD2: return 33; /* bold white */
  512. case HL_STRING: return 33; /* bold white */
  513. case HL_NUMBER: return 32; /* white */
  514. case HL_MATCH: return 33; /* bold white */
  515. default: return 34; /* black */
  516. }
  517. }
  518.  
  519. /* Select the syntax highlight scheme depending on the filename,
  520. * setting it in the global state E.syntax. */
  521. void editorSelectSyntaxHighlight(char *filename) {
  522. for (unsigned int j = 0; j < HLDB_ENTRIES; j++) {
  523. struct editorSyntax *s = HLDB+j;
  524. unsigned int i = 0;
  525.  
  526. while(s->filematch[i]) {
  527. char *p;
  528. int patlen = strlen(s->filematch[i]);
  529. if ((p = strstr(filename, s->filematch[i])) != NULL) {
  530. if (s->filematch[i][0] != '.' || p[patlen] == '\0') {
  531. E.syntax = s;
  532. return;
  533. }
  534. }
  535. i++;
  536. }
  537. }
  538. }
  539.  
  540. /* ======================= Editor Rows Implementation ======================= */
  541.  
  542. /* Update the rendered version and the syntax highlight of a row. */
  543. void editorUpdateRow(erow *row) {
  544. int tabs = 0, nonprint = 0, j, idx;
  545.  
  546. /* Create a version of the row we can directly print on the screen,
  547. * respecting tabs, substituting non printable characters with '?'. */
  548. free(row->render);
  549. for (j = 0; j < row->size; j++) if (row->chars[j] == TAB) tabs++; // CHANGE
  550.  
  551. row->render = malloc(row->size + tabs*8 + nonprint*9 + 1);
  552. idx = 0;
  553. for (j = 0; j < row->size; j++) {
  554. if (row->chars[j] == TAB) {
  555. row->render[idx++] = ' ';
  556. while((idx+1) % 8 != 0) row->render[idx++] = ' ';
  557. } else {
  558. row->render[idx++] = row->chars[j];
  559. }
  560. }
  561. row->rsize = idx;
  562. row->render[idx] = '\0';
  563.  
  564. /* Update the syntax highlighting attributes of the row. */
  565. editorUpdateSyntax(row);
  566. }
  567.  
  568. /* Insert a row at the specified position, shifting the other rows on the bottom
  569. * if required. */
  570. void editorInsertRow(int at, char *s, size_t len) {
  571. if (at > E.numrows) return;
  572. E.row = realloc(E.row, sizeof(erow)*(E.numrows+1));
  573. if (at != E.numrows) {
  574. memmove(E.row+at+1,E.row+at,sizeof(E.row[0])*(E.numrows-at));
  575. for (int j = at+1; j <= E.numrows; j++) E.row[j].idx++;
  576. }
  577. E.row[at].size = len;
  578. E.row[at].chars = malloc(len+1);
  579. memcpy(E.row[at].chars,s,len+1);
  580. E.row[at].hl = NULL;
  581. E.row[at].hl_oc = 0;
  582. E.row[at].render = NULL;
  583. E.row[at].rsize = 0;
  584. E.row[at].idx = at;
  585. editorUpdateRow(E.row+at);
  586. E.numrows++;
  587. E.dirty++;
  588. }
  589.  
  590. /* Free row's heap allocated stuff. */
  591. void editorFreeRow(erow *row) {
  592. free(row->render);
  593. free(row->chars);
  594. free(row->hl);
  595. }
  596.  
  597. /* Remove the row at the specified position, shifting the remainign on the
  598. * top. */
  599. void editorDelRow(int at) {
  600. erow *row;
  601.  
  602. if (at >= E.numrows) return;
  603. row = E.row+at;
  604. editorFreeRow(row);
  605. memmove(E.row+at, E.row+at+1, sizeof(E.row[0])*(E.numrows-at-1));
  606. for (int j = at; j < E.numrows-1; j++) E.row[j].idx++;
  607. E.numrows--;
  608. E.dirty++;
  609. }
  610.  
  611. /* Turn the editor rows into a single heap-allocated string.
  612. * Returns the pointer to the heap-allocated string and populate the
  613. * integer pointed by 'buflen' with the size of the string, escluding
  614. * the final nulterm. */
  615. char *editorRowsToString(int *buflen) {
  616. char *buf = NULL, *p;
  617. int totlen = 0;
  618. int j;
  619.  
  620. /* Compute count of bytes */
  621. for (j = 0; j < E.numrows; j++)
  622. totlen += E.row[j].size+1; /* +1 is for "\n" at end of every row */
  623. *buflen = totlen;
  624. totlen++; /* Also make space for nulterm */
  625.  
  626. p = buf = malloc(totlen);
  627. for (j = 0; j < E.numrows; j++) {
  628. memcpy(p, E.row[j].chars, E.row[j].size);
  629. p += E.row[j].size;
  630. *p = '\n';
  631. p++;
  632. }
  633. *p = '\0';
  634. return buf;
  635. }
  636.  
  637. /* Insert a character at the specified position in a row, moving the remaining
  638. * chars on the right if needed. */
  639. void editorRowInsertChar(erow *row, int at, int c) {
  640. if (at > row->size) {
  641. /* Pad the string with spaces if the insert location is outside the
  642. * current length by more than a single character. */
  643. int padlen = at-row->size;
  644. /* In the next line +2 means: new char and null term. */
  645. row->chars = realloc(row->chars, row->size+padlen+2);
  646. memset(row->chars+row->size, ' ', padlen);
  647. row->chars[row->size+padlen+1] = '\0';
  648. row->size += padlen+1;
  649. } else {
  650. /* If we are in the middle of the string just make space for 1 new
  651. * char plus the (already existing) null term. */
  652. row->chars = realloc(row->chars, row->size+2);
  653. memmove(row->chars+at+1,row->chars+at, row->size-at+1);
  654. row->size++;
  655. }
  656. row->chars[at] = c;
  657. editorUpdateRow(row);
  658. E.dirty++;
  659. }
  660.  
  661. /* Append the string 's' at the end of a row */
  662. void editorRowAppendString(erow *row, char *s, size_t len) {
  663. row->chars = realloc(row->chars, row->size+len+1);
  664. memcpy(row->chars+row->size, s, len);
  665. row->size += len;
  666. row->chars[row->size] = '\0';
  667. editorUpdateRow(row);
  668. E.dirty++;
  669. }
  670.  
  671. /* Delete the character at offset 'at' from the specified row. */
  672. void editorRowDelChar(erow *row, int at) {
  673. if (row->size <= at) return;
  674. memmove(row->chars+at, row->chars+at+1, row->size-at);
  675. editorUpdateRow(row);
  676. row->size--;
  677. E.dirty++;
  678. }
  679.  
  680. /* Insert the specified char at the current prompt position. */
  681. void editorInsertChar(int c) {
  682. int filerow = E.rowoff+E.cy;
  683. int filecol = E.coloff+E.cx;
  684. erow *row = (filerow >= E.numrows) ? NULL : &E.row[filerow];
  685.  
  686. /* If the row where the cursor is currently located does not exist in our
  687. * logical representaion of the file, add enough empty rows as needed. */
  688. if (!row) while(E.numrows <= filerow) editorInsertRow(E.numrows, "", 0); //CH
  689. row = &E.row[filerow];
  690. editorRowInsertChar(row, filecol, c);
  691. if (E.cx == E.screencols-1) E.coloff++; // CH
  692. else E.cx++; // CH
  693. E.dirty++;
  694. }
  695.  
  696. /* Inserting a newline is slightly complex as we have to handle inserting a
  697. * newline in the middle of a line, splitting the line as needed. */
  698. void editorInsertNewline(void) {
  699. int filerow = E.rowoff+E.cy;
  700. int filecol = E.coloff+E.cx;
  701. erow *row = (filerow >= E.numrows) ? NULL : &E.row[filerow];
  702.  
  703. if (!row) {
  704. if (filerow == E.numrows) {
  705. editorInsertRow(filerow, "", 0);
  706. goto fixcursor;
  707. }
  708. return;
  709. }
  710. /* If the cursor is over the current line size, we want to conceptually
  711. * think it's just over the last character. */
  712. if (filecol >= row->size) filecol = row->size;
  713. if (filecol == 0) {
  714. editorInsertRow(filerow, "", 0);
  715. } else {
  716. /* We are in the middle of a line. Split it between two rows. */
  717. editorInsertRow(filerow+1, row->chars+filecol, row->size-filecol);
  718. row = &E.row[filerow];
  719. row->chars[filecol] = '\0';
  720. row->size = filecol;
  721. editorUpdateRow(row);
  722. }
  723. fixcursor:
  724. if (E.cy == E.screenrows-1) E.rowoff++;
  725. else E.cy++; // CH
  726. E.cx = 0;
  727. E.coloff = 0;
  728. }
  729.  
  730. /* Delete the char at the current prompt position. */
  731. void editorDelChar() {
  732. int filerow = E.rowoff+E.cy;
  733. int filecol = E.coloff+E.cx;
  734. erow *row = (filerow >= E.numrows) ? NULL : &E.row[filerow];
  735.  
  736. if (!row || (filecol == 0 && filerow == 0)) return;
  737. if (filecol == 0) {
  738. /* Handle the case of column 0, we need to move the current line
  739. * on the right of the previous one. */
  740. filecol = E.row[filerow-1].size;
  741. editorRowAppendString(&E.row[filerow-1], row->chars, row->size);
  742. editorDelRow(filerow);
  743. row = NULL;
  744. if (E.cy == 0) E.rowoff--;
  745. else E.cy--; // CH
  746. E.cx = filecol;
  747. if (E.cx >= E.screencols) {
  748. int shift = (E.screencols-E.cx)+1;
  749. E.cx -= shift;
  750. E.coloff += shift;
  751. }
  752. } else {
  753. editorRowDelChar(row, filecol-1);
  754. if (E.cx == 0 && E.coloff)
  755. E.coloff--;
  756. else
  757. E.cx--;
  758. }
  759.  
  760. if (row) editorUpdateRow(row);
  761. E.dirty++;
  762. }
  763.  
  764. /* Load the specified program in the editor memory and return 0 on success
  765. * or 1 on error. */
  766. int editorOpen(char *filename) {
  767. FILE *fp;
  768.  
  769. E.dirty = 0;
  770. free(E.filename);
  771. E.filename = strdup(filename);
  772.  
  773. fp = fopen(filename, "r");
  774. if (!fp) {
  775. if (errno != ENOENT) {
  776. perror("Opening file");
  777. exit(1);
  778. }
  779. return 1;
  780. }
  781.  
  782. char *line = NULL;
  783. size_t linecap = 0;
  784. ssize_t linelen;
  785. while((linelen = getline(&line, &linecap, fp)) != -1) {
  786. if (linelen && (line[linelen-1] == '\n' || line[linelen-1] == '\r'))
  787. line[--linelen] = '\0';
  788. editorInsertRow(E.numrows, line, linelen);
  789. }
  790. free(line);
  791. fclose(fp);
  792. E.dirty = 0;
  793. return 0;
  794. }
  795.  
  796. /* Save the current file on disk. Return 0 on success, 1 on error. */
  797. int editorSave(void) {
  798. int len;
  799. char *buf = editorRowsToString(&len);
  800. int fd = open(E.filename, O_RDWR|O_CREAT, 0644);
  801. if (fd == -1) goto writeerr;
  802.  
  803. /* Use truncate + a single write(2) call in order to make saving
  804. * a bit safer, under the limits of what we can do in a small editor. */
  805. if (ftruncate(fd, len) == -1) goto writeerr;
  806. if (write(fd, buf, len) != len) goto writeerr;
  807.  
  808. close(fd);
  809. free(buf);
  810. E.dirty = 0;
  811. editorSetStatusMessage("%d bytes written on disk!", len);
  812. return 0;
  813.  
  814. writeerr:
  815. free(buf);
  816. if (fd != -1) close(fd);
  817. editorSetStatusMessage("Can't save! I/O error: %s", strerror(errno));
  818. return 1;
  819. }
  820.  
  821. /* ============================= Terminal update ============================ */
  822.  
  823. /* We define a very simple "append buffer" structure, that is an heap
  824. * allocated string where we can append to. This is useful in order to
  825. * write all the escape sequences in a buffer and flush them to the standard
  826. * output in a single call, to avoid flickering effects. */
  827. struct abuf {
  828. char *b;
  829. int len;
  830. };
  831.  
  832. #define ABUF_INIT {NULL, 0}
  833.  
  834. void abAppend(struct abuf *ab, const char *s, int len) {
  835. char *new = realloc(ab->b, ab->len+len);
  836.  
  837. if (new == NULL) return;
  838. memcpy(new+ab->len, s, len);
  839. ab->b = new;
  840. ab->len += len;
  841. }
  842.  
  843. void abFree(struct abuf *ab) {
  844. free(ab->b);
  845. }
  846.  
  847. /* This function writes the whole screen using VT100 escape characters
  848. * starting from the logical state of the editor in the global state 'E'. */
  849. void editorRefreshScreen(void) {
  850. int y;
  851. erow *r;
  852. char buf[32];
  853. struct abuf ab = ABUF_INIT;
  854.  
  855. abAppend(&ab,"\x1b[?25l",6); /* Hide cursor. */
  856. abAppend(&ab,"\x1b[H",3); /* Go home. */
  857. for (y = 0; y < E.screenrows; y++) {
  858. int filerow = E.rowoff+y;
  859.  
  860. if (filerow >= E.numrows) {
  861. if (E.numrows == 0 && y == E.screenrows/3) {
  862. char welcome[80];
  863. int welcomelen = snprintf(welcome, sizeof(welcome),
  864. "Kilogram Editor -- v%s\x1b[0K\r\n", KILOGRAM_VERSION);
  865. int padding = (E.screencols-welcomelen)/2;
  866. if (padding) {
  867. abAppend(&ab, "~", 1);
  868. padding--;
  869. }
  870. while(padding--) abAppend(&ab, " ", 1);
  871. abAppend(&ab, welcome, welcomelen);
  872. } else {
  873. abAppend(&ab, "~\x1b[0K\r\n", 7);
  874. }
  875. continue;
  876. }
  877.  
  878. r = &E.row[filerow];
  879.  
  880. int len = r->rsize - E.coloff;
  881. int current_color = -1;
  882. if (len > 0) {
  883. if (len > E.screencols) len = E.screencols;
  884. char *c = r->render+E.coloff;
  885. unsigned char *hl = r->hl+E.coloff;
  886. int j;
  887. for (j = 0; j < len; j++) {
  888. if (hl[j] == HL_NONPRINT) {
  889. char sym;
  890. abAppend(&ab, "\x1b[7m", 4);
  891. if (c[j] <= 26) sym = '@'+c[j]; // CH
  892. else sym = '?';
  893. abAppend(&ab, &sym, 1);
  894. abAppend(&ab, "\x1b[0m", 4);
  895. } else if (hl[j] == HL_NORMAL) {
  896. if (current_color != -1) {
  897. abAppend(&ab, "\x1b[39m", 5);
  898. current_color = -1;
  899. }
  900. abAppend(&ab, c+j, 1);
  901. } else {
  902. int color = editorSyntaxToColor(hl[j]);
  903. if (color != current_color) {
  904. char buf[16];
  905. int clen = snprintf(buf, sizeof(buf), "\x1b[%dm", color);
  906. current_color = color;
  907. abAppend(&ab, buf, clen);
  908. }
  909. abAppend(&ab, c+j, 1);
  910. }
  911. }
  912. }
  913. abAppend(&ab,"\x1b[39m", 5);
  914. abAppend(&ab,"\x1b[0K", 4);
  915. abAppend(&ab,"\r\n", 2);
  916. }
  917.  
  918. /* Create a two rows status. First row: */
  919. abAppend(&ab, "\x1b[0K", 4);
  920. abAppend(&ab, "\x1b[7m", 4);
  921.  
  922. char status[80], rstatus[80];
  923. int len = snprintf(status, sizeof(status), "%.20s - %d lines %s",
  924. E.filename, E.numrows, E.dirty ? "(modified)" : ""); /* Sum of lines */
  925. int rlen = snprintf(rstatus, sizeof(rstatus),
  926. "%d/%d", E.rowoff+E.cy+1, E.numrows); /* Line indicator */
  927. if (len > E.screencols) len = E.screencols;
  928.  
  929. abAppend(&ab, status, len);
  930. while(len < E.screencols) {
  931. if (E.screencols - len == rlen) {
  932. abAppend(&ab, rstatus, rlen);
  933. break;
  934. } else {
  935. abAppend(&ab, " ", 1);
  936. len++;
  937. }
  938. }
  939. abAppend(&ab, "\x1b[0m\r\n", 6);
  940.  
  941. /* Second row depends on E.statusmsg and the status message update time. */
  942. abAppend(&ab, "\x1b[0K", 4);
  943. int msglen = strlen(E.statusmsg);
  944. if (msglen && time(NULL)-E.statusmsg_time < 5)
  945. abAppend(&ab, E.statusmsg,msglen <= E.screencols ? msglen : E.screencols);
  946.  
  947. /* Put cursor at its current position. Note that the horizontal position
  948. * at which the cursor is displayed may be different compared to 'E.cx'
  949. * because of TABs. */
  950. int j;
  951. int cx = 1;
  952. int filerow = E.rowoff+E.cy;
  953. erow *row = (filerow >= E.numrows) ? NULL : &E.row[filerow];
  954. if (row) {
  955. for (j = E.coloff; j < (E.cx+E.coloff); j++) {
  956. if (j < row->size && row->chars[j] == TAB) cx += 7-((cx)%8);
  957. cx++;
  958. }
  959. }
  960. snprintf(buf, sizeof(buf), "\x1b[%d;%dH", E.cy+1, cx);
  961. abAppend(&ab, buf, strlen(buf));
  962. abAppend(&ab, "\x1b[?25h", 6); /* Show cursor. */
  963.  
  964. write(STDOUT_FILENO, ab.b, ab.len);
  965. abFree(&ab);
  966. }
  967.  
  968. /* Set an editor status message for the second line of the status, at the
  969. * end of the screen. */
  970. void editorSetStatusMessage(const char *fmt, ...) {
  971. va_list ap;
  972. va_start(ap, fmt);
  973. vsnprintf(E.statusmsg, sizeof(E.statusmsg), fmt, ap);
  974. va_end(ap);
  975. E.statusmsg_time = time(NULL);
  976. }
  977.  
  978. /* =============================== Find mode ================================ */
  979.  
  980. #define KILOGRAM_QUERY_LEN 256
  981.  
  982. void editorFind(int fd) {
  983. char query[KILO_QUERY_LEN+1] = {0};
  984. int qlen = 0;
  985. int last_match = -1; /* Last line where a match was found. -1 for none. */
  986. int find_next = 0; /* if 1 search next, if -1 search prev. */
  987. int saved_hl_line = -1; /* No saved HL */
  988. char *saved_hl = NULL;
  989.  
  990. #define FIND_RESTORE_HL do { \
  991. if (saved_hl) { \
  992. memcpy(E.row[saved_hl_line].hl,saved_hl, E.row[saved_hl_line].rsize); \
  993. saved_hl = NULL; \
  994. } \
  995. } while (0)
  996.  
  997. /* Save the cursor position in order to restore it later. */
  998. int saved_cx = E.cx, saved_cy = E.cy;
  999. int saved_coloff = E.coloff, saved_rowoff = E.rowoff;
  1000.  
  1001. while(1) {
  1002. editorSetStatusMessage("Search: %s (Use ESC/Arrows/Enter)", query);
  1003. editorRefreshScreen();
  1004.  
  1005. int c = editorReadKey(fd);
  1006. if (c == DEL_KEY || c == CTRL_H || c == BACKSPACE) {
  1007. if (qlen != 0) query[--qlen] = '\0';
  1008. last_match = -1;
  1009. } else if (c == ESC || c == ENTER) {
  1010. if (c == ESC) {
  1011. E.cx = saved_cx; E.cy = saved_cy;
  1012. E.coloff = saved_coloff; E.rowoff = saved_rowoff;
  1013. }
  1014. FIND_RESTORE_HL;
  1015. editorSetStatusMessage("");
  1016. return;
  1017. } else if (c == ARROW_RIGHT || c == ARROW_DOWN) {
  1018. find_next = 1;
  1019. } else if (c == ARROW_LEFT || c == ARROW_UP) {
  1020. find_next = -1;
  1021. } else if (isprint(c)) {
  1022. if (qlen < KILOGRAM_QUERY_LEN) {
  1023. query[qlen++] = c;
  1024. query[qlen] = '\0';
  1025. last_match = -1;
  1026. }
  1027. }
  1028.  
  1029. /* Search occurrence. */
  1030. if (last_match == -1) find_next = 1;
  1031. if (find_next) {
  1032. char *match = NULL;
  1033. int match_offset = 0;
  1034. int i, current = last_match;
  1035.  
  1036. for (i = 0; i < E.numrows; i++) {
  1037. current += find_next;
  1038. if (current == -1) current = E.numrows-1;
  1039. else if (current == E.numrows) current = 0;
  1040. match = strstr(E.row[current].render, query);
  1041. if (match) {
  1042. match_offset = match-E.row[current].render;
  1043. break;
  1044. }
  1045. }
  1046. find_next = 0;
  1047.  
  1048. /* Highlight */
  1049. FIND_RESTORE_HL;
  1050.  
  1051. if (match) {
  1052. erow *row = &E.row[current];
  1053. last_match = current;
  1054. if (row->hl) {
  1055. saved_hl_line = current;
  1056. saved_hl = malloc(row->rsize);
  1057. memcpy(saved_hl, row->hl, row->rsize);
  1058. memset(row->hl+match_offset, HL_MATCH, qlen);
  1059. }
  1060. E.cy = 0;
  1061. E.cx = match_offset;
  1062. E.rowoff = current;
  1063. E.coloff = 0;
  1064.  
  1065. /* Scroll horizontally as needed. */
  1066. if (E.cx > E.screencols) {
  1067. int diff = E.cx - E.screencols;
  1068. E.cx -= diff;
  1069. E.coloff += diff;
  1070. }
  1071. }
  1072. }
  1073. }
  1074. }
  1075.  
  1076. /* ========================= Editor Events Handling ======================== */
  1077.  
  1078. /* Handle cursor position change because arrow keys were pressed. */
  1079. void editorMoveCursor(int key) {
  1080. int filerow = E.rowoff+E.cy;
  1081. int filecol = E.coloff+E.cx;
  1082. int rowlen;
  1083. erow *row = (filerow >= E.numrows) ? NULL : &E.row[filerow];
  1084.  
  1085. switch(key) {
  1086. case ARROW_LEFT:
  1087. if (E.cx == 0) {
  1088. if (E.coloff) {
  1089. E.coloff--;
  1090. } else {
  1091. if (filerow > 0) {
  1092. E.cy--;
  1093. E.cx = E.row[filerow-1].size;
  1094. if (E.cx > E.screencols-1) {
  1095. E.coloff = E.cx-E.screencols+1;
  1096. E.cx = E.screencols-1;
  1097. }
  1098. }
  1099. }
  1100. } else {
  1101. E.cx -= 1;
  1102. }
  1103. break;
  1104. case ARROW_RIGHT:
  1105. if (row && filecol < row->size) {
  1106. if (E.cx == E.screencols-1) {
  1107. E.coloff++;
  1108. } else {
  1109. E.cx += 1;
  1110. }
  1111. } else if (row && filecol == row->size) {
  1112. E.cx = 0;
  1113. E.coloff = 0;
  1114. if (E.cy == E.screenrows-1) {
  1115. E.rowoff++;
  1116. } else {
  1117. E.cy += 1;
  1118. }
  1119. }
  1120. break;
  1121. case ARROW_UP:
  1122. if (E.cy == 0) {
  1123. if (E.rowoff) E.rowoff--;
  1124. } else {
  1125. E.cy -= 1;
  1126. }
  1127. break;
  1128. case ARROW_DOWN:
  1129. if (filerow < E.numrows) {
  1130. if (E.cy == E.screenrows-1) {
  1131. E.rowoff++;
  1132. } else {
  1133. E.cy += 1;
  1134. }
  1135. }
  1136. break;
  1137. }
  1138.  
  1139. /* Fix cx if the current line has not enough chars. */
  1140. filerow = E.rowoff+E.cy;
  1141. filecol = E.coloff+E.cx;
  1142. row = (filerow >= E.numrows) ? NULL : &E.row[filerow];
  1143. rowlen = row ? row->size : 0;
  1144. if (filecol > rowlen) {
  1145. E.cx -= filecol-rowlen;
  1146. if (E.cx < 0) {
  1147. E.coloff += E.cx;
  1148. E.cx = 0;
  1149. }
  1150. }
  1151. }
  1152.  
  1153. /* Process events arriving from the standard input, which is, the user
  1154. * is typing stuff on the terminal. */
  1155. #define KILOGRAM_QUIT_TIMES 3
  1156. void editorProcessKeypress(int fd) {
  1157. /* When the file is modified, requires CTRL-Q to be pressed N times
  1158. * before actually quitting. */
  1159. static int quit_times = KILOGRAM_QUIT_TIMES;
  1160.  
  1161. int c = editorReadKey(fd);
  1162. switch(c) {
  1163. case ENTER: /* Enter */
  1164. editorInsertNewline();
  1165. break;
  1166. case CTRL_C: /* CTRL-C */
  1167. /* We ignore ctrl-c, it can't be so simple to lose the changes
  1168. * to the edited file. */
  1169. break;
  1170. case CTRL_Q: /* CTRL-Q */
  1171. /* Quit if the file was already saved. */
  1172. if (E.dirty && quit_times) {
  1173. editorSetStatusMessage("File has unsaved changes. "
  1174. "Press CTRL-Q %d more times to quit.", quit_times);
  1175. quit_times--;
  1176. return;
  1177. }
  1178. exit(0);
  1179. break;
  1180. case CTRL_S: /* CTRL-S */
  1181. editorSave();
  1182. break;
  1183. case CTRL_F: /* CTRL-F */
  1184. editorFind(fd);
  1185. break;
  1186. case BACKSPACE: /* Backspace */
  1187. case CTRL_H: /* CTRL-H */
  1188. case DEL_KEY: /* Delete */
  1189. editorDelChar();
  1190. break;
  1191. case PAGE_UP: /* Page up*/
  1192. case PAGE_DOWN: /* Page down */
  1193. if (c == PAGE_UP && E.cy != 0) E.cy = 0;
  1194. else if (c == PAGE_DOWN && E.cy != E.screenrows-1)
  1195. E.cy = E.screenrows-1;
  1196. {
  1197. int times = E.screenrows;
  1198. while(times--) editorMoveCursor(c == PAGE_UP ? ARROW_UP: ARROW_DOWN);
  1199. }
  1200. break;
  1201.  
  1202. case ARROW_UP:
  1203. case ARROW_DOWN:
  1204. case ARROW_LEFT:
  1205. case ARROW_RIGHT:
  1206. editorMoveCursor(c);
  1207. break;
  1208. case CTRL_L: /* CTRL-L, clear screen */
  1209. /* Just refresht the line as side effect. */
  1210. break;
  1211. case ESC:
  1212. /* Nothing to do for ESC in this mode. */
  1213. break;
  1214. default:
  1215. editorInsertChar(c);
  1216. break;
  1217. }
  1218.  
  1219. quit_times = KILOGRAM_QUIT_TIMES; /* Reset it to the original value. */
  1220. }
  1221.  
  1222. int editorFileWasModified(void) {
  1223. return E.dirty;
  1224. }
  1225.  
  1226. void initEditor(void) {
  1227. E.cx = 0;
  1228. E.cy = 0;
  1229. E.rowoff = 0;
  1230. E.coloff = 0;
  1231. E.numrows = 0;
  1232. E.row = NULL;
  1233. E.dirty = 0;
  1234. E.filename = NULL;
  1235. E.syntax = NULL;
  1236. if (getWindowSize(STDIN_FILENO,STDOUT_FILENO,
  1237. &E.screenrows, &E.screencols) == -1)
  1238. {
  1239. perror("Unable to query the screen for size (columns/rows)");
  1240. exit(1);
  1241. }
  1242. E.screenrows -= 2; /* Get room for status bar. */
  1243. }
  1244.  
  1245. int main(int argc, char **argv) {
  1246. if (argc != 2) {
  1247. fprintf(stderr, "Usage: kg <filename>\n");
  1248. exit(1);
  1249. }
  1250.  
  1251. initEditor();
  1252. editorSelectSyntaxHighlight(argv[1]);
  1253. editorOpen(argv[1]);
  1254. enableRawMode(STDIN_FILENO);
  1255. editorSetStatusMessage("HELP: CTRL-S = Save | CTRL-Q = Quit | CTRL-F = Find");
  1256.  
  1257. while(1) {
  1258. editorRefreshScreen();
  1259. editorProcessKeypress(STDIN_FILENO);
  1260. }
  1261.  
  1262. return 0;
  1263. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement