tve.c - ve - a minimal text editor (work in progress)
(HTM) git clone git://src.adamsgaard.dk/ve
(DIR) Log
(DIR) Files
(DIR) Refs
(DIR) README
(DIR) LICENSE
---
tve.c (24211B)
---
1 /* see LICENSE for license details */
2
3 #include <sys/types.h>
4 #include <sys/ioctl.h>
5
6 #include <ctype.h>
7 #include <errno.h>
8 #include <fcntl.h>
9 #include <stdarg.h>
10 #include <stdlib.h>
11 #include <stdio.h>
12 #include <stdlib.h>
13 #include <string.h>
14 #include <termios.h>
15 #include <time.h>
16 #include <unistd.h>
17
18 #include "arg.h"
19
20 /* MACROS */
21 #define ABUF_INIT {NULL, 0}
22 #define CTRL_KEY(k) ((k) & 0x1f)
23
24
25 /* TYPES */
26 struct abuf {
27 char *b;
28 int len;
29 };
30
31 /* editor row: stores a row of text */
32 typedef struct eRow {
33 char *chars; /* text read from file */
34 char *rchars; /* text to render */
35 int size; /* length of chars */
36 int rsize; /* length of rchars */
37 unsigned char *hl; /* array of rsize storing highlight type per rchar */
38 } eRow;
39
40 struct editor_config {
41 int cursor_x, cursor_y, cursor_rx;
42 int screen_rows, screen_columns;
43 int num_rows;
44 eRow *row;
45 int row_offset, column_offset;
46 char *filename;
47 struct termios orig_termios;
48 int mode; /* 0: normal, 1: insert, 2: visual */
49 int show_status;
50 char status_msg[80];
51 time_t status_msg_time;
52 int file_changed;
53 char *find_query;
54 int find_direction;
55 };
56
57 /* color scheme types */
58 enum { ColorNormal, ColorDigit, ColorComment, ColorKeyword };
59 enum { FG, BG, STYLE };
60
61 /* configuration, allows nested code to access above variables */
62 #include "config.h"
63
64
65 /* FUNCTION DECLARATIONS */
66 char* editor_prompt(char *prompt);
67 void editor_set_status_message(const char *fmt, ...);
68
69
70 /* GLOBAL VARIABLES */
71 struct editor_config E;
72 char *argv0;
73
74 /* FUNCTION DEFINITIONS */
75
76 /** TERMINAL **/
77 void
78 die(const char *s)
79 {
80 /* clear screen on exit */
81 write(STDOUT_FILENO, "\x1b[2J", 4);
82 write(STDOUT_FILENO, "\x1b[H", 3);
83 perror(s);
84 exit(1);
85 }
86
87 void
88 disable_raw_mode()
89 {
90 if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &E.orig_termios) == -1)
91 die("tcsetattr in disable_raw_mode()");
92 }
93
94 void
95 enable_raw_mode()
96 {
97 struct termios raw;
98
99 if (tcgetattr(STDIN_FILENO, &E.orig_termios) == -1)
100 die("tcgetattr in enable_raw_mode()");
101 atexit(disable_raw_mode);
102
103 /* fix modifier keys, set 8 bits per char, ignore interrupts */
104 raw = E.orig_termios;
105 raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
106 raw.c_oflag &= ~(OPOST);
107 raw.c_cflag |= (CS8);
108 raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);
109
110 /* set read() timeout in tenths of seconds */
111 raw.c_cc[VMIN] = 0;
112 raw.c_cc[VTIME] = 1;
113
114 /* apply terminal settings */
115 if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw) == -1)
116 die("tcsetattr in enable_raw_mode()");
117 }
118
119 char
120 editor_read_key()
121 {
122 int nread;
123 char c;
124 while ((nread = read(STDIN_FILENO, &c, 1)) != 1) {
125 if (nread == -1 && errno != EAGAIN)
126 die("read in editor_read_key()");
127 }
128 return c;
129 }
130
131 /* get screen size by moving cursor and get current position */
132 int
133 get_cursor_position(int *rows, int *cols) {
134 char buf[32];
135 unsigned int i;
136
137 if (write(STDOUT_FILENO, "\x1b[6n", 4) != 4)
138 return -1;
139
140 i = 0;
141 while (i < sizeof(buf) - 1) {
142 if (read(STDIN_FILENO, &buf[i], 1) != 1)
143 break;
144 if (buf[i] == 'R')
145 break;
146 ++i;
147 }
148 buf[i] = '\0';
149
150 if (buf[0] != '\x1b' || buf[1] != '[')
151 return -1;
152 if (sscanf(&buf[2], "%d;%d", rows, cols) != 2)
153 return -1;
154 return 0;
155 }
156
157 int
158 get_window_size(int *rows, int *cols)
159 {
160 struct winsize ws;
161
162 if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == -1 || ws.ws_col == 0) {
163 /* fallback screen size detection */
164 if (write(STDOUT_FILENO, "\x1b[999C\x1b[999B", 12) != 12)
165 return -1;
166 return get_cursor_position(rows, cols);
167 } else {
168 *cols = ws.ws_col;
169 *rows = ws.ws_row;
170 return 0;
171 }
172 }
173
174
175 /** SYNTAX HIGHLIGHTING **/
176
177 void
178 editor_update_syntax(eRow *row)
179 {
180 int i;
181 row->hl = realloc(row->hl, row->rsize);
182 memset(row->hl, ColorNormal, row->rsize);
183 for (i=0; i<row->rsize; ++i) {
184 if (isdigit(row->rchars[i]))
185 row->hl[i] = ColorDigit;
186 }
187 }
188
189
190 /** ROW OPERATIONS **/
191
192 /* navigate over tab-representative string as one character */
193 int
194 editor_row_cursor_x_to_rx(eRow *row, int cursor_x)
195 {
196 int rx, j;
197 rx = 0;
198 for (j=0; j<cursor_x; ++j) {
199 if (row->chars[j] == '\t')
200 rx += (tab_width - 1) - (rx % tab_width);
201 rx++;
202 }
203 return rx;
204 }
205
206 /* translate on-screen position to data position */
207 int
208 editor_row_cursor_rx_to_x(eRow *row, int cursor_rx)
209 {
210 int cur_rx, cx;
211 cur_rx = 0;
212 for (cx=0; cx<row->size; ++cx) {
213 if (row->chars[cx] == '\t')
214 cur_rx += (tab_width - 1) - (cur_rx % tab_width);
215 cur_rx++;
216
217 if (cur_rx > cursor_rx)
218 return cx;
219 }
220 return cx;
221 }
222
223 /* translate tabs before display */
224 void
225 editor_row_update(eRow* row)
226 {
227 int j, idx, tabs;
228
229 free(row->rchars);
230 row->rchars = malloc(row->size + 1);
231
232 tabs = 0;
233 for (j=0; j<row->size; ++j)
234 if (row->chars[j] == '\t')
235 tabs++;
236
237 free(row->rchars);
238 row->rchars = malloc(row->size + tabs*(tab_width - 1) + 1);
239
240 idx = 0;
241 for (j=0; j<row->size; ++j) {
242 if (row->chars[j] == '\t') {
243 row->rchars[idx++] = '>';
244 while (idx % tab_width != 0)
245 row->rchars[idx++] = ' ';
246 } else {
247 row->rchars[idx++] = row->chars[j];
248 }
249 }
250 row->rchars[idx] = '\0';
251 row->rsize = idx;
252 editor_update_syntax(row);
253 }
254
255 /* add row to buffer */
256 void
257 editor_row_insert(int i, char *s, size_t len)
258 {
259 if (i<0 || i>E.num_rows)
260 return;
261
262 E.row = realloc(E.row, sizeof(eRow) * (E.num_rows + 1));
263 memmove(&E.row[i+1], &E.row[i], sizeof(eRow) * (E.num_rows - i));
264
265 E.row[i].size = len;
266 E.row[i].chars = malloc(len + 1);
267 memcpy(E.row[i].chars, s, len);
268 E.row[i].chars[len] = '\0';
269
270 E.row[i].rsize = 0;
271 E.row[i].rchars = NULL;
272 E.row[i].hl = NULL;
273 editor_row_update(&E.row[i]);
274
275 ++E.num_rows;
276 E.file_changed = 1;
277 }
278
279 /* insert character to row, making sure to allocate memory accordingly */
280 void
281 editor_row_insert_char(eRow *row, int i, int c)
282 {
283 if (i<0 || i>row->size)
284 i = row->size;
285 row->chars = realloc(row->chars, row->size + 2);
286 memmove(&row->chars[i+1], &row->chars[i], row->size - i+1);
287 row->size++;
288 row->chars[i] = c;
289 editor_row_update(row);
290 E.file_changed = 1;
291 }
292
293 /* append a string to the end of a row */
294 void
295 editor_row_append_string(eRow *row, char *s, size_t len)
296 {
297 row->chars = realloc(row->chars, row->size + len + 1);
298 memcpy(&row->chars[row->size], s, len);
299 row->size += len;
300 row->chars[row->size] = '\0';
301 editor_row_update(row);
302 E.file_changed = 1;
303 }
304
305 void
306 editor_row_delete_char(eRow *row, int i)
307 {
308 if (i<0 || i>=row->size)
309 return;
310 memmove(&row->chars[i], &row->chars[i+1], row->size - i);
311 row->size--;
312 editor_row_update(row);
313 E.file_changed = 1;
314 }
315
316 void
317 editor_row_free(eRow *row)
318 {
319 free(row->chars);
320 free(row->rchars);
321 free(row->hl);
322 }
323
324 void
325 editor_row_delete(int i)
326 {
327 if (i<0 || i>=E.num_rows)
328 return;
329 editor_row_free(&E.row[i]);
330 memmove(&E.row[i], &E.row[i+1], sizeof(eRow)*(E.num_rows - i - 1));
331 E.num_rows--;
332 E.file_changed = 1;
333 }
334
335 void
336 editor_insert_char(int c)
337 {
338 if (E.cursor_y == E.num_rows)
339 editor_row_insert(E.num_rows, "", 0);
340 editor_row_insert_char(&E.row[E.cursor_y], E.cursor_x, c);
341 E.cursor_x++;
342 }
343
344 void
345 editor_insert_new_line()
346 {
347 eRow *row;
348 if (E.cursor_x == 0) {
349 editor_row_insert(E.cursor_y, "", 0);
350 } else {
351 row = &E.row[E.cursor_y];
352 editor_row_insert(E.cursor_y + 1, &row->chars[E.cursor_x],
353 row->size - E.cursor_x);
354 row = &E.row[E.cursor_y];
355 row->size = E.cursor_x;
356 row->chars[row->size] = '\0';
357 editor_row_update(row);
358 }
359 E.cursor_y++;
360 E.cursor_x = 0;
361 }
362
363 /* delete a character before the cursor, and allow for deletion across rows */
364 void
365 editor_delete_char_left()
366 {
367 eRow *row;
368 if (E.cursor_y == E.num_rows || (E.cursor_x == 0 && E.cursor_y == 0))
369 return;
370
371 row = &E.row[E.cursor_y];
372 if (E.cursor_x > 0) {
373 editor_row_delete_char(row, E.cursor_x - 1);
374 E.cursor_x--;
375 } else {
376 E.cursor_x = E.row[E.cursor_y - 1].size;
377 editor_row_append_string(&E.row[E.cursor_y - 1],
378 row->chars, row->size);
379 editor_row_delete(E.cursor_y);
380 E.cursor_y--;
381 }
382 }
383
384 void
385 editor_delete_char_right()
386 {
387 if (E.cursor_y == E.num_rows)
388 return;
389 eRow *row = &E.row[E.cursor_y];
390 editor_row_delete_char(row, E.cursor_x);
391 }
392
393 /* convert rows to one long char array of length buflen */
394 char*
395 editor_concatenate_rows(int *buflen)
396 {
397 int totlen, j;
398 char *buf, *p;
399
400 totlen = 0;
401 for (j=0; j<E.num_rows; ++j)
402 totlen += E.row[j].size + 1; /* add space for newline char */
403 *buflen = totlen;
404
405 buf = malloc(totlen);
406 p = buf;
407 for (j=0; j<E.num_rows; ++j) {
408 memcpy(p, E.row[j].chars, E.row[j].size);
409 p += E.row[j].size;
410 *p = '\n';
411 p++;
412 }
413 return buf;
414 }
415
416
417 /** FILE IO **/
418
419 /* open file and read lines into memory */
420 void
421 file_open(char *filename)
422 {
423 free(E.filename);
424 E.filename = strdup(filename);
425
426 FILE *fp;
427 char *line;
428 size_t linecap;
429 ssize_t linelen;
430
431 fp = fopen(filename, "r");
432 if (!fp)
433 die("fopen in file_open");
434
435 line = NULL;
436 linecap = 0;
437 while ((linelen = getline(&line, &linecap, fp)) != -1) {
438 while (linelen > 0 && (line[linelen - 1] == '\n' ||
439 line[linelen - 1] == '\r'))
440 linelen--;
441 editor_row_insert(E.num_rows, line, linelen);
442 }
443 free(line);
444 fclose(fp);
445 E.file_changed = 0;
446 }
447
448 void
449 file_save(char *filename)
450 {
451 int len, fd;
452 char *buf;
453
454 if (filename == NULL) {
455 filename = editor_prompt("save as: %s");
456 if (filename == NULL) {
457 editor_set_status_message("save aborted");
458 return;
459 }
460 }
461
462 buf = editor_concatenate_rows(&len);
463
464 fd = open(filename, O_RDWR | O_CREAT, 0644);
465 if (fd != -1) {
466 if (ftruncate(fd, len) != -1) {
467 if (write(fd, buf, len) == len) {
468 close(fd);
469 free(buf);
470 E.filename = filename;
471 E.file_changed = 0;
472 editor_set_status_message("%d bytes written to disk", len);
473 return;
474 }
475 }
476 close(fd);
477 }
478 free(buf);
479 editor_set_status_message("error: can't save! I/O error %s",
480 strerror(errno));
481 }
482
483
484 /** FIND **/
485
486 /* reverse of strstr (3) */
487 char*
488 strrstr(char *haystack, char *needle,
489 size_t haystack_length, size_t needle_length)
490 {
491 char *cp;
492 for (cp = haystack + haystack_length - needle_length;
493 cp >= haystack;
494 cp--) {
495 if (strncmp(cp, needle, needle_length) == 0)
496 return cp;
497 }
498 return NULL;
499 }
500
501 /* find E.find_query from current cursor position moving in E.direction
502 * if opposite_direction = 0, and opposite E.direction if
503 * opposite_direction = 1 */
504 void
505 editor_find_next_occurence(int opposite_direction)
506 {
507 int y, y_inc, x_offset, query_len;
508 eRow *row;
509 char *match;
510
511 if (!E.find_query)
512 return;
513
514 if ((E.find_direction && !opposite_direction) ||
515 (!E.find_direction && opposite_direction))
516 y_inc = +1;
517 else
518 y_inc = -1;
519
520 x_offset = 0;
521 query_len = strlen(E.find_query);
522 /* from cursor until end of document */
523 for (y = E.cursor_y;
524 y != ((y_inc == +1) ? E.num_rows : -1);
525 y += y_inc) {
526 row = &E.row[y];
527
528 if (y == E.cursor_y)
529 x_offset = y_inc + E.cursor_x;
530 else
531 x_offset = 0;
532
533 if (y_inc == +1) {
534 match = strstr(row->chars + x_offset, E.find_query);
535 if (match)
536 break;
537 } else {
538 match = strrstr(row->chars, E.find_query,
539 (y == E.cursor_y) ? E.cursor_x : row->size,
540 query_len);
541 if (match)
542 break;
543 }
544 }
545
546 if (match) {
547 E.cursor_y = y;
548 E.cursor_x = match - row->chars;
549 /* E.row_offset = E.num_rows; */ /* put line to top of screen */
550
551 } else {
552 /* from other end of file until cursor */
553 for (y = (y_inc == +1) ? 0 : E.num_rows - 1;
554 y != E.cursor_y + y_inc;
555 y += y_inc) {
556 row = &E.row[y];
557 match = strstr(row->chars, E.find_query);
558 if (match)
559 break;
560 }
561 if (match) {
562 E.cursor_y = y;
563 E.cursor_x = editor_row_cursor_rx_to_x(row, match - row->chars);
564 }
565 }
566 }
567
568 /* direction = 1 is forward, 0 is backward */
569 void
570 editor_find(int direction)
571 {
572 char *query;
573
574 if (direction)
575 query = editor_prompt("/%s");
576 else
577 query = editor_prompt("?%s");
578 if (query == NULL)
579 return;
580
581 E.find_direction = direction;
582 free(E.find_query);
583 E.find_query = strdup(query);
584 editor_find_next_occurence(0);
585 free(query);
586 }
587
588
589 /** OUTPUT **/
590
591 void
592 editor_update_screen_size()
593 {
594 if (get_window_size(&E.screen_rows, &E.screen_columns) == -1)
595 die("get_window_size in main()");
596 E.screen_rows--;
597 if (E.show_status)
598 E.screen_rows--;
599 }
600
601 /* reallocate append buffer to hold string s */
602 void
603 ab_append(struct abuf *ab, const char *s, int len)
604 {
605 char *new;
606 new = realloc(ab->b, ab->len + len);
607
608 if (new == NULL)
609 return;
610 memcpy(&new[ab->len], s, len);
611 ab->b = new;
612 ab->len += len;
613 }
614
615 void
616 ab_free(struct abuf *ab) {
617 free(ab->b);
618 }
619
620 int
621 show_status_message()
622 {
623 if (E.show_status &&
624 strlen(E.status_msg) &&
625 time(NULL) - E.status_msg_time < status_message_timeout) {
626 return 1;
627 } else {
628 if (E.show_status) {
629 E.show_status = 0;
630 E.screen_rows++;
631 }
632 return 0;
633 }
634 }
635
636 /* draw status line consisting of left and right components.
637 * if the screen is narrower than both parts, just show the left status,
638 * truncated, if necessary. */
639 void
640 editor_draw_status(struct abuf *ab)
641 {
642 char left_status[512], right_status[512];
643 int left_status_len, right_status_len, padding;
644 int percentage;
645
646 left_status_len = 0;
647 switch (E.mode) {
648 case 1:
649 left_status_len = snprintf(left_status, sizeof(left_status),
650 "INSERT ");
651 break;
652 case 2:
653 left_status_len = snprintf(left_status, sizeof(left_status),
654 "VISUAL ");
655 break;
656 }
657 left_status_len += snprintf(left_status + left_status_len,
658 sizeof(left_status),
659 "%.20s %s",
660 E.filename ? E.filename : "[unnamed]",
661 E.file_changed ? "[+] " : "");
662
663 percentage = (int)((float)(E.cursor_y)/(E.num_rows-1)*100);
664 if (percentage < 0) percentage = 0;
665 right_status_len = snprintf(right_status, sizeof(right_status),
666 "%d%% < %d, %d",
667 percentage,
668 E.cursor_x+1, E.cursor_y+1);
669
670 if (left_status_len > E.screen_columns)
671 left_status_len = E.screen_columns;
672
673 padding = E.screen_columns - left_status_len - right_status_len;
674 if (padding < 0) {
675 if (left_status_len < E.screen_columns)
676 ab_append(ab, left_status, left_status_len);
677 else
678 ab_append(ab, left_status, E.screen_columns);
679 } else {
680 ab_append(ab, left_status, left_status_len);
681 while (padding--)
682 ab_append(ab, " ", 1);
683 ab_append(ab, right_status, right_status_len);
684 }
685
686 if (show_status_message()) {
687 ab_append(ab, "\x1b[m", 3);
688 ab_append(ab, "\r\n", 2);
689 }
690 }
691
692 /* draw status message if as long as it is specified and it is not too old */
693 void
694 editor_draw_status_message(struct abuf *ab)
695 {
696 int msglen;
697 ab_append(ab, "\x1b[K", 3);
698 msglen = strlen(E.status_msg);
699 if (msglen > E.screen_columns)
700 msglen = E.screen_columns;
701 if (show_status_message())
702 ab_append(ab, E.status_msg, msglen);
703 }
704
705 /* set vertical offset between file and screen when hitting the boundaries */
706 void
707 editor_scroll()
708 {
709 E.cursor_rx = 0;
710 if (E.cursor_y < E.num_rows)
711 E.cursor_rx = editor_row_cursor_x_to_rx(&E.row[E.cursor_y],
712 E.cursor_x);
713
714 if (E.cursor_y < E.row_offset)
715 E.row_offset = E.cursor_y;
716 else if (E.cursor_y >= E.row_offset + E.screen_rows)
717 E.row_offset = E.cursor_y - E.screen_rows + 1;
718
719 if (E.cursor_rx < E.column_offset)
720 E.column_offset = E.cursor_rx;
721 else if (E.cursor_rx >= E.column_offset + E.screen_columns)
722 E.column_offset = E.cursor_rx - E.screen_columns + 1;
723 }
724
725 /* draw editor screen.
726 * show tilde characters after EOF, and show status on last line */
727 void
728 editor_draw_rows(struct abuf *ab)
729 {
730 int y, j, len, file_row;
731 char *c;
732 unsigned char *hl;
733 char buf[16];
734 for (y = 0; y < E.screen_rows; ++y) {
735 file_row = y + E.row_offset;
736 if (file_row < E.num_rows) {
737 len = E.row[file_row].rsize - E.column_offset;
738 if (len < 0)
739 len = 0;
740 if (len > E.screen_columns)
741 len = E.screen_columns;
742 c = &E.row[file_row].rchars[E.column_offset];
743 hl = &E.row[file_row].hl[E.column_offset];
744 for (j = 0; j < len; ++j) {
745 if (hl[j] == ColorDigit) {
746 snprintf(buf, sizeof(buf),
747 "\x1b[3%dm", colors[ColorDigit][FG]);
748 ab_append(ab, buf, strlen(buf));
749 /* ab_append(ab, "\x1b[31m", 5); */
750 ab_append(ab, &c[j], 1);
751 ab_append(ab, "\x1b[39m", 5);
752 } else {
753 ab_append(ab, &c[j], 1);
754 }
755 }
756 } else {
757 ab_append(ab, "~", 1);
758 }
759
760 ab_append(ab, "\x1b[K", 3); /* erase to end of line */
761 ab_append(ab, "\r\n", 2);
762 }
763 }
764
765 /* fill output append buffer and write to terminal.
766 * move cursor to left before repaint, and to cursor position after.
767 * hide the cursor when repainting.
768 * VT100 escape sequences:
769 * - http://vt100.net/docs/vt100-ug/chapter3.html
770 * - http://vt100.net/docs/vt100-ug/chapter3.html#CUP */
771 void
772 editor_refresh_screen()
773 {
774 char buf[32];
775 struct abuf ab = ABUF_INIT;
776
777 show_status_message();
778 editor_scroll();
779
780 ab_append(&ab, "\x1b[?25l", 6); /* hide cursor */
781 ab_append(&ab, "\x1b[H", 3); /* cursor to home */
782
783 editor_draw_rows(&ab);
784 editor_draw_status(&ab);
785 editor_draw_status_message(&ab);
786
787 snprintf(buf, sizeof(buf), "\x1b[%d;%dH",
788 (E.cursor_y - E.row_offset)+1,
789 (E.cursor_rx - E.column_offset)+1);
790 ab_append(&ab, buf, strlen(buf));
791
792 ab_append(&ab, "\x1b[?25h", 6); /* show cursor */
793
794 write(STDOUT_FILENO, ab.b, ab.len);
795 ab_free(&ab);
796 }
797
798 void
799 editor_place_cursor(int cx, int cy)
800 {
801 char buf[128];
802 snprintf(buf, sizeof(buf), "\x1b[%d;%dH", cy, cx);
803 write(STDOUT_FILENO, buf, strlen(buf));
804 }
805
806 /* set status message text, uses same format as printf */
807 void
808 editor_set_status_message(const char *fmt, ...)
809 {
810 va_list ap;
811 va_start(ap, fmt);
812 vsnprintf(E.status_msg, sizeof(E.status_msg), fmt, ap);
813 va_end(ap);
814 E.status_msg_time = time(NULL);
815
816 if (!E.show_status) {
817 E.screen_rows--;
818 E.show_status = 1;
819 }
820 }
821
822
823 /** INPUT **/
824
825 /* prompt is expected to be a format string containing a %s */
826 char*
827 editor_prompt(char *prompt)
828 {
829 size_t bufsize, buflen;
830 char *buf;
831 int c;
832
833 bufsize = 128;
834 buflen = 0;
835 buf = malloc(bufsize);
836 buf[0] = '\0';
837
838 while (1) {
839 editor_update_screen_size();
840 editor_set_status_message(prompt, buf);
841 editor_refresh_screen();
842 editor_place_cursor(strlen(prompt) - 1 + strlen(buf),
843 E.screen_rows + 2);
844
845 c = editor_read_key();
846 if (c == CTRL_KEY('h') || c == 127) { /* detect backspace */
847 if (buflen != 0)
848 buf[--buflen] = '\0';
849 } else if (c == '\x1b' || (c == '\r' && !buflen)) { /* detect escape */
850 editor_set_status_message("");
851 free(buf);
852 return NULL;
853 } else if (c == '\r') {
854 if (buflen != 0) {
855 editor_set_status_message("");
856 return buf;
857 }
858 } else if (!iscntrl(c) && c < 128) {
859 if (buflen >= bufsize - 1) {
860 bufsize *= 2;
861 buf = realloc(buf, bufsize);
862 }
863 buf[buflen++] = c;
864 buf[buflen] = '\0';
865 }
866 }
867 }
868
869 /* move cursor according to screen, file, and line limits */
870 void
871 editor_move_cursor(char key)
872 {
873 int row_len;
874 eRow *row;
875
876 switch(key) {
877 case 'h':
878 if (E.cursor_x != 0) {
879 E.cursor_x--;
880 } else if (E.cursor_y > 0) {
881 E.cursor_y--;
882 E.cursor_x = E.row[E.cursor_y].size;
883 }
884 break;
885 case 'j':
886 if (E.cursor_y < E.num_rows - 1)
887 E.cursor_y++;
888 break;
889 case 'k':
890 if (E.cursor_y != 0)
891 E.cursor_y--;
892 break;
893 case 'l':
894 row = (E.cursor_y >= E.num_rows) ? NULL : &E.row[E.cursor_y];
895 if (row && E.cursor_x < row->size - 1) {
896 E.cursor_x++;
897 } else if (row && E.cursor_x == row->size &&
898 E.cursor_y < E.num_rows - 1) {
899 E.cursor_y++;
900 E.cursor_x = 0;
901 }
902 break;
903 }
904
905 /* do not allow navigation past EOL by vertical navigation */
906 row = (E.cursor_y >= E.num_rows) ? NULL : &E.row[E.cursor_y];
907 row_len = row ? row->size : 0;
908 if (E.cursor_x > row_len)
909 E.cursor_x = row_len;
910 }
911
912 void
913 editor_read_command()
914 {
915 char* query;
916 query = editor_prompt(":%s");
917
918 editor_set_status_message("you typed: '%s'", query);
919 free(query);
920 }
921
922 /* first word in query is the command, the remaining are interpreted as args */
923 void
924 editor_command(char *query)
925 {
926 switch(query) {
927
928 }
929 }
930
931
932 void
933 editor_process_keypress()
934 {
935 char c;
936 int i;
937
938 c = editor_read_key();
939
940 if (E.mode == 0) { /* normal mode */
941 switch (c) {
942 case 'i':
943 E.mode = 1;
944 break;
945 case 'a':
946 editor_move_cursor('l');
947 E.mode = 1;
948 break;
949
950 case 'h':
951 case 'j':
952 case 'k':
953 case 'l':
954 editor_move_cursor(c);
955 break;
956
957 case ':':
958 editor_read_command();
959 break;
960
961 case LEADER:
962 c = editor_read_key();
963 switch (c) {
964 case 'w':
965 file_save(E.filename);
966 break;
967 case 'q':
968 if (E.file_changed) {
969 editor_set_status_message(
970 "error: file has unsaved changes! "
971 "Press :q! to confirm quit");
972 break;
973 } else {
974 /* clear screen */
975 write(STDOUT_FILENO, "\x1b[2J", 4);
976 write(STDOUT_FILENO, "\x1b[H", 3);
977 exit(0);
978 break;
979 }
980 }
981 break;
982
983 case CTRL_KEY('f'):
984 i = E.screen_rows;
985 while (i--)
986 editor_move_cursor('j');
987 break;
988 case CTRL_KEY('b'):
989 i = E.screen_rows;
990 while (i--)
991 editor_move_cursor('k');
992 break;
993
994 case CTRL_KEY('d'):
995 i = E.screen_rows/2;
996 while (i--)
997 editor_move_cursor('j');
998 break;
999 case CTRL_KEY('u'):
1000 i = E.screen_rows/2;
1001 while (i--)
1002 editor_move_cursor('k');
1003 break;
1004
1005 case '0':
1006 E.cursor_x = 0;
1007 break;
1008 case '$':
1009 if (E.cursor_y < E.num_rows)
1010 E.cursor_x = E.row[E.cursor_y].size - 1;
1011 break;
1012
1013 case 'g':
1014 c = editor_read_key();
1015 if (c == 'g') {
1016 E.cursor_x = 0;
1017 E.cursor_y = 0;
1018 }
1019 break;
1020 case 'G':
1021 E.cursor_x = 0;
1022 E.cursor_y = E.num_rows - 1;
1023 break;
1024
1025 case 'x':
1026 editor_delete_char_right();
1027 break;
1028 case 'd':
1029 c = editor_read_key();
1030 if (c == 'd') {
1031 editor_row_delete(E.cursor_y);
1032 editor_move_cursor('h');
1033 }
1034 break;
1035
1036 case 'o':
1037 if (E.cursor_y < E.num_rows)
1038 E.cursor_x = E.row[E.cursor_y].size;
1039 editor_insert_new_line();
1040 E.mode = 1;
1041 break;
1042 case 'O':
1043 E.cursor_x = 0;
1044 editor_insert_new_line();
1045 editor_move_cursor('k');
1046 E.mode = 1;
1047 break;
1048
1049 case 'I':
1050 E.cursor_x = 0;
1051 E.mode = 1;
1052 break;
1053 case 'A':
1054 if (E.cursor_y < E.num_rows)
1055 E.cursor_x = E.row[E.cursor_y].size;
1056 E.mode = 1;
1057 break;
1058
1059 case '/':
1060 editor_find(1);
1061 break;
1062 case '?':
1063 editor_find(0);
1064 break;
1065 case 'n':
1066 editor_find_next_occurence(0);
1067 break;
1068 case 'N':
1069 editor_find_next_occurence(1);
1070 break;
1071 }
1072 } else if (E.mode == 1) { /* insert mode */
1073 switch (c) {
1074 case CTRL_KEY('c'):
1075 case '\x1b': /* escape */
1076 E.mode = 0;
1077 break;
1078
1079 case CTRL_KEY('\r'): /* enter */
1080 editor_insert_new_line();
1081 break;
1082
1083 case 127: /* backspace */
1084 case CTRL_KEY('h'):
1085 editor_delete_char_left();
1086 break;
1087
1088 case CTRL_KEY('l'):
1089 break;
1090
1091 default:
1092 editor_insert_char(c);
1093 break;
1094 }
1095 }
1096 }
1097
1098
1099 /** INIT **/
1100
1101 void
1102 deinit_editor() {
1103 int i;
1104 free(E.filename);
1105 free(E.find_query);
1106 for (i=0; i<E.num_rows; ++i)
1107 editor_row_free(&E.row[i]);
1108 free(E.row);
1109 }
1110
1111 /* set editor state variables, make room for status */
1112 void
1113 init_editor()
1114 {
1115 E.cursor_x = 0;
1116 E.cursor_y = 0;
1117 E.cursor_rx = 0;
1118 E.mode = 0;
1119 E.num_rows = 0;
1120 atexit(deinit_editor);
1121 E.row = NULL;
1122 E.row_offset = 0;
1123 E.column_offset = 0;
1124 E.filename = NULL;
1125 E.status_msg[0] = '\0';
1126 E.status_msg_time = 0;
1127 E.show_status = 0;
1128 E.file_changed = 0;
1129 E.find_query = NULL;
1130 }
1131
1132 void
1133 usage()
1134 {
1135 printf("%s: %s [OPTIONS] [FILES]\n"
1136 "\nOptional arguments:\n"
1137 " -h show this message\n"
1138 " -v show version information\n",
1139 __func__, PROGNAME);
1140 }
1141
1142 void
1143 version()
1144 {
1145 printf("%s version %s\n"
1146 "Licensed under the GNU Public License, v3+\n"
1147 "written by Anders Damsgaard, anders@adamsgaard.dk\n"
1148 "https://gitlab.com/admesg/ve\n",
1149 PROGNAME, VERSION);
1150 }
1151
1152 /** MAIN **/
1153 int
1154 main(int argc, char *argv[])
1155 {
1156 ARGBEGIN {
1157 case 'h':
1158 usage();
1159 return 0;
1160 case 'v':
1161 version();
1162 return 0;
1163 default:
1164 usage();
1165 } ARGEND;
1166
1167 enable_raw_mode();
1168 init_editor();
1169
1170 editor_set_status_message("%s %s", PROGNAME, VERSION);
1171
1172 if (argv[0])
1173 file_open(argv[0]);
1174
1175 while (1) {
1176 editor_update_screen_size();
1177 editor_refresh_screen();
1178 editor_process_keypress();
1179 }
1180 return 0;
1181 }