| tve.c - ve - a minimal text editor (work in progress) | |
| git clone git://src.adamsgaard.dk/ve | |
| Log | |
| Files | |
| Refs | |
| README | |
| 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 p… | |
| 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 == … | |
| 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 r… | |
| 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 =… | |
| 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 c… | |
| 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 writ… | |
| 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_que… | |
| 535 if (match) | |
| 536 break; | |
| 537 } else { | |
| 538 match = strrstr(row->chars, E.find_query, | |
| 539 (y == E.cursor_y) ? E.cursor_x :… | |
| 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 s… | |
| 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, matc… | |
| 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(l… | |
| 650 "INSERT "); | |
| 651 break; | |
| 652 case 2: | |
| 653 left_status_len = snprintf(left_status, sizeof(l… | |
| 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 o… | |
| 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 boundari… | |
| 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_… | |
| 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[Col… | |
| 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 backspac… | |
| 847 if (buflen != 0) | |
| 848 buf[--buflen] = '\0'; | |
| 849 } else if (c == '\x1b' || (c == '\r' && !buflen)) { /* d… | |
| 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… | |
| 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 … | |
| 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_statu… | |
| 970 "error: … | |
| 971 "Press :… | |
| 972 break; | |
| 973 } else { | |
| 974 /* clear screen … | |
| 975 write(STDOUT_FIL… | |
| 976 write(STDOUT_FIL… | |
| 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].s… | |
| 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].s… | |
| 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].s… | |
| 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, [email protected]\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 } |