sled

Једноставни уређивач текста
git clone https://git.sr.ht/~strahinja/sled
Дневник | Датотеке | Референце | ПРОЧИТАЈМЕ | ЛИЦЕНЦА

syntax.patch (36903B)


      1 From e0b72ba7c83465d51e24140506426098a9bf78c7 Mon Sep 17 00:00:00 2001
      2 From: Страхиња Радић <sr@strahinja.org>
      3 Date: Tue Mar 31 14:06:36 2026 UTC
      4 Subject: [PATCH] Add syntax highlighting
      5 MIME-Version: 1.0
      6 Content-Type: text/plain; charset=UTF-8
      7 Content-Transfer-Encoding: 8bit
      8 
      9 ---
     10  config.def.h  |    7+   0-
     11  draw.c        |  618+  31-
     12  draw.h        |   76+   6-
     13  sled.c        |   82+  15-
     14  4 files changed, 783 insertions(+), 52 deletions(-)
     15 
     16 diff --git a/config.def.h b/config.def.h
     17 index 68cc281d8..e0b72ba7c 100644
     18 --- a/config.def.h
     19 +++ b/config.def.h
     20 @@ -234,6 +234,13 @@ static const struct Key command_keys[] = {
     21  #endif /* SLED_C_GLOBALS */
     22  
     23  /* clang-format off */
     24 +static const struct Color syntax_colors[] = {
     25 +	[SE_DEFAULT]	= { .fg = TB_DEFAULT,		.bg = TB_DEFAULT },
     26 +	[SE_COMMENT]	= { .fg = TB_GREEN,		.bg = TB_DEFAULT },
     27 +	[SE_KEYWORD]	= { .fg = TB_CYAN | TB_BOLD,	.bg = TB_DEFAULT },
     28 +	[SE_OPERATOR]	= { .fg = TB_YELLOW | TB_BOLD,	.bg = TB_DEFAULT },
     29 +	[SE_LITERAL]	= { .fg = TB_MAGENTA | TB_BOLD,	.bg = TB_DEFAULT },
     30 +};
     31  static const struct StatusSegment status_segments[] = {
     32  	{.format      = "%s%s",
     33  	 .callback    = format_filename,
     34 diff --git a/draw.c b/draw.c
     35 index 68cc281d8..e0b72ba7c 100644
     36 --- a/draw.c
     37 +++ b/draw.c
     38 @@ -3,6 +3,7 @@
     39   * See the file LICENSE for exact copyright and license details. */
     40  
     41  #include <assert.h>
     42 +#include <ctype.h>
     43  #include <errno.h>
     44  #include <stdio.h>
     45  #include <stdlib.h>
     46 @@ -14,6 +15,25 @@
     47  #include "util.h"
     48  #include "version.h"
     49  
     50 +/* Keyword initializer specification macro */
     51 +#define KW(literal)                                                         \
     52 +	{                                                                   \
     53 +		.text = literal, .size = sizeof(literal) / sizeof(*literal) \
     54 +	}
     55 +/* Single-line comment specification macro */
     56 +#define SLC(com)                                                             \
     57 +	{                                                                    \
     58 +		.start_text = com, .start_size = sizeof(com) / sizeof(*com), \
     59 +		.end_text = NULL, .end_size = 0                              \
     60 +	}
     61 +/* Area comment specification macro */
     62 +#define AC(com1, com2)                                                        \
     63 +	{                                                                     \
     64 +		.start_text = com1,                                           \
     65 +		.start_size = sizeof(com1) / sizeof(*com1), .end_text = com2, \
     66 +		.end_size = sizeof(com2) / sizeof(*com2)                      \
     67 +	}
     68 +
     69  enum {
     70  	UNKNOWN_MODE,
     71  	COMMAND_MODE,
     72 @@ -89,6 +109,95 @@ static const struct {
     73  	{ .boundary =      99, .width = 3 },
     74  	{ .boundary =       9, .width = 2 }
     75  };
     76 +extern const struct Color syntax_colors[];
     77 +const struct Keyword c_keywords[] = {
     78 +	KW("auto"), KW("break"), KW("case"), KW("char"), KW("const"),
     79 +	KW("continue"), KW("default"), KW("do"), KW("double"), KW("else"),
     80 +	KW("enum"), KW("extern"), KW("float"), KW("for"), KW("goto"),
     81 +	KW("if"), KW("inline"), KW("int"), KW("long"), KW("register"),
     82 +	KW("restrict"), KW("return"), KW("short"), KW("signed"),
     83 +	KW("sizeof"), KW("static"), KW("struct"), KW("switch"),
     84 +	KW("typedef"), KW("union"), KW("unsigned"), KW("void"),
     85 +	KW("volatile"), KW("while"), KW("_Bool"), KW("_Complex"),
     86 +	KW("_Imaginary"),
     87 +	{ .text = NULL,		.size = 0 }
     88 +};
     89 +const struct Comment c_comments[] = {
     90 +	SLC("//"),
     91 +	AC("/*", "*/"),
     92 +	{ .start_text = NULL,	.start_size = 0 }
     93 +};
     94 +const struct Operator c_operators[] = {
     95 +	KW("<<="), KW(">>="), KW("<<"), KW(">>"),
     96 +	KW("+="), KW("-="), KW("*="), KW("/="), KW("%="),
     97 +	KW("&="), KW("|="), KW("^="), 
     98 +	KW("=="), KW("!="), KW("<="), KW(">="),
     99 +	KW("->"),
    100 +	KW("++"), KW("--"),
    101 +	KW("&&"), KW("||"),
    102 +	KW("="), KW("+"), KW("-"), KW("~"), KW("*"), KW("/"), KW("%"),
    103 +	KW("&"), KW("|"), KW("^"), KW("!"),
    104 +	KW("<"), KW(">"),
    105 +	KW(","), KW(";"), KW(":"), KW("?"), KW("."),
    106 +	KW("("), KW(")"), KW("["), KW("]"), KW("{"), KW("}"),
    107 +	{ .text = NULL,		.size = 0 }
    108 +};
    109 +const struct Keyword shell_keywords[]	= {
    110 +	KW("!"), KW("{"), KW("}"),
    111 +	KW("case"), KW("do"), KW("done"), KW("elif"), KW("else"),
    112 +	KW("esac"), KW("fi"), KW("for"), KW("if"), KW("in"), KW("then"),
    113 +	KW("until"), KW("while"), KW("[["), KW("]]"), KW("function"),
    114 +	KW("select"), KW("admin"), KW("alias"), KW("ar"), KW("asa"),
    115 +	KW("at"), KW("awk"), KW("basename"), KW("batch"), KW("bc"),
    116 +	KW("bg"), KW("break"), KW("c99"), KW("cal"), KW("cat"), KW("cd"),
    117 +	KW("cflow"), KW("chgrp"), KW("chmod"), KW("chown"), KW("cksum"),
    118 +	KW("cmp"), KW("colon"), KW("comm"), KW("command"), KW("compress"),
    119 +	KW("continue"), KW("cp"), KW("crontab"), KW("csplit"), KW("ctags"),
    120 +	KW("cut"), KW("cxref"), KW("date"), KW("dd"), KW("delta"),
    121 +	KW("df"), KW("diff"), KW("dirname"), KW("dot"), KW("du"),
    122 +	KW("echo"), KW("ed"), KW("env"), KW("eval"), KW("ex"), KW("exec"),
    123 +	KW("exit"), KW("expand"), KW("export"), KW("expr"), KW("false"),
    124 +	KW("fc"), KW("fg"), KW("file"), KW("find"), KW("fold"),
    125 +	KW("fort77"), KW("fuser"), KW("gencat"), KW("get"), KW("getconf"),
    126 +	KW("getopts"), KW("grep"), KW("hash"), KW("head"), KW("iconv"),
    127 +	KW("id"), KW("ipcrm"), KW("ipcs"), KW("jobs"), KW("join"),
    128 +	KW("kill"), KW("lex"), KW("link"), KW("ln"), KW("locale"),
    129 +	KW("localedef"), KW("logger"), KW("logname"), KW("lp"), KW("ls"),
    130 +	KW("m4"), KW("mailx"), KW("make"), KW("man"), KW("mesg"),
    131 +	KW("mkdir"), KW("mkfifo"), KW("more"), KW("mv"), KW("newgrp"),
    132 +	KW("nice"), KW("nl"), KW("nm"), KW("nohup"), KW("od"),
    133 +	KW("paste"), KW("patch"), KW("pathchk"), KW("pax"), KW("pr"),
    134 +	KW("printf"), KW("prs"), KW("ps"), KW("pwd"), KW("qalter"),
    135 +	KW("qdel"), KW("qhold"), KW("qmove"), KW("qmsg"), KW("qrerun"),
    136 +	KW("qrls"), KW("qselect"), KW("qsig"), KW("qstat"), KW("qsub"),
    137 +	KW("read"), KW("readonly"), KW("renice"), KW("return"), KW("rm"),
    138 +	KW("rmdel"), KW("rmdir"), KW("sact"), KW("sccs"), KW("sed"),
    139 +	KW("set"), KW("sh"), KW("shift"), KW("sleep"), KW("sort"),
    140 +	KW("split"), KW("strings"), KW("strip"), KW("stty"), KW("tabs"),
    141 +	KW("tail"), KW("talk"), KW("tee"), KW("test"), KW("time"),
    142 +	KW("times"), KW("touch"), KW("tput"), KW("tr"), KW("trap"),
    143 +	KW("true"), KW("tsort"), KW("tty"), KW("type"), KW("ulimit"),
    144 +	KW("umask"), KW("unalias"), KW("uname"), KW("uncompress"),
    145 +	KW("unexpand"), KW("unget"), KW("uniq"), KW("unlink"), KW("unset"),
    146 +	KW("uucp"), KW("uudecode"), KW("uuencode"), KW("uustat"),
    147 +	KW("uux"), KW("val"), KW("vi"), KW("wait"), KW("wc"), KW("what"),
    148 +	KW("who"), KW("write"), KW("xargs"), KW("yacc"), KW("zcat"),
    149 +	KW("break"), KW(":"), KW("."), KW("continue"), KW("eval"),
    150 +	KW("exec"), KW("exit"), KW("export"), KW("readonly"), KW("return"),
    151 +	KW("set"), KW("shift"), KW("times"), KW("trap"), KW("unset"),
    152 +	{ .text = NULL,		.size = 0 }
    153 +};
    154 +const struct Comment shell_comments[]	= {
    155 +	SLC("#"),
    156 +	{ .start_text = NULL,	.start_size = 0 }
    157 +};
    158 +const struct Operator shell_operators[] = {
    159 +	KW("<<"), KW(">>"),
    160 +	KW("&&"), KW("||"), KW("$"), KW("`"), KW("="),
    161 +	KW("<"), KW(">"),
    162 +	KW(";"), KW("("), KW(")"), KW("["), KW("]"), KW("{"), KW("}"),
    163 +	{ .text = NULL,		.size = 0 }
    164 +};
    165  const char* go_to_dialog_title   = "Go to line";
    166  const char* prompt_dirty	 = "File changed, save [y/n/ESC]?";
    167  const char* prompt_overwrite     = "Not empty, overwrite? [y/n/ESC]?";
    168 @@ -97,6 +206,46 @@ const char* replace_dialog_title = "Replace with";
    169  const char* search_dialog_title  = "Search text";
    170  /* clang-format on */
    171  
    172 +/* Set current fg/bg taking into account syntax highlighting. */
    173 +static void
    174 +synsetcolor(uint16_t* fg, uint16_t* bg, const struct Color* defcolor,
    175 +	const struct Color* selection, const int sel_on,
    176 +	const struct Color* highlight, const int hi_on,
    177 +	const struct SyntaxRegion* syntax, const ssize_t syntax_length,
    178 +	const int x)
    179 +{
    180 +	ssize_t i;
    181 +	assert((fg != NULL) && (bg != NULL) && (defcolor != NULL)
    182 +		&& (selection != NULL) && (highlight != NULL));
    183 +	if (hi_on)
    184 +	{
    185 +		*fg = highlight->fg;
    186 +		*bg = highlight->bg;
    187 +		return;
    188 +	}
    189 +
    190 +	if (sel_on)
    191 +	{
    192 +		*fg = selection->fg;
    193 +		*bg = selection->bg;
    194 +		return;
    195 +	}
    196 +
    197 +	for (i = 0; i < syntax_length; i++)
    198 +	{
    199 +		if ((syntax[i].start <= x)
    200 +			&& (x < syntax[i].start + syntax[i].length))
    201 +		{
    202 +			*fg = syntax[i].color.fg;
    203 +			*bg = syntax[i].color.bg;
    204 +			return;
    205 +		}
    206 +	}
    207 +
    208 +	*fg = defcolor->fg;
    209 +	*bg = defcolor->bg;
    210 +}
    211 +
    212  void get_selection(int* ssr, int* ssc, int* ser, int* sec,
    213  	const struct DrawState* state);
    214  
    215 @@ -270,14 +419,14 @@ draw_help(const struct DrawState* state)
    216  
    217  	draw_box(startx, endx, starty, endy, colors[C_DLG].fg, colors[C_DLG].bg);
    218  	if (draw_string(startx, starty, colors[C_TITLE].fg, colors[C_TITLE].bg,
    219 -		    help_title, strlen(help_title) + 2, w, 1, 1, CENTER, 0, 0,
    220 -		    NULL, 0, 0, 0, 0, 0, 0))
    221 +		    help_title, NULL, 0, strlen(help_title) + 2, w, 1, 1,
    222 +		    CENTER, 0, 0, NULL, 0, 0, 0, 0, 0, 0))
    223  		return 1;
    224  	while ((size_t)row < LEN(help))
    225  	{
    226  		if (draw_string(startx + 1, starty + 1 + row, colors[C_DLG].fg,
    227 -			    colors[C_DLG].bg, help[row], w - 2, w - 2, 1, 1,
    228 -			    LEFT, 0, 1, NULL, 0, 0, 0, 0, 0, 0))
    229 +			    colors[C_DLG].bg, help[row], NULL, 0, w - 2, w - 2,
    230 +			    1, 1, LEFT, 0, 1, NULL, 0, 0, 0, 0, 0, 0))
    231  			return 1;
    232  		row++;
    233  	}
    234 @@ -313,16 +462,16 @@ draw_input_dialog(const struct DrawState* state)
    235  		colors[C_INPUT_EDIT].fg, colors[C_INPUT_EDIT].bg);
    236  	if (state->input_title)
    237  		if (draw_string(startx, starty, colors[C_TITLE].fg,
    238 -			    colors[C_TITLE].bg, state->input_title,
    239 +			    colors[C_TITLE].bg, state->input_title, NULL, 0,
    240  			    strlen(state->input_title) + 2, w, 1, 1, CENTER, 0,
    241  			    0, NULL, 0, 0, 0, 0, 0, 0))
    242  			return 1;
    243  	dlen = display_length(state->input, len);
    244  	if (dlen > state->input_first_shown_column)
    245  		u32_draw_string(startx + 1, starty + 1, colors[C_INPUT_EDIT].fg,
    246 -			colors[C_INPUT_EDIT].bg, state->input, w - 2, w - 2, 1,
    247 -			1, LEFT, state->input_first_shown_column, 1, NULL, 0, 0,
    248 -			0, 0, 0, 0);
    249 +			colors[C_INPUT_EDIT].bg, state->input, NULL, 0, w - 2,
    250 +			w - 2, 1, 1, LEFT, state->input_first_shown_column, 1,
    251 +			NULL, 0, 0, 0, 0, 0, 0);
    252  	tb_set_cursor(startx + 2 + state->input_display_column
    253  			- state->input_first_shown_column,
    254  		starty + 1);
    255 @@ -344,7 +493,7 @@ draw_line_number(const struct DrawState* state, const 
    256  			? numbuf_end - state->line_numbers_width
    257  			: numbuf;
    258  		draw_string(0, cy, colors[C_LINENO].fg, colors[C_LINENO].bg,
    259 -			pnumbuf, numbuf_end - numbuf + 1,
    260 +			pnumbuf, NULL, 0, numbuf_end - numbuf + 1,
    261  			state->line_numbers_width, 0, 0, RIGHT, 0, 1, NULL, 0,
    262  			0, 0, 0, 0, 0);
    263  	}
    264 @@ -425,8 +574,9 @@ draw_screen(const struct DrawState* state)
    265  					? state->line_numbers_width + 2
    266  					: 0,
    267  				cy, colors[C_TEXT].fg, colors[C_TEXT].bg,
    268 -				current->text, maxx, maxx, 0, 0, LEFT, fsc, 0,
    269 -				state->search, colors[C_SEARCH].fg,
    270 +				current->text, current->syntax,
    271 +				current->syntax_size, maxx, maxx, 0, 0, LEFT,
    272 +				fsc, 0, state->search, colors[C_SEARCH].fg,
    273  				colors[C_SEARCH].bg, ssi, sei, colors[C_SEL].fg,
    274  				colors[C_SEL].bg);
    275  		i++;
    276 @@ -458,7 +608,7 @@ draw_status(const struct DrawState* state)
    277  	if (IN(state->mode, M_PROMPT) && *state->prompt)
    278  	{
    279  		if (draw_string(0, state->maxy - 1, colors[C_PROMPT].fg,
    280 -			    colors[C_PROMPT].bg, state->prompt,
    281 +			    colors[C_PROMPT].bg, state->prompt, NULL, 0,
    282  			    strlen(state->prompt) + 2, maxx, 1, 1, CENTER, 0, 1,
    283  			    NULL, 0, 0, 0, 0, 0, 0))
    284  			return 1;
    285 @@ -466,15 +616,15 @@ draw_status(const struct DrawState* state)
    286  	else if (state->action)
    287  	{
    288  		if (draw_string(0, state->maxy - 1, colors[C_ACTION].fg,
    289 -			    colors[C_ACTION].bg, actions[state->action],
    290 -			    strlen(actions[state->action]) + 2, maxx, 1, 1,
    291 +			    colors[C_ACTION].bg, actions[state->action], NULL,
    292 +			    0, strlen(actions[state->action]) + 2, maxx, 1, 1,
    293  			    CENTER, 0, 1, NULL, 0, 0, 0, 0, 0, 0))
    294  			return 1;
    295  	}
    296  	else if (state->error)
    297  	{
    298  		if (draw_string(0, state->maxy - 1, colors[C_ERROR].fg,
    299 -			    colors[C_ERROR].bg, errors[state->error],
    300 +			    colors[C_ERROR].bg, errors[state->error], NULL, 0,
    301  			    strlen(errors[state->error]) + 2, maxx, 1, 1,
    302  			    CENTER, 0, 1, NULL, 0, 0, 0, 0, 0, 0))
    303  			return 1;
    304 @@ -500,7 +650,7 @@ draw_status(const struct DrawState* state)
    305  				seg_size = maxx - 1 - current_start;
    306  			if (draw_string(current_start, state->maxy - 1,
    307  				    colors[pseg->color_index].fg,
    308 -				    colors[pseg->color_index].bg, buf,
    309 +				    colors[pseg->color_index].bg, buf, NULL, 0,
    310  				    MIN(seg_size, (ssize_t)(strlen(buf) + 2)),
    311  				    last ? seg_size + 1 : seg_size, 1, 1,
    312  				    pseg->alignment, 0, 1, NULL, 0, 0, 0, 0, 0,
    313 @@ -535,8 +685,9 @@ draw_status(const struct DrawState* state)
    314   * */
    315  int
    316  draw_string(const int x, const int y, const uint16_t fg, const uint16_t bg,
    317 -	const char* s, const int mc, const int fc, const int p_l, const int p_r,
    318 -	const Alignment align, const int ds, const int fill,
    319 +	const char* s, const struct SyntaxRegion* syntax,
    320 +	const ssize_t syntax_length, const int mc, const int fc, const int p_l,
    321 +	const int p_r, const Alignment align, const int ds, const int fill,
    322  	const uint32_t* srch, const uint16_t sfg, const uint16_t sbg,
    323  	const int ssi, const int sei, const uint16_t slfg, const uint16_t slbg)
    324  {
    325 @@ -546,8 +697,8 @@ draw_string(const int x, const int y, const uint16_t f
    326  	if (!us)
    327  		return 1;
    328  	u8_string_to_unicode(us, s, mc);
    329 -	u32_draw_string(x, y, fg, bg, us, mc, fc, p_l, p_r, align, ds, fill,
    330 -		srch, sfg, sbg, ssi, sei, slfg, slbg);
    331 +	u32_draw_string(x, y, fg, bg, us, syntax, syntax_length, mc, fc, p_l,
    332 +		p_r, align, ds, fill, srch, sfg, sbg, ssi, sei, slfg, slbg);
    333  	free(us);
    334  	return 0;
    335  }
    336 @@ -653,12 +804,15 @@ free_bufferline(struct BufferLine* bl)
    337  	bl->length = 0;
    338  	bl->fg	   = 0;
    339  	bl->bg	   = 0;
    340 +	free(bl->syntax);
    341 +	bl->syntax = NULL;
    342  }
    343  
    344  void
    345  free_drawstate(struct DrawState* state)
    346  {
    347  	ssize_t row = 0;
    348 +
    349  	assert(state != NULL);
    350  	for (row = 0; row < state->buffer_size; row++)
    351  		free_bufferline(state->buffer + row);
    352 @@ -672,11 +826,13 @@ init_bufferline(struct BufferLine* bl)
    353  {
    354  	if (!bl)
    355  		return;
    356 -	bl->text   = NULL;
    357 -	bl->length = 0;
    358 -	bl->size   = 0;
    359 -	bl->fg	   = 0;
    360 -	bl->bg	   = 0;
    361 +	bl->text	= NULL;
    362 +	bl->length	= 0;
    363 +	bl->size	= 0;
    364 +	bl->fg		= 0;
    365 +	bl->bg		= 0;
    366 +	bl->syntax	= NULL;
    367 +	bl->syntax_size = 0;
    368  }
    369  
    370  void
    371 @@ -701,6 +857,14 @@ init_drawstate(struct DrawState* state, char* filename
    372  		state->orig_real_filename = NULL;
    373  		state->backup_filename	  = NULL;
    374  		state->rfn_len		  = 0;
    375 +		state->recalculate_syntax = 1;
    376 +		state->syntax_enabled	  = 0;
    377 +		state->keywords		  = NULL;
    378 +		state->comments		  = NULL;
    379 +		state->operators	  = NULL;
    380 +		state->ukeywords	  = NULL;
    381 +		state->ucomments	  = NULL;
    382 +		state->uoperators	  = NULL;
    383  	}
    384  	state->input	    = NULL;
    385  	state->search	    = NULL;
    386 @@ -759,7 +923,407 @@ recalculate_size(struct DrawState* state)
    387  	recalculate_line_numbers(state);
    388  }
    389  
    390 +int
    391 +syntax_add(struct BufferLine* bl, SyntaxElement type, const ssize_t start,
    392 +	const ssize_t length)
    393 +{
    394 +	struct SyntaxRegion* temps = NULL;
    395 +	struct SyntaxRegion* newreg;
    396 +
    397 +	assert(bl != NULL);
    398 +	temps = reallocarray(bl->syntax, bl->syntax_size + 1,
    399 +		sizeof(struct SyntaxRegion));
    400 +	if (!temps)
    401 +		return -1;
    402 +	bl->syntax = temps;
    403 +	bl->syntax_size++;
    404 +	newreg		 = &bl->syntax[bl->syntax_size - 1];
    405 +	newreg->start	 = start;
    406 +	newreg->length	 = length;
    407 +	newreg->color.fg = syntax_colors[type].fg;
    408 +	newreg->color.bg = syntax_colors[type].bg;
    409 +	return 0;
    410 +}
    411 +
    412 +/*
    413 + * Regenerate syntax regions from the document text.
    414 + * This is more complicated than it should be due to several factors:
    415 + *   - We are using 32-bit Unicode strings internally (uint32_t*)
    416 + *   - For non-ASCII, C99 supports only wchar_t string literals L"string" (if we
    417 + *     don't consider using UTF-8 everywhere, which would complicate everything
    418 + *     dealing with text and introduce numerous potential bugs)
    419 + *   - There are some systems out there (Windows) which use 16-bit wchar_t since
    420 + *     the standard is disappointingly vague, so we can't rely on seamless
    421 + *     conversion from wchar_t* to uint32_t*. Using code as-is leads to infinite
    422 + *     loop hangs.
    423 + * Thus, we can't use L"literals" to define the list of keywords, operators and
    424 + * comment delimiters, so we can either always convert them to uint32_t*, which
    425 + * is severely suboptimal performance-wise, or do what seems to be the most
    426 + * optimal solution, applied here: convert everything once and keep separate
    427 + * arrays with keywords, operators and comment delimiters explicitly converted
    428 + * to uint32_t*. This comes at the expense of more code bloat.
    429 + */
    430  void
    431 +recalculate_syntax(struct DrawState* state)
    432 +{
    433 +	struct BufferLine* curline;
    434 +	struct Keyword32* keyword;
    435 +	struct Comment32* comment;
    436 +	struct Operator32* operator;
    437 +	uint32_t* temp32;
    438 +	ssize_t i, li, ti, comment_start, quote_start, literal_start;
    439 +	uint32_t quote;
    440 +	int comment_index, seen_end_comment;
    441 +
    442 +	assert((state != NULL) && (state->buffer != NULL)
    443 +		&& (state->keywords != NULL) && (state->comments != NULL)
    444 +		&& (state->operators != NULL)
    445 +		&& (state->buffer[state->first_shown.row].text != NULL));
    446 +
    447 +	if (!state->ukeywords)
    448 +	{
    449 +		struct Keyword* kw = state->keywords;
    450 +		struct Keyword32* pklist;
    451 +		size_t keyword_count = 0;
    452 +		while (kw->text != NULL)
    453 +		{
    454 +			keyword_count++;
    455 +			kw++;
    456 +		}
    457 +		assert(keyword_count > 0);
    458 +		state->ukeywords
    459 +			= calloc(keyword_count + 1, sizeof(struct Keyword32));
    460 +		if (!state->ukeywords)
    461 +		{
    462 +			state->running = 0;
    463 +			return;
    464 +		}
    465 +		pklist = state->ukeywords;
    466 +		kw     = state->keywords;
    467 +		while (kw->text != NULL)
    468 +		{
    469 +			uint32_t ukw[kw->size];
    470 +			u8_string_to_unicode(ukw, kw->text, kw->size - 1);
    471 +			pklist->text = calloc(kw->size, sizeof(*ukw));
    472 +			if (!pklist->text)
    473 +			{
    474 +				state->running = 0;
    475 +				return;
    476 +			}
    477 +			U32_MEMCCPY(pklist->text, ukw, kw->size, temp32);
    478 +			pklist->size = kw->size;
    479 +			kw++;
    480 +			pklist++;
    481 +		}
    482 +		pklist->text = NULL;
    483 +		pklist->size = -1;
    484 +	}
    485 +	if (!state->ucomments)
    486 +	{
    487 +		struct Comment* cm = state->comments;
    488 +		struct Comment32* pclist;
    489 +		size_t comment_count = 0;
    490 +		while (cm->start_text != NULL)
    491 +		{
    492 +			comment_count++;
    493 +			cm++;
    494 +		}
    495 +		assert(comment_count > 0);
    496 +		state->ucomments
    497 +			= calloc(comment_count + 1, sizeof(struct Comment32));
    498 +		if (!state->ucomments)
    499 +		{
    500 +			state->running = 0;
    501 +			return;
    502 +		}
    503 +		pclist = state->ucomments;
    504 +		cm     = state->comments;
    505 +		while (cm->start_text != NULL)
    506 +		{
    507 +			uint32_t ucm[MAX(cm->start_size, cm->end_size)];
    508 +			u8_string_to_unicode(ucm, cm->start_text,
    509 +				cm->start_size - 1);
    510 +			pclist->start_text
    511 +				= calloc(cm->start_size, sizeof(*ucm));
    512 +			if (!pclist->start_text)
    513 +			{
    514 +				state->running = 0;
    515 +				return;
    516 +			}
    517 +			U32_MEMCCPY(pclist->start_text, ucm, cm->start_size,
    518 +				temp32);
    519 +			pclist->start_size = cm->start_size;
    520 +			if (!cm->end_text)
    521 +			{
    522 +				pclist->end_text = NULL;
    523 +				pclist->end_size = -1;
    524 +				goto pclist_skip_copy;
    525 +			}
    526 +			u8_string_to_unicode(ucm, cm->end_text,
    527 +				cm->end_size - 1);
    528 +			pclist->end_text = calloc(cm->end_size, sizeof(*ucm));
    529 +			if (!pclist->end_text)
    530 +			{
    531 +				state->running = 0;
    532 +				return;
    533 +			}
    534 +			U32_MEMCCPY(pclist->end_text, ucm, cm->end_size, temp32);
    535 +			pclist->end_size = cm->end_size;
    536 +		pclist_skip_copy:
    537 +			cm++;
    538 +			pclist++;
    539 +		}
    540 +		pclist->start_text = NULL;
    541 +		pclist->start_size = -1;
    542 +	}
    543 +	if (!state->uoperators)
    544 +	{
    545 +		struct Operator* op = state->operators;
    546 +		struct Operator32* polist;
    547 +		size_t operator_count = 0;
    548 +		while (op->text != NULL)
    549 +		{
    550 +			operator_count++;
    551 +			op++;
    552 +		}
    553 +		assert(operator_count > 0);
    554 +		state->uoperators
    555 +			= calloc(operator_count + 1, sizeof(struct Operator32));
    556 +		if (!state->uoperators)
    557 +		{
    558 +			state->running = 0;
    559 +			return;
    560 +		}
    561 +		polist = state->uoperators;
    562 +		op     = state->operators;
    563 +		while (op->text != NULL)
    564 +		{
    565 +			uint32_t uop[op->size];
    566 +			u8_string_to_unicode(uop, op->text, op->size - 1);
    567 +			polist->text = calloc(op->size, sizeof(*uop));
    568 +			if (!polist->text)
    569 +			{
    570 +				state->running = 0;
    571 +				return;
    572 +			}
    573 +			U32_MEMCCPY(polist->text, uop, op->size, temp32);
    574 +			polist->size = op->size;
    575 +			op++;
    576 +			polist++;
    577 +		}
    578 +		polist->text = NULL;
    579 +		polist->size = -1;
    580 +	}
    581 +	li	      = 0;
    582 +	quote	      = 0;
    583 +	comment_index = -1;
    584 +recalc_syn_next_line:
    585 +	curline = state->buffer + li;
    586 +	free(curline->syntax);
    587 +	curline->syntax	     = NULL;
    588 +	curline->syntax_size = 0;
    589 +	if (li == state->rows_count)
    590 +		return;
    591 +
    592 +	ti	      = 0;
    593 +	quote_start   = 0;
    594 +	comment_start = 0;
    595 +recalc_syn_next_token:
    596 +	if ((ti >= curline->length) || (curline->text[ti] == 0L))
    597 +	{
    598 +		li++;
    599 +		goto recalc_syn_next_line;
    600 +	}
    601 +
    602 +	if (quote)
    603 +		goto recalc_syn_skip_comments;
    604 +
    605 +	/* Check for comments */
    606 +	if (comment_index >= 0)
    607 +	{
    608 +	recalc_syn_comment_rest_of_line:
    609 +		seen_end_comment = 0;
    610 +		while (ti < curline->length)
    611 +		{
    612 +			seen_end_comment = u32_starts_with(&curline->text[ti],
    613 +				state->ucomments[comment_index].end_text);
    614 +			if (seen_end_comment)
    615 +				break;
    616 +			ti++;
    617 +		}
    618 +		assert(state->comments[comment_index].end_size > 1);
    619 +		ti += state->comments[comment_index].end_size - 1;
    620 +		if (seen_end_comment)
    621 +			comment_index = -1;
    622 +		if (syntax_add(curline, SE_COMMENT, comment_start,
    623 +			    ti - comment_start)
    624 +			< 0)
    625 +		{
    626 +			state->running = 0;
    627 +			return;
    628 +		}
    629 +		ti++;
    630 +		comment_start = 0;
    631 +		if (comment_index >= 0)
    632 +		{
    633 +			li++;
    634 +			goto recalc_syn_next_line;
    635 +		}
    636 +		goto recalc_syn_next_token;
    637 +	}
    638 +
    639 +	i	= 0;
    640 +	comment = state->ucomments;
    641 +	while (comment->start_text != NULL)
    642 +	{
    643 +		if ((curline->length >= ti + comment->start_size - 1)
    644 +			&& (u32_starts_with(&curline->text[ti],
    645 +				comment->start_text)))
    646 +		{
    647 +			assert(comment->start_size > 1);
    648 +			if (comment->end_text != NULL)
    649 +				goto recalc_syn_ml_comment;
    650 +
    651 +			if (syntax_add(curline, SE_COMMENT, ti,
    652 +				    curline->length - ti)
    653 +				< 0)
    654 +			{
    655 +				state->running = 0;
    656 +				return;
    657 +			}
    658 +			li++;
    659 +			goto recalc_syn_next_line;
    660 +
    661 +		recalc_syn_ml_comment:
    662 +			comment_start = ti;
    663 +			comment_index = i;
    664 +			ti += comment->start_size;
    665 +			goto recalc_syn_comment_rest_of_line;
    666 +		}
    667 +		i++;
    668 +		comment = state->ucomments + i;
    669 +	}
    670 +
    671 +	/* Check for string literals */
    672 +recalc_syn_skip_comments:
    673 +	if (quote)
    674 +	{
    675 +		while ((ti < curline->length)
    676 +			&& (((ti == 1 && curline->text[ti - 1] == '\\')
    677 +				    || (ti > 1 && curline->text[ti - 2] != '\\'
    678 +					    && curline->text[ti - 1] == '\\'))
    679 +				|| curline->text[ti] != quote))
    680 +			ti++;
    681 +		if (curline->text[ti] == quote)
    682 +		{
    683 +			quote = 0;
    684 +			ti++;
    685 +		}
    686 +		assert(ti != quote_start);
    687 +		if (syntax_add(curline, SE_LITERAL, quote_start,
    688 +			    ti - quote_start)
    689 +			< 0)
    690 +		{
    691 +			state->running = 0;
    692 +			return;
    693 +		}
    694 +		quote_start = 0;
    695 +		goto recalc_syn_next_token;
    696 +	}
    697 +
    698 +	assert(quote == 0);
    699 +	if ((ti == 0 || (ti == 1 && curline->text[ti - 1] != '\\')
    700 +		    || (ti > 1
    701 +			    && (curline->text[ti - 2] == '\\'
    702 +				    || curline->text[ti - 1] != '\\')))
    703 +		&& ((curline->text[ti] == '"') || (curline->text[ti] == '\'')))
    704 +	{
    705 +		quote	    = curline->text[ti];
    706 +		quote_start = ti;
    707 +		ti++;
    708 +		goto recalc_syn_next_token;
    709 +	}
    710 +
    711 +	/* Check for numeric literals */
    712 +	if (isdigit(curline->text[ti])
    713 +		&& ((ti == 0) || ((ti > 0) && !isalnum(curline->text[ti - 1]))))
    714 +	{
    715 +		literal_start = ti;
    716 +		while ((ti < curline->length) && (isdigit(curline->text[ti])))
    717 +			ti++;
    718 +		assert(ti != literal_start);
    719 +		if (syntax_add(curline, SE_LITERAL, literal_start,
    720 +			    ti - literal_start)
    721 +			< 0)
    722 +		{
    723 +			state->running = 0;
    724 +			return;
    725 +		}
    726 +		goto recalc_syn_next_token;
    727 +	}
    728 +
    729 +	/* Check for keywords */
    730 +	i	= 0;
    731 +	keyword = state->ukeywords;
    732 +	while (keyword->text != NULL)
    733 +	{
    734 +		if ((curline->length >= ti + keyword->size - 1)
    735 +			&& ((ti == 0)
    736 +				|| ((ti > 0) && !isalnum(curline->text[ti - 1])
    737 +					&& curline->text[ti - 1] != L'_'))
    738 +			&& (!isalnum(curline->text[ti + keyword->size - 1]))
    739 +			&& (curline->text[ti + keyword->size - 1] != L'_')
    740 +			&& (u32_starts_with(&curline->text[ti], keyword->text)))
    741 +		{
    742 +			/* Add new syntax region */
    743 +			assert(keyword->size > 1);
    744 +			if (syntax_add(curline, SE_KEYWORD, ti,
    745 +				    keyword->size - 1)
    746 +				< 0)
    747 +			{
    748 +				state->running = 0;
    749 +				return;
    750 +			}
    751 +			ti += keyword->size - 1;
    752 +			goto recalc_syn_next_token;
    753 +		}
    754 +		i++;
    755 +		keyword = state->ukeywords + i;
    756 +	}
    757 +
    758 +	/* Check for operators */
    759 +	i = 0;
    760 +	/* clang-format off */
    761 +	operator = state->uoperators;
    762 +	/* clang-format on */
    763 +	while (operator->text != NULL)
    764 +	{
    765 +		if ((curline->length >= ti + operator->size - 1)
    766 +			&& (u32_starts_with(&curline->text[ti], operator->text)))
    767 +		{
    768 +			/* Add new syntax region */
    769 +			assert(operator->size> 1);
    770 +			if (syntax_add(curline, SE_OPERATOR,
    771 +				    ti, operator->size - 1)
    772 +				< 0)
    773 +			{
    774 +				state->running = 0;
    775 +				return;
    776 +			}
    777 +			ti += operator->size - 1;
    778 +			goto recalc_syn_next_token;
    779 +		}
    780 +		i++;
    781 +		/* clang-format off */
    782 +		operator = state->uoperators + i;
    783 +		/* clang-format on */
    784 +	}
    785 +
    786 +	ti++;
    787 +	goto recalc_syn_next_token;
    788 +}
    789 +
    790 +void
    791  redraw_status(struct DrawState* state)
    792  {
    793  	assert(state != NULL);
    794 @@ -790,7 +1354,8 @@ redraw_status(struct DrawState* state)
    795   * */
    796  void
    797  u32_draw_string(const int x, const int y, const uint16_t fg, const uint16_t bg,
    798 -	const uint32_t* s, const int mc, const int fc, const int p_l,
    799 +	const uint32_t* s, const struct SyntaxRegion* syntax,
    800 +	const ssize_t syntax_length, const int mc, const int fc, const int p_l,
    801  	const int p_r, const Alignment align, const int ds, const int fill,
    802  	const uint32_t* srch, const uint16_t sfg, const uint16_t sbg,
    803  	const int ssi, const int sei, const uint16_t slfg, const uint16_t slbg)
    804 @@ -828,6 +1393,8 @@ u32_draw_string(const int x, const int y, const uint16
    805  
    806  	int w = 0; /* actual on-screen rune width */
    807  
    808 +	int sel_on = 0;
    809 +
    810  	const int ts = TAB_SIZE;
    811  
    812  	const uint32_t* ps = s; /* pointer to current character in
    813 @@ -836,11 +1403,23 @@ u32_draw_string(const int x, const int y, const uint16
    814  	uint16_t cfg = fg; /* current fg */
    815  	uint16_t cbg = bg; /* current bg */
    816  
    817 +	struct Color defcolor, selection, highlight;
    818 +
    819  	int hctl = 0; /* highlight "characters to live", when this
    820  		       * reaches zero the highlighting is turned
    821  		       * off */
    822  
    823  	assert(s != NULL);
    824 +
    825 +	defcolor.fg = fg;
    826 +	defcolor.bg = bg;
    827 +
    828 +	selection.fg = slfg;
    829 +	selection.bg = slbg;
    830 +
    831 +	highlight.fg = sfg;
    832 +	highlight.bg = sbg;
    833 +
    834  	c = mc - p_l - p_r;
    835  
    836  	if (align == LEFT)
    837 @@ -872,7 +1451,12 @@ u32_draw_string(const int x, const int y, const uint16
    838  	 */
    839  	while (*ps && sc < c)
    840  	{
    841 -		if ((hctl == 0) && ((ps - s) >= ssi) && ((ps - s) < sei))
    842 +		cfg = fg;
    843 +		cbg = bg;
    844 +
    845 +		sel_on = ((ps - s) >= ssi) && ((ps - s) < sei);
    846 +
    847 +		if ((hctl == 0) && sel_on)
    848  		{
    849  			cfg = slfg;
    850  			cbg = slbg;
    851 @@ -889,6 +1473,9 @@ u32_draw_string(const int x, const int y, const uint16
    852  			}
    853  		}
    854  
    855 +		synsetcolor(&cfg, &cbg, &defcolor, &selection, sel_on,
    856 +			&highlight, hctl > 0, syntax, syntax_length, ps - s);
    857 +
    858  		if (*ps == '\t')
    859  		{
    860  			if (ssc < ds)
    861 @@ -1004,11 +1591,11 @@ u32_draw_string(const int x, const int y, const uint16
    862  			}
    863  			ssc += w;
    864  		}
    865 +		sel_on = ((ps - s) >= ssi) && ((ps - s) < sei);
    866 +
    867  		if (hctl == 0)
    868 -		{
    869 -			cfg = fg;
    870 -			cbg = bg;
    871 -		}
    872 +			synsetcolor(&cfg, &cbg, &defcolor, &selection, sel_on,
    873 +				&highlight, 0, syntax, syntax_length, ps - s);
    874  		ps++;
    875  	}
    876  
    877 diff --git a/draw.h b/draw.h
    878 index 68cc281d8..e0b72ba7c 100644
    879 --- a/draw.h
    880 +++ b/draw.h
    881 @@ -63,17 +63,69 @@ struct Color {
    882  	uint16_t bg;
    883  };
    884  
    885 +typedef enum { FT_GENERIC, FT_C, FT_SHELL } FileType;
    886 +
    887 +typedef enum {
    888 +	SE_DEFAULT,
    889 +	SE_COMMENT,
    890 +	SE_KEYWORD,
    891 +	SE_OPERATOR,
    892 +	SE_LITERAL
    893 +} SyntaxElement;
    894 +
    895 +struct Keyword {
    896 +	char* text;
    897 +	int size;
    898 +};
    899 +
    900 +struct Keyword32 {
    901 +	uint32_t* text;
    902 +	int size;
    903 +};
    904 +
    905 +struct Comment {
    906 +	char* start_text;
    907 +	char* end_text;
    908 +	int start_size;
    909 +	int end_size;
    910 +};
    911 +
    912 +struct Comment32 {
    913 +	uint32_t* start_text;
    914 +	uint32_t* end_text;
    915 +	int start_size;
    916 +	int end_size;
    917 +};
    918 +
    919 +struct Operator {
    920 +	char* text;
    921 +	int size;
    922 +};
    923 +
    924 +struct Operator32 {
    925 +	uint32_t* text;
    926 +	int size;
    927 +};
    928 +
    929  struct Coord {
    930  	int col;
    931  	int row;
    932  };
    933  
    934 +struct SyntaxRegion {
    935 +	ssize_t start;
    936 +	ssize_t length;
    937 +	struct Color color;
    938 +};
    939 +
    940  struct BufferLine {
    941  	uint32_t* text;
    942  	ssize_t length; /* both in uint32_t units */
    943  	ssize_t size;
    944  	uint16_t fg;
    945  	uint16_t bg;
    946 +	struct SyntaxRegion* syntax;
    947 +	ssize_t syntax_size;
    948  };
    949  
    950  struct DrawState {
    951 @@ -113,12 +165,20 @@ struct DrawState {
    952  	int dirty;
    953  	int new_file;
    954  	int recalculate_size;
    955 +	int syntax_enabled;
    956 +	int recalculate_syntax;
    957  	int mode;
    958  	int maxx;
    959  	int maxy;
    960  	struct Coord sel_start; /* buffer coordinate */
    961  	struct Coord sel_end;	/* buffer coordinate */
    962  	int backup_possible;	/* is saving backup possible? */
    963 +	struct Keyword* keywords;
    964 +	struct Comment* comments;
    965 +	struct Operator* operators;
    966 +	struct Keyword32* ukeywords; /* Converted to 32-bit Unicode */
    967 +	struct Comment32* ucomments;
    968 +	struct Operator32* uoperators;
    969  };
    970  
    971  struct Key {
    972 @@ -134,6 +194,13 @@ struct StatusSegment {
    973  	Alignment alignment;
    974  };
    975  
    976 +extern const struct Keyword c_keywords[];
    977 +extern const struct Comment c_comments[];
    978 +extern const struct Operator c_operators[];
    979 +extern const struct Keyword shell_keywords[];
    980 +extern const struct Comment shell_comments[];
    981 +extern const struct Operator shell_operators[];
    982 +
    983  void assign_coord(struct Coord* dest, const struct Coord src);
    984  ssize_t buffer_length(uint32_t* buffer, const ssize_t max_index);
    985  int calculate_help_width(const struct DrawState* state);
    986 @@ -150,7 +217,8 @@ void draw_rect(const int startx, const int endx, const
    987  int draw_screen(const struct DrawState* state);
    988  int draw_status(const struct DrawState* state);
    989  int draw_string(const int x, const int y, const uint16_t fg, const uint16_t bg,
    990 -	const char* s, const int max_cols, const int fill_cols,
    991 +	const char* s, const struct SyntaxRegion* syntax,
    992 +	const ssize_t syntax_length, const int max_cols, const int fill_cols,
    993  	const int padding_left, const int padding_right, const Alignment align,
    994  	const int ds, const int fill, const uint32_t* srch, const uint16_t sfg,
    995  	const uint16_t sbg, const int ssi, const int sei, const uint16_t slfg,
    996 @@ -171,10 +239,12 @@ void init_drawstate(struct DrawState* state, char* fil
    997  	const int skip_paste_and_mode_init, const int skip_filenames_init);
    998  void recalculate_line_numbers(struct DrawState* state);
    999  void recalculate_size(struct DrawState* state);
   1000 +void recalculate_syntax(struct DrawState* state);
   1001  void redraw_status(struct DrawState* state);
   1002  void u32_draw_string(const int x, const int y, const uint16_t fg,
   1003 -	const uint16_t bg, const uint32_t* s, const int max_cols,
   1004 -	const int fill_cols, const int padding_left, const int padding_right,
   1005 -	const Alignment align, const int ds, const int fill,
   1006 -	const uint32_t* srch, const uint16_t sfg, const uint16_t sbg,
   1007 -	const int ssi, const int sei, const uint16_t slfg, const uint16_t slbg);
   1008 +	const uint16_t bg, const uint32_t* s, const struct SyntaxRegion* syntax,
   1009 +	const ssize_t syntax_length, const int max_cols, const int fill_cols,
   1010 +	const int padding_left, const int padding_right, const Alignment align,
   1011 +	const int ds, const int fill, const uint32_t* srch, const uint16_t sfg,
   1012 +	const uint16_t sbg, const int ssi, const int sei, const uint16_t slfg,
   1013 +	const uint16_t slbg);
   1014 diff --git a/sled.c b/sled.c
   1015 index 68cc281d8..e0b72ba7c 100644
   1016 --- a/sled.c
   1017 +++ b/sled.c
   1018 @@ -50,6 +50,18 @@ extern const char* prompt_reread_dirty;
   1019  extern const char* replace_dialog_title;
   1020  extern const char* search_dialog_title;
   1021  
   1022 +struct SyntaxEnding {
   1023 +	char* text;
   1024 +	FileType type;
   1025 +};
   1026 +/* clang-format off */
   1027 +static const struct SyntaxEnding syntax_endings[] = {
   1028 +	{ .text = ".c",		.type = FT_C		},
   1029 +	{ .text = ".h",		.type = FT_C		},
   1030 +	{ .text = ".sh",	.type = FT_SHELL	}
   1031 +};
   1032 +/* clang-format on */
   1033 +
   1034  static int
   1035  cancel_close(struct DrawState* state)
   1036  {
   1037 @@ -178,8 +190,9 @@ create_file(struct DrawState* state)
   1038  	state->buffer_size = ALLOC_DELTA;
   1039  	state->rows_count  = 1;
   1040  	recalculate_line_numbers(state);
   1041 -	state->coord.row = 0;
   1042 -	state->dirty	 = 1;
   1043 +	state->coord.row	  = 0;
   1044 +	state->dirty		  = 1;
   1045 +	state->recalculate_syntax = 1;
   1046  
   1047  	return 0;
   1048  }
   1049 @@ -310,6 +323,7 @@ do_search_replace(struct DrawState* state)
   1050  		state->buffer[row].length = buflen;
   1051  	}
   1052  	free(buf);
   1053 +	state->recalculate_syntax = 1;
   1054  	return 0;
   1055  
   1056  do_search_replace_alloc_error:
   1057 @@ -360,7 +374,8 @@ erase_backwards(struct DrawState* state)
   1058  		buffer_line->text[i - 1] = 0;
   1059  		buffer_line->length--;
   1060  		position_to(state, state->coord.row, state->coord.col - 1);
   1061 -		state->dirty = 1;
   1062 +		state->dirty		  = 1;
   1063 +		state->recalculate_syntax = 1;
   1064  	}
   1065  	else if (state->coord.row > 0)
   1066  	{
   1067 @@ -397,7 +412,8 @@ erase_forward(struct DrawState* state)
   1068  		}
   1069  		buffer_line->text[i] = 0;
   1070  		buffer_line->length--;
   1071 -		state->dirty = 1;
   1072 +		state->dirty		  = 1;
   1073 +		state->recalculate_syntax = 1;
   1074  	}
   1075  	else if ((state->coord.row + 1) < state->rows_count)
   1076  		simple_join_lines(state);
   1077 @@ -431,7 +447,8 @@ erase_line(struct DrawState* state)
   1078  		state->coord.row--;
   1079  	position_to(state, state->coord.row, 0);
   1080  	state->rows_count--;
   1081 -	state->dirty = 1;
   1082 +	state->dirty		  = 1;
   1083 +	state->recalculate_syntax = 1;
   1084  	recalculate_line_numbers(state);
   1085  	return 0;
   1086  
   1087 @@ -474,6 +491,7 @@ erase_prev_word(struct DrawState* state)
   1088  	if (state->display_column < state->first_shown.col)
   1089  		state->first_shown.col = state->display_column;
   1090  	state->dirty = 1;
   1091 +	state->recalculate_syntax = 1;
   1092  	return 0;
   1093  }
   1094  
   1095 @@ -529,7 +547,8 @@ erase_selection(struct DrawState* state)
   1096  			recalculate_line_numbers(state);
   1097  		}
   1098  		free(buf);
   1099 -		state->dirty = 1;
   1100 +		state->dirty		  = 1;
   1101 +		state->recalculate_syntax = 1;
   1102  	}
   1103  	return 0;
   1104  }
   1105 @@ -554,7 +573,8 @@ erase_to_end(struct DrawState* state)
   1106  	buffer_line->text[state->coord.col] = 0;
   1107  	buffer_line->length		    = state->coord.col;
   1108  	move_end(state);
   1109 -	state->dirty = 1;
   1110 +	state->dirty		  = 1;
   1111 +	state->recalculate_syntax = 1;
   1112  	return 0;
   1113  }
   1114  
   1115 @@ -588,7 +608,8 @@ erase_to_start(struct DrawState* state)
   1116  	buffer_line->text[len - state->coord.col] = 0;
   1117  	buffer_line->length			  = len - state->coord.col;
   1118  	move_start(state);
   1119 -	state->dirty = 1;
   1120 +	state->dirty		  = 1;
   1121 +	state->recalculate_syntax = 1;
   1122  	return 0;
   1123  }
   1124  
   1125 @@ -1148,8 +1169,8 @@ insert_char(struct DrawState* state, uint32_t ch)
   1126  	}
   1127  
   1128  insert_skip_wrap:
   1129 -	state->dirty = 1;
   1130 -
   1131 +	state->dirty		  = 1;
   1132 +	state->recalculate_syntax = 1;
   1133  	return 0;
   1134  
   1135  insert_char_error:
   1136 @@ -1200,7 +1221,8 @@ insert_skip_wrap:
   1137  	simple_insert_line(state);
   1138  	state->saved_column = -1;
   1139  	position_to(state, state->coord.row + 1, 0);
   1140 -	state->dirty = 1;
   1141 +	state->dirty		  = 1;
   1142 +	state->recalculate_syntax = 1;
   1143  	return 0;
   1144  }
   1145  
   1146 @@ -1735,7 +1757,8 @@ paste_from_paste(struct DrawState* state)
   1147  	}
   1148  
   1149  	position_to(state, newpos.row, newpos.col);
   1150 -	state->dirty = 1;
   1151 +	state->dirty		  = 1;
   1152 +	state->recalculate_syntax = 1;
   1153  
   1154  paste_from_paste_cleanup:
   1155  	free(rest);
   1156 @@ -2001,6 +2024,7 @@ reread_file(struct DrawState* state)
   1157  	}
   1158  
   1159  	recalculate_line_numbers(state);
   1160 +	state->recalculate_syntax = 1;
   1161  	return 0;
   1162  }
   1163  
   1164 @@ -2398,6 +2422,7 @@ simple_join_lines(struct DrawState* state)
   1165  	state->rows_count--;
   1166  	state->dirty = 1;
   1167  	recalculate_line_numbers(state);
   1168 +	state->recalculate_syntax = 1;
   1169  	return;
   1170  
   1171  simple_join_lines_error:
   1172 @@ -2875,6 +2900,8 @@ main(int argc, char** argv)
   1173  	int saved_errno;
   1174  	int tbires;
   1175  	int optind = 1;
   1176 +	int found_ending = 0;
   1177 +	int i;
   1178  
   1179  	*prompt = 0;
   1180  
   1181 @@ -2981,6 +3008,40 @@ main_options_loop:
   1182  
   1183  	init_drawstate(&state, filename, prompt, 0, 0);
   1184  
   1185 +	for (i = 0; (size_t)i < LEN(syntax_endings); i++)
   1186 +	{
   1187 +		int filename_len = strlen(state.filename);
   1188 +		int ending_len	 = strlen(syntax_endings[i].text);
   1189 +
   1190 +		found_ending = 0;
   1191 +		if ((filename_len >= ending_len)
   1192 +			&& !strcmp(syntax_endings[i].text,
   1193 +				state.filename + filename_len - ending_len))
   1194 +		{
   1195 +			found_ending = 1;
   1196 +			break;
   1197 +		}
   1198 +	}
   1199 +	if (found_ending)
   1200 +	{
   1201 +		state.syntax_enabled = 1;
   1202 +		switch (syntax_endings[i].type)
   1203 +		{
   1204 +		case FT_C:
   1205 +			state.keywords	= (struct Keyword*)c_keywords;
   1206 +			state.comments	= (struct Comment*)c_comments;
   1207 +			state.operators = (struct Operator*)c_operators;
   1208 +			break;
   1209 +		case FT_SHELL:
   1210 +			state.keywords	= (struct Keyword*)shell_keywords;
   1211 +			state.comments	= (struct Comment*)shell_comments;
   1212 +			state.operators = (struct Operator*)shell_operators;
   1213 +			break;
   1214 +		default:
   1215 +			/* Shouldn't happen */
   1216 +			assert(0);
   1217 +		}
   1218 +	}
   1219  #ifdef __OpenBSD__
   1220  	if (unveil(state.filename, "rwc") < 0)
   1221  	{
   1222 @@ -3200,9 +3261,10 @@ init_termbox:
   1223  		goto main_done;
   1224  	}
   1225  
   1226 -	errno		       = 0;
   1227 -	state.maxx	       = 0;
   1228 -	state.recalculate_size = 1;
   1229 +	errno			 = 0;
   1230 +	state.maxx		 = 0;
   1231 +	state.recalculate_size	 = 1;
   1232 +	state.recalculate_syntax = 1;
   1233  
   1234  	while (state.running)
   1235  	{
   1236 @@ -3214,6 +3276,11 @@ init_termbox:
   1237  			recalculate_size(&state);
   1238  			state.recalculate_size = 0;
   1239  		}
   1240 +		if (state.syntax_enabled && state.recalculate_syntax)
   1241 +		{
   1242 +			recalculate_syntax(&state);
   1243 +			state.recalculate_syntax = 0;
   1244 +		}
   1245  		if (draw(&state))
   1246  		{
   1247  			state.running = 0;
   1248 --
   1249 2.44.1
   1250