чување a55be18ef394913a9be698c37137db0419288e2a
родитељ 39ca2835b2e586289806e92744832513d93243f8
Аутор: Страхиња Радић <sr@strahinja.org>
Датум: Wed, 7 Aug 2024 21:13:48 +0200
patch/syntax.patch: New patch (syntax highlighting)
Diffstat:
| A | patch/syntax.patch | | | 1104 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
измењених датотека: 1, додавања: 1104(+), брисања: 0(-)
diff --git a/patch/syntax.patch b/patch/syntax.patch
@@ -0,0 +1,1104 @@
+From fb80cff14430251ddd845727af64307397fed61a Mon Sep 17 00:00:00 2001
+From: Страхиња Радић <sr@strahinja.org>
+Date: Wed Aug 7 19:09:36 2024 UTC
+Subject: [PATCH] Add syntax highlighting
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Add syntax highlighting. This is a rudimentary, flawed version,
+but gets the job done. TODO: Optimization.
+---
+ config.def.h | 7+ 0-
+ draw.c | 485+ 29-
+ draw.h | 62+ 6-
+ sled.c | 91+ 7-
+ 4 files changed, 645 insertions(+), 42 deletions(-)
+
+diff --git a/config.def.h b/config.def.h
+index 39ca2835b..fb80cff14 100644
+--- a/config.def.h
++++ b/config.def.h
+@@ -239,6 +239,13 @@ static const struct Key command_keys[] = {
+ #endif /* SLED_C_GLOBALS */
+
+ /* clang-format off */
++static const struct Color syntax_colors[] = {
++ [SE_DEFAULT] = { .fg = TEXT_FG, .bg = TEXT_BG },
++ [SE_COMMENT] = { .fg = TB_GREEN, .bg = TEXT_BG },
++ [SE_KEYWORD] = { .fg = TB_CYAN | TB_BOLD, .bg = TEXT_BG },
++ [SE_OPERATOR] = { .fg = TB_YELLOW | TB_BOLD, .bg = TEXT_BG },
++ [SE_LITERAL] = { .fg = TB_MAGENTA | TB_BOLD, .bg = TEXT_BG },
++};
+ static const struct StatusSegment status_segments[] = {
+ {.format = "%s%s",
+ .callback = format_filename,
+diff --git a/draw.c b/draw.c
+index 39ca2835b..fb80cff14 100644
+--- a/draw.c
++++ b/draw.c
+@@ -2,6 +2,7 @@
+ * any later version. Copyright (C) 2022-2024 Страхиња Радић.
+ * See the file LICENSE for exact copyright and license details. */
+ #include <assert.h>
++#include <ctype.h>
+ #include <errno.h>
+ #include <stdio.h>
+ #include <stdlib.h>
+@@ -13,6 +14,28 @@
+ #include "util.h"
+ #include "version.h"
+
++/* Keyword initializer specification macro */
++#define KW(literal) \
++ { \
++ .text = (uint32_t*)literal, \
++ .size = sizeof(literal) / sizeof(uint32_t) \
++ }
++/* Single-line comment specification macro */
++#define SLC(com) \
++ { \
++ .start_text = (uint32_t*)com, \
++ .start_size = sizeof(com) / sizeof(uint32_t), \
++ .end_text = NULL, .end_size = -1 \
++ }
++/* Area comment specification macro */
++#define AC(com1, com2) \
++ { \
++ .start_text = (uint32_t*)com1, \
++ .start_size = sizeof(com1) / sizeof(uint32_t), \
++ .end_text = (uint32_t*)com2, \
++ .end_size = sizeof(com2) / sizeof(uint32_t) \
++ }
++
+ enum {
+ UNKNOWN_MODE,
+ COMMAND_MODE,
+@@ -87,6 +110,95 @@ static const struct {
+ { .boundary = 99, .width = 3 },
+ { .boundary = 9, .width = 2 }
+ };
++extern const struct Color syntax_colors[];
++const struct Keyword c_keywords[] = {
++ KW(L"auto"), KW(L"break"), KW(L"case"), KW(L"char"), KW(L"const"),
++ KW(L"continue"), KW(L"default"), KW(L"do"), KW(L"double"), KW(L"else"),
++ KW(L"enum"), KW(L"extern"), KW(L"float"), KW(L"for"), KW(L"goto"),
++ KW(L"if"), KW(L"inline"), KW(L"int"), KW(L"long"), KW(L"register"),
++ KW(L"restrict"), KW(L"return"), KW(L"short"), KW(L"signed"),
++ KW(L"sizeof"), KW(L"static"), KW(L"struct"), KW(L"switch"),
++ KW(L"typedef"), KW(L"union"), KW(L"unsigned"), KW(L"void"),
++ KW(L"volatile"), KW(L"while"), KW(L"_Bool"), KW(L"_Complex"),
++ KW(L"_Imaginary"),
++ { .text = NULL }
++};
++const struct Comment c_comments[] = {
++ SLC(L"//"),
++ AC(L"/*", L"*/"),
++ { .start_text = NULL }
++};
++const struct Operator c_operators[] = {
++ KW(L"<<="), KW(L">>="), KW(L"<<"), KW(L">>"),
++ KW(L"+="), KW(L"-="), KW(L"*="), KW(L"/="), KW(L"%="),
++ KW(L"&="), KW(L"|="), KW(L"^="),
++ KW(L"=="), KW(L"!="), KW(L"<="), KW(L">="),
++ KW(L"->"),
++ KW(L"++"), KW(L"--"),
++ KW(L"&&"), KW(L"||"),
++ KW(L"="), KW(L"+"), KW(L"-"), KW(L"~"), KW(L"*"), KW(L"/"), KW(L"%"),
++ KW(L"&"), KW(L"|"), KW(L"^"), KW(L"!"),
++ KW(L"<"), KW(L">"),
++ KW(L","), KW(L";"), KW(L":"), KW(L"?"), KW(L"."),
++ KW(L"("), KW(L")"), KW(L"["), KW(L"]"), KW(L"{"), KW(L"}"),
++ { .text = NULL }
++};
++const struct Keyword shell_keywords[] = {
++ KW(L"!"), KW(L"{"), KW(L"}"),
++ KW(L"case"), KW(L"do"), KW(L"done"), KW(L"elif"), KW(L"else"),
++ KW(L"esac"), KW(L"fi"), KW(L"for"), KW(L"if"), KW(L"in"), KW(L"then"),
++ KW(L"until"), KW(L"while"), KW(L"[["), KW(L"]]"), KW(L"function"),
++ KW(L"select"), KW(L"admin"), KW(L"alias"), KW(L"ar"), KW(L"asa"),
++ KW(L"at"), KW(L"awk"), KW(L"basename"), KW(L"batch"), KW(L"bc"),
++ KW(L"bg"), KW(L"break"), KW(L"c99"), KW(L"cal"), KW(L"cat"), KW(L"cd"),
++ KW(L"cflow"), KW(L"chgrp"), KW(L"chmod"), KW(L"chown"), KW(L"cksum"),
++ KW(L"cmp"), KW(L"colon"), KW(L"comm"), KW(L"command"), KW(L"compress"),
++ KW(L"continue"), KW(L"cp"), KW(L"crontab"), KW(L"csplit"), KW(L"ctags"),
++ KW(L"cut"), KW(L"cxref"), KW(L"date"), KW(L"dd"), KW(L"delta"),
++ KW(L"df"), KW(L"diff"), KW(L"dirname"), KW(L"dot"), KW(L"du"),
++ KW(L"echo"), KW(L"ed"), KW(L"env"), KW(L"eval"), KW(L"ex"), KW(L"exec"),
++ KW(L"exit"), KW(L"expand"), KW(L"export"), KW(L"expr"), KW(L"false"),
++ KW(L"fc"), KW(L"fg"), KW(L"file"), KW(L"find"), KW(L"fold"),
++ KW(L"fort77"), KW(L"fuser"), KW(L"gencat"), KW(L"get"), KW(L"getconf"),
++ KW(L"getopts"), KW(L"grep"), KW(L"hash"), KW(L"head"), KW(L"iconv"),
++ KW(L"id"), KW(L"ipcrm"), KW(L"ipcs"), KW(L"jobs"), KW(L"join"),
++ KW(L"kill"), KW(L"lex"), KW(L"link"), KW(L"ln"), KW(L"locale"),
++ KW(L"localedef"), KW(L"logger"), KW(L"logname"), KW(L"lp"), KW(L"ls"),
++ KW(L"m4"), KW(L"mailx"), KW(L"make"), KW(L"man"), KW(L"mesg"),
++ KW(L"mkdir"), KW(L"mkfifo"), KW(L"more"), KW(L"mv"), KW(L"newgrp"),
++ KW(L"nice"), KW(L"nl"), KW(L"nm"), KW(L"nohup"), KW(L"od"),
++ KW(L"paste"), KW(L"patch"), KW(L"pathchk"), KW(L"pax"), KW(L"pr"),
++ KW(L"printf"), KW(L"prs"), KW(L"ps"), KW(L"pwd"), KW(L"qalter"),
++ KW(L"qdel"), KW(L"qhold"), KW(L"qmove"), KW(L"qmsg"), KW(L"qrerun"),
++ KW(L"qrls"), KW(L"qselect"), KW(L"qsig"), KW(L"qstat"), KW(L"qsub"),
++ KW(L"read"), KW(L"readonly"), KW(L"renice"), KW(L"return"), KW(L"rm"),
++ KW(L"rmdel"), KW(L"rmdir"), KW(L"sact"), KW(L"sccs"), KW(L"sed"),
++ KW(L"set"), KW(L"sh"), KW(L"shift"), KW(L"sleep"), KW(L"sort"),
++ KW(L"split"), KW(L"strings"), KW(L"strip"), KW(L"stty"), KW(L"tabs"),
++ KW(L"tail"), KW(L"talk"), KW(L"tee"), KW(L"test"), KW(L"time"),
++ KW(L"times"), KW(L"touch"), KW(L"tput"), KW(L"tr"), KW(L"trap"),
++ KW(L"true"), KW(L"tsort"), KW(L"tty"), KW(L"type"), KW(L"ulimit"),
++ KW(L"umask"), KW(L"unalias"), KW(L"uname"), KW(L"uncompress"),
++ KW(L"unexpand"), KW(L"unget"), KW(L"uniq"), KW(L"unlink"), KW(L"unset"),
++ KW(L"uucp"), KW(L"uudecode"), KW(L"uuencode"), KW(L"uustat"),
++ KW(L"uux"), KW(L"val"), KW(L"vi"), KW(L"wait"), KW(L"wc"), KW(L"what"),
++ KW(L"who"), KW(L"write"), KW(L"xargs"), KW(L"yacc"), KW(L"zcat"),
++ KW(L"break"), KW(L":"), KW(L"."), KW(L"continue"), KW(L"eval"),
++ KW(L"exec"), KW(L"exit"), KW(L"export"), KW(L"readonly"), KW(L"return"),
++ KW(L"set"), KW(L"shift"), KW(L"times"), KW(L"trap"), KW(L"unset"),
++ {.text = NULL}
++};
++const struct Comment shell_comments[] = {
++ SLC(L"#"),
++ {.start_text = NULL}
++};
++const struct Operator shell_operators[] = {
++ KW(L"<<"), KW(L">>"),
++ KW(L"&&"), KW(L"||"), KW(L"$"), KW(L"`"), KW(L"="),
++ KW(L"<"), KW(L">"),
++ KW(L";"), KW(L"("), KW(L")"), KW(L"["), KW(L"]"), KW(L"{"), KW(L"}"),
++ {.text = NULL}
++};
+ const char* go_to_dialog_title = "Go to line";
+ const char* prompt_dirty = "File changed, save [y/n/ESC]?";
+ const char* prompt_overwrite = "Not empty, overwrite? [y/n/ESC]?";
+@@ -95,6 +207,46 @@ const char* replace_dialog_title = "Replace with";
+ const char* search_dialog_title = "Search text";
+ /* clang-format on */
+
++/* Set current fg/bg taking into account syntax highlighting. */
++static void
++synsetcolor(uint16_t* fg, uint16_t* bg, const struct Color* defcolor,
++ const struct Color* selection, const int sel_on,
++ const struct Color* highlight, const int hi_on,
++ const struct SyntaxRegion* syntax, const ssize_t syntax_length,
++ const int x)
++{
++ ssize_t i;
++ assert((fg != NULL) && (bg != NULL) && (defcolor != NULL)
++ && (selection != NULL) && (highlight != NULL));
++ if (hi_on)
++ {
++ *fg = highlight->fg;
++ *bg = highlight->bg;
++ return;
++ }
++
++ if (sel_on)
++ {
++ *fg = selection->fg;
++ *bg = selection->bg;
++ return;
++ }
++
++ for (i = 0; i < syntax_length; i++)
++ {
++ if ((syntax[i].start <= x)
++ && (x < syntax[i].start + syntax[i].length))
++ {
++ *fg = syntax[i].color.fg;
++ *bg = syntax[i].color.bg;
++ return;
++ }
++ }
++
++ *fg = defcolor->fg;
++ *bg = defcolor->bg;
++}
++
+ void get_selection(int* ssr, int* ssc, int* ser, int* sec,
+ const struct DrawState* state);
+
+@@ -264,15 +416,15 @@ draw_help(const struct DrawState* state)
+ endy = h > maxy - 1 ? maxy - 2 : starty + h;
+
+ draw_box(startx, endx, starty, endy, DLG_FG, DLG_BG);
+- if (draw_string(startx, starty, TITLE_FG, TITLE_BG, help_title,
++ if (draw_string(startx, starty, TITLE_FG, TITLE_BG, help_title, NULL, 0,
+ strlen(help_title) + 2, w, 1, 1, CENTER, 0, 0, NULL, 0, 0,
+ 0, 0, 0, 0))
+ return 1;
+ while ((size_t)row < LEN(help))
+ {
+ if (draw_string(startx + 1, starty + 1 + row, DLG_FG, DLG_BG,
+- help[row], w - 2, w - 2, 1, 1, LEFT, 0, 1, NULL, 0,
+- 0, 0, 0, 0, 0))
++ help[row], NULL, 0, w - 2, w - 2, 1, 1, LEFT, 0, 1,
++ NULL, 0, 0, 0, 0, 0, 0))
+ return 1;
+ row++;
+ }
+@@ -307,15 +459,16 @@ draw_input_dialog(const struct DrawState* state)
+ INPUT_EDIT_BG);
+ if (state->input_title)
+ if (draw_string(startx, starty, TITLE_FG, TITLE_BG,
+- state->input_title, strlen(state->input_title) + 2,
+- w, 1, 1, CENTER, 0, 0, NULL, 0, 0, 0, 0, 0, 0))
++ state->input_title, NULL, 0,
++ strlen(state->input_title) + 2, w, 1, 1, CENTER, 0,
++ 0, NULL, 0, 0, 0, 0, 0, 0))
+ return 1;
+ dlen = display_length(state->input, len);
+ if (dlen > state->input_first_shown_column)
+ u32_draw_string(startx + 1, starty + 1, INPUT_EDIT_FG,
+- INPUT_EDIT_BG, state->input, w - 2, w - 2, 1, 1, LEFT,
+- state->input_first_shown_column, 1, NULL, 0, 0, 0, 0, 0,
+- 0);
++ INPUT_EDIT_BG, state->input, NULL, 0, w - 2, w - 2, 1,
++ 1, LEFT, state->input_first_shown_column, 1, NULL, 0, 0,
++ 0, 0, 0, 0);
+ tb_set_cursor(startx + 2 + state->input_display_column
+ - state->input_first_shown_column,
+ starty + 1);
+@@ -336,7 +489,7 @@ draw_line_number(const struct DrawState* state, const
+ pnumbuf = numbuf_end - numbuf > state->line_numbers_width
+ ? numbuf_end - state->line_numbers_width
+ : numbuf;
+- draw_string(0, cy, LINENO_FG, LINENO_BG, pnumbuf,
++ draw_string(0, cy, LINENO_FG, LINENO_BG, pnumbuf, NULL, 0,
+ numbuf_end - numbuf + 1, state->line_numbers_width, 0,
+ 0, RIGHT, 0, 1, NULL, 0, 0, 0, 0, 0, 0);
+ }
+@@ -416,9 +569,10 @@ draw_screen(const struct DrawState* state)
+ u32_draw_string(state->line_numbers
+ ? state->line_numbers_width + 2
+ : 0,
+- cy, TEXT_FG, TEXT_BG, current->text, maxx, maxx,
+- 0, 0, LEFT, fsc, 0, state->search, SEARCH_FG,
+- SEARCH_BG, ssi, sei, SEL_FG, SEL_BG);
++ cy, TEXT_FG, TEXT_BG, current->text,
++ current->syntax, current->syntax_length, maxx,
++ maxx, 0, 0, LEFT, fsc, 0, state->search,
++ SEARCH_FG, SEARCH_BG, ssi, sei, SEL_FG, SEL_BG);
+ i++;
+ cy++;
+ }
+@@ -448,22 +602,22 @@ draw_status(const struct DrawState* state)
+ if (IN(state->mode, M_PROMPT) && *state->prompt)
+ {
+ if (draw_string(0, state->maxy - 1, PROMPT_FG, PROMPT_BG,
+- state->prompt, strlen(state->prompt) + 2, maxx, 1,
+- 1, CENTER, 0, 1, NULL, 0, 0, 0, 0, 0, 0))
++ state->prompt, NULL, 0, strlen(state->prompt) + 2,
++ maxx, 1, 1, CENTER, 0, 1, NULL, 0, 0, 0, 0, 0, 0))
+ return 1;
+ }
+ else if (state->action && *state->action)
+ {
+ if (draw_string(0, state->maxy - 1, ACTION_FG, ACTION_BG,
+- state->action, strlen(state->action) + 2, maxx, 1,
+- 1, CENTER, 0, 1, NULL, 0, 0, 0, 0, 0, 0))
++ state->action, NULL, 0, strlen(state->action) + 2,
++ maxx, 1, 1, CENTER, 0, 1, NULL, 0, 0, 0, 0, 0, 0))
+ return 1;
+ }
+ else if (state->error && *state->error)
+ {
+ if (draw_string(0, state->maxy - 1, ERROR_FG, ERROR_BG,
+- state->error, strlen(state->error) + 2, maxx, 1, 1,
+- CENTER, 0, 1, NULL, 0, 0, 0, 0, 0, 0))
++ state->error, NULL, 0, strlen(state->error) + 2,
++ maxx, 1, 1, CENTER, 0, 1, NULL, 0, 0, 0, 0, 0, 0))
+ return 1;
+ }
+ else
+@@ -486,7 +640,7 @@ draw_status(const struct DrawState* state)
+ if (pseg == status_segments + LEN(status_segments) - 1)
+ seg_size = maxx - 1 - current_start;
+ if (draw_string(current_start, state->maxy - 1,
+- pseg->fg, pseg->bg, buf,
++ pseg->fg, pseg->bg, buf, NULL, 0,
+ MIN(seg_size, (ssize_t)(strlen(buf) + 2)),
+ last ? seg_size + 1 : seg_size, 1, 1,
+ pseg->alignment, 0, 1, NULL, 0, 0, 0, 0, 0,
+@@ -521,8 +675,9 @@ draw_status(const struct DrawState* state)
+ * */
+ int
+ draw_string(const int x, const int y, const uint16_t fg, const uint16_t bg,
+- const char* s, const int mc, const int fc, const int p_l, const int p_r,
+- const Alignment align, const int ds, const int fill,
++ const char* s, const struct SyntaxRegion* syntax,
++ const ssize_t syntax_length, const int mc, const int fc, const int p_l,
++ const int p_r, const Alignment align, const int ds, const int fill,
+ const uint32_t* srch, const uint16_t sfg, const uint16_t sbg,
+ const int ssi, const int sei, const uint16_t slfg, const uint16_t slbg)
+ {
+@@ -532,8 +687,8 @@ draw_string(const int x, const int y, const uint16_t f
+ if (!us)
+ return 1;
+ u8_string_to_unicode(us, s, mc);
+- u32_draw_string(x, y, fg, bg, us, mc, fc, p_l, p_r, align, ds, fill,
+- srch, sfg, sbg, ssi, sei, slfg, slbg);
++ u32_draw_string(x, y, fg, bg, us, syntax, syntax_length, mc, fc, p_l,
++ p_r, align, ds, fill, srch, sfg, sbg, ssi, sei, slfg, slbg);
+ free(us);
+ return 0;
+ }
+@@ -638,6 +793,8 @@ free_bufferline(struct BufferLine* bl)
+ bl->length = 0;
+ bl->fg = 0;
+ bl->bg = 0;
++ free(bl->syntax);
++ bl->syntax = NULL;
+ }
+
+ void
+@@ -676,6 +833,7 @@ init_bufferline(struct BufferLine* bl)
+ bl->size = 0;
+ bl->fg = 0;
+ bl->bg = 0;
++ bl->syntax = NULL;
+ }
+
+ void
+@@ -701,6 +859,11 @@ init_drawstate(struct DrawState* state, char* error, c
+ state->orig_real_filename = NULL;
+ state->backup_filename = NULL;
+ state->rfn_len = 0;
++ state->recalculate_syntax = 1;
++ state->syntax_enabled = 0;
++ state->keywords = NULL;
++ state->comments = NULL;
++ state->operators = NULL;
+ }
+ state->input = NULL;
+ state->search = NULL;
+@@ -759,7 +922,272 @@ recalculate_size(struct DrawState* state)
+ recalculate_line_numbers(state);
+ }
+
++int
++syntax_add(struct BufferLine* bl, SyntaxElement type, const ssize_t start,
++ const ssize_t length)
++{
++ struct SyntaxRegion* temps = NULL;
++ struct SyntaxRegion* newreg;
++
++ assert(bl != NULL);
++ if (bl->syntax_length + 1 >= bl->syntax_size)
++ {
++ temps = reallocarray(bl->syntax, bl->syntax_size + 1,
++ sizeof(struct SyntaxRegion));
++ if (!temps)
++ return -1;
++ bl->syntax = temps;
++ bl->syntax_size++;
++ }
++ newreg = &bl->syntax[bl->syntax_length];
++ newreg->start = start;
++ newreg->length = length;
++ newreg->color.fg = syntax_colors[type].fg;
++ newreg->color.bg = syntax_colors[type].bg;
++ bl->syntax_length++;
++ return 0;
++}
++
+ void
++recalculate_syntax(struct DrawState* state)
++{
++ struct BufferLine* curline;
++ struct Keyword* keyword;
++ struct Comment* comment;
++ struct Operator* operator;
++ struct Keyword* klist;
++ struct Comment* clist;
++ struct Operator* olist;
++ char* temp;
++ ssize_t i, li, ti, comment_start, quote_start, literal_start;
++ uint32_t quote;
++ int comment_index, seen_end_comment;
++
++ assert((state != NULL) && (state->buffer != NULL)
++ && (state->keywords != NULL) && (state->comments != NULL)
++ && (state->operators != NULL)
++ && (state->buffer[state->first_shown.row].text != NULL));
++
++ klist = state->keywords;
++ clist = state->comments;
++ olist = state->operators;
++ li = state->first_shown.row;
++ quote = 0;
++ comment_index = -1;
++recalc_syn_next_line:
++ curline = state->buffer + li;
++ free(curline->syntax);
++ curline->syntax = NULL;
++ curline->syntax_length = 0;
++ curline->syntax_size = 0;
++ if (li == state->first_shown.row + state->maxy - 1)
++ return;
++
++ ti = 0;
++ quote_start = 0;
++ comment_start = 0;
++recalc_syn_next_token:
++ if ((ti >= curline->length) || (curline->text[ti] == 0L))
++ {
++ li++;
++ goto recalc_syn_next_line;
++ }
++
++ if (quote)
++ goto recalc_syn_skip_comments;
++
++ /* Check for comments */
++ if (comment_index >= 0)
++ {
++ seen_end_comment = 0;
++ while (ti < curline->length)
++ {
++ seen_end_comment = u32_starts_with(&curline->text[ti],
++ clist[comment_index].end_text);
++ if (seen_end_comment)
++ break;
++ ti++;
++ }
++ assert(clist[comment_index].end_size > 1);
++ ti += clist[comment_index].end_size - 1;
++ if (seen_end_comment)
++ comment_index = -1;
++ if (syntax_add(curline, SE_COMMENT, comment_start,
++ ti - comment_start)
++ < 0)
++ {
++ MEMCCPY(state->error, errors[ERR_CANT_ALLOC],
++ LINE_DEFAULT, temp);
++ state->running = 0;
++ return;
++ }
++ ti++;
++ comment_start = 0;
++ if (comment_index >= 0)
++ {
++ li++;
++ goto recalc_syn_next_line;
++ }
++ goto recalc_syn_next_token;
++ }
++
++ i = 0;
++ comment = &clist[0];
++ while (comment->start_text != NULL)
++ {
++ if ((curline->length >= ti + comment->start_size - 1)
++ && (u32_starts_with(&curline->text[ti],
++ comment->start_text)))
++ {
++ assert(comment->start_size > 1);
++ if (comment->end_text != NULL)
++ goto recalc_syn_ml_comment;
++
++ if (syntax_add(curline, SE_COMMENT, ti,
++ curline->length - ti)
++ < 0)
++ {
++ MEMCCPY(state->error, errors[ERR_CANT_ALLOC],
++ LINE_DEFAULT, temp);
++ state->running = 0;
++ return;
++ }
++ li++;
++ goto recalc_syn_next_line;
++
++ recalc_syn_ml_comment:
++ comment_start = ti;
++ comment_index = i;
++ ti += comment->start_size;
++ goto recalc_syn_next_token;
++ }
++ i++;
++ comment = &clist[i];
++ }
++
++ /* Check for string literals */
++recalc_syn_skip_comments:
++ if (quote)
++ {
++ while ((ti < curline->length)
++ && (((ti > 0) && (curline->text[ti - 1] == L'\\'))
++ || (curline->text[ti] != quote)))
++ ti++;
++ if (curline->text[ti] == quote)
++ {
++ quote = 0;
++ ti++;
++ }
++ assert(ti != quote_start);
++ if (syntax_add(curline, SE_LITERAL, quote_start,
++ ti - quote_start)
++ < 0)
++ {
++ MEMCCPY(state->error, errors[ERR_CANT_ALLOC],
++ LINE_DEFAULT, temp);
++ state->running = 0;
++ return;
++ }
++ quote_start = 0;
++ goto recalc_syn_next_token;
++ }
++
++ assert(quote == 0);
++ if (((ti == 0) || ((ti > 0) && (curline->text[ti - 1] != L'\\')))
++ && ((curline->text[ti] == '"') || (curline->text[ti] == '\'')))
++ {
++ quote = curline->text[ti];
++ quote_start = ti;
++ ti++;
++ goto recalc_syn_next_token;
++ }
++
++ /* Check for numeric literals */
++ if (isdigit((const char)curline->text[ti])
++ && ((ti == 0) || ((ti > 0) && !isalnum(curline->text[ti - 1]))))
++ {
++ literal_start = ti;
++ while ((ti < curline->length) && (isdigit(curline->text[ti])))
++ ti++;
++ assert(ti != literal_start);
++ if (syntax_add(curline, SE_LITERAL, literal_start,
++ ti - literal_start)
++ < 0)
++ {
++ MEMCCPY(state->error, errors[ERR_CANT_ALLOC],
++ LINE_DEFAULT, temp);
++ state->running = 0;
++ return;
++ }
++ goto recalc_syn_next_token;
++ }
++
++ /* Check for keywords */
++ i = 0;
++ keyword = klist;
++ while (keyword->text != NULL)
++ {
++ if ((curline->length >= ti + keyword->size - 1)
++ && ((ti == 0)
++ || ((ti > 0) && !isalnum(curline->text[ti - 1])
++ && curline->text[ti - 1] != L'_'))
++ && (!isalnum(curline->text[ti + keyword->size - 1]))
++ && (curline->text[ti + keyword->size - 1] != L'_')
++ && (u32_starts_with(&curline->text[ti], keyword->text)))
++ {
++ /* Add new syntax region */
++ assert(keyword->size > 1);
++ if (syntax_add(curline, SE_KEYWORD, ti,
++ keyword->size - 1)
++ < 0)
++ {
++ MEMCCPY(state->error, errors[ERR_CANT_ALLOC],
++ LINE_DEFAULT, temp);
++ state->running = 0;
++ return;
++ }
++ ti += keyword->size - 1;
++ goto recalc_syn_next_token;
++ }
++ i++;
++ keyword = &klist[i];
++ }
++
++ /* Check for operators */
++ i = 0;
++ /* clang-format off */
++ operator = olist;
++ /* clang-format on */
++ while (operator->text != NULL)
++ {
++ if ((curline->length >= ti + operator->size - 1)
++ && (u32_starts_with(&curline->text[ti], operator->text)))
++ {
++ /* Add new syntax region */
++ assert(operator->size> 1);
++ if (syntax_add(curline, SE_OPERATOR,
++ ti, operator->size - 1)
++ < 0)
++ {
++ MEMCCPY(state->error, errors[ERR_CANT_ALLOC],
++ LINE_DEFAULT, temp);
++ state->running = 0;
++ return;
++ }
++ ti += operator->size - 1;
++ goto recalc_syn_next_token;
++ }
++ i++;
++ /* clang-format off */
++ operator = &olist[i];
++ /* clang-format on */
++ }
++
++ ti++;
++ goto recalc_syn_next_token;
++}
++
++void
+ redraw_status(struct DrawState* state)
+ {
+ assert(state != NULL);
+@@ -768,6 +1196,11 @@ redraw_status(struct DrawState* state)
+ recalculate_size(state);
+ state->recalculate_size = 0;
+ }
++ if (state->syntax_enabled && state->recalculate_syntax)
++ {
++ recalculate_syntax(state);
++ state->recalculate_syntax = 0;
++ }
+ (void)draw_status(state);
+ tb_present();
+ }
+@@ -790,7 +1223,8 @@ redraw_status(struct DrawState* state)
+ * */
+ void
+ u32_draw_string(const int x, const int y, const uint16_t fg, const uint16_t bg,
+- const uint32_t* s, const int mc, const int fc, const int p_l,
++ const uint32_t* s, const struct SyntaxRegion* syntax,
++ const ssize_t syntax_length, const int mc, const int fc, const int p_l,
+ const int p_r, const Alignment align, const int ds, const int fill,
+ const uint32_t* srch, const uint16_t sfg, const uint16_t sbg,
+ const int ssi, const int sei, const uint16_t slfg, const uint16_t slbg)
+@@ -828,6 +1262,8 @@ u32_draw_string(const int x, const int y, const uint16
+
+ int w = 0; /* actual on-screen rune width */
+
++ int sel_on = 0;
++
+ const int ts = TAB_SIZE;
+
+ const uint32_t* ps = s; /* pointer to current character in
+@@ -836,11 +1272,23 @@ u32_draw_string(const int x, const int y, const uint16
+ uint16_t cfg = fg; /* current fg */
+ uint16_t cbg = bg; /* current bg */
+
++ struct Color defcolor, selection, highlight;
++
+ int hctl = 0; /* highlight "characters to live", when this
+ * reaches zero the highlighting is turned
+ * off */
+
+ assert(s != NULL);
++
++ defcolor.fg = fg;
++ defcolor.bg = bg;
++
++ selection.fg = slfg;
++ selection.bg = slbg;
++
++ highlight.fg = sfg;
++ highlight.bg = sbg;
++
+ c = mc - p_l - p_r;
+
+ if (align == LEFT)
+@@ -872,7 +1320,12 @@ u32_draw_string(const int x, const int y, const uint16
+ */
+ while (*ps && sc < c)
+ {
+- if ((hctl == 0) && ((ps - s) >= ssi) && ((ps - s) < sei))
++ cfg = fg;
++ cbg = bg;
++
++ sel_on = ((ps - s) >= ssi) && ((ps - s) < sei);
++
++ if ((hctl == 0) && sel_on)
+ {
+ cfg = slfg;
+ cbg = slbg;
+@@ -889,6 +1342,9 @@ u32_draw_string(const int x, const int y, const uint16
+ }
+ }
+
++ synsetcolor(&cfg, &cbg, &defcolor, &selection, sel_on,
++ &highlight, hctl > 0, syntax, syntax_length, ps - s);
++
+ if (*ps == '\t')
+ {
+ if (ssc < ds)
+@@ -1004,11 +1460,11 @@ u32_draw_string(const int x, const int y, const uint16
+ }
+ ssc += w;
+ }
++ sel_on = ((ps - s) >= ssi) && ((ps - s) < sei);
++
+ if (hctl == 0)
+- {
+- cfg = fg;
+- cbg = bg;
+- }
++ synsetcolor(&cfg, &cbg, &defcolor, &selection, sel_on,
++ &highlight, 0, syntax, syntax_length, ps - s);
+ ps++;
+ }
+
+diff --git a/draw.h b/draw.h
+index 39ca2835b..fb80cff14 100644
+--- a/draw.h
++++ b/draw.h
+@@ -37,17 +37,58 @@ enum {
+
+ typedef enum { LEFT, CENTER, RIGHT } Alignment;
+
++typedef enum { FT_GENERIC, FT_C, FT_SHELL } FileType;
++
++typedef enum {
++ SE_DEFAULT,
++ SE_COMMENT,
++ SE_KEYWORD,
++ SE_OPERATOR,
++ SE_LITERAL
++} SyntaxElement;
++
++struct Keyword {
++ uint32_t* text;
++ int size;
++};
++
++struct Comment {
++ uint32_t* start_text;
++ uint32_t* end_text;
++ int start_size;
++ int end_size;
++};
++
++struct Operator {
++ uint32_t* text;
++ int size;
++};
++
+ struct Coord {
+ int col;
+ int row;
+ };
+
++struct Color {
++ uint16_t fg;
++ uint16_t bg;
++};
++
++struct SyntaxRegion {
++ ssize_t start;
++ ssize_t length;
++ struct Color color;
++};
++
+ struct BufferLine {
+ uint32_t* text;
+ ssize_t length; /* both in uint32_t units */
+ ssize_t size;
+ uint16_t fg;
+ uint16_t bg;
++ struct SyntaxRegion* syntax;
++ ssize_t syntax_length;
++ ssize_t syntax_size;
+ };
+
+ struct DrawState {
+@@ -87,12 +128,17 @@ struct DrawState {
+ int dirty;
+ int new_file;
+ int recalculate_size;
++ int syntax_enabled;
++ int recalculate_syntax;
+ int mode;
+ int maxx;
+ int maxy;
+ struct Coord sel_start; /* buffer coordinate */
+ struct Coord sel_end; /* buffer coordinate */
+ int backup_possible; /* is saving backup possible? */
++ struct Keyword* keywords;
++ struct Comment* comments;
++ struct Operator* operators;
+ };
+
+ struct Key {
+@@ -109,6 +155,13 @@ struct StatusSegment {
+ Alignment alignment;
+ };
+
++extern const struct Keyword c_keywords[];
++extern const struct Comment c_comments[];
++extern const struct Operator c_operators[];
++extern const struct Keyword shell_keywords[];
++extern const struct Comment shell_comments[];
++extern const struct Operator shell_operators[];
++
+ void assign_coord(struct Coord* dest, const struct Coord src);
+ ssize_t buffer_length(uint32_t* buffer, const ssize_t max_index);
+ int calculate_help_width(const struct DrawState* state);
+@@ -125,7 +178,8 @@ void draw_rect(const int startx, const int endx, const
+ int draw_screen(const struct DrawState* state);
+ int draw_status(const struct DrawState* state);
+ int draw_string(const int x, const int y, const uint16_t fg, const uint16_t bg,
+- const char* s, const int max_cols, const int fill_cols,
++ const char* s, const struct SyntaxRegion* syntax,
++ const ssize_t syntax_length, const int max_cols, const int fill_cols,
+ const int padding_left, const int padding_right, const Alignment align,
+ const int ds, const int fill, const uint32_t* srch, const uint16_t sfg,
+ const uint16_t sbg, const int ssi, const int sei, const uint16_t slfg,
+@@ -148,10 +202,12 @@ void init_drawstate(struct DrawState* state, char* err
+ const int skip_filenames_init);
+ void recalculate_line_numbers(struct DrawState* state);
+ void recalculate_size(struct DrawState* state);
++void recalculate_syntax(struct DrawState* state);
+ void redraw_status(struct DrawState* state);
+ void u32_draw_string(const int x, const int y, const uint16_t fg,
+- const uint16_t bg, const uint32_t* s, const int max_cols,
+- const int fill_cols, const int padding_left, const int padding_right,
+- const Alignment align, const int ds, const int fill,
+- const uint32_t* srch, const uint16_t sfg, const uint16_t sbg,
+- const int ssi, const int sei, const uint16_t slfg, const uint16_t slbg);
++ const uint16_t bg, const uint32_t* s, const struct SyntaxRegion* syntax,
++ const ssize_t syntax_length, const int max_cols, const int fill_cols,
++ const int padding_left, const int padding_right, const Alignment align,
++ const int ds, const int fill, const uint32_t* srch, const uint16_t sfg,
++ const uint16_t sbg, const int ssi, const int sei, const uint16_t slfg,
++ const uint16_t slbg);
+diff --git a/sled.c b/sled.c
+index 39ca2835b..fb80cff14 100644
+--- a/sled.c
++++ b/sled.c
+@@ -67,6 +67,18 @@ extern const char* prompt_reread_dirty;
+ extern const char* replace_dialog_title;
+ extern const char* search_dialog_title;
+
++struct SyntaxEnding {
++ char* text;
++ FileType type;
++};
++/* clang-format off */
++static const struct SyntaxEnding syntax_endings[] = {
++ { .text = ".c", .type = FT_C },
++ { .text = ".h", .type = FT_C },
++ { .text = ".sh", .type = FT_SHELL }
++};
++/* clang-format on */
++
+ static int
+ cancel_close(struct DrawState* state)
+ {
+@@ -195,6 +207,8 @@ create_file(struct DrawState* state)
+ recalculate_line_numbers(state);
+ state->coord.row = 0;
+ state->dirty = 1;
++ if (state->syntax_enabled)
++ recalculate_syntax(state);
+
+ return LOAD_ERR_NONE;
+ }
+@@ -325,6 +339,8 @@ do_search_replace(struct DrawState* state)
+ state->buffer[row].length = buflen;
+ }
+ free(buf);
++ if (state->syntax_enabled)
++ recalculate_syntax(state);
+ return 0;
+
+ do_search_replace_alloc_error:
+@@ -378,6 +394,8 @@ erase_backwards(struct DrawState* state)
+ buffer_line->length--;
+ position_to(state, state->coord.row, state->coord.col - 1);
+ state->dirty = 1;
++ if (state->syntax_enabled)
++ recalculate_syntax(state);
+ }
+ else if (state->coord.row > 0)
+ {
+@@ -415,6 +433,8 @@ erase_forward(struct DrawState* state)
+ buffer_line->text[i] = 0;
+ buffer_line->length--;
+ state->dirty = 1;
++ if (state->syntax_enabled)
++ recalculate_syntax(state);
+ }
+ else if ((state->coord.row + 1) < state->rows_count)
+ simple_join_lines(state);
+@@ -450,6 +470,8 @@ erase_line(struct DrawState* state)
+ position_to(state, state->coord.row, 0);
+ state->rows_count--;
+ state->dirty = 1;
++ if (state->syntax_enabled)
++ recalculate_syntax(state);
+ recalculate_line_numbers(state);
+ return 0;
+
+@@ -489,6 +511,8 @@ erase_prev_word(struct DrawState* state)
+ state->coord.col = i;
+ state->display_column = display_length(current->text, state->coord.col);
+ state->dirty = 1;
++ if (state->syntax_enabled)
++ recalculate_syntax(state);
+ return 0;
+ }
+
+@@ -545,6 +569,8 @@ erase_selection(struct DrawState* state)
+ }
+ free(buf);
+ state->dirty = 1;
++ if (state->syntax_enabled)
++ recalculate_syntax(state);
+ }
+ return 0;
+ }
+@@ -570,6 +596,8 @@ erase_to_end(struct DrawState* state)
+ buffer_line->length = state->coord.col;
+ move_end(state);
+ state->dirty = 1;
++ if (state->syntax_enabled)
++ recalculate_syntax(state);
+ return 0;
+ }
+
+@@ -604,6 +632,8 @@ erase_to_start(struct DrawState* state)
+ buffer_line->length = len - state->coord.col;
+ move_start(state);
+ state->dirty = 1;
++ if (state->syntax_enabled)
++ recalculate_syntax(state);
+ return 0;
+ }
+
+@@ -1160,6 +1190,8 @@ insert_char(struct DrawState* state, uint32_t ch)
+
+ insert_skip_wrap:
+ state->dirty = 1;
++ if (state->syntax_enabled)
++ recalculate_syntax(state);
+
+ return 0;
+
+@@ -1865,7 +1897,11 @@ position_to(struct DrawState* state, const int row, co
+ state->first_shown.col = fsc;
+ }
+ if (old_first_shown_row != state->first_shown.row)
++ {
++ if (state->syntax_enabled)
++ recalculate_syntax(state);
+ recalculate_line_numbers(state);
++ }
+
+ if (IN(state->mode, M_SELECTION))
+ assign_coord(&state->sel_end, state->coord);
+@@ -2025,6 +2061,8 @@ reread_file(struct DrawState* state)
+ break;
+ default:
+ recalculate_line_numbers(state);
++ if (state->syntax_enabled)
++ recalculate_syntax(state);
+ }
+ return result;
+ }
+@@ -2905,6 +2943,8 @@ main(int argc, char** argv)
+ int result = 0;
+ int dir_access;
+ int dir_access_errno;
++ int found_ending = 0;
++ int i;
+
+ *error = 0;
+ *action = 0;
+@@ -2988,6 +3028,40 @@ main(int argc, char** argv)
+
+ init_drawstate(&state, error, action, filename, prompt, 0, 0);
+
++ for (i = 0; (size_t)i < LEN(syntax_endings); i++)
++ {
++ int filename_len = strlen(state.filename);
++ int ending_len = strlen(syntax_endings[i].text);
++
++ found_ending = 0;
++ if ((filename_len >= ending_len)
++ && !strcmp(syntax_endings[i].text,
++ state.filename + filename_len - ending_len))
++ {
++ found_ending = 1;
++ break;
++ }
++ }
++ if (found_ending)
++ {
++ state.syntax_enabled = 1;
++ switch (syntax_endings[i].type)
++ {
++ case FT_C:
++ state.keywords = (struct Keyword*)c_keywords;
++ state.comments = (struct Comment*)c_comments;
++ state.operators = (struct Operator*)c_operators;
++ break;
++ case FT_SHELL:
++ state.keywords = (struct Keyword*)shell_keywords;
++ state.comments = (struct Comment*)shell_comments;
++ state.operators = (struct Operator*)shell_operators;
++ break;
++ default:
++ /* Shouldn't happen */
++ assert(0);
++ }
++ }
+ #ifdef __OpenBSD__
+ if (unveil(state.filename, "rwc") < 0)
+ {
+@@ -3054,6 +3128,8 @@ main(int argc, char** argv)
+ }
+ else /* rfn is relative */
+ {
++ size_t rfn_dir_len;
++
+ state.orig_real_filename
+ = strndup(rfn, state.rfn_len);
+ if (!state.orig_real_filename)
+@@ -3069,11 +3145,13 @@ main(int argc, char** argv)
+ print_error(1, "Pathname too long");
+ goto main_cleanup;
+ }
+- *(temp - 1) = '/';
++ *(temp - 1) = '/'; /* overwrite NUL */
++ rfn_dir_len = temp - state.real_filename - 1;
+ MEMCCPY_EXT(temp, state.real_filename, rfn,
+- limits[L_PATH_MAX] - state.rfn_len - 1,
+- temp);
+- state.rfn_len = temp - state.real_filename - 1;
++ limits[L_PATH_MAX] - rfn_dir_len, temp);
++ if (temp)
++ state.rfn_len = temp
++ - state.real_filename - 1;
+ }
+ free(rfn);
+ rfn = NULL;
+@@ -3206,9 +3284,10 @@ main_skip_backups:
+
+ init_termbox:
+ tb_init();
+- *error = 0;
+- state.maxx = 0;
+- state.recalculate_size = 1;
++ *error = 0;
++ state.maxx = 0;
++ state.recalculate_size = 1;
++ state.recalculate_syntax = 1;
+
+ while (state.running)
+ {
+@@ -3220,6 +3299,11 @@ init_termbox:
+ recalculate_size(&state);
+ state.recalculate_size = 0;
+ }
++ if (state.syntax_enabled && state.recalculate_syntax)
++ {
++ recalculate_syntax(&state);
++ state.recalculate_syntax = 0;
++ }
+ if (draw(&state))
+ {
+ state.running = 0;
+--
+2.44.1
+