sled

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

draw.c (26342B)


      1 /* This program is licensed under the terms of GNU GPL v3 or (at your option)
      2  * any later version. Copyright (C) 2022-2026  Страхиња Радић.
      3  * See the file LICENSE for exact copyright and license details. */
      4 
      5 #include <assert.h>
      6 #include <errno.h>
      7 #include <stdio.h>
      8 #include <stdlib.h>
      9 #include <string.h>
     10 
     11 #include "draw.h"
     12 #include "config.h"
     13 #include "limit.h"
     14 #include "util.h"
     15 #include "version.h"
     16 
     17 enum {
     18 	UNKNOWN_MODE,
     19 	COMMAND_MODE,
     20 	HELP_MODE,
     21 	INSERT_MODE,
     22 	OVERWRITE_MODE,
     23 	PROMPT_MODE,
     24 	INPUT_MDOE,
     25 	INPUT_NUM_MODE,
     26 	SELECTION_MODE
     27 };
     28 
     29 /* clang-format off */
     30 static const char* help_title = "sled help";
     31 static const char* help[]     = {
     32 	"Esc            - Close dialog, clear error, cancel",
     33 	"                 selection",
     34 	"F1             - Show this screen",
     35 	"C-\\            - Toggle command mode",
     36 	"Home or C-A    - Go to start of line",
     37 	"End or C-E     - Go to end of line",
     38 	"Insert         - Toggle overwrite mode",
     39 	"C-L            - Show Go to line dialog",
     40 	"C-O            - Reread (reopen) file",
     41 	"C-Q            - Quit",
     42 	"C-R            - Go to previous word",
     43 	"C-S            - Write (save) file",
     44 	"C-Space        - Toggle selection mode",
     45 	"C-T            - Go to next word",
     46 	"",
     47 	"Arrow keys work in insert, selection and command modes.",
     48 	"Some vi-like keys work in command and selection modes.",
     49 	"",
     50 	"See the manual (man sled) for full help."};
     51 static const char* mode_labels[] = {
     52 	[UNKNOWN_MODE]	 = "UNKNOWN",
     53 	[COMMAND_MODE]	 = "COMMAND",
     54 	[HELP_MODE]	 = "HELP",
     55 	[INSERT_MODE]	 = "INSERT",
     56 	[OVERWRITE_MODE] = "OVERWRITE",
     57 	[PROMPT_MODE]	 = "PROMPT",
     58 	[INPUT_MDOE]	 = "INPUT",
     59 	[INPUT_NUM_MODE] = "NUM INPUT",
     60 	[SELECTION_MODE] = "SELECTION"
     61 };
     62 const char* errors[]           = {
     63 	[ERR_NO_CL_ARG]	       = "No filename given",
     64 	[ERR_BINARY]	       = "Not opening a binary file",
     65 	[ERR_NOTREG_FILE]      = "'%s' is not a regular file",
     66 	[ERR_TB_ERROR]	       = "termbox error",
     67 	[ERR_UNKNOWN_KEY]      = "Unknown key (press F1 for help)",
     68 	[ERR_EXIT_KEY]	       = "Press C-Q to quit",
     69 	[ERR_DLG_OPEN]	       = "Press Esc to close dialog or C-Q to quit",
     70 	[ERR_DLG_OPEN_QUIT]    = "Press Esc to close dialog",
     71 	[ERR_INPUT_NONNUM]     = "Input must be numerical",
     72 	[ERR_INVALID_NUM]      = "Invalid input",
     73 	[ERR_NOT_IMP]	       = "Function not implemented/configured",
     74 	[ERR_ERRNO_SET]	       = "System call/library function error"};
     75 const char* actions[]  = {
     76 	[ACT_WRITING]  = "Writing...",
     77 	[ACT_READING]  = "Reading...",
     78 	[ACT_WRAPPING] = "Wrapping..."
     79 };
     80 static const struct {
     81 	ssize_t boundary;
     82 	int width;
     83 } number_widths[] = {
     84 	{ .boundary = 9999999, .width = 8 },
     85 	{ .boundary =  999999, .width = 7 },
     86 	{ .boundary =   99999, .width = 6 },
     87 	{ .boundary =    9999, .width = 5 },
     88 	{ .boundary =     999, .width = 4 },
     89 	{ .boundary =      99, .width = 3 },
     90 	{ .boundary =       9, .width = 2 }
     91 };
     92 const char* go_to_dialog_title   = "Go to line";
     93 const char* prompt_dirty	 = "File changed, save [y/n/ESC]?";
     94 const char* prompt_overwrite     = "Not empty, overwrite? [y/n/ESC]?";
     95 const char* prompt_reread_dirty	 = "File changed, lose changes [y/n/ESC]?";
     96 const char* replace_dialog_title = "Replace with";
     97 const char* search_dialog_title  = "Search text";
     98 /* clang-format on */
     99 
    100 void get_selection(int* ssr, int* ssc, int* ser, int* sec,
    101 	const struct DrawState* state);
    102 
    103 void
    104 assign_coord(struct Coord* dest, const struct Coord src)
    105 {
    106 	init_coord(dest, src.col, src.row);
    107 }
    108 
    109 /* Calculates buffer length from a display length.
    110  * max_index = the display index up to which the length is calculated
    111  */
    112 ssize_t
    113 buffer_length(uint32_t* buffer, const ssize_t max_index)
    114 {
    115 	uint32_t* pbuf = buffer;
    116 	ssize_t len    = 0;
    117 	ssize_t i;
    118 	int w;
    119 
    120 	if (!pbuf)
    121 		return 0;
    122 
    123 	for (i = 0; *pbuf && len < max_index; i++)
    124 	{
    125 		pbuf = buffer + i;
    126 		if (*pbuf == '\t')
    127 			len += MIN(TAB_SIZE - (len % TAB_SIZE), max_index - len);
    128 		else
    129 		{
    130 			w = wcwidth((wchar_t)*pbuf);
    131 			if (w > 0)
    132 				len += MIN(w, max_index - len);
    133 		}
    134 	}
    135 	return i;
    136 }
    137 
    138 int
    139 calculate_help_width(const struct DrawState* state)
    140 {
    141 	assert(state != NULL);
    142 	return state->maxx > HELP_WIDTH + 2 ? HELP_WIDTH : state->maxx - 2;
    143 }
    144 
    145 int
    146 calculate_input_dialog_width(const struct DrawState* state)
    147 {
    148 	assert(state != NULL);
    149 	return state->maxx > INPUT_DLG_WIDTH + 2 ? INPUT_DLG_WIDTH
    150 						 : state->maxx - 2;
    151 }
    152 
    153 int
    154 calculate_line_numbers_width(const struct DrawState* state)
    155 {
    156 	ssize_t last_row;
    157 	int i;
    158 	assert(state != NULL);
    159 	last_row = MIN(state->rows_count,
    160 		state->first_shown.row + state->maxy - 1);
    161 	i	 = 0;
    162 	while ((size_t)i < LEN(number_widths))
    163 	{
    164 		if (last_row > number_widths[i].boundary)
    165 			return number_widths[i].width;
    166 		i++;
    167 	}
    168 	return 1;
    169 }
    170 
    171 /* Calculates display length of a buffer line.
    172  * max_index = the buffer index up to which the length is calculated
    173  */
    174 ssize_t
    175 display_length(uint32_t* buffer, const ssize_t max_index)
    176 {
    177 	uint32_t* pbuf = buffer;
    178 	ssize_t len    = 0;
    179 	int w;
    180 
    181 	assert(buffer != NULL);
    182 	for (ssize_t i = 0; *pbuf && i < max_index; i++)
    183 	{
    184 		pbuf = &buffer[i];
    185 		if (*pbuf == '\t')
    186 			len += TAB_SIZE - (len % TAB_SIZE);
    187 		else if (!*pbuf)
    188 			len++;
    189 		else
    190 		{
    191 			w = wcwidth((wchar_t)*pbuf);
    192 			if (w > 0)
    193 				len += w;
    194 		}
    195 	}
    196 
    197 	return len;
    198 }
    199 
    200 int
    201 draw(const struct DrawState* state)
    202 {
    203 	assert(state != NULL);
    204 	if (draw_screen(state))
    205 		return 1;
    206 	if (IN(state->mode, M_HELP))
    207 		if (draw_help(state))
    208 			return 1;
    209 	if (IN(state->mode, M_INPUT))
    210 		if (draw_input_dialog(state))
    211 			return 1;
    212 	if (draw_status(state))
    213 		return 1;
    214 	return 0;
    215 }
    216 
    217 void
    218 draw_box(const int startx, const int endx, const int starty, const int endy,
    219 	const uint16_t fg, const uint16_t bg)
    220 {
    221 	int x, y;
    222 
    223 	tb_set_cell(startx, starty, BORDER_NW, fg, bg);
    224 	for (x = startx + 1; x < endx - 1; x++)
    225 		tb_set_cell(x, starty, BORDER_HOR, fg, bg);
    226 	tb_set_cell(endx - 1, starty, BORDER_NE, fg, bg);
    227 
    228 	for (y = starty + 1; y < endy - 2; y++)
    229 	{
    230 		tb_set_cell(startx, y, BORDER_VER, fg, bg);
    231 		for (x = startx + 1; x < endx - 1; x++)
    232 			tb_set_cell(x, y, ' ', fg, bg);
    233 		tb_set_cell(endx - 1, y, BORDER_VER, fg, bg);
    234 		tb_set_cell(endx, y, ' ', colors[C_SHADOW].fg,
    235 			colors[C_SHADOW].bg);
    236 	}
    237 
    238 	tb_set_cell(startx, endy - 2, BORDER_SW, fg, bg);
    239 	for (x = startx + 1; x < endx - 1; x++)
    240 		tb_set_cell(x, endy - 2, BORDER_HOR, fg, bg);
    241 	tb_set_cell(endx - 1, endy - 2, BORDER_SE, fg, bg);
    242 	tb_set_cell(endx, endy - 2, ' ', colors[C_SHADOW].fg,
    243 		colors[C_SHADOW].bg);
    244 
    245 	for (x = startx + 1; x < endx + 1; x++)
    246 		tb_set_cell(x, endy - 1, ' ', colors[C_SHADOW].fg,
    247 			colors[C_SHADOW].bg);
    248 }
    249 
    250 int
    251 draw_help(const struct DrawState* state)
    252 {
    253 	int startx, endx, starty, endy;
    254 	int w, h;
    255 	int row = 0;
    256 	int maxx;
    257 	int maxy;
    258 
    259 	assert(state != NULL);
    260 	w    = state->help_width;
    261 	h    = HELP_HEIGHT;
    262 	maxx = state->maxx;
    263 	maxy = state->maxy;
    264 
    265 	startx = w > maxx ? 0 : (maxx - w) / 2;
    266 	endx   = w > maxx ? maxx - 1 : startx + w;
    267 
    268 	starty = h > maxy - 1 ? 0 : (maxy - h) / 2;
    269 	endy   = h > maxy - 1 ? maxy - 2 : starty + h;
    270 
    271 	draw_box(startx, endx, starty, endy, colors[C_DLG].fg, colors[C_DLG].bg);
    272 	if (draw_string(startx, starty, colors[C_TITLE].fg, colors[C_TITLE].bg,
    273 		    help_title, strlen(help_title) + 2, w, 1, 1, CENTER, 0, 0,
    274 		    NULL, 0, 0, 0, 0, 0, 0))
    275 		return 1;
    276 	while ((size_t)row < LEN(help))
    277 	{
    278 		if (draw_string(startx + 1, starty + 1 + row, colors[C_DLG].fg,
    279 			    colors[C_DLG].bg, help[row], w - 2, w - 2, 1, 1,
    280 			    LEFT, 0, 1, NULL, 0, 0, 0, 0, 0, 0))
    281 			return 1;
    282 		row++;
    283 	}
    284 	tb_hide_cursor();
    285 	return 0;
    286 }
    287 
    288 int
    289 draw_input_dialog(const struct DrawState* state)
    290 {
    291 	int startx, endx, starty, endy;
    292 	int w, h;
    293 	int maxx, maxy;
    294 	int len;
    295 	int dlen = 0;
    296 
    297 	assert(state != NULL);
    298 	w    = state->input_dialog_width;
    299 	h    = INPUT_DLG_HEIGHT;
    300 	maxx = state->maxx;
    301 	maxy = state->maxy;
    302 	len  = u32_strlen(state->input);
    303 
    304 	startx = w > maxx ? 0 : (maxx - w) / 2;
    305 	endx   = w > maxx ? maxx - 1 : startx + w;
    306 
    307 	starty = maxy - 1 - h;
    308 	endy   = maxy - 1;
    309 
    310 	draw_box(startx, endx, starty, endy, colors[C_INPUT].fg,
    311 		colors[C_INPUT].bg);
    312 	draw_rect(startx + 1, endx - 1, starty + 1, endy - 2,
    313 		colors[C_INPUT_EDIT].fg, colors[C_INPUT_EDIT].bg);
    314 	if (state->input_title)
    315 		if (draw_string(startx, starty, colors[C_TITLE].fg,
    316 			    colors[C_TITLE].bg, state->input_title,
    317 			    strlen(state->input_title) + 2, w, 1, 1, CENTER, 0,
    318 			    0, NULL, 0, 0, 0, 0, 0, 0))
    319 			return 1;
    320 	dlen = display_length(state->input, len);
    321 	if (dlen > state->input_first_shown_column)
    322 		u32_draw_string(startx + 1, starty + 1, colors[C_INPUT_EDIT].fg,
    323 			colors[C_INPUT_EDIT].bg, state->input, w - 2, w - 2, 1,
    324 			1, LEFT, state->input_first_shown_column, 1, NULL, 0, 0,
    325 			0, 0, 0, 0);
    326 	tb_set_cursor(startx + 2 + state->input_display_column
    327 			- state->input_first_shown_column,
    328 		starty + 1);
    329 	return 0;
    330 }
    331 
    332 void
    333 draw_line_number(const struct DrawState* state, const int i, const int cy)
    334 {
    335 	char numbuf[LINE_DEFAULT];
    336 	char* numbuf_end = NULL;
    337 	char* pnumbuf	 = NULL;
    338 	assert(state != NULL);
    339 	snprintf(numbuf, LINE_DEFAULT, "%d", i + 1);
    340 	numbuf_end = strchr(numbuf, 0);
    341 	if (numbuf_end)
    342 	{
    343 		pnumbuf = numbuf_end - numbuf > state->line_numbers_width
    344 			? numbuf_end - state->line_numbers_width
    345 			: numbuf;
    346 		draw_string(0, cy, colors[C_LINENO].fg, colors[C_LINENO].bg,
    347 			pnumbuf, numbuf_end - numbuf + 1,
    348 			state->line_numbers_width, 0, 0, RIGHT, 0, 1, NULL, 0,
    349 			0, 0, 0, 0, 0);
    350 	}
    351 }
    352 
    353 void
    354 draw_rect(const int startx, const int endx, const int starty, const int endy,
    355 	const uint16_t fg, const uint16_t bg)
    356 {
    357 	assert((starty < endy) && (startx < endx));
    358 	for (int y = starty; y < endy; y++)
    359 		for (int x = startx; x < endx; x++)
    360 			tb_set_cell(x, y, ' ', fg, bg);
    361 }
    362 
    363 int
    364 draw_screen(const struct DrawState* state)
    365 {
    366 	const struct BufferLine* current = NULL;
    367 	int max_screen_lines;
    368 	int cy = 0;
    369 	int i;
    370 	/* Selection start/end index - passed to u32_draw_string */
    371 	int ssi = 0;
    372 	int sei = 0;
    373 	/* Selection start row/col */
    374 	int ssr = 0;
    375 	int ssc = 0;
    376 	/* Selection end row/col */
    377 	int ser = 0;
    378 	int sec = 0;
    379 	int maxx;
    380 	int fsc; /* First Shown Col */
    381 
    382 	assert(state != NULL);
    383 	maxx = state->maxx;
    384 	fsc  = state->first_shown.col;
    385 	if (state->line_numbers)
    386 		maxx -= state->line_numbers_width + 2;
    387 
    388 	if (state->rows_count == 0)
    389 		return 0;
    390 
    391 	max_screen_lines = state->maxy - 1;
    392 	i		 = state->first_shown.row;
    393 
    394 	while (((ssize_t)i < state->rows_count) && (cy < max_screen_lines))
    395 	{
    396 		current = state->buffer + i;
    397 		assert(current != NULL);
    398 
    399 		ssi = 0;
    400 		sei = 0;
    401 
    402 		get_selection(&ssr, &ssc, &ser, &sec, state);
    403 		if ((i > ssr) && (i < ser))
    404 		{
    405 			ssi = 0;
    406 			sei = current->length;
    407 		}
    408 		else
    409 		{
    410 			if (i == ssr)
    411 			{
    412 				ssi = ssc;
    413 				if (i != ser)
    414 					sei = current->length;
    415 			}
    416 			if (i == ser)
    417 			{
    418 				sei = sec;
    419 			}
    420 		}
    421 		if (state->line_numbers)
    422 			draw_line_number(state, i, cy);
    423 		if (current->text)
    424 			u32_draw_string(state->line_numbers
    425 					? state->line_numbers_width + 2
    426 					: 0,
    427 				cy, colors[C_TEXT].fg, colors[C_TEXT].bg,
    428 				current->text, maxx, maxx, 0, 0, LEFT, fsc, 0,
    429 				state->search, colors[C_SEARCH].fg,
    430 				colors[C_SEARCH].bg, ssi, sei, colors[C_SEL].fg,
    431 				colors[C_SEL].bg);
    432 		i++;
    433 		cy++;
    434 	}
    435 
    436 	tb_set_cursor(state->line_numbers ? state->line_numbers_width + 2
    437 				+ state->display_column - fsc
    438 					  : state->display_column - fsc,
    439 		state->coord.row - state->first_shown.row);
    440 
    441 	return 0;
    442 }
    443 
    444 int
    445 draw_status(const struct DrawState* state)
    446 {
    447 	const struct StatusSegment* pseg;
    448 	char* buf = NULL;
    449 	ssize_t seg_size;
    450 	int current_start = 0;
    451 	int maxx;
    452 
    453 	assert(state != NULL);
    454 	maxx	 = state->maxx;
    455 	seg_size = maxx / LEN(status_segments);
    456 	pseg	 = status_segments;
    457 
    458 	if (IN(state->mode, M_PROMPT) && *state->prompt)
    459 	{
    460 		if (draw_string(0, state->maxy - 1, colors[C_PROMPT].fg,
    461 			    colors[C_PROMPT].bg, state->prompt,
    462 			    strlen(state->prompt) + 2, maxx, 1, 1, CENTER, 0, 1,
    463 			    NULL, 0, 0, 0, 0, 0, 0))
    464 			return 1;
    465 	}
    466 	else if (state->action)
    467 	{
    468 		if (draw_string(0, state->maxy - 1, colors[C_ACTION].fg,
    469 			    colors[C_ACTION].bg, actions[state->action],
    470 			    strlen(actions[state->action]) + 2, maxx, 1, 1,
    471 			    CENTER, 0, 1, NULL, 0, 0, 0, 0, 0, 0))
    472 			return 1;
    473 	}
    474 	else if (state->error)
    475 	{
    476 		if (draw_string(0, state->maxy - 1, colors[C_ERROR].fg,
    477 			    colors[C_ERROR].bg, errors[state->error],
    478 			    strlen(errors[state->error]) + 2, maxx, 1, 1,
    479 			    CENTER, 0, 1, NULL, 0, 0, 0, 0, 0, 0))
    480 			return 1;
    481 	}
    482 	else
    483 		while (pseg < status_segments + LEN(status_segments))
    484 		{
    485 			int last = pseg - status_segments
    486 				== LEN(status_segments) - 1;
    487 
    488 			buf = calloc(limits[L_LINE_MAX], 1);
    489 			if (!buf)
    490 				return 1;
    491 
    492 			*buf = 0;
    493 			if (pseg->callback)
    494 				pseg->callback(buf, limits[L_LINE_MAX],
    495 					pseg->format, seg_size, state);
    496 			else
    497 				memccpy(buf, pseg->format, 0,
    498 					limits[L_LINE_MAX]);
    499 			if (pseg == status_segments + LEN(status_segments) - 1)
    500 				seg_size = maxx - 1 - current_start;
    501 			if (draw_string(current_start, state->maxy - 1,
    502 				    colors[pseg->color_index].fg,
    503 				    colors[pseg->color_index].bg, buf,
    504 				    MIN(seg_size, (ssize_t)(strlen(buf) + 2)),
    505 				    last ? seg_size + 1 : seg_size, 1, 1,
    506 				    pseg->alignment, 0, 1, NULL, 0, 0, 0, 0, 0,
    507 				    0))
    508 			{
    509 				free(buf);
    510 				return 1;
    511 			}
    512 			free(buf);
    513 			pseg++;
    514 			current_start += seg_size;
    515 		}
    516 
    517 	return 0;
    518 }
    519 
    520 /*  mc  - (max cols) pack text and padding into this many columns
    521  *  fc  - (fill cols) this many columns from the left/right/center will be
    522  *  	  filled
    523  *  p_? - (padding) this many cells will be skipped from ? side
    524  *  ds  - (display start) string starts being displayed from this display column
    525  * 	  counted from the start of the string; an "entry point" joining string
    526  * 	  index with screen
    527  * fill - boolean; 1 = fill fc, 0 = don't fill fc
    528  * srch - search string; if empty or NULL no highlighting happens
    529  * sfg  - fg color of search highlght
    530  * sbg  - bg color of search highlight
    531  * ssi  - selection start index (>=)
    532  * sei  - selection end index (<)
    533  * slfg - selection fg color
    534  * slbg - selection bg color
    535  * */
    536 int
    537 draw_string(const int x, const int y, const uint16_t fg, const uint16_t bg,
    538 	const char* s, const int mc, const int fc, const int p_l, const int p_r,
    539 	const Alignment align, const int ds, const int fill,
    540 	const uint32_t* srch, const uint16_t sfg, const uint16_t sbg,
    541 	const int ssi, const int sei, const uint16_t slfg, const uint16_t slbg)
    542 {
    543 	uint32_t* us = NULL;
    544 	assert(s != NULL);
    545 	us = calloc(strlen(s) + 1, sizeof(uint32_t));
    546 	if (!us)
    547 		return 1;
    548 	u8_string_to_unicode(us, s, mc);
    549 	u32_draw_string(x, y, fg, bg, us, mc, fc, p_l, p_r, align, ds, fill,
    550 		srch, sfg, sbg, ssi, sei, slfg, slbg);
    551 	free(us);
    552 	return 0;
    553 }
    554 
    555 void
    556 format_codep(char* result, ssize_t result_size, const char* format,
    557 	const ssize_t seg_size, const struct DrawState* state)
    558 {
    559 	char codep[LINE_DEFAULT];
    560 	assert((state != NULL) && (format != NULL));
    561 	UNUSED(seg_size);
    562 	if (state->buffer
    563 		&& state->buffer[state->coord.row].text[state->coord.col])
    564 		snprintf(codep, LINE_DEFAULT, "%06X",
    565 			state->buffer[state->coord.row].text[state->coord.col]);
    566 	else
    567 		memccpy(codep, "------", 0, LINE_DEFAULT);
    568 	snprintf(result, result_size, format, codep);
    569 }
    570 
    571 void
    572 format_filename(char* result, ssize_t result_size, const char* format,
    573 	const ssize_t seg_size, const struct DrawState* state)
    574 {
    575 	char* basename	  = NULL;
    576 	const char* pbase = NULL;
    577 
    578 	assert((state != NULL) && (format != NULL));
    579 	if (!result || seg_size < 4)
    580 		return;
    581 
    582 	pbase	 = u8_basename(state->filename);
    583 	basename = strndup(pbase, strlen(pbase) + 1);
    584 
    585 	if ((ssize_t)(u8_strlen(basename) + 2) >= seg_size)
    586 	{
    587 		if (state->dirty)
    588 			basename[seg_size - 4] = 0;
    589 		else
    590 			basename[seg_size - 3] = 0;
    591 		snprintf(result, result_size, format, basename,
    592 			state->dirty ? "…*" : "…");
    593 	}
    594 	else
    595 		snprintf(result, result_size, format, basename,
    596 			state->dirty ? "*" : "");
    597 	free(basename);
    598 }
    599 
    600 void
    601 format_mode(char* result, ssize_t result_size, const char* format,
    602 	const ssize_t seg_size, const struct DrawState* state)
    603 {
    604 	const char* mode_label = mode_labels[UNKNOWN_MODE];
    605 	assert((state != NULL) && (format != NULL));
    606 	UNUSED(seg_size);
    607 	if (!result)
    608 		goto format_print;
    609 	if (IN(state->mode, M_INPUT_NUM))
    610 		mode_label = mode_labels[INPUT_NUM_MODE];
    611 	else if (IN(state->mode, M_INPUT))
    612 		mode_label = mode_labels[INPUT_MDOE];
    613 	else if (IN(state->mode, M_HELP))
    614 		mode_label = mode_labels[HELP_MODE];
    615 	else if (IN(state->mode, M_PROMPT))
    616 		mode_label = mode_labels[PROMPT_MODE];
    617 	else if (IN(state->mode, M_SELECTION))
    618 		mode_label = mode_labels[SELECTION_MODE];
    619 	else if (IN(state->mode, M_COMMAND))
    620 		mode_label = mode_labels[COMMAND_MODE];
    621 	else if (IN(state->mode, M_OVERWRITE))
    622 		mode_label = mode_labels[OVERWRITE_MODE];
    623 	else
    624 		mode_label = mode_labels[INSERT_MODE];
    625 
    626 format_print:
    627 	snprintf(result, result_size, format, mode_label);
    628 }
    629 
    630 void
    631 format_position(char* result, ssize_t result_size, const char* format,
    632 	const ssize_t seg_size, const struct DrawState* state)
    633 {
    634 	assert((state != NULL) && (format != NULL));
    635 	UNUSED(seg_size);
    636 	if (!result)
    637 		return;
    638 	snprintf(result, result_size, format, state->first_shown.col,
    639 		state->display_column + 1, state->coord.row + 1,
    640 		state->rows_count > 1
    641 			? (state->coord.row * 100) / (state->rows_count - 1)
    642 			: 0);
    643 }
    644 
    645 void
    646 free_bufferline(struct BufferLine* bl)
    647 {
    648 	if (!bl)
    649 		return;
    650 	free(bl->text);
    651 	bl->text   = NULL;
    652 	bl->size   = 0;
    653 	bl->length = 0;
    654 	bl->fg	   = 0;
    655 	bl->bg	   = 0;
    656 }
    657 
    658 void
    659 free_drawstate(struct DrawState* state)
    660 {
    661 	ssize_t row = 0;
    662 	assert(state != NULL);
    663 	for (row = 0; row < state->buffer_size; row++)
    664 		free_bufferline(state->buffer + row);
    665 	free(state->buffer);
    666 	free(state->input);
    667 	free(state->search);
    668 }
    669 
    670 void
    671 init_bufferline(struct BufferLine* bl)
    672 {
    673 	if (!bl)
    674 		return;
    675 	bl->text   = NULL;
    676 	bl->length = 0;
    677 	bl->size   = 0;
    678 	bl->fg	   = 0;
    679 	bl->bg	   = 0;
    680 }
    681 
    682 void
    683 init_coord(struct Coord* c, const int col, const int row)
    684 {
    685 	assert(c != NULL);
    686 	c->col = col;
    687 	c->row = row;
    688 }
    689 
    690 void
    691 init_drawstate(struct DrawState* state, char* filename, char* prompt,
    692 	const int skip_paste_and_mode_init, const int skip_filenames_init)
    693 {
    694 	assert(state != NULL);
    695 	state->error  = ERR_NONE;
    696 	state->action = ACT_IDLE;
    697 	if (!skip_filenames_init)
    698 	{
    699 		state->filename		  = filename;
    700 		state->real_filename	  = NULL;
    701 		state->orig_real_filename = NULL;
    702 		state->backup_filename	  = NULL;
    703 		state->rfn_len		  = 0;
    704 	}
    705 	state->input	    = NULL;
    706 	state->search	    = NULL;
    707 	state->chain_dialog = 0;
    708 	state->buffer	    = NULL;
    709 	state->buffer_size  = 0;
    710 	init_coord(&state->coord, 0, 0);
    711 	state->display_column = 0;
    712 	init_coord(&state->first_shown, 0, 0);
    713 	state->input_first_shown_column = 0;
    714 	state->input_display_column	= 0;
    715 	state->input_column		= 0;
    716 	state->input_title		= NULL;
    717 	state->rows_count		= 0;
    718 	state->saved_column		= -1;
    719 	state->help_width		= calculate_help_width(state);
    720 	state->input_dialog_width	= calculate_input_dialog_width(state);
    721 	state->line_numbers		= line_numbers;
    722 	if (!skip_paste_and_mode_init)
    723 	{
    724 		state->paste	   = NULL;
    725 		state->paste_count = 0;
    726 		state->paste_size  = 0;
    727 		CLEAR(state->mode, M_OVERWRITE | M_COMMAND);
    728 	}
    729 	state->prompt		     = prompt;
    730 	state->prompt_callback	     = NULL;
    731 	state->input_callback	     = NULL;
    732 	state->input_update_callback = NULL;
    733 	state->running		     = 1;
    734 	state->dirty		     = 0;
    735 	state->new_file		     = 0;
    736 	state->recalculate_size	     = 1;
    737 	CLEAR(state->mode,
    738 		M_HELP | M_INPUT | M_INPUT_NUM | M_PROMPT | M_SELECTION);
    739 	init_coord(&state->sel_start, 0, 0);
    740 	init_coord(&state->sel_end, 0, 0);
    741 	state->backup_possible = 1;
    742 }
    743 
    744 void
    745 recalculate_line_numbers(struct DrawState* state)
    746 {
    747 	assert(state != NULL);
    748 	state->line_numbers_width = calculate_line_numbers_width(state);
    749 }
    750 
    751 void
    752 recalculate_size(struct DrawState* state)
    753 {
    754 	assert(state != NULL);
    755 	state->maxx		  = tb_width();
    756 	state->help_width	  = calculate_help_width(state);
    757 	state->input_dialog_width = calculate_input_dialog_width(state);
    758 	state->maxy		  = tb_height();
    759 	recalculate_line_numbers(state);
    760 }
    761 
    762 void
    763 redraw_status(struct DrawState* state)
    764 {
    765 	assert(state != NULL);
    766 	if (state->recalculate_size)
    767 	{
    768 		recalculate_size(state);
    769 		state->recalculate_size = 0;
    770 	}
    771 	(void)draw_status(state);
    772 	tb_present();
    773 }
    774 
    775 /*  mc  - (max cols) pack text and padding into this many columns
    776  *  fc  - (fill cols) this many columns from the left/right/center will be
    777  *  	  filled
    778  *  p_? - (padding) this many cells will be skipped from ? side
    779  *  ds  - (display start) string starts being displayed from this display column
    780  * 	  counted from the start of the string; an "entry point" joining string
    781  * 	  index with screen
    782  * fill - boolean; 1 = fill fc, 0 = don't fill fc
    783  * srch - search string; if empty or NULL no highlighting happens
    784  * sfg  - fg color of search highlight
    785  * sbg  - bg color of search highlight
    786  * ssi  - selection start index (>=)
    787  * sei  - selection end index (<)
    788  * slfg - selection fg color
    789  * slbg - selection bg color
    790  * */
    791 void
    792 u32_draw_string(const int x, const int y, const uint16_t fg, const uint16_t bg,
    793 	const uint32_t* s, const int mc, const int fc, const int p_l,
    794 	const int p_r, const Alignment align, const int ds, const int fill,
    795 	const uint32_t* srch, const uint16_t sfg, const uint16_t sbg,
    796 	const int ssi, const int sei, const uint16_t slfg, const uint16_t slbg)
    797 {
    798 	/*
    799 	 *                   <-------- visible part ---------->
    800 	 *        +---------+-------+----------------+---------+
    801 	 *        |         |       |                |         |
    802 	 *        |<-- d -->|<-p_l->|<------ c ----->|<- p_r ->|
    803 	 *        |         |<-------------- mc -------------->|
    804 	 *        |<------------------ fc -------------------->|
    805 	 *        x                 |                |
    806 	 *                          ds               ds+c
    807 	 *                           <.....(sc).....>
    808 	 *                      (align == RIGHT in this case)
    809 	 */
    810 
    811 	int d = 0; /* distance, in screen columns, from x to
    812 		    * visible part of the string, followed by
    813 		    * p_l */
    814 
    815 	int c = 0; /* distance in screen columns from
    816 		    * d + p_l to d + mc - p_r (part of the
    817 		    * displayed string containing text, between
    818 		    * padding; string fits within this many columns
    819 		    */
    820 
    821 	int sc = 0; /* current screen column; goes from 0..c-1
    822 		     * only increased when actual characters are
    823 		     * written
    824 		     */
    825 
    826 	int ssc = 0; /* current string screen column; goes from start
    827 		      * of the string until ds+c */
    828 
    829 	int w = 0; /* actual on-screen rune width */
    830 
    831 	const int ts = TAB_SIZE;
    832 
    833 	const uint32_t* ps = s; /* pointer to current character in
    834 				 * string; string index: ps-s */
    835 
    836 	uint16_t cfg = fg; /* current fg */
    837 	uint16_t cbg = bg; /* current bg */
    838 
    839 	int hctl = 0; /* highlight "characters to live", when this
    840 		       * reaches zero the highlighting is turned
    841 		       * off */
    842 
    843 	assert(s != NULL);
    844 	c = mc - p_l - p_r;
    845 
    846 	if (align == LEFT)
    847 		d = 0;
    848 	else if (align == RIGHT)
    849 		d = fc >= mc ? fc - mc : 0;
    850 	else if (align == CENTER)
    851 		d = fc >= mc ? (fc - mc) / 2 : 0;
    852 
    853 	/* fill */
    854 	if (fill)
    855 		for (int j = 0; j < fc; j++)
    856 			tb_set_cell(x + j, y, L' ', fg, bg);
    857 
    858 	/* left padding */
    859 	for (int j = 0; j < p_l; j++)
    860 		tb_set_cell(x + d + j, y, L' ', fg, bg);
    861 
    862 	sc = 0;
    863 
    864 	/*
    865 	 * main idea: we go from start of the string to the end and
    866 	 * count displayed characters until we get to ds, then we also
    867 	 * output characters; tabs count as spaces up to multiple of ts,
    868 	 * counted from the start of the string
    869 	 *
    870 	 * exit condition is also sc == c, when we have reached the
    871 	 * maximum number of characters to output
    872 	 */
    873 	while (*ps && sc < c)
    874 	{
    875 		if ((hctl == 0) && ((ps - s) >= ssi) && ((ps - s) < sei))
    876 		{
    877 			cfg = slfg;
    878 			cbg = slbg;
    879 		}
    880 
    881 		if (srch && *srch && hctl == 0)
    882 		{
    883 			if (u32_starts_with(ps, srch))
    884 			{
    885 				hctl = u32_strlen(srch)
    886 					- (ssc < ds ? ds - ssc : 0);
    887 				cfg = sfg;
    888 				cbg = sbg;
    889 			}
    890 		}
    891 
    892 		if (*ps == '\t')
    893 		{
    894 			if (ssc < ds)
    895 			{
    896 				/* clang-format off */
    897 				/*                     ssc-(ssc%ts)+ts
    898 				 *              \t     |  = ssc+(ts-(ssc%ts))
    899 				 *  ----------|-----[ :|::: ]
    900 				 *            | |     |
    901 				 *            | |     ds
    902 				 * ssc-(ssc%ts) |
    903 				 *              ssc
    904 				 */
    905 				/* clang-format on */
    906 				if (ssc - (ssc % ts) + ts >= ds)
    907 				{
    908 					for (int j = 0;
    909 						j < ssc - (ssc % ts) + ts - ds;
    910 						j++)
    911 						tb_set_cell(x + d + p_l + sc + j,
    912 							y, L' ', cfg, cbg);
    913 					sc += ssc + (ts - (ssc % ts)) - ds;
    914 					hctl -= ssc + (ts - (ssc % ts)) - ds;
    915 					if (hctl < 0)
    916 						hctl = 0;
    917 				}
    918 				/* clang-format off */
    919 				/* else, just increase counter
    920 				 *
    921 				 * \t
    922 				 * ----------|----[ ::::: ]
    923 				 * |         |      |
    924 				 * |         |      ds
    925 				 * |         ssc-(ssc%ts)+ts
    926 				 * ssc         = ssc + (ts-(ssc%ts))
    927 				 * */
    928 				/* clang-format on */
    929 				ssc += (ts - (ssc % ts));
    930 			}
    931 			else /* ssc >= ds */
    932 			{
    933 				/* clang-format off */
    934 				/*
    935 				 *   ds          ssc+(ts-(ssc%ts))
    936 				 *   |   \t      |
    937 				 * [ ::::::: ]---|-----
    938 				 *       |  |
    939 				 *       |  ds+c
    940 				 *       ssc
    941 				 */
    942 				/* clang-format on */
    943 				if (ssc + (ts - (ssc % ts)) >= ds + c)
    944 				{
    945 					for (int j = 0; j < ds + c - ssc; j++)
    946 						tb_set_cell(x + d + p_l + sc + j,
    947 							y, L' ', cfg, cbg);
    948 					sc   = c;
    949 					ssc  = ds + c;
    950 					hctl = 0;
    951 					cfg  = fg;
    952 					cbg  = bg;
    953 				}
    954 				else /* ssc+(ts-(ssc%ts)) < ds+c */
    955 				{
    956 					/* clang-format off */
    957 					/*
    958 					 *      \t
    959 					 * [ :::::::::::|::: ]-----
    960 					 *   |  |       |   |
    961 					 *   |  |       |   ds+c
    962 					 *   |  |	ssc+(ts-(ssc%ts))
    963 					 *   |  ssc
    964 					 *   ds
    965 					 */
    966 					/* clang-format on */
    967 					for (int j = 0; j < ts - (ssc % ts); j++)
    968 						tb_set_cell(x + d + p_l + sc + j,
    969 							y, L' ', cfg, cbg);
    970 					sc += ts - (ssc % ts);
    971 					hctl -= ts - (ssc % ts);
    972 					if (hctl < 0)
    973 						hctl = 0;
    974 					ssc += ts - (ssc % ts);
    975 				}
    976 			}
    977 		}
    978 		else
    979 		{
    980 			w = wcwidth((wchar_t)*ps);
    981 			if (w < 0)
    982 				w = 1;
    983 			if (ssc >= ds)
    984 			{
    985 				if (sc < c && w > 0)
    986 				{
    987 					/*if ((w == 0) && (x + d + p_l +
    988 					sc > 0)) tb_extend_cell(x + d +
    989 					p_l + sc
    990 								- 1,
    991 							y, *ps);
    992 					else*/
    993 					/* TODO: Get the previous cell
    994 					 * (if applicable) and overwrite
    995 					 * it if it has wcwidth >1
    996 					 */
    997 					tb_set_cell(x + d + p_l + sc, y, *ps,
    998 						cfg, cbg);
    999 					sc += w;
   1000 				}
   1001 				hctl--;
   1002 				if (hctl < 0)
   1003 					hctl = 0;
   1004 			}
   1005 			ssc += w;
   1006 		}
   1007 		if (hctl == 0)
   1008 		{
   1009 			cfg = fg;
   1010 			cbg = bg;
   1011 		}
   1012 		ps++;
   1013 	}
   1014 
   1015 	/* right padding */
   1016 	for (int j = 0; j < p_r; j++)
   1017 		tb_set_cell(x + d + p_l + c + j, y, L' ', fg, bg);
   1018 }