poe

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

poe.c (71633B)


      1 /* This program is licensed under the terms of GNU GPL v3 or (at your option)
      2  * any later version. Copyright (C) 2021-2026  Страхиња Радић.
      3  * See the file LICENSE for exact copyright and license details. */
      4 
      5 #include <assert.h>
      6 #include <errno.h>
      7 #include <fcntl.h>
      8 #include <locale.h>
      9 #include <stdarg.h>
     10 #include <stdint.h>
     11 #include <stdio.h>
     12 #include <stdlib.h>
     13 #include <string.h>
     14 #include <sys/stat.h>
     15 #include <unistd.h>
     16 
     17 #define COPYRIGHT                                                             \
     18 	("  This program is licensed under the terms of GNU GPL v3"           \
     19 	 " or (at your option)\n"                                             \
     20 	 "  any later version. Copyright (C) 2021-2026  Strahinya Radich.\n"  \
     21 	 "  See the file LICENSE for exact copyright and license "            \
     22 	 "details.\n"                                                         \
     23 	 "  File termbox.h is based on termbox2 TUI library, (c) 2021"        \
     24 	 " termbox developers,\n"                                             \
     25 	 "  which is released under the terms of Expat license, see the file" \
     26 	 "  LICENSE.termbox.\n")
     27 #define POE_C_GLOBALS
     28 #define TB_IMPL
     29 
     30 /* Order is important! */
     31 #include "draw.h"
     32 #include "poe.h"
     33 #include "config.h"
     34 #include "limit.h"
     35 #include "po.h"
     36 #include "strtonum.h"
     37 #include "util.h"
     38 #include "version.h"
     39 
     40 #define MAXCOPYBUF 8192
     41 
     42 extern const char* errors[];
     43 extern const char* actions[];
     44 extern const char* prompt_dirty;
     45 extern const char* prompt_edit_dirty;
     46 extern const char* prompt_overwrite;
     47 
     48 static int
     49 cancel_callback(struct DrawState* state, struct tb_event* ev)
     50 {
     51 	assert((state != NULL) && (ev != NULL));
     52 	if (strchr("Yy", ev->ch))
     53 	{
     54 		state->reshow_edit = 0;
     55 		save_msgstr(state);
     56 	}
     57 	else /* not saving */
     58 	{
     59 		state->edit_dirty = 0;
     60 		cancel_msgstr(state);
     61 	}
     62 	return 0;
     63 }
     64 
     65 static int
     66 cancel_close(struct DrawState* state)
     67 {
     68 	assert(state != NULL);
     69 	if (!state->error && state->in_prompt)
     70 	{
     71 		state->in_prompt = 0;
     72 		*state->prompt	 = 0;
     73 	}
     74 	else if (state->show_help)
     75 		state->show_help = 0;
     76 	else if (state->show_edit)
     77 		cancel_msgstr(state);
     78 	else if (state->show_search)
     79 		cancel_search(state);
     80 	else
     81 		state->error = ERR_EXIT_KEY;
     82 	return 0;
     83 }
     84 
     85 static int
     86 cancel_msgstr(struct DrawState* state)
     87 {
     88 	char* temp = NULL;
     89 	ssize_t i;
     90 
     91 	assert(state != NULL);
     92 	if (state->edit_dirty)
     93 	{
     94 		state->in_prompt = 1;
     95 		MEMCCPY(state->prompt, prompt_edit_dirty, LINE_DEFAULT, temp);
     96 		state->prompt_callback = cancel_callback;
     97 		return 0;
     98 	}
     99 
    100 	state->show_edit	 = 0;
    101 	state->reshow_edit	 = 0;
    102 	state->edit_info_focused = 0;
    103 
    104 	for (i = 0; i < state->input_rows_count; i++)
    105 		free_bufferline(state->input_buffer + i);
    106 	free(state->input_buffer);
    107 	state->input_buffer	= NULL;
    108 	state->input_rows_count = 0;
    109 	if (state->info_buffer)
    110 		for (i = 0; i < state->info_rows_count; i++)
    111 			free_bufferline(&state->info_buffer[i]);
    112 	free(state->info_buffer);
    113 	state->info_buffer     = NULL;
    114 	state->info_rows_count = 0;
    115 	state->fuzzy_count     = state->fuzzy_before_edit;
    116 	switch (state->input_changed_fuzzy)
    117 	{
    118 	case SET:
    119 		state->entries[state->msgid_number - 1].flags &= ~FL_FUZZY;
    120 		break;
    121 	case CLEARED:
    122 		state->entries[state->msgid_number - 1].flags |= FL_FUZZY;
    123 		break;
    124 	default:;
    125 	}
    126 	state->input_changed_fuzzy = UNCHANGED;
    127 	tb_hide_cursor();
    128 	return 0;
    129 }
    130 
    131 static int
    132 cancel_search(struct DrawState* state)
    133 {
    134 	assert(state != NULL);
    135 	state->show_search = 0;
    136 	*state->search	   = 0;
    137 	tb_hide_cursor();
    138 	return 0;
    139 }
    140 
    141 static int
    142 copy_msgid_to_input(struct DrawState* state)
    143 {
    144 	struct PoEntry* current;
    145 	struct BufferLine* newbuf;
    146 	struct BufferLine* pbuffer;
    147 	const uint32_t* pmsgid;
    148 	const uint32_t* old_pmsgid;
    149 	uint32_t* newtext;
    150 	char* temp = NULL;
    151 	ssize_t msgid_num_lines, lines_to_init, lines_to_free, i, len, newlen;
    152 
    153 	assert(state != NULL);
    154 
    155 	if (state->edit_info_focused)
    156 	{
    157 		state->error = ERR_DLG_FOCUS_EDIT;
    158 		return 0;
    159 	}
    160 
    161 	if (!state->in_prompt && *state->input_buffer->text)
    162 	{
    163 		state->in_prompt = 1;
    164 		MEMCCPY(state->prompt, prompt_overwrite, LINE_DEFAULT, temp);
    165 		state->prompt_callback = overwrite_copy_msgid_callback;
    166 		return 0;
    167 	}
    168 
    169 	if (state->msgid_number == 1)
    170 	{
    171 		state->error = ERR_ILLEGAL_ON_FIRST;
    172 		return 0;
    173 	}
    174 
    175 	assert(state->entries != NULL);
    176 	current		= &state->entries[state->msgid_number - 1];
    177 	msgid_num_lines = u32_lines_in_string(current->msgid, 0, 0);
    178 	lines_to_init	= state->input_rows_count < msgid_num_lines
    179 		  ? msgid_num_lines - state->input_rows_count
    180 		  : 0;
    181 	lines_to_free	= state->input_rows_count > msgid_num_lines
    182 		  ? state->input_rows_count - msgid_num_lines
    183 		  : 0;
    184 	for (i = 0; i < lines_to_free; i++)
    185 		free_bufferline(
    186 			state->input_buffer + state->input_rows_count - i - 1);
    187 	newbuf = realloc(state->input_buffer,
    188 		msgid_num_lines * sizeof(struct BufferLine));
    189 	pmsgid = current->msgid;
    190 
    191 	if (!newbuf)
    192 		return 0;
    193 
    194 	pbuffer = NULL;
    195 	for (i = 0; i < lines_to_init; i++)
    196 	{
    197 		pbuffer = newbuf + msgid_num_lines - 1 - i;
    198 		init_bufferline(pbuffer);
    199 		newtext = calloc(LINE_DEFAULT, sizeof(uint32_t));
    200 		if (!newtext)
    201 			return 0;
    202 		pbuffer->text = newtext;
    203 	}
    204 
    205 	pbuffer = newbuf;
    206 	while (pmsgid && *pmsgid)
    207 	{
    208 		old_pmsgid = pmsgid;
    209 		len	   = u32_next_line(current->msgid, &pmsgid, 0, 0);
    210 		u32_strncpy(pbuffer->text, old_pmsgid,
    211 			MIN(len + 1, LINE_DEFAULT));
    212 		newlen = u32_decode_tabs(state, pbuffer->text, LINE_DEFAULT);
    213 		pbuffer->length = newlen;
    214 		pbuffer++;
    215 	}
    216 
    217 	state->input_rows_count = msgid_num_lines;
    218 	state->input_buffer	= newbuf;
    219 	state->edit_dirty	= 1;
    220 
    221 	return 0;
    222 }
    223 
    224 int
    225 cp(const char* from, const char* to)
    226 {
    227 	struct stat st;
    228 	struct timespec times[2];
    229 	char buf[MAXCOPYBUF];
    230 	int fromfd, tofd;
    231 	int nread    = 0;
    232 	int nwritten = 0;
    233 	int result;
    234 	int saved_errno;
    235 
    236 	assert((from != NULL) && (to != NULL));
    237 
    238 	tofd   = 0;
    239 	fromfd = open(from, O_RDONLY);
    240 	if (fromfd < 0)
    241 		return 1;
    242 	if (fstat(fromfd, &st) < 0)
    243 	{
    244 		result = 1;
    245 		goto cp_cleanup;
    246 	}
    247 
    248 	tofd = open(to, O_WRONLY | O_CREAT | O_TRUNC, st.st_mode);
    249 	if (tofd < 0)
    250 		return 1;
    251 	if (fchmod(tofd, st.st_mode) < 0)
    252 	{
    253 		result = 1;
    254 		goto cp_cleanup;
    255 	}
    256 	times[0] = st.st_atim;
    257 	times[1] = st.st_mtim;
    258 
    259 	result = 0;
    260 	while ((nread = read(fromfd, buf, MAXCOPYBUF)) > 0)
    261 	{
    262 		nwritten = write(tofd, buf, nread);
    263 		if (nwritten < nread)
    264 		{
    265 			result = 1;
    266 			goto cp_cleanup;
    267 		}
    268 	}
    269 
    270 	if (nread < 0)
    271 	{
    272 		result = 1;
    273 		goto cp_cleanup;
    274 	}
    275 
    276 	errno = 0;
    277 	if (futimens(tofd, times) < 0)
    278 		result = 1;
    279 
    280 cp_cleanup:
    281 	saved_errno = errno;
    282 	if (tofd)
    283 		close(tofd);
    284 	close(fromfd);
    285 	errno = saved_errno;
    286 	return result;
    287 }
    288 
    289 static char*
    290 dirname(char* dir, char* path, const ssize_t dir_size)
    291 {
    292 	char* p = NULL;
    293 
    294 	assert((dir != NULL) && (path != NULL) && (dir_size > 0));
    295 	if (!*path)
    296 		return NULL;
    297 
    298 	MEMCCPY(dir, path, dir_size, p);
    299 
    300 	if (p > dir + 2 && *(p - 2) == '/')
    301 		*p = 0;
    302 	if (p > dir + 1)
    303 		p--;
    304 
    305 	while (p != dir && *p != '/')
    306 	{
    307 		p--;
    308 		if (p != dir && *p == '/')
    309 		{
    310 			*p = 0;
    311 			return dir;
    312 		}
    313 		else if (p == dir && *p == '/')
    314 			return dir;
    315 	}
    316 	dir[0] = '.';
    317 	dir[1] = 0;
    318 	return dir;
    319 }
    320 
    321 static int
    322 edit_page_down(struct DrawState* state)
    323 {
    324 	char* temp = NULL;
    325 	assert(state != NULL);
    326 	if (state->edit_info_focused)
    327 	{
    328 		if (state->info_first_shown.row + (EDIT_HEIGHT - 3) / 2
    329 			< state->info_rows_count - 1)
    330 			state->info_first_shown.row += (EDIT_HEIGHT - 3) / 2;
    331 		return 0;
    332 	}
    333 	if (state->edit_dirty)
    334 	{
    335 		state->in_prompt = 1;
    336 		MEMCCPY(state->prompt, prompt_edit_dirty, LINE_DEFAULT, temp);
    337 		state->prompt_callback = edit_page_down_callback;
    338 		return 0;
    339 	}
    340 	move_list_down(state);
    341 	state->msgstr_index = 0;
    342 	return show_edit(state);
    343 }
    344 
    345 static int
    346 edit_page_down_callback(struct DrawState* state, struct tb_event* ev)
    347 {
    348 	assert((state != NULL) && (ev != NULL));
    349 	if (strchr("Yy", ev->ch))
    350 	{
    351 		state->reshow_edit = 1;
    352 		save_msgstr(state);
    353 	}
    354 	else
    355 		state->edit_dirty = 0;
    356 	return edit_page_down(state);
    357 }
    358 
    359 static int
    360 edit_page_up(struct DrawState* state)
    361 {
    362 	char* temp = NULL;
    363 	assert(state != NULL);
    364 	if (state->edit_info_focused)
    365 	{
    366 		if (state->info_first_shown.row > (EDIT_HEIGHT - 3) / 2)
    367 			state->info_first_shown.row -= (EDIT_HEIGHT - 3) / 2;
    368 		else
    369 			move_start(state);
    370 		return 0;
    371 	}
    372 	if (state->edit_dirty)
    373 	{
    374 		state->in_prompt = 1;
    375 		MEMCCPY(state->prompt, prompt_edit_dirty, LINE_DEFAULT, temp);
    376 		state->prompt_callback = edit_page_up_callback;
    377 		return 0;
    378 	}
    379 	move_list_up(state);
    380 	state->msgstr_index = 0;
    381 	return show_edit(state);
    382 }
    383 
    384 static int
    385 edit_page_up_callback(struct DrawState* state, struct tb_event* ev)
    386 {
    387 	assert((state != NULL) && (ev != NULL));
    388 	if (strchr("Yy", ev->ch))
    389 	{
    390 		state->reshow_edit = 1;
    391 		save_msgstr(state);
    392 	}
    393 	else
    394 		state->edit_dirty = 0;
    395 	return edit_page_up(state);
    396 }
    397 
    398 static int
    399 erase_backwards(struct DrawState* state)
    400 {
    401 	struct BufferLine* buffer_line;
    402 
    403 	assert((state != NULL) && (state->input_buffer != NULL));
    404 	buffer_line = &state->input_buffer[state->coord.row];
    405 	if (state->edit_info_focused)
    406 	{
    407 		state->error = ERR_DLG_FOCUS_EDIT;
    408 		return 1;
    409 	}
    410 
    411 	if (state->coord.col > 0)
    412 	{
    413 		ssize_t i = state->coord.col;
    414 		assert(buffer_line->text != NULL);
    415 		while (i <= buffer_line->length)
    416 		{
    417 			buffer_line->text[i - 1] = buffer_line->text[i];
    418 			i++;
    419 		}
    420 		buffer_line->text[i - 1] = 0;
    421 		buffer_line->length--;
    422 		position_to(state, state->coord.row, state->coord.col - 1);
    423 		state->edit_dirty = 1;
    424 	}
    425 	else if (state->coord.row > 0)
    426 	{
    427 		move_up(state);
    428 		move_end(state);
    429 		erase_forward(state);
    430 	}
    431 	return 0;
    432 }
    433 
    434 static int
    435 erase_forward(struct DrawState* state)
    436 {
    437 	struct BufferLine* buffer_line;
    438 	size_t i;
    439 
    440 	assert((state != NULL) && (state->input_buffer != NULL));
    441 	buffer_line = &state->input_buffer[state->coord.row];
    442 	if (state->edit_info_focused)
    443 	{
    444 		state->error = ERR_DLG_FOCUS_EDIT;
    445 		return 1;
    446 	}
    447 
    448 	assert(buffer_line->text != NULL);
    449 	if (buffer_line->text[state->coord.col])
    450 	{
    451 		i = state->coord.col;
    452 		while (i < LINE_DEFAULT - 2 && buffer_line->text[i + 1])
    453 		{
    454 			buffer_line->text[i] = buffer_line->text[i + 1];
    455 			i++;
    456 		}
    457 		buffer_line->text[i] = 0;
    458 		buffer_line->length--;
    459 		state->edit_dirty = 1;
    460 	}
    461 	else if (state->coord.row < state->input_rows_count - 1)
    462 	{
    463 		if (join_lines(state))
    464 		{
    465 			state->error   = ERR_CANT_ALLOC;
    466 			state->running = 0;
    467 			return 1;
    468 		}
    469 	}
    470 	return 0;
    471 }
    472 
    473 static int
    474 erase_prev_word(struct DrawState* state)
    475 {
    476 	uint32_t* pline;
    477 	ssize_t len;
    478 	int i, j, d;
    479 
    480 	assert(state != NULL);
    481 	if (state->edit_info_focused)
    482 	{
    483 		state->error = ERR_DLG_FOCUS_EDIT;
    484 		return 1;
    485 	}
    486 	i     = state->coord.col;
    487 	len   = state->input_buffer[state->coord.row].length;
    488 	pline = state->input_buffer[state->coord.row].text;
    489 
    490 	if (i > 0 && u32_is_word_boundary(pline[i - 1], 0))
    491 		i--;
    492 	while (i > 0 && u32_is_word_boundary(pline[i], 0))
    493 		i--;
    494 	while (i > 0 && !u32_is_word_boundary(pline[i], 0)
    495 		&& !u32_is_word_boundary(pline[i - 1], 0))
    496 		i--;
    497 
    498 	d = state->coord.col - i;
    499 
    500 	for (j = i; j < len - d; j++)
    501 		pline[j] = pline[j + d];
    502 
    503 	len -= d;
    504 	state->input_buffer[state->coord.row].length = len;
    505 	pline[len]				     = 0;
    506 	state->coord.col			     = i;
    507 	state->input_display_column = display_length(pline, state->coord.col);
    508 	if (state->input_display_column < state->input_first_shown.col)
    509 		state->input_first_shown.col = state->input_display_column;
    510 	state->edit_dirty = 1;
    511 	return 0;
    512 }
    513 
    514 static int
    515 erase_search_backwards(struct DrawState* state)
    516 {
    517 	size_t i;
    518 	assert(state != NULL);
    519 	if (state->search_column > 0)
    520 	{
    521 		i = state->search_column;
    522 		do
    523 		{
    524 			state->search[i - 1] = state->search[i];
    525 			i++;
    526 		} while (state->search[i]);
    527 		state->search[i - 1] = 0;
    528 		search_position_to(state, state->search_column - 1);
    529 		return next_match(state);
    530 	}
    531 	return 0;
    532 }
    533 
    534 static int
    535 erase_search_forward(struct DrawState* state)
    536 {
    537 	size_t i;
    538 	assert(state != NULL);
    539 	if (state->search[state->search_column])
    540 	{
    541 		i = state->search_column;
    542 		while (i < MAXSEARCH - 2 && state->search[i + 1])
    543 		{
    544 			state->search[i] = state->search[i + 1];
    545 			i++;
    546 		}
    547 		state->search[i] = 0;
    548 
    549 		return next_match(state);
    550 	}
    551 	return 0;
    552 }
    553 
    554 static int
    555 erase_search_prev_word(struct DrawState* state)
    556 {
    557 	uint32_t* pline;
    558 	ssize_t len;
    559 	int i, j, d;
    560 
    561 	assert(state != NULL);
    562 	pline = state->search;
    563 	len   = u32_strlen(state->search);
    564 	i     = state->search_column;
    565 	if (i > 0 && u32_is_word_boundary(pline[i - 1], 0))
    566 		i--;
    567 	while (i > 0 && u32_is_word_boundary(pline[i], 0))
    568 		i--;
    569 	while (i > 0 && !u32_is_word_boundary(pline[i], 0)
    570 		&& !u32_is_word_boundary(pline[i - 1], 0))
    571 		i--;
    572 
    573 	d = state->search_column - i;
    574 
    575 	for (j = i; j < len - d; j++)
    576 		pline[j] = pline[j + d];
    577 
    578 	len -= d;
    579 	pline[len] = 0;
    580 	search_position_to(state, i);
    581 	return next_match(state);
    582 }
    583 
    584 static int
    585 erase_search_to_end(struct DrawState* state)
    586 {
    587 	assert(state != NULL);
    588 	state->search[state->search_column] = 0;
    589 	move_search_end(state);
    590 	return next_match(state);
    591 }
    592 
    593 static int
    594 erase_search_to_start(struct DrawState* state)
    595 {
    596 	size_t i, len;
    597 
    598 	assert((state != NULL) && (state->search != NULL));
    599 	i   = state->search_column;
    600 	len = u32_strlen(state->search);
    601 	do
    602 	{
    603 		assert(i < MAXSEARCH);
    604 		state->search[i - state->search_column] = state->search[i];
    605 		i++;
    606 	} while (state->search[i]);
    607 	state->search[len - state->search_column] = 0;
    608 	move_search_start(state);
    609 	return next_match(state);
    610 }
    611 
    612 static int
    613 erase_to_end(struct DrawState* state)
    614 {
    615 	struct BufferLine* buffer_line = NULL;
    616 
    617 	assert((state != NULL) && (state->input_buffer != NULL));
    618 	if (state->edit_info_focused)
    619 	{
    620 		state->error = ERR_DLG_FOCUS_EDIT;
    621 		return 1;
    622 	}
    623 
    624 	buffer_line = &state->input_buffer[state->coord.row];
    625 	assert(buffer_line->text != NULL);
    626 	buffer_line->text[state->coord.col] = 0;
    627 	buffer_line->length		    = state->coord.col;
    628 	move_end(state);
    629 	state->edit_dirty = 1;
    630 	return 0;
    631 }
    632 
    633 static int
    634 erase_to_start(struct DrawState* state)
    635 {
    636 	struct BufferLine* buffer_line;
    637 	ssize_t i;
    638 	ssize_t len;
    639 
    640 	assert((state != NULL) && (state->input_buffer != NULL));
    641 	i	    = state->coord.col;
    642 	buffer_line = &state->input_buffer[state->coord.row];
    643 	len	    = buffer_line->length;
    644 
    645 	if (state->edit_info_focused)
    646 	{
    647 		state->error = ERR_DLG_FOCUS_EDIT;
    648 		return 1;
    649 	}
    650 
    651 	do
    652 	{
    653 		assert(i <= len);
    654 		buffer_line->text[i - state->coord.col] = buffer_line->text[i];
    655 		i++;
    656 	} while (buffer_line->text[i]);
    657 	buffer_line->text[len - state->coord.col] = 0;
    658 	buffer_line->length			  = len - state->coord.col;
    659 	move_start(state);
    660 	state->edit_dirty = 1;
    661 	return 0;
    662 }
    663 
    664 static int
    665 exit_program(struct DrawState* state)
    666 {
    667 	char* temp = NULL;
    668 
    669 	assert(state != NULL);
    670 	if (state->edit_dirty)
    671 	{
    672 		state->in_prompt = 1;
    673 		MEMCCPY(state->prompt, prompt_edit_dirty, LINE_DEFAULT, temp);
    674 		state->prompt_callback = quit_callback;
    675 	}
    676 	else if (state->dirty)
    677 	{
    678 		state->in_prompt = 1;
    679 		MEMCCPY(state->prompt, prompt_dirty, LINE_DEFAULT, temp);
    680 		state->prompt_callback = quit_callback;
    681 	}
    682 	else
    683 		state->running = 0;
    684 	state->error = ERR_NONE;
    685 	return 0;
    686 }
    687 
    688 static void
    689 goto_msgid(struct DrawState* state, const ssize_t msgid_number)
    690 {
    691 	ssize_t last_non_obsolete;
    692 
    693 	assert(state != NULL);
    694 	last_non_obsolete
    695 		= state->real_msgid_count - state->obsolete_at_end_count;
    696 	if (msgid_number > state->real_msgid_count)
    697 		return;
    698 
    699 	state->msgid_number = msgid_number;
    700 
    701 	if (state->msgid_number < state->first_shown_msgid)
    702 		state->first_shown_msgid = state->msgid_number;
    703 	else if (state->msgid_number
    704 		> state->first_shown_msgid + state->maxy - 2)
    705 		state->first_shown_msgid
    706 			= state->msgid_number - (state->maxy - 2);
    707 	else if (last_non_obsolete < state->msgid_number + state->maxy - 2)
    708 		state->first_shown_msgid = (last_non_obsolete > state->maxy - 2
    709 				? last_non_obsolete - (state->maxy - 2)
    710 				: 1);
    711 }
    712 
    713 static void
    714 handle_key_event(struct tb_event* ev, struct DrawState* state)
    715 {
    716 	const struct Key* current = NULL;
    717 	size_t key;
    718 
    719 	assert((state != NULL) && (ev != NULL));
    720 	state->error = ERR_NONE;
    721 	errno	     = 0;
    722 
    723 	if (state->in_prompt)
    724 	{
    725 		if (ev->ch && strchr("YyNn", ev->ch))
    726 		{
    727 			if (state->prompt_callback)
    728 			{
    729 				state->prompt_callback(state, ev);
    730 				state->prompt_callback = NULL;
    731 			}
    732 			state->in_prompt = 0;
    733 			*state->prompt	 = 0;
    734 		}
    735 		else if (ev->key == TB_KEY_ESC)
    736 			cancel_close(state);
    737 		return;
    738 	}
    739 
    740 	/* Remember to also edit the following condition if you
    741 	 * reassign motion keys */
    742 	if (ev->key != TB_KEY_ARROW_DOWN && ev->key != TB_KEY_ARROW_UP
    743 		&& state->show_edit)
    744 		state->saved_column = -1;
    745 
    746 	for (key = 0; key < LEN(top_level_keys); key++)
    747 	{
    748 		current = top_level_keys + key;
    749 		if (((current->ev.mod == ev->mod)
    750 			    && (current->ev.key == ev->key) && !ev->ch)
    751 			|| (current->ev.ch && (current->ev.ch == ev->ch)))
    752 			if (current->callback)
    753 			{
    754 				current->callback(state);
    755 				return;
    756 			}
    757 	}
    758 
    759 	if (state->show_edit)
    760 	{
    761 		for (key = 0; key < LEN(edit_keys); key++)
    762 		{
    763 			current = &edit_keys[key];
    764 			if (((current->ev.mod == ev->mod)
    765 				    && (current->ev.key == ev->key) && !ev->ch)
    766 				|| (current->ev.ch
    767 					&& (current->ev.ch == ev->ch)))
    768 				if (current->callback)
    769 				{
    770 					current->callback(state);
    771 					return;
    772 				}
    773 		}
    774 		if (state->edit_info_focused)
    775 		{
    776 			state->error = ERR_DLG_FOCUS_EDIT;
    777 			return;
    778 		}
    779 		if (ev->ch)
    780 		{
    781 			insert_char(state, ev->ch);
    782 			return;
    783 		}
    784 	}
    785 
    786 	if (state->show_search)
    787 	{
    788 		for (key = 0; key < LEN(search_keys); key++)
    789 		{
    790 			current = &search_keys[key];
    791 			if (((current->ev.mod == ev->mod)
    792 				    && (current->ev.key == ev->key) && !ev->ch)
    793 				|| (current->ev.ch
    794 					&& (current->ev.ch == ev->ch)))
    795 				if (current->callback)
    796 				{
    797 					current->callback(state);
    798 					return;
    799 				}
    800 		}
    801 		if (ev->ch)
    802 		{
    803 			insert_search_char(state, ev->ch);
    804 			return;
    805 		}
    806 	}
    807 
    808 	if (state->show_help)
    809 	{
    810 		for (key = 0; key < LEN(help_keys); key++)
    811 		{
    812 			current = help_keys + key;
    813 			if (((current->ev.mod == ev->mod)
    814 				    && (current->ev.key == ev->key) && !ev->ch)
    815 				|| (current->ev.ch
    816 					&& (current->ev.ch == ev->ch)))
    817 				if (current->callback)
    818 				{
    819 					current->callback(state);
    820 					return;
    821 				}
    822 		}
    823 
    824 		state->error = ERR_DLG_OPEN;
    825 		return;
    826 	}
    827 
    828 	if (state->show_search || state->show_edit)
    829 	{
    830 		state->error = ERR_DLG_OPEN_QUIT;
    831 		return;
    832 	}
    833 
    834 	for (key = 0; key < LEN(main_keys); key++)
    835 	{
    836 		current = &main_keys[key];
    837 		if (((current->ev.mod == ev->mod)
    838 			    && (current->ev.key == ev->key) && !ev->ch)
    839 			|| (current->ev.ch && (current->ev.ch == ev->ch)))
    840 			if (current->callback)
    841 			{
    842 				current->callback(state);
    843 				return;
    844 			}
    845 	}
    846 
    847 	if (state->edit_info_focused)
    848 		state->error = ERR_DLG_FOCUS_EDIT;
    849 	else if (state->show_edit || state->show_search)
    850 		state->error = ERR_UNKNOWN_KEY;
    851 	else
    852 		state->error = ERR_UNKNOWN_KEY_HELP;
    853 }
    854 
    855 static int
    856 hide_search(struct DrawState* state)
    857 {
    858 	assert(state != NULL);
    859 	state->show_search = 0;
    860 	tb_hide_cursor();
    861 	return 0;
    862 }
    863 
    864 static int
    865 insert_char(struct DrawState* state, uint32_t ch)
    866 {
    867 	struct BufferLine* buffer_line;
    868 	uint32_t* temp = NULL;
    869 	ssize_t i, len, size;
    870 
    871 	assert((state != NULL) && (state->input_buffer != NULL));
    872 	if (state->edit_info_focused)
    873 		return 1;
    874 
    875 	ENSURE_SIZE(state->input_buffer[state->coord.row].text, temp,
    876 		state->input_buffer[state->coord.row].size,
    877 		state->input_buffer[state->coord.row].length + 2,
    878 		state->input_buffer[state->coord.row].size + LINE_ALLOC_DELTA,
    879 		insert_char_alloc_error, uint32_t);
    880 
    881 	buffer_line = &state->input_buffer[state->coord.row];
    882 	len	    = buffer_line->length;
    883 	size	    = buffer_line->size;
    884 	i	    = len;
    885 
    886 	assert(buffer_line->text != NULL);
    887 	while (i + 1 < size && i > state->coord.col)
    888 	{
    889 		buffer_line->text[i] = buffer_line->text[i - 1];
    890 		i--;
    891 	}
    892 	buffer_line->text[state->coord.col] = ch;
    893 	buffer_line->length++;
    894 	position_to(state, state->coord.row, state->coord.col + 1);
    895 	buffer_line->text[buffer_line->length] = 0;
    896 
    897 	state->edit_dirty = 1;
    898 	return 0;
    899 
    900 insert_char_alloc_error:
    901 	state->error = ERR_CANT_ALLOC;
    902 	return 1;
    903 }
    904 
    905 static int
    906 insert_line(struct DrawState* state)
    907 {
    908 	struct BufferLine* newbuf = NULL;
    909 	struct BufferLine* line	  = NULL;
    910 	uint32_t* newtext	  = NULL;
    911 	uint32_t* ptext		  = NULL;
    912 	ssize_t i;
    913 
    914 	if (state->edit_info_focused)
    915 	{
    916 		state->error = ERR_DLG_FOCUS_EDIT;
    917 		return 1;
    918 	}
    919 
    920 	assert(state != NULL);
    921 	newbuf = realloc(state->input_buffer,
    922 		(state->input_rows_count + 1) * sizeof(struct BufferLine));
    923 	if (!newbuf)
    924 		goto insert_line_alloc_error;
    925 	state->input_buffer = newbuf;
    926 	line		    = &state->input_buffer[state->input_rows_count];
    927 	init_bufferline(line);
    928 	newtext = calloc(LINE_DEFAULT, sizeof(uint32_t));
    929 	if (!newtext)
    930 		goto insert_line_alloc_error;
    931 	line->text = newtext;
    932 	line->size = LINE_DEFAULT;
    933 	state->input_rows_count++;
    934 	for (i = state->input_rows_count - 1; i > state->coord.row + 1; i--)
    935 	{
    936 		U32_SAFE_COPY(state->input_buffer[i].text, ptext,
    937 			state->input_buffer[i].size,
    938 			state->input_buffer[i - 1].text,
    939 			state->input_buffer[i].size, insert_line_alloc_error);
    940 		state->input_buffer[i].length
    941 			= state->input_buffer[i - 1].length;
    942 	}
    943 	ENSURE_SIZE(state->input_buffer[state->coord.row + 1].text, ptext,
    944 		state->input_buffer[state->coord.row + 1].size, LINE_DEFAULT,
    945 		LINE_DEFAULT, insert_line_alloc_error, uint32_t);
    946 	*state->input_buffer[state->coord.row + 1].text = 0;
    947 	if (state->coord.col < state->input_buffer[state->coord.row].length)
    948 		U32_SAFE_COPY(state->input_buffer[state->coord.row + 1].text,
    949 			ptext, state->input_buffer[state->coord.row + 1].size,
    950 			state->input_buffer[state->coord.row].text
    951 				+ state->coord.col,
    952 			state->input_buffer[state->coord.row].size,
    953 			insert_line_alloc_error);
    954 	state->input_buffer[state->coord.row + 1].length
    955 		= state->input_buffer[state->coord.row].length
    956 		- state->coord.col;
    957 	state->input_buffer[state->coord.row].text[state->coord.col] = 0;
    958 	state->input_buffer[state->coord.row].length = state->coord.col;
    959 
    960 	state->saved_column = -1;
    961 	state->coord.row++;
    962 	if (state->coord.row
    963 		> state->input_first_shown.row + (EDIT_HEIGHT - 3) / 2)
    964 		state->input_first_shown.row
    965 			= state->coord.row - (EDIT_HEIGHT - 3) / 2;
    966 	move_start(state);
    967 	state->edit_dirty = 1;
    968 	return 0;
    969 
    970 insert_line_alloc_error:
    971 	state->error = ERR_CANT_ALLOC;
    972 	return 1;
    973 }
    974 
    975 static int
    976 insert_search_char(struct DrawState* state, uint32_t ch)
    977 {
    978 	ssize_t len, i;
    979 	int add_null;
    980 	assert(state != NULL);
    981 	len = u32_strlen(state->search);
    982 	if (state->search_column + 1 < MAXSEARCH)
    983 	{
    984 		i	 = len + 1;
    985 		add_null = state->search[state->search_column] == 0;
    986 		while (i + 2 < MAXSEARCH && i > state->search_column)
    987 		{
    988 			state->search[i] = state->search[i - 1];
    989 			i--;
    990 		}
    991 		state->search[state->search_column++] = ch;
    992 		if (state->search_column == MAXSEARCH)
    993 			state->search_column = MAXSEARCH - 1;
    994 		state->search_display_column
    995 			= display_length(state->search, state->search_column);
    996 		if (state->search_display_column
    997 				- state->search_first_shown_column
    998 			> state->search_width - 4)
    999 		{
   1000 			state->search_first_shown_column
   1001 				= state->search_display_column
   1002 				- state->search_width + 4;
   1003 			state->search_display_column
   1004 				= display_length(state->search,
   1005 					state->search_column);
   1006 		}
   1007 		if (add_null)
   1008 			state->search[state->search_column] = 0;
   1009 
   1010 		next_match(state);
   1011 	}
   1012 	return 0;
   1013 }
   1014 
   1015 static int
   1016 insert_search_space(struct DrawState* state)
   1017 {
   1018 	return insert_search_char(state, SPACE);
   1019 }
   1020 
   1021 static int
   1022 insert_search_tab(struct DrawState* state)
   1023 {
   1024 	return insert_search_char(state, TAB);
   1025 }
   1026 
   1027 static int
   1028 insert_space(struct DrawState* state)
   1029 {
   1030 	return insert_char(state, SPACE);
   1031 }
   1032 
   1033 static int
   1034 insert_tab(struct DrawState* state)
   1035 {
   1036 	return insert_char(state, TAB);
   1037 }
   1038 
   1039 static int
   1040 join_lines(struct DrawState* state)
   1041 {
   1042 	struct BufferLine* first_line = NULL;
   1043 	struct BufferLine* next_line  = NULL;
   1044 	uint32_t* ptext		      = NULL;
   1045 	ssize_t i;
   1046 
   1047 	assert((state != NULL) && (state->input_buffer != NULL));
   1048 	if (state->coord.row + 1 == state->input_rows_count)
   1049 		return 1;
   1050 
   1051 	first_line = &state->input_buffer[state->coord.row];
   1052 	next_line  = &state->input_buffer[state->coord.row + 1];
   1053 	ENSURE_SIZE(first_line->text, ptext, first_line->size,
   1054 		first_line->length + next_line->length + 1,
   1055 		first_line->length + next_line->length + LINE_ALLOC_DELTA,
   1056 		join_lines_alloc_error, uint32_t);
   1057 	U32_MEMCCPY(first_line->text + first_line->length, next_line->text,
   1058 		next_line->length + 1, ptext);
   1059 	first_line->length = u32_strlen(first_line->text);
   1060 	for (i = state->coord.row + 1; i + 1 < state->input_rows_count; i++)
   1061 	{
   1062 		U32_SAFE_COPY(state->input_buffer[i].text, ptext,
   1063 			state->input_buffer[i].size,
   1064 			state->input_buffer[i + 1].text,
   1065 			state->input_buffer[i + 1].size,
   1066 			join_lines_alloc_error);
   1067 		state->input_buffer[i].length
   1068 			= state->input_buffer[i + 1].length;
   1069 	}
   1070 	free_bufferline(&state->input_buffer[state->input_rows_count - 1]);
   1071 	state->input_rows_count--;
   1072 	state->edit_dirty = 1;
   1073 	return 0;
   1074 
   1075 join_lines_alloc_error:
   1076 	state->error = ERR_CANT_ALLOC;
   1077 	return 1;
   1078 }
   1079 
   1080 static int
   1081 load_info(struct DrawState* state)
   1082 {
   1083 	struct PoEntry* first	   = NULL;
   1084 	struct PoEntry* current	   = NULL;
   1085 	struct BufferLine* newbuf  = NULL;
   1086 	struct BufferLine* pnewbuf = NULL;
   1087 	uint32_t* newtext	   = NULL;
   1088 	ssize_t msgid_num_lines, msgid_plural_num_lines, num_lines, i;
   1089 
   1090 	assert((state != NULL) && (state->entries != NULL));
   1091 	first		= &state->entries[0];
   1092 	current		= &state->entries[state->msgid_number - 1];
   1093 	msgid_num_lines = u32_lines_in_string(current->msgid, 0, 0);
   1094 	msgid_plural_num_lines
   1095 		= u32_lines_in_string(current->msgid_plural, 0, 0);
   1096 	num_lines = 0;
   1097 
   1098 	for (i = 0; i < state->info_rows_count; i++)
   1099 		free_bufferline(&state->info_buffer[i]);
   1100 	free(state->info_buffer);
   1101 
   1102 	state->info_maxlen = 0;
   1103 
   1104 	if (current->msgid)
   1105 		num_lines += msgid_num_lines + 1;
   1106 	if (current->msgid_plural)
   1107 	{
   1108 		num_lines += msgid_plural_num_lines + 1;
   1109 		if (first->plural_forms)
   1110 			num_lines += 2; /* title + plural_forms */
   1111 	}
   1112 	if (current->comments)
   1113 		num_lines += current->comment_lines + 1;
   1114 	if (current->extr_comments)
   1115 		num_lines += current->extr_comment_lines + 1;
   1116 	if (current->ref_comments)
   1117 		num_lines += current->ref_comment_lines + 1;
   1118 	newbuf = calloc(num_lines, sizeof(struct BufferLine));
   1119 	if (!newbuf)
   1120 		goto load_info_alloc_error;
   1121 
   1122 	pnewbuf = newbuf;
   1123 	for (i = 0; i < num_lines; i++)
   1124 	{
   1125 		init_bufferline(pnewbuf);
   1126 		newtext = calloc(LINE_DEFAULT, sizeof(uint32_t));
   1127 		if (!newtext)
   1128 			goto load_info_alloc_error;
   1129 		pnewbuf->text = newtext;
   1130 		pnewbuf->size = LINE_DEFAULT;
   1131 		pnewbuf->size = 0;
   1132 		pnewbuf++;
   1133 	}
   1134 
   1135 	state->info_rows_count = num_lines;
   1136 
   1137 	pnewbuf = newbuf;
   1138 
   1139 	if (current->msgid)
   1140 		if (load_info_msgid(state, &pnewbuf))
   1141 			return 1;
   1142 
   1143 	if (current->msgid_plural)
   1144 		if (load_info_msgid_plural(state, &pnewbuf))
   1145 			return 1;
   1146 
   1147 	if (current->comments)
   1148 		if (load_info_comments(state, &pnewbuf))
   1149 			return 1;
   1150 
   1151 	if (current->extr_comments)
   1152 		if (load_info_extr_comments(state, &pnewbuf))
   1153 			return 1;
   1154 
   1155 	if (current->ref_comments)
   1156 		if (load_info_ref_comments(state, &pnewbuf))
   1157 			return 1;
   1158 
   1159 	state->info_buffer = newbuf;
   1160 	return 0;
   1161 
   1162 load_info_alloc_error:
   1163 	state->error = ERR_CANT_ALLOC;
   1164 	return 1;
   1165 }
   1166 
   1167 static int
   1168 load_info_comments(struct DrawState* state, struct BufferLine** lines)
   1169 {
   1170 	struct PoEntry* current = NULL;
   1171 	uint32_t* comment	= NULL;
   1172 	uint32_t* ptext		= NULL;
   1173 	ssize_t len, llen, size, i;
   1174 
   1175 	current = &state->entries[state->msgid_number - 1];
   1176 	ptext	= u32_u8_memccpy((*lines)->text, "Comments:", 0, LINE_DEFAULT);
   1177 	(*lines)->length = llen
   1178 		= ptext ? ptext - (*lines)->text : LINE_DEFAULT - 1;
   1179 	(*lines)->size = LINE_DEFAULT;
   1180 	if (llen > state->info_maxlen)
   1181 		state->info_maxlen = llen;
   1182 	(*lines)->fg = colors[C_EDIT_MSGID_HEADING].fg;
   1183 	(*lines)->bg = colors[C_EDIT_MSGID_HEADING].bg;
   1184 	(*lines)++;
   1185 	for (i = 0; i < current->comment_lines; i++)
   1186 	{
   1187 		comment = current->comments[i];
   1188 		len	= u32_strlen(comment);
   1189 		size	= MAX(len + 2, LINE_DEFAULT); /* + NUL */
   1190 		ENSURE_SIZE((*lines)->text, ptext, (*lines)->size, size, size,
   1191 			load_info_comments_alloc_error, uint32_t);
   1192 		U32_MEMCCPY((*lines)->text, comment, len + 1, ptext);
   1193 		llen = ptext ? ptext - (*lines)->text : (*lines)->size - 1;
   1194 		(*lines)->length = llen;
   1195 		if (llen > state->info_maxlen)
   1196 			state->info_maxlen = llen;
   1197 		(*lines)++;
   1198 	}
   1199 	return 0;
   1200 
   1201 load_info_comments_alloc_error:
   1202 	state->error = ERR_CANT_ALLOC;
   1203 	return 1;
   1204 }
   1205 
   1206 static int
   1207 load_info_extr_comments(struct DrawState* state, struct BufferLine** lines)
   1208 {
   1209 	struct PoEntry* current = NULL;
   1210 	uint32_t* extr_comment	= NULL;
   1211 	uint32_t* ptext		= NULL;
   1212 	ssize_t len, llen, size, i;
   1213 
   1214 	current = &state->entries[state->msgid_number - 1];
   1215 	ptext	= u32_u8_memccpy((*lines)->text, "Translation comments:", 0,
   1216 		  LINE_DEFAULT);
   1217 	(*lines)->length = llen = ptext ? ptext - (*lines)->text : LINE_DEFAULT;
   1218 	(*lines)->size		= MAX(llen + 1, LINE_DEFAULT);
   1219 	if (llen > state->info_maxlen)
   1220 		state->info_maxlen = llen;
   1221 	(*lines)->fg = colors[C_EDIT_MSGID_HEADING].fg;
   1222 	(*lines)->bg = colors[C_EDIT_MSGID_HEADING].bg;
   1223 	(*lines)++;
   1224 	for (i = 0; i < current->extr_comment_lines; i++)
   1225 	{
   1226 		extr_comment = current->extr_comments[i];
   1227 		len	     = u32_strlen(extr_comment);
   1228 		size	     = MAX(len + 2, LINE_DEFAULT); /* + NUL */
   1229 		ENSURE_SIZE((*lines)->text, ptext, (*lines)->size, size, size,
   1230 			load_info_extr_comments_alloc_error, uint32_t);
   1231 		U32_MEMCCPY((*lines)->text, extr_comment, len + 1, ptext);
   1232 		/*len = ptext ? ptext - (*lines)->text : (*lines)->size - 1;*/
   1233 		(*lines)->length = llen;
   1234 		if (llen > state->info_maxlen)
   1235 			state->info_maxlen = llen;
   1236 		(*lines)++;
   1237 	}
   1238 	return 0;
   1239 
   1240 load_info_extr_comments_alloc_error:
   1241 	state->error = ERR_CANT_ALLOC;
   1242 	return 1;
   1243 }
   1244 
   1245 static int
   1246 load_info_msgid(struct DrawState* state, struct BufferLine** lines)
   1247 {
   1248 	struct PoEntry* current	    = NULL;
   1249 	const uint32_t* pumsgid	    = NULL;
   1250 	const uint32_t* old_pumsgid = NULL;
   1251 	uint32_t* ptext		    = NULL;
   1252 	ssize_t len, llen, size;
   1253 
   1254 	current = &state->entries[state->msgid_number - 1];
   1255 	ptext	= u32_u8_memccpy((*lines)->text, "msgid:", 0, LINE_DEFAULT);
   1256 	(*lines)->length = llen = ptext ? ptext - (*lines)->text : LINE_DEFAULT;
   1257 	if (llen > state->info_maxlen)
   1258 		state->info_maxlen = llen;
   1259 	(*lines)->fg = colors[C_EDIT_MSGID_HEADING].fg;
   1260 	(*lines)->bg = colors[C_EDIT_MSGID_HEADING].bg;
   1261 	(*lines)++;
   1262 	pumsgid = current->msgid;
   1263 	while (*pumsgid)
   1264 	{
   1265 		old_pumsgid = pumsgid;
   1266 		len	    = u32_next_line(current->msgid, &pumsgid, 0, 0);
   1267 		size	    = MAX(len + 2, LINE_DEFAULT); /* + NUL */
   1268 		ENSURE_SIZE((*lines)->text, ptext, (*lines)->size, size, size,
   1269 			load_info_msgid_alloc_error, uint32_t);
   1270 		U32_MEMCCPY((*lines)->text, old_pumsgid, len + 1, ptext);
   1271 		llen = u32_decode_tabs(state, (*lines)->text, len + 1);
   1272 		(*lines)->length = llen;
   1273 		if (llen > state->info_maxlen)
   1274 			state->info_maxlen = llen;
   1275 		(*lines)++;
   1276 	}
   1277 	return 0;
   1278 
   1279 load_info_msgid_alloc_error:
   1280 	state->error = ERR_CANT_ALLOC;
   1281 	return 1;
   1282 }
   1283 
   1284 static int
   1285 load_info_msgid_plural(struct DrawState* state, struct BufferLine** lines)
   1286 {
   1287 	struct PoEntry* current	    = NULL;
   1288 	struct PoEntry* first	    = NULL;
   1289 	const uint32_t* old_pumsgid = NULL;
   1290 	const uint32_t* pumsgid	    = NULL;
   1291 	uint32_t* ptext		    = NULL;
   1292 	ssize_t len, llen, size;
   1293 
   1294 	first	= &state->entries[0];
   1295 	current = &state->entries[state->msgid_number - 1];
   1296 	ptext = u32_u8_memccpy((*lines)->text, "msgid_plural:", 0, LINE_DEFAULT);
   1297 	(*lines)->length = llen = ptext ? ptext - (*lines)->text : LINE_DEFAULT;
   1298 	if (llen > state->info_maxlen)
   1299 		state->info_maxlen = llen;
   1300 	(*lines)->fg = colors[C_EDIT_MSGID_HEADING].fg;
   1301 	(*lines)->bg = colors[C_EDIT_MSGID_HEADING].bg;
   1302 	(*lines)++;
   1303 	pumsgid = current->msgid_plural;
   1304 	while (*pumsgid)
   1305 	{
   1306 		old_pumsgid = pumsgid;
   1307 		len  = u32_next_line(current->msgid_plural, &pumsgid, 0, 0);
   1308 		size = MAX(len + 2, LINE_DEFAULT);
   1309 		ENSURE_SIZE((*lines)->text, ptext, (*lines)->size, size, size,
   1310 			load_info_msgid_plural_alloc_error, uint32_t);
   1311 		U32_MEMCCPY((*lines)->text, old_pumsgid, len + 1, ptext);
   1312 		llen		 = u32_decode_tabs(state, (*lines)->text, size);
   1313 		(*lines)->length = llen;
   1314 		if (llen > state->info_maxlen)
   1315 			state->info_maxlen = llen;
   1316 		(*lines)++;
   1317 	}
   1318 	if (first->plural_forms)
   1319 	{
   1320 		ptext = u32_u8_memccpy((*lines)->text, "Plural forms:", 0,
   1321 			LINE_DEFAULT);
   1322 		(*lines)->length = llen
   1323 			= ptext ? ptext - (*lines)->text : LINE_DEFAULT - 1;
   1324 		(*lines)->size = LINE_DEFAULT;
   1325 		if (llen > state->info_maxlen)
   1326 			state->info_maxlen = llen;
   1327 		(*lines)->fg = colors[C_EDIT_MSGID_HEADING].fg;
   1328 		(*lines)->bg = colors[C_EDIT_MSGID_HEADING].bg;
   1329 		(*lines)++;
   1330 		U32_SAFE_COPY((*lines)->text, ptext, (*lines)->size,
   1331 			first->plural_forms, first->plural_forms_size,
   1332 			load_info_msgid_plural_alloc_error);
   1333 		(*lines)->length = llen
   1334 			= ptext ? ptext - (*lines)->text : (*lines)->size - 1;
   1335 		//(*lines)->size = MAX(llen + 1, LINE_DEFAULT);
   1336 		if (llen > state->info_maxlen)
   1337 			state->info_maxlen = llen;
   1338 		(*lines)++;
   1339 	}
   1340 	return 0;
   1341 
   1342 load_info_msgid_plural_alloc_error:
   1343 	state->error = ERR_CANT_ALLOC;
   1344 	return 1;
   1345 }
   1346 
   1347 static int
   1348 load_info_ref_comments(struct DrawState* state, struct BufferLine** lines)
   1349 {
   1350 	struct PoEntry* current = NULL;
   1351 	uint32_t* ptext		= NULL;
   1352 	char* ref_comment	= NULL;
   1353 	ssize_t len, llen, size, i;
   1354 
   1355 	current = &state->entries[state->msgid_number - 1];
   1356 	ptext = u32_u8_memccpy((*lines)->text, "References:", 0, LINE_DEFAULT);
   1357 	(*lines)->length = llen = ptext ? ptext - (*lines)->text : LINE_DEFAULT;
   1358 	(*lines)->size		= MAX(llen + 1, LINE_DEFAULT);
   1359 	if (llen > state->info_maxlen)
   1360 		state->info_maxlen = llen;
   1361 	(*lines)->fg = colors[C_EDIT_MSGID_HEADING].fg;
   1362 	(*lines)->bg = colors[C_EDIT_MSGID_HEADING].bg;
   1363 	(*lines)++;
   1364 	for (i = 0; i < current->ref_comment_lines; i++)
   1365 	{
   1366 		ref_comment = current->ref_comments[i];
   1367 		len	    = strlen(ref_comment);
   1368 		size	    = MAX(len + 2, LINE_DEFAULT); /* + NUL */
   1369 		ENSURE_SIZE((*lines)->text, ptext, (*lines)->size, size, size,
   1370 			load_info_ref_comments_alloc_error, uint32_t);
   1371 		U32_U8_MEMCCPY((*lines)->text, ref_comment, len + 1, ptext);
   1372 		llen = ptext ? ptext - (*lines)->text : LINE_DEFAULT;
   1373 		(*lines)->length = llen;
   1374 		if (llen > state->info_maxlen)
   1375 			state->info_maxlen = llen;
   1376 		(*lines)++;
   1377 	}
   1378 	return 0;
   1379 
   1380 load_info_ref_comments_alloc_error:
   1381 	state->error = ERR_CANT_ALLOC;
   1382 	return 1;
   1383 }
   1384 
   1385 static int
   1386 load_msgstr(struct DrawState* state)
   1387 {
   1388 	struct PoEntry* current;
   1389 	struct BufferLine* newbuf;
   1390 	struct BufferLine* pnewbuf;
   1391 	const uint32_t* old_pumsgstr = NULL;
   1392 	const uint32_t* pumsgstr     = NULL;
   1393 	uint32_t* newtext	     = NULL;
   1394 	uint32_t* temp		     = NULL;
   1395 	ssize_t num_lines, i, len, newlen, size, row;
   1396 	int msgstr_index;
   1397 
   1398 	assert((state != NULL) && (state->entries != NULL));
   1399 	current	     = &state->entries[state->msgid_number - 1];
   1400 	newbuf	     = NULL;
   1401 	pnewbuf	     = NULL;
   1402 	msgstr_index = state->msgstr_index;
   1403 	num_lines    = current->msgstr_count == 0
   1404 		   ? 1
   1405 		   : u32_lines_in_string(current->msgstr[msgstr_index], 0, 0);
   1406 
   1407 	for (row = 0; row < state->input_rows_count; row++)
   1408 		free_bufferline(&state->input_buffer[row]);
   1409 	free(state->input_buffer);
   1410 
   1411 	newbuf = calloc(num_lines, sizeof(struct BufferLine));
   1412 	if (!newbuf)
   1413 		goto load_msgstr_alloc_error;
   1414 
   1415 	pnewbuf = newbuf;
   1416 	for (i = 0; i < num_lines; i++)
   1417 	{
   1418 		init_bufferline(pnewbuf);
   1419 		assert(current->msgstr_size != NULL);
   1420 		size	= MAX(current->msgstr_size[msgstr_index], LINE_DEFAULT);
   1421 		newtext = calloc(size, sizeof(uint32_t));
   1422 		if (!newtext)
   1423 			goto load_msgstr_alloc_error;
   1424 		pnewbuf->text	= newtext;
   1425 		pnewbuf->size	= size;
   1426 		pnewbuf->length = 0;
   1427 		pnewbuf++;
   1428 	}
   1429 	state->input_rows_count = num_lines;
   1430 
   1431 	if (current->msgstr_count > 0)
   1432 	{
   1433 		pumsgstr = current->msgstr[msgstr_index];
   1434 		if (!pumsgstr)
   1435 		{
   1436 			newbuf[0].length = 0;
   1437 			goto load_msgstr_done;
   1438 		}
   1439 		pnewbuf = newbuf;
   1440 		while (*pumsgstr)
   1441 		{
   1442 			old_pumsgstr = pumsgstr;
   1443 			len  = u32_next_line(current->msgstr[msgstr_index],
   1444 				 &pumsgstr, 0, 0);
   1445 			size = MAX(len + 2, LINE_DEFAULT); /* + NUL */
   1446 			ENSURE_SIZE(pnewbuf->text, temp, pnewbuf->size, size,
   1447 				size, load_msgstr_alloc_error, uint32_t);
   1448 			U32_MEMCCPY(pnewbuf->text, old_pumsgstr, len + 1, temp);
   1449 			newlen = u32_decode_tabs(state, pnewbuf->text, len + 1);
   1450 			pnewbuf->length = newlen;
   1451 			pnewbuf++;
   1452 		}
   1453 	}
   1454 	else
   1455 		newbuf[0].length = 0;
   1456 
   1457 load_msgstr_done:
   1458 	state->input_buffer = newbuf;
   1459 	return 0;
   1460 
   1461 load_msgstr_alloc_error:
   1462 	state->error = ERR_CANT_ALLOC;
   1463 	return 1;
   1464 }
   1465 
   1466 static int
   1467 move_down(struct DrawState* state)
   1468 {
   1469 	assert(state != NULL);
   1470 	if (state->edit_info_focused)
   1471 	{
   1472 		if (state->info_first_shown.row + (EDIT_HEIGHT - 3) / 2
   1473 			< state->info_rows_count)
   1474 			state->info_first_shown.row++;
   1475 		return 0;
   1476 	}
   1477 	position_to(state, state->coord.row + 1, state->coord.col);
   1478 	return 0;
   1479 }
   1480 
   1481 static int
   1482 move_end(struct DrawState* state)
   1483 {
   1484 	assert((state != NULL) && (state->input_buffer != NULL));
   1485 	if (state->edit_info_focused)
   1486 	{
   1487 		if (state->info_maxlen < state->edit_width - 3)
   1488 			state->info_first_shown.col = 0;
   1489 		else
   1490 			state->info_first_shown.col
   1491 				= state->info_maxlen - state->edit_width + 3;
   1492 		return 0;
   1493 	}
   1494 	position_to(state, state->coord.row,
   1495 		state->input_buffer[state->coord.row].length);
   1496 	return 0;
   1497 }
   1498 
   1499 static int
   1500 move_left(struct DrawState* state)
   1501 {
   1502 	assert((state != NULL) && (state->input_buffer != NULL));
   1503 	if (state->edit_info_focused)
   1504 	{
   1505 		if (state->info_first_shown.col > 0)
   1506 			state->info_first_shown.col--;
   1507 		return 0;
   1508 	}
   1509 	position_to(state, state->coord.row, state->coord.col - 1);
   1510 	return 0;
   1511 }
   1512 
   1513 static int
   1514 move_list_down(struct DrawState* state)
   1515 {
   1516 	assert(state != NULL);
   1517 	goto_msgid(state, next_msgid(state, 1));
   1518 	return 0;
   1519 }
   1520 
   1521 static int
   1522 move_list_end(struct DrawState* state)
   1523 {
   1524 	assert(state != NULL);
   1525 	goto_msgid(state,
   1526 		next_msgid(state,
   1527 			state->real_msgid_count - state->msgid_number));
   1528 	return 0;
   1529 }
   1530 
   1531 static int
   1532 move_list_page_down(struct DrawState* state)
   1533 {
   1534 	assert(state != NULL);
   1535 	state->error = ERR_NONE;
   1536 	goto_msgid(state, next_msgid(state, state->maxy - 2));
   1537 	return 0;
   1538 }
   1539 
   1540 static int
   1541 move_list_page_up(struct DrawState* state)
   1542 {
   1543 	assert(state != NULL);
   1544 	state->error = ERR_NONE;
   1545 	goto_msgid(state, prev_msgid(state, state->maxy - 2));
   1546 	return 0;
   1547 }
   1548 
   1549 static int
   1550 move_list_start(struct DrawState* state)
   1551 {
   1552 	assert(state != NULL);
   1553 	goto_msgid(state, 1);
   1554 	return 0;
   1555 }
   1556 
   1557 static int
   1558 move_list_up(struct DrawState* state)
   1559 {
   1560 	assert(state != NULL);
   1561 	goto_msgid(state, prev_msgid(state, 1));
   1562 	return 0;
   1563 }
   1564 
   1565 static int
   1566 move_next_word(struct DrawState* state)
   1567 {
   1568 	uint32_t* pline;
   1569 	ssize_t len;
   1570 	int i;
   1571 
   1572 	assert(state != NULL);
   1573 	if (state->edit_info_focused)
   1574 	{
   1575 		state->error = ERR_DLG_FOCUS_EDIT;
   1576 		return 1;
   1577 	}
   1578 
   1579 	i     = state->coord.col;
   1580 	pline = state->input_buffer[state->coord.row].text;
   1581 	len   = state->input_buffer[state->coord.row].length;
   1582 
   1583 	while (i < len && !u32_is_word_boundary(pline[i], 0))
   1584 		i++;
   1585 	while (i < len && u32_is_word_boundary(pline[i], 0))
   1586 		i++;
   1587 
   1588 	position_to(state, state->coord.row, i);
   1589 	return 0;
   1590 }
   1591 
   1592 static int
   1593 move_prev_word(struct DrawState* state)
   1594 {
   1595 	uint32_t* pline;
   1596 	int i;
   1597 
   1598 	assert((state != NULL) && (state->input_buffer != NULL));
   1599 	if (state->edit_info_focused)
   1600 	{
   1601 		state->error = ERR_DLG_FOCUS_EDIT;
   1602 		return 1;
   1603 	}
   1604 
   1605 	i     = state->coord.col;
   1606 	pline = state->input_buffer[state->coord.row].text;
   1607 
   1608 	if (i > 0 && u32_is_word_boundary(pline[i - 1], 0))
   1609 		i--;
   1610 	while (i > 0 && u32_is_word_boundary(pline[i], 0))
   1611 		i--;
   1612 	while (i > 0 && !u32_is_word_boundary(pline[i], 0)
   1613 		&& !u32_is_word_boundary(pline[i - 1], 0))
   1614 		i--;
   1615 
   1616 	position_to(state, state->coord.row, i);
   1617 	return 0;
   1618 }
   1619 
   1620 static int
   1621 move_right(struct DrawState* state)
   1622 {
   1623 	assert(state != NULL);
   1624 	if (state->edit_info_focused)
   1625 	{
   1626 		if (state->info_first_shown.col + state->edit_width - 3
   1627 			< state->info_maxlen + 1)
   1628 			state->info_first_shown.col++;
   1629 		return 0;
   1630 	}
   1631 	position_to(state, state->coord.row, state->coord.col + 1);
   1632 	return 0;
   1633 }
   1634 
   1635 static int
   1636 move_search_end(struct DrawState* state)
   1637 {
   1638 	assert(state != NULL);
   1639 	search_position_to(state, u32_strlen(state->search));
   1640 	return 0;
   1641 }
   1642 
   1643 static int
   1644 move_search_left(struct DrawState* state)
   1645 {
   1646 	assert((state != NULL) && (state->search != NULL));
   1647 	if (state->search_column == 0)
   1648 		return 1;
   1649 	search_position_to(state, state->search_column - 1);
   1650 	return 0;
   1651 }
   1652 
   1653 static int
   1654 move_search_next_word(struct DrawState* state)
   1655 {
   1656 	uint32_t* pline = NULL;
   1657 	ssize_t len;
   1658 	int i;
   1659 
   1660 	assert((state != NULL) && (state->search != NULL));
   1661 	i     = state->search_column;
   1662 	pline = state->search;
   1663 	len   = u32_strlen(state->search);
   1664 	while (i < len && !u32_is_word_boundary(pline[i], 0))
   1665 		i++;
   1666 	while (i < len && u32_is_word_boundary(pline[i], 0))
   1667 		i++;
   1668 	search_position_to(state, i);
   1669 	return 0;
   1670 }
   1671 
   1672 static int
   1673 move_search_prev_word(struct DrawState* state)
   1674 {
   1675 	uint32_t* pline = NULL;
   1676 	int i;
   1677 
   1678 	assert((state != NULL) && (state->search != NULL));
   1679 	i     = state->search_column;
   1680 	pline = state->search;
   1681 	if (i > 0 && u32_is_word_boundary(pline[i - 1], 0))
   1682 		i--;
   1683 	while (i > 0 && u32_is_word_boundary(pline[i], 0))
   1684 		i--;
   1685 	while (i > 0 && !u32_is_word_boundary(pline[i], 0)
   1686 		&& !u32_is_word_boundary(pline[i - 1], 0))
   1687 		i--;
   1688 	search_position_to(state, i);
   1689 	return 0;
   1690 }
   1691 
   1692 static int
   1693 move_search_right(struct DrawState* state)
   1694 {
   1695 	assert(state != NULL);
   1696 	search_position_to(state, state->search_display_column + 1);
   1697 	return 0;
   1698 }
   1699 
   1700 static int
   1701 move_search_start(struct DrawState* state)
   1702 {
   1703 	assert(state != NULL);
   1704 	search_position_to(state, 0);
   1705 	return 0;
   1706 }
   1707 
   1708 static int
   1709 move_start(struct DrawState* state)
   1710 {
   1711 	assert(state != NULL);
   1712 	if (state->edit_info_focused)
   1713 	{
   1714 		state->info_first_shown.row = 0;
   1715 		state->info_first_shown.col = 0;
   1716 		return 0;
   1717 	}
   1718 	position_to(state, state->coord.row, 0);
   1719 	return 0;
   1720 }
   1721 
   1722 static int
   1723 move_up(struct DrawState* state)
   1724 {
   1725 	assert(state != NULL);
   1726 	if (state->edit_info_focused)
   1727 	{
   1728 		if (state->info_first_shown.row > 0)
   1729 			state->info_first_shown.row--;
   1730 		return 0;
   1731 	}
   1732 	position_to(state, state->coord.row > 1 ? state->coord.row - 1 : 0,
   1733 		state->coord.col);
   1734 	return 0;
   1735 }
   1736 
   1737 static int
   1738 next_fuzzy(struct DrawState* state)
   1739 {
   1740 	struct PoEntry* current = NULL;
   1741 	char* temp		= NULL;
   1742 	ssize_t e;
   1743 
   1744 	assert(state != NULL);
   1745 	if (state->msgid_count == 0)
   1746 		return 1;
   1747 
   1748 	if (state->edit_dirty)
   1749 	{
   1750 		state->in_prompt = 1;
   1751 		MEMCCPY(state->prompt, prompt_edit_dirty, LINE_DEFAULT, temp);
   1752 		state->prompt_callback = next_fuzzy_callback;
   1753 		return 0;
   1754 	}
   1755 
   1756 	for (e = state->msgid_number; e < state->msgid_count; e++)
   1757 	{
   1758 		current = &state->entries[e];
   1759 		if (current->flags & FL_FUZZY)
   1760 		{
   1761 			goto_msgid(state, e + 1);
   1762 			if (state->show_edit)
   1763 			{
   1764 				state->msgstr_index = 0;
   1765 				show_edit(state);
   1766 			}
   1767 			return 0;
   1768 		}
   1769 	}
   1770 	return 0;
   1771 }
   1772 
   1773 static int
   1774 next_fuzzy_callback(struct DrawState* state, struct tb_event* ev)
   1775 {
   1776 	assert((state != NULL) && (ev != NULL));
   1777 	if (strchr("Yy", ev->ch))
   1778 	{
   1779 		state->reshow_edit = 1;
   1780 		save_msgstr(state);
   1781 	}
   1782 	else
   1783 		state->edit_dirty = 0;
   1784 	return next_fuzzy(state);
   1785 }
   1786 
   1787 static int
   1788 next_match(struct DrawState* state)
   1789 {
   1790 	struct PoEntry* current = NULL;
   1791 	ssize_t e;
   1792 
   1793 	assert(state != NULL);
   1794 	if (state->msgid_count == 0 || !state->search || !*state->search)
   1795 		return 1;
   1796 
   1797 	for (e = state->msgid_number; e < state->real_msgid_count; e++)
   1798 	{
   1799 		current = state->entries + e;
   1800 		if (current->obsolete)
   1801 			continue;
   1802 		if ((current->msgid && *current->msgid
   1803 			    && u32_strstr(current->msgid, state->search))
   1804 			|| (current->msgstr && *current->msgstr
   1805 				&& **current->msgstr
   1806 				&& u32_strstr(*current->msgstr, state->search)))
   1807 		{
   1808 			goto_msgid(state, e + 1);
   1809 			return 0;
   1810 		}
   1811 	}
   1812 	return 0;
   1813 }
   1814 
   1815 static int
   1816 next_plural(struct DrawState* state)
   1817 {
   1818 	struct PoEntry* current = NULL;
   1819 	char* temp		= NULL;
   1820 	assert((state != NULL) && (state->entries != NULL));
   1821 	if (state->edit_info_focused)
   1822 	{
   1823 		state->error = ERR_DLG_FOCUS_EDIT;
   1824 		return 1;
   1825 	}
   1826 	if (state->edit_dirty)
   1827 	{
   1828 		state->in_prompt = 1;
   1829 		MEMCCPY(state->prompt, prompt_edit_dirty, LINE_DEFAULT, temp);
   1830 		state->prompt_callback = next_plural_callback;
   1831 		return 0;
   1832 	}
   1833 	current = &state->entries[state->msgid_number - 1];
   1834 	if (state->msgstr_index + 1 < current->msgstr_count)
   1835 		state->msgstr_index++;
   1836 	show_edit(state);
   1837 	return 0;
   1838 }
   1839 
   1840 static int
   1841 next_plural_callback(struct DrawState* state, struct tb_event* ev)
   1842 {
   1843 	assert((state != NULL) && (ev != NULL));
   1844 	if (strchr("Yy", ev->ch))
   1845 	{
   1846 		state->reshow_edit = 1;
   1847 		save_msgstr(state);
   1848 	}
   1849 	else
   1850 		state->edit_dirty = 0;
   1851 	return next_plural(state);
   1852 }
   1853 
   1854 static int
   1855 next_untranslated(struct DrawState* state)
   1856 {
   1857 	struct PoEntry* current = NULL;
   1858 	char* temp		= NULL;
   1859 	ssize_t e;
   1860 
   1861 	assert((state != NULL) && (state->entries != NULL));
   1862 	if (state->msgid_count == 0)
   1863 		return 1;
   1864 
   1865 	if (state->edit_dirty)
   1866 	{
   1867 		state->in_prompt = 1;
   1868 		MEMCCPY(state->prompt, prompt_edit_dirty, LINE_DEFAULT, temp);
   1869 		state->prompt_callback = next_untranslated_callback;
   1870 		return 0;
   1871 	}
   1872 
   1873 	for (e = state->msgid_number; e < state->msgid_count; e++)
   1874 	{
   1875 		current = state->entries + e;
   1876 		if (!current->msgstr || !*current->msgstr
   1877 			|| u32_strlen(*current->msgstr) == 0)
   1878 		{
   1879 			goto_msgid(state, e + 1);
   1880 			if (state->show_edit)
   1881 			{
   1882 				state->msgstr_index = 0;
   1883 				show_edit(state);
   1884 			}
   1885 			return 0;
   1886 		}
   1887 	}
   1888 	return 0;
   1889 }
   1890 
   1891 static int
   1892 next_untranslated_callback(struct DrawState* state, struct tb_event* ev)
   1893 {
   1894 	assert((state != NULL) && (ev != NULL));
   1895 	if (strchr("Yy", ev->ch))
   1896 	{
   1897 		state->reshow_edit = 1;
   1898 		save_msgstr(state);
   1899 	}
   1900 	else
   1901 		state->edit_dirty = 0;
   1902 	return next_untranslated(state);
   1903 }
   1904 
   1905 static size_t
   1906 next_msgid(struct DrawState* state, const int delta)
   1907 {
   1908 	size_t ret, start, last_non_obsolete;
   1909 	assert((state != NULL) && (state->entries != NULL));
   1910 	ret   = state->msgid_number;
   1911 	start = ret;
   1912 	last_non_obsolete
   1913 		= state->real_msgid_count - state->obsolete_at_end_count;
   1914 	while (ret < last_non_obsolete + 1)
   1915 	{
   1916 		ret++;
   1917 		if (!state->entries[ret - 1].obsolete && ret >= start + delta)
   1918 			break;
   1919 	}
   1920 	return ret == last_non_obsolete + 1 ? last_non_obsolete : ret;
   1921 }
   1922 
   1923 static int
   1924 overwrite_copy_msgid_callback(struct DrawState* state, struct tb_event* ev)
   1925 {
   1926 	assert((state != NULL) && (ev != NULL));
   1927 	if (ev->ch == 'y')
   1928 		copy_msgid_to_input(state);
   1929 	state->prompt_callback = NULL;
   1930 	return 0;
   1931 }
   1932 
   1933 static int
   1934 overwrite_paste_callback(struct DrawState* state, struct tb_event* ev)
   1935 {
   1936 	assert((state != NULL) && (ev != NULL));
   1937 	if (ev->ch == 'y')
   1938 		paste_paste_buffer_to_input(state);
   1939 	state->prompt_callback = NULL;
   1940 	return 0;
   1941 }
   1942 
   1943 static int
   1944 paste_paste_buffer_to_input(struct DrawState* state)
   1945 {
   1946 	struct BufferLine* pinput = NULL;
   1947 	struct BufferLine* newbuf = NULL;
   1948 	struct BufferLine* ppaste = NULL;
   1949 	uint32_t* newtext	  = NULL;
   1950 	uint32_t* utemp		  = NULL;
   1951 	char* temp		  = NULL;
   1952 	ssize_t lines_to_init	  = 0;
   1953 	ssize_t lines_to_free	  = 0;
   1954 	ssize_t i;
   1955 
   1956 	assert(state != NULL);
   1957 	if (state->edit_info_focused)
   1958 	{
   1959 		state->error = ERR_DLG_FOCUS_EDIT;
   1960 		return 1;
   1961 	}
   1962 
   1963 	assert(state->input_buffer != NULL);
   1964 	if (!state->in_prompt && *state->input_buffer->text)
   1965 	{
   1966 		state->in_prompt = 1;
   1967 		MEMCCPY(state->prompt, prompt_overwrite, LINE_DEFAULT, temp);
   1968 		state->prompt_callback = overwrite_paste_callback;
   1969 		return 0;
   1970 	}
   1971 
   1972 	if (state->msgid_number == 1)
   1973 	{
   1974 		state->error = ERR_ILLEGAL_ON_FIRST;
   1975 		return 1;
   1976 	}
   1977 
   1978 	if (!state->paste || !(*state->paste).text || !*(*state->paste).text)
   1979 	{
   1980 		/* leave one row */
   1981 		for (i = 1; i < state->input_rows_count; i++)
   1982 			free_bufferline(state->input_buffer + i);
   1983 		state->input_buffer = realloc(state->input_buffer,
   1984 			sizeof(struct BufferLine));
   1985 		if (!state->input_buffer)
   1986 			goto paste_paste_buffer_alloc_error;
   1987 		*(state->input_buffer->text) = 0;
   1988 		init_coord(&state->coord, 0, 0);
   1989 		state->input_display_column = 0;
   1990 		init_coord(&state->input_first_shown, 0, 0);
   1991 		state->input_rows_count = 1;
   1992 		state->edit_dirty	= 1;
   1993 		return 0;
   1994 	}
   1995 
   1996 	lines_to_init = state->input_rows_count < state->paste_count
   1997 		? state->paste_count - state->input_rows_count
   1998 		: 0;
   1999 	lines_to_free = state->input_rows_count > state->paste_count
   2000 		? state->input_rows_count - state->paste_count
   2001 		: 0;
   2002 
   2003 	for (i = 0; i < lines_to_free; i++)
   2004 		free_bufferline(
   2005 			state->input_buffer + state->input_rows_count - i - 1);
   2006 	newbuf = realloc(state->input_buffer,
   2007 		state->paste_count * sizeof(struct BufferLine));
   2008 	ppaste = state->paste;
   2009 
   2010 	if (!newbuf)
   2011 		goto paste_paste_buffer_alloc_error;
   2012 
   2013 	for (i = 0; i < lines_to_init; i++)
   2014 	{
   2015 		pinput = newbuf + state->paste_count - 1 - i;
   2016 		init_bufferline(pinput);
   2017 		newtext = calloc(LINE_DEFAULT, sizeof(uint32_t));
   2018 		if (!newtext)
   2019 			goto paste_paste_buffer_alloc_error;
   2020 		pinput->text = newtext;
   2021 	}
   2022 
   2023 	if (ppaste)
   2024 		for (i = 0; i < state->paste_count; i++)
   2025 		{
   2026 			pinput = newbuf + i;
   2027 			U32_MEMCCPY(pinput->text, ppaste->text,
   2028 				MIN(ppaste->length + 1, LINE_DEFAULT), utemp);
   2029 			pinput->length = ppaste->length;
   2030 			ppaste++;
   2031 		}
   2032 
   2033 	state->input_rows_count = state->paste_count;
   2034 	state->input_buffer	= newbuf;
   2035 	state->edit_dirty	= 1;
   2036 	return 0;
   2037 
   2038 paste_paste_buffer_alloc_error:
   2039 	state->error = ERR_CANT_ALLOC;
   2040 	return 1;
   2041 }
   2042 
   2043 /*
   2044  * state->saved_column - display coordinate
   2045  * row, column - buffer coordinates (not display)
   2046  */
   2047 static void
   2048 position_to(struct DrawState* state, const int row, const int column)
   2049 {
   2050 	ssize_t dlen;
   2051 
   2052 	assert((state != NULL) && (state->input_buffer != NULL));
   2053 	if (row != state->coord.row)
   2054 	{
   2055 		state->coord.row = row;
   2056 		if ((state->coord.row + 1) > state->input_rows_count)
   2057 			state->coord.row = state->input_rows_count - 1;
   2058 		if (state->coord.row < state->input_first_shown.row)
   2059 			state->input_first_shown.row = state->coord.row;
   2060 		if (state->input_first_shown.row + (EDIT_HEIGHT - 3) / 2
   2061 			< state->coord.row)
   2062 			state->input_first_shown.row
   2063 				= state->coord.row - (EDIT_HEIGHT - 3) / 2;
   2064 		if (state->saved_column == -1)
   2065 		{
   2066 			state->saved_column = state->input_display_column;
   2067 			state->coord.col    = column;
   2068 		}
   2069 		else
   2070 			state->coord.col = buffer_length(
   2071 				state->input_buffer[state->coord.row].text,
   2072 				state->saved_column);
   2073 		if (state->coord.col < 0)
   2074 			state->coord.col = 0;
   2075 		else if (state->coord.col
   2076 			> (int)state->input_buffer[state->coord.row].length)
   2077 			state->coord.col
   2078 				= state->input_buffer[state->coord.row].length;
   2079 		dlen = display_length(state->input_buffer[state->coord.row].text,
   2080 			state->coord.col);
   2081 		if (dlen < state->saved_column)
   2082 			state->input_display_column = dlen;
   2083 		else
   2084 			state->input_display_column = state->saved_column;
   2085 	}
   2086 	else
   2087 	{
   2088 		state->coord.col = column;
   2089 		if (state->coord.col < 0)
   2090 		{
   2091 			if (state->coord.row > 0)
   2092 			{
   2093 				state->coord.row--;
   2094 				if (state->input_first_shown.row > 0
   2095 					&& state->input_first_shown.row
   2096 						> state->coord.row)
   2097 					state->input_first_shown.row--;
   2098 				state->coord.col
   2099 					= state->input_buffer[state->coord.row]
   2100 						  .length;
   2101 			}
   2102 			else
   2103 				state->coord.col = 0;
   2104 		}
   2105 		else if (state->coord.col
   2106 			> state->input_buffer[state->coord.row].length)
   2107 		{
   2108 			if ((state->coord.row + 1) < state->input_rows_count)
   2109 			{
   2110 				state->coord.row++;
   2111 				if (state->input_first_shown.row
   2112 							+ (state->maxy - 2)
   2113 						< state->coord.row
   2114 					&& state->input_first_shown.row + 1
   2115 						< state->input_rows_count)
   2116 					state->input_first_shown.row++;
   2117 				state->coord.col = 0;
   2118 			}
   2119 			else
   2120 				state->coord.col
   2121 					= state->input_buffer[state->coord.row]
   2122 						  .length;
   2123 		}
   2124 		state->input_display_column
   2125 			= display_length(state->input_buffer[state->coord.row]
   2126 						 .text,
   2127 				state->coord.col);
   2128 		state->saved_column = state->input_display_column;
   2129 	}
   2130 	if (state->input_first_shown.col > state->input_display_column)
   2131 		state->input_first_shown.col = state->input_display_column;
   2132 	if (state->input_first_shown.col + state->edit_width - 4
   2133 		< state->input_display_column)
   2134 	{
   2135 		int index_first;
   2136 		int fsc = state->input_first_shown.col;
   2137 		index_first
   2138 			= buffer_length(state->input_buffer[state->coord.row]
   2139 						.text,
   2140 				fsc);
   2141 		while (fsc + state->edit_width < state->input_display_column + 4)
   2142 		{
   2143 			index_first++;
   2144 			fsc = display_length(state->input_buffer[state->coord.row]
   2145 						     .text,
   2146 				index_first);
   2147 		}
   2148 		state->input_first_shown.col = fsc;
   2149 	}
   2150 }
   2151 
   2152 static int
   2153 prev_match(struct DrawState* state)
   2154 {
   2155 	struct PoEntry* current = NULL;
   2156 	ssize_t e;
   2157 
   2158 	assert(state != NULL);
   2159 	if (state->msgid_count == 0 || !state->search || !*state->search)
   2160 		return 1;
   2161 	for (e = state->msgid_number - 1; e > 0; e--)
   2162 	{
   2163 		current = state->entries + e - 1;
   2164 		if ((current->msgid && *current->msgid
   2165 			    && u32_strstr(current->msgid, state->search))
   2166 			|| (current->msgstr && *current->msgstr
   2167 				&& **current->msgstr
   2168 				&& u32_strstr(*current->msgstr, state->search)))
   2169 		{
   2170 			goto_msgid(state, e);
   2171 			return 0;
   2172 		}
   2173 	}
   2174 	return 0;
   2175 }
   2176 
   2177 static size_t
   2178 prev_msgid(struct DrawState* state, const int delta)
   2179 {
   2180 	ssize_t ret   = state->msgid_number;
   2181 	ssize_t start = ret;
   2182 
   2183 	assert((state != NULL) && (state->entries != NULL));
   2184 	if (start < delta)
   2185 		return state->real_msgid_count ? 1 : 0;
   2186 	while (ret > 1)
   2187 	{
   2188 		ret--;
   2189 		if (!state->entries[ret - 1].obsolete && ret <= start - delta)
   2190 			break;
   2191 	}
   2192 	return ret;
   2193 }
   2194 
   2195 static int
   2196 prev_plural(struct DrawState* state)
   2197 {
   2198 	char* temp = NULL;
   2199 	assert(state != NULL);
   2200 	if (state->edit_info_focused)
   2201 	{
   2202 		state->error = ERR_DLG_FOCUS_EDIT;
   2203 		return 1;
   2204 	}
   2205 	if (state->edit_dirty)
   2206 	{
   2207 		state->in_prompt = 1;
   2208 		MEMCCPY(state->prompt, prompt_edit_dirty, LINE_DEFAULT, temp);
   2209 		state->prompt_callback = prev_plural_callback;
   2210 		return 0;
   2211 	}
   2212 	if (state->msgstr_index > 0)
   2213 		state->msgstr_index--;
   2214 	show_edit(state);
   2215 	return 0;
   2216 }
   2217 
   2218 static int
   2219 prev_plural_callback(struct DrawState* state, struct tb_event* ev)
   2220 {
   2221 	assert((state != NULL) && (ev != NULL));
   2222 	if (strchr("Yy", ev->ch))
   2223 	{
   2224 		state->reshow_edit = 1;
   2225 		save_msgstr(state);
   2226 	}
   2227 	else
   2228 		state->edit_dirty = 0;
   2229 	return prev_plural(state);
   2230 }
   2231 
   2232 int
   2233 print_error(const int code, const char* msg, ...)
   2234 {
   2235 	char* buf = NULL;
   2236 	va_list args;
   2237 	long bufsize;
   2238 	assert((msg != NULL) && (limits != NULL));
   2239 	bufsize = limits[L_LINE_MAX] ? limits[L_LINE_MAX]
   2240 				     : PRINT_BUF_MAX_FALLBACK;
   2241 	buf	= malloc(bufsize);
   2242 	if (!buf)
   2243 		return ERR_CANT_ALLOC;
   2244 	va_start(args, msg);
   2245 	if (vsnprintf(buf, bufsize, msg, args) >= bufsize)
   2246 		buf[bufsize - 1] = 0;
   2247 	va_end(args);
   2248 	fprintf(stderr, "%s: %s\n", PROGRAM_NAME, buf);
   2249 	free(buf);
   2250 	return code;
   2251 }
   2252 
   2253 static int
   2254 quit_callback(struct DrawState* state, struct tb_event* ev)
   2255 {
   2256 	int result = 0;
   2257 	assert((state != NULL) && (ev != NULL));
   2258 	if (strchr("Yy", ev->ch))
   2259 	{
   2260 		state->reshow_edit = 0;
   2261 		if (state->edit_dirty)
   2262 			save_msgstr(state);
   2263 		result = write_file(state);
   2264 	}
   2265 	state->prompt_callback = NULL;
   2266 	state->running	       = 0;
   2267 	return result;
   2268 }
   2269 
   2270 int
   2271 redraw_status(struct DrawState* state)
   2272 {
   2273 	int result = 0;
   2274 	assert(state != NULL);
   2275 	if (state->recalculate_size)
   2276 	{
   2277 		recalculate_size(state);
   2278 		state->recalculate_size = 0;
   2279 	}
   2280 	result = draw_status(state);
   2281 	tb_present();
   2282 	return result;
   2283 }
   2284 
   2285 static int
   2286 save_msgstr(struct DrawState* state)
   2287 {
   2288 	struct BufferLine* pinput_buffer;
   2289 	struct PoEntry* current = NULL;
   2290 	uint32_t* to_save	= NULL;
   2291 	uint32_t* temp		= NULL;
   2292 	uint32_t* temp2		= NULL;
   2293 	size_t to_save_size	= 0;
   2294 	ssize_t i;
   2295 
   2296 	assert(state != NULL);
   2297 	if (state->reshow_edit)
   2298 		state->reshow_edit = 0;
   2299 	else
   2300 		state->show_edit = 0;
   2301 	state->edit_info_focused = 0;
   2302 
   2303 	if (state->input_buffer)
   2304 	{
   2305 		to_save_size = state->input_rows_count * MAXMSGLINE;
   2306 		to_save	     = calloc(to_save_size, sizeof(uint32_t));
   2307 		if (!to_save)
   2308 		{
   2309 			state->error = ERR_CANT_ALLOC;
   2310 			return 1;
   2311 		}
   2312 		pinput_buffer = NULL;
   2313 		for (i = 0; i < state->input_rows_count; i++)
   2314 		{
   2315 			pinput_buffer = &state->input_buffer[i];
   2316 			if (i == 0)
   2317 				U32_MEMCCPY(to_save, pinput_buffer->text,
   2318 					to_save_size * sizeof(uint32_t), temp);
   2319 			else if (temp)
   2320 			{
   2321 				U32_U8_MEMCCPY((temp - 1), "\\n",
   2322 					to_save_size * sizeof(uint32_t), temp2);
   2323 				if (temp2)
   2324 					U32_MEMCCPY((temp2 - 1),
   2325 						pinput_buffer->text,
   2326 						to_save_size * sizeof(uint32_t),
   2327 						temp);
   2328 			}
   2329 			free_bufferline(pinput_buffer);
   2330 		}
   2331 	}
   2332 	free(state->input_buffer);
   2333 	state->input_buffer	= NULL;
   2334 	state->input_rows_count = 0;
   2335 	assert(state->info_buffer != NULL);
   2336 	for (i = 0; i < state->info_rows_count; i++)
   2337 		free_bufferline(&state->info_buffer[i]);
   2338 	free(state->info_buffer);
   2339 	state->dirty	       = 1;
   2340 	state->edit_dirty      = 0;
   2341 	state->info_rows_count = 0;
   2342 	state->info_buffer     = NULL;
   2343 	if (u32_encode_tabs(state, to_save, to_save_size * sizeof(uint32_t)) < 0)
   2344 		goto save_msgstr_error;
   2345 	current = &state->entries[state->msgid_number - 1];
   2346 	if (u32_match_msgid_ending(state, current->msgid, to_save,
   2347 		    to_save_size * sizeof(uint32_t))
   2348 		< 0)
   2349 		goto save_msgstr_error;
   2350 	if (*current->msgstr[state->msgstr_index] && (!to_save || !*to_save))
   2351 		state->untranslated_count++;
   2352 	else if (!*current->msgstr[state->msgstr_index] && to_save && *to_save)
   2353 		state->untranslated_count--;
   2354 
   2355 	u32_set_msgstr(state, current, to_save, state->msgstr_index, 0);
   2356 	update_warning(current->msgid, to_save, &current->warning);
   2357 	state->input_changed_fuzzy = UNCHANGED;
   2358 
   2359 save_msgstr_error:
   2360 	if (state->error == ERR_CANT_ALLOC)
   2361 		goto save_msgstr_alloc_error;
   2362 
   2363 	tb_hide_cursor();
   2364 	free(to_save);
   2365 	return !!state->error;
   2366 
   2367 save_msgstr_alloc_error:
   2368 	return 1;
   2369 }
   2370 
   2371 static int
   2372 search_position_to(struct DrawState* state, const int column)
   2373 {
   2374 	ssize_t len;
   2375 
   2376 	assert((state != NULL) && (state->search != NULL));
   2377 	len		     = u32_strlen(state->search);
   2378 	state->search_column = column;
   2379 
   2380 	if (state->search_column > len)
   2381 		state->search_column = len;
   2382 	state->search_display_column
   2383 		= display_length(state->search, state->search_column);
   2384 	if (state->search_first_shown_column > state->search_display_column)
   2385 		state->search_first_shown_column = state->search_display_column;
   2386 	if (state->search_first_shown_column + (state->search_width - 4)
   2387 		< state->search_column)
   2388 		state->search_first_shown_column = state->search_display_column
   2389 			- (state->search_width - 4);
   2390 	return 0;
   2391 }
   2392 
   2393 static int
   2394 show_edit(struct DrawState* state)
   2395 {
   2396 	assert((state != NULL) && (state->entries != NULL));
   2397 	if (state->msgstr_index + 1
   2398 		> state->entries[state->msgid_number - 1].msgstr_count)
   2399 		state->msgstr_index = 0;
   2400 
   2401 	if (load_msgstr(state) || load_info(state))
   2402 	{
   2403 		state->running = 0;
   2404 		return 1;
   2405 	}
   2406 
   2407 	state->fuzzy_before_edit   = state->fuzzy_count;
   2408 	state->input_changed_fuzzy = UNCHANGED;
   2409 	state->show_edit	   = 1;
   2410 	state->coord.row	   = 0;
   2411 	init_coord(&state->input_first_shown, 0, 0);
   2412 	init_coord(&state->info_first_shown, 0, 0);
   2413 	move_start(state);
   2414 	state->error = ERR_NONE;
   2415 	return 0;
   2416 }
   2417 
   2418 static int
   2419 show_help(struct DrawState* state)
   2420 {
   2421 	assert(state != NULL);
   2422 	state->show_help = 1;
   2423 	state->error	 = ERR_NONE;
   2424 	return 0;
   2425 }
   2426 
   2427 static int
   2428 show_search(struct DrawState* state)
   2429 {
   2430 	assert(state != NULL);
   2431 	state->show_search		 = 1;
   2432 	state->search_column		 = 0;
   2433 	state->search_display_column	 = 0;
   2434 	state->search_first_shown_column = 0;
   2435 	if (!state->search)
   2436 		state->search = calloc(MAXSEARCH, sizeof(uint32_t));
   2437 	*state->search = 0;
   2438 	tb_set_cursor((state->maxx - state->search_width + 2) / 2,
   2439 		state->maxy - 1 - SEARCH_HEIGHT + 1);
   2440 	return 0;
   2441 }
   2442 
   2443 static int
   2444 toggle_fuzzy(struct DrawState* state)
   2445 {
   2446 	assert((state != NULL) && (state->entries != NULL));
   2447 	if (state->msgid_number == 1)
   2448 	{
   2449 		state->error = ERR_ILLEGAL_ON_FIRST;
   2450 		return 0;
   2451 	}
   2452 	if (state->msgid_count > 0)
   2453 	{
   2454 		state->entries[state->msgid_number - 1].flags ^= FL_FUZZY;
   2455 		if (state->entries[state->msgid_number - 1].flags & FL_FUZZY)
   2456 		{
   2457 			state->fuzzy_count++;
   2458 			if (state->input_changed_fuzzy == CLEARED)
   2459 				state->input_changed_fuzzy = UNCHANGED;
   2460 			else
   2461 				state->input_changed_fuzzy = SET;
   2462 		}
   2463 		else
   2464 		{
   2465 			state->fuzzy_count--;
   2466 			if (state->input_changed_fuzzy == SET)
   2467 				state->input_changed_fuzzy = UNCHANGED;
   2468 			else
   2469 				state->input_changed_fuzzy = CLEARED;
   2470 		}
   2471 		state->edit_dirty = 1;
   2472 	}
   2473 	return 0;
   2474 }
   2475 
   2476 static int
   2477 toggle_info_focused(struct DrawState* state)
   2478 {
   2479 	assert(state != NULL);
   2480 	state->edit_info_focused = !state->edit_info_focused;
   2481 	return 0;
   2482 }
   2483 
   2484 static int
   2485 yank_input_to_paste_buffer(struct DrawState* state)
   2486 {
   2487 	struct BufferLine* ppaste = NULL;
   2488 	struct BufferLine* newbuf = NULL;
   2489 	struct BufferLine* pinput = NULL;
   2490 	uint32_t* newtext	  = NULL;
   2491 	uint32_t* temp		  = NULL;
   2492 	ssize_t lines_to_init	  = 0;
   2493 	ssize_t lines_to_free	  = 0;
   2494 	ssize_t i;
   2495 
   2496 	assert(state != NULL);
   2497 	if (state->edit_info_focused)
   2498 		return 1;
   2499 
   2500 	if (state->msgid_number == 1)
   2501 	{
   2502 		state->error = ERR_ILLEGAL_ON_FIRST;
   2503 		return 0;
   2504 	}
   2505 
   2506 	if (!state->input_buffer || !(*state->input_buffer).text
   2507 		|| !*(*state->input_buffer).text)
   2508 	{
   2509 		for (i = 0; i < state->paste_count; i++)
   2510 			free_bufferline(state->paste + i);
   2511 		free(state->paste);
   2512 		state->paste	   = NULL;
   2513 		state->paste_count = 0;
   2514 		return 0;
   2515 	}
   2516 
   2517 	lines_to_init = state->paste_count < state->input_rows_count
   2518 		? state->input_rows_count - state->paste_count
   2519 		: 0;
   2520 	lines_to_free = state->paste_count > state->input_rows_count
   2521 		? state->paste_count - state->input_rows_count
   2522 		: 0;
   2523 
   2524 	for (i = 0; i < lines_to_free; i++)
   2525 		free_bufferline(state->paste + state->paste_count - i - 1);
   2526 	if (!state->paste)
   2527 		newbuf = calloc(state->input_rows_count,
   2528 			sizeof(struct BufferLine));
   2529 	else
   2530 		newbuf = realloc(state->paste,
   2531 			state->input_rows_count * sizeof(struct BufferLine));
   2532 	if (!newbuf)
   2533 		goto yank_input_alloc_error;
   2534 
   2535 	pinput = state->input_buffer;
   2536 	for (i = 0; i < lines_to_init; i++)
   2537 	{
   2538 		ppaste = newbuf + state->input_rows_count - 1 - i;
   2539 		init_bufferline(ppaste);
   2540 		newtext = calloc(LINE_DEFAULT, sizeof(uint32_t));
   2541 		if (!newtext)
   2542 			goto yank_input_alloc_error;
   2543 		ppaste->text = newtext;
   2544 		ppaste->size = LINE_DEFAULT;
   2545 	}
   2546 
   2547 	if (pinput)
   2548 		for (i = 0; i < state->input_rows_count; i++)
   2549 		{
   2550 			ppaste = &newbuf[i];
   2551 			U32_SAFE_COPY(ppaste->text, temp, ppaste->size,
   2552 				pinput->text, pinput->size,
   2553 				yank_input_alloc_error);
   2554 			ppaste->length = pinput->length;
   2555 			pinput++;
   2556 		}
   2557 
   2558 	state->paste_count = state->input_rows_count;
   2559 	state->paste	   = newbuf;
   2560 	return 0;
   2561 
   2562 yank_input_alloc_error:
   2563 	state->error = ERR_CANT_ALLOC;
   2564 	return 1;
   2565 }
   2566 
   2567 static int
   2568 write_file(struct DrawState* state)
   2569 {
   2570 	int result;
   2571 	assert(state != NULL);
   2572 	result = save_file(state);
   2573 	if (result)
   2574 	{
   2575 		state->running = 0;
   2576 		return result;
   2577 	}
   2578 	state->error	  = ERR_NONE;
   2579 	state->edit_dirty = 0;
   2580 	state->dirty	  = 0;
   2581 	return 0;
   2582 }
   2583 
   2584 int
   2585 main(int argc, char** argv)
   2586 {
   2587 	struct DrawState state;
   2588 	struct tb_event ev;
   2589 	struct stat st;
   2590 	char prompt[LINE_DEFAULT];
   2591 	char* dir		   = NULL;
   2592 	char* filename		   = NULL;
   2593 	char* pdir		   = NULL;
   2594 	char* rfn		   = NULL;
   2595 	const char* saved_tb_error = NULL;
   2596 	char* temp		   = NULL;
   2597 	char* wd		   = NULL;
   2598 	ssize_t filename_size	   = 0;
   2599 	long col		   = 1;
   2600 	long lineno		   = 1;
   2601 	int dir_access;
   2602 	int dir_access_errno;
   2603 	int errornum;
   2604 	int optind = 1;
   2605 	int result;
   2606 	int saved_errno = 0;
   2607 	int tbires;
   2608 
   2609 	*prompt = 0;
   2610 
   2611 #ifdef __OpenBSD__
   2612 	if (pledge("stdio tty unveil rpath wpath cpath fattr", NULL) < 0)
   2613 	{
   2614 		perror(PROGRAM_NAME ": pledge");
   2615 		exit(errno);
   2616 	}
   2617 #endif
   2618 
   2619 	/* Necessary in order for termbox to function properly
   2620 	 * See: https://github.com/termbox/termbox2/issues/73
   2621 	 */
   2622 	setlocale(LC_ALL, "");
   2623 
   2624 main_options_loop:
   2625 	if (optind >= argc)
   2626 		return print_error(CUSTOM_ERROR_START + ERR_NO_CL_ARG,
   2627 			errors[ERR_NO_CL_ARG]);
   2628 
   2629 	if (!strcmp(argv[optind], "-h") || !strcmp(argv[optind], "--help"))
   2630 	{
   2631 		printf("Usage:\t%s -h | --help | -V | --full-version | -v |"
   2632 		       " --version\n"
   2633 		       "\t%s [-B | --no-backup] [-b | --backup]"
   2634 		       " [-L | --dont-break-literals]"
   2635 		       " [-W | --no-wrap] [-w <cols> | --wrap-width=<cols>]"
   2636 		       " filename.po\n",
   2637 			PROGRAM_NAME, PROGRAM_NAME);
   2638 		return 0;
   2639 	}
   2640 	else if (!strcmp(argv[optind], "-v")
   2641 		|| !strcmp(argv[optind], "--version"))
   2642 	{
   2643 		printf("%s %s, committed on %s\n", PROGRAM_NAME, VERSION, DATE);
   2644 		return 0;
   2645 	}
   2646 	else if (!strcmp(argv[optind], "-V")
   2647 		|| !strcmp(argv[optind], "--full-version"))
   2648 	{
   2649 		printf("%s %s, committed on %s\n", PROGRAM_NAME, VERSION, DATE);
   2650 		puts(COPYRIGHT);
   2651 		printf("Features set in config.h:\n");
   2652 		printf("  backup_fail:\t\t\t%s\n",
   2653 			backup_fail ? "set" : "unset");
   2654 		if (backup_suffix)
   2655 			printf("  backup_suffix:\t\t\"%s\"\n", backup_suffix);
   2656 		else
   2657 			printf("  backup_suffix:\t\tunset\n");
   2658 		printf("  break_literals:\t\t%s\n",
   2659 			break_literals ? "set" : "unset");
   2660 		printf("  create_backups:\t\t%s\n",
   2661 			create_backups ? "set" : "unset");
   2662 		printf("  literal_break_threshold:\t%d\n",
   2663 			literal_break_threshold);
   2664 		printf("  match_msgstr_ending:\t\t%s\n",
   2665 			match_msgstr_ending ? "set" : "unset");
   2666 		if (plural_string)
   2667 			printf("  plural_string:\t\t\"%s\"\n", plural_string);
   2668 		else
   2669 			printf("  plural_string:\t\tunset\n");
   2670 		printf("  warn_count_dots:\t\t%s\n",
   2671 			warn_count_dots ? "set" : "unset");
   2672 		printf("  wrap_width:\t\t\t%d\n", wrap_width);
   2673 		return 0;
   2674 	}
   2675 	else if (!strcmp(argv[optind], "-B")
   2676 		|| !strcmp(argv[optind], "--no-backup"))
   2677 	{
   2678 		create_backups = 0;
   2679 		optind++;
   2680 		goto main_options_loop;
   2681 	}
   2682 	else if (!strcmp(argv[optind], "-b")
   2683 		|| !strcmp(argv[optind], "--backup"))
   2684 	{
   2685 		create_backups = 1;
   2686 		optind++;
   2687 		goto main_options_loop;
   2688 	}
   2689 	else if (!strcmp(argv[optind], "-L")
   2690 		|| !strcmp(argv[optind], "--dont-break-literals"))
   2691 	{
   2692 		break_literals = 0;
   2693 		optind++;
   2694 		goto main_options_loop;
   2695 	}
   2696 	else if (!strcmp(argv[optind], "-W")
   2697 		|| !strcmp(argv[optind], "--no-wrap"))
   2698 	{
   2699 		wrap_width = 0;
   2700 		optind++;
   2701 		goto main_options_loop;
   2702 	}
   2703 	else if (!strcmp(argv[optind], "-w")
   2704 		|| !strcmp(argv[optind], "--wrap-width="))
   2705 	{
   2706 		optind++;
   2707 		wrap_width = strtonumber(argv[optind], 0, INT_MAX, &errornum);
   2708 		if (errornum != STN_NONE)
   2709 		{
   2710 			if (*argv[optind] == '-')
   2711 				state.error = ERR_NO_CL_ARG_PARAM;
   2712 			else
   2713 				state.error = ERR_BAD_CL_ARG;
   2714 			goto main_done;
   2715 		}
   2716 		if (wrap_width < 0)
   2717 			wrap_width = 0;
   2718 		optind++;
   2719 		goto main_options_loop;
   2720 	}
   2721 	else if (starts_with(argv[optind], "--")
   2722 		|| starts_with(argv[optind], "-"))
   2723 	{
   2724 		state.error = ERR_BAD_CL_ARG;
   2725 		goto main_done;
   2726 	}
   2727 
   2728 	/*
   2729 	 * Chicken and egg problem: to get fpathconf(dirfd, _PC_PATH_MAX)
   2730 	 * we need a dir, which needs to be allocated, and to allocate it we
   2731 	 * need to know the value returned by fpathconf(dirfd, _PC_PATH_MAX)...
   2732 	 */
   2733 	/* argv[optind] is the filename; BTW, "filename" is a misnomer */
   2734 	filename_size = strlen(argv[optind]) + 1;
   2735 	if (!(dir = malloc(filename_size)))
   2736 	{
   2737 		perror(PROGRAM_NAME ": malloc");
   2738 		exit(errno);
   2739 	}
   2740 
   2741 	(void)dirname(dir, argv[optind], filename_size);
   2742 	if (init_limits(dir) < 0)
   2743 	{
   2744 		perror(PROGRAM_NAME ": init_limits");
   2745 		free(dir);
   2746 		exit(errno);
   2747 	}
   2748 
   2749 	filename = calloc(limits[L_PATH_MAX], 1);
   2750 	if (!filename)
   2751 	{
   2752 		perror(PROGRAM_NAME ": calloc");
   2753 		exit(errno);
   2754 	}
   2755 
   2756 	*filename = 0;
   2757 	MEMCCPY(filename, argv[optind], limits[L_PATH_MAX], temp);
   2758 	if (!*filename)
   2759 	{
   2760 		free(filename);
   2761 		free(dir);
   2762 		return print_error(CUSTOM_ERROR_START + ERR_NO_CL_ARG,
   2763 			errors[ERR_NO_CL_ARG]);
   2764 	}
   2765 
   2766 	init_drawstate(&state, filename, prompt);
   2767 
   2768 #ifdef __OpenBSD__
   2769 	if (unveil(state.filename, "rwc") < 0)
   2770 	{
   2771 		perror(PROGRAM_NAME ": unveil");
   2772 		goto main_done;
   2773 	}
   2774 	if (unveil(dir, "rwc") < 0)
   2775 	{
   2776 		perror(PROGRAM_NAME ": unveil");
   2777 		goto main_done;
   2778 	}
   2779 #endif
   2780 
   2781 	if (!create_backups)
   2782 		goto main_skip_backups;
   2783 
   2784 	if (!(wd = malloc(limits[L_PATH_MAX])))
   2785 	{
   2786 		perror(PROGRAM_NAME ": malloc");
   2787 		goto main_done;
   2788 	}
   2789 	if (!getcwd(wd, limits[L_PATH_MAX]))
   2790 	{
   2791 		perror(PROGRAM_NAME ": getcwd");
   2792 		goto main_done;
   2793 	}
   2794 #ifdef __OpenBSD__
   2795 	if (unveil(dir, "rwc") < 0)
   2796 	{
   2797 		perror(PROGRAM_NAME ": unveil");
   2798 		goto main_done;
   2799 	}
   2800 #endif
   2801 
   2802 	errno = 0;
   2803 	if (lstat(state.filename, &st) != -1)
   2804 	{
   2805 		if (S_ISLNK(st.st_mode))
   2806 		{
   2807 			if (!(rfn = malloc(limits[L_PATH_MAX])))
   2808 			{
   2809 				perror(PROGRAM_NAME ": malloc");
   2810 				goto main_done;
   2811 			}
   2812 
   2813 			if (!(state.real_filename = malloc(limits[L_PATH_MAX])))
   2814 			{
   2815 				perror(PROGRAM_NAME ": malloc");
   2816 				goto main_done;
   2817 			}
   2818 
   2819 			state.rfn_len = readlink(state.filename, rfn,
   2820 				limits[L_PATH_MAX]);
   2821 			if (state.rfn_len == -1)
   2822 				goto main_done;
   2823 
   2824 			rfn[state.rfn_len] = 0;
   2825 
   2826 			if (*rfn == '/') /* rfn is absolute */
   2827 			{
   2828 				MEMCCPY(state.real_filename, rfn,
   2829 					limits[L_PATH_MAX], temp);
   2830 				state.rfn_len = strlen(rfn);
   2831 			}
   2832 			else /* rfn is relative */
   2833 			{
   2834 				size_t rfn_dir_len;
   2835 
   2836 				state.orig_real_filename
   2837 					= strndup(rfn, state.rfn_len);
   2838 				if (!state.orig_real_filename)
   2839 				{
   2840 					perror(PROGRAM_NAME ": strndup");
   2841 					goto main_done;
   2842 				}
   2843 
   2844 				MEMCCPY(state.real_filename, dir,
   2845 					limits[L_PATH_MAX], temp);
   2846 				if (!temp)
   2847 				{
   2848 					errno = ENAMETOOLONG;
   2849 					goto main_done;
   2850 				}
   2851 				*(temp - 1) = '/'; /* overwrite NUL */
   2852 				rfn_dir_len = temp - state.real_filename - 1;
   2853 				MEMCCPY_EXT(temp, state.real_filename, rfn,
   2854 					limits[L_PATH_MAX] - rfn_dir_len, temp);
   2855 				if (temp)
   2856 					state.rfn_len = temp
   2857 						- state.real_filename - 1;
   2858 			}
   2859 			free(rfn);
   2860 			rfn = NULL;
   2861 #ifdef __OpenBSD__
   2862 			if (unveil(state.real_filename, "rwc") < 0)
   2863 			{
   2864 				perror(PROGRAM_NAME ": unveil");
   2865 				goto main_done;
   2866 			}
   2867 #endif
   2868 			if (stat(state.real_filename, &st) == -1)
   2869 				goto main_done;
   2870 		}
   2871 	}
   2872 	else
   2873 		goto main_done;
   2874 
   2875 	if (!(state.backup_filename = malloc(limits[L_PATH_MAX])))
   2876 	{
   2877 		perror(PROGRAM_NAME ": malloc");
   2878 		goto main_done;
   2879 	}
   2880 
   2881 	/* Backup filename is the target file + suffix if the file to
   2882 	 * write is a symlink, otherwise file + suffix */
   2883 	MEMCCPY(state.backup_filename,
   2884 		state.rfn_len > 0 ? state.real_filename : state.filename,
   2885 		limits[L_PATH_MAX], temp);
   2886 	if (!temp) /* filename is full, turning off backups */
   2887 		state.backup_possible = 0;
   2888 	else
   2889 		MEMCCPY_EXT((temp - 1), state.backup_filename, backup_suffix,
   2890 			limits[L_PATH_MAX] + temp - state.backup_filename,
   2891 			temp);
   2892 #ifdef __OpenBSD__
   2893 	if (unveil(state.backup_filename, "rwc") < 0)
   2894 	{
   2895 		perror(PROGRAM_NAME ": unveil");
   2896 		goto main_done;
   2897 	}
   2898 #endif
   2899 
   2900 main_skip_backups:
   2901 	if (state.rfn_len > 0)
   2902 	{
   2903 		if (!(pdir = realloc(dir, limits[L_PATH_MAX])))
   2904 		{
   2905 			perror(PROGRAM_NAME ": realloc");
   2906 			goto main_done;
   2907 		}
   2908 		dir  = pdir;
   2909 		*dir = 0;
   2910 		(void)dirname(dir, state.real_filename, limits[L_PATH_MAX]);
   2911 #ifdef __OpenBSD__
   2912 		if (unveil(dir, "rwc") < 0)
   2913 		{
   2914 			perror(PROGRAM_NAME ": unveil");
   2915 			goto main_done;
   2916 		}
   2917 #endif
   2918 	}
   2919 
   2920 #ifdef __OpenBSD__
   2921 	if (unveil_terminfo_path() < 0)
   2922 	{
   2923 		perror(PROGRAM_NAME ": unveil");
   2924 		goto main_done;
   2925 	}
   2926 	/* Have to explicitly unveil /dev/tty before tb_init despite pledge(tty)
   2927 	 * in OpenBSD 7.9+
   2928 	 */
   2929 	if (unveil("/dev/tty", "rw"))
   2930 	{
   2931 		perror(PROGRAM_NAME ": unveil");
   2932 		goto main_done;
   2933 	}
   2934 	if (unveil(NULL, NULL) < 0)
   2935 	{
   2936 		perror(PROGRAM_NAME ": unveil");
   2937 		goto main_done;
   2938 	}
   2939 #endif
   2940 
   2941 	dir_access	 = access(dir, W_OK | X_OK);
   2942 	dir_access_errno = errno;
   2943 
   2944 	if (!backup_fail && state.backup_possible)
   2945 		state.backup_possible = dir_access != -1;
   2946 
   2947 	result = stat(state.filename, &st);
   2948 	if (result == -1)
   2949 	{
   2950 		if (dir_access == -1)
   2951 		{
   2952 			errno = dir_access_errno;
   2953 			goto main_done;
   2954 		}
   2955 		goto init_termbox;
   2956 	}
   2957 
   2958 	errno = 0;
   2959 
   2960 	if (!S_ISREG(st.st_mode))
   2961 	{
   2962 		state.error = ERR_NOTREG_FILE;
   2963 		goto main_done;
   2964 	}
   2965 
   2966 	if (access(state.filename, F_OK | R_OK | W_OK) == -1)
   2967 		goto main_done;
   2968 
   2969 	if (state.rfn_len > 0
   2970 		&& access(state.real_filename, F_OK | R_OK | W_OK) == -1)
   2971 		goto main_done;
   2972 
   2973 	if (load_file(&state, &lineno, &col))
   2974 		goto main_done;
   2975 
   2976 	state.msgid_number	= state.msgid_count > 0 ? 1 : 0;
   2977 	state.first_shown_msgid = state.msgid_number;
   2978 
   2979 init_termbox:
   2980 	if ((tbires = tb_init()))
   2981 	{
   2982 		saved_tb_error = tb_strerror(tbires);
   2983 		errno	       = ERR_TB_ERROR;
   2984 		goto main_done;
   2985 	}
   2986 
   2987 	errno		       = 0;
   2988 	state.maxx	       = 0;
   2989 	state.recalculate_size = 1;
   2990 
   2991 	while (state.running)
   2992 	{
   2993 		tb_set_clear_attrs(colors[C_CLEAR].fg, colors[C_CLEAR].bg);
   2994 		tb_clear();
   2995 
   2996 		if (state.recalculate_size)
   2997 		{
   2998 			recalculate_size(&state);
   2999 			state.recalculate_size = 0;
   3000 		}
   3001 		if (draw(&state))
   3002 		{
   3003 			state.running = 0;
   3004 			goto main_done;
   3005 		}
   3006 		tb_present();
   3007 		result = tb_poll_event(&ev);
   3008 		if (result == TB_OK && ev.type == TB_EVENT_RESIZE)
   3009 			state.recalculate_size = 1;
   3010 		else if (result == TB_OK && ev.type == TB_EVENT_KEY)
   3011 			handle_key_event(&ev, &state);
   3012 	}
   3013 
   3014 main_done:
   3015 	saved_errno = errno;
   3016 
   3017 	if (saved_tb_error)
   3018 	{
   3019 		tb_shutdown(1);
   3020 		print_error(1, saved_tb_error);
   3021 		exit(CUSTOM_ERROR_START + saved_errno);
   3022 	}
   3023 
   3024 	tb_shutdown(saved_errno != ENOMEM);
   3025 
   3026 	if (saved_errno)
   3027 		perror(PROGRAM_NAME);
   3028 
   3029 	switch (state.error)
   3030 	{
   3031 	case ERR_NOTREG_FILE:
   3032 		print_error(1, errors[state.error], state.filename);
   3033 		break;
   3034 	case ERR_NO_CL_ARG_PARAM:
   3035 		print_error(1, errors[state.error], argv[optind - 1]);
   3036 		break;
   3037 	case ERR_BAD_CL_ARG:
   3038 		print_error(1, errors[state.error], argv[optind]);
   3039 		break;
   3040 	case ERR_SYNTAX:
   3041 		print_error(1, errors[state.error], state.filename, lineno, col);
   3042 		break;
   3043 	case ERR_NONE:
   3044 		break;
   3045 	default:
   3046 		print_error(1, errors[state.error]);
   3047 	}
   3048 
   3049 	return saved_errno
   3050 		? saved_errno
   3051 		: (state.error ? CUSTOM_ERROR_START + state.error : 0);
   3052 }