From a19a251bf6aa070d2de31298bcc36f369c300b13 Mon Sep 17 00:00:00 2001 From: hyunsooda Date: Sun, 14 Oct 2018 00:50:14 +0900 Subject: [PATCH 01/22] implementation of multiline --- kilo.c | 56 ++++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 42 insertions(+), 14 deletions(-) diff --git a/kilo.c b/kilo.c index 9490a77..88de95a 100644 --- a/kilo.c +++ b/kilo.c @@ -360,7 +360,8 @@ int editorRowHasOpenComment(erow *row) { if (row->hl && row->rsize && row->hl[row->rsize-1] == HL_MLCOMMENT && (row->rsize < 2 || (row->render[row->rsize-2] != '*' || row->render[row->rsize-1] != '/'))) return 1; - return 0; + + return 0; } /* Set every byte of row->hl (that corresponds to every character in the line) @@ -372,13 +373,15 @@ void editorUpdateSyntax(erow *row) { if (E.syntax == NULL) return; /* No syntax, everything is HL_NORMAL. */ int i, prev_sep, in_string, in_comment; - char *p; + int rowidx_backup; + char *p; char **keywords = E.syntax->keywords; char *scs = E.syntax->singleline_comment_start; char *mcs = E.syntax->multiline_comment_start; char *mce = E.syntax->multiline_comment_end; - - /* Point to the first non-space char. */ + char* temp; + + /* Point to the first non-space char. */ p = row->render; i = 0; /* Current char offset */ while(*p && isspace(*p)) { @@ -389,12 +392,37 @@ void editorUpdateSyntax(erow *row) { in_string = 0; /* Are we inside "" or '' ? */ in_comment = 0; /* Are we inside multi-line comment? */ - /* If the previous line has an open comment, this line starts - * with an open comment state. */ - if (row->idx > 0 && editorRowHasOpenComment(&E.row[row->idx-1])) - in_comment = 1; - - while(*p) { + rowidx_backup = row->idx; + temp = p; + while(row->idx > 0 && *temp != mce[0] && *(temp+1) != mce[1]) { + if(editorRowHasOpenComment(&E.row[row->idx-1])) + in_comment = 1; + row->idx--; + temp = E.row[row->idx].render; + while(*temp && isspace(*temp)) + temp++; + } + row->idx = rowidx_backup; + + rowidx_backup = row->idx; + temp = p; + if(!in_comment && *(temp) == mce[0] && *(temp+1) == mce[1]) { + while(row->idx > 0) { + while(*temp && isspace(*temp)) + temp++; + if(*temp != mcs[0] && *(temp+1) != mcs[1]) + in_comment = 0; + else { + in_comment = 1; + break; + } + row->idx--; + temp = E.row[row->idx].render; + } + } + row->idx = rowidx_backup; + + while(*p) { /* Handle // comments. */ if (prev_sep && *p == scs[0] && *(p+1) == scs[1]) { /* From here to end is a comment */ @@ -413,16 +441,15 @@ void editorUpdateSyntax(erow *row) { continue; } else { prev_sep = 0; - p++; i++; + p+=1; i+=1; continue; } } else if (*p == mcs[0] && *(p+1) == mcs[1]) { row->hl[i] = HL_MLCOMMENT; row->hl[i+1] = HL_MLCOMMENT; p += 2; i += 2; - in_comment = 1; prev_sep = 0; - continue; + continue; } /* Handle "" and '' */ @@ -603,7 +630,7 @@ void editorDelRow(int at) { row = E.row+at; editorFreeRow(row); memmove(E.row+at,E.row+at+1,sizeof(E.row[0])*(E.numrows-at-1)); - for (int j = at; j < E.numrows-1; j++) E.row[j].idx++; + for (int j = at; j < E.numrows-1; j++) E.row[j].idx--; E.numrows--; E.dirty++; } @@ -998,6 +1025,7 @@ void editorFind(int fd) { #define FIND_RESTORE_HL do { \ if (saved_hl) { \ memcpy(E.row[saved_hl_line].hl,saved_hl, E.row[saved_hl_line].rsize); \ + free(saved_hl); \ saved_hl = NULL; \ } \ } while (0) From ff291331dc1792a401cfa6484b0b2ab1f4b86ab9 Mon Sep 17 00:00:00 2001 From: hyunsooda Date: Tue, 16 Oct 2018 01:40:40 +0900 Subject: [PATCH 02/22] Add move to cursor to start and end --- .kilo.c.swp | Bin 0 -> 16384 bytes kilo.c | 23 ++++++++++++++++++++--- 2 files changed, 20 insertions(+), 3 deletions(-) create mode 100644 .kilo.c.swp diff --git a/.kilo.c.swp b/.kilo.c.swp new file mode 100644 index 0000000000000000000000000000000000000000..342ab4533af0f864649da1a704bb6e32ccae74d9 GIT binary patch literal 16384 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 MjPY(4H?h$8A1GjaLI3~& literal 0 HcmV?d00001 diff --git a/kilo.c b/kilo.c index 88de95a..409489a 100644 --- a/kilo.c +++ b/kilo.c @@ -50,6 +50,7 @@ #include #include #include +#include /* Syntax highlight types */ #define HL_NORMAL 0 @@ -113,7 +114,9 @@ enum KEY_ACTION{ CTRL_C = 3, /* Ctrl-c */ CTRL_D = 4, /* Ctrl-d */ CTRL_F = 6, /* Ctrl-f */ - CTRL_H = 8, /* Ctrl-h */ + CTRL_G = 7, /* Ctrl-g */ + CTRL_T = 20, /* Ctrl-T */ + CTRL_H = 8, /* Ctrl-h */ TAB = 9, /* Tab */ CTRL_L = 12, /* Ctrl+l */ ENTER = 13, /* Enter */ @@ -181,6 +184,14 @@ struct editorSyntax HLDB[] = { } }; +void moveToEnd() { + E.cy = E.numrows - 1; +} + +void moveToFirst() { + E.cy = 0; +} + #define HLDB_ENTRIES (sizeof(HLDB)/sizeof(HLDB[0])) /* ======================= Low level terminal handling ====================== */ @@ -537,7 +548,7 @@ int editorSyntaxToColor(int hl) { case HL_KEYWORD1: return 33; /* yellow */ case HL_KEYWORD2: return 32; /* green */ case HL_STRING: return 35; /* magenta */ - case HL_NUMBER: return 31; /* red */ + case HL_NUMBER: return 37; /* white */ case HL_MATCH: return 34; /* blu */ default: return 37; /* white */ } @@ -1202,7 +1213,13 @@ void editorProcessKeypress(int fd) { /* We ignore ctrl-c, it can't be so simple to lose the changes * to the edited file. */ break; - case CTRL_Q: /* Ctrl-q */ + case CTRL_G: + moveToEnd(); + break; + case CTRL_T: + moveToFirst(); + break; + case CTRL_Q: /* Ctrl-q */ /* Quit if the file was already saved. */ if (E.dirty && quit_times) { editorSetStatusMessage("WARNING!!! File has unsaved changes. " From 4e5c795100fbc1a2704b460a38f600fbc7e8ab2c Mon Sep 17 00:00:00 2001 From: hyunsooda Date: Tue, 16 Oct 2018 01:54:02 +0900 Subject: [PATCH 03/22] clear screen when program is exited --- kilo.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/kilo.c b/kilo.c index 409489a..40dcdeb 100644 --- a/kilo.c +++ b/kilo.c @@ -1227,7 +1227,13 @@ void editorProcessKeypress(int fd) { quit_times--; return; } - exit(0); + struct abuf temp = ABUF_INIT; + abAppend(&temp, "\x1b[2J", 4); + abAppend(&temp, "\x1b[H", 3); + + write(STDOUT_FILENO, temp.b, temp.len); + abFree(&temp); + exit(0); break; case CTRL_S: /* Ctrl-s */ editorSave(); From c421e73112b338f152f2d1fd2c571a48ddf1a30b Mon Sep 17 00:00:00 2001 From: hyunsooda Date: Tue, 16 Oct 2018 13:37:50 +0900 Subject: [PATCH 04/22] add move to end of current line --- kilo.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/kilo.c b/kilo.c index 40dcdeb..0c1edbd 100644 --- a/kilo.c +++ b/kilo.c @@ -192,6 +192,10 @@ void moveToFirst() { E.cy = 0; } +void moveToLineEnd() { + E.cx = E.row[E.cy].size; +} + #define HLDB_ENTRIES (sizeof(HLDB)/sizeof(HLDB[0])) /* ======================= Low level terminal handling ====================== */ @@ -1219,6 +1223,9 @@ void editorProcessKeypress(int fd) { case CTRL_T: moveToFirst(); break; + case CTRL_D: + moveToLineEnd(); + break; case CTRL_Q: /* Ctrl-q */ /* Quit if the file was already saved. */ if (E.dirty && quit_times) { From fd088091ccdf5ce31ccff1d850e248160bce7d15 Mon Sep 17 00:00:00 2001 From: hyunsooda Date: Tue, 16 Oct 2018 19:50:31 +0900 Subject: [PATCH 05/22] Fix function move to first line and last line --- .kilo.c.swo | Bin 0 -> 61440 bytes kilo.c | 37 +- kilo.c~ | 1346 +++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 1380 insertions(+), 3 deletions(-) create mode 100644 .kilo.c.swo create mode 100644 kilo.c~ diff --git a/.kilo.c.swo b/.kilo.c.swo new file mode 100644 index 0000000000000000000000000000000000000000..a2c1be73dec930cd2c50c77e19eb37fdf1a89612 GIT binary patch literal 61440 zcmeIb37n)?UGH5;5GSk`K37EH<(Y;|Z`D0pGD&7K6MDL9W}4~gp7fGQn1r^vy1KhE z)m5FU>gnlhiUhf@paBdZvLm8^M7?lDUqD4g*<^JK$}1qsd)=;VMo=#I`}?1Bo~5dK z5)eP{y`N84@|&)Dp7WgLfBxs3|8~x^u;=>mQ_)q!dvbgppUeIE`uoqme&F%Bs~?@q zt!%E>n)SNwT=e794P9SpwAPE&qxB2jli%eN?j9>Q&$a4nyU$gsb^B+y)cuL=tJ@UV zroeZF0_&}%p{pJ|Y33K0-thY1Sroc7@wkfbpfo%$GQ(&6{+Z5QQ zz%~W`&!9kS?J>C@p{n2GtGnp`KCJEUCI0sr|F_(B{}KLo(BLnE1sYsf1f;ey}$prZTJ1*ClPK;#4xR_D0S}<)-v^e!Q^22L7`O)<1K&bP z{xwhow}1+m04Kqf;OU?TJO+Fnh5K*7r@(K4w}GDl&jq)DS@2Ad2TulL6QF? zcn|mqa3dH6L*U8a5#WA|0e=SG30@5D0h^!%CP5!~IQT3^k57P)fV%Ei)3LJwu$ppbH7*mI0X6%Dv+p^mrR<-qNHG&;hGx<~&FQiJ3t6G6P7epvkVyRLs z=cC)`^`V7g)AMf|e~2Yu4yrM`QfYdwS#PKXYmIUf5;mh^)TA=ia#XF<;ub1%jm08q z#hs+E3g+%iSaPXaTyDCbM&8x+YOA75<~`fY)17Dc-+a*|%~r9|$|iEv$zeQ*i=4NwutFl_|yjrQ3Ixe)*?TIsyhk(9m_YCjt$>nSTV~IN!&7K&aiDssb&7K*VF65)a^@Yi3{8%(HcK!HFVa%l{M5m`_ zX2y?>PmIsrs7o_*@bK8ksgdJ_Ve*Rl#wSN7=EmUpXtHpeaZ;EZEksk((bVjT!t@z( z>W@aI$7hsad@>rDyfHd6LQZqDp@6xWf-)lc)U*{bF+SqT%SUHU6qJpg9G#k+ogNvT z%|{vsqq9*e8O6;`P0uQuv60!4yvjd4J#}n+cIJ@2ADyH8!psc$%oe7n=T6T;lK$w# z)EOE*9gU7qhcW6#qpga{Xkz@-_$*gvr}D~cbZ)v}W5;N4bY^yZc5b#19iN&Sn^6%n zh3V_ZM+=lU0m0NZ^2{rvnbU>Q@sSA#d_&>X>4}l)8?An{XQpu79K(^Do}x?}F*)

U5ltK+un~~`B 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 E.screenrows) + factor = E.numrows / E.screenrows; + + if(factor > 0) { + if(E.cy != E.screenrows) { + E.cy = E.screenrows - 1; + E.cx = 0; + } + editorRefreshScreen(); + if(++E.rowoff + E.cy <= E.numrows-1) { + editorRefreshScreen(); + moveToEnd(); + } else + E.rowoff--; + } else { + E.cy = E.numrows - 1; + E.cx = 0; + } } void moveToFirst() { - E.cy = 0; + if(E.rowoff > 0) { + E.cy = 0; + E.cx = 0; + editorRefreshScreen(); + E.rowoff--; + editorRefreshScreen(); + moveToFirst(); + } else { + E.cy = 0; + E.cx = 0; + } } void moveToLineEnd() { - E.cx = E.row[E.cy].size; + E.cx = E.row[E.rowoff + E.cy].size; } #define HLDB_ENTRIES (sizeof(HLDB)/sizeof(HLDB[0])) diff --git a/kilo.c~ b/kilo.c~ new file mode 100644 index 0000000..bfffc00 --- /dev/null +++ b/kilo.c~ @@ -0,0 +1,1346 @@ +/* Kilo -- A very simple editor in less than 1-kilo lines of code (as counted + * by "cloc"). Does not depend on libcurses, directly emits VT100 + * escapes on the terminal. + * + * ----------------------------------------------------------------------- + * + * Copyright (C) 2016 Salvatore Sanfilippo + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#define KILO_VERSION "0.0.1" + +#define _BSD_SOURCE +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Syntax highlight types */ +#define HL_NORMAL 0 +#define HL_NONPRINT 1 +#define HL_COMMENT 2 /* Single line comment. */ +#define HL_MLCOMMENT 3 /* Multi-line comment. */ +#define HL_KEYWORD1 4 +#define HL_KEYWORD2 5 +#define HL_STRING 6 +#define HL_NUMBER 7 +#define HL_MATCH 8 /* Search match. */ + +#define HL_HIGHLIGHT_STRINGS (1<<0) +#define HL_HIGHLIGHT_NUMBERS (1<<1) + +struct editorSyntax { + char **filematch; + char **keywords; + char singleline_comment_start[2]; + char multiline_comment_start[3]; + char multiline_comment_end[3]; + int flags; +}; + +/* This structure represents a single line of the file we are editing. */ +typedef struct erow { + int idx; /* Row index in the file, zero-based. */ + int size; /* Size of the row, excluding the null term. */ + int rsize; /* Size of the rendered row. */ + char *chars; /* Row content. */ + char *render; /* Row content "rendered" for screen (for TABs). */ + unsigned char *hl; /* Syntax highlight type for each character in render.*/ + int hl_oc; /* Row had open comment at end in last syntax highlight + check. */ +} erow; + +typedef struct hlcolor { + int r,g,b; +} hlcolor; + +struct editorConfig { + int cx,cy; /* Cursor x and y position in characters */ + int rowoff; /* Offset of row displayed. */ + int coloff; /* Offset of column displayed. */ + int screenrows; /* Number of rows that we can show */ + int screencols; /* Number of cols that we can show */ + int numrows; /* Number of rows */ + int rawmode; /* Is terminal raw mode enabled? */ + erow *row; /* Rows */ + int dirty; /* File modified but not saved. */ + char *filename; /* Currently open filename */ + char statusmsg[80]; + time_t statusmsg_time; + struct editorSyntax *syntax; /* Current syntax highlight, or NULL. */ +}; + +static struct editorConfig E; + +enum KEY_ACTION{ + KEY_NULL = 0, /* NULL */ + CTRL_C = 3, /* Ctrl-c */ + CTRL_D = 4, /* Ctrl-d */ + CTRL_F = 6, /* Ctrl-f */ + CTRL_G = 7, /* Ctrl-g */ + CTRL_T = 20, /* Ctrl-T */ + CTRL_H = 8, /* Ctrl-h */ + TAB = 9, /* Tab */ + CTRL_L = 12, /* Ctrl+l */ + ENTER = 13, /* Enter */ + CTRL_Q = 17, /* Ctrl-q */ + CTRL_S = 19, /* Ctrl-s */ + CTRL_U = 21, /* Ctrl-u */ + ESC = 27, /* Escape */ + BACKSPACE = 127, /* Backspace */ + /* The following are just soft codes, not really reported by the + * terminal directly. */ + ARROW_LEFT = 1000, + ARROW_RIGHT, + ARROW_UP, + ARROW_DOWN, + DEL_KEY, + HOME_KEY, + END_KEY, + PAGE_UP, + PAGE_DOWN +}; + +void editorSetStatusMessage(const char *fmt, ...); + +/* =========================== Syntax highlights DB ========================= + * + * In order to add a new syntax, define two arrays with a list of file name + * matches and keywords. The file name matches are used in order to match + * a given syntax with a given file name: if a match pattern starts with a + * dot, it is matched as the last past of the filename, for example ".c". + * Otherwise the pattern is just searched inside the filenme, like "Makefile"). + * + * The list of keywords to highlight is just a list of words, however if they + * a trailing '|' character is added at the end, they are highlighted in + * a different color, so that you can have two different sets of keywords. + * + * Finally add a stanza in the HLDB global variable with two two arrays + * of strings, and a set of flags in order to enable highlighting of + * comments and numbers. + * + * The characters for single and multi line comments must be exactly two + * and must be provided as well (see the C language example). + * + * There is no support to highlight patterns currently. */ + +/* C / C++ */ +char *C_HL_extensions[] = {".c",".cpp",NULL}; +char *C_HL_keywords[] = { + /* A few C / C++ keywords */ + "switch","if","while","for","break","continue","return","else", + "struct","union","typedef","static","enum","class", + /* C types */ + "int|","long|","double|","float|","char|","unsigned|","signed|", + "void|",NULL +}; + +/* Here we define an array of syntax highlights by extensions, keywords, + * comments delimiters and flags. */ +struct editorSyntax HLDB[] = { + { + /* C / C++ */ + C_HL_extensions, + C_HL_keywords, + "//","/*","*/", + HL_HIGHLIGHT_STRINGS | HL_HIGHLIGHT_NUMBERS + } +}; + +void editorRefreshScreen(); + +void moveToEnd() { + int factor = 0; + + if(E.numrows > E.screencols) + factor = E.numrows / E.screenrows; + + if(factor) { + if (E.cy == E.screencols-2) + E.cy = E.numrows - 1; + E.coloff++; + editorRefreshScreen(); + moveToEnd(); + } + E.cy = E.numrows - 3 - E.rowoff; + + +} + +void moveToFirst() { + E.cy = 0; +} + +void moveToLineEnd() { + E.cx = E.row[E.cy].size; +} + +#define HLDB_ENTRIES (sizeof(HLDB)/sizeof(HLDB[0])) + +/* ======================= Low level terminal handling ====================== */ + +static struct termios orig_termios; /* In order to restore at exit.*/ + +void disableRawMode(int fd) { + /* Don't even check the return value as it's too late. */ + if (E.rawmode) { + tcsetattr(fd,TCSAFLUSH,&orig_termios); + E.rawmode = 0; + } +} + +/* Called at exit to avoid remaining in raw mode. */ +void editorAtExit(void) { + disableRawMode(STDIN_FILENO); +} + +/* Raw mode: 1960 magic shit. */ +int enableRawMode(int fd) { + struct termios raw; + + if (E.rawmode) return 0; /* Already enabled. */ + if (!isatty(STDIN_FILENO)) goto fatal; + atexit(editorAtExit); + if (tcgetattr(fd,&orig_termios) == -1) goto fatal; + + raw = orig_termios; /* modify the original mode */ + /* input modes: no break, no CR to NL, no parity check, no strip char, + * no start/stop output control. */ + raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON); + /* output modes - disable post processing */ + raw.c_oflag &= ~(OPOST); + /* control modes - set 8 bit chars */ + raw.c_cflag |= (CS8); + /* local modes - choing off, canonical off, no extended functions, + * no signal chars (^Z,^C) */ + raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG); + /* control chars - set return condition: min number of bytes and timer. */ + raw.c_cc[VMIN] = 0; /* Return each byte, or zero for timeout. */ + raw.c_cc[VTIME] = 1; /* 100 ms timeout (unit is tens of second). */ + + /* put terminal in raw mode after flushing */ + if (tcsetattr(fd,TCSAFLUSH,&raw) < 0) goto fatal; + E.rawmode = 1; + return 0; + +fatal: + errno = ENOTTY; + return -1; +} + +/* Read a key from the terminal put in raw mode, trying to handle + * escape sequences. */ +int editorReadKey(int fd) { + int nread; + char c, seq[3]; + while ((nread = read(fd,&c,1)) == 0); + if (nread == -1) exit(1); + + while(1) { + switch(c) { + case ESC: /* escape sequence */ + /* If this is just an ESC, we'll timeout here. */ + if (read(fd,seq,1) == 0) return ESC; + if (read(fd,seq+1,1) == 0) return ESC; + + /* ESC [ sequences. */ + if (seq[0] == '[') { + if (seq[1] >= '0' && seq[1] <= '9') { + /* Extended escape, read additional byte. */ + if (read(fd,seq+2,1) == 0) return ESC; + if (seq[2] == '~') { + switch(seq[1]) { + case '3': return DEL_KEY; + case '5': return PAGE_UP; + case '6': return PAGE_DOWN; + } + } + } else { + switch(seq[1]) { + case 'A': return ARROW_UP; + case 'B': return ARROW_DOWN; + case 'C': return ARROW_RIGHT; + case 'D': return ARROW_LEFT; + case 'H': return HOME_KEY; + case 'F': return END_KEY; + } + } + } + + /* ESC O sequences. */ + else if (seq[0] == 'O') { + switch(seq[1]) { + case 'H': return HOME_KEY; + case 'F': return END_KEY; + } + } + break; + default: + return c; + } + } +} + +/* Use the ESC [6n escape sequence to query the horizontal cursor position + * and return it. On error -1 is returned, on success the position of the + * cursor is stored at *rows and *cols and 0 is returned. */ +int getCursorPosition(int ifd, int ofd, int *rows, int *cols) { + char buf[32]; + unsigned int i = 0; + + /* Report cursor location */ + if (write(ofd, "\x1b[6n", 4) != 4) return -1; + + /* Read the response: ESC [ rows ; cols R */ + while (i < sizeof(buf)-1) { + if (read(ifd,buf+i,1) != 1) break; + if (buf[i] == 'R') break; + i++; + } + buf[i] = '\0'; + + /* Parse it. */ + if (buf[0] != ESC || buf[1] != '[') return -1; + if (sscanf(buf+2,"%d;%d",rows,cols) != 2) return -1; + return 0; +} + +/* Try to get the number of columns in the current terminal. If the ioctl() + * call fails the function will try to query the terminal itself. + * Returns 0 on success, -1 on error. */ +int getWindowSize(int ifd, int ofd, int *rows, int *cols) { + struct winsize ws; + + if (ioctl(1, TIOCGWINSZ, &ws) == -1 || ws.ws_col == 0) { + /* ioctl() failed. Try to query the terminal itself. */ + int orig_row, orig_col, retval; + + /* Get the initial position so we can restore it later. */ + retval = getCursorPosition(ifd,ofd,&orig_row,&orig_col); + if (retval == -1) goto failed; + + /* Go to right/bottom margin and get position. */ + if (write(ofd,"\x1b[999C\x1b[999B",12) != 12) goto failed; + retval = getCursorPosition(ifd,ofd,rows,cols); + if (retval == -1) goto failed; + + /* Restore position. */ + char seq[32]; + snprintf(seq,32,"\x1b[%d;%dH",orig_row,orig_col); + if (write(ofd,seq,strlen(seq)) == -1) { + /* Can't recover... */ + } + return 0; + } else { + *cols = ws.ws_col; + *rows = ws.ws_row; + return 0; + } + +failed: + return -1; +} + +/* ====================== Syntax highlight color scheme ==================== */ + +int is_separator(int c) { + return c == '\0' || isspace(c) || strchr(",.()+-/*=~%[];",c) != NULL; +} + +/* Return true if the specified row last char is part of a multi line comment + * that starts at this row or at one before, and does not end at the end + * of the row but spawns to the next row. */ +int editorRowHasOpenComment(erow *row) { + if (row->hl && row->rsize && row->hl[row->rsize-1] == HL_MLCOMMENT && + (row->rsize < 2 || (row->render[row->rsize-2] != '*' || + row->render[row->rsize-1] != '/'))) return 1; + + return 0; +} + +/* Set every byte of row->hl (that corresponds to every character in the line) + * to the right syntax highlight type (HL_* defines). */ +void editorUpdateSyntax(erow *row) { + row->hl = realloc(row->hl,row->rsize); + memset(row->hl,HL_NORMAL,row->rsize); + + if (E.syntax == NULL) return; /* No syntax, everything is HL_NORMAL. */ + + int i, prev_sep, in_string, in_comment; + int rowidx_backup; + char *p; + char **keywords = E.syntax->keywords; + char *scs = E.syntax->singleline_comment_start; + char *mcs = E.syntax->multiline_comment_start; + char *mce = E.syntax->multiline_comment_end; + char* temp; + + /* Point to the first non-space char. */ + p = row->render; + i = 0; /* Current char offset */ + while(*p && isspace(*p)) { + p++; + i++; + } + prev_sep = 1; /* Tell the parser if 'i' points to start of word. */ + in_string = 0; /* Are we inside "" or '' ? */ + in_comment = 0; /* Are we inside multi-line comment? */ + + rowidx_backup = row->idx; + temp = p; + while(row->idx > 0 && *temp != mce[0] && *(temp+1) != mce[1]) { + if(editorRowHasOpenComment(&E.row[row->idx-1])) + in_comment = 1; + row->idx--; + temp = E.row[row->idx].render; + while(*temp && isspace(*temp)) + temp++; + } + row->idx = rowidx_backup; + + rowidx_backup = row->idx; + temp = p; + if(!in_comment && *(temp) == mce[0] && *(temp+1) == mce[1]) { + while(row->idx > 0) { + while(*temp && isspace(*temp)) + temp++; + if(*temp != mcs[0] && *(temp+1) != mcs[1]) + in_comment = 0; + else { + in_comment = 1; + break; + } + row->idx--; + temp = E.row[row->idx].render; + } + } + row->idx = rowidx_backup; + + while(*p) { + /* Handle // comments. */ + if (prev_sep && *p == scs[0] && *(p+1) == scs[1]) { + /* From here to end is a comment */ + memset(row->hl+i,HL_COMMENT,row->size-i); + return; + } + + /* Handle multi line comments. */ + if (in_comment) { + row->hl[i] = HL_MLCOMMENT; + if (*p == mce[0] && *(p+1) == mce[1]) { + row->hl[i+1] = HL_MLCOMMENT; + p += 2; i += 2; + in_comment = 0; + prev_sep = 1; + continue; + } else { + prev_sep = 0; + p+=1; i+=1; + continue; + } + } else if (*p == mcs[0] && *(p+1) == mcs[1]) { + row->hl[i] = HL_MLCOMMENT; + row->hl[i+1] = HL_MLCOMMENT; + p += 2; i += 2; + prev_sep = 0; + continue; + } + + /* Handle "" and '' */ + if (in_string) { + row->hl[i] = HL_STRING; + if (*p == '\\') { + row->hl[i+1] = HL_STRING; + p += 2; i += 2; + prev_sep = 0; + continue; + } + if (*p == in_string) in_string = 0; + p++; i++; + continue; + } else { + if (*p == '"' || *p == '\'') { + in_string = *p; + row->hl[i] = HL_STRING; + p++; i++; + prev_sep = 0; + continue; + } + } + + /* Handle non printable chars. */ + if (!isprint(*p)) { + row->hl[i] = HL_NONPRINT; + p++; i++; + prev_sep = 0; + continue; + } + + /* Handle numbers */ + if ((isdigit(*p) && (prev_sep || row->hl[i-1] == HL_NUMBER)) || + (*p == '.' && i >0 && row->hl[i-1] == HL_NUMBER)) { + row->hl[i] = HL_NUMBER; + p++; i++; + prev_sep = 0; + continue; + } + + /* Handle keywords and lib calls */ + if (prev_sep) { + int j; + for (j = 0; keywords[j]; j++) { + int klen = strlen(keywords[j]); + int kw2 = keywords[j][klen-1] == '|'; + if (kw2) klen--; + + if (!memcmp(p,keywords[j],klen) && + is_separator(*(p+klen))) + { + /* Keyword */ + memset(row->hl+i,kw2 ? HL_KEYWORD2 : HL_KEYWORD1,klen); + p += klen; + i += klen; + break; + } + } + if (keywords[j] != NULL) { + prev_sep = 0; + continue; /* We had a keyword match */ + } + } + + /* Not special chars */ + prev_sep = is_separator(*p); + p++; i++; + } + + /* Propagate syntax change to the next row if the open commen + * state changed. This may recursively affect all the following rows + * in the file. */ + int oc = editorRowHasOpenComment(row); + if (row->hl_oc != oc && row->idx+1 < E.numrows) + editorUpdateSyntax(&E.row[row->idx+1]); + row->hl_oc = oc; +} + +/* Maps syntax highlight token types to terminal colors. */ +int editorSyntaxToColor(int hl) { + switch(hl) { + case HL_COMMENT: + case HL_MLCOMMENT: return 36; /* cyan */ + case HL_KEYWORD1: return 33; /* yellow */ + case HL_KEYWORD2: return 32; /* green */ + case HL_STRING: return 35; /* magenta */ + case HL_NUMBER: return 37; /* white */ + case HL_MATCH: return 34; /* blu */ + default: return 37; /* white */ + } +} + +/* Select the syntax highlight scheme depending on the filename, + * setting it in the global state E.syntax. */ +void editorSelectSyntaxHighlight(char *filename) { + for (unsigned int j = 0; j < HLDB_ENTRIES; j++) { + struct editorSyntax *s = HLDB+j; + unsigned int i = 0; + while(s->filematch[i]) { + char *p; + int patlen = strlen(s->filematch[i]); + if ((p = strstr(filename,s->filematch[i])) != NULL) { + if (s->filematch[i][0] != '.' || p[patlen] == '\0') { + E.syntax = s; + return; + } + } + i++; + } + } +} + +/* ======================= Editor rows implementation ======================= */ + +/* Update the rendered version and the syntax highlight of a row. */ +void editorUpdateRow(erow *row) { + int tabs = 0, nonprint = 0, j, idx; + + /* Create a version of the row we can directly print on the screen, + * respecting tabs, substituting non printable characters with '?'. */ + free(row->render); + for (j = 0; j < row->size; j++) + if (row->chars[j] == TAB) tabs++; + + row->render = malloc(row->size + tabs*8 + nonprint*9 + 1); + idx = 0; + for (j = 0; j < row->size; j++) { + if (row->chars[j] == TAB) { + row->render[idx++] = ' '; + while((idx+1) % 8 != 0) row->render[idx++] = ' '; + } else { + row->render[idx++] = row->chars[j]; + } + } + row->rsize = idx; + row->render[idx] = '\0'; + + /* Update the syntax highlighting attributes of the row. */ + editorUpdateSyntax(row); +} + +/* Insert a row at the specified position, shifting the other rows on the bottom + * if required. */ +void editorInsertRow(int at, char *s, size_t len) { + if (at > E.numrows) return; + E.row = realloc(E.row,sizeof(erow)*(E.numrows+1)); + if (at != E.numrows) { + memmove(E.row+at+1,E.row+at,sizeof(E.row[0])*(E.numrows-at)); + for (int j = at+1; j <= E.numrows; j++) E.row[j].idx++; + } + E.row[at].size = len; + E.row[at].chars = malloc(len+1); + memcpy(E.row[at].chars,s,len+1); + E.row[at].hl = NULL; + E.row[at].hl_oc = 0; + E.row[at].render = NULL; + E.row[at].rsize = 0; + E.row[at].idx = at; + editorUpdateRow(E.row+at); + E.numrows++; + E.dirty++; +} + +/* Free row's heap allocated stuff. */ +void editorFreeRow(erow *row) { + free(row->render); + free(row->chars); + free(row->hl); +} + +/* Remove the row at the specified position, shifting the remainign on the + * top. */ +void editorDelRow(int at) { + erow *row; + + if (at >= E.numrows) return; + row = E.row+at; + editorFreeRow(row); + memmove(E.row+at,E.row+at+1,sizeof(E.row[0])*(E.numrows-at-1)); + for (int j = at; j < E.numrows-1; j++) E.row[j].idx--; + E.numrows--; + E.dirty++; +} + +/* Turn the editor rows into a single heap-allocated string. + * Returns the pointer to the heap-allocated string and populate the + * integer pointed by 'buflen' with the size of the string, escluding + * the final nulterm. */ +char *editorRowsToString(int *buflen) { + char *buf = NULL, *p; + int totlen = 0; + int j; + + /* Compute count of bytes */ + for (j = 0; j < E.numrows; j++) + totlen += E.row[j].size+1; /* +1 is for "\n" at end of every row */ + *buflen = totlen; + totlen++; /* Also make space for nulterm */ + + p = buf = malloc(totlen); + for (j = 0; j < E.numrows; j++) { + memcpy(p,E.row[j].chars,E.row[j].size); + p += E.row[j].size; + *p = '\n'; + p++; + } + *p = '\0'; + return buf; +} + +/* Insert a character at the specified position in a row, moving the remaining + * chars on the right if needed. */ +void editorRowInsertChar(erow *row, int at, int c) { + if (at > row->size) { + /* Pad the string with spaces if the insert location is outside the + * current length by more than a single character. */ + int padlen = at-row->size; + /* In the next line +2 means: new char and null term. */ + row->chars = realloc(row->chars,row->size+padlen+2); + memset(row->chars+row->size,' ',padlen); + row->chars[row->size+padlen+1] = '\0'; + row->size += padlen+1; + } else { + /* If we are in the middle of the string just make space for 1 new + * char plus the (already existing) null term. */ + row->chars = realloc(row->chars,row->size+2); + memmove(row->chars+at+1,row->chars+at,row->size-at+1); + row->size++; + } + row->chars[at] = c; + editorUpdateRow(row); + E.dirty++; +} + +/* Append the string 's' at the end of a row */ +void editorRowAppendString(erow *row, char *s, size_t len) { + row->chars = realloc(row->chars,row->size+len+1); + memcpy(row->chars+row->size,s,len); + row->size += len; + row->chars[row->size] = '\0'; + editorUpdateRow(row); + E.dirty++; +} + +/* Delete the character at offset 'at' from the specified row. */ +void editorRowDelChar(erow *row, int at) { + if (row->size <= at) return; + memmove(row->chars+at,row->chars+at+1,row->size-at); + editorUpdateRow(row); + row->size--; + E.dirty++; +} + +/* Insert the specified char at the current prompt position. */ +void editorInsertChar(int c) { + int filerow = E.rowoff+E.cy; + int filecol = E.coloff+E.cx; + erow *row = (filerow >= E.numrows) ? NULL : &E.row[filerow]; + + /* If the row where the cursor is currently located does not exist in our + * logical representaion of the file, add enough empty rows as needed. */ + if (!row) { + while(E.numrows <= filerow) + editorInsertRow(E.numrows,"",0); + } + row = &E.row[filerow]; + editorRowInsertChar(row,filecol,c); + if (E.cx == E.screencols-1) + E.coloff++; + else + E.cx++; + E.dirty++; +} + +/* Inserting a newline is slightly complex as we have to handle inserting a + * newline in the middle of a line, splitting the line as needed. */ +void editorInsertNewline(void) { + int filerow = E.rowoff+E.cy; + int filecol = E.coloff+E.cx; + erow *row = (filerow >= E.numrows) ? NULL : &E.row[filerow]; + + if (!row) { + if (filerow == E.numrows) { + editorInsertRow(filerow,"",0); + goto fixcursor; + } + return; + } + /* If the cursor is over the current line size, we want to conceptually + * think it's just over the last character. */ + if (filecol >= row->size) filecol = row->size; + if (filecol == 0) { + editorInsertRow(filerow,"",0); + } else { + /* We are in the middle of a line. Split it between two rows. */ + editorInsertRow(filerow+1,row->chars+filecol,row->size-filecol); + row = &E.row[filerow]; + row->chars[filecol] = '\0'; + row->size = filecol; + editorUpdateRow(row); + } +fixcursor: + if (E.cy == E.screenrows-1) { + E.rowoff++; + } else { + E.cy++; + } + E.cx = 0; + E.coloff = 0; +} + +/* Delete the char at the current prompt position. */ +void editorDelChar() { + int filerow = E.rowoff+E.cy; + int filecol = E.coloff+E.cx; + erow *row = (filerow >= E.numrows) ? NULL : &E.row[filerow]; + + if (!row || (filecol == 0 && filerow == 0)) return; + if (filecol == 0) { + /* Handle the case of column 0, we need to move the current line + * on the right of the previous one. */ + filecol = E.row[filerow-1].size; + editorRowAppendString(&E.row[filerow-1],row->chars,row->size); + editorDelRow(filerow); + row = NULL; + if (E.cy == 0) + E.rowoff--; + else + E.cy--; + E.cx = filecol; + if (E.cx >= E.screencols) { + int shift = (E.screencols-E.cx)+1; + E.cx -= shift; + E.coloff += shift; + } + } else { + editorRowDelChar(row,filecol-1); + if (E.cx == 0 && E.coloff) + E.coloff--; + else + E.cx--; + } + if (row) editorUpdateRow(row); + E.dirty++; +} + +/* Load the specified program in the editor memory and returns 0 on success + * or 1 on error. */ +int editorOpen(char *filename) { + FILE *fp; + + E.dirty = 0; + free(E.filename); + E.filename = strdup(filename); + + fp = fopen(filename,"r"); + if (!fp) { + if (errno != ENOENT) { + perror("Opening file"); + exit(1); + } + return 1; + } + + char *line = NULL; + size_t linecap = 0; + ssize_t linelen; + while((linelen = getline(&line,&linecap,fp)) != -1) { + if (linelen && (line[linelen-1] == '\n' || line[linelen-1] == '\r')) + line[--linelen] = '\0'; + editorInsertRow(E.numrows,line,linelen); + } + free(line); + fclose(fp); + E.dirty = 0; + return 0; +} + +/* Save the current file on disk. Return 0 on success, 1 on error. */ +int editorSave(void) { + int len; + char *buf = editorRowsToString(&len); + int fd = open(E.filename,O_RDWR|O_CREAT,0644); + if (fd == -1) goto writeerr; + + /* Use truncate + a single write(2) call in order to make saving + * a bit safer, under the limits of what we can do in a small editor. */ + if (ftruncate(fd,len) == -1) goto writeerr; + if (write(fd,buf,len) != len) goto writeerr; + + close(fd); + free(buf); + E.dirty = 0; + editorSetStatusMessage("%d bytes written on disk", len); + return 0; + +writeerr: + free(buf); + if (fd != -1) close(fd); + editorSetStatusMessage("Can't save! I/O error: %s",strerror(errno)); + return 1; +} + +/* ============================= Terminal update ============================ */ + +/* We define a very simple "append buffer" structure, that is an heap + * allocated string where we can append to. This is useful in order to + * write all the escape sequences in a buffer and flush them to the standard + * output in a single call, to avoid flickering effects. */ +struct abuf { + char *b; + int len; +}; + +#define ABUF_INIT {NULL,0} + +void abAppend(struct abuf *ab, const char *s, int len) { + char *new = realloc(ab->b,ab->len+len); + + if (new == NULL) return; + memcpy(new+ab->len,s,len); + ab->b = new; + ab->len += len; +} + +void abFree(struct abuf *ab) { + free(ab->b); +} + +/* This function writes the whole screen using VT100 escape characters + * starting from the logical state of the editor in the global state 'E'. */ +void editorRefreshScreen(void) { + int y; + erow *r; + char buf[32]; + struct abuf ab = ABUF_INIT; + + abAppend(&ab,"\x1b[?25l",6); /* Hide cursor. */ + abAppend(&ab,"\x1b[H",3); /* Go home. */ + for (y = 0; y < E.screenrows; y++) { + int filerow = E.rowoff+y; + + if (filerow >= E.numrows) { + if (E.numrows == 0 && y == E.screenrows/3) { + char welcome[80]; + int welcomelen = snprintf(welcome,sizeof(welcome), + "Kilo editor -- verison %s\x1b[0K\r\n", KILO_VERSION); + int padding = (E.screencols-welcomelen)/2; + if (padding) { + abAppend(&ab,"~",1); + padding--; + } + while(padding--) abAppend(&ab," ",1); + abAppend(&ab,welcome,welcomelen); + } else { + abAppend(&ab,"~\x1b[0K\r\n",7); + } + continue; + } + + r = &E.row[filerow]; + + int len = r->rsize - E.coloff; + int current_color = -1; + if (len > 0) { + if (len > E.screencols) len = E.screencols; + char *c = r->render+E.coloff; + unsigned char *hl = r->hl+E.coloff; + int j; + for (j = 0; j < len; j++) { + if (hl[j] == HL_NONPRINT) { + char sym; + abAppend(&ab,"\x1b[7m",4); + if (c[j] <= 26) + sym = '@'+c[j]; + else + sym = '?'; + abAppend(&ab,&sym,1); + abAppend(&ab,"\x1b[0m",4); + } else if (hl[j] == HL_NORMAL) { + if (current_color != -1) { + abAppend(&ab,"\x1b[39m",5); + current_color = -1; + } + abAppend(&ab,c+j,1); + } else { + int color = editorSyntaxToColor(hl[j]); + if (color != current_color) { + char buf[16]; + int clen = snprintf(buf,sizeof(buf),"\x1b[%dm",color); + current_color = color; + abAppend(&ab,buf,clen); + } + abAppend(&ab,c+j,1); + } + } + } + abAppend(&ab,"\x1b[39m",5); + abAppend(&ab,"\x1b[0K",4); + abAppend(&ab,"\r\n",2); + } + + /* Create a two rows status. First row: */ + abAppend(&ab,"\x1b[0K",4); + abAppend(&ab,"\x1b[7m",4); + char status[80], rstatus[80]; + int len = snprintf(status, sizeof(status), "%.20s - %d lines %s", + E.filename, E.numrows, E.dirty ? "(modified)" : ""); + int rlen = snprintf(rstatus, sizeof(rstatus), + "%d/%d",E.rowoff+E.cy+1,E.numrows); + if (len > E.screencols) len = E.screencols; + abAppend(&ab,status,len); + while(len < E.screencols) { + if (E.screencols - len == rlen) { + abAppend(&ab,rstatus,rlen); + break; + } else { + abAppend(&ab," ",1); + len++; + } + } + abAppend(&ab,"\x1b[0m\r\n",6); + + /* Second row depends on E.statusmsg and the status message update time. */ + abAppend(&ab,"\x1b[0K",4); + int msglen = strlen(E.statusmsg); + if (msglen && time(NULL)-E.statusmsg_time < 5) + abAppend(&ab,E.statusmsg,msglen <= E.screencols ? msglen : E.screencols); + + /* Put cursor at its current position. Note that the horizontal position + * at which the cursor is displayed may be different compared to 'E.cx' + * because of TABs. */ + int j; + int cx = 1; + int filerow = E.rowoff+E.cy; + erow *row = (filerow >= E.numrows) ? NULL : &E.row[filerow]; + if (row) { + for (j = E.coloff; j < (E.cx+E.coloff); j++) { + if (j < row->size && row->chars[j] == TAB) cx += 7-((cx)%8); + cx++; + } + } + snprintf(buf,sizeof(buf),"\x1b[%d;%dH",E.cy+1,cx); + abAppend(&ab,buf,strlen(buf)); + abAppend(&ab,"\x1b[?25h",6); /* Show cursor. */ + write(STDOUT_FILENO,ab.b,ab.len); + abFree(&ab); +} + +/* Set an editor status message for the second line of the status, at the + * end of the screen. */ +void editorSetStatusMessage(const char *fmt, ...) { + va_list ap; + va_start(ap,fmt); + vsnprintf(E.statusmsg,sizeof(E.statusmsg),fmt,ap); + va_end(ap); + E.statusmsg_time = time(NULL); +} + +/* =============================== Find mode ================================ */ + +#define KILO_QUERY_LEN 256 + +void editorFind(int fd) { + char query[KILO_QUERY_LEN+1] = {0}; + int qlen = 0; + int last_match = -1; /* Last line where a match was found. -1 for none. */ + int find_next = 0; /* if 1 search next, if -1 search prev. */ + int saved_hl_line = -1; /* No saved HL */ + char *saved_hl = NULL; + +#define FIND_RESTORE_HL do { \ + if (saved_hl) { \ + memcpy(E.row[saved_hl_line].hl,saved_hl, E.row[saved_hl_line].rsize); \ + free(saved_hl); \ + saved_hl = NULL; \ + } \ +} while (0) + + /* Save the cursor position in order to restore it later. */ + int saved_cx = E.cx, saved_cy = E.cy; + int saved_coloff = E.coloff, saved_rowoff = E.rowoff; + + while(1) { + editorSetStatusMessage( + "Search: %s (Use ESC/Arrows/Enter)", query); + editorRefreshScreen(); + + int c = editorReadKey(fd); + if (c == DEL_KEY || c == CTRL_H || c == BACKSPACE) { + if (qlen != 0) query[--qlen] = '\0'; + last_match = -1; + } else if (c == ESC || c == ENTER) { + if (c == ESC) { + E.cx = saved_cx; E.cy = saved_cy; + E.coloff = saved_coloff; E.rowoff = saved_rowoff; + } + FIND_RESTORE_HL; + editorSetStatusMessage(""); + return; + } else if (c == ARROW_RIGHT || c == ARROW_DOWN) { + find_next = 1; + } else if (c == ARROW_LEFT || c == ARROW_UP) { + find_next = -1; + } else if (isprint(c)) { + if (qlen < KILO_QUERY_LEN) { + query[qlen++] = c; + query[qlen] = '\0'; + last_match = -1; + } + } + + /* Search occurrence. */ + if (last_match == -1) find_next = 1; + if (find_next) { + char *match = NULL; + int match_offset = 0; + int i, current = last_match; + + for (i = 0; i < E.numrows; i++) { + current += find_next; + if (current == -1) current = E.numrows-1; + else if (current == E.numrows) current = 0; + match = strstr(E.row[current].render,query); + if (match) { + match_offset = match-E.row[current].render; + break; + } + } + find_next = 0; + + /* Highlight */ + FIND_RESTORE_HL; + + if (match) { + erow *row = &E.row[current]; + last_match = current; + if (row->hl) { + saved_hl_line = current; + saved_hl = malloc(row->rsize); + memcpy(saved_hl,row->hl,row->rsize); + memset(row->hl+match_offset,HL_MATCH,qlen); + } + E.cy = 0; + E.cx = match_offset; + E.rowoff = current; + E.coloff = 0; + /* Scroll horizontally as needed. */ + if (E.cx > E.screencols) { + int diff = E.cx - E.screencols; + E.cx -= diff; + E.coloff += diff; + } + } + } + } +} + +/* ========================= Editor events handling ======================== */ + +/* Handle cursor position change because arrow keys were pressed. */ +void editorMoveCursor(int key) { + int filerow = E.rowoff+E.cy; + int filecol = E.coloff+E.cx; + int rowlen; + erow *row = (filerow >= E.numrows) ? NULL : &E.row[filerow]; + + switch(key) { + case ARROW_LEFT: + if (E.cx == 0) { + if (E.coloff) { + E.coloff--; + } else { + if (filerow > 0) { + E.cy--; + E.cx = E.row[filerow-1].size; + if (E.cx > E.screencols-1) { + E.coloff = E.cx-E.screencols+1; + E.cx = E.screencols-1; + } + } + } + } else { + E.cx -= 1; + } + break; + case ARROW_RIGHT: + if (row && filecol < row->size) { + if (E.cx == E.screencols-1) { + E.coloff++; + } else { + E.cx += 1; + } + } else if (row && filecol == row->size) { + E.cx = 0; + E.coloff = 0; + if (E.cy == E.screenrows-1) { + E.rowoff++; + } else { + E.cy += 1; + } + } + break; + case ARROW_UP: + if (E.cy == 0) { + if (E.rowoff) E.rowoff--; + } else { + E.cy -= 1; + } + break; + case ARROW_DOWN: + if (filerow < E.numrows) { + if (E.cy == E.screenrows-1) { + E.rowoff++; + } else { + E.cy += 1; + } + } + break; + } + /* Fix cx if the current line has not enough chars. */ + filerow = E.rowoff+E.cy; + filecol = E.coloff+E.cx; + row = (filerow >= E.numrows) ? NULL : &E.row[filerow]; + rowlen = row ? row->size : 0; + if (filecol > rowlen) { + E.cx -= filecol-rowlen; + if (E.cx < 0) { + E.coloff += E.cx; + E.cx = 0; + } + } +} + +/* Process events arriving from the standard input, which is, the user + * is typing stuff on the terminal. */ +#define KILO_QUIT_TIMES 3 +void editorProcessKeypress(int fd) { + /* When the file is modified, requires Ctrl-q to be pressed N times + * before actually quitting. */ + static int quit_times = KILO_QUIT_TIMES; + + int c = editorReadKey(fd); + switch(c) { + case ENTER: /* Enter */ + editorInsertNewline(); + break; + case CTRL_C: /* Ctrl-c */ + /* We ignore ctrl-c, it can't be so simple to lose the changes + * to the edited file. */ + break; + case CTRL_G: + moveToEnd(); + break; + case CTRL_T: + moveToFirst(); + break; + case CTRL_D: + moveToLineEnd(); + break; + case CTRL_Q: /* Ctrl-q */ + /* Quit if the file was already saved. */ + if (E.dirty && quit_times) { + editorSetStatusMessage("WARNING!!! File has unsaved changes. " + "Press Ctrl-Q %d more times to quit.", quit_times); + quit_times--; + return; + } + struct abuf temp = ABUF_INIT; + abAppend(&temp, "\x1b[2J", 4); + abAppend(&temp, "\x1b[H", 3); + + write(STDOUT_FILENO, temp.b, temp.len); + abFree(&temp); + exit(0); + break; + case CTRL_S: /* Ctrl-s */ + editorSave(); + break; + case CTRL_F: + editorFind(fd); + break; + case BACKSPACE: /* Backspace */ + case CTRL_H: /* Ctrl-h */ + case DEL_KEY: + editorDelChar(); + break; + case PAGE_UP: + case PAGE_DOWN: + if (c == PAGE_UP && E.cy != 0) + E.cy = 0; + else if (c == PAGE_DOWN && E.cy != E.screenrows-1) + E.cy = E.screenrows-1; + { + int times = E.screenrows; + while(times--) + editorMoveCursor(c == PAGE_UP ? ARROW_UP: + ARROW_DOWN); + } + break; + + case ARROW_UP: + case ARROW_DOWN: + case ARROW_LEFT: + case ARROW_RIGHT: + editorMoveCursor(c); + break; + case CTRL_L: /* ctrl+l, clear screen */ + /* Just refresht the line as side effect. */ + break; + case ESC: + /* Nothing to do for ESC in this mode. */ + break; + default: + editorInsertChar(c); + break; + } + + quit_times = KILO_QUIT_TIMES; /* Reset it to the original value. */ +} + +int editorFileWasModified(void) { + return E.dirty; +} + +void initEditor(void) { + E.cx = 0; + E.cy = 0; + E.rowoff = 0; + E.coloff = 0; + E.numrows = 0; + E.row = NULL; + E.dirty = 0; + E.filename = NULL; + E.syntax = NULL; + if (getWindowSize(STDIN_FILENO,STDOUT_FILENO, + &E.screenrows,&E.screencols) == -1) + { + perror("Unable to query the screen for size (columns / rows)"); + exit(1); + } + E.screenrows -= 2; /* Get room for status bar. */ +} + +int main(int argc, char **argv) { + if (argc != 2) { + fprintf(stderr,"Usage: kilo \n"); + exit(1); + } + + initEditor(); + editorSelectSyntaxHighlight(argv[1]); + editorOpen(argv[1]); + enableRawMode(STDIN_FILENO); + editorSetStatusMessage( + "HELP: Ctrl-S = save | Ctrl-Q = quit | Ctrl-F = find"); + while(1) { + editorRefreshScreen(); + editorProcessKeypress(STDIN_FILENO); + } + return 0; +} From 9c03efa274b1a1f1a85d4f7936e14b1476e1ceeb Mon Sep 17 00:00:00 2001 From: hyunsooda Date: Tue, 16 Oct 2018 23:59:02 +0900 Subject: [PATCH 06/22] Fix backspace error when previous line is overflowed --- .kilo.c.swp | Bin 16384 -> 0 bytes kilo.c | 21 +++++++++++++-------- 2 files changed, 13 insertions(+), 8 deletions(-) delete mode 100644 .kilo.c.swp diff --git a/.kilo.c.swp b/.kilo.c.swp deleted file mode 100644 index 342ab4533af0f864649da1a704bb6e32ccae74d9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16384 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 MjPY(4H?h$8A1GjaLI3~& diff --git a/kilo.c b/kilo.c index 9cefd95..699771b 100644 --- a/kilo.c +++ b/kilo.c @@ -737,7 +737,7 @@ void editorRowAppendString(erow *row, char *s, size_t len) { memcpy(row->chars+row->size,s,len); row->size += len; row->chars[row->size] = '\0'; - editorUpdateRow(row); + editorUpdateRow(row); E.dirty++; } @@ -812,7 +812,8 @@ void editorInsertNewline(void) { void editorDelChar() { int filerow = E.rowoff+E.cy; int filecol = E.coloff+E.cx; - erow *row = (filerow >= E.numrows) ? NULL : &E.row[filerow]; + erow *row = (filerow >= E.numrows) ? NULL : &E.row[filerow]; + int delRowSize = row->size; if (!row || (filecol == 0 && filerow == 0)) return; if (filecol == 0) { @@ -821,17 +822,21 @@ void editorDelChar() { filecol = E.row[filerow-1].size; editorRowAppendString(&E.row[filerow-1],row->chars,row->size); editorDelRow(filerow); - row = NULL; + row = NULL; if (E.cy == 0) E.rowoff--; else E.cy--; - E.cx = filecol; - if (E.cx >= E.screencols) { - int shift = (E.screencols-E.cx)+1; + + E.coloff = E.row[filerow-1].size - E.screencols; + E.cx = E.screencols - delRowSize; + /* + if (E.cx >= E.screencols) { + int shift = (E.screencols-E.cx)+1; E.cx -= shift; E.coloff += shift; - } + } + */ } else { editorRowDelChar(row,filecol-1); if (E.cx == 0 && E.coloff) @@ -1198,7 +1203,7 @@ void editorMoveCursor(int key) { } else { E.cy += 1; } - } + } break; case ARROW_UP: if (E.cy == 0) { From ba02eb5f320e01112bd8ff07fe38a145a4f0937f Mon Sep 17 00:00:00 2001 From: hyunsooda Date: Wed, 17 Oct 2018 16:19:05 +0900 Subject: [PATCH 07/22] Add config file to tailor user's experiences --- kilo.c | 136 ++++++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 126 insertions(+), 10 deletions(-) diff --git a/kilo.c b/kilo.c index 699771b..22d511b 100644 --- a/kilo.c +++ b/kilo.c @@ -107,7 +107,17 @@ struct editorConfig { struct editorSyntax *syntax; /* Current syntax highlight, or NULL. */ }; +typedef struct _rc { + int indent; + int background; + int keywordColor1; + int keywordColor2; + int stringColor; + int numberColor; +}Rc; + static struct editorConfig E; +Rc myrc; enum KEY_ACTION{ KEY_NULL = 0, /* NULL */ @@ -233,6 +243,45 @@ void moveToLineEnd() { static struct termios orig_termios; /* In order to restore at exit.*/ +#define MAX_STR_LEN 4000 + +// 문자열 우측 공백문자 삭제 함수 +char* rtrim(char* s) { + char t[MAX_STR_LEN]; + char *end; + + strcpy(t, s); // 이것은 Visual C 2005용 + end = t + strlen(t) - 1; + while (end != t && isspace(*end)) + end--; + *(end + 1) = '\0'; + s = t; + + return s; +} + +// 문자열 좌측 공백문자 삭제 함수 +char* ltrim(char *s) { + char* begin; + begin = s; + + while (*begin != '\0') { + if (isspace(*begin)) + begin++; + else { + s = begin; + break; + } + } + + return s; +} + +// 문자열 앞뒤 공백 모두 삭제 함수 +char* trim(char *s) { + return rtrim(ltrim(s)); +} + void disableRawMode(int fd) { /* Don't even check the return value as it's too late. */ if (E.rawmode) { @@ -580,10 +629,10 @@ int editorSyntaxToColor(int hl) { switch(hl) { case HL_COMMENT: case HL_MLCOMMENT: return 36; /* cyan */ - case HL_KEYWORD1: return 33; /* yellow */ - case HL_KEYWORD2: return 32; /* green */ - case HL_STRING: return 35; /* magenta */ - case HL_NUMBER: return 37; /* white */ + case HL_KEYWORD1: return myrc.keywordColor1; // return 33; /* yellow */ + case HL_KEYWORD2: return myrc.keywordColor2; // return 32; /* green */ + case HL_STRING: return myrc.stringColor; // return 35; /* magenta */ + case HL_NUMBER: return myrc.numberColor; // return 37; /* white */ case HL_MATCH: return 34; /* blu */ default: return 37; /* white */ } @@ -621,12 +670,12 @@ void editorUpdateRow(erow *row) { for (j = 0; j < row->size; j++) if (row->chars[j] == TAB) tabs++; - row->render = malloc(row->size + tabs*8 + nonprint*9 + 1); + row->render = malloc(row->size + tabs*myrc.indent + nonprint*9 + 1); idx = 0; for (j = 0; j < row->size; j++) { if (row->chars[j] == TAB) { row->render[idx++] = ' '; - while((idx+1) % 8 != 0) row->render[idx++] = ' '; + while((idx+1) % myrc.indent != 0) row->render[idx++] = ' '; } else { row->render[idx++] = row->chars[j]; } @@ -937,11 +986,14 @@ void editorRefreshScreen(void) { int y; erow *r; char buf[32]; + char background_color[32]; struct abuf ab = ABUF_INIT; abAppend(&ab,"\x1b[?25l",6); /* Hide cursor. */ abAppend(&ab,"\x1b[H",3); /* Go home. */ - for (y = 0; y < E.screenrows; y++) { + sprintf(background_color, "\x1b[%dm", myrc.background); + abAppend(&ab, background_color, strlen(background_color)); + for (y = 0; y < E.screenrows; y++) { int filerow = E.rowoff+y; if (filerow >= E.numrows) { @@ -994,7 +1046,7 @@ void editorRefreshScreen(void) { int clen = snprintf(buf,sizeof(buf),"\x1b[%dm",color); current_color = color; abAppend(&ab,buf,clen); - } + } abAppend(&ab,c+j,1); } } @@ -1040,7 +1092,7 @@ void editorRefreshScreen(void) { erow *row = (filerow >= E.numrows) ? NULL : &E.row[filerow]; if (row) { for (j = E.coloff; j < (E.cx+E.coloff); j++) { - if (j < row->size && row->chars[j] == TAB) cx += 7-((cx)%8); + if (j < row->size && row->chars[j] == TAB) cx += myrc.indent-1-((cx)%myrc.indent); cx++; } } @@ -1327,6 +1379,25 @@ int editorFileWasModified(void) { return E.dirty; } +int vt100_color(char* str, int whatColor) { + if(!strcmp(str, "cyan")) + return whatColor ? 36+10 : 36; + if(!strcmp(str, "yellow")) + return whatColor ? 33+10 : 33; + if(!strcmp(str, "green")) + return whatColor ? 32+10 : 32; + if(!strcmp(str, "magenta")) + return whatColor ? 35+10 : 35; + if(!strcmp(str, "red")) + return whatColor ? 31+10 : 31; + if(!strcmp(str, "blue")) + return whatColor ? 34+10 : 34; + if(!strcmp(str, "white")) + return whatColor ? 37+10 : 37; + + return whatColor ? 39+10 : 39; // default +} + void initEditor(void) { E.cx = 0; E.cy = 0; @@ -1337,7 +1408,52 @@ void initEditor(void) { E.dirty = 0; E.filename = NULL; E.syntax = NULL; - if (getWindowSize(STDIN_FILENO,STDOUT_FILENO, + + FILE* fp; + char str[100]; + char type[100]; + char* separate; + char* ret; + char* types[] = {"background", "keywordcolor1", "keywordcolor2", "indent", "stringcolor", "numbercolor"}; + + myrc.indent = 8; + myrc.background = 49; // default; + myrc.keywordColor1 = 33; + myrc.keywordColor2 = 32; + myrc.stringColor = 35; + myrc.numberColor = 37; + + fp = fopen("./.kilorc", "r"); + + if(fp != NULL) { + while(fgets(str, 100, fp) != NULL) { + str[strlen(str)-1] = '\0'; + separate = strtok(str, "="); + strcpy(type, separate); + strcpy(type, trim(type)); + separate = strtok(NULL,"="); + ret = trim(separate); + + for(int i=0; i<6; i++) { + if(!strcmp(types[i], type)) { + if(!strcmp(type,"background")) + myrc.background = vt100_color(ret, 1); + if(!strcmp(type,"keywordcolor1")) + myrc.keywordColor1 = vt100_color(ret, 0); + if(!strcmp(type,"keywordcolor2")) + myrc.keywordColor2 = vt100_color(ret, 0); + if(!strcmp(type,"indent")) + myrc.indent = atoi(ret); + if(!strcmp(type,"stringcolorg")) + myrc.stringColor = vt100_color(ret, 0); + if(!strcmp(type,"numbercolor")) + myrc.numberColor = vt100_color(ret, 0); + } + } + } + } + + if (getWindowSize(STDIN_FILENO,STDOUT_FILENO, &E.screenrows,&E.screencols) == -1) { perror("Unable to query the screen for size (columns / rows)"); From a3e33f82a87380cf3526b7095f24066ef3fac9db Mon Sep 17 00:00:00 2001 From: hyunsooda Date: Wed, 17 Oct 2018 16:27:56 +0900 Subject: [PATCH 08/22] Fix typo --- kilo.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kilo.c b/kilo.c index 22d511b..b4a74b0 100644 --- a/kilo.c +++ b/kilo.c @@ -1444,7 +1444,7 @@ void initEditor(void) { myrc.keywordColor2 = vt100_color(ret, 0); if(!strcmp(type,"indent")) myrc.indent = atoi(ret); - if(!strcmp(type,"stringcolorg")) + if(!strcmp(type,"stringcolor")) myrc.stringColor = vt100_color(ret, 0); if(!strcmp(type,"numbercolor")) myrc.numberColor = vt100_color(ret, 0); From c4c18df1a5d989fcdf44daea4808717ab22543f5 Mon Sep 17 00:00:00 2001 From: hyunsooda Date: Wed, 17 Oct 2018 16:51:00 +0900 Subject: [PATCH 09/22] Fix bug of editorDelChar funtion --- kilo.c | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/kilo.c b/kilo.c index b4a74b0..98a6bc1 100644 --- a/kilo.c +++ b/kilo.c @@ -864,7 +864,9 @@ void editorDelChar() { erow *row = (filerow >= E.numrows) ? NULL : &E.row[filerow]; int delRowSize = row->size; - if (!row || (filecol == 0 && filerow == 0)) return; + if (!row || (filecol == 0 && filerow == 0)) { + return; + } if (filecol == 0) { /* Handle the case of column 0, we need to move the current line * on the right of the previous one. */ @@ -876,16 +878,18 @@ void editorDelChar() { E.rowoff--; else E.cy--; - - E.coloff = E.row[filerow-1].size - E.screencols; - E.cx = E.screencols - delRowSize; - /* - if (E.cx >= E.screencols) { - int shift = (E.screencols-E.cx)+1; - E.cx -= shift; - E.coloff += shift; + + if(E.row[filerow-1].size > E.screencols) { + E.coloff = E.row[filerow-1].size - E.screencols; + E.cx = E.screencols - delRowSize; + } else { + E.cx = filecol; + if (E.cx >= E.screencols) { + int shift = (E.screencols-E.cx)+1; + E.cx -= shift; + E.coloff += shift; + } } - */ } else { editorRowDelChar(row,filecol-1); if (E.cx == 0 && E.coloff) @@ -998,9 +1002,9 @@ void editorRefreshScreen(void) { if (filerow >= E.numrows) { if (E.numrows == 0 && y == E.screenrows/3) { - char welcome[80]; + char welcome[100]; int welcomelen = snprintf(welcome,sizeof(welcome), - "Kilo editor -- verison %s\x1b[0K\r\n", KILO_VERSION); + "Hyunsoo editor -- verison %s\x1b[0K\r\n", KILO_VERSION); int padding = (E.screencols-welcomelen)/2; if (padding) { abAppend(&ab,"~",1); @@ -1451,7 +1455,7 @@ void initEditor(void) { } } } - } + } if (getWindowSize(STDIN_FILENO,STDOUT_FILENO, &E.screenrows,&E.screencols) == -1) From f7e5c9062b184bff97b654f6f1dec295fbe49946 Mon Sep 17 00:00:00 2001 From: hyunsooda Date: Wed, 17 Oct 2018 18:08:55 +0900 Subject: [PATCH 10/22] Add remove function that perform remove one line and then move cursor to abvoe line --- kilo.c | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/kilo.c b/kilo.c index 98a6bc1..cfcda55 100644 --- a/kilo.c +++ b/kilo.c @@ -126,6 +126,7 @@ enum KEY_ACTION{ CTRL_F = 6, /* Ctrl-f */ CTRL_G = 7, /* Ctrl-g */ CTRL_T = 20, /* Ctrl-T */ + CTRL_X = 24, /* CTRL-X */ CTRL_H = 8, /* Ctrl-h */ TAB = 9, /* Tab */ CTRL_L = 12, /* Ctrl+l */ @@ -234,6 +235,11 @@ void moveToFirst() { } void moveToLineEnd() { + int filerow = E.rowoff+E.cy; + int filecol = E.coloff+E.cx; + + if(!filerow && !filecol) + return; E.cx = E.row[E.rowoff + E.cy].size; } @@ -984,6 +990,19 @@ void abFree(struct abuf *ab) { free(ab->b); } +void removeOneLine() { + int filerow = E.rowoff+E.cy; + erow *row = (filerow >= E.numrows) ? NULL : &E.row[filerow]; + int size = row->size; + + if(!row) + return; + + moveToLineEnd(); + for(int i=0; i Date: Wed, 17 Oct 2018 19:23:28 +0900 Subject: [PATCH 11/22] Add function that perform copy and paste(2 functions) --- kilo.c | 34 ++++++++++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/kilo.c b/kilo.c index cfcda55..3b6385a 100644 --- a/kilo.c +++ b/kilo.c @@ -118,6 +118,7 @@ typedef struct _rc { static struct editorConfig E; Rc myrc; +char* copyText = NULL; enum KEY_ACTION{ KEY_NULL = 0, /* NULL */ @@ -134,7 +135,8 @@ enum KEY_ACTION{ CTRL_Q = 17, /* Ctrl-q */ CTRL_S = 19, /* Ctrl-s */ CTRL_U = 21, /* Ctrl-u */ - ESC = 27, /* Escape */ + CTRL_V = 22, /* Ctrl-v */ + ESC = 27, /* Escape */ BACKSPACE = 127, /* Backspace */ /* The following are just soft codes, not really reported by the * terminal directly. */ @@ -196,6 +198,8 @@ struct editorSyntax HLDB[] = { }; void editorRefreshScreen(); // forward declear to avoid compile exception +void copyOneLine(); +void pasteOneLine(); void moveToEnd() { int factor = 0; @@ -1327,7 +1331,11 @@ void editorProcessKeypress(int fd) { case CTRL_C: /* Ctrl-c */ /* We ignore ctrl-c, it can't be so simple to lose the changes * to the edited file. */ - break; + copyOneLine(); + break; + case CTRL_V: + pasteOneLine(); + break; case CTRL_G: moveToEnd(); break; @@ -1405,6 +1413,28 @@ int editorFileWasModified(void) { return E.dirty; } +void copyOneLine() { + int filerow = E.rowoff+E.cy; + erow *row = (filerow >= E.numrows) ? NULL : &E.row[filerow]; + char* str = (char*)malloc(sizeof(char)*row->size); + strcpy(str, row->chars); + if(copyText) + free(copyText); + copyText = str; +} + +void pasteOneLine() { + if(copyText == NULL) + return; + + int size = strlen(copyText); + + for(int i=0; i Date: Fri, 19 Oct 2018 15:44:02 +0900 Subject: [PATCH 12/22] Update README.md --- .kilorc | 4 ++++ README.md | 6 ++++++ TODO | 13 +++---------- kilo.c | 2 +- 4 files changed, 14 insertions(+), 11 deletions(-) create mode 100644 .kilorc diff --git a/.kilorc b/.kilorc new file mode 100644 index 0000000..9c2e357 --- /dev/null +++ b/.kilorc @@ -0,0 +1,4 @@ +indent = 8 +keywordcolor1 = red +keywordcolor2 = cyan +stringcolor = white diff --git a/README.md b/README.md index 47d612f..c2e127b 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,12 @@ Keys: CTRL-S: Save CTRL-Q: Quit CTRL-F: Find string in file (ESC to exit search, arrows to navigate) + CTRL-V: Paste one line + CTRL-C: Copy one line + CTRL-G: Move to end of currnet line + CTRL-T: Move to first of current line + CTRL-D: Move to end of file + CTRL-X: Remove one line Kilo does not depend on any library (not even curses). It uses fairly standard VT100 (and similar terminals) escape sequences. The project is in alpha diff --git a/TODO b/TODO index 95ae28b..4bb2dc1 100644 --- a/TODO +++ b/TODO @@ -1,10 +1,3 @@ -IMPORTANT -=== - -* Testing and stability to reach "usable" level. - -MAYBE -=== - -* Send alternate screen sequences if TERM=xterm: "\033[?1049h" and "\033[?1049l" -* Improve internals to be more understandable. +3. status console manipluation +4. find +5. turn to REPL diff --git a/kilo.c b/kilo.c index 3b6385a..d93c410 100644 --- a/kilo.c +++ b/kilo.c @@ -1529,7 +1529,7 @@ int main(int argc, char **argv) { editorOpen(argv[1]); enableRawMode(STDIN_FILENO); editorSetStatusMessage( - "HELP: Ctrl-S = save | Ctrl-Q = quit | Ctrl-F = find"); + "HELP: Ctrl-S = save | Ctrl-Q = quit | See the README.md for other functions"); while(1) { editorRefreshScreen(); editorProcessKeypress(STDIN_FILENO); From f072817360bc8e05e5b8f5014f9bb0ef824e9dd6 Mon Sep 17 00:00:00 2001 From: hyunsooda Date: Fri, 19 Oct 2018 22:19:38 +0900 Subject: [PATCH 13/22] Add undo(CTRL+Z) --- kilo.c | 139 ++++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 133 insertions(+), 6 deletions(-) diff --git a/kilo.c b/kilo.c index d93c410..59ba459 100644 --- a/kilo.c +++ b/kilo.c @@ -136,6 +136,7 @@ enum KEY_ACTION{ CTRL_S = 19, /* Ctrl-s */ CTRL_U = 21, /* Ctrl-u */ CTRL_V = 22, /* Ctrl-v */ + CTRL_Z = 26, /* ctrl-z */ ESC = 27, /* Escape */ BACKSPACE = 127, /* Backspace */ /* The following are just soft codes, not really reported by the @@ -197,10 +198,115 @@ struct editorSyntax HLDB[] = { } }; -void editorRefreshScreen(); // forward declear to avoid compile exception +// stack +#define TRUE 1 +#define FALSE 0 + +typedef struct _History { // singleton object + int len; + int cx; + int cy; + short isTracing; +}History; + +typedef struct _node { + History data; + struct _node* next; +}Node; + +typedef struct _listStack { + Node* head; +}ListStack; + +typedef ListStack Stack; +Stack stack; // global-variable +History ht; // global-variable + +void StackInit() { + stack.head = NULL; +} +void HistoryInit() { + ht.isTracing = TRUE; + ht.len = 0; + ht.cx = 0; + ht.cy = 0; +} +int SIsEmpty() { + if(stack.head == NULL) + return TRUE; + else + return FALSE; +} +void SPush() { + Node* newNode = (Node*)malloc(sizeof(Node)); + History temp = ht; + newNode->data = temp; + newNode->next = stack.head; + stack.head = newNode; +} + +void turnOffTracing() { + if(ht.isTracing) { + SPush(); + HistoryInit(); + } +} + +History SPop() { + History rdata; + Node* rnode; + + if(SIsEmpty(stack)) { + printf("Stack Memory Error!"); + exit(-1); + } + + rdata = stack.head->data; + rnode = stack.head; + + stack.head = stack.head->next; + free(rnode); + + return rdata; +} +History SPeek() { + if(SIsEmpty(stack)) { + printf("Stack Memory Error!"); + exit(-1); + } + + return stack.head->data; +} + + +void editorRefreshScreen(); // forward declare to avoid compile exception +void editorDelChar(); // forward declare void copyOneLine(); void pasteOneLine(); +void undo() { + History temp; + + if(ht.len) + turnOffTracing(); + + if(SIsEmpty()) + return; + + temp = SPop(); + + for(int i=0; i 1) + // editorDelChar(); + +// printf("%d ", E.cx); +// while(!SIsEmpty()) { +// printf("%d ", SPop().cx); +// } +// exit(1); +} + void moveToEnd() { int factor = 0; @@ -677,8 +783,11 @@ void editorUpdateRow(erow *row) { /* Create a version of the row we can directly print on the screen, * respecting tabs, substituting non printable characters with '?'. */ free(row->render); - for (j = 0; j < row->size; j++) - if (row->chars[j] == TAB) tabs++; + for (j = 0; j < row->size; j++) { + if (row->chars[j] == TAB) { + tabs++; + } + } row->render = malloc(row->size + tabs*myrc.indent + nonprint*9 + 1); idx = 0; @@ -813,7 +922,17 @@ void editorRowDelChar(erow *row, int at) { void editorInsertChar(int c) { int filerow = E.rowoff+E.cy; int filecol = E.coloff+E.cx; - erow *row = (filerow >= E.numrows) ? NULL : &E.row[filerow]; + erow *row = (filerow >= E.numrows) ? NULL : &E.row[filerow]; + + if(c == TAB) { + turnOffTracing(); + ht.len = 1; + } else if(isspace(c)) { + turnOffTracing(); + ht.len = 1; + } else if(ht.isTracing == TRUE) { + ht.len++; + } /* If the row where the cursor is currently located does not exist in our * logical representaion of the file, add enough empty rows as needed. */ @@ -1119,7 +1238,7 @@ void editorRefreshScreen(void) { erow *row = (filerow >= E.numrows) ? NULL : &E.row[filerow]; if (row) { for (j = E.coloff; j < (E.cx+E.coloff); j++) { - if (j < row->size && row->chars[j] == TAB) cx += myrc.indent-1-((cx)%myrc.indent); + if (j < row->size && row->chars[j] == TAB) cx += myrc.indent-1-((cx)%myrc.indent); cx++; } } @@ -1326,6 +1445,8 @@ void editorProcessKeypress(int fd) { int c = editorReadKey(fd); switch(c) { case ENTER: /* Enter */ + ht.len++; + turnOffTracing(); editorInsertNewline(); break; case CTRL_C: /* Ctrl-c */ @@ -1348,6 +1469,9 @@ void editorProcessKeypress(int fd) { case CTRL_X: removeOneLine(); break; + case CTRL_Z: + undo(); + break; case CTRL_Q: /* Ctrl-q */ /* Quit if the file was already saved. */ if (E.dirty && quit_times) { @@ -1393,6 +1517,7 @@ void editorProcessKeypress(int fd) { case ARROW_DOWN: case ARROW_LEFT: case ARROW_RIGHT: + turnOffTracing(); editorMoveCursor(c); break; case CTRL_L: /* ctrl+l, clear screen */ @@ -1523,7 +1648,9 @@ int main(int argc, char **argv) { fprintf(stderr,"Usage: kilo \n"); exit(1); } - + StackInit(); + HistoryInit(); + initEditor(); editorSelectSyntaxHighlight(argv[1]); editorOpen(argv[1]); From 170a02959de50c73b082922946df520116f6d56e Mon Sep 17 00:00:00 2001 From: hyunsooda Date: Sat, 20 Oct 2018 03:14:40 +0900 Subject: [PATCH 14/22] Fix bug undo --- kilo.c | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/kilo.c b/kilo.c index 59ba459..2286e4b 100644 --- a/kilo.c +++ b/kilo.c @@ -278,7 +278,6 @@ History SPeek() { return stack.head->data; } - void editorRefreshScreen(); // forward declare to avoid compile exception void editorDelChar(); // forward declare void copyOneLine(); @@ -294,17 +293,13 @@ void undo() { return; temp = SPop(); - + E.cx = temp.cx + 1; + E.cy = temp.cy; + editorRefreshScreen(); for(int i=0; i 1) - // editorDelChar(); - -// printf("%d ", E.cx); -// while(!SIsEmpty()) { -// printf("%d ", SPop().cx); -// } -// exit(1); + + HistoryInit(); } void moveToEnd() { @@ -925,12 +920,22 @@ void editorInsertChar(int c) { erow *row = (filerow >= E.numrows) ? NULL : &E.row[filerow]; if(c == TAB) { + ht.cx = filecol; + ht.cy = filerow; turnOffTracing(); ht.len = 1; + ht.cx = filecol; + ht.cy = filerow; } else if(isspace(c)) { turnOffTracing(); + ht.cx = filecol; + ht.cy = filerow; ht.len = 1; + ht.cx = filecol; + ht.cy = filerow; } else if(ht.isTracing == TRUE) { + ht.cx = filecol; + ht.cy = filerow; ht.len++; } @@ -1517,8 +1522,7 @@ void editorProcessKeypress(int fd) { case ARROW_DOWN: case ARROW_LEFT: case ARROW_RIGHT: - turnOffTracing(); - editorMoveCursor(c); + editorMoveCursor(c); break; case CTRL_L: /* ctrl+l, clear screen */ /* Just refresht the line as side effect. */ From fc16558440531e5aa592a174dfdb46548c553a81 Mon Sep 17 00:00:00 2001 From: hyunsooda Date: Sun, 21 Oct 2018 00:24:32 +0900 Subject: [PATCH 15/22] Add function that perform replace from original string to what you want to replace --- kilo.c | 132 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 132 insertions(+) diff --git a/kilo.c b/kilo.c index 2286e4b..b75f00b 100644 --- a/kilo.c +++ b/kilo.c @@ -126,6 +126,7 @@ enum KEY_ACTION{ CTRL_D = 4, /* Ctrl-d */ CTRL_F = 6, /* Ctrl-f */ CTRL_G = 7, /* Ctrl-g */ + CTRL_P = 16, /* Ctrl-p */ CTRL_T = 20, /* Ctrl-T */ CTRL_X = 24, /* CTRL-X */ CTRL_H = 8, /* Ctrl-h */ @@ -1439,6 +1440,133 @@ void editorMoveCursor(int key) { } } +int flag = 0; + +typedef struct _info { + int* offset; + int len; + int rowidx; + int num; +}Info; + +void replace() { + char target[300] = {0,}; + char org_str[300] = {0,}; + int len = 0; + int c; + char* row_offset; + char* before_row_offset; + int depth = 2; + int offset_depth = 2; + int offset_count = 0; + int target_len = 0; + int ret_cx, ret_cy, ret_rowoff, ret_coloff; + Info* info = (Info*)malloc(sizeof(Info) * depth); + + while(1) { + editorSetStatusMessage("Replace : %s ", target); + editorRefreshScreen(); + c = editorReadKey(0); + + if (c == DEL_KEY || c == BACKSPACE) { + if(len != 0) + target[--len] = '\0'; + } else if(c == ESC) { + editorSetStatusMessage(""); + return; + } else if(c == ENTER) { + if(!strstr(target, "->")) { + editorSetStatusMessage("Bad input! (Usage : original_sring -> target_string)"); + editorRefreshScreen(); + } else { + char* token = strtok(target, "->"); + strcpy(org_str, token); + org_str[strlen(org_str)-1] = '\0'; + token = strtok(NULL, "-> "); + strcpy(target, token); + } + goto repl; + } else if(isprint(c)) { + target[len++] = c; + target[len] = '\0'; + } + } + +repl: + len = 0; // the number of founded string + target_len = strlen(target); + ret_cx = E.cx; + ret_cy = E.cy; + for(int i=0; i E.screencols) { + diff = E.cx - E.screencols; + E.coloff += diff; + E.cx -= diff; + } + if(E.cy > E.screenrows) { + diff = E.cy - E.screenrows; + E.rowoff += diff; + E.cy -= diff; + } + editorDelChar(); + } + for(int j=0; j\n"); From b43cf99b62977b8ab1d7ead3e6d2343287651320 Mon Sep 17 00:00:00 2001 From: hyunsooda Date: Sun, 21 Oct 2018 00:31:20 +0900 Subject: [PATCH 16/22] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index c2e127b..ddd3f17 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ Keys: CTRL-T: Move to first of current line CTRL-D: Move to end of file CTRL-X: Remove one line + CTRL-P: Replace from original string to what you want to replace it Kilo does not depend on any library (not even curses). It uses fairly standard VT100 (and similar terminals) escape sequences. The project is in alpha From 65db6ff0eac41cbb147f6d8bfc12f8f51e4ae341 Mon Sep 17 00:00:00 2001 From: hyunsooda Date: Thu, 1 Nov 2018 00:26:15 +0900 Subject: [PATCH 17/22] Update README.md --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index ddd3f17..f28277e 100644 --- a/README.md +++ b/README.md @@ -3,8 +3,6 @@ Kilo Kilo is a small text editor in less than 1K lines of code (counted with cloc). -A screencast is available here: https://asciinema.org/a/90r2i9bq8po03nazhqtsifksb - Usage: kilo `` Keys: From 31d612d0b9885e0b233fed8268917720ddbb857f Mon Sep 17 00:00:00 2001 From: hyunsooda Date: Sun, 18 Nov 2018 21:00:11 +0900 Subject: [PATCH 18/22] Update README.md --- README.md | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index f28277e..22a36f8 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ Kilo === -Kilo is a small text editor in less than 1K lines of code (counted with cloc). +Kilo is a small text editor Usage: kilo `` @@ -17,15 +17,5 @@ Keys: CTRL-D: Move to end of file CTRL-X: Remove one line CTRL-P: Replace from original string to what you want to replace it + CTRL-Z: Undo -Kilo does not depend on any library (not even curses). It uses fairly standard -VT100 (and similar terminals) escape sequences. The project is in alpha -stage and was written in just a few hours taking code from my other two -projects, load81 and linenoise. - -People are encouraged to use it as a starting point to write other editors -or command line interfaces that are more advanced than the usual REPL -style CLI. - -Kilo was written by Salvatore Sanfilippo aka antirez and is released -under the BSD 2 clause license. From 2036abba97319e217d3a935c1ec77ccdcc04829a Mon Sep 17 00:00:00 2001 From: hyunsooda Date: Sun, 18 Nov 2018 21:08:17 +0900 Subject: [PATCH 19/22] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 22a36f8..64c2f2d 100644 --- a/README.md +++ b/README.md @@ -18,4 +18,5 @@ Keys: CTRL-X: Remove one line CTRL-P: Replace from original string to what you want to replace it CTRL-Z: Undo + .kilorc: Editor configuration file From 4dfd8dd979968661fd256c42b9007d53338d3f5c Mon Sep 17 00:00:00 2001 From: iamwooki Date: Fri, 21 Dec 2018 00:15:15 +0900 Subject: [PATCH 20/22] merge --- kilo.c | 89 +++++++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 72 insertions(+), 17 deletions(-) diff --git a/kilo.c b/kilo.c index b75f00b..53ef35c 100644 --- a/kilo.c +++ b/kilo.c @@ -133,6 +133,8 @@ enum KEY_ACTION{ TAB = 9, /* Tab */ CTRL_L = 12, /* Ctrl+l */ ENTER = 13, /* Enter */ + CTRL_N = 14, /* CTRL_N : display row index number*/ + CTRL_O = 15, /* HELP*/ CTRL_Q = 17, /* Ctrl-q */ CTRL_S = 19, /* Ctrl-s */ CTRL_U = 21, /* Ctrl-u */ @@ -153,6 +155,8 @@ enum KEY_ACTION{ PAGE_DOWN }; +int displayNumberFlag=0; +int helpFlag = 0; void editorSetStatusMessage(const char *fmt, ...); /* =========================== Syntax highlights DB ========================= @@ -1148,25 +1152,56 @@ void editorRefreshScreen(void) { for (y = 0; y < E.screenrows; y++) { int filerow = E.rowoff+y; - if (filerow >= E.numrows) { - if (E.numrows == 0 && y == E.screenrows/3) { - char welcome[100]; - int welcomelen = snprintf(welcome,sizeof(welcome), - "Hyunsoo editor -- verison %s\x1b[0K\r\n", KILO_VERSION); - int padding = (E.screencols-welcomelen)/2; - if (padding) { - abAppend(&ab,"~",1); - padding--; - } - while(padding--) abAppend(&ab," ",1); - abAppend(&ab,welcome,welcomelen); + if (filerow >= E.numrows || helpFlag) { + if ((E.numrows == 0 ||helpFlag )&& y == E.screenrows/3) { + //welcome msg + char welcome[80]; + int welcomelen = snprintf(welcome,sizeof(welcome),"Kilo Editor -- 2018 SP Project HyunSoo,Shin / HyunWook, Hong\r\n"); + int padding = (E.screencols-welcomelen)/3; + if (padding) { + abAppend(&ab,"?",1); + padding--; + } + while(padding--) abAppend(&ab," ",1); + abAppend(&ab,"\x1b[33m",5); + abAppend(&ab,welcome,welcomelen); + //introduce function + char function[15][80]={"\x1b[7mFUNCTIONS\x1b[27m forked from antirez/kilo\r\n", + "\x1b[7mCTRL-S\x1b[27m: Save\r\n","\x1b[7mCTRL-Q\x1b[27m: Quit\r\n","\x1b[7mCTRL-F\x1b[27m: Find string in file (ESC to exit search, arrows to navigate)\r\n", + "\x1b[7mEXTENTION\x1b[27m\r\n", + "\x1b[7mCTRL-C\x1b[27m: Copy one line\r\n","\x1b[7mCTRL-V\x1b[27m: Paste one line\r\n","\x1b[7mCTRL-G\x1b[27m: Move to end of currnet line\r\n", + "\x1b[7mCTRL-T\x1b[27m: Move to first of current line\r\n","\x1b[7mCTRL-P\x1b[27m: Replace from original string to what you want to replace it\r\n", + "\x1b[7mCTRL-Z\x1b[27m: Undo\r\n","\x1b[7mCTRL-D\x1b[27m: Move to end of file\r\n","\x1b[7mCTRL-X\x1b[27m: Remove one line\r\n", + "\x1b[7mCTRL-N\x1b[27m: display row numbers\r\n","\x1b[7mCTRL-O\x1b[27m: Help, Re-display the document you are reading currently\r\n"}; + for(int i=0;i<15;++i){ + int padding = (E.screencols-welcomelen)/3; + if (padding) { + abAppend(&ab,"?",1); + padding--; + } + while(padding--) abAppend(&ab," ",1); + abAppend(&ab,function[i],strlen(function[i])); + } + abAppend(&ab,"\x1b[0m",4); //컬러 해제 + } else { abAppend(&ab,"~\x1b[0K\r\n",7); } continue; } - r = &E.row[filerow]; + //filerow < E.numrows) + r = &E.row[filerow]; //현재 해당하는 줄의 row불러오기 + + //display row Number when CTRL_N was pressed + if(displayNumberFlag){ + char curRow[6]; + snprintf(curRow,sizeof(curRow),"%d ",filerow); + abAppend(&ab,"\x1b[36m",5); + abAppend(&ab,curRow,strlen(curRow)); + abAppend(&ab,"\x1b[0m",4); //컬러 해제 + } + int len = r->rsize - E.coloff; int current_color = -1; @@ -1212,7 +1247,7 @@ void editorRefreshScreen(void) { abAppend(&ab,"\x1b[0K",4); abAppend(&ab,"\x1b[7m",4); char status[80], rstatus[80]; - int len = snprintf(status, sizeof(status), "%.20s - %d lines %s", + int len = snprintf(status, sizeof(status), "FILENAME : %.20s - %d lines %s", E.filename, E.numrows, E.dirty ? "(modified)" : ""); int rlen = snprintf(rstatus, sizeof(rstatus), "%d/%d",E.rowoff+E.cy+1,E.numrows); @@ -1658,6 +1693,26 @@ void editorProcessKeypress(int fd) { case CTRL_L: /* ctrl+l, clear screen */ /* Just refresht the line as side effect. */ break; + case CTRL_N: + if(displayNumberFlag){ + displayNumberFlag = 0; + editorSetStatusMessage("set number off"); + } + else{ + displayNumberFlag = 1; + editorSetStatusMessage("set number on"); + } + break; + case CTRL_O: + if(helpFlag){ + helpFlag = 0; + editorSetStatusMessage("show help off"); + } + else{ + helpFlag = 1; + editorSetStatusMessage("show help on"); + } + break; case ESC: /* Nothing to do for ESC in this mode. */ break; @@ -1724,7 +1779,8 @@ void initEditor(void) { E.dirty = 0; E.filename = NULL; E.syntax = NULL; - + displayNumberFlag = 0; + helpFlag = 0; FILE* fp; char str[100]; char type[100]; @@ -1791,8 +1847,7 @@ int main(int argc, char **argv) { editorSelectSyntaxHighlight(argv[1]); editorOpen(argv[1]); enableRawMode(STDIN_FILENO); - editorSetStatusMessage( - "HELP: Ctrl-S = save | Ctrl-Q = quit | See the README.md for other functions"); + editorSetStatusMessage("if you want HELP Message, Please press CTRL_O button"); while(1) { editorRefreshScreen(); editorProcessKeypress(STDIN_FILENO); From 3358fc66a1a53211f6fc6f5df707a511c51b5623 Mon Sep 17 00:00:00 2001 From: Hyun-soo Shin Date: Mon, 11 Mar 2019 01:13:02 +0900 Subject: [PATCH 21/22] Delete .kilo.c.swo --- .kilo.c.swo | Bin 61440 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 .kilo.c.swo diff --git a/.kilo.c.swo b/.kilo.c.swo deleted file mode 100644 index a2c1be73dec930cd2c50c77e19eb37fdf1a89612..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 61440 zcmeIb37n)?UGH5;5GSk`K37EH<(Y;|Z`D0pGD&7K6MDL9W}4~gp7fGQn1r^vy1KhE z)m5FU>gnlhiUhf@paBdZvLm8^M7?lDUqD4g*<^JK$}1qsd)=;VMo=#I`}?1Bo~5dK z5)eP{y`N84@|&)Dp7WgLfBxs3|8~x^u;=>mQ_)q!dvbgppUeIE`uoqme&F%Bs~?@q zt!%E>n)SNwT=e794P9SpwAPE&qxB2jli%eN?j9>Q&$a4nyU$gsb^B+y)cuL=tJ@UV zroeZF0_&}%p{pJ|Y33K0-thY1Sroc7@wkfbpfo%$GQ(&6{+Z5QQ zz%~W`&!9kS?J>C@p{n2GtGnp`KCJEUCI0sr|F_(B{}KLo(BLnE1sYsf1f;ey}$prZTJ1*ClPK;#4xR_D0S}<)-v^e!Q^22L7`O)<1K&bP z{xwhow}1+m04Kqf;OU?TJO+Fnh5K*7r@(K4w}GDl&jq)DS@2Ad2TulL6QF? zcn|mqa3dH6L*U8a5#WA|0e=SG30@5D0h^!%CP5!~IQT3^k57P)fV%Ei)3LJwu$ppbH7*mI0X6%Dv+p^mrR<-qNHG&;hGx<~&FQiJ3t6G6P7epvkVyRLs z=cC)`^`V7g)AMf|e~2Yu4yrM`QfYdwS#PKXYmIUf5;mh^)TA=ia#XF<;ub1%jm08q z#hs+E3g+%iSaPXaTyDCbM&8x+YOA75<~`fY)17Dc-+a*|%~r9|$|iEv$zeQ*i=4NwutFl_|yjrQ3Ixe)*?TIsyhk(9m_YCjt$>nSTV~IN!&7K&aiDssb&7K*VF65)a^@Yi3{8%(HcK!HFVa%l{M5m`_ zX2y?>PmIsrs7o_*@bK8ksgdJ_Ve*Rl#wSN7=EmUpXtHpeaZ;EZEksk((bVjT!t@z( z>W@aI$7hsad@>rDyfHd6LQZqDp@6xWf-)lc)U*{bF+SqT%SUHU6qJpg9G#k+ogNvT z%|{vsqq9*e8O6;`P0uQuv60!4yvjd4J#}n+cIJ@2ADyH8!psc$%oe7n=T6T;lK$w# z)EOE*9gU7qhcW6#qpga{Xkz@-_$*gvr}D~cbZ)v}W5;N4bY^yZc5b#19iN&Sn^6%n zh3V_ZM+=lU0m0NZ^2{rvnbU>Q@sSA#d_&>X>4}l)8?An{XQpu79K(^Do}x?}F*)

U5ltK+un~~`B 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 Date: Mon, 11 Mar 2019 01:13:11 +0900 Subject: [PATCH 22/22] Delete kilo.c~ --- kilo.c~ | 1346 ------------------------------------------------------- 1 file changed, 1346 deletions(-) delete mode 100644 kilo.c~ diff --git a/kilo.c~ b/kilo.c~ deleted file mode 100644 index bfffc00..0000000 --- a/kilo.c~ +++ /dev/null @@ -1,1346 +0,0 @@ -/* Kilo -- A very simple editor in less than 1-kilo lines of code (as counted - * by "cloc"). Does not depend on libcurses, directly emits VT100 - * escapes on the terminal. - * - * ----------------------------------------------------------------------- - * - * Copyright (C) 2016 Salvatore Sanfilippo - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#define KILO_VERSION "0.0.1" - -#define _BSD_SOURCE -#define _GNU_SOURCE - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* Syntax highlight types */ -#define HL_NORMAL 0 -#define HL_NONPRINT 1 -#define HL_COMMENT 2 /* Single line comment. */ -#define HL_MLCOMMENT 3 /* Multi-line comment. */ -#define HL_KEYWORD1 4 -#define HL_KEYWORD2 5 -#define HL_STRING 6 -#define HL_NUMBER 7 -#define HL_MATCH 8 /* Search match. */ - -#define HL_HIGHLIGHT_STRINGS (1<<0) -#define HL_HIGHLIGHT_NUMBERS (1<<1) - -struct editorSyntax { - char **filematch; - char **keywords; - char singleline_comment_start[2]; - char multiline_comment_start[3]; - char multiline_comment_end[3]; - int flags; -}; - -/* This structure represents a single line of the file we are editing. */ -typedef struct erow { - int idx; /* Row index in the file, zero-based. */ - int size; /* Size of the row, excluding the null term. */ - int rsize; /* Size of the rendered row. */ - char *chars; /* Row content. */ - char *render; /* Row content "rendered" for screen (for TABs). */ - unsigned char *hl; /* Syntax highlight type for each character in render.*/ - int hl_oc; /* Row had open comment at end in last syntax highlight - check. */ -} erow; - -typedef struct hlcolor { - int r,g,b; -} hlcolor; - -struct editorConfig { - int cx,cy; /* Cursor x and y position in characters */ - int rowoff; /* Offset of row displayed. */ - int coloff; /* Offset of column displayed. */ - int screenrows; /* Number of rows that we can show */ - int screencols; /* Number of cols that we can show */ - int numrows; /* Number of rows */ - int rawmode; /* Is terminal raw mode enabled? */ - erow *row; /* Rows */ - int dirty; /* File modified but not saved. */ - char *filename; /* Currently open filename */ - char statusmsg[80]; - time_t statusmsg_time; - struct editorSyntax *syntax; /* Current syntax highlight, or NULL. */ -}; - -static struct editorConfig E; - -enum KEY_ACTION{ - KEY_NULL = 0, /* NULL */ - CTRL_C = 3, /* Ctrl-c */ - CTRL_D = 4, /* Ctrl-d */ - CTRL_F = 6, /* Ctrl-f */ - CTRL_G = 7, /* Ctrl-g */ - CTRL_T = 20, /* Ctrl-T */ - CTRL_H = 8, /* Ctrl-h */ - TAB = 9, /* Tab */ - CTRL_L = 12, /* Ctrl+l */ - ENTER = 13, /* Enter */ - CTRL_Q = 17, /* Ctrl-q */ - CTRL_S = 19, /* Ctrl-s */ - CTRL_U = 21, /* Ctrl-u */ - ESC = 27, /* Escape */ - BACKSPACE = 127, /* Backspace */ - /* The following are just soft codes, not really reported by the - * terminal directly. */ - ARROW_LEFT = 1000, - ARROW_RIGHT, - ARROW_UP, - ARROW_DOWN, - DEL_KEY, - HOME_KEY, - END_KEY, - PAGE_UP, - PAGE_DOWN -}; - -void editorSetStatusMessage(const char *fmt, ...); - -/* =========================== Syntax highlights DB ========================= - * - * In order to add a new syntax, define two arrays with a list of file name - * matches and keywords. The file name matches are used in order to match - * a given syntax with a given file name: if a match pattern starts with a - * dot, it is matched as the last past of the filename, for example ".c". - * Otherwise the pattern is just searched inside the filenme, like "Makefile"). - * - * The list of keywords to highlight is just a list of words, however if they - * a trailing '|' character is added at the end, they are highlighted in - * a different color, so that you can have two different sets of keywords. - * - * Finally add a stanza in the HLDB global variable with two two arrays - * of strings, and a set of flags in order to enable highlighting of - * comments and numbers. - * - * The characters for single and multi line comments must be exactly two - * and must be provided as well (see the C language example). - * - * There is no support to highlight patterns currently. */ - -/* C / C++ */ -char *C_HL_extensions[] = {".c",".cpp",NULL}; -char *C_HL_keywords[] = { - /* A few C / C++ keywords */ - "switch","if","while","for","break","continue","return","else", - "struct","union","typedef","static","enum","class", - /* C types */ - "int|","long|","double|","float|","char|","unsigned|","signed|", - "void|",NULL -}; - -/* Here we define an array of syntax highlights by extensions, keywords, - * comments delimiters and flags. */ -struct editorSyntax HLDB[] = { - { - /* C / C++ */ - C_HL_extensions, - C_HL_keywords, - "//","/*","*/", - HL_HIGHLIGHT_STRINGS | HL_HIGHLIGHT_NUMBERS - } -}; - -void editorRefreshScreen(); - -void moveToEnd() { - int factor = 0; - - if(E.numrows > E.screencols) - factor = E.numrows / E.screenrows; - - if(factor) { - if (E.cy == E.screencols-2) - E.cy = E.numrows - 1; - E.coloff++; - editorRefreshScreen(); - moveToEnd(); - } - E.cy = E.numrows - 3 - E.rowoff; - - -} - -void moveToFirst() { - E.cy = 0; -} - -void moveToLineEnd() { - E.cx = E.row[E.cy].size; -} - -#define HLDB_ENTRIES (sizeof(HLDB)/sizeof(HLDB[0])) - -/* ======================= Low level terminal handling ====================== */ - -static struct termios orig_termios; /* In order to restore at exit.*/ - -void disableRawMode(int fd) { - /* Don't even check the return value as it's too late. */ - if (E.rawmode) { - tcsetattr(fd,TCSAFLUSH,&orig_termios); - E.rawmode = 0; - } -} - -/* Called at exit to avoid remaining in raw mode. */ -void editorAtExit(void) { - disableRawMode(STDIN_FILENO); -} - -/* Raw mode: 1960 magic shit. */ -int enableRawMode(int fd) { - struct termios raw; - - if (E.rawmode) return 0; /* Already enabled. */ - if (!isatty(STDIN_FILENO)) goto fatal; - atexit(editorAtExit); - if (tcgetattr(fd,&orig_termios) == -1) goto fatal; - - raw = orig_termios; /* modify the original mode */ - /* input modes: no break, no CR to NL, no parity check, no strip char, - * no start/stop output control. */ - raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON); - /* output modes - disable post processing */ - raw.c_oflag &= ~(OPOST); - /* control modes - set 8 bit chars */ - raw.c_cflag |= (CS8); - /* local modes - choing off, canonical off, no extended functions, - * no signal chars (^Z,^C) */ - raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG); - /* control chars - set return condition: min number of bytes and timer. */ - raw.c_cc[VMIN] = 0; /* Return each byte, or zero for timeout. */ - raw.c_cc[VTIME] = 1; /* 100 ms timeout (unit is tens of second). */ - - /* put terminal in raw mode after flushing */ - if (tcsetattr(fd,TCSAFLUSH,&raw) < 0) goto fatal; - E.rawmode = 1; - return 0; - -fatal: - errno = ENOTTY; - return -1; -} - -/* Read a key from the terminal put in raw mode, trying to handle - * escape sequences. */ -int editorReadKey(int fd) { - int nread; - char c, seq[3]; - while ((nread = read(fd,&c,1)) == 0); - if (nread == -1) exit(1); - - while(1) { - switch(c) { - case ESC: /* escape sequence */ - /* If this is just an ESC, we'll timeout here. */ - if (read(fd,seq,1) == 0) return ESC; - if (read(fd,seq+1,1) == 0) return ESC; - - /* ESC [ sequences. */ - if (seq[0] == '[') { - if (seq[1] >= '0' && seq[1] <= '9') { - /* Extended escape, read additional byte. */ - if (read(fd,seq+2,1) == 0) return ESC; - if (seq[2] == '~') { - switch(seq[1]) { - case '3': return DEL_KEY; - case '5': return PAGE_UP; - case '6': return PAGE_DOWN; - } - } - } else { - switch(seq[1]) { - case 'A': return ARROW_UP; - case 'B': return ARROW_DOWN; - case 'C': return ARROW_RIGHT; - case 'D': return ARROW_LEFT; - case 'H': return HOME_KEY; - case 'F': return END_KEY; - } - } - } - - /* ESC O sequences. */ - else if (seq[0] == 'O') { - switch(seq[1]) { - case 'H': return HOME_KEY; - case 'F': return END_KEY; - } - } - break; - default: - return c; - } - } -} - -/* Use the ESC [6n escape sequence to query the horizontal cursor position - * and return it. On error -1 is returned, on success the position of the - * cursor is stored at *rows and *cols and 0 is returned. */ -int getCursorPosition(int ifd, int ofd, int *rows, int *cols) { - char buf[32]; - unsigned int i = 0; - - /* Report cursor location */ - if (write(ofd, "\x1b[6n", 4) != 4) return -1; - - /* Read the response: ESC [ rows ; cols R */ - while (i < sizeof(buf)-1) { - if (read(ifd,buf+i,1) != 1) break; - if (buf[i] == 'R') break; - i++; - } - buf[i] = '\0'; - - /* Parse it. */ - if (buf[0] != ESC || buf[1] != '[') return -1; - if (sscanf(buf+2,"%d;%d",rows,cols) != 2) return -1; - return 0; -} - -/* Try to get the number of columns in the current terminal. If the ioctl() - * call fails the function will try to query the terminal itself. - * Returns 0 on success, -1 on error. */ -int getWindowSize(int ifd, int ofd, int *rows, int *cols) { - struct winsize ws; - - if (ioctl(1, TIOCGWINSZ, &ws) == -1 || ws.ws_col == 0) { - /* ioctl() failed. Try to query the terminal itself. */ - int orig_row, orig_col, retval; - - /* Get the initial position so we can restore it later. */ - retval = getCursorPosition(ifd,ofd,&orig_row,&orig_col); - if (retval == -1) goto failed; - - /* Go to right/bottom margin and get position. */ - if (write(ofd,"\x1b[999C\x1b[999B",12) != 12) goto failed; - retval = getCursorPosition(ifd,ofd,rows,cols); - if (retval == -1) goto failed; - - /* Restore position. */ - char seq[32]; - snprintf(seq,32,"\x1b[%d;%dH",orig_row,orig_col); - if (write(ofd,seq,strlen(seq)) == -1) { - /* Can't recover... */ - } - return 0; - } else { - *cols = ws.ws_col; - *rows = ws.ws_row; - return 0; - } - -failed: - return -1; -} - -/* ====================== Syntax highlight color scheme ==================== */ - -int is_separator(int c) { - return c == '\0' || isspace(c) || strchr(",.()+-/*=~%[];",c) != NULL; -} - -/* Return true if the specified row last char is part of a multi line comment - * that starts at this row or at one before, and does not end at the end - * of the row but spawns to the next row. */ -int editorRowHasOpenComment(erow *row) { - if (row->hl && row->rsize && row->hl[row->rsize-1] == HL_MLCOMMENT && - (row->rsize < 2 || (row->render[row->rsize-2] != '*' || - row->render[row->rsize-1] != '/'))) return 1; - - return 0; -} - -/* Set every byte of row->hl (that corresponds to every character in the line) - * to the right syntax highlight type (HL_* defines). */ -void editorUpdateSyntax(erow *row) { - row->hl = realloc(row->hl,row->rsize); - memset(row->hl,HL_NORMAL,row->rsize); - - if (E.syntax == NULL) return; /* No syntax, everything is HL_NORMAL. */ - - int i, prev_sep, in_string, in_comment; - int rowidx_backup; - char *p; - char **keywords = E.syntax->keywords; - char *scs = E.syntax->singleline_comment_start; - char *mcs = E.syntax->multiline_comment_start; - char *mce = E.syntax->multiline_comment_end; - char* temp; - - /* Point to the first non-space char. */ - p = row->render; - i = 0; /* Current char offset */ - while(*p && isspace(*p)) { - p++; - i++; - } - prev_sep = 1; /* Tell the parser if 'i' points to start of word. */ - in_string = 0; /* Are we inside "" or '' ? */ - in_comment = 0; /* Are we inside multi-line comment? */ - - rowidx_backup = row->idx; - temp = p; - while(row->idx > 0 && *temp != mce[0] && *(temp+1) != mce[1]) { - if(editorRowHasOpenComment(&E.row[row->idx-1])) - in_comment = 1; - row->idx--; - temp = E.row[row->idx].render; - while(*temp && isspace(*temp)) - temp++; - } - row->idx = rowidx_backup; - - rowidx_backup = row->idx; - temp = p; - if(!in_comment && *(temp) == mce[0] && *(temp+1) == mce[1]) { - while(row->idx > 0) { - while(*temp && isspace(*temp)) - temp++; - if(*temp != mcs[0] && *(temp+1) != mcs[1]) - in_comment = 0; - else { - in_comment = 1; - break; - } - row->idx--; - temp = E.row[row->idx].render; - } - } - row->idx = rowidx_backup; - - while(*p) { - /* Handle // comments. */ - if (prev_sep && *p == scs[0] && *(p+1) == scs[1]) { - /* From here to end is a comment */ - memset(row->hl+i,HL_COMMENT,row->size-i); - return; - } - - /* Handle multi line comments. */ - if (in_comment) { - row->hl[i] = HL_MLCOMMENT; - if (*p == mce[0] && *(p+1) == mce[1]) { - row->hl[i+1] = HL_MLCOMMENT; - p += 2; i += 2; - in_comment = 0; - prev_sep = 1; - continue; - } else { - prev_sep = 0; - p+=1; i+=1; - continue; - } - } else if (*p == mcs[0] && *(p+1) == mcs[1]) { - row->hl[i] = HL_MLCOMMENT; - row->hl[i+1] = HL_MLCOMMENT; - p += 2; i += 2; - prev_sep = 0; - continue; - } - - /* Handle "" and '' */ - if (in_string) { - row->hl[i] = HL_STRING; - if (*p == '\\') { - row->hl[i+1] = HL_STRING; - p += 2; i += 2; - prev_sep = 0; - continue; - } - if (*p == in_string) in_string = 0; - p++; i++; - continue; - } else { - if (*p == '"' || *p == '\'') { - in_string = *p; - row->hl[i] = HL_STRING; - p++; i++; - prev_sep = 0; - continue; - } - } - - /* Handle non printable chars. */ - if (!isprint(*p)) { - row->hl[i] = HL_NONPRINT; - p++; i++; - prev_sep = 0; - continue; - } - - /* Handle numbers */ - if ((isdigit(*p) && (prev_sep || row->hl[i-1] == HL_NUMBER)) || - (*p == '.' && i >0 && row->hl[i-1] == HL_NUMBER)) { - row->hl[i] = HL_NUMBER; - p++; i++; - prev_sep = 0; - continue; - } - - /* Handle keywords and lib calls */ - if (prev_sep) { - int j; - for (j = 0; keywords[j]; j++) { - int klen = strlen(keywords[j]); - int kw2 = keywords[j][klen-1] == '|'; - if (kw2) klen--; - - if (!memcmp(p,keywords[j],klen) && - is_separator(*(p+klen))) - { - /* Keyword */ - memset(row->hl+i,kw2 ? HL_KEYWORD2 : HL_KEYWORD1,klen); - p += klen; - i += klen; - break; - } - } - if (keywords[j] != NULL) { - prev_sep = 0; - continue; /* We had a keyword match */ - } - } - - /* Not special chars */ - prev_sep = is_separator(*p); - p++; i++; - } - - /* Propagate syntax change to the next row if the open commen - * state changed. This may recursively affect all the following rows - * in the file. */ - int oc = editorRowHasOpenComment(row); - if (row->hl_oc != oc && row->idx+1 < E.numrows) - editorUpdateSyntax(&E.row[row->idx+1]); - row->hl_oc = oc; -} - -/* Maps syntax highlight token types to terminal colors. */ -int editorSyntaxToColor(int hl) { - switch(hl) { - case HL_COMMENT: - case HL_MLCOMMENT: return 36; /* cyan */ - case HL_KEYWORD1: return 33; /* yellow */ - case HL_KEYWORD2: return 32; /* green */ - case HL_STRING: return 35; /* magenta */ - case HL_NUMBER: return 37; /* white */ - case HL_MATCH: return 34; /* blu */ - default: return 37; /* white */ - } -} - -/* Select the syntax highlight scheme depending on the filename, - * setting it in the global state E.syntax. */ -void editorSelectSyntaxHighlight(char *filename) { - for (unsigned int j = 0; j < HLDB_ENTRIES; j++) { - struct editorSyntax *s = HLDB+j; - unsigned int i = 0; - while(s->filematch[i]) { - char *p; - int patlen = strlen(s->filematch[i]); - if ((p = strstr(filename,s->filematch[i])) != NULL) { - if (s->filematch[i][0] != '.' || p[patlen] == '\0') { - E.syntax = s; - return; - } - } - i++; - } - } -} - -/* ======================= Editor rows implementation ======================= */ - -/* Update the rendered version and the syntax highlight of a row. */ -void editorUpdateRow(erow *row) { - int tabs = 0, nonprint = 0, j, idx; - - /* Create a version of the row we can directly print on the screen, - * respecting tabs, substituting non printable characters with '?'. */ - free(row->render); - for (j = 0; j < row->size; j++) - if (row->chars[j] == TAB) tabs++; - - row->render = malloc(row->size + tabs*8 + nonprint*9 + 1); - idx = 0; - for (j = 0; j < row->size; j++) { - if (row->chars[j] == TAB) { - row->render[idx++] = ' '; - while((idx+1) % 8 != 0) row->render[idx++] = ' '; - } else { - row->render[idx++] = row->chars[j]; - } - } - row->rsize = idx; - row->render[idx] = '\0'; - - /* Update the syntax highlighting attributes of the row. */ - editorUpdateSyntax(row); -} - -/* Insert a row at the specified position, shifting the other rows on the bottom - * if required. */ -void editorInsertRow(int at, char *s, size_t len) { - if (at > E.numrows) return; - E.row = realloc(E.row,sizeof(erow)*(E.numrows+1)); - if (at != E.numrows) { - memmove(E.row+at+1,E.row+at,sizeof(E.row[0])*(E.numrows-at)); - for (int j = at+1; j <= E.numrows; j++) E.row[j].idx++; - } - E.row[at].size = len; - E.row[at].chars = malloc(len+1); - memcpy(E.row[at].chars,s,len+1); - E.row[at].hl = NULL; - E.row[at].hl_oc = 0; - E.row[at].render = NULL; - E.row[at].rsize = 0; - E.row[at].idx = at; - editorUpdateRow(E.row+at); - E.numrows++; - E.dirty++; -} - -/* Free row's heap allocated stuff. */ -void editorFreeRow(erow *row) { - free(row->render); - free(row->chars); - free(row->hl); -} - -/* Remove the row at the specified position, shifting the remainign on the - * top. */ -void editorDelRow(int at) { - erow *row; - - if (at >= E.numrows) return; - row = E.row+at; - editorFreeRow(row); - memmove(E.row+at,E.row+at+1,sizeof(E.row[0])*(E.numrows-at-1)); - for (int j = at; j < E.numrows-1; j++) E.row[j].idx--; - E.numrows--; - E.dirty++; -} - -/* Turn the editor rows into a single heap-allocated string. - * Returns the pointer to the heap-allocated string and populate the - * integer pointed by 'buflen' with the size of the string, escluding - * the final nulterm. */ -char *editorRowsToString(int *buflen) { - char *buf = NULL, *p; - int totlen = 0; - int j; - - /* Compute count of bytes */ - for (j = 0; j < E.numrows; j++) - totlen += E.row[j].size+1; /* +1 is for "\n" at end of every row */ - *buflen = totlen; - totlen++; /* Also make space for nulterm */ - - p = buf = malloc(totlen); - for (j = 0; j < E.numrows; j++) { - memcpy(p,E.row[j].chars,E.row[j].size); - p += E.row[j].size; - *p = '\n'; - p++; - } - *p = '\0'; - return buf; -} - -/* Insert a character at the specified position in a row, moving the remaining - * chars on the right if needed. */ -void editorRowInsertChar(erow *row, int at, int c) { - if (at > row->size) { - /* Pad the string with spaces if the insert location is outside the - * current length by more than a single character. */ - int padlen = at-row->size; - /* In the next line +2 means: new char and null term. */ - row->chars = realloc(row->chars,row->size+padlen+2); - memset(row->chars+row->size,' ',padlen); - row->chars[row->size+padlen+1] = '\0'; - row->size += padlen+1; - } else { - /* If we are in the middle of the string just make space for 1 new - * char plus the (already existing) null term. */ - row->chars = realloc(row->chars,row->size+2); - memmove(row->chars+at+1,row->chars+at,row->size-at+1); - row->size++; - } - row->chars[at] = c; - editorUpdateRow(row); - E.dirty++; -} - -/* Append the string 's' at the end of a row */ -void editorRowAppendString(erow *row, char *s, size_t len) { - row->chars = realloc(row->chars,row->size+len+1); - memcpy(row->chars+row->size,s,len); - row->size += len; - row->chars[row->size] = '\0'; - editorUpdateRow(row); - E.dirty++; -} - -/* Delete the character at offset 'at' from the specified row. */ -void editorRowDelChar(erow *row, int at) { - if (row->size <= at) return; - memmove(row->chars+at,row->chars+at+1,row->size-at); - editorUpdateRow(row); - row->size--; - E.dirty++; -} - -/* Insert the specified char at the current prompt position. */ -void editorInsertChar(int c) { - int filerow = E.rowoff+E.cy; - int filecol = E.coloff+E.cx; - erow *row = (filerow >= E.numrows) ? NULL : &E.row[filerow]; - - /* If the row where the cursor is currently located does not exist in our - * logical representaion of the file, add enough empty rows as needed. */ - if (!row) { - while(E.numrows <= filerow) - editorInsertRow(E.numrows,"",0); - } - row = &E.row[filerow]; - editorRowInsertChar(row,filecol,c); - if (E.cx == E.screencols-1) - E.coloff++; - else - E.cx++; - E.dirty++; -} - -/* Inserting a newline is slightly complex as we have to handle inserting a - * newline in the middle of a line, splitting the line as needed. */ -void editorInsertNewline(void) { - int filerow = E.rowoff+E.cy; - int filecol = E.coloff+E.cx; - erow *row = (filerow >= E.numrows) ? NULL : &E.row[filerow]; - - if (!row) { - if (filerow == E.numrows) { - editorInsertRow(filerow,"",0); - goto fixcursor; - } - return; - } - /* If the cursor is over the current line size, we want to conceptually - * think it's just over the last character. */ - if (filecol >= row->size) filecol = row->size; - if (filecol == 0) { - editorInsertRow(filerow,"",0); - } else { - /* We are in the middle of a line. Split it between two rows. */ - editorInsertRow(filerow+1,row->chars+filecol,row->size-filecol); - row = &E.row[filerow]; - row->chars[filecol] = '\0'; - row->size = filecol; - editorUpdateRow(row); - } -fixcursor: - if (E.cy == E.screenrows-1) { - E.rowoff++; - } else { - E.cy++; - } - E.cx = 0; - E.coloff = 0; -} - -/* Delete the char at the current prompt position. */ -void editorDelChar() { - int filerow = E.rowoff+E.cy; - int filecol = E.coloff+E.cx; - erow *row = (filerow >= E.numrows) ? NULL : &E.row[filerow]; - - if (!row || (filecol == 0 && filerow == 0)) return; - if (filecol == 0) { - /* Handle the case of column 0, we need to move the current line - * on the right of the previous one. */ - filecol = E.row[filerow-1].size; - editorRowAppendString(&E.row[filerow-1],row->chars,row->size); - editorDelRow(filerow); - row = NULL; - if (E.cy == 0) - E.rowoff--; - else - E.cy--; - E.cx = filecol; - if (E.cx >= E.screencols) { - int shift = (E.screencols-E.cx)+1; - E.cx -= shift; - E.coloff += shift; - } - } else { - editorRowDelChar(row,filecol-1); - if (E.cx == 0 && E.coloff) - E.coloff--; - else - E.cx--; - } - if (row) editorUpdateRow(row); - E.dirty++; -} - -/* Load the specified program in the editor memory and returns 0 on success - * or 1 on error. */ -int editorOpen(char *filename) { - FILE *fp; - - E.dirty = 0; - free(E.filename); - E.filename = strdup(filename); - - fp = fopen(filename,"r"); - if (!fp) { - if (errno != ENOENT) { - perror("Opening file"); - exit(1); - } - return 1; - } - - char *line = NULL; - size_t linecap = 0; - ssize_t linelen; - while((linelen = getline(&line,&linecap,fp)) != -1) { - if (linelen && (line[linelen-1] == '\n' || line[linelen-1] == '\r')) - line[--linelen] = '\0'; - editorInsertRow(E.numrows,line,linelen); - } - free(line); - fclose(fp); - E.dirty = 0; - return 0; -} - -/* Save the current file on disk. Return 0 on success, 1 on error. */ -int editorSave(void) { - int len; - char *buf = editorRowsToString(&len); - int fd = open(E.filename,O_RDWR|O_CREAT,0644); - if (fd == -1) goto writeerr; - - /* Use truncate + a single write(2) call in order to make saving - * a bit safer, under the limits of what we can do in a small editor. */ - if (ftruncate(fd,len) == -1) goto writeerr; - if (write(fd,buf,len) != len) goto writeerr; - - close(fd); - free(buf); - E.dirty = 0; - editorSetStatusMessage("%d bytes written on disk", len); - return 0; - -writeerr: - free(buf); - if (fd != -1) close(fd); - editorSetStatusMessage("Can't save! I/O error: %s",strerror(errno)); - return 1; -} - -/* ============================= Terminal update ============================ */ - -/* We define a very simple "append buffer" structure, that is an heap - * allocated string where we can append to. This is useful in order to - * write all the escape sequences in a buffer and flush them to the standard - * output in a single call, to avoid flickering effects. */ -struct abuf { - char *b; - int len; -}; - -#define ABUF_INIT {NULL,0} - -void abAppend(struct abuf *ab, const char *s, int len) { - char *new = realloc(ab->b,ab->len+len); - - if (new == NULL) return; - memcpy(new+ab->len,s,len); - ab->b = new; - ab->len += len; -} - -void abFree(struct abuf *ab) { - free(ab->b); -} - -/* This function writes the whole screen using VT100 escape characters - * starting from the logical state of the editor in the global state 'E'. */ -void editorRefreshScreen(void) { - int y; - erow *r; - char buf[32]; - struct abuf ab = ABUF_INIT; - - abAppend(&ab,"\x1b[?25l",6); /* Hide cursor. */ - abAppend(&ab,"\x1b[H",3); /* Go home. */ - for (y = 0; y < E.screenrows; y++) { - int filerow = E.rowoff+y; - - if (filerow >= E.numrows) { - if (E.numrows == 0 && y == E.screenrows/3) { - char welcome[80]; - int welcomelen = snprintf(welcome,sizeof(welcome), - "Kilo editor -- verison %s\x1b[0K\r\n", KILO_VERSION); - int padding = (E.screencols-welcomelen)/2; - if (padding) { - abAppend(&ab,"~",1); - padding--; - } - while(padding--) abAppend(&ab," ",1); - abAppend(&ab,welcome,welcomelen); - } else { - abAppend(&ab,"~\x1b[0K\r\n",7); - } - continue; - } - - r = &E.row[filerow]; - - int len = r->rsize - E.coloff; - int current_color = -1; - if (len > 0) { - if (len > E.screencols) len = E.screencols; - char *c = r->render+E.coloff; - unsigned char *hl = r->hl+E.coloff; - int j; - for (j = 0; j < len; j++) { - if (hl[j] == HL_NONPRINT) { - char sym; - abAppend(&ab,"\x1b[7m",4); - if (c[j] <= 26) - sym = '@'+c[j]; - else - sym = '?'; - abAppend(&ab,&sym,1); - abAppend(&ab,"\x1b[0m",4); - } else if (hl[j] == HL_NORMAL) { - if (current_color != -1) { - abAppend(&ab,"\x1b[39m",5); - current_color = -1; - } - abAppend(&ab,c+j,1); - } else { - int color = editorSyntaxToColor(hl[j]); - if (color != current_color) { - char buf[16]; - int clen = snprintf(buf,sizeof(buf),"\x1b[%dm",color); - current_color = color; - abAppend(&ab,buf,clen); - } - abAppend(&ab,c+j,1); - } - } - } - abAppend(&ab,"\x1b[39m",5); - abAppend(&ab,"\x1b[0K",4); - abAppend(&ab,"\r\n",2); - } - - /* Create a two rows status. First row: */ - abAppend(&ab,"\x1b[0K",4); - abAppend(&ab,"\x1b[7m",4); - char status[80], rstatus[80]; - int len = snprintf(status, sizeof(status), "%.20s - %d lines %s", - E.filename, E.numrows, E.dirty ? "(modified)" : ""); - int rlen = snprintf(rstatus, sizeof(rstatus), - "%d/%d",E.rowoff+E.cy+1,E.numrows); - if (len > E.screencols) len = E.screencols; - abAppend(&ab,status,len); - while(len < E.screencols) { - if (E.screencols - len == rlen) { - abAppend(&ab,rstatus,rlen); - break; - } else { - abAppend(&ab," ",1); - len++; - } - } - abAppend(&ab,"\x1b[0m\r\n",6); - - /* Second row depends on E.statusmsg and the status message update time. */ - abAppend(&ab,"\x1b[0K",4); - int msglen = strlen(E.statusmsg); - if (msglen && time(NULL)-E.statusmsg_time < 5) - abAppend(&ab,E.statusmsg,msglen <= E.screencols ? msglen : E.screencols); - - /* Put cursor at its current position. Note that the horizontal position - * at which the cursor is displayed may be different compared to 'E.cx' - * because of TABs. */ - int j; - int cx = 1; - int filerow = E.rowoff+E.cy; - erow *row = (filerow >= E.numrows) ? NULL : &E.row[filerow]; - if (row) { - for (j = E.coloff; j < (E.cx+E.coloff); j++) { - if (j < row->size && row->chars[j] == TAB) cx += 7-((cx)%8); - cx++; - } - } - snprintf(buf,sizeof(buf),"\x1b[%d;%dH",E.cy+1,cx); - abAppend(&ab,buf,strlen(buf)); - abAppend(&ab,"\x1b[?25h",6); /* Show cursor. */ - write(STDOUT_FILENO,ab.b,ab.len); - abFree(&ab); -} - -/* Set an editor status message for the second line of the status, at the - * end of the screen. */ -void editorSetStatusMessage(const char *fmt, ...) { - va_list ap; - va_start(ap,fmt); - vsnprintf(E.statusmsg,sizeof(E.statusmsg),fmt,ap); - va_end(ap); - E.statusmsg_time = time(NULL); -} - -/* =============================== Find mode ================================ */ - -#define KILO_QUERY_LEN 256 - -void editorFind(int fd) { - char query[KILO_QUERY_LEN+1] = {0}; - int qlen = 0; - int last_match = -1; /* Last line where a match was found. -1 for none. */ - int find_next = 0; /* if 1 search next, if -1 search prev. */ - int saved_hl_line = -1; /* No saved HL */ - char *saved_hl = NULL; - -#define FIND_RESTORE_HL do { \ - if (saved_hl) { \ - memcpy(E.row[saved_hl_line].hl,saved_hl, E.row[saved_hl_line].rsize); \ - free(saved_hl); \ - saved_hl = NULL; \ - } \ -} while (0) - - /* Save the cursor position in order to restore it later. */ - int saved_cx = E.cx, saved_cy = E.cy; - int saved_coloff = E.coloff, saved_rowoff = E.rowoff; - - while(1) { - editorSetStatusMessage( - "Search: %s (Use ESC/Arrows/Enter)", query); - editorRefreshScreen(); - - int c = editorReadKey(fd); - if (c == DEL_KEY || c == CTRL_H || c == BACKSPACE) { - if (qlen != 0) query[--qlen] = '\0'; - last_match = -1; - } else if (c == ESC || c == ENTER) { - if (c == ESC) { - E.cx = saved_cx; E.cy = saved_cy; - E.coloff = saved_coloff; E.rowoff = saved_rowoff; - } - FIND_RESTORE_HL; - editorSetStatusMessage(""); - return; - } else if (c == ARROW_RIGHT || c == ARROW_DOWN) { - find_next = 1; - } else if (c == ARROW_LEFT || c == ARROW_UP) { - find_next = -1; - } else if (isprint(c)) { - if (qlen < KILO_QUERY_LEN) { - query[qlen++] = c; - query[qlen] = '\0'; - last_match = -1; - } - } - - /* Search occurrence. */ - if (last_match == -1) find_next = 1; - if (find_next) { - char *match = NULL; - int match_offset = 0; - int i, current = last_match; - - for (i = 0; i < E.numrows; i++) { - current += find_next; - if (current == -1) current = E.numrows-1; - else if (current == E.numrows) current = 0; - match = strstr(E.row[current].render,query); - if (match) { - match_offset = match-E.row[current].render; - break; - } - } - find_next = 0; - - /* Highlight */ - FIND_RESTORE_HL; - - if (match) { - erow *row = &E.row[current]; - last_match = current; - if (row->hl) { - saved_hl_line = current; - saved_hl = malloc(row->rsize); - memcpy(saved_hl,row->hl,row->rsize); - memset(row->hl+match_offset,HL_MATCH,qlen); - } - E.cy = 0; - E.cx = match_offset; - E.rowoff = current; - E.coloff = 0; - /* Scroll horizontally as needed. */ - if (E.cx > E.screencols) { - int diff = E.cx - E.screencols; - E.cx -= diff; - E.coloff += diff; - } - } - } - } -} - -/* ========================= Editor events handling ======================== */ - -/* Handle cursor position change because arrow keys were pressed. */ -void editorMoveCursor(int key) { - int filerow = E.rowoff+E.cy; - int filecol = E.coloff+E.cx; - int rowlen; - erow *row = (filerow >= E.numrows) ? NULL : &E.row[filerow]; - - switch(key) { - case ARROW_LEFT: - if (E.cx == 0) { - if (E.coloff) { - E.coloff--; - } else { - if (filerow > 0) { - E.cy--; - E.cx = E.row[filerow-1].size; - if (E.cx > E.screencols-1) { - E.coloff = E.cx-E.screencols+1; - E.cx = E.screencols-1; - } - } - } - } else { - E.cx -= 1; - } - break; - case ARROW_RIGHT: - if (row && filecol < row->size) { - if (E.cx == E.screencols-1) { - E.coloff++; - } else { - E.cx += 1; - } - } else if (row && filecol == row->size) { - E.cx = 0; - E.coloff = 0; - if (E.cy == E.screenrows-1) { - E.rowoff++; - } else { - E.cy += 1; - } - } - break; - case ARROW_UP: - if (E.cy == 0) { - if (E.rowoff) E.rowoff--; - } else { - E.cy -= 1; - } - break; - case ARROW_DOWN: - if (filerow < E.numrows) { - if (E.cy == E.screenrows-1) { - E.rowoff++; - } else { - E.cy += 1; - } - } - break; - } - /* Fix cx if the current line has not enough chars. */ - filerow = E.rowoff+E.cy; - filecol = E.coloff+E.cx; - row = (filerow >= E.numrows) ? NULL : &E.row[filerow]; - rowlen = row ? row->size : 0; - if (filecol > rowlen) { - E.cx -= filecol-rowlen; - if (E.cx < 0) { - E.coloff += E.cx; - E.cx = 0; - } - } -} - -/* Process events arriving from the standard input, which is, the user - * is typing stuff on the terminal. */ -#define KILO_QUIT_TIMES 3 -void editorProcessKeypress(int fd) { - /* When the file is modified, requires Ctrl-q to be pressed N times - * before actually quitting. */ - static int quit_times = KILO_QUIT_TIMES; - - int c = editorReadKey(fd); - switch(c) { - case ENTER: /* Enter */ - editorInsertNewline(); - break; - case CTRL_C: /* Ctrl-c */ - /* We ignore ctrl-c, it can't be so simple to lose the changes - * to the edited file. */ - break; - case CTRL_G: - moveToEnd(); - break; - case CTRL_T: - moveToFirst(); - break; - case CTRL_D: - moveToLineEnd(); - break; - case CTRL_Q: /* Ctrl-q */ - /* Quit if the file was already saved. */ - if (E.dirty && quit_times) { - editorSetStatusMessage("WARNING!!! File has unsaved changes. " - "Press Ctrl-Q %d more times to quit.", quit_times); - quit_times--; - return; - } - struct abuf temp = ABUF_INIT; - abAppend(&temp, "\x1b[2J", 4); - abAppend(&temp, "\x1b[H", 3); - - write(STDOUT_FILENO, temp.b, temp.len); - abFree(&temp); - exit(0); - break; - case CTRL_S: /* Ctrl-s */ - editorSave(); - break; - case CTRL_F: - editorFind(fd); - break; - case BACKSPACE: /* Backspace */ - case CTRL_H: /* Ctrl-h */ - case DEL_KEY: - editorDelChar(); - break; - case PAGE_UP: - case PAGE_DOWN: - if (c == PAGE_UP && E.cy != 0) - E.cy = 0; - else if (c == PAGE_DOWN && E.cy != E.screenrows-1) - E.cy = E.screenrows-1; - { - int times = E.screenrows; - while(times--) - editorMoveCursor(c == PAGE_UP ? ARROW_UP: - ARROW_DOWN); - } - break; - - case ARROW_UP: - case ARROW_DOWN: - case ARROW_LEFT: - case ARROW_RIGHT: - editorMoveCursor(c); - break; - case CTRL_L: /* ctrl+l, clear screen */ - /* Just refresht the line as side effect. */ - break; - case ESC: - /* Nothing to do for ESC in this mode. */ - break; - default: - editorInsertChar(c); - break; - } - - quit_times = KILO_QUIT_TIMES; /* Reset it to the original value. */ -} - -int editorFileWasModified(void) { - return E.dirty; -} - -void initEditor(void) { - E.cx = 0; - E.cy = 0; - E.rowoff = 0; - E.coloff = 0; - E.numrows = 0; - E.row = NULL; - E.dirty = 0; - E.filename = NULL; - E.syntax = NULL; - if (getWindowSize(STDIN_FILENO,STDOUT_FILENO, - &E.screenrows,&E.screencols) == -1) - { - perror("Unable to query the screen for size (columns / rows)"); - exit(1); - } - E.screenrows -= 2; /* Get room for status bar. */ -} - -int main(int argc, char **argv) { - if (argc != 2) { - fprintf(stderr,"Usage: kilo \n"); - exit(1); - } - - initEditor(); - editorSelectSyntaxHighlight(argv[1]); - editorOpen(argv[1]); - enableRawMode(STDIN_FILENO); - editorSetStatusMessage( - "HELP: Ctrl-S = save | Ctrl-Q = quit | Ctrl-F = find"); - while(1) { - editorRefreshScreen(); - editorProcessKeypress(STDIN_FILENO); - } - return 0; -}