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