From 22221a20fe62af36386cefd2707538b0769ef895 Mon Sep 17 00:00:00 2001 From: "K. Lange" Date: Thu, 16 Aug 2018 19:46:13 +0900 Subject: [PATCH] [bim] Syntax highlighting --- apps/bim.c | 386 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 382 insertions(+), 4 deletions(-) diff --git a/apps/bim.c b/apps/bim.c index cfc23370..d3f59e5b 100644 --- a/apps/bim.c +++ b/apps/bim.c @@ -58,6 +58,36 @@ #define COLOR_SEARCH_FG 234 #define COLOR_SEARCH_BG 226 +#define COLOR_KEYWORD 117 +#define COLOR_STRING 113 +#define COLOR_COMMENT 102 +#define COLOR_TYPE 185 +#define COLOR_PRAGMA 173 + +#define FLAG_NONE 0 +#define FLAG_KEYWORD 1 +#define FLAG_STRING 2 +#define FLAG_COMMENT 3 +#define FLAG_TYPE 4 +#define FLAG_PRAGMA 5 + +int flag_to_color(int flag) { + switch (flag) { + case FLAG_KEYWORD: + return COLOR_KEYWORD; + case FLAG_STRING: + return COLOR_STRING; + case FLAG_COMMENT: + return COLOR_COMMENT; + case FLAG_TYPE: + return COLOR_TYPE; + case FLAG_PRAGMA: + return COLOR_PRAGMA; + default: + return COLOR_FG; + } +} + /** * Line buffer definitions * @@ -134,6 +164,7 @@ typedef struct _env { int line_avail; int col_no; char * search; + struct syntax_definition * syntax; short modified; short mode; line_t ** lines; @@ -220,6 +251,301 @@ buffer_t * buffer_close(buffer_t * buf) { * so that they can act on buffers other than the active one. */ +int syn_c_iskeywordchar(int c) { + if (isalnum(c)) return 1; + if (c == '_') return 1; + return 0; +} + +static char * syn_c_keywords[] = { + "while","if","for","continue","return","break","switch","case","sizeof", + "struct","union","typedef","do","default","else","goto", + "alignas","alignof","offsetof", + NULL +}; + +static char * syn_c_types[] = { + "static","int","char","short","float","double","void","unsigned","volatile", + "register","long","inline","restrict","enum","auto","extern","bool","complex", + "uint8_t","uint16_t","uint32_t","uint64_t", + "int8_t","int16_t","int32_t","int64_t", + NULL +}; + +static int syn_c_extended(line_t * line, int i, int c, int last, int * out_left) { + if (i == 0 && c == '#') { + *out_left = line->actual+1; + return FLAG_PRAGMA; + } + + if (c == '/') { + if (line->text[i+1].codepoint == '/') { + *out_left = (line->actual + 1) - i; + return FLAG_COMMENT; + } + + if (line->text[i+1].codepoint == '*') { + int last = 0; + for (int j = i + 2; j < line->actual + 1; ++j) { + int c = line->text[j].codepoint; + if (c == '/' && last == '*') { + *out_left = j - i; + return FLAG_COMMENT; + } + last = c; + } + /* TODO multiline - update next */ + *out_left = (line->actual + 1) - i; + return FLAG_COMMENT; + } + } + + if (line->text[i].codepoint == '"') { + int last = 0; + for (int j = i+1; j < line->actual + 1; ++j) { + int c = line->text[j].codepoint; + if (last != '\\' && c == '"') { + *out_left = j - i; + return FLAG_STRING; + } + if (last == '\\' && c == '\\') { + last = 0; + } + last = c; + } + *out_left = (line->actual + 1) - i; /* unterminated string */ + return FLAG_STRING; + } + + return 0; +} + +char * syn_c_ext[] = {".c",".h",NULL}; + +static char * syn_py_keywords[] = { + "class","def","return","del","if","else","elif", + "for","while","continue","break","assert", + "as","and","or","except","finally","from", + "global","import","in","is","lambda","with", + "nonlocal","not","pass","raise","try","yield", + NULL +}; + +static char * syn_py_types[] = { + "True","False","None", + "object","set","dict","int","str","bytes", + NULL +}; + +static int syn_py_extended(line_t * line, int i, int c, int last, int * out_left) { + if (i == 0 && c == 'i') { + /* Check for import */ + char * import = "import "; + for (int j = 0; j < line->actual + 1; ++j) { + if (import[j] == '\0') { + *out_left = j - 2; + return FLAG_PRAGMA; + } + if (line->text[j].codepoint != import[j]) break; + } + } + + if (c == '#') { + *out_left = (line->actual + 1) - i; + return FLAG_COMMENT; + } + + if (c == '@') { + for (int j = i+1; j < line->actual + 1; ++j) { + if (!syn_c_iskeywordchar(line->text[j].codepoint)) { + *out_left = j - i - 1; + return FLAG_PRAGMA; + } + *out_left = (line->actual + 1) - i; + return FLAG_PRAGMA; + } + } + + if (line->text[i].codepoint == '\'') { + int last = 0; + for (int j = i+1; j < line->actual + 1; ++j) { + int c = line->text[j].codepoint; + if (last != '\\' && c == '\'') { + *out_left = j - i; + return FLAG_STRING; + } + if (last == '\\' && c == '\\') { + last = 0; + } + last = c; + } + *out_left = (line->actual + 1) - i; /* unterminated string */ + return FLAG_STRING; + } + + if (line->text[i].codepoint == '"') { + int last = 0; + for (int j = i+1; j < line->actual + 1; ++j) { + int c = line->text[j].codepoint; + if (last != '\\' && c == '"') { + *out_left = j - i; + return FLAG_STRING; + } + if (last == '\\' && c == '\\') { + last = 0; + } + last = c; + } + *out_left = (line->actual + 1) - i; /* unterminated string */ + return FLAG_STRING; + } + + return 0; +} + +char * syn_py_ext[] = {".py",NULL}; + +static char * syn_sh_keywords[] = { + "cd","exit","export","help","history","if", + "empty?","equals?","return","export-cmd", + "source","exec","not","while","then","else", + NULL, +}; + +static char * syn_sh_types[] = {NULL}; + +static int syn_sh_extended(line_t * line, int i, int c, int last, int * out_left) { + if (c == '#') { + *out_left = (line->actual + 1) - i; + return FLAG_COMMENT; + } + + if (line->text[i].codepoint == '\'') { + int last = 0; + for (int j = i+1; j < line->actual + 1; ++j) { + int c = line->text[j].codepoint; + if (last != '\\' && c == '\'') { + *out_left = j - i; + return FLAG_STRING; + } + if (last == '\\' && c == '\\') { + last = 0; + } + last = c; + } + *out_left = (line->actual + 1) - i; /* unterminated string */ + return FLAG_STRING; + } + + if (line->text[i].codepoint == '"') { + int last = 0; + for (int j = i+1; j < line->actual + 1; ++j) { + int c = line->text[j].codepoint; + if (last != '\\' && c == '"') { + *out_left = j - i; + return FLAG_STRING; + } + if (last == '\\' && c == '\\') { + last = 0; + } + last = c; + } + *out_left = (line->actual + 1) - i; /* unterminated string */ + return FLAG_STRING; + } + + return 0; +} + +static int syn_sh_iskeywordchar(int c) { + if (isalnum(c)) return 1; + if (c == '-') return 1; + if (c == '_') return 1; + if (c == '?') return 1; + return 0; +} + +static char * syn_sh_ext[] = {".sh",".eshrc",".esh",NULL}; + +struct syntax_definition { + char * name; + char ** ext; + char ** keywords; + char ** types; + int (*extended)(line_t *, int, int, int, int *); + int (*iskwchar)(int); +} syntaxes[] = { + {"c",syn_c_ext,syn_c_keywords,syn_c_types,syn_c_extended,syn_c_iskeywordchar}, + {"python",syn_py_ext,syn_py_keywords,syn_py_types,syn_py_extended,syn_c_iskeywordchar}, + {"esh",syn_sh_ext,syn_sh_keywords,syn_sh_types,syn_sh_extended,syn_sh_iskeywordchar}, + {NULL, NULL, NULL, NULL, NULL, NULL} +}; + +int check_line(line_t * line, int c, char * str, int last) { + if (env->syntax->iskwchar(last)) return 0; + for (int i = c; i < line->actual + 1; ++i, ++str) { + if (*str == '\0' && !env->syntax->iskwchar(line->text[i].codepoint)) return 1; + if (line->text[i].codepoint == *str) continue; + return 0; + } + if (*str == '\0') return 1; + return 0; +} + +void recalculate_syntax(line_t * line, int offset) { + if (!env->syntax) return; + + int state = 0; + int left = 0; + int last = 0; + for (int i = 0; i < line->actual+1; last = line->text[i++].codepoint) { + if (state) { + left--; + line->text[i].flags = state; + + if (!left) { + state = 0; + } + + continue; + } + + int c = line->text[i].codepoint; + line->text[i].flags = FLAG_NONE; + + if (env->syntax->extended) { + int s = env->syntax->extended(line,i,c,last,&left); + if (s) { + state = s; + goto _continue; + } + } + + for (char ** kw = env->syntax->keywords; *kw; kw++) { + int c = check_line(line, i, *kw, last); + if (c == 1) { + left = strlen(*kw)-1; + state = FLAG_KEYWORD; + goto _continue; + } + } + + for (char ** kw = env->syntax->types; *kw; kw++) { + int c = check_line(line, i, *kw, last); + if (c == 1) { + left = strlen(*kw)-1; + state = FLAG_TYPE; + goto _continue; + } + } + +_continue: + line->text[i].flags = state; + } + + (void)offset; /* TODO Process later lines if we, say, opened a comment */ +} + /** * Insert a character into an existing line. */ @@ -242,6 +568,9 @@ line_t * line_insert(line_t * line, char_t c, int offset) { /* There is one new character in the line */ line->actual += 1; + + recalculate_syntax(line, offset); + return line; } @@ -259,6 +588,8 @@ void line_delete(line_t * line, int offset) { /* The line is one character shorter */ line->actual -= 1; + + recalculate_syntax(line, offset); } /** @@ -339,6 +670,8 @@ line_t ** merge_lines(line_t ** lines, int lineb) { /* The first line is now longer */ lines[linea]->actual = lines[linea]->actual + lines[lineb]->actual; + recalculate_syntax(lines[linea], linea); + /* Remove the second line */ return remove_line(lines, lineb); } @@ -385,6 +718,9 @@ line_t ** split_line(line_t ** lines, int line, int split) { memmove(lines[line]->text, &lines[line-1]->text[split], sizeof(char_t) * remaining); lines[line-1]->actual = split; + recalculate_syntax(lines[line-1], line-1); + recalculate_syntax(lines[line], line); + /* There is one new line */ env->line_count += 1; @@ -516,6 +852,14 @@ void set_colors(int fg, int bg) { fflush(stdout); } +/** + * Set just the foreground color + */ +void set_fg_color(int fg) { + printf("\033[38;5;%dm", fg); + fflush(stdout); +} + /** * Clear the rest of this line */ @@ -706,6 +1050,9 @@ void render_line(line_t * line, int width, int offset) { break; } + /* Syntax hilighting */ + set_fg_color(flag_to_color(c.flags)); + /* Render special characters */ if (c.codepoint == '\t') { /* TODO: This should adapt based on tabstops. */ @@ -837,9 +1184,15 @@ void redraw_statusbar(void) { printf("[No Name]"); } + printf(" "); + + if (env->syntax) { + printf("[%s]", env->syntax->name); + } + /* Print file status indicators */ if (env->modified) { - printf(" [+]"); + printf("[+]"); } /* Clear the rest of the status bar */ @@ -875,10 +1228,11 @@ void redraw_commandline(void) { if (env->mode == MODE_INSERT) { set_bold(); printf("-- INSERT --"); + clear_to_end(); + reset(); + } else { + clear_to_end(); } - - /* Fill the rest of the command line with the background color */ - clear_to_end(); } /** @@ -1069,6 +1423,25 @@ void add_buffer(uint8_t * buf, int size) { } } +struct syntax_definition * match_syntax(char * file) { + for (struct syntax_definition * s = syntaxes; s->name; ++s) { + for (char ** ext = s->ext; *ext; ++ext) { + int i = strlen(file); + int j = strlen(*ext); + + do { + if (file[i] != (*ext)[j]) break; + if (j == 0) return s; + if (i == 0) break; + i--; + j--; + } while (1); + } + } + + return NULL; +} + /** * Create a new buffer from a file. */ @@ -1078,9 +1451,14 @@ void open_file(char * file) { env->file_name = malloc(strlen(file) + 1); memcpy(env->file_name, file, strlen(file) + 1); + env->syntax = match_syntax(file); + setup_buffer(env); FILE * f = fopen(file, "r"); + if (!f) { + f = fopen(file,"a+"); + } if (!f) { char buf[1024];