From e4fae09d46854bb08bc07e652089c68e6d92b1ac Mon Sep 17 00:00:00 2001 From: moshix Date: Mon, 27 Mar 2023 02:45:23 -0400 Subject: [PATCH] Update kilo.c --- kilo.c | 2094 ++++++++++++++++++++++++++------------------------------ 1 file changed, 962 insertions(+), 1132 deletions(-) diff --git a/kilo.c b/kilo.c index 6a0ed93..8e39a87 100644 --- a/kilo.c +++ b/kilo.c @@ -1,165 +1,121 @@ -/* 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. - * - * Extensions by Moshix (march 2023): JCL and PL/I keywords, cleaned up language - * - * ----------------------------------------------------------------------- - * - * Copyright (C) 2016 Salvatore Sanfilippo - * - * All rights reserved. - * - * - */ +/* based on original work by Salvatore Sanfilippo 2016 */ +/* enahnced by https://viewsourcecode.org/snaptoken/kilo/index.html */ +/* enahnced by moshix 2013 */ -#define KILO_VERSION "0.0.1" +/* Makefile: +all: kilo -#ifdef __linux__ -#define _POSIX_C_SOURCE 200809L -#endif +kilo: kilo.c + $(CC) -O3 -o kilo kilo.c -Wall -W -pedantic -std=c99 + +clean: + rm kilo +*/ + + + +#define _DEFAULT_SOURCE +#define _BSD_SOURCE +#define _GNU_SOURCE -#include -#include -#include -#include -#include -#include #include -#include -#include -#include -#include -#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. */ +/*** defines ***/ -#define HL_HIGHLIGHT_STRINGS (1<<0) -#define HL_HIGHLIGHT_NUMBERS (1<<1) +#define KILO_VERSION "0.0.2" +#define KILO_TAB_STOP 8 +#define KILO_QUIT_TIMES 2 +#define ARM_QUIT 0 + +#define CTRL_KEY(k) ((k) & 0x1f) + +enum editorKey { + BACKSPACE = 127, + ARROW_LEFT = 1000, + ARROW_RIGHT, + ARROW_UP, + ARROW_DOWN, + DEL_KEY, + HOME_KEY, + END_KEY, + PAGE_UP, + PAGE_DOWN +}; + +enum editorHighlight { + HL_NORMAL = 0, + HL_COMMENT, + HL_MLCOMMENT, + HL_KEYWORD1, + HL_KEYWORD2, + HL_STRING, + HL_NUMBER, + HL_MATCH +}; + +#define HL_HIGHLIGHT_NUMBERS (1<<0) +#define HL_HIGHLIGHT_STRINGS (1<<1) + +/*** data ***/ struct editorSyntax { - char **filematch; - char **keywords; - char singleline_comment_start[2]; - char multiline_comment_start[3]; - char multiline_comment_end[3]; - int flags; + char *filetype; + char **filematch; + char **keywords; + char *singleline_comment_start; + char *multiline_comment_start; + char *multiline_comment_end; + 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. */ + int idx; + int size; + int rsize; + char *chars; + char *render; + unsigned char *hl; + int hl_open_comment; } 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. */ + int cx, cy; + int rx; + int rowoff; + int coloff; + int screenrows; + int screencols; + int numrows; + erow *row; + int dirty; + char *filename; + char statusmsg[80]; + time_t statusmsg_time; + struct editorSyntax *syntax; + struct termios orig_termios; }; -static struct editorConfig E; +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_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 -}; +/*** filetypes ***/ -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",".h",".cpp",".hpp",".cc",NULL}; +char *C_HL_extensions[] = { ".c", ".h", ".cpp", ".jcl", NULL }; char *C_HL_keywords[] = { - /* C Keywords */ - "auto","break","case","continue","default","do","else","enum", - "extern","for","goto","if","register","return","sizeof","static", - "struct","switch","typedef","union","volatile","while","NULL", + "switch", "if", "while", "for", "break", "continue", "return", "else", + "struct", "union", "typedef", "static", "enum", "class", "case", - /* C++ Keywords */ - "alignas","alignof","and","and_eq","asm","bitand","bitor","class", - "compl","constexpr","const_cast","deltype","delete","dynamic_cast", - "explicit","export","false","friend","inline","mutable","namespace", - "new","noexcept","not","not_eq","nullptr","operator","or","or_eq", - "private","protected","public","reinterpret_cast","static_assert", - "static_cast","template","this","thread_local","throw","true","try", - "typeid","typename","virtual","xor","xor_eq", - - /* JCL keywords */ + /* JCL keywords */ "JOB", "DD", "DSN", "DISP", "MSGCLASS", "CLASS", "NOTIFY", "MSGLEVEL", "UNIT","TAPE", "SYSDA", "VOL=SER", "RECFM", "RECL", "SYSPRINT", "SYSOUT", "JOBLIB", "STEPLIB", "EXEC", "PGM", "SHR", "NEW", "CATLG", @@ -168,1131 +124,1005 @@ char *C_HL_keywords[] = { "OPTIONS", "PROCEDURE", "END", "WHILE", "DO", "IF", "BIN", "FIXED", "options", "procedure", "end", "while", "do", "if", "bin", "fixed", - /* C types */ - "int|","long|","double|","float|","char|","unsigned|","signed|", - "void|","short|","auto|","const|","bool|",NULL + "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 - } + { + "c", + C_HL_extensions, + C_HL_keywords, + "//", "/*", "*/", + HL_HIGHLIGHT_NUMBERS | HL_HIGHLIGHT_STRINGS + }, }; -#define HLDB_ENTRIES (sizeof(HLDB)/sizeof(HLDB[0])) +#define HLDB_ENTRIES (sizeof(HLDB) / sizeof(HLDB[0])) -/* ======================= Low level terminal handling ====================== */ +/*** prototypes ***/ -static struct termios orig_termios; /* In order to restore at exit.*/ +void editorSetStatusMessage(const char *fmt, ...); +void editorRefreshScreen(); +char *editorPrompt(char *prompt, void (*callback)(char *, int)); -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; - } +/*** terminal ***/ + +void die(const char *s) { + write(STDOUT_FILENO, "\x1b[2J", 4); + write(STDOUT_FILENO, "\x1b[H", 3); + + perror(s); + exit(1); } -/* Called at exit to avoid remaining in raw mode. */ -void editorAtExit(void) { - disableRawMode(STDIN_FILENO); +void disableRawMode() { + if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &E.orig_termios) == -1) + die("tcsetattr"); } -/* Raw mode: 1960 magic stuff. */ -int enableRawMode(int fd) { - struct termios raw; +void enableRawMode() { + if (tcgetattr(STDIN_FILENO, &E.orig_termios) == -1) die("tcgetattr"); + atexit(disableRawMode); - if (E.rawmode) return 0; /* Already enabled. */ - if (!isatty(STDIN_FILENO)) goto fatal; - atexit(editorAtExit); - if (tcgetattr(fd,&orig_termios) == -1) goto fatal; + struct termios raw = E.orig_termios; + raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON); + raw.c_oflag &= ~(OPOST); + raw.c_cflag |= (CS8); + raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG); + raw.c_cc[VMIN] = 0; + raw.c_cc[VTIME] = 1; - 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; + if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw) == -1) die("tcsetattr"); } -/* 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); +int editorReadKey() { + int nread; + char c; + while ((nread = read(STDIN_FILENO, &c, 1)) != 1) { + if (nread == -1 && errno != EAGAIN) die("read"); + } - 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; + if (c == '\x1b') { + char seq[3]; - /* 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; - } - } - } + if (read(STDIN_FILENO, &seq[0], 1) != 1) return '\x1b'; + if (read(STDIN_FILENO, &seq[1], 1) != 1) return '\x1b'; - /* 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; + if (seq[0] == '[') { + if (seq[1] >= '0' && seq[1] <= '9') { + if (read(STDIN_FILENO, &seq[2], 1) != 1) return '\x1b'; + if (seq[2] == '~') { + switch (seq[1]) { + case '1': return HOME_KEY; + case '3': return DEL_KEY; + case '4': return END_KEY; + case '5': return PAGE_UP; + case '6': return PAGE_DOWN; + case '7': return HOME_KEY; + case '8': return END_KEY; + } } - } -} - -/* 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... */ + } 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; } - return 0; - } else { - *cols = ws.ws_col; - *rows = ws.ws_row; - return 0; + } + } else if (seq[0] == 'O') { + switch (seq[1]) { + case 'H': return HOME_KEY; + case 'F': return END_KEY; + } } -failed: - return -1; + return '\x1b'; + } else { + return c; + } } -/* ====================== Syntax highlight color scheme ==================== */ +int getCursorPosition(int *rows, int *cols) { + char buf[32]; + unsigned int i = 0; + + if (write(STDOUT_FILENO, "\x1b[6n", 4) != 4) return -1; + + while (i < sizeof(buf) - 1) { + if (read(STDIN_FILENO, &buf[i], 1) != 1) break; + if (buf[i] == 'R') break; + i++; + } + buf[i] = '\0'; + + if (buf[0] != '\x1b' || buf[1] != '[') return -1; + if (sscanf(&buf[2], "%d;%d", rows, cols) != 2) return -1; + + return 0; +} + +int getWindowSize(int *rows, int *cols) { + struct winsize ws; + + if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == -1 || ws.ws_col == 0) { + if (write(STDOUT_FILENO, "\x1b[999C\x1b[999B", 12) != 12) return -1; + return getCursorPosition(rows, cols); + } else { + *cols = ws.ws_col; + *rows = ws.ws_row; + return 0; + } +} + +/*** syntax highlighting ***/ int is_separator(int c) { - return c == '\0' || isspace(c) || strchr(",.()+-/*=~%[];",c) != NULL; + return isspace(c) || c == '\0' || 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); + 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. */ + if (E.syntax == NULL) return; - int i, prev_sep, in_string, in_comment; - 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 **keywords = E.syntax->keywords; - /* Point to the first non-space char. */ - p = row->render; - i = 0; /* Current char offset */ - while(*p && isspace(*p)) { - p++; - i++; + char *scs = E.syntax->singleline_comment_start; + char *mcs = E.syntax->multiline_comment_start; + char *mce = E.syntax->multiline_comment_end; + + int scs_len = scs ? strlen(scs) : 0; + int mcs_len = mcs ? strlen(mcs) : 0; + int mce_len = mce ? strlen(mce) : 0; + + int prev_sep = 1; + int in_string = 0; + int in_comment = (row->idx > 0 && E.row[row->idx - 1].hl_open_comment); + + int i = 0; + while (i < row->rsize) { + char c = row->render[i]; + unsigned char prev_hl = (i > 0) ? row->hl[i - 1] : HL_NORMAL; + + if (scs_len && !in_string && !in_comment) { + if (!strncmp(&row->render[i], scs, scs_len)) { + memset(&row->hl[i], HL_COMMENT, row->rsize - i); + break; + } } - 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? */ - /* 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])) + if (mcs_len && mce_len && !in_string) { + if (in_comment) { + row->hl[i] = HL_MLCOMMENT; + if (!strncmp(&row->render[i], mce, mce_len)) { + memset(&row->hl[i], HL_MLCOMMENT, mce_len); + i += mce_len; + in_comment = 0; + prev_sep = 1; + continue; + } else { + i++; + continue; + } + } else if (!strncmp(&row->render[i], mcs, mcs_len)) { + memset(&row->hl[i], HL_MLCOMMENT, mcs_len); + i += mcs_len; in_comment = 1; - - 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++; i++; - 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; - } - - /* 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++; + continue; + } } - /* 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; + if (E.syntax->flags & HL_HIGHLIGHT_STRINGS) { + if (in_string) { + row->hl[i] = HL_STRING; + if (c == '\\' && i + 1 < row->rsize) { + row->hl[i + 1] = HL_STRING; + i += 2; + continue; + } + if (c == in_string) in_string = 0; + i++; + prev_sep = 1; + continue; + } else { + if (c == '"' || c == '\'') { + in_string = c; + row->hl[i] = HL_STRING; + i++; + continue; + } + } + } + + if (E.syntax->flags & HL_HIGHLIGHT_NUMBERS) { + if ((isdigit(c) && (prev_sep || prev_hl == HL_NUMBER)) || + (c == '.' && prev_hl == HL_NUMBER)) { + row->hl[i] = HL_NUMBER; + i++; + prev_sep = 0; + continue; + } + } + + 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 (!strncmp(&row->render[i], keywords[j], klen) && + is_separator(row->render[i + klen])) { + memset(&row->hl[i], kw2 ? HL_KEYWORD2 : HL_KEYWORD1, klen); + i += klen; + break; + } + } + if (keywords[j] != NULL) { + prev_sep = 0; + continue; + } + } + + prev_sep = is_separator(c); + i++; + } + + int changed = (row->hl_open_comment != in_comment); + row->hl_open_comment = in_comment; + if (changed && row->idx + 1 < E.numrows) + editorUpdateSyntax(&E.row[row->idx + 1]); } -/* Maps syntax highlight token types to terminal colors. */ int editorSyntaxToColor(int hl) { - switch(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 31; /* red */ - case HL_MATCH: return 34; /* blu */ - default: return 37; /* white */ - } + case HL_MLCOMMENT: return 36; + case HL_KEYWORD1: return 33; + case HL_KEYWORD2: return 32; + case HL_STRING: return 35; + case HL_NUMBER: return 31; + case HL_MATCH: return 34; + default: return 37; + } } -/* 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++; +void editorSelectSyntaxHighlight() { + E.syntax = NULL; + if (E.filename == NULL) return; + + char *ext = strrchr(E.filename, '.'); + + for (unsigned int j = 0; j < HLDB_ENTRIES; j++) { + struct editorSyntax *s = &HLDB[j]; + unsigned int i = 0; + while (s->filematch[i]) { + int is_ext = (s->filematch[i][0] == '.'); + if ((is_ext && ext && !strcmp(ext, s->filematch[i])) || + (!is_ext && strstr(E.filename, s->filematch[i]))) { + E.syntax = s; + + int filerow; + for (filerow = 0; filerow < E.numrows; filerow++) { + editorUpdateSyntax(&E.row[filerow]); } + + return; + } + i++; } + } } -/* ======================= Editor rows implementation ======================= */ +/*** row operations ***/ + +int editorRowCxToRx(erow *row, int cx) { + int rx = 0; + int j; + for (j = 0; j < cx; j++) { + if (row->chars[j] == '\t') + rx += (KILO_TAB_STOP - 1) - (rx % KILO_TAB_STOP); + rx++; + } + return rx; +} + +int editorRowRxToCx(erow *row, int rx) { + int cur_rx = 0; + int cx; + for (cx = 0; cx < row->size; cx++) { + if (row->chars[cx] == '\t') + cur_rx += (KILO_TAB_STOP - 1) - (cur_rx % KILO_TAB_STOP); + cur_rx++; + + if (cur_rx > rx) return cx; + } + return cx; +} -/* Update the rendered version and the syntax highlight of a row. */ void editorUpdateRow(erow *row) { - unsigned int tabs = 0, nonprint = 0; - int j, idx; + int tabs = 0; + int j; + for (j = 0; j < row->size; j++) + if (row->chars[j] == '\t') tabs++; - /* 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++; + free(row->render); + row->render = malloc(row->size + tabs*(KILO_TAB_STOP - 1) + 1); - unsigned long long allocsize = - (unsigned long long) row->size + tabs*8 + nonprint*9 + 1; - if (allocsize > UINT32_MAX) { - printf("Some line of the edited file is too long for kilo\n"); - exit(1); - } - - 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; + int idx = 0; + for (j = 0; j < row->size; j++) { + if (row->chars[j] == '\t') { + row->render[idx++] = ' '; + while (idx % KILO_TAB_STOP != 0) row->render[idx++] = ' '; } 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->render[idx++] = row->chars[j]; } - row->chars[at] = c; - editorUpdateRow(row); - E.dirty++; + } + row->render[idx] = '\0'; + row->rsize = idx; + + editorUpdateSyntax(row); +} + +void editorInsertRow(int at, char *s, size_t len) { + if (at < 0 || at > E.numrows) return; + + E.row = realloc(E.row, sizeof(erow) * (E.numrows + 1)); + memmove(&E.row[at + 1], &E.row[at], sizeof(erow) * (E.numrows - at)); + for (int j = at + 1; j <= E.numrows; j++) E.row[j].idx++; + + E.row[at].idx = at; + + E.row[at].size = len; + E.row[at].chars = malloc(len + 1); + memcpy(E.row[at].chars, s, len); + E.row[at].chars[len] = '\0'; + + E.row[at].rsize = 0; + E.row[at].render = NULL; + E.row[at].hl = NULL; + E.row[at].hl_open_comment = 0; + editorUpdateRow(&E.row[at]); + + E.numrows++; + E.dirty++; +} + +void editorFreeRow(erow *row) { + free(row->render); + free(row->chars); + free(row->hl); +} + +void editorDelRow(int at) { + if (at < 0 || at >= E.numrows) return; + editorFreeRow(&E.row[at]); + memmove(&E.row[at], &E.row[at + 1], sizeof(erow) * (E.numrows - at - 1)); + for (int j = at; j < E.numrows - 1; j++) E.row[j].idx--; + E.numrows--; + E.dirty++; +} + +void editorRowInsertChar(erow *row, int at, int c) { + if (at < 0 || at > row->size) at = row->size; + 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 = 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++; +} + +void editorRowDelChar(erow *row, int at) { + if (at < 0 || at >= row->size) return; + memmove(&row->chars[at], &row->chars[at + 1], row->size - at); + row->size--; + editorUpdateRow(row); + E.dirty++; +} + +/*** editor operations ***/ + +void editorInsertChar(int c) { + if (E.cy == E.numrows) { + editorInsertRow(E.numrows, "", 0); + } + editorRowInsertChar(&E.row[E.cy], E.cx, c); + E.cx++; +} + +void editorInsertNewline() { + if (E.cx == 0) { + editorInsertRow(E.cy, "", 0); + } else { + erow *row = &E.row[E.cy]; + editorInsertRow(E.cy + 1, &row->chars[E.cx], row->size - E.cx); + row = &E.row[E.cy]; + row->size = E.cx; row->chars[row->size] = '\0'; editorUpdateRow(row); - E.dirty++; + } + E.cy++; + E.cx = 0; } -/* 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 (E.cy == E.numrows) return; + if (E.cx == 0 && E.cy == 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. */ - 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++; + erow *row = &E.row[E.cy]; + if (E.cx > 0) { + editorRowDelChar(row, E.cx - 1); + E.cx--; + } else { + E.cx = E.row[E.cy - 1].size; + editorRowAppendString(&E.row[E.cy - 1], row->chars, row->size); + editorDelRow(E.cy); + E.cy--; + } } -/* Load the specified program in the editor memory and returns 0 on success - * or 1 on error. */ -int editorOpen(char *filename) { - FILE *fp; +/*** file i/o ***/ - E.dirty = 0; - free(E.filename); - size_t fnlen = strlen(filename)+1; - E.filename = malloc(fnlen); - memcpy(E.filename,filename,fnlen); +char *editorRowsToString(int *buflen) { + int totlen = 0; + int j; + for (j = 0; j < E.numrows; j++) + totlen += E.row[j].size + 1; + *buflen = totlen; - fp = fopen(filename,"r"); - if (!fp) { - if (errno != ENOENT) { - perror("Opening file"); - exit(1); - } - return 1; - } + char *buf = malloc(totlen); + char *p = buf; + 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++; + } - 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; + return buf; } -/* 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; +void editorOpen(char *filename) { + free(E.filename); + E.filename = strdup(filename); - /* 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; + editorSelectSyntaxHighlight(); + FILE *fp = fopen(filename, "r"); + if (!fp) die("fopen"); + + char *line = NULL; + size_t linecap = 0; + ssize_t linelen; + while ((linelen = getline(&line, &linecap, fp)) != -1) { + while (linelen > 0 && (line[linelen - 1] == '\n' || + line[linelen - 1] == '\r')) + linelen--; + editorInsertRow(E.numrows, line, linelen); + } + free(line); + fclose(fp); + E.dirty = 0; +} + +void editorSave() { + if (E.filename == NULL) { + E.filename = editorPrompt("Save as: %s (ESC to cancel)", NULL); + if (E.filename == NULL) { + editorSetStatusMessage("Save aborted"); + return; + } + editorSelectSyntaxHighlight(); + } + + int len; + char *buf = editorRowsToString(&len); + + int fd = open(E.filename, O_RDWR | O_CREAT, 0644); + if (fd != -1) { + if (ftruncate(fd, len) != -1) { + if (write(fd, buf, len) == len) { + close(fd); + free(buf); + E.dirty = 0; + editorSetStatusMessage("%d bytes written to disk", len); + return; + } + } 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; + free(buf); + editorSetStatusMessage("Can't save! I/O error: %s", strerror(errno)); } -/* ============================= Terminal update ============================ */ +/*** find ***/ + +void editorFindCallback(char *query, int key) { + static int last_match = -1; + static int direction = 1; + + static int saved_hl_line; + static char *saved_hl = NULL; + + if (saved_hl) { + memcpy(E.row[saved_hl_line].hl, saved_hl, E.row[saved_hl_line].rsize); + free(saved_hl); + saved_hl = NULL; + } + + if (key == '\r' || key == '\x1b') { + last_match = -1; + direction = 1; + return; + } else if (key == ARROW_RIGHT || key == ARROW_DOWN) { + direction = 1; + } else if (key == ARROW_LEFT || key == ARROW_UP) { + direction = -1; + } else { + last_match = -1; + direction = 1; + } + + if (last_match == -1) direction = 1; + int current = last_match; + int i; + for (i = 0; i < E.numrows; i++) { + current += direction; + if (current == -1) current = E.numrows - 1; + else if (current == E.numrows) current = 0; + + erow *row = &E.row[current]; + char *match = strstr(row->render, query); + if (match) { + last_match = current; + E.cy = current; + E.cx = editorRowRxToCx(row, match - row->render); + E.rowoff = E.numrows; + + saved_hl_line = current; + saved_hl = malloc(row->rsize); + memcpy(saved_hl, row->hl, row->rsize); + memset(&row->hl[match - row->render], HL_MATCH, strlen(query)); + break; + } + } +} + +void editorFind() { + int saved_cx = E.cx; + int saved_cy = E.cy; + int saved_coloff = E.coloff; + int saved_rowoff = E.rowoff; + + char *query = editorPrompt("Search: %s (Use ESC/Arrows/Enter)", + editorFindCallback); + + if (query) { + free(query); + } else { + E.cx = saved_cx; + E.cy = saved_cy; + E.coloff = saved_coloff; + E.rowoff = saved_rowoff; + } +} + +/*** append buffer ***/ -/* 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; + char *b; + int len; }; -#define ABUF_INIT {NULL,0} +#define ABUF_INIT {NULL, 0} void abAppend(struct abuf *ab, const char *s, int len) { - char *new = realloc(ab->b,ab->len+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; + if (new == NULL) return; + memcpy(&new[ab->len], s, len); + ab->b = new; + ab->len += len; } void abFree(struct abuf *ab) { - free(ab->b); + 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; +/*** output ***/ - 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; +void editorScroll() { + E.rx = 0; + if (E.cy < E.numrows) { + E.rx = editorRowCxToRx(&E.row[E.cy], E.cx); + } - 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; + if (E.cy < E.rowoff) { + E.rowoff = E.cy; + } + if (E.cy >= E.rowoff + E.screenrows) { + E.rowoff = E.cy - E.screenrows + 1; + } + if (E.rx < E.coloff) { + E.coloff = E.rx; + } + if (E.rx >= E.coloff + E.screencols) { + E.coloff = E.rx - E.screencols + 1; + } +} + +void editorDrawRows(struct abuf *ab) { + int y; + for (y = 0; y < E.screenrows; y++) { + int filerow = y + E.rowoff; + if (filerow >= E.numrows) { + if (E.numrows == 0 && y == E.screenrows / 3) { + char welcome[80]; + int welcomelen = snprintf(welcome, sizeof(welcome), + "Kilo editor -- version %s", KILO_VERSION); + if (welcomelen > E.screencols) welcomelen = E.screencols; + int padding = (E.screencols - welcomelen) / 2; + if (padding) { + abAppend(ab, "~", 1); + padding--; } - - 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; + while (padding--) abAppend(ab, " ", 1); + abAppend(ab, welcome, welcomelen); + } else { + abAppend(ab, "~", 1); + } + } else { + int len = E.row[filerow].rsize - E.coloff; + if (len < 0) len = 0; + if (len > E.screencols) len = E.screencols; + char *c = &E.row[filerow].render[E.coloff]; + unsigned char *hl = &E.row[filerow].hl[E.coloff]; + int current_color = -1; + int j; + for (j = 0; j < len; j++) { + if (iscntrl(c[j])) { + char sym = (c[j] <= 26) ? '@' + c[j] : '?'; + abAppend(ab, "\x1b[7m", 4); + abAppend(ab, &sym, 1); + abAppend(ab, "\x1b[m", 3); + if (current_color != -1) { + char buf[16]; + int clen = snprintf(buf, sizeof(buf), "\x1b[%dm", current_color); + abAppend(ab, buf, clen); + } + } else if (hl[j] == HL_NORMAL) { + if (current_color != -1) { + abAppend(ab, "\x1b[39m", 5); + current_color = -1; + } + abAppend(ab, &c[j], 1); } else { - abAppend(&ab," ",1); - len++; + int color = editorSyntaxToColor(hl[j]); + if (color != current_color) { + current_color = color; + char buf[16]; + int clen = snprintf(buf, sizeof(buf), "\x1b[%dm", color); + abAppend(ab, buf, clen); + } + abAppend(ab, &c[j], 1); } + } + abAppend(ab, "\x1b[39m", 5); } - 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); + abAppend(ab, "\x1b[K", 3); + abAppend(ab, "\r\n", 2); + } +} + +void editorDrawStatusBar(struct abuf *ab) { + abAppend(ab, "\x1b[7m", 4); + char status[80], rstatus[80]; + int len = snprintf(status, sizeof(status), "%.20s - %d lines %s", + E.filename ? E.filename : "[No Name]", E.numrows, + E.dirty ? "(modified)" : ""); + int rlen = snprintf(rstatus, sizeof(rstatus), "%s | %d/%d", + E.syntax ? E.syntax->filetype : "no ft", 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[m", 3); + abAppend(ab, "\r\n", 2); +} + +void editorDrawMessageBar(struct abuf *ab) { + abAppend(ab, "\x1b[K", 3); + int msglen = strlen(E.statusmsg); + if (msglen > E.screencols) msglen = E.screencols; + if (msglen && time(NULL) - E.statusmsg_time < 5) + abAppend(ab, E.statusmsg, msglen); +} + +void editorRefreshScreen() { + editorScroll(); + + struct abuf ab = ABUF_INIT; + + abAppend(&ab, "\x1b[?25l", 6); + abAppend(&ab, "\x1b[H", 3); + + editorDrawRows(&ab); + editorDrawStatusBar(&ab); + editorDrawMessageBar(&ab); + + char buf[32]; + snprintf(buf, sizeof(buf), "\x1b[%d;%dH", (E.cy - E.rowoff) + 1, + (E.rx - E.coloff) + 1); + abAppend(&ab, buf, strlen(buf)); + + abAppend(&ab, "\x1b[?25h", 6); + + 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); + 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 ================================ */ +/*** input ***/ -#define KILO_QUERY_LEN 256 +char *editorPrompt(char *prompt, void (*callback)(char *, int)) { + size_t bufsize = 128; + char *buf = malloc(bufsize); -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; + size_t buflen = 0; + buf[0] = '\0'; -#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) + while (1) { + editorSetStatusMessage(prompt, buf); + editorRefreshScreen(); - /* 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; - } - } - } + int c = editorReadKey(); + if (c == DEL_KEY || c == CTRL_KEY('h') || c == BACKSPACE) { + if (buflen != 0) buf[--buflen] = '\0'; + } else if (c == '\x1b') { + editorSetStatusMessage(""); + if (callback) callback(buf, c); + free(buf); + return NULL; + } else if (c == '\r') { + if (buflen != 0) { + editorSetStatusMessage(""); + if (callback) callback(buf, c); + return buf; + } + } else if (!iscntrl(c) && c < 128) { + if (buflen == bufsize - 1) { + bufsize *= 2; + buf = realloc(buf, bufsize); + } + buf[buflen++] = c; + buf[buflen] = '\0'; } + + if (callback) callback(buf, c); + } } -/* ========================= 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]; + erow *row = (E.cy >= E.numrows) ? NULL : &E.row[E.cy]; - switch(key) { + 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; + if (E.cx != 0) { + E.cx--; + } else if (E.cy > 0) { + E.cy--; + E.cx = E.row[E.cy].size; + } + 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; + if (row && E.cx < row->size) { + E.cx++; + } else if (row && E.cx == row->size) { + E.cy++; + E.cx = 0; + } + break; case ARROW_UP: - if (E.cy == 0) { - if (E.rowoff) E.rowoff--; - } else { - E.cy -= 1; - } - break; + if (E.cy != 0) { + E.cy--; + } + 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; - } - } + if (E.cy < E.numrows) { + E.cy++; + } + break; + } + + row = (E.cy >= E.numrows) ? NULL : &E.row[E.cy]; + int rowlen = row ? row->size : 0; + if (E.cx > rowlen) { + E.cx = rowlen; + } } -/* 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; +void editorProcessKeypress() { + static int quit_times = KILO_QUIT_TIMES; - int c = editorReadKey(fd); - switch(c) { - case ENTER: /* Enter */ - editorInsertNewline(); + int c = editorReadKey(); + + switch (c) { + case '\r': + editorInsertNewline(); + break; + + case CTRL_KEY('k'): + if (E.dirty && quit_times > 0) { + editorSetStatusMessage("WARNING!!! File has unsaved changes. " + "Press Ctrl-K %d more times to quit.", quit_times); + quit_times--; + return; + } + write(STDOUT_FILENO, "\x1b[2J", 4); + write(STDOUT_FILENO, "\x1b[H", 3); + exit(0); + break; + case CTRL_KEY('q'): + if (E.dirty && ARM_QUIT > 0) { + editorSetStatusMessage("WARNING!!! File has unsaved changes. " + "Press Ctrl-C to quit."); + return; + } + write(STDOUT_FILENO, "\x1b[2J", 4); + write(STDOUT_FILENO, "\x1b[H", 3); +/* exit(0); */ + break; + + case CTRL_KEY('s'): + editorSave(); + break; + + case CTRL_KEY('e'): /* end of file */ + E.cy = E.numrows; + break; + + case CTRL_KEY('t'): /* top of file */ + E.cy = 0; + break; + + case HOME_KEY: + E.cx = 0; + break; + + case CTRL_KEY('a'): /* beginning of line */ + E.cx = 0; + break; + + case END_KEY: + if (E.cy < E.numrows) + E.cx = E.row[E.cy].size; + break; + + case CTRL_KEY('z'): /* end of line */ + if (E.cy < E.numrows) + E.cx = E.row[E.cy].size; + break; + + case CTRL_KEY('f'): + editorFind(); + break; + + case BACKSPACE: + case CTRL_KEY('h'): + editorSetStatusMessage( + "C^S save | C^K quit | C^F find | C^T Top | C^E bottom | C^A begLine | C^Z endLine"); 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_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; - } - 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; + if (c == DEL_KEY) editorMoveCursor(ARROW_RIGHT); + 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); + { + if (c == PAGE_UP) { + E.cy = E.rowoff; + } else if (c == PAGE_DOWN) { + E.cy = E.rowoff + E.screenrows - 1; + if (E.cy > E.numrows) E.cy = E.numrows; } - break; + + 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; + editorMoveCursor(c); + break; + + case CTRL_KEY('l'): + case '\x1b': + break; + default: - editorInsertChar(c); - break; - } + editorInsertChar(c); + break; + } - quit_times = KILO_QUIT_TIMES; /* Reset it to the original value. */ + quit_times = KILO_QUIT_TIMES; } -int editorFileWasModified(void) { - return E.dirty; +/*** init ***/ + +void initEditor() { + E.cx = 0; + E.cy = 0; + E.rx = 0; + E.rowoff = 0; + E.coloff = 0; + E.numrows = 0; + E.row = NULL; + E.dirty = 0; + E.filename = NULL; + E.statusmsg[0] = '\0'; + E.statusmsg_time = 0; + E.syntax = NULL; + + if (getWindowSize(&E.screenrows, &E.screencols) == -1) die("getWindowSize"); + E.screenrows -= 2; } -void updateWindowSize(void) { - 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. */ +void Help() { + editorSetStatusMessage( + "C^S save | C^K quit | C^F find | C^T Top | C^E bottom | C^A begLine | C^Z endLine"); } -void handleSigWinCh(int unused __attribute__((unused))) { - updateWindowSize(); - if (E.cy > E.screenrows) E.cy = E.screenrows - 1; - if (E.cx > E.screencols) E.cx = E.screencols - 1; - editorRefreshScreen(); -} -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; - updateWindowSize(); - signal(SIGWINCH, handleSigWinCh); -} - -int main(int argc, char **argv) { - if (argc != 2) { - fprintf(stderr,"Must provide a filename \n"); - exit(1); - } - - initEditor(); - editorSelectSyntaxHighlight(argv[1]); +int main(int argc, char *argv[]) { + enableRawMode(); + initEditor(); + if (argc >= 2) { 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; + } + + editorSetStatusMessage( + "C^S save | C^K quit | C^F find | C^T Top | C^E bottom | C^A begLine | C^Z endLine"); + + while (1) { + editorRefreshScreen(); + editorProcessKeypress(); + } + + return 0; }