diff --git a/CMakeLists.txt b/CMakeLists.txt index 59b729e..3158765 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -54,6 +54,12 @@ target_link_libraries(${PROJECT_NAME} PRIVATE ${PROJECT_OBJECTS}) add_compile_definitions(_XOPEN_SOURCE=700) +# --- symlink zum data Ordner erstellen +execute_process( + COMMAND ${CMAKE_COMMAND} -E create_symlink + ${CMAKE_SOURCE_DIR}/data + ${CMAKE_BINARY_DIR}/data +) # CTest-Unterstützung aktivieren enable_testing() diff --git a/assets/screenshot.png b/assets/screenshot.png index 53ec641..bcceffc 100644 Binary files a/assets/screenshot.png and b/assets/screenshot.png differ diff --git a/data/syntax/ini.ini b/data/syntax/ini.ini index dc4d5d3..99674f0 100644 --- a/data/syntax/ini.ini +++ b/data/syntax/ini.ini @@ -5,39 +5,46 @@ name = INI file_extensions = ini [block:root] -allowed_blocks = comment, section, keyvalue +child_blocks = comment, section, comment, assignment +color = 15 [block:comment] # comments start with ";" or "#" -start = "^[;#]" +start = "(;|#)" end = "$" color = 242 # gray -allowed_blocks = +child_blocks = [block:section] # section: [section_name] -start = "^[ \t]*\\[.*\\]" +start = "\\[.*\\]" end = "$" color = 33 # cyan -allowed_blocks = +child_blocks = comment -[block:keyvalue] -# key = Value -start = "^[ \t]*[^;#= \t][^=]*=" -end = "$" -color = 226 # yellow -allowed_blocks = string +[block:assignment] +start = [ \t]*[.\-_:a-zA-Z0-9]+[ \t]*=[ \t]* +end = $ +color=82 +child_blocks= number, string, bare_string, comment + +[block:number] +start = [0-9]+ +color = 43 [block:string] -# string-values start = "\"" -end = "\"" -color = 82 # green -allowed_blocks = escape_sequence - -[block:escape_sequence] -# escape inside strings -start = "\\\\" -end = "." -color = 208 # orange -allowed_blocks = +end = "\"|$" +color = 160 +child_blocks = escape_chars + +[block:bare_string] +start = "." +end = $ +color = 67 +ends_on = comment + +[block:escape_chars] +start = "\\\\[\\\\\\nt'\"]" +color = 217 + diff --git a/data/syntax/md.ini b/data/syntax/md.ini new file mode 100644 index 0000000..6952dac --- /dev/null +++ b/data/syntax/md.ini @@ -0,0 +1,40 @@ +[meta] +name = Markdown +file_extensions = md + +[block:root] +child_blocks = title1, title2, title3, codeblock, code, emph, img +color = 15 + +[block:title1] +# comments start with ";" or "#" +start = "^# (.*)$" +color = 33 +child_blocks = + +[block:title2] +start = "^## (.*)$" +color = 33 + +[block:title3] +start = "^### (.*)$" +color = 33 + +[block:code] +start = `[^`]*` # make pattern greedy by explicitly exclude ` from permitted characters +color = 64 + +[block:emph] +start = \*\* +end = \*\* +color = 45 + +[block:img] +start = !\[.*\]\([^\)]+\) +color = 92 + +[block:codeblock] +start = "^```" +end = "^```" +color = 64 +child_blocks = diff --git a/src/common/config.c b/src/common/config.c index 71c57eb..878bd37 100644 --- a/src/common/config.c +++ b/src/common/config.c @@ -30,6 +30,8 @@ typedef struct _Config { const char *exe_path; + const char *syntax; + int indent_size; bool use_spaces_for_indent; char filename[PATH_MAX]; @@ -45,6 +47,7 @@ void Config_Init(const char *argv0) { config.table = NULL; config.editor = NULL; config.colors = NULL; + config.syntax = NULL; config.dirty = false; } @@ -55,6 +58,7 @@ void Config_Deinit() { config.table = NULL; config.editor = NULL; config.colors = NULL; + config.syntax = NULL; } @@ -103,6 +107,17 @@ const char *Config_GetFilename() { return config.filename; } + +void Config_SetSyntax(const char *type) { + config.syntax = type; + config.dirty = true; +} + +const char *Config_GetSyntax() { + return config.syntax; +} + + Table *Config_GetModuleConfig(const char *section) { if (!config.table || TypedTable_GetType(config.table, section) != VALUE_TYPE_TABLE) { return NULL; diff --git a/src/common/config.h b/src/common/config.h index 92b5f5d..8b16666 100644 --- a/src/common/config.h +++ b/src/common/config.h @@ -34,6 +34,10 @@ const char *Config_GetExePath(); void Config_SetFilename(const char *filename); const char* Config_GetFilename(); +void Config_SetSyntax(const char *type); +const char *Config_GetSyntax(); + + Table *Config_GetModuleConfig(const char *section); int Config_GetNumber(Table *table, const char *key, int fallback); const char *Config_GetStr(Table *table, const char *key, const char *fallback); diff --git a/src/common/string.c b/src/common/string.c index 941ebe2..65c46a2 100644 --- a/src/common/string.c +++ b/src/common/string.c @@ -535,7 +535,7 @@ void String_AppendView(String *str, const StringView *view) { String_Deinit(&tmp); return; } - if (new_byte_size > str->bytes_capacity) { + if (new_byte_size + 1 > str->bytes_capacity) { resize_bytes_capacity(str, new_byte_size + 1); } // use memove to handle overlapping memory areas diff --git a/src/common/table.c b/src/common/table.c index 8608c54..072ce6e 100644 --- a/src/common/table.c +++ b/src/common/table.c @@ -123,7 +123,12 @@ static void increase_capacity(Table *table) { return; } // allocate memory for the increased table - Table *new_table = Table_Create(); + Table *new_table = Table_CreateCustom( + table->hash_func, + table->key_cmp_func, + table->key_copy_func, + table->key_free_func + ); new_table->capacity = table->capacity == 0 ? TABLE_INITIAL_CAPACITY : table->capacity * TABLE_GROWTH_FACTOR; new_table->slots = malloc(new_table->capacity * sizeof(TableSlot)); if (!new_table->slots) { diff --git a/src/main.c b/src/main.c index 5ee749f..e19e235 100644 --- a/src/main.c +++ b/src/main.c @@ -34,13 +34,15 @@ #include "common/logging.h" #include "common/iniparser.h" +#include "syntax/loader.h" + #include "widgets/primitives/frame.h" #include "widgets/primitives/menu.h" -//const char *TESTFILE = "/home/cypher/projekte/clieditor/README.md"; - TextBuffer tb; +SyntaxHighlighting *highlighting; + static void print_help(const char *program_name) { fprintf(stderr, "Usage:\n %s \n", program_name); @@ -56,6 +58,29 @@ static void parse_arguments(int argc, char *argv[]) { // No file provided, could set a default or leave it empty. Config_SetFilename(NULL); } + + int opt; + while ((opt = getopt(argc, argv, "hs:")) != -1) { + switch (opt) { + case 'h': + print_help(argv[0]); + return; + case 's': + Config_SetSyntax(optarg); + break; + case '?': + fprintf(stderr, "Unknown option: -%c\n", optopt); + exit(1); + } + } + + // Verbleibende Argumente: + for (int i = optind; i < argc; i++) { + Config_SetFilename(argv[i]); + return; + } + + print_help(argv[0]); } static void load_environment() { @@ -100,6 +125,7 @@ static void finish() { // called automatically (set with atexit()) Timer_Deinit(); App_Deinit(); Config_Deinit(); + SyntaxHighlighting_Destroy(highlighting); TextBuffer_Deinit(&tb); Input_Deinit(); Screen_Deinit(); @@ -132,6 +158,32 @@ int main(int argc, char *argv[]) { TextBuffer_Init(&tb); + SyntaxHighlightingLoaderError error; + highlighting = SyntaxHighlighting_LoadFromFile(Config_GetSyntax(), &error); + if (!highlighting) { + switch (error.code) { + case SYNTAX_LOADER_FILE_NOT_FOUND: + logFatal("Syntax file not found."); + break; + case SYNTAX_LOADER_FILE_READ_ERROR: + logFatal("Could not read syntax file."); + break; + case SYNTAX_LOADER_PARSE_ERROR: + logFatal("Could not parse syntax file.\n%s", error.parsing_error.message); + break; + case SYNTAX_LOADER_DEFINITION_ERROR: + logFatal("Syntax definition error.\n%s", error.def_error.message); + break; + default: + logFatal("Could not load Syntaxdefinition (Code: %d)", error.code); + break; + } + SyntaxHighlightingLoaderError_Deinit(&error); + highlighting = NULL; + exit(1); + } + + const char * fn = Config_GetFilename(); bool failure_on_file_load = false; // the failure message can only be shown after initializing the widget system if (strcmp(fn, "") != 0) { @@ -162,6 +214,7 @@ int main(int argc, char *argv[]) { EditorView *editor = EditorView_Create(AS_WIDGET(&app), &tb); Widget_Focus(AS_WIDGET(editor)); + editor->editor->sh_binding.sh = highlighting; (void)editor; BottomBar *bottombar = BottomBar_Create(AS_WIDGET(&app)); (void)bottombar; diff --git a/src/syntax/definition.c b/src/syntax/definition.c index 82a3c6d..f202dd5 100644 --- a/src/syntax/definition.c +++ b/src/syntax/definition.c @@ -217,10 +217,15 @@ static SyntaxDefinitionError build_blocks(SyntaxDefinition *def, const Table *ta return NO_ERROR; } -static SyntaxDefinitionError map_block_names_to_blocks(SyntaxBlockDef *current_block, StringView *block_names, size_t count, SyntaxBlockDef **out, Table *blocks) { +static SyntaxDefinitionError map_block_names_to_blocks(SyntaxBlockDef *current_block, StringView *block_names, size_t count, SyntaxBlockDef **out, size_t *out_count, Table *blocks) { + *out_count = 0; for (size_t i=0; iblock; + out[(*out_count)++] = mapping->block; } return NO_ERROR; } @@ -266,14 +271,14 @@ static SyntaxDefinitionError block_name_list_str_to_blocks(SyntaxBlockDef *curre logFatal("Cannot allocate memory for children of SyntaxBlockDef."); } - SyntaxDefinitionError error = map_block_names_to_blocks(current_block, children, count, *out, blocks); + SyntaxDefinitionError error = map_block_names_to_blocks(current_block, children, count, *out, out_count, blocks); if (error.code != SYNTAXDEFINITION_NO_ERROR) { + *out_count = 0; String_Deinit(&s); free(children); return error; } - *out_count = count; String_Deinit(&s); free(children); diff --git a/src/syntax/definition.h b/src/syntax/definition.h index f174914..cc9fa9e 100644 --- a/src/syntax/definition.h +++ b/src/syntax/definition.h @@ -103,7 +103,7 @@ void SyntaxDefinitionError_Deinit(SyntaxDefinitionError *error); * @brief Helper struct to cache regex match results. This information are used by the Highlight module. */ typedef struct _MatchCache { - ssize_t offset; // total offset from where match was calculated + ssize_t offset; // total **byte offset** from where match was calculated regmatch_t match; // last match bool done; // if true the last match was already found } MatchCache; diff --git a/src/syntax/highlighting.c b/src/syntax/highlighting.c index 561c31f..b38fdbc 100644 --- a/src/syntax/highlighting.c +++ b/src/syntax/highlighting.c @@ -86,6 +86,18 @@ void SyntaxHighlightingString_Clear(SyntaxHighlightingString *shs) { Stack_Clear(&shs->open_blocks_at_end); } +const SyntaxHighlightingTag *SyntaxHighlightingString_GetTag(const SyntaxHighlightingString *shs, size_t offset) { + // TODO: Need massive optimization + if (!shs || shs->tags_count == 0) { + return NULL; + } + for (int i=(int)shs->tags_count-1; i>=0; i--) { + if (shs->tags[i].byte_offset == offset) { + return &shs->tags[i]; + } + } + return NULL; +} /*****************************************************************************/ /* SyntaxHighlighting */ @@ -96,6 +108,9 @@ void SyntaxHighlighting_Init(SyntaxHighlighting *hl, SyntaxDefinition *def) { } void SyntaxHighlighting_Deinit(SyntaxHighlighting *hl) { + if (!hl) { + return; + } if (hl->strings) { Table_Destroy(hl->strings); } @@ -116,6 +131,9 @@ SyntaxHighlighting *SyntaxHighlighting_Create(SyntaxDefinition *def) { } void SyntaxHighlighting_Destroy(SyntaxHighlighting *hl) { + if (!hl) { + return; + } SyntaxHighlighting_Deinit(hl); free(hl); } @@ -189,32 +207,18 @@ static void init_match_cache(SyntaxHighlighting *sh) { } } -static bool stack_equal(const Stack *a, const Stack *b) { - if (a->size != b->size) { - return false; - } - for (size_t i=0; isize; i++) { - if (a->items[i] != b->items[i]) { - return false; - } - } - return true; -} - const Stack *SyntaxHighlighting_HighlightString(SyntaxHighlighting *sh, const String *text, const Stack *open_blocks_at_begin) { // check if there is already old infomation about text in the table SyntaxHighlightingString *shs = Table_Get(sh->strings, text); if (shs) { - // early exit if nothing changed since the last calculation - if (open_blocks_at_begin && stack_equal(&shs->open_blocks_at_begin, open_blocks_at_begin)) { - return &shs->open_blocks_at_end; - } - // if open_blocks_at_begin changed clear the old information + // if open_blocks_at_begin changed, clear the old information and reuse the existing shs SyntaxHighlightingString_Clear(shs); } else { // if there is no object present in sh->strings table create one and store it shs = SyntaxHighlightingString_Create(text); + // Only set it in the table if it's new. If it existed, we just cleared it + // and will now re-populate it. Table_Set(sh->strings, text, shs, (void(*)(void*))SyntaxHighlightingString_Destroy); } @@ -229,8 +233,6 @@ const Stack *SyntaxHighlighting_HighlightString(SyntaxHighlighting *sh, const St Stack_Push(&shs->open_blocks_at_begin, sh->def->root); Stack_CopyTo(&shs->open_blocks_at_end, &shs->open_blocks_at_begin); } - Stack *open_blocks = &shs->open_blocks_at_end; - // initialize the cache fields of the SyntaxBlockDefs init_match_cache(sh); @@ -239,7 +241,7 @@ const Stack *SyntaxHighlighting_HighlightString(SyntaxHighlighting *sh, const St size_t offset = 0; for (;;) { // take the current block from the stack (but keep it there) - SyntaxBlockDef *current_block = (SyntaxBlockDef*)Stack_Peek(open_blocks); + SyntaxBlockDef *current_block = (SyntaxBlockDef*)Stack_Peek(&shs->open_blocks_at_end); // find the first child block regmatch_t child_match; @@ -271,7 +273,7 @@ const Stack *SyntaxHighlighting_HighlightString(SyntaxHighlighting *sh, const St offset += child_match.rm_eo; // push the child to the stack to continue with it in the next iteration - Stack_Push(open_blocks, (void*)child); + Stack_Push(&shs->open_blocks_at_end, (void*)child); continue; } // check if ends_on is the first match @@ -279,7 +281,7 @@ const Stack *SyntaxHighlighting_HighlightString(SyntaxHighlighting *sh, const St && (!end_found || ends_on_match.rm_so < end_match.rm_so)) { // the current block ends by the occurence of the ends_on block - Stack_Pop(open_blocks); // removes current (which was just peeked before) + Stack_Pop(&shs->open_blocks_at_end); // removes current (which was just peeked before) // add a tag for the beginning of the ends_on block SyntaxHighlightingTag tag; @@ -289,7 +291,7 @@ const Stack *SyntaxHighlighting_HighlightString(SyntaxHighlighting *sh, const St SyntaxHighlightingString_AddTag(shs, tag); // add the ends_on block to the stack - Stack_Push(open_blocks, (void*)ends_on); + Stack_Push(&shs->open_blocks_at_end, (void*)ends_on); // increase the offset to the end of the match offset += ends_on_match.rm_eo; @@ -298,13 +300,13 @@ const Stack *SyntaxHighlighting_HighlightString(SyntaxHighlighting *sh, const St // last case.... if end_found it's the first match automatically if (end_found) { // end of block found so remove it from stack - Stack_Pop(open_blocks); // removes current (which was just peeked before) + Stack_Pop(&shs->open_blocks_at_end); // removes current (which was just peeked before) // and add a tag for the (new) begin of the surrounding block SyntaxHighlightingTag tag; tag.text = text; tag.byte_offset = offset + end_match.rm_eo; // the current block goes behind the match of its end - tag.block = Stack_Peek(open_blocks); // new current block + tag.block = Stack_Peek(&shs->open_blocks_at_end); // new current block SyntaxHighlightingString_AddTag(shs, tag); // increase offset @@ -312,7 +314,7 @@ const Stack *SyntaxHighlighting_HighlightString(SyntaxHighlighting *sh, const St continue; } // neither the beginning of a new block nor the end of the current block found - return open_blocks; + return &shs->open_blocks_at_end; } // if this happens there is an error in the SyntaxDefinition. diff --git a/src/syntax/highlighting.h b/src/syntax/highlighting.h index c6dcc2e..c763ac4 100644 --- a/src/syntax/highlighting.h +++ b/src/syntax/highlighting.h @@ -73,6 +73,17 @@ void SyntaxHighlightingString_Destroy(SyntaxHighlightingString *shs); void SyntaxHighlightingString_AddTag(SyntaxHighlightingString *shs, SyntaxHighlightingTag tag); void SyntaxHighlightingString_Clear(SyntaxHighlightingString *shs); +/** + * @brief Return the tag for the given offset or NULL if there is no tag for the offset. + * + * @param shs The `SyntaxHighlightingString` from the `SyntaxHighlighting->strings` table. + * @param offset The offset in bytes. + * @returns + * A pointer to the internal SyntaxHighlightingTag. This pointer is only valid until the + * next recalculation of `shs`! + */ +const SyntaxHighlightingTag *SyntaxHighlightingString_GetTag(const SyntaxHighlightingString *shs, size_t offset); + /** * @brief Holds the highlighting information for text of multiple `Strings`. */ diff --git a/src/syntax/textlayoutbindings.c b/src/syntax/textlayoutbindings.c index 1c4b256..53f44d5 100644 --- a/src/syntax/textlayoutbindings.c +++ b/src/syntax/textlayoutbindings.c @@ -1,8 +1,9 @@ #include "textlayoutbindings.h" -void SyntaxHighlightingBinding_Init(SyntaxHighlightingBinding *binding, const TextLayout *tl, SyntaxHighlighting *sh) { +void SyntaxHighlightingBinding_Init(SyntaxHighlightingBinding *binding, TextLayout *tl, SyntaxHighlighting *sh) { binding->tl = tl; binding->sh = sh; + binding->need_full_update = true; } void SyntaxHighlightingBinding_Deinit(SyntaxHighlightingBinding *binding) { @@ -10,12 +11,32 @@ void SyntaxHighlightingBinding_Deinit(SyntaxHighlightingBinding *binding) { binding->sh = NULL; } -static void update_lines(SyntaxHighlightingBinding *binding, const Line *line, const Line *last_line, const Stack *open_blocks) { +static bool stack_equal(const Stack *a, const Stack *b) { + if (a->size != b->size) { + return false; + } + for (size_t i=0; isize; i++) { + if (a->items[i] != b->items[i]) { + return false; + } + } + return true; +} + +static void update_following_lines(SyntaxHighlightingBinding *binding, const Line *first_line, const Line *last_line, Stack *open_blocks) { const Stack *open_blocks_begin = open_blocks; + const Line *line = first_line; while (line) { + if (line != first_line) { + SyntaxHighlightingString *shs = Table_Get(binding->sh->strings, &line->text); + if (shs && stack_equal(&shs->open_blocks_at_begin, open_blocks_begin)) { + break; + } + } // open_blocks == NULL is also handled by the function const Stack *open_blocks_end = SyntaxHighlighting_HighlightString(binding->sh, &line->text, open_blocks_begin); open_blocks_begin = open_blocks_end; + //Stack_Destroy(open_blocks_end); if (line == last_line) { break; } @@ -23,7 +44,7 @@ static void update_lines(SyntaxHighlightingBinding *binding, const Line *line, c } } -void SyntaxHighlightingBinding_Update(SyntaxHighlightingBinding *binding, const Line *line, const Line *last_line) { +void SyntaxHighlightingBinding_UpdateLine(SyntaxHighlightingBinding *binding, const Line *line, const Line *last_line) { if (!binding || !binding->sh || !binding->tl || !binding->tl->tb || !line) { return; } @@ -44,7 +65,7 @@ void SyntaxHighlightingBinding_Update(SyntaxHighlightingBinding *binding, const else { // highlighting for the line is not calculated so far // so run this function for thr previous line - SyntaxHighlightingBinding_Update(binding, prev_line, last_line); + SyntaxHighlightingBinding_UpdateLine(binding, prev_line, last_line); return; } } @@ -55,5 +76,35 @@ void SyntaxHighlightingBinding_Update(SyntaxHighlightingBinding *binding, const } // update all lines until last_line (including) - update_lines(binding, line, last_line, open_blocks); + update_following_lines(binding, line, last_line, open_blocks); + if (open_blocks) { + //Stack_Destroy(open_blocks); + } } + +void SyntaxHighlightingBinding_Update(SyntaxHighlightingBinding *binding) { + if (!binding || !binding->sh || !binding->tl || !binding->tl->tb) { + return; + } + const TextBuffer *tb = binding->tl->tb; + Line *current = tb->current_line; + VisualLine *last_vl = TextLayout_GetVisualLine(binding->tl, binding->tl->height - 1); + Line *last = last_vl ? last_vl->src : TextBuffer_GetLastLine(tb); + if (!current || !last) { + return; + } + SyntaxHighlightingBinding_UpdateLine(binding, current, last); +} + +void SyntaxHighlightingBinding_UpdateAll(SyntaxHighlightingBinding *binding, bool force) { + if (!force && !binding->need_full_update) { + return; + } + binding->need_full_update = false; + SyntaxHighlightingBinding_UpdateLine( + binding, + TextBuffer_GetFirstLine(binding->tl->tb), + TextBuffer_GetLastLine(binding->tl->tb) + ); + +} \ No newline at end of file diff --git a/src/syntax/textlayoutbindings.h b/src/syntax/textlayoutbindings.h index 14cb1bb..a93aa0d 100644 --- a/src/syntax/textlayoutbindings.h +++ b/src/syntax/textlayoutbindings.h @@ -1,17 +1,21 @@ #ifndef SYNTAX_TEXTLAYOUTBINDINGS_H #define SYNTAX_TEXTLAYOUTBINDINGS_H +#include #include "highlighting.h" #include "document/textlayout.h" typedef struct _SyntaxHighlightingBinding { - const TextLayout *tl; + TextLayout *tl; SyntaxHighlighting *sh; + bool need_full_update; } SyntaxHighlightingBinding; -void SyntaxHighlightingBinding_Init(SyntaxHighlightingBinding *binding, const TextLayout *tl, SyntaxHighlighting *sh); +void SyntaxHighlightingBinding_Init(SyntaxHighlightingBinding *binding, TextLayout *tl, SyntaxHighlighting *sh); void SyntaxHighlightingBinding_Deinit(SyntaxHighlightingBinding *binding); -void SyntaxHighlightingBinding_Update(SyntaxHighlightingBinding *binding, const Line *line, const Line *last_line); +void SyntaxHighlightingBinding_UpdateLine(SyntaxHighlightingBinding *binding, const Line *line, const Line *last_line); +void SyntaxHighlightingBinding_Update(SyntaxHighlightingBinding *binding); +void SyntaxHighlightingBinding_UpdateAll(SyntaxHighlightingBinding *binding, bool force); #endif \ No newline at end of file diff --git a/src/widgets/components/editor.c b/src/widgets/components/editor.c index 72d2aed..b117383 100644 --- a/src/widgets/components/editor.c +++ b/src/widgets/components/editor.c @@ -36,6 +36,8 @@ static void alternate_cursor_visibility(uint8_t timer_id, void *user_data) { static void editor_destroy(Widget *self) { Editor *editor = AS_EDITOR(self); Timer_Stop(editor->cursor_timer); + SyntaxHighlightingBinding_Deinit(&editor->sh_binding); + TextSelection_Deinit(&editor->ts); TextLayout_Deinit(&editor->tl); TextEdit_Deinit(&editor->te); TextSelection_Deinit(&editor->ts); @@ -64,12 +66,23 @@ void draw_char(TextLayout *tl, Canvas *canvas, uint32_t cp, int x_pos, bool has_ // draw line and return new y_offset (changes to one if the cursor wraps to the next line) static int draw_visual_line(Editor *editor, Canvas *canvas, int y, int y_offset) { + // 1 --- Setup stuff + + // get visual line VisualLine *line = TextLayout_GetVisualLine(&editor->tl, y); TextSelection *ts = &editor->ts; - if (!line){ + if (!line){ // no visual line at this position return y_offset; } + // check if it's a line with a gap + bool is_gap_line = false; + if (line->src == editor->tb->current_line) { + is_gap_line = true; + } + // setup syntax highlighting stuff + SyntaxHighlighting *sh = editor->sh_binding.sh; + SyntaxHighlightingString *shs = Table_Get(sh->strings, &line->src->text); // Move cursor to the start position Canvas_MoveCursor(canvas, 0, y + y_offset); @@ -78,19 +91,38 @@ static int draw_visual_line(Editor *editor, Canvas *canvas, int y, int y_offset) CursorLayoutInfo cursor; TextLayout_GetCursorLayoutInfo(&editor->tl, &cursor); + // change the style if it's the current line - Style orig_style = canvas->current_style; - Style line_style; - if (line->src == editor->tb->current_line) { - line_style = editor->config.active; + if (shs && Stack_Peek(&shs->open_blocks_at_begin)) { + SyntaxBlockDef *first_block = Stack_Peek(&shs->open_blocks_at_begin); + canvas->current_style.fg = first_block->color; } - else { - line_style = orig_style; + Style orig_style = canvas->current_style; + Style line_style = orig_style; + if (is_gap_line) { + line_style.bg = editor->config.active.bg; } + - // draw the characters + // 2 --- Draw characters loop for (int i=0; ilength; i++) { - uint32_t cp = utf8_to_codepoint(VisualLine_GetChar(line, i)); + // get the character to draw (might not be in line->src->text.bytes if it's in the gap in general) + // in the moment the gap is always merged when updateing the syntax highlighting, so it should + // work at the moment + // Pay attention in future changes! + const char *ch = VisualLine_GetChar(line, i); + + // This will not always work if is_gap_line and the gap is not merged!! + size_t byte_offset = byte_offset = ch - line->src->text.bytes; + + const SyntaxHighlightingTag *tag = SyntaxHighlightingString_GetTag(shs, byte_offset); + if (tag) { + canvas->current_style.fg = tag->block->color; + line_style.fg = tag->block->color; + } + + + uint32_t cp = utf8_to_codepoint(ch); bool has_cursor = (line == cursor.line && i == cursor.idx && editor->mode == EDITOR_MODE_INPUT); if (TextSelection_IsSelected(ts, line->src, line->offset + i)) { canvas->current_style = editor->config.selected; @@ -319,6 +351,7 @@ static bool editor_handle_input(Widget *self, InputEvent input) { input_handled = input_handled || handle_input_scrolling(editor, input); input_handled = input_handled || handle_input_text_editing(editor, input, cursor); if (input_handled) { + SyntaxHighlightingBinding_Update(&editor->sh_binding); return true; } } @@ -346,14 +379,19 @@ static void editor_on_blur(Widget *self) { // this is basically if the cursor gets lost during terminal window resize static void editor_update(Widget *self) { Editor *editor = AS_EDITOR(self); + + // fix cursor CursorLayoutInfo cursor; int cursor_pos = TextLayout_GetCursorLayoutInfo(&editor->tl, &cursor); if (cursor_pos != 0) { editor->tl.first_line = editor->tb->current_line; editor->tl.first_visual_line_idx = 0; editor->tl.dirty = true; - return; } + + // initialize highlighting + // will only be called once due to internal flags + SyntaxHighlightingBinding_UpdateAll(&editor->sh_binding, false); } static void editor_on_config_changed(Widget *self) { @@ -417,6 +455,8 @@ void Editor_Init(Editor *self, Widget *parent, TextBuffer *tb) { self->config.active.bg = Color_GetCodeById(COLOR_HIGHLIGHT_BG); self->config.cursor = self->base.style; self->config.cursor.bg = Color_GetCodeById(COLOR_HIGHLIGHT_BG); + + SyntaxHighlightingBinding_Init(&self->sh_binding, &self->tl, NULL); } Editor *Editor_Create(Widget *parent, TextBuffer *tb) { diff --git a/src/widgets/components/editor.h b/src/widgets/components/editor.h index 099a1d8..5a7f461 100644 --- a/src/widgets/components/editor.h +++ b/src/widgets/components/editor.h @@ -22,6 +22,7 @@ #include "document/textedit.h" #include "document/textselection.h" #include "io/timer.h" +#include "syntax/textlayoutbindings.h" typedef enum { EDITOR_MODE_INPUT, @@ -50,6 +51,8 @@ typedef struct { uint8_t cursor_timer; bool cursor_visible; + SyntaxHighlightingBinding sh_binding; + EditorConfig config; } Editor; diff --git a/tests/test_syntax_highlighting.c b/tests/test_syntax_highlighting.c index e7742f0..7751d76 100644 --- a/tests/test_syntax_highlighting.c +++ b/tests/test_syntax_highlighting.c @@ -65,6 +65,7 @@ static void assert_highlight_tags( // the syntax definition SyntaxDefinition *def = create_definition(ini); + TEST_ASSERT(def); // the highlighting engine SyntaxHighlighting hl; SyntaxHighlighting_Init(&hl, def); @@ -322,6 +323,23 @@ const char *test_ini2 = "start = //\n" "end = $\n"; + +const char *test_ini3 = +"[meta]\n" +"name = Markdown\n" +"file_extensions = md\n" +"\n" +"[block:root]\n" +"child_blocks = title\n" +"\n" +"[block:title]\n" +"# comments start with \";\" or \"#\"\n" +"start = \"^# \"\n" +"end = \"$\"\n" +"color = 33 # gray\n" +"child_blocks =\n"; + + void test_moderate(void) { TagTestCase cases[] = { { @@ -335,6 +353,17 @@ void test_moderate(void) { 1, {"root"} }, + { + test_ini3, + "# Title", + 1, + { "root" }, + 2, + {0, 7}, + {"title", "root"}, + 1, + {"root"} + }, }; size_t count = sizeof(cases) / sizeof(cases[0]); diff --git a/tests/test_syntax_textlayoutbindings.c b/tests/test_syntax_textlayoutbindings.c index 0209ef1..be1d770 100644 --- a/tests/test_syntax_textlayoutbindings.c +++ b/tests/test_syntax_textlayoutbindings.c @@ -113,7 +113,7 @@ void test_binding_basic(void) { // 2. Calculate - SyntaxHighlightingBinding_Update(binding, tb->current_line->next, tb->current_line->next); + SyntaxHighlightingBinding_UpdateLine(binding, tb->current_line->next, tb->current_line->next); // 3. Check TEST_CHECK(Table_Get(sh->strings, &fixture.lines[0]->text) != NULL); @@ -140,7 +140,7 @@ void test_binding_basic2(void) { // 2. Calculate - SyntaxHighlightingBinding_Update(binding, fixture.lines[1], fixture.lines[1]); + SyntaxHighlightingBinding_UpdateLine(binding, fixture.lines[1], fixture.lines[1]); // 3. Check SyntaxHighlightingString *shs0 = Table_Get(sh->strings, &fixture.lines[0]->text);