poe

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

чување 5e2d2a94f06895f95f117fe96e43a5a8b1a14fa2
родитељ 0c3818f8a9db1e237a1b8072b611c760c2c35a61
Аутор: Страхиња Радић <sr@strahinja.org>
Датум:   Tue, 10 Feb 2026 09:39:19 +0100

WIP tidy up logic based on sled

Diffstat:
MMakefile | 6+++---
MTODO | 2+-
Mconfig.Haiku | 6+++---
Mconfig.Linux | 6+++---
Mconfig.OpenBSD | 6+++---
Mconfig.def.h | 325+++++++++++++++++++++++++++++++++++--------------------------------------------
Mdraw.c | 1265+++++++++++++++++++++++++++++++++++++++++++------------------------------------
Mdraw.h | 179+++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------------
Mpo.c | 250++++++++++++++++++++++++++++++++++++++++---------------------------------------
Mpo.h | 36++++++++++++++++++++----------------
Mpoe.c | 1774+++++++++++++++++++++++++++++++++++++++++++++----------------------------------
Mtermbox.h | 2++
Mutil.c | 131+++++++++++++++++++++++++++++++++++++++++++++++++------------------------------
Mutil.h | 108++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------
Mversion.h.in | 5+++--
измењених датотека: 15, додавања: 2313(+), брисања: 1788(-)

diff --git a/Makefile b/Makefile @@ -28,15 +28,15 @@ makedate: makeversion: sh lib/makeversion -l -poe.o: poe.c config.h draw.h po.h termbox.h util.h version.h +poe.o: poe.c config.h draw.h limit.h po.h termbox.h util.h version.h $(MANPAGE): $(MANPAGE).in date version version.h: version.h.in date version -draw.o: draw.c config.h draw.h po.h termbox.h util.h version.h +draw.o: draw.c config.h draw.h limit.h po.h termbox.h util.h version.h -util.o: util.c termbox.h po.h util.h +util.o: util.c limit.h termbox.h po.h util.h $(PROG): $(OBJS) $(SRC) $(HEADERS) $(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) -o $@ $(OBJS) $(LIBS) diff --git a/TODO b/TODO @@ -4,7 +4,7 @@ TODO [ ] Support Yudit's nonstandard PO format (no empty lines between individual entries) -[~] Tidy up basic logic in poe.c +[ ] Tidy up basic logic everywhere (based on sled) < > Wrap long lines? Not if it would complicate the code too much. diff --git a/config.Haiku b/config.Haiku @@ -5,9 +5,9 @@ CPPFLAGS = -D_DEFAULT_SOURCE -D_POSIX_C_SOURCE=200809L \ DESTDIR = /boot/home/config/non-packaged INSTALL = install LIBS = -SRC = draw.c po.c poe.c strlcat.c strlcpy.c util.c -HEADERS = config.h draw.h po.h termbox.h util.h version.h -OBJS = draw.o po.o poe.o strlcat.o strlcpy.o util.o +SRC = draw.c limit.c po.c poe.c strlcat.c strlcpy.c util.c +HEADERS = config.h draw.h limit.h po.h termbox.h util.h version.h +OBJS = draw.o limit.o po.o poe.o strlcat.o strlcpy.o util.o PREFIX = PROG = poe diff --git a/config.Linux b/config.Linux @@ -3,9 +3,9 @@ CPPFLAGS = -D_DEFAULT_SOURCE -D_POSIX_C_SOURCE=200809L \ -D_XOPEN_SOURCE=700 -DNDEBUG INSTALL = install LIBS = -SRC = draw.c po.c poe.c strlcat.c strlcpy.c util.c -HEADERS = config.h draw.h po.h termbox.h util.h version.h -OBJS = draw.o po.o poe.o strlcat.o strlcpy.o util.o +SRC = draw.c limit.c po.c poe.c strlcat.c strlcpy.c util.c +HEADERS = config.h draw.h limit.h po.h termbox.h util.h version.h +OBJS = draw.o limit.o po.o poe.o strlcat.o strlcpy.o util.o PREFIX = /usr/local PROG = poe diff --git a/config.OpenBSD b/config.OpenBSD @@ -3,9 +3,9 @@ CPPFLAGS = -D_DEFAULT_SOURCE -D_POSIX_C_SOURCE=200809L \ -D_XOPEN_SOURCE=700 -D_BSD_SOURCE -DNDEBUG INSTALL = install LIBS = -SRC = draw.c po.c poe.c strlcat.c strlcpy.c util.c -HEADERS = config.h draw.h po.h termbox.h util.h version.h -OBJS = draw.o po.o poe.o strlcat.o strlcpy.o util.o +SRC = draw.c limit.c po.c poe.c strlcat.c strlcpy.c util.c +HEADERS = config.h draw.h limit.h po.h termbox.h util.h version.h +OBJS = draw.o limit.o po.o poe.o strlcat.o strlcpy.o util.o PREFIX = /usr/local PROG = poe diff --git a/config.def.h b/config.def.h @@ -2,147 +2,104 @@ * any later version. Copyright (C) 2021-2026 Страхиња Радић. * See the file LICENSE for exact copyright and license details. */ -/* Comment out to not create backups */ -#define CREATE_BACKUPS - -#ifdef PO_C_GLOBALS -const char* backup_suffix = "~"; -#endif /* PO_C_GLOBALS */ - -/* Correct the ending of msgstr to match msgid if ' ', '.' or '\\n' */ -#define MATCH_MSGSTR_ENDING - -/* Uncomment to insert this after Plural-Forms: in the first msgstr (only if - * Plural-Forms isn't already present) - * The plural string given here is for Serbian; adjust to match your language */ -/*#define PLURAL_STRING "nplurals=3; plural=(n%10==1 && n%100!=11 ?" \ - " 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);"*/ - -#define SAVE_WRAP_WIDTH 70 -#define WRAP_FIRST_MSGSTR 0 -#define WARN_COUNT_DOTS 0 +/* clang-format off */ +static const struct Color colors[] = { + [C_CLEAR] = { .fg = TB_DEFAULT, .bg = TB_DEFAULT }, + [C_DLG] = { .fg = TB_BLACK, .bg = TB_WHITE }, + [C_EDIT_MSGID] = { .fg = TB_BLACK | TB_BOLD, + .bg = TB_WHITE }, + [C_EDIT_MSGID_FOCUS] + = { .fg = TB_BLACK, .bg = TB_WHITE }, + [C_EDIT_MSGID_HEADING] + = { .fg = TB_WHITE | TB_BOLD, + .bg = TB_WHITE }, + [C_EDIT_MSGSTR] = { .fg = TB_BLACK | TB_BOLD, + .bg = TB_BLACK }, + [C_EDIT_MSGSTR_FOCUS] + = { .fg = TB_YELLOW | TB_BOLD, + .bg = TB_BLACK }, + [C_ERROR] = { .fg = TB_WHITE | TB_BOLD, + .bg = TB_RED }, + [C_FLAG] = { .fg = TB_YELLOW | TB_BOLD, + .bg = TB_DEFAULT }, + [C_LIST_FLAGS] = { .fg = TB_YELLOW | TB_BOLD, + .bg = TB_DEFAULT }, + [C_LIST_FLAGS_SEL] + = { .fg = TB_WHITE | TB_BOLD, + .bg = TB_BLUE }, + [C_LIST_MSGID] = { .fg = TB_WHITE, .bg = TB_DEFAULT }, + [C_LIST_MSGID_SEL] + = { .fg = TB_WHITE, .bg = TB_BLUE }, + [C_LIST_MSGID_SPECIAL] + = { .fg = TB_YELLOW | TB_BOLD, + .bg = TB_DEFAULT }, + [C_LIST_MSGID_SPECIAL_SEL] + = { .fg = TB_WHITE | TB_BOLD, + .bg = TB_BLUE }, + [C_LIST_MSGSTR] = { .fg = TB_GREEN, .bg = TB_DEFAULT }, + [C_LIST_MSGSTR_SEL] + = { .fg = TB_WHITE | TB_BOLD, + .bg = TB_BLUE }, + [C_LIST_MSGSTR_WARN] + = { .fg = TB_RED | TB_BOLD, + .bg = TB_DEFAULT }, + [C_LIST_MSGSTR_SEL_WARN] + = { .fg = TB_RED | TB_BOLD, + .bg = TB_BLUE }, + [C_LIST_SEL] = { .fg = TB_WHITE, .bg = TB_BLUE }, + [C_MSGID] = { .fg = TB_GREEN, .bg = TB_DEFAULT }, + [C_MSGSTR] = { .fg = TB_WHITE, .bg = TB_DEFAULT }, + [C_PROMPT] = { .fg = TB_YELLOW | TB_BOLD, + .bg = TB_MAGENTA }, + [C_SEARCH] = { .fg = TB_BLACK, .bg = TB_WHITE }, + [C_SEARCH_EDIT] = { .fg = TB_WHITE | TB_BOLD, + .bg = TB_BLACK }, + [C_SEARCH_HL] = { .fg = TB_WHITE | TB_BOLD, + .bg = TB_GREEN }, + [C_SHADOW] = { .fg = TB_BLACK, .bg = TB_BLACK | TB_BOLD + }, + [C_STATUS_1] = { .fg = TB_BLACK, .bg = TB_WHITE }, + [C_STATUS_2] = { .fg = TB_WHITE | TB_BOLD, + .bg = TB_BLUE }, + [C_STATUS_3] = { .fg = TB_BLACK, .bg = TB_WHITE }, + [C_TITLE] = { .fg = TB_BLACK, .bg = TB_WHITE }, +}; +/* clang-format on */ -#define DLG_FG TB_BLACK -#define DLG_BG TB_WHITE -#define EDIT_MSGID_FG TB_BLACK | TB_BOLD -#define EDIT_MSGID_BG TB_WHITE -#define EDIT_MSGID_FOCUS_FG TB_BLACK -#define EDIT_MSGID_FOCUS_BG TB_WHITE -#define EDIT_MSGID_HEADING_FG TB_WHITE | TB_BOLD -#define EDIT_MSGID_HEADING_BG TB_WHITE -#define EDIT_MSGSTR_FG TB_BLACK | TB_BOLD -#define EDIT_MSGSTR_BG TB_BLACK -#define EDIT_MSGSTR_FOCUS_FG TB_YELLOW | TB_BOLD -#define EDIT_MSGSTR_FOCUS_BG TB_BLACK -#define ERROR_FG TB_WHITE | TB_BOLD -#define ERROR_BG TB_RED -#define FLAG_FG TB_YELLOW | TB_BOLD -#define FLAG_BG TB_DEFAULT -#define LIST_FLAGS_FG TB_YELLOW | TB_BOLD -#define LIST_FLAGS_BG TB_DEFAULT -#define LIST_FLAGS_SEL_FG TB_WHITE | TB_BOLD -#define LIST_FLAGS_SEL_BG TB_BLUE -#define LIST_MSGID_FG TB_WHITE -#define LIST_MSGID_BG TB_DEFAULT -#define LIST_MSGID_SEL_FG TB_WHITE -#define LIST_MSGID_SEL_BG TB_BLUE -#define LIST_MSGID_SPECIAL_FG TB_YELLOW | TB_BOLD -#define LIST_MSGID_SPECIAL_BG TB_DEFAULT -#define LIST_MSGID_SPECIAL_SEL_FG TB_WHITE | TB_BOLD -#define LIST_MSGID_SPECIAL_SEL_BG TB_BLUE -#define LIST_MSGSTR_FG TB_GREEN -#define LIST_MSGSTR_BG TB_DEFAULT -#define LIST_MSGSTR_SEL_FG TB_WHITE | TB_BOLD -#define LIST_MSGSTR_SEL_BG TB_BLUE -#define LIST_MSGSTR_WARN_FG TB_RED | TB_BOLD -#define LIST_MSGSTR_WARN_BG TB_DEFAULT -#define LIST_MSGSTR_SEL_WARN_FG TB_RED | TB_BOLD -#define LIST_MSGSTR_SEL_WARN_BG TB_BLUE -#define LIST_SEL_FG TB_WHITE -#define LIST_SEL_BG TB_BLUE -#define MSGID_FG TB_GREEN -#define MSGID_BG TB_DEFAULT -#define MSGSTR_FG TB_WHITE -#define MSGSTR_BG TB_DEFAULT -#define PROMPT_FG TB_YELLOW | TB_BOLD -#define PROMPT_BG TB_MAGENTA -#define SEARCH_FG TB_BLACK -#define SEARCH_BG TB_WHITE -#define SEARCH_EDIT_FG TB_WHITE | TB_BOLD -#define SEARCH_EDIT_BG TB_BLACK -#define SEARCH_HL_FG TB_WHITE | TB_BOLD -#define SEARCH_HL_BG TB_GREEN -#define SHADOW_FG TB_BLACK -#define SHADOW_BG TB_BLACK | TB_BOLD -#define TITLE_FG TB_BLACK -#define TITLE_BG TB_WHITE +extern int backup_fail; +extern char* backup_suffix; +extern int create_backups; +extern int match_msgstr_ending; +extern char* plural_string; +extern int warn_count_dots; +extern int wrap_first_msgstr; +extern int wrap_width; #ifdef POE_C_GLOBALS -int cancel_close(struct DrawState* state); -int cancel_msgstr(struct DrawState* state); -int cancel_search(struct DrawState* state); -int copy_msgid_to_input(struct DrawState* state); -int edit_page_down(struct DrawState* state); -int edit_page_up(struct DrawState* state); -int erase_backwards(struct DrawState* state); -int erase_forward(struct DrawState* state); -int erase_prev_word(struct DrawState* state); -int erase_search_backwards(struct DrawState* state); -int erase_search_forward(struct DrawState* state); -int erase_search_prev_word(struct DrawState* state); -int erase_search_to_end(struct DrawState* state); -int erase_search_to_start(struct DrawState* state); -int erase_to_end(struct DrawState* state); -int erase_to_start(struct DrawState* state); -int exit_program(struct DrawState* state); -int hide_search(struct DrawState* state); -int insert_line(struct DrawState* state); -int insert_search_space(struct DrawState* state); -int insert_search_tab(struct DrawState* state); -int insert_space(struct DrawState* state); -int insert_tab(struct DrawState* state); -int move_end(struct DrawState* state); -int move_down(struct DrawState* state); -int move_left(struct DrawState* state); -int move_list_down(struct DrawState* state); -int move_list_end(struct DrawState* state); -int move_list_page_down(struct DrawState* state); -int move_list_page_up(struct DrawState* state); -int move_list_start(struct DrawState* state); -int move_list_up(struct DrawState* state); -int move_next_word(struct DrawState* state); -int move_prev_word(struct DrawState* state); -int move_right(struct DrawState* state); -int move_search_end(struct DrawState* state); -int move_search_left(struct DrawState* state); -int move_search_next_word(struct DrawState* state); -int move_search_prev_word(struct DrawState* state); -int move_search_right(struct DrawState* state); -int move_search_start(struct DrawState* state); -int move_start(struct DrawState* state); -int move_up(struct DrawState* state); -int next_fuzzy(struct DrawState* state); -int next_match(struct DrawState* state); -int next_plural(struct DrawState* state); -int next_untranslated(struct DrawState* state); -int paste_paste_buffer_to_input(struct DrawState* state); -int prev_match(struct DrawState* state); -int prev_plural(struct DrawState* state); -int save_msgstr(struct DrawState* state); -int show_edit(struct DrawState* state); -int show_help(struct DrawState* state); -int show_search(struct DrawState* state); -int toggle_fuzzy(struct DrawState* state); -int toggle_info_focused(struct DrawState* state); -int yank_input_to_paste_buffer(struct DrawState* state); -int write_file(struct DrawState* state); - /* clang-format off */ +/* Fail with error if creating backups is not possible due to file and + * directory permissions? */ +int backup_fail = 1; +char* backup_suffix = "~"; +int create_backups = 1; /* comment out to not create backups */ +int match_msgstr_ending = 1; /* correct the ending of msgstr to + * match msgid if ' ', '.' or '\\n'? */ +/* Uncomment/comment the appropriate definition to insert this after + * Plural-Forms: in the first msgstr (only if Plural-Forms isn't already + * present) The plural string given here is for Serbian; adjust to match + * your language */ +char* plural_string = NULL; +/*char* plural_string = "nplurals=3; plural=(n%10==1 && n%100!=11 ?" + " 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);";*/ +int warn_count_dots = 0; +int wrap_first_msgstr = 0; +int wrap_width = 70; + /* Always processed */ static const struct Key top_level_keys[] = { { .ev = { .key = TB_KEY_ESC }, .callback = cancel_close }, - { .ev = { .key = TB_KEY_CTRL_Q }, + { .ev = { .key = TB_KEY_CTRL_Q, .mod = TB_MOD_CTRL }, .callback = exit_program }, }; @@ -160,31 +117,31 @@ static const struct Key edit_keys[] = { .callback = erase_backwards }, { .ev = { .key = TB_KEY_BACKSPACE2 }, .callback = erase_backwards }, - { .ev = { .key = TB_KEY_CTRL_A }, + { .ev = { .key = TB_KEY_CTRL_A, .mod = TB_MOD_CTRL }, .callback = move_start }, - { .ev = { .key = TB_KEY_CTRL_C }, + { .ev = { .key = TB_KEY_CTRL_C, .mod = TB_MOD_CTRL }, .callback = yank_input_to_paste_buffer }, - { .ev = { .key = TB_KEY_CTRL_E }, + { .ev = { .key = TB_KEY_CTRL_E, .mod = TB_MOD_CTRL }, .callback = move_end }, - { .ev = { .key = TB_KEY_CTRL_K }, + { .ev = { .key = TB_KEY_CTRL_K, .mod = TB_MOD_CTRL }, .callback = erase_to_end }, - { .ev = { .key = TB_KEY_CTRL_N }, + { .ev = { .key = TB_KEY_CTRL_N, .mod = TB_MOD_CTRL }, .callback = next_plural }, - { .ev = { .key = TB_KEY_CTRL_P }, + { .ev = { .key = TB_KEY_CTRL_P, .mod = TB_MOD_CTRL }, .callback = prev_plural }, - { .ev = { .key = TB_KEY_CTRL_R }, + { .ev = { .key = TB_KEY_CTRL_R, .mod = TB_MOD_CTRL }, .callback = move_prev_word }, - { .ev = { .key = TB_KEY_CTRL_S }, + { .ev = { .key = TB_KEY_CTRL_S, .mod = TB_MOD_CTRL }, .callback = save_msgstr }, - { .ev = { .key = TB_KEY_CTRL_T }, + { .ev = { .key = TB_KEY_CTRL_T, .mod = TB_MOD_CTRL }, .callback = move_next_word }, - { .ev = { .key = TB_KEY_CTRL_U }, + { .ev = { .key = TB_KEY_CTRL_U, .mod = TB_MOD_CTRL }, .callback = erase_to_start }, - { .ev = { .key = TB_KEY_CTRL_V }, + { .ev = { .key = TB_KEY_CTRL_V, .mod = TB_MOD_CTRL }, .callback = paste_paste_buffer_to_input }, - { .ev = { .key = TB_KEY_CTRL_W }, + { .ev = { .key = TB_KEY_CTRL_W, .mod = TB_MOD_CTRL }, .callback = erase_prev_word }, - { .ev = { .key = TB_KEY_CTRL_Z }, + { .ev = { .key = TB_KEY_CTRL_Z, .mod = TB_MOD_CTRL }, .callback = toggle_fuzzy }, { .ev = { .key = TB_KEY_DELETE }, .callback = erase_forward }, @@ -209,37 +166,42 @@ static const struct Key search_keys[] = { .callback = move_search_left }, { .ev = { .key = TB_KEY_ARROW_RIGHT }, .callback = move_search_right }, - { .ev = { .key = TB_KEY_BACKSPACE }, + { .ev = { .key = TB_KEY_BACKSPACE, .mod = TB_MOD_CTRL }, .callback = erase_search_backwards }, - { .ev = { .key = TB_KEY_BACKSPACE2 }, + { .ev = { .key = TB_KEY_BACKSPACE2, .mod = TB_MOD_CTRL }, .callback = erase_search_backwards }, - { .ev = { .key = TB_KEY_CTRL_A }, + { .ev = { .key = TB_KEY_CTRL_A, .mod = TB_MOD_CTRL }, .callback = move_search_start }, - { .ev = { .key = TB_KEY_CTRL_E }, + { .ev = { .key = TB_KEY_CTRL_E, .mod = TB_MOD_CTRL }, .callback = move_search_end }, - { .ev = { .key = TB_KEY_CTRL_K }, + { .ev = { .key = TB_KEY_CTRL_K, .mod = TB_MOD_CTRL }, .callback = erase_search_to_end }, - { .ev = { .key = TB_KEY_CTRL_R }, + { .ev = { .key = TB_KEY_CTRL_R, .mod = TB_MOD_CTRL }, .callback = move_search_prev_word }, - { .ev = { .key = TB_KEY_CTRL_S }, + { .ev = { .key = TB_KEY_CTRL_S, .mod = TB_MOD_CTRL }, .callback = write_file }, - { .ev = { .key = TB_KEY_CTRL_T }, + { .ev = { .key = TB_KEY_CTRL_T, .mod = TB_MOD_CTRL }, .callback = move_search_next_word }, - { .ev = { .key = TB_KEY_CTRL_U }, + { .ev = { .key = TB_KEY_CTRL_U, .mod = TB_MOD_CTRL }, .callback = erase_search_to_start }, - { .ev = { .key = TB_KEY_CTRL_W }, + { .ev = { .key = TB_KEY_CTRL_W, .mod = TB_MOD_CTRL }, .callback = erase_search_prev_word }, { .ev = { .key = TB_KEY_DELETE }, .callback = erase_search_forward }, { .ev = { .key = TB_KEY_END }, .callback = move_search_end }, - { .ev = { .key = TB_KEY_ENTER }, + /* Enter = C-M, hence the TB_MOD_CTRL; tb doesn't make a difference + * between C-Enter and Enter... */ + { .ev = { .key = TB_KEY_ENTER, .mod = TB_MOD_CTRL }, .callback = hide_search }, { .ev = { .key = TB_KEY_F3 }, .callback = next_match }, { .ev = { .key = TB_KEY_F4 }, .callback = prev_match }, { .ev = { .key = TB_KEY_HOME }, .callback = move_search_start }, { .ev = { .key = TB_KEY_SPACE }, .callback = insert_search_space }, - { .ev = { .key = TB_KEY_TAB }, .callback = insert_search_tab }, + /* Tab = C-I, hence the TB_MOD_CTRL; tb doesn't make a difference + * between C-I and Tab... */ + { .ev = { .key = TB_KEY_TAB, .mod = TB_MOD_CTRL }, + .callback = insert_search_tab }, }; /* Main window */ @@ -250,7 +212,9 @@ static const struct Key main_keys[] = { .callback = move_list_up }, { .ev = { .key = TB_KEY_END }, .callback = move_list_end }, - { .ev = { .key = TB_KEY_ENTER }, + /* Enter = C-M, hence the TB_MOD_CTRL; tb doesn't make a difference + * between C-Enter and Enter... */ + { .ev = { .key = TB_KEY_ENTER, .mod = TB_MOD_CTRL }, .callback = show_edit }, { .ev = { .key = TB_KEY_F1 }, .callback = show_help }, { .ev = { .key = TB_KEY_F3 }, .callback = next_match }, @@ -259,13 +223,13 @@ static const struct Key main_keys[] = { { .ev = { .key = TB_KEY_F8 }, .callback = next_untranslated }, { .ev = { .key = TB_KEY_F9 }, .callback = next_fuzzy }, { .ev = { .key = TB_KEY_HOME }, .callback = move_list_start }, - { .ev = { .key = TB_KEY_CTRL_B }, + { .ev = { .key = TB_KEY_CTRL_B, .mod = TB_MOD_CTRL }, .callback = move_list_page_up }, - { .ev = { .key = TB_KEY_CTRL_F }, + { .ev = { .key = TB_KEY_CTRL_F, .mod = TB_MOD_CTRL }, .callback = move_list_page_down }, - { .ev = { .key = TB_KEY_CTRL_S }, + { .ev = { .key = TB_KEY_CTRL_S, .mod = TB_MOD_CTRL }, .callback = write_file }, - { .ev = { .key = TB_KEY_CTRL_Z }, + { .ev = { .key = TB_KEY_CTRL_Z, .mod = TB_MOD_CTRL }, .callback = toggle_fuzzy }, { .ev = { .key = TB_KEY_PGDN }, .callback = move_list_page_down }, @@ -285,27 +249,22 @@ static const struct Key main_keys[] = { { .ev = { .ch = 'z' }, .callback = toggle_fuzzy }, }; +/* clang-format on */ #endif /* POE_C_GLOBALS */ -void format_filename(char* result, size_t result_size, const char* format, - const struct DrawState* state); -void format_msgs(char* result, size_t result_size, const char* format, - const struct DrawState* state); - +/* clang-format off */ static const struct StatusSegment status_segments[] = { - {.format = "%s%s", - .callback = format_filename, - .fg = TB_BLACK, - .bg = TB_WHITE, - .alignment = LEFT}, - {.format = "%ld/%ld (%ldu/%ldf/%ldo)", - .callback = format_msgs, - .fg = TB_WHITE | TB_BOLD, - .bg = TB_BLUE, - .alignment = CENTER}, - {.format = "H=HELP, q=QUIT", - .callback = NULL, - .fg = TB_BLACK, - .bg = TB_WHITE, - .alignment = RIGHT}, + {.format = "%s%s", + .callback = format_filename, + .color_index = C_STATUS_1, + .alignment = LEFT}, + {.format = "%ld/%ld (%ldu/%ldf/%ldo)", + .callback = format_msgs, + .color_index = C_STATUS_2, + .alignment = CENTER}, + {.format = "H=HELP, q=QUIT", + .callback = NULL, + .color_index = C_STATUS_3, + .alignment = RIGHT}, }; +/* clang-format on */ diff --git a/draw.c b/draw.c @@ -2,16 +2,18 @@ * any later version. Copyright (C) 2021-2026 Страхиња Радић. * See the file LICENSE for exact copyright and license details. */ +#include <assert.h> #include <errno.h> #include <stdio.h> #include <stdlib.h> #include <string.h> -#include "version.h" -#include "util.h" -#include "po.h" #include "draw.h" #include "config.h" +#include "limit.h" +#include "po.h" +#include "util.h" +#include "version.h" /* clang-format off */ static const char* help_title = "Main view help"; @@ -37,18 +39,28 @@ static const char* help[] = { "See the manual (man poe) for full help."}; static const char* editbox_title = "Edit translation"; const char* errors[] = { - [ERR_UNKNOWN_KEY] = "Unknown key", + [ERR_NO_CL_ARG] = "No filename given", + [ERR_NOTPO_FILE] = "Not a PO file", + [ERR_NOTREG_FILE] = "'%s' is not a regular file", + [ERR_TB_ERROR] = "termbox error", + [ERR_UNKNOWN_KEY] = "Unknown key (press F1 for help)", [ERR_UNKNOWN_KEY_HELP] = "Unknown key (press H or F1 for help)", [ERR_EXIT_KEY] = "Press q or C-Q to quit", - [ERR_DLG_OPEN_QUIT] = "Press Esc to close dialog or q/C-Q to quit", - [ERR_DLG_OPEN] = "Press Esc to close dialog first", + [ERR_DLG_OPEN] = "Press Esc to close dialog or q/C-Q to quit", + [ERR_DLG_OPEN_QUIT] = "Press Esc to close dialog first", [ERR_DLG_FOCUS_EDIT] = "Switch to edit box first with F6", [ERR_EMPTY_FILE] = "No msgids to edit", [ERR_CANT_ALLOC] = "Memory allocation failed", [ERR_CANT_SAVE] = "File is not writeable", [ERR_CANT_MOVE] = "Cannot move temporary file", [ERR_ILLEGAL_ON_FIRST] = "Not allowed on first entry", - [ERR_NO_PLURAL_FORMS] = "No Plural-Forms (add manually)"}; + [ERR_SYNTAX] = "%s:%ld:%ld: Syntax error", + [ERR_NO_PLURAL_FORMS] = "No Plural-Forms (add manually)", + [ERR_ERRNO_SET] = "System call/library function error"}; +const char* actions[] = { + [ACT_WRITING] = "Writing...", + [ACT_READING] = "Reading..." +}; const char* flag_strings[] = { [FL_C_FORMAT] = "c-format", [FL_NO_C_FORMAT] = "no-c-format", @@ -59,13 +71,556 @@ const char* prompt_cancel_dirty = "Entry changed, save [y/n/ESC]?"; const char* prompt_overwrite = "Not empty, overwrite? [y/n/ESC]?"; /* clang-format on */ -int print_error(const int code, const char* msg, ...); +extern int print_error(const int code, const char* msg, ...); void -init_bufferline(struct BufferLine* bl) +assign_coord(struct Coord* dest, const struct Coord src) +{ + init_coord(dest, src.col, src.row); +} + +int +calculate_edit_width(const struct DrawState* state) +{ + assert(state != NULL); + return state->maxx > EDIT_WIDTH + 3 ? EDIT_WIDTH : state->maxx - 3; +} + +int +calculate_help_width(const struct DrawState* state) +{ + assert(state != NULL); + return state->maxx > HELP_WIDTH + 2 ? HELP_WIDTH : state->maxx - 2; +} + +int +calculate_search_width(const struct DrawState* state) +{ + assert(state != NULL); + return state->maxx > SEARCH_WIDTH + 2 ? SEARCH_WIDTH : state->maxx - 2; +} + +/* Calculates display length of an input buffer line. + * max_index = the input index up to which the length is calculated + */ +ssize_t +display_length(uint32_t* buffer, const ssize_t max_index) +{ + uint32_t* pbuf = buffer; + ssize_t len = 0; + int w; + + assert(buffer != NULL); + for (ssize_t i = 0; *pbuf && i < max_index; i++) + { + pbuf = &buffer[i]; + if (*pbuf == '\t') + len += TAB_SIZE - (len % TAB_SIZE); + else if (!*pbuf) + len++; + else + { + w = wcwidth((wchar_t)*pbuf); + if (w > 0) + len += w; + } + } + + return len; +} + +int +draw(const struct DrawState* state) +{ + assert(state != NULL); + if (draw_list_visible(state)) + return 1; + if (state->show_help) + if (draw_help(state)) + return 1; + if (state->show_edit) + if (draw_editbox(state)) + return 1; + if (state->show_search) + if (draw_search(state)) + return 1; + if (draw_status(state)) + return 1; + return 0; +} + +void +draw_box(const int startx, const int endx, const int starty, const int endy, + const uint16_t fg, const uint16_t bg) +{ + int x, y; + + tb_set_cell(startx, starty, BORDER_NW, fg, bg); + for (x = startx + 1; x < endx - 1; x++) + tb_set_cell(x, starty, BORDER_HOR, fg, bg); + tb_set_cell(endx - 1, starty, BORDER_NE, fg, bg); + + for (y = starty + 1; y < endy - 2; y++) + { + tb_set_cell(startx, y, BORDER_VER, fg, bg); + for (x = startx + 1; x < endx - 1; x++) + tb_set_cell(x, y, ' ', fg, bg); + tb_set_cell(endx - 1, y, BORDER_VER, fg, bg); + tb_set_cell(endx, y, ' ', colors[C_SHADOW].fg, + colors[C_SHADOW].bg); + } + + tb_set_cell(startx, endy - 2, BORDER_SW, fg, bg); + for (x = startx + 1; x < endx - 1; x++) + tb_set_cell(x, endy - 2, BORDER_HOR, fg, bg); + tb_set_cell(endx - 1, endy - 2, BORDER_SE, fg, bg); + tb_set_cell(endx, endy - 2, ' ', colors[C_SHADOW].fg, + colors[C_SHADOW].bg); + + for (x = startx + 1; x < endx + 1; x++) + tb_set_cell(x, endy - 1, ' ', colors[C_SHADOW].fg, + colors[C_SHADOW].bg); +} + +int +draw_help(const struct DrawState* state) +{ + int startx, endx, starty, endy; + int w, h; + int row = 0; + int maxx, maxy; + + assert(state != NULL); + w = state->help_width; + h = HELP_HEIGHT; + maxx = state->maxx; + maxy = state->maxy; + + startx = w > maxx ? 0 : (maxx - w) / 2; + endx = w > maxx ? maxx - 1 : startx + w; + + starty = h > maxy - 1 ? 0 : (maxy - h) / 2; + endy = h > maxy - 1 ? maxy - 2 : starty + h; + + draw_box(startx, endx, starty, endy, colors[C_DLG].fg, colors[C_DLG].bg); + if (draw_string(startx, starty, colors[C_TITLE].fg, colors[C_TITLE].bg, + help_title, strlen(help_title) + 2, w, 1, 1, CENTER, 0, 0, + NULL, 0, 0)) + return 1; + while ((size_t)row < LEN(help)) + { + if (draw_string(startx + 1, starty + 1 + row, colors[C_DLG].fg, + colors[C_DLG].bg, help[row], w - 2, w - 2, 1, 1, + LEFT, 0, 1, NULL, 0, 0)) + return 1; + row++; + } + tb_hide_cursor(); + return 0; +} + +int +draw_editbox(const struct DrawState* state) +{ + struct PoEntry* entry; + char flags_buf[MAXFLAGSBUF]; + char sbuf[MAXBUFLINE]; + ssize_t dlen; + uint16_t fg, bg; + int startx, endx, starty, endy; + int w, h; + int maxx, maxy; + + assert(state != NULL); + w = state->edit_width; + h = EDIT_HEIGHT; + maxx = state->maxx; + maxy = state->maxy; + entry = state->entries + state->msgid_number - 1; + dlen = 0; + + format_flags(flags_buf, MAXFLAGSBUF, entry, 1); + + startx = w > maxx ? 0 : (maxx - w) / 2; + endx = w > maxx ? maxx - 1 : startx + w; + + starty = h > maxy - 1 ? 0 : (maxy - h) / 2; + endy = h > maxy - 1 ? maxy - 2 : starty + h; + + draw_box(startx, endx, starty, endy, colors[C_DLG].fg, colors[C_DLG].bg); + if (draw_string(startx + 1, starty, colors[C_TITLE].fg, + colors[C_TITLE].bg, editbox_title, strlen(editbox_title) + 2, + w - 2, 1, 1, CENTER, 0, 0, NULL, 0, 0)) + return 1; + + fg = state->edit_info_focused ? colors[C_EDIT_MSGID_FOCUS].fg + : colors[C_EDIT_MSGID].fg; + bg = state->edit_info_focused ? colors[C_EDIT_MSGID_FOCUS].bg + : colors[C_EDIT_MSGID].bg; + + draw_rect(startx + 1, endx - 1, starty + 1, endy - h / 2 - 1, fg, bg); + for (ssize_t i = 0; i < (h - 3) / 2 + 1 + && (ssize_t)state->info_first_shown.row + i + < state->info_rows_count; + i++) + { + struct BufferLine* curline + = &state->info_buffer[state->info_first_shown.row + i]; + if (!curline || !curline->text) + return 1; + dlen = display_length(curline->text, curline->length); + if (dlen > state->info_first_shown.col) + u32_draw_string(startx + 1, starty + 1 + i, + curline->fg == 0 ? fg : curline->fg, + curline->bg == 0 ? bg : curline->bg, + curline->text, w - 2, w - 2, 1, 1, LEFT, + state->info_first_shown.col, 1, NULL, 0, 0); + } + + if (state->info_first_shown.row > 0) + tb_set_cell(endx - 2, starty + 1, ARROW_UP, fg, bg); + if (state->info_first_shown.row + (h - 3) / 2 < state->info_rows_count) + tb_set_cell(endx - 2, starty + (h - 3) / 2, ARROW_DOWN, fg, bg); + + fg = state->edit_info_focused ? colors[C_EDIT_MSGSTR].fg + : colors[C_EDIT_MSGSTR_FOCUS].fg; + bg = state->edit_info_focused ? colors[C_EDIT_MSGSTR].bg + : colors[C_EDIT_MSGSTR_FOCUS].bg; + + draw_rect(startx + 1, endx - 1, endy - h / 2 - 1, endy - 2, fg, bg); + for (ssize_t i = 0; i < (h - 3) / 2 + 1 + && (ssize_t)state->input_first_shown.row + i + < state->input_rows_count; + i++) + { + struct BufferLine* curline + = &state->input_buffer[state->input_first_shown.row + i]; + if (!curline || !curline->text) + return 1; + dlen = display_length(curline->text, curline->length); + if (dlen > state->input_first_shown.col) + u32_draw_string(startx + 1, endy - h / 2 - 1 + i, fg, + bg, curline->text, w - 2, w - 2, 1, 1, LEFT, + state->input_first_shown.col, 1, NULL, 0, 0); + } + + if (state->edit_info_focused) + tb_hide_cursor(); + else + tb_set_cursor(startx + 2 + state->input_display_column + - state->input_first_shown.col, + endy - h / 2 - 1 + state->coord.row + - state->input_first_shown.row); + + if (entry->msgstr_count > 1) + snprintf(sbuf, MAXBUFLINE, "%d[%d]:%d[%d]/%ld (plural: %d/%d)", + state->input_display_column, + state->input_first_shown.col, state->coord.row, + state->input_first_shown.row, state->input_rows_count, + state->msgstr_index + 1, entry->msgstr_count); + else + snprintf(sbuf, MAXBUFLINE, "%d[%d]:%d[%d]/%ld", + state->input_display_column, + state->input_first_shown.col, state->coord.row, + state->input_first_shown.row, state->input_rows_count); + draw_string(startx + 1, endy - 2, colors[C_DLG].fg, colors[C_DLG].bg, + sbuf, strlen(sbuf) + 2, w - 2, 1, 1, LEFT, 0, 0, NULL, 0, 0); + if (entry->flags != FL_NONE) + if (draw_string(endx - MAXFLAGSBUF - 4 - 5, endy - 2, + colors[C_DLG].fg, colors[C_DLG].bg, flags_buf, + MAXFLAGSBUF + 4 - 1, MAXFLAGSBUF + 4 - 1, 0, 0, + LEFT, 0, 0, NULL, 0, 0)) + return 1; + return 0; +} + +int +draw_list_visible(const struct DrawState* state) +{ + const struct PoEntry* current = NULL; + ssize_t i = 0; + int flags_len; + int max_screen_entries = 0; + int cy = 0; + + assert(state != NULL); + + if (state->msgid_count == 0) + return 0; + + max_screen_entries = state->maxy - 1; + i = state->first_shown_msgid; + + while (i <= state->real_msgid_count && cy < max_screen_entries) + { + char flags_buf[MAXFLAGSBUF]; + int sel; + + current = state->entries + i - 1; + if (current->obsolete) + { + i++; + continue; + } + + sel = i == state->msgid_number; + + if (sel) + draw_rect(0, state->maxx - 1, cy, cy + 1, + colors[C_LIST_SEL].fg, colors[C_LIST_SEL].bg); + + format_flags(flags_buf, MAXFLAGSBUF, current, 0); + flags_len = strlen(flags_buf) + 1; + + if (draw_string(0, cy, + sel ? colors[C_LIST_FLAGS_SEL].fg + : colors[C_LIST_FLAGS].fg, + sel ? colors[C_LIST_FLAGS_SEL].bg + : colors[C_LIST_FLAGS].bg, + flags_buf, flags_len, flags_len, 2, 0, LEFT, 0, 1, + NULL, 0, 0)) + return 1; + if (current->msgid) + u32_draw_string(flags_len, cy, + sel ? colors[C_LIST_MSGID_SEL].fg + : colors[C_LIST_MSGID].fg, + sel ? colors[C_LIST_MSGID_SEL].bg + : colors[C_LIST_MSGID].bg, + current->msgid, state->maxx / 2 - flags_len, + state->maxx / 2 - flags_len, 2, 0, LEFT, 0, 1, + state->search, colors[C_SEARCH_HL].fg, + colors[C_SEARCH_HL].bg); + else if (current == state->entries) /* special */ + draw_string(flags_len, cy, + sel ? colors[C_LIST_MSGID_SPECIAL_SEL].fg + : colors[C_LIST_MSGID_SPECIAL].fg, + sel ? colors[C_LIST_MSGID_SPECIAL_SEL].bg + : colors[C_LIST_MSGID_SPECIAL].bg, + first_entry_msgid, state->maxx / 2 - flags_len, + state->maxx / 2 - flags_len, 2, 0, LEFT, 0, 1, + NULL, 0, 0); + + /* don't use msgstr_index as then the entire list will shift + * when changing currently displayed plural form msgstr in an + * edit box */ + if (current->msgstr && current->msgstr_count > 0) + u32_draw_string(state->maxx / 2, cy, + current->warning + ? (sel ? colors[C_LIST_MSGSTR_SEL_WARN].fg + : colors[C_LIST_MSGSTR_WARN].fg) + : (sel ? colors[C_LIST_MSGSTR_SEL].fg + : colors[C_LIST_MSGSTR].fg), + current->warning + ? (sel ? colors[C_LIST_MSGSTR_SEL_WARN].bg + : colors[C_LIST_MSGSTR_WARN].bg) + : (sel ? colors[C_LIST_MSGSTR_SEL].bg + : colors[C_LIST_MSGSTR].bg), + *(current->msgstr), state->maxx / 2, + state->maxx / 2, 2, 2, LEFT, 0, 1, + state->search, colors[C_SEARCH_HL].fg, + colors[C_SEARCH_HL].bg); + i++; + cy++; + } + return 0; +} + +void +draw_rect(const int startx, const int endx, const int starty, const int endy, + const uint16_t fg, const uint16_t bg) +{ + for (int y = starty; y < endy; y++) + for (int x = startx; x < endx; x++) + tb_set_cell(x, y, ' ', fg, bg); +} + +int +draw_search(const struct DrawState* state) +{ + ssize_t len; + ssize_t dlen = 0; + int startx, endx, starty, endy; + int w, h; + int maxx, maxy; + + assert(state != NULL); + w = state->search_width; + h = SEARCH_HEIGHT; + maxx = state->maxx; + maxy = state->maxy; + len = u32_strlen(state->search); + + startx = w > maxx ? 0 : (maxx - w) / 2; + endx = w > maxx ? maxx - 1 : startx + w; + + starty = maxy - 1 - h; + endy = maxy - 1; + + draw_box(startx, endx, starty, endy, colors[C_SEARCH].fg, + colors[C_SEARCH].bg); + draw_rect(startx + 1, endx - 1, starty + 1, endy - 2, + colors[C_SEARCH_EDIT].fg, colors[C_SEARCH_EDIT].bg); + dlen = display_length(state->search, len); + if (dlen > state->search_first_shown_column) + u32_draw_string(startx + 1, starty + 1, + colors[C_SEARCH_EDIT].fg, colors[C_SEARCH_EDIT].bg, + state->search, w - 2, w - 2, 1, 1, LEFT, + state->search_first_shown_column, 1, NULL, 0, 0); + tb_set_cursor(startx + 2 + state->search_display_column + - state->search_first_shown_column, + starty + 1); + return 0; +} + +int +draw_status(const struct DrawState* state) +{ + const struct StatusSegment* pseg; + char* buf = NULL; + ssize_t seg_size; + int current_start; + int maxx; + + assert(state != NULL); + maxx = state->maxx; + seg_size = maxx / LEN(status_segments); + current_start = 0; + pseg = status_segments; + + if (state->in_prompt && *state->prompt) + { + if (draw_string(0, state->maxy - 1, colors[C_PROMPT].fg, + colors[C_PROMPT].bg, state->prompt, + strlen(state->prompt) + 2, maxx, 1, 1, CENTER, 0, 1, + NULL, 0, 0)) + return 1; + } + else if (state->error) + { + if (draw_string(0, state->maxy - 1, colors[C_ERROR].fg, + colors[C_ERROR].bg, errors[state->error], + strlen(errors[state->error]) + 2, maxx, 1, 1, + CENTER, 0, 1, NULL, 0, 0)) + return 1; + } + else + while (pseg < status_segments + LEN(status_segments)) + { + int last = pseg - status_segments + == LEN(status_segments) - 1; + buf = calloc(limits[L_LINE_MAX], 1); + if (!buf) + return 1; + + *buf = 0; + if (pseg->callback) + pseg->callback(buf, limits[L_LINE_MAX], + pseg->format, seg_size, state); + else + memccpy(buf, pseg->format, 0, + limits[L_LINE_MAX]); + if (pseg == status_segments + LEN(status_segments) - 1) + seg_size = maxx - 1 - current_start; + if (draw_string(current_start, state->maxy - 1, + colors[pseg->color_index].fg, + colors[pseg->color_index].bg, buf, + MIN(seg_size, (ssize_t)(strlen(buf) + 2)), + last ? seg_size + 1 : seg_size, 1, 1, + pseg->alignment, 0, 1, NULL, 0, 0)) + { + free(buf); + return 1; + } + free(buf); + pseg++; + current_start += seg_size; + } + return 0; +} + +/* mc - (max cols) pack text and padding into this many columns + * fc - (fill cols) this many columns from the left/right/center will be + * filled + * p_? - (padding) this many cells will be skipped from ? side + * ds - (display start) string starts being displayed from this display column + * counted from the start of the string; an "entry point" joining string + * index with screen + * fill - boolean; 1 = fill fc, 0 = don't fill fc + * srch - search string; if empty or NULL no highlighting happens + * sfg - fg color of search highlght + * sbg - bg color of search highlight + * */ +int +draw_string(const int x, const int y, const uint16_t fg, const uint16_t bg, + const char* s, const int mc, const int fc, const int p_l, const int p_r, + const Alignment align, const int ds, const int fill, + const uint32_t* srch, const uint16_t sfg, const uint16_t sbg) +{ + uint32_t* us = NULL; + assert(s != NULL); + us = calloc(strlen(s) + 1, sizeof(uint32_t)); + if (!us) + return 1; + u8_string_to_unicode(us, s, mc); + u32_draw_string(x, y, fg, bg, us, mc, fc, p_l, p_r, align, ds, fill, + srch, sfg, sbg); + free(us); + return 0; +} + +void +format_filename(char* result, ssize_t result_size, const char* format, + const ssize_t seg_size, const struct DrawState* state) +{ + char* basename = NULL; + const char* pbase = NULL; + int dirty; + + assert((state != NULL) && (format != NULL)); + if (!result || seg_size < 4) + return; + + pbase = u8_basename(state->filename); + basename = strndup(pbase, strlen(pbase) + 1); + dirty = (state->show_edit && state->dirty_before_edit) || state->dirty; + if ((ssize_t)(u8_strlen(basename) + 2) >= seg_size) + { + if (dirty) + basename[seg_size - 4] = 0; + else + basename[seg_size - 3] = 0; + snprintf(result, result_size, format, basename, + dirty ? "…*" : "…"); + } + else + snprintf(result, result_size, format, basename, + dirty ? "*" : ""); + free(basename); +} + +void +format_msgs(char* result, ssize_t result_size, const char* format, + const ssize_t seg_size, const struct DrawState* state) +{ + assert((state != NULL) && (format != NULL)); + UNUSED(seg_size); + if (!result) + return; + + snprintf(result, result_size, format, + state ? real_msgid_to_display_msgid(state) : 0, + state ? state->msgid_count : 0, + state ? state->untranslated_count : 0, + state ? state->fuzzy_count : 0, + state ? state->obsolete_count : 0); +} + +void +free_bufferline(struct BufferLine* bl) { if (!bl) return; + free(bl->text); bl->text = NULL; bl->length = 0; bl->fg = 0; @@ -73,11 +628,27 @@ init_bufferline(struct BufferLine* bl) } void -free_bufferline(struct BufferLine* bl) +free_drawstate(struct DrawState* state) +{ + ssize_t row = 0; + assert(state != NULL); + for (row = 0; row < state->input_rows_count; row++) + free_bufferline(state->input_buffer + row); + free(state->input_buffer); + for (row = 0; row < state->info_rows_count; row++) + free_bufferline(state->info_buffer + row); + free(state->info_buffer); + for (row = 0; row < state->real_msgid_count; row++) + free_po_entry(state->entries + row); + free(state->search); + free(state->entries); +} + +void +init_bufferline(struct BufferLine* bl) { if (!bl) return; - free(bl->text); bl->text = NULL; bl->length = 0; bl->fg = 0; @@ -85,38 +656,56 @@ free_bufferline(struct BufferLine* bl) } void -init_drawstate(struct DrawState* state, char* error, char* filename, - char* prompt) +init_coord(struct Coord* c, const int col, const int row) { - state->error = error; - state->filename = filename; - state->real_filename = NULL; - state->backup_filename = NULL; - state->rfn_len = 0; - state->search = NULL; - state->input_buffer = NULL; - state->input_changed_fuzzy = UNCHANGED; - state->input_column = 0; - state->input_display_column = 0; - state->input_first_shown_column = 0; - state->input_first_shown_row = 0; - state->input_row = 0; - state->input_rows_count = 0; - state->saved_column = -1; - state->info_buffer = NULL; - state->info_first_shown_column = 0; - state->info_first_shown_row = 0; + assert(c != NULL); + c->col = col; + c->row = row; +} + +void +init_drawstate(struct DrawState* state, char* filename, char* prompt) +{ + assert(state != NULL); + state->error = ERR_NONE; + state->action = ACT_IDLE; + state->prompt = prompt; + state->filename = filename; + state->orig_real_filename = NULL; + state->real_filename = NULL; + state->backup_filename = NULL; + state->rfn_len = 0; + state->search = NULL; + state->input_buffer = NULL; + state->buffer_size = 0; + init_coord(&state->coord, 0, 0); + init_coord(&state->first_shown, 0, 0); + init_coord(&state->input_first_shown, 0, 0); + // state->input_column = 0; + // state->input_row = 0; + /*state->input_first_shown_column = 0; + state->input_first_shown_row = 0;*/ + state->input_rows_count = 0; + state->input_display_column = 0; + state->input_changed_fuzzy = UNCHANGED; + state->saved_column = -1; + state->info_buffer = NULL; + init_coord(&state->info_first_shown, 0, 0); + /*state->info_first_shown_column = 0; + state->info_first_shown_row = 0;*/ state->info_rows_count = 0; + state->info_rows_maxlen = 0; state->help_width = calculate_help_width(state); state->edit_width = calculate_edit_width(state); state->search_width = calculate_search_width(state); - state->paste_buffer = NULL; - state->paste_rows_count = 0; + state->paste = NULL; + state->paste_count = 0; + state->paste_size = 0; state->search_column = 0; state->search_first_shown_column = 0; state->search_display_column = 0; + state->entries = NULL; state->nplurals = 2; - state->prompt = prompt; state->prompt_callback = NULL; state->running = 1; state->dirty = 0; @@ -127,140 +716,31 @@ init_drawstate(struct DrawState* state, char* error, char* filename, state->show_edit = 0; state->edit_info_focused = 0; state->show_search = 0; + state->recalculate_size = 1; + state->maxx = 0; + state->maxy = 0; state->msgid_number = state->msgid_count > 0 ? 1 : 0; + state->msgid_count = 0; + state->msgid_size = 0; state->fuzzy_count = 0; state->untranslated_count = 0; state->obsolete_count = 0; state->obsolete_at_end_count = 0; + state->real_msgid_count = 0; state->msgstr_index = 0; state->first_shown_msgid = state->msgid_number; state->backup_possible = 1; } -void -free_drawstate(struct DrawState* state) -{ - size_t i = 0; - - for (i = 0; i < state->input_rows_count; i++) - free_bufferline(state->input_buffer + i); - free(state->input_buffer); - for (i = 0; i < state->paste_rows_count; i++) - free_bufferline(state->paste_buffer + i); - free(state->paste_buffer); - for (i = 0; i < state->info_rows_count; i++) - free_bufferline(state->info_buffer + i); - free(state->info_buffer); - for (i = 0; i < state->real_msgid_count; i++) - free_po_entry(state->entries + i); - free(state->search); - free(state->entries); -} - -void -format_filename(char* result, size_t result_size, const char* format, - const struct DrawState* state) -{ - if (!result) - return; - snprintf(result, result_size, format, u8_basename(state->filename), - state->show_edit ? ( - (state->dirty_before_edit || state->dirty) ? "*" : "") - : (state->dirty ? "*" : "")); -} - -size_t real_msgid_to_display_msgid(const struct DrawState* state); - -void -format_msgs(char* result, size_t result_size, const char* format, - const struct DrawState* state) -{ - if (!result) - return; - - snprintf(result, result_size, format, - state ? real_msgid_to_display_msgid(state) : 0, - state ? state->msgid_count : 0, - state ? state->untranslated_count : 0, - state ? state->fuzzy_count : 0, - state ? state->obsolete_count : 0); -} - -struct DrawState* -set_nplurals(struct DrawState* state, struct PoEntry* entry) -{ - if (!entry || !entry->plural_forms) - return NULL; - - int read_number = 0; - uint32_t* ppf = entry->plural_forms; - char token[MAXBUFLINE]; - char* ptoken = token; - - *ptoken = 0; - while (*ppf) - { - if (u32_starts_with(ppf, (uint32_t*)L"nplurals=")) - { - ppf += strlen("nplurals="); - read_number = 1; - } - else if (read_number && *ppf >= '0' && *ppf <= '9') - { - *ptoken++ = *ppf++; - *ptoken = 0; - } - else if (read_number && *ppf == ';') - { - *ptoken = 0; - ptoken = token; - break; - } - else - ppf++; - } - if (*ptoken) - { - state->nplurals = strtol(ptoken, NULL, 0); - if (errno) - return NULL; - } - - return state; -} - -/* Calculates display length of an input buffer line. - * max_index = the input index up to which the length is calculated - */ -size_t -display_length(const struct DrawState* state, uint32_t* buffer, - const size_t max_index) -{ - size_t len = 0; - uint32_t* pbuf = buffer; - - for (size_t i = 0; *pbuf && i < max_index; i++) - { - pbuf = buffer + i; - if (*pbuf == '\t') - len += TAB_SIZE - (len % TAB_SIZE); - else - len++; - } - - return len; -} - /* Calculates input length from a display length. * max_index = the display index up to which the length is calculated */ -size_t -input_length(const struct DrawState* state, uint32_t* buffer, - const size_t max_index) +ssize_t +input_length(uint32_t* buffer, const ssize_t max_index) { - size_t len = 0; uint32_t* pbuf = buffer; - size_t i = 0; + ssize_t len = 0; + ssize_t i = 0; for (i = 0; *pbuf && len < max_index; i++) { @@ -274,89 +754,78 @@ input_length(const struct DrawState* state, uint32_t* buffer, return i; } -int -calculate_help_width(const struct DrawState* state) +size_t +real_msgid_to_display_msgid(const struct DrawState* state) { - return state->maxx > HELP_WIDTH + 2 ? HELP_WIDTH : state->maxx - 2; -} + struct PoEntry* current = state->entries; + size_t ret = 1; -int -calculate_edit_width(const struct DrawState* state) -{ - return state->maxx > EDIT_WIDTH + 3 ? EDIT_WIDTH : state->maxx - 3; -} + if (!state->msgid_count) + return 0; -int -calculate_search_width(const struct DrawState* state) -{ - return state->maxx > SEARCH_WIDTH + 2 ? SEARCH_WIDTH : state->maxx - 2; + while (current != state->entries + state->msgid_number - 1) + { + if (current != state->entries + state->msgid_number - 1 + && !current->obsolete) + ret++; + current++; + } + + return ret; } void -draw_rect(const int startx, const int endx, const int starty, const int endy, - const uint16_t fg, const uint16_t bg) +recalculate_size(struct DrawState* state) { - for (int y = starty; y < endy; y++) - for (int x = startx; x < endx; x++) - tb_change_cell(x, y, ' ', fg, bg); + assert(state != NULL); + state->maxx = tb_width(); + state->maxy = tb_height(); + state->help_width = calculate_help_width(state); + state->edit_width = calculate_edit_width(state); + state->search_width = calculate_search_width(state); } -void -draw_box(const int startx, const int endx, const int starty, const int endy, - const uint16_t fg, const uint16_t bg) +struct DrawState* +set_nplurals(struct DrawState* state, struct PoEntry* entry) { - int x, y; + if (!entry || !entry->plural_forms) + return NULL; - tb_change_cell(startx, starty, BORDER_NW, fg, bg); - for (x = startx + 1; x < endx - 1; x++) - tb_change_cell(x, starty, BORDER_HOR, fg, bg); - tb_change_cell(endx - 1, starty, BORDER_NE, fg, bg); + int read_number = 0; + uint32_t* ppf = entry->plural_forms; + char token[MAXBUFLINE]; + char* ptoken = token; - for (y = starty + 1; y < endy - 2; y++) + *ptoken = 0; + while (*ppf) { - tb_change_cell(startx, y, BORDER_VER, fg, bg); - for (x = startx + 1; x < endx - 1; x++) - tb_change_cell(x, y, ' ', fg, bg); - tb_change_cell(endx - 1, y, BORDER_VER, fg, bg); - tb_change_cell(endx, y, ' ', SHADOW_FG, SHADOW_BG); + if (u32_starts_with(ppf, (uint32_t*)L"nplurals=")) + { + ppf += strlen("nplurals="); + read_number = 1; + } + else if (read_number && *ppf >= '0' && *ppf <= '9') + { + *ptoken++ = *ppf++; + *ptoken = 0; + } + else if (read_number && *ppf == ';') + { + *ptoken = 0; + ptoken = token; + break; + } + else + ppf++; + } + if (*ptoken) + { + state->nplurals = strtol(ptoken, NULL, 0); + if (errno) + return NULL; } - tb_change_cell(startx, endy - 2, BORDER_SW, fg, bg); - for (x = startx + 1; x < endx - 1; x++) - tb_change_cell(x, endy - 2, BORDER_HOR, fg, bg); - tb_change_cell(endx - 1, endy - 2, BORDER_SE, fg, bg); - tb_change_cell(endx, endy - 2, ' ', SHADOW_FG, SHADOW_BG); - - for (x = startx + 1; x < endx + 1; x++) - tb_change_cell(x, endy - 1, ' ', SHADOW_FG, SHADOW_BG); -} - -/* mc - (max cols) pack text and padding into this many columns - * fc - (fill cols) this many columns from the left/right/center will be - * filled - * p_? - (padding) this many cells will be skipped from ? side - * ds - (display start) string starts being displayed from this display column - * counted from the start of the string; an "entry point" joining string - * index with screen - * fill - boolean; 1 = fill fc, 0 = don't fill fc - * srch - search string; if empty or NULL no highlighting happens - * sfg - fg color of search highlght - * sbg - bg color of search highlight - * */ -const char* -draw_string(const int x, const int y, const uint16_t fg, const uint16_t bg, - const char* s, const int mc, const int fc, const int p_l, const int p_r, - const Alignment align, const int ds, const int fill, - const uint32_t* srch, const uint16_t sfg, const uint16_t sbg) -{ - uint32_t* us = calloc(strlen(s) + 1, sizeof(uint32_t)); - if (!us) - return NULL; - u8_string_to_unicode(us, s, mc); - u32_draw_string(x, y, fg, bg, us, mc, fc, p_l, p_r, align, ds, fill, - srch, sfg, sbg); - free(us); - return s; + return state; } /* mc - (max cols) pack text and padding into this many columns @@ -432,11 +901,11 @@ u32_draw_string(const int x, const int y, const uint16_t fg, const uint16_t bg, /* fill */ if (fill) for (int j = 0; j < fc; j++) - tb_change_cell(x + j, y, ' ', fg, bg); + tb_set_cell(x + j, y, ' ', fg, bg); /* left padding */ for (int j = 0; j < p_l; j++) - tb_change_cell(x + d + j, y, ' ', fg, bg); + tb_set_cell(x + d + j, y, ' ', fg, bg); sc = 0; @@ -478,8 +947,7 @@ u32_draw_string(const int x, const int y, const uint16_t fg, const uint16_t bg, for (int j = 0; j < ssc - (ssc % ts) + ts - ds; j++) - tb_change_cell(x + d + p_l + sc - + j, + tb_set_cell(x + d + p_l + sc + j, y, ' ', cfg, cbg); sc += ssc + (ts - (ssc % ts)) - ds; hctl -= ssc + (ts - (ssc % ts)) - ds; @@ -510,8 +978,7 @@ u32_draw_string(const int x, const int y, const uint16_t fg, const uint16_t bg, if (ssc + (ts - (ssc % ts)) >= ds + c) { for (int j = 0; j < ds + c - ssc; j++) - tb_change_cell(x + d + p_l + sc - + j, + tb_set_cell(x + d + p_l + sc + j, y, ' ', cfg, cbg); sc = c; ssc = ds + c; @@ -531,8 +998,7 @@ u32_draw_string(const int x, const int y, const uint16_t fg, const uint16_t bg, * ds * */ for (int j = 0; j < ts - (ssc % ts); j++) - tb_change_cell(x + d + p_l + sc - + j, + tb_set_cell(x + d + p_l + sc + j, y, ' ', cfg, cbg); sc += ts - (ssc % ts); hctl -= ts - (ssc % ts); @@ -547,7 +1013,7 @@ u32_draw_string(const int x, const int y, const uint16_t fg, const uint16_t bg, if (ssc >= ds) { if (sc < c) - tb_change_cell(x + d + p_l + sc, y, *ps, + tb_set_cell(x + d + p_l + sc, y, *ps, cfg, cbg); sc++; hctl--; @@ -566,342 +1032,5 @@ u32_draw_string(const int x, const int y, const uint16_t fg, const uint16_t bg, /* right padding */ for (int j = 0; j < p_r; j++) - tb_change_cell(x + d + p_l + c + j, y, ' ', fg, bg); -} - -const struct DrawState* -draw_help(const struct DrawState* state) -{ - if (!state) - return NULL; - - int startx, endx, starty, endy; - int w = state->help_width; - int h = HELP_HEIGHT; - int row = 0; - int maxx = state->maxx; - int maxy = state->maxy; - - startx = w > maxx ? 0 : (maxx - w) / 2; - endx = w > maxx ? maxx - 1 : startx + w; - - starty = h > maxy - 1 ? 0 : (maxy - h) / 2; - endy = h > maxy - 1 ? maxy - 2 : starty + h; - - draw_box(startx, endx, starty, endy, DLG_FG, DLG_BG); - if (!draw_string(startx, starty, TITLE_FG, TITLE_BG, help_title, - strlen(help_title) + 2, w, 1, 1, CENTER, 0, 0, NULL, 0, 0)) - return NULL; - while (row < LEN(help)) - { - if (!draw_string(startx + 1, starty + 1 + row, DLG_FG, DLG_BG, - help[row], w - 2, w - 2, 1, 1, LEFT, 0, 1, NULL, 0, - 0)) - return NULL; - row++; - } - return state; -} - -const struct DrawState* -draw_editbox(const struct DrawState* state) -{ - if (!state) - return NULL; - - int startx, endx, starty, endy; - int w = state->edit_width; - int h = EDIT_HEIGHT; - int maxx = state->maxx; - int maxy = state->maxy; - char flags_buf[MAXFLAGSBUF]; - struct PoEntry* entry = state->entries + state->msgid_number - 1; - size_t dlen = 0; - - format_flags(flags_buf, MAXFLAGSBUF, entry, 1); - - startx = w > maxx ? 0 : (maxx - w) / 2; - endx = w > maxx ? maxx - 1 : startx + w; - - starty = h > maxy - 1 ? 0 : (maxy - h) / 2; - endy = h > maxy - 1 ? maxy - 2 : starty + h; - - draw_box(startx, endx, starty, endy, DLG_FG, DLG_BG); - if (!draw_string(startx + 1, starty, TITLE_FG, TITLE_BG, editbox_title, - strlen(editbox_title) + 2, w - 2, 1, 1, CENTER, 0, 0, NULL, - 0, 0)) - return NULL; - - uint16_t fg = state->edit_info_focused ? EDIT_MSGID_FOCUS_FG - : EDIT_MSGID_FG; - uint16_t bg = state->edit_info_focused ? EDIT_MSGID_FOCUS_BG - : EDIT_MSGID_BG; - - draw_rect(startx + 1, endx - 1, starty + 1, endy - h / 2 - 1, fg, bg); - for (size_t i = 0; i < (h - 3) / 2 + 1 - && state->info_first_shown_row + i < state->info_rows_count; - i++) - { - struct BufferLine* curline - = &state->info_buffer[state->info_first_shown_row + i]; - if (!curline || !curline->text) - return NULL; - dlen = display_length(state, curline->text, curline->length); - if (dlen > state->info_first_shown_column) - u32_draw_string(startx + 1, starty + 1 + i, - curline->fg == 0 ? fg : curline->fg, - curline->bg == 0 ? bg : curline->bg, - curline->text, w - 2, w - 2, 1, 1, LEFT, - state->info_first_shown_column, 1, NULL, 0, 0); - } - - if (state->info_first_shown_row > 0) - u32_draw_string(endx - 2, starty + 1, fg, bg, - (uint32_t*)L"\u2191", 1, 1, 0, 0, LEFT, 0, 0, NULL, 0, - 0); - if (state->info_first_shown_row + (h - 3) / 2 < state->info_rows_count) - u32_draw_string(endx - 2, starty + (h - 3) / 2, fg, bg, - (uint32_t*)L"\u2193", 1, 1, 0, 0, LEFT, 0, 0, NULL, 0, - 0); - - fg = state->edit_info_focused ? EDIT_MSGSTR_FG : EDIT_MSGSTR_FOCUS_FG; - bg = state->edit_info_focused ? EDIT_MSGSTR_BG : EDIT_MSGSTR_FOCUS_BG; - - draw_rect(startx + 1, endx - 1, endy - h / 2 - 1, endy - 2, fg, bg); - for (size_t i = 0; i < (h - 3) / 2 + 1 - && state->input_first_shown_row + i < state->input_rows_count; - i++) - { - struct BufferLine* curline - = &state->input_buffer[state->input_first_shown_row + i]; - if (!curline || !curline->text) - return NULL; - dlen = display_length(state, curline->text, curline->length); - if (dlen > state->input_first_shown_column) - u32_draw_string(startx + 1, endy - h / 2 - 1 + i, fg, - bg, curline->text, w - 2, w - 2, 1, 1, LEFT, - state->input_first_shown_column, 1, NULL, 0, 0); - } - - if (state->edit_info_focused) - tb_hide_cursor(); - else - tb_set_cursor(startx + 2 + state->input_display_column - - state->input_first_shown_column, - endy - h / 2 - 1 + state->input_row - - state->input_first_shown_row); - - char sbuf[MAXBUFLINE]; - if (entry->msgstr_count > 1) - snprintf(sbuf, MAXBUFLINE, "%d[%d]:%d[%d]/%d (plural: %d/%d)", - state->input_display_column, - state->input_first_shown_column, state->input_row, - state->input_first_shown_row, state->input_rows_count, - state->msgstr_index + 1, entry->msgstr_count); - else - snprintf(sbuf, MAXBUFLINE, "%d[%d]:%d[%d]/%d", - state->input_display_column, - state->input_first_shown_column, state->input_row, - state->input_first_shown_row, state->input_rows_count); - draw_string(startx + 1, endy - 2, DLG_FG, DLG_BG, sbuf, - strlen(sbuf) + 2, w - 2, 1, 1, LEFT, 0, 0, NULL, 0, 0); - if (entry->flags != FL_NONE) - if (!draw_string(endx - MAXFLAGSBUF - 4 - 5, endy - 2, DLG_FG, - DLG_BG, flags_buf, MAXFLAGSBUF + 4 - 1, - MAXFLAGSBUF + 4 - 1, 0, 0, LEFT, 0, 0, NULL, 0, 0)) - return NULL; - return state; -} - -const struct DrawState* -draw_search(const struct DrawState* state) -{ - int startx, endx, starty, endy; - int w = state->search_width; - int h = SEARCH_HEIGHT; - int maxx = state->maxx; - int maxy = state->maxy; - size_t len = u32_strlen(state->search); - size_t dlen = 0; - - startx = w > maxx ? 0 : (maxx - w) / 2; - endx = w > maxx ? maxx - 1 : startx + w; - - starty = maxy - 1 - h; - endy = maxy - 1; - - draw_box(startx, endx, starty, endy, SEARCH_FG, SEARCH_BG); - draw_rect(startx + 1, endx - 1, starty + 1, endy - 2, SEARCH_EDIT_FG, - SEARCH_EDIT_BG); - dlen = display_length(state, state->search, len); - if (dlen > state->search_first_shown_column) - u32_draw_string(startx + 1, starty + 1, SEARCH_EDIT_FG, - SEARCH_EDIT_BG, state->search, w - 2, w - 2, 1, 1, LEFT, - state->search_first_shown_column, 1, NULL, 0, 0); - tb_set_cursor(startx + 2 + state->search_display_column - - state->search_first_shown_column, - starty + 1); - return state; -} - -const struct DrawState* -draw_status(const struct DrawState* state) -{ - if (!state) - return NULL; - - char buf[MAXBUFLINE]; - int seg_size = state->maxx / 3; - int current_start = 0; - const struct StatusSegment* pseg = status_segments; - - if (state->in_prompt && state->prompt[0]) - { - if (!draw_string(0, state->maxy - 1, PROMPT_FG, PROMPT_BG, - state->prompt, strlen(state->prompt) + 2, - state->maxx, 1, 1, CENTER, 0, 1, NULL, 0, 0)) - return NULL; - } - else if (state->error && state->error[0]) - { - if (!draw_string(0, state->maxy - 1, ERROR_FG, ERROR_BG, - state->error, strlen(state->error) + 2, state->maxx, - 1, 1, CENTER, 0, 1, NULL, 0, 0)) - return NULL; - } - else - while (pseg < status_segments + LEN(status_segments)) - { - int last = pseg - status_segments - == LEN(status_segments) - 1; - buf[0] = 0; - if (pseg->callback) - pseg->callback(buf, MAXBUFLINE, pseg->format, - state); - else - strncpy(buf, pseg->format, MAXBUFLINE - 1); - buf[MAXBUFLINE - 1] = 0; - if (pseg == status_segments + LEN(status_segments) - 1) - seg_size = state->maxx - 1 - current_start; - if (!draw_string(current_start, state->maxy - 1, - pseg->fg, pseg->bg, buf, - MIN(seg_size, strlen(buf) + 2), - last ? seg_size + 1 : seg_size, 1, 1, - pseg->alignment, 0, 1, NULL, 0, 0)) - return NULL; - pseg++; - current_start += seg_size; - } - return state; -} - -const struct DrawState* -draw_list_visible(const struct DrawState* state) -{ - int flags_len; - const struct PoEntry* current = NULL; - int max_screen_entries = 0; - int cy = 0; - size_t i = 0; - - if (!state) - return NULL; - - if (state->msgid_count == 0) - return state; - - max_screen_entries = state->maxy - 1; - i = state->first_shown_msgid; - - while (i <= state->real_msgid_count && cy < max_screen_entries) - { - int sel; - char flags_buf[MAXFLAGSBUF]; - - current = state->entries + i - 1; - if (current->obsolete) - { - i++; - continue; - } - - sel = i == state->msgid_number; - - if (sel) - draw_rect(0, state->maxx - 1, cy, cy + 1, LIST_SEL_FG, - LIST_SEL_BG); - - format_flags(flags_buf, MAXFLAGSBUF, current, 0); - flags_len = strlen(flags_buf) + 1; - - if (!draw_string(0, cy, sel ? LIST_FLAGS_SEL_FG : LIST_FLAGS_FG, - sel ? LIST_FLAGS_SEL_BG : LIST_FLAGS_BG, flags_buf, - flags_len, flags_len, 2, 0, LEFT, 0, 1, NULL, 0, 0)) - return NULL; - if (current->msgid) - u32_draw_string(flags_len, cy, - sel ? LIST_MSGID_SEL_FG : LIST_MSGID_FG, - sel ? LIST_MSGID_SEL_BG : LIST_MSGID_BG, - current->msgid, state->maxx / 2 - flags_len, - state->maxx / 2 - flags_len, 2, 0, LEFT, 0, 1, - state->search, SEARCH_HL_FG, SEARCH_HL_BG); - else if (current == state->entries) /* special */ - draw_string(flags_len, cy, - sel ? LIST_MSGID_SPECIAL_SEL_FG - : LIST_MSGID_SPECIAL_FG, - sel ? LIST_MSGID_SPECIAL_SEL_BG - : LIST_MSGID_SPECIAL_BG, - first_entry_msgid, state->maxx / 2 - flags_len, - state->maxx / 2 - flags_len, 2, 0, LEFT, 0, 1, - NULL, 0, 0); - - /* don't use msgstr_index as then the entire list will shift - * when changing currently displayed plural form msgstr in an - * edit box */ - if (current->msgstr && current->msgstr_count > 0) - u32_draw_string(state->maxx / 2, cy, - current->warning - ? (sel ? LIST_MSGSTR_SEL_WARN_FG - : LIST_MSGSTR_WARN_FG) - : (sel ? LIST_MSGSTR_SEL_FG - : LIST_MSGSTR_FG), - current->warning - ? (sel ? LIST_MSGSTR_SEL_WARN_BG - : LIST_MSGSTR_WARN_BG) - : (sel ? LIST_MSGSTR_SEL_BG - : LIST_MSGSTR_BG), - *(current->msgstr), state->maxx / 2, - state->maxx / 2, 2, 2, LEFT, 0, 1, - state->search, SEARCH_HL_FG, SEARCH_HL_BG); - i++; - cy++; - } - return state; -} - -const struct DrawState* -draw(const struct DrawState* state) -{ - if (!state) - return NULL; - - if (!draw_list_visible(state)) - return NULL; - - if (state->show_help) - if (!draw_help(state)) - return NULL; - - if (state->show_edit) - if (!draw_editbox(state)) - return NULL; - - if (state->show_search) - if (!draw_search(state)) - return NULL; - - if (!draw_status(state)) - return NULL; - - return state; + tb_set_cell(x + d + p_l + c + j, y, ' ', fg, bg); } diff --git a/draw.h b/draw.h @@ -3,11 +3,11 @@ * See the file LICENSE for exact copyright and license details. */ #include <stddef.h> +#include <sys/types.h> #include <termios.h> #include <unistd.h> #include <wchar.h> -#define TB_OPT_V1_COMPAT #include "termbox.h" #define BORDER_NW ((uint32_t)0x250c) @@ -16,6 +16,8 @@ #define BORDER_SE ((uint32_t)0x2518) #define BORDER_VER ((uint32_t)0x2502) #define BORDER_HOR ((uint32_t)0x2500) +#define ARROW_UP ((uint32_t)0x2191) +#define ARROW_DOWN ((uint32_t)0x2193) #define EDIT_WIDTH 70 #define EDIT_HEIGHT 20 @@ -28,70 +30,124 @@ typedef enum { LEFT, CENTER, RIGHT } Alignment; +typedef enum { + C_CLEAR, + C_DLG, + C_EDIT_MSGID, + C_EDIT_MSGID_FOCUS, + C_EDIT_MSGID_HEADING, + C_EDIT_MSGSTR, + C_EDIT_MSGSTR_FOCUS, + C_ERROR, + C_FLAG, + C_LIST_FLAGS, + C_LIST_FLAGS_SEL, + C_LIST_MSGID, + C_LIST_MSGID_SEL, + C_LIST_MSGID_SPECIAL, + C_LIST_MSGID_SPECIAL_SEL, + C_LIST_MSGSTR, + C_LIST_MSGSTR_SEL, + C_LIST_MSGSTR_WARN, + C_LIST_MSGSTR_SEL_WARN, + C_LIST_SEL, + C_MSGID, + C_MSGSTR, + C_PROMPT, + C_SEARCH, + C_SEARCH_EDIT, + C_SEARCH_HL, + C_SHADOW, + C_STATUS_1, + C_STATUS_2, + C_STATUS_3, + C_TITLE +} ColorIndex; + typedef enum { UNCHANGED, SET, CLEARED } FlagChange; +struct Color { + uint16_t fg; + uint16_t bg; +}; + +struct Coord { + int col; + int row; +}; + struct BufferLine { uint32_t* text; - size_t length; + ssize_t length; /* both in uint32_t units */ + ssize_t size; uint16_t fg; uint16_t bg; }; struct DrawState { - char* error; char* prompt; char* filename; - char* real_filename; /* what symlink points to */ + char* orig_real_filename; /* what symlink points to */ + char* real_filename; /* directory holding the symlink prepended to it */ char* backup_filename; - size_t rfn_len; + ssize_t rfn_len; uint32_t* search; + int error; + int action; struct BufferLine* input_buffer; /* edit box */ - int input_column; - int input_row; - int input_first_shown_column; - int input_first_shown_row; - int input_rows_count; - int input_display_column; + ssize_t buffer_size; /* buffer parameter */ + struct Coord coord; /* buffer coordinates */ + struct Coord first_shown; /* display coordinates */ + struct Coord input_first_shown; /* display coordinates */ + /*int input_column; + int input_row;*/ + /*int input_first_shown_column; + int input_first_shown_row;*/ + ssize_t input_rows_count; /* buffer parameter */ + int input_display_column; /* display coordinate */ FlagChange input_changed_fuzzy; - int saved_column; + int saved_column; /* display coordinate */ struct BufferLine* info_buffer; /* information pane above * edit box */ - int info_first_shown_column; - int info_first_shown_row; + struct Coord info_first_shown; /* display coordinates */ + /*int info_first_shown_column; + int info_first_shown_row;*/ int info_rows_count; int info_rows_maxlen; - int help_width; - int edit_width; - int search_width; - struct BufferLine* paste_buffer; - int paste_rows_count; + int help_width; /* display parameter */ + int edit_width; /* display parameter */ + int search_width; /* display parameter */ + struct BufferLine* paste; + ssize_t paste_count; + ssize_t paste_size; int search_column; int search_first_shown_column; int search_display_column; struct PoEntry* entries; - long nplurals; - void (*prompt_callback)(struct DrawState*, struct tb_event*); + int nplurals; + int (*prompt_callback)(struct DrawState*, struct tb_event*); int running; int dirty; int dirty_before_edit; - size_t fuzzy_before_edit; + ssize_t fuzzy_before_edit; int in_prompt; int show_help; int show_edit; int edit_info_focused; int show_search; + int recalculate_size; int maxx; int maxy; - size_t msgid_number; /* "real" msgid (inc. obsolete msgids) */ - size_t msgid_count; /* without obsolete msgids */ - size_t msgid_size; - size_t fuzzy_count; - size_t untranslated_count; - size_t obsolete_count; - size_t obsolete_at_end_count; - size_t real_msgid_count; /* msgid_count + obsolete_count */ - int msgstr_index; /* with msgstr[] */ - size_t first_shown_msgid; + ssize_t msgid_number; /* "real" msgid (inc. obsolete msgids) */ + ssize_t msgid_count; /* without obsolete msgids */ + ssize_t msgid_size; + ssize_t fuzzy_count; + ssize_t untranslated_count; + ssize_t obsolete_count; + ssize_t obsolete_at_end_count; + ssize_t real_msgid_count; /* msgid_count + obsolete_count */ + int msgstr_index; /* with msgstr[] */ + ssize_t first_shown_msgid; int backup_possible; /* is saving backup possible? */ }; @@ -102,42 +158,47 @@ struct Key { struct StatusSegment { const char* format; - void (*callback)(char*, size_t, const char*, const struct DrawState*); - uint16_t fg; - uint16_t bg; + void (*callback)(char*, ssize_t, const char*, const ssize_t, + const struct DrawState*); + ColorIndex color_index; Alignment alignment; }; -void init_bufferline(struct BufferLine* bl); -void free_bufferline(struct BufferLine* bl); -void init_drawstate(struct DrawState* state, char* error, char* filename, - char* prompt); -void free_drawstate(struct DrawState* state); -struct DrawState* set_nplurals(struct DrawState* state, struct PoEntry* entry); -size_t display_length(const struct DrawState* state, uint32_t* buffer, - const size_t max_index); -size_t input_length(const struct DrawState* state, uint32_t* buffer, - const size_t max_index); -int calculate_help_width(const struct DrawState* state); +void assign_coord(struct Coord* dest, const struct Coord src); int calculate_edit_width(const struct DrawState* state); +int calculate_help_width(const struct DrawState* state); int calculate_search_width(const struct DrawState* state); -void draw_rect(const int startx, const int endx, const int starty, - const int endy, const uint16_t fg, const uint16_t bg); +ssize_t display_length(uint32_t* buffer, const ssize_t max_index); +int draw(const struct DrawState* state); void draw_box(const int startx, const int endx, const int starty, const int endy, const uint16_t fg, const uint16_t bg); -const char* draw_string(const int x, const int y, const uint16_t fg, - const uint16_t bg, const char* s, const int max_cols, - const int fill_cols, const int padding_left, const int padding_right, - const Alignment align, const int ds, const int fill, - const uint32_t* srch, const uint16_t sfg, const uint16_t sbg); +int draw_help(const struct DrawState* state); +int draw_editbox(const struct DrawState* state); +int draw_list_visible(const struct DrawState* state); +void draw_rect(const int startx, const int endx, const int starty, + const int endy, const uint16_t fg, const uint16_t bg); +int draw_search(const struct DrawState* state); +int draw_status(const struct DrawState* state); +int draw_string(const int x, const int y, const uint16_t fg, const uint16_t bg, + const char* s, const int max_cols, const int fill_cols, + const int padding_left, const int padding_right, const Alignment align, + const int ds, const int fill, const uint32_t* srch, const uint16_t sfg, + const uint16_t sbg); +void format_filename(char* result, ssize_t result_size, const char* format, + const ssize_t seg_size, const struct DrawState* state); +void format_msgs(char* result, ssize_t result_size, const char* format, + const ssize_t seg_size, const struct DrawState* state); +void free_bufferline(struct BufferLine* bl); +void free_drawstate(struct DrawState* state); +void init_bufferline(struct BufferLine* bl); +void init_coord(struct Coord* c, const int col, const int row); +void init_drawstate(struct DrawState* state, char* filename, char* prompt); +ssize_t input_length(uint32_t* buffer, const ssize_t max_index); +size_t real_msgid_to_display_msgid(const struct DrawState* state); +void recalculate_size(struct DrawState* state); +struct DrawState* set_nplurals(struct DrawState* state, struct PoEntry* entry); void u32_draw_string(const int x, const int y, const uint16_t fg, const uint16_t bg, const uint32_t* s, const int max_cols, const int fill_cols, const int padding_left, const int padding_right, const Alignment align, const int ds, const int fill, const uint32_t* srch, const uint16_t sfg, const uint16_t sbg); -const struct DrawState* draw_help(const struct DrawState* state); -const struct DrawState* draw_editbox(const struct DrawState* state); -const struct DrawState* draw_search(const struct DrawState* state); -const struct DrawState* draw_status(const struct DrawState* state); -const struct DrawState* draw_list_visible(const struct DrawState* state); -const struct DrawState* draw(const struct DrawState* state); diff --git a/po.c b/po.c @@ -2,6 +2,7 @@ * any later version. Copyright (C) 2021-2026 Страхиња Радић. * See the file LICENSE for exact copyright and license details. */ +#include <assert.h> #include <libgen.h> #include <stdio.h> #include <stdlib.h> @@ -9,18 +10,19 @@ #include <sys/stat.h> #include <time.h> -#define PO_C_GLOBALS - -#include "version.h" -#include "util.h" -#include "po.h" #include "draw.h" #include "config.h" +#include "limit.h" +#include "po.h" +#include "util.h" +#include "version.h" extern const char* errors[]; extern const char* flag_strings[]; -int print_error(const int code, const char* msg, ...); +// TODO: Check ifdef PLURAL_ ... + +extern int print_error(const int code, const char* msg, ...); char* format_flags(char* buffer, const size_t max, const struct PoEntry* entry, @@ -119,9 +121,9 @@ set_msgid(struct PoEntry* entry, const char* msgid, int append) { uint32_t umsgid[MAXMSGLINE]; size_t msgid_size = 0; - if (!entry) - return NULL; - msgid_size = u8_string_to_unicode(umsgid, msgid, MAXMSGLINE); + assert(entry != NULL); + if (msgid) + msgid_size = u8_string_to_unicode(umsgid, msgid, MAXMSGLINE); return u32_set_msgid(entry, umsgid, msgid_size, append); } @@ -130,10 +132,10 @@ set_msgid_plural(struct PoEntry* entry, const char* msgid_plural, int append) { uint32_t umsgid_plural[MAXMSGLINE]; size_t msgid_plural_size = 0; - if (!entry) - return NULL; - msgid_plural_size - = u8_string_to_unicode(umsgid_plural, msgid_plural, MAXMSGLINE); + assert(entry != NULL); + if (msgid_plural) + msgid_plural_size = u8_string_to_unicode(umsgid_plural, + msgid_plural, MAXMSGLINE); return u32_set_msgid_plural(entry, umsgid_plural, msgid_plural_size, append); } @@ -142,9 +144,9 @@ struct PoEntry* set_plural_forms(struct PoEntry* entry, const char* plural_forms, int append) { uint32_t uplural_forms[MAXMSGLINE]; - if (!entry) - return NULL; - u8_string_to_unicode(uplural_forms, plural_forms, MAXMSGLINE); + assert(entry != NULL); + if (plural_forms) + u8_string_to_unicode(uplural_forms, plural_forms, MAXMSGLINE); return u32_set_plural_forms(entry, uplural_forms, append); } @@ -153,9 +155,9 @@ set_msgstr(struct PoEntry* entry, const char* msgstr, int msgstr_index, int append, struct DrawState* state) { uint32_t umsgstr[MAXMSGLINE]; - if (!entry) - return NULL; - u8_string_to_unicode(umsgstr, msgstr, MAXMSGLINE); + assert(entry != NULL); + if (msgstr) + u8_string_to_unicode(umsgstr, msgstr, MAXMSGLINE); return u32_set_msgstr(entry, umsgstr, msgstr_index, append, state); } @@ -164,6 +166,8 @@ u32_set_msgid(struct PoEntry* entry, const uint32_t* msgid, size_t msgid_size, int append) { size_t msgid_len = 0; + /* TODO: Unused? */ + UNUSED(msgid_size); if (!entry) return NULL; if (msgid) @@ -195,6 +199,8 @@ u32_set_msgid_plural(struct PoEntry* entry, const uint32_t* msgid_plural, size_t msgid_plural_size, int append) { size_t msgid_plural_len = 0; + /* TODO: Unused? */ + UNUSED(msgid_plural_size); if (!entry) return NULL; if (msgid_plural) @@ -414,21 +420,20 @@ add_ref_comment(struct PoEntry* entry, const char* comment) int load_file(struct DrawState* dstate, long* lineno, long* col) { - struct PoEntry* newchunk = calloc(ALLOC_DELTA, sizeof(struct PoEntry)); + struct PoEntry* newchunk = NULL; struct PoEntry* entry = NULL; struct PoEntry* current = NULL; - ParseState pstate = PS_NONE; -#ifndef PLURAL_STRING - int has_plural_msgid = 0; -#endif - FILE* input = NULL; + FILE* input = NULL; char input_line[MAXBUFLINE]; + ParseState pstate = PS_NONE; + int has_plural_msgid = 0; + ssize_t non_obsolete_sofar = 0; /* lines to tolerate the lack of msgid */ - int msgid_counter = PO_DETECTION_LINES; - int msgstr_index = -1; - size_t non_obsolete_sofar = 0; + int msgid_counter = PO_DETECTION_LINES; + int msgstr_index = -1; dstate->msgid_size = 0; + newchunk = calloc(ALLOC_DELTA, sizeof(struct PoEntry)); if (!newchunk) return LOAD_ERR_CANT_ALLOC; @@ -453,10 +458,8 @@ load_file(struct DrawState* dstate, long* lineno, long* col) result = parse_po_line(input_line, dstate, &entry, &pstate, col, &msgstr_index); -#ifndef PLURAL_STRING if (entry && entry->msgid_plural) has_plural_msgid = 1; -#endif if (result == PARSE_ERR_NONE) { @@ -469,9 +472,10 @@ load_file(struct DrawState* dstate, long* lineno, long* col) return LOAD_ERR_NOT_PO_FILE; } else if (result == PARSE_ERR_SYNTAX) - snprintf(dstate->error, MAXBUFLINE, - "%s:%ld:%ld: Syntax error", dstate->filename, - *lineno, *col); + { + dstate->error = ERR_SYNTAX; + // TODO: Handle this properly + } else { free(dstate->entries); @@ -494,11 +498,8 @@ load_file(struct DrawState* dstate, long* lineno, long* col) /* obsolete entry */ entry->obsolete = 1; } -#ifndef PLURAL_STRING if (has_plural_msgid && !dstate->entries->plural_forms) - STRLCPY(errors[ERR_NO_PLURAL_FORMS], dstate->error, - MAXBUFLINE); -#endif + dstate->error = ERR_NO_PLURAL_FORMS; } /* file has < PO_DETECTION_LINES */ @@ -528,36 +529,38 @@ load_file(struct DrawState* dstate, long* lineno, long* col) } int -save_file(const struct PoEntry* entries, const size_t real_msgid_count, - const char* filename, const int nplurals) +save_file(struct DrawState* state) { - const struct PoEntry* current = entries; FILE* output = NULL; -#ifdef CREATE_BACKUPS - char* backup_filename = NULL; -#endif + const struct PoEntry* current = NULL; struct stat st; - int result = SAVE_ERR_NONE; - char* real_filename = NULL; - size_t rfn_len = 0; + char* real_filename = NULL; + char* backup_filename = NULL; + ssize_t rfn_len = 0; Flags flags; + int saved_errno; + int result = 0; + assert(state != NULL); + current = state->entries; if (!(real_filename = calloc(MAXPATH, 1))) return SAVE_ERR_CANT_ALLOC; -#ifdef CREATE_BACKUPS + if (!create_backups) + goto save_skip_backup_copy; + if (!backup_fail && !state->backup_possible) + goto save_skip_backup_copy; if (!(backup_filename = calloc(MAXPATH, 1))) { free(real_filename); return SAVE_ERR_CANT_ALLOC; } -#endif -#ifdef CREATE_BACKUPS - if (lstat(filename, &st) != -1) + if (lstat(state->filename, &st) != -1) { if (S_ISLNK(st.st_mode)) { - rfn_len = readlink(filename, real_filename, MAXPATH); + rfn_len = readlink(state->filename, real_filename, + MAXPATH); if (rfn_len == -1) { result = SAVE_ERR_CANT_READLINK; @@ -570,9 +573,9 @@ save_file(const struct PoEntry* entries, const size_t real_msgid_count, goto save_loop_entries_done; } } - STRLCPY(filename, backup_filename, MAXPATH); + STRLCPY(state->filename, backup_filename, MAXPATH); STRLCAT(backup_suffix, backup_filename, MAXPATH); - if (rename(rfn_len > 0 ? real_filename : filename, + if (rename(rfn_len > 0 ? real_filename : state->filename, backup_filename) == -1) { @@ -585,32 +588,32 @@ save_file(const struct PoEntry* entries, const size_t real_msgid_count, result = SAVE_ERR_CANT_STAT_FILE; goto save_loop_entries_done; } -#endif - if (!(output = fopen(rfn_len > 0 ? real_filename : filename, "wt"))) - { - result = SAVE_ERR_CANT_OPEN_FILE; - goto save_loop_entries_done; - } - -#ifdef CREATE_BACKUPS if (rfn_len > 0) { - if (unlink(filename) == -1) + if (unlink(state->filename) == -1) { result = SAVE_ERR_CANT_UNLINK; goto save_loop_entries_done; } - if (symlink(real_filename, filename) == -1) + if (symlink(real_filename, state->filename) == -1) { result = SAVE_ERR_CANT_SYMLINK; goto save_loop_entries_done; } } - chmod(rfn_len > 0 ? real_filename : filename, st.st_mode); -#endif + chmod(rfn_len > 0 ? real_filename : state->filename, st.st_mode); + +save_skip_backup_copy: + if (!(output = fopen(rfn_len > 0 ? real_filename : state->filename, + "wt"))) + { + state->error = ERR_ERRNO_SET; + result = 1; + goto save_file_cleanup; + } save_loop_entries_start: - if (current >= entries + real_msgid_count) + if (current >= state->entries + state->real_msgid_count) goto save_loop_entries_done; if (current->obsolete) @@ -634,9 +637,9 @@ save_comments: if (!u8_comment) { result = SAVE_ERR_CANT_ALLOC; - goto save_loop_entries_done; + goto save_file_alloc_error; } - unicode_string_to_u8(u8_comment, size, + unicode_string_to_u8(u8_comment, *(current->comments + c), len); fprintf(output, "#%s%s\n", current->obsolete ? "~" : "", u8_comment); @@ -663,9 +666,9 @@ save_comments: if (!u8_trans_comment) { result = SAVE_ERR_CANT_ALLOC; - goto save_loop_entries_done; + goto save_file_alloc_error; } - unicode_string_to_u8(u8_trans_comment, size, + unicode_string_to_u8(u8_trans_comment, *(current->trans_comments + tc), len); fprintf(output, "#.%s%s\n", u8_trans_comment[0] == 0 ? "" : " ", @@ -717,7 +720,7 @@ save_flags: save_obsolete_check: if (current->obsolete) { - if (current < entries + real_msgid_count - 1) + if (current < state->entries + state->real_msgid_count - 1) fprintf(output, "\n"); current++; goto save_loop_entries_start; @@ -725,8 +728,8 @@ save_obsolete_check: if (current->msgid) { - size_t num_lines = u32_lines_in_string(current->msgid, 1, - SAVE_WRAP_WIDTH); + size_t num_lines + = u32_lines_in_string(current->msgid, 1, wrap_width); if ((num_lines > 1) && (u32_strlen(current->msgid) @@ -744,7 +747,7 @@ save_obsolete_check: const uint32_t* old_pumsgid = pumsgid; len = u32_next_line(current->msgid, &pumsgid, 1, - SAVE_WRAP_WIDTH); + wrap_width); if (len == 0) continue; @@ -754,13 +757,12 @@ save_obsolete_check: if (!u8_msgid) { result = SAVE_ERR_CANT_ALLOC; - goto save_loop_entries_done; + goto save_file_alloc_error; } memset(u8_msgid, 0, size); - unicode_string_to_u8(u8_msgid, size, - old_pumsgid, len); + unicode_string_to_u8(u8_msgid, old_pumsgid, len); fprintf(output, "\"%s\"\n", u8_msgid); free(u8_msgid); } @@ -775,12 +777,12 @@ save_obsolete_check: if (!u8_msgid) { result = SAVE_ERR_CANT_ALLOC; - goto save_loop_entries_done; + goto save_file_alloc_error; } memset(u8_msgid, 0, size); - unicode_string_to_u8(u8_msgid, size, current->msgid, + unicode_string_to_u8(u8_msgid, current->msgid, current->msgid_size); fprintf(output, "msgid \"%s\"\n", u8_msgid); free(u8_msgid); @@ -792,7 +794,7 @@ save_obsolete_check: if (current->msgid_plural) { size_t num_lines = u32_lines_in_string(current->msgid_plural, 1, - SAVE_WRAP_WIDTH); + wrap_width); if ((num_lines > 1) && (u32_strlen(current->msgid_plural) @@ -810,7 +812,7 @@ save_obsolete_check: size_t size; len = u32_next_line(current->msgid_plural, - &pumsgid, 1, SAVE_WRAP_WIDTH); + &pumsgid, 1, wrap_width); if (len == 0) continue; @@ -819,13 +821,12 @@ save_obsolete_check: if (!u8_msgid) { result = SAVE_ERR_CANT_ALLOC; - goto save_loop_entries_done; + goto save_file_alloc_error; } memset(u8_msgid, 0, size); - unicode_string_to_u8(u8_msgid, size, - old_pumsgid, len); + unicode_string_to_u8(u8_msgid, old_pumsgid, len); fprintf(output, "\"%s\"\n", u8_msgid); free(u8_msgid); } @@ -841,14 +842,14 @@ save_obsolete_check: if (!u8_msgid) { result = SAVE_ERR_CANT_ALLOC; - goto save_loop_entries_done; + goto save_file_alloc_error; } memset(u8_msgid, 9, size); msgid_plural_len = u32_strlen(current->msgid_plural); - unicode_string_to_u8(u8_msgid, size, - current->msgid_plural, msgid_plural_len); + unicode_string_to_u8(u8_msgid, current->msgid_plural, + msgid_plural_len); fprintf(output, "msgid_plural \"%s\"\n", u8_msgid); free(u8_msgid); } @@ -856,20 +857,19 @@ save_obsolete_check: if (current->msgstr_count == 1) { - size_t num_lines = u32_lines_in_string(*current->msgstr, 1, - SAVE_WRAP_WIDTH); - int first_entry = (current == entries) && !current->msgid; + ssize_t num_lines + = u32_lines_in_string(*current->msgstr, 1, wrap_width); + int first_entry + = (current == state->entries) && !current->msgid; if ((num_lines > 1) && (first_entry || (u32_strlen(*current->msgstr) - > SAVE_MULTILINE_TRESHOLD))) + > (size_t)SAVE_MULTILINE_TRESHOLD))) { const uint32_t* pumsgstr = *current->msgstr; int seen_generator = 0; -#ifdef PLURAL_STRING - int seen_plural_forms = 0; -#endif + int seen_plural_forms = 0; char now[MAXDATEBUF]; time_t t = time(NULL); struct tm* now_tm = localtime(&t); @@ -886,9 +886,9 @@ save_obsolete_check: len = u32_next_line(*current->msgstr, &pumsgstr, 1, - ((first_entry && WRAP_FIRST_MSGSTR) + ((first_entry && wrap_first_msgstr) || !first_entry) - ? SAVE_WRAP_WIDTH + ? wrap_width : 0); if (len == 0) @@ -899,13 +899,13 @@ save_obsolete_check: if (!u8_msgstr) { result = SAVE_ERR_CANT_ALLOC; - goto save_loop_entries_done; + goto save_file_alloc_error; } memset(u8_msgstr, 0, size); - unicode_string_to_u8(u8_msgstr, size, - old_pumsgstr, len); + unicode_string_to_u8(u8_msgstr, old_pumsgstr, + len); if (first_entry && starts_with(u8_msgstr, "PO-Revision-Date: ")) @@ -921,33 +921,29 @@ save_obsolete_check: "\"X-Generator: poe %s\\n\"\n", VERSION); } -#ifdef PLURAL_STRING - else if (first_entry + else if (first_entry && plural_string && starts_with(u8_msgstr, "Plural-Forms: ")) { seen_plural_forms = 1; fprintf(output, "\"Plural-Forms: %s\\n\"\n", - PLURAL_STRING); + plural_string); } -#endif else fprintf(output, "\"%s\"\n", u8_msgstr); free(u8_msgstr); } -#ifdef PLURAL_STRING - if (first_entry && !seen_plural_forms) + if (first_entry && plural_string && !seen_plural_forms) fprintf(output, "\"Plural-Forms: %s\\n\"\n", - PLURAL_STRING); -#endif + plural_string); if (first_entry && !seen_generator) fprintf(output, "\"X-Generator: poe %s\\n\"\n", VERSION); } else if (current->msgid_plural && (!*current->msgstr || !**current->msgstr)) - for (int i = 0; i < nplurals; i++) + for (int i = 0; i < state->nplurals; i++) fprintf(output, "msgstr[%d] \"\"\n", i); else { @@ -956,17 +952,18 @@ save_obsolete_check: size_t msgstr_len; msgstr_len = u32_strlen(*current->msgstr); - unicode_string_to_u8(u8_msgstr, size, *current->msgstr, + unicode_string_to_u8(u8_msgstr, *current->msgstr, msgstr_len); fprintf(output, "msgstr \"%s\"\n", u8_msgstr); } } else if (current->msgstr_count > 0) { - for (size_t m = 0; m < current->msgstr_count; m++) + for (ssize_t m = 0; m < current->msgstr_count; m++) { - size_t num_lines = u32_lines_in_string(*current->msgstr, - 1, SAVE_WRAP_WIDTH); + ssize_t num_lines + = u32_lines_in_string(*current->msgstr, 1, + wrap_width); if ((num_lines > 1) && (u32_strlen(*(current->msgstr + m)) @@ -986,7 +983,7 @@ save_obsolete_check: len = u32_next_line(*(current->msgstr + m), - &pumsgstr, 1, SAVE_WRAP_WIDTH); + &pumsgstr, 1, wrap_width); if (len == 0) continue; @@ -995,12 +992,12 @@ save_obsolete_check: if (!u8_msgstr) { result = SAVE_ERR_CANT_ALLOC; - goto save_loop_entries_done; + goto save_file_alloc_error; } memset(u8_msgstr, 0, size); - unicode_string_to_u8(u8_msgstr, size, + unicode_string_to_u8(u8_msgstr, old_pumsgstr, len); fprintf(output, "\"%s\"\n", u8_msgstr); free(u8_msgstr); @@ -1012,7 +1009,7 @@ save_obsolete_check: char u8_msgstr[size]; size_t msgstr_len; msgstr_len = u32_strlen(*(current->msgstr + m)); - unicode_string_to_u8(u8_msgstr, size, + unicode_string_to_u8(u8_msgstr, *(current->msgstr + m), msgstr_len); fprintf(output, "msgstr[%ld] \"%s\"\n", m, u8_msgstr); @@ -1022,23 +1019,30 @@ save_obsolete_check: else { if (current->msgid_plural) - for (int i = 0; i < nplurals; i++) + for (int i = 0; i < state->nplurals; i++) fprintf(output, "msgstr[%d] \"\"\n", i); else fprintf(output, "msgstr \"\"\n"); } - if (current < entries + real_msgid_count - 1) + if (current < state->entries + state->real_msgid_count - 1) fprintf(output, "\n"); current++; goto save_loop_entries_start; +save_file_alloc_error: + if (output) + fclose(output); + return 1; + save_loop_entries_done: - fclose(output); -#ifdef CREATE_BACKUPS +save_file_cleanup: + saved_errno = errno; + if (output) + fclose(output); free(backup_filename); -#endif free(real_filename); - + state->action = ACT_IDLE; + errno = saved_errno; return result; } @@ -1422,7 +1426,7 @@ update_warning(const uint32_t* msgid, const uint32_t* msgstr, int* warning) return; if (u32_count_strings(msgid, (const uint32_t*)L"\\n") != u32_count_strings(msgstr, (const uint32_t*)L"\\n") - || (WARN_COUNT_DOTS + || (warn_count_dots && u32_count_chars(msgid, '.') != u32_count_chars(msgstr, '.'))) *warning = 1; diff --git a/po.h b/po.h @@ -5,8 +5,8 @@ #include <stdint.h> #include <stdlib.h> -#define MSGSTR_MAX 4096 -#define ALLOC_DELTA 10 +#define MSGSTR_MAX 4096 +// #define ALLOC_DELTA 10 #define PO_DETECTION_LINES 24 /* If msgid/msgstr is longer, it will be saved as multiple lines */ #define SAVE_MULTILINE_TRESHOLD 70 @@ -97,36 +97,40 @@ struct PoEntry { size_t ref_comment_lines; }; -char* format_flags(char* buffer, const size_t max, const struct PoEntry* entry, - const int draw_box); +char* format_flags(char* buffer, const size_t max, + const struct PoEntry* entry, const int draw_box); void init_po_entry(struct PoEntry* entry); void free_po_entry(struct PoEntry* entry); struct PoEntry* set_flags(struct PoEntry* entry, const Flags flags); -struct PoEntry* set_msgid(struct PoEntry* entry, const char* msgid, int append); +struct PoEntry* set_msgid(struct PoEntry* entry, const char* msgid, + int append); struct PoEntry* set_msgid_plural(struct PoEntry* entry, const char* msgid_plural, int append); struct PoEntry* set_plural_forms(struct PoEntry* entry, const char* plural_forms, int append); struct PoEntry* set_msgstr(struct PoEntry* entry, const char* msgstr, int msgstr_index, int append, struct DrawState* state); -struct PoEntry* u32_set_msgid(struct PoEntry* entry, const uint32_t* msgid, - size_t msgid_size, int append); +struct PoEntry* u32_set_msgid(struct PoEntry* entry, + const uint32_t* msgid, size_t msgid_size, int append); struct PoEntry* u32_set_msgid_plural(struct PoEntry* entry, const uint32_t* msgid_plural, size_t msgid_plural_size, int append); struct PoEntry* u32_set_plural_forms(struct PoEntry* entry, const uint32_t* plural_forms, int append); -struct PoEntry* u32_set_msgstr(struct PoEntry* entry, const uint32_t* msgstr, - int msgstr_index, int append, struct DrawState* state); -struct PoEntry* u32_add_comment(struct PoEntry* entry, const uint32_t* comment); +struct PoEntry* u32_set_msgstr(struct PoEntry* entry, + const uint32_t* msgstr, int msgstr_index, int append, + struct DrawState* state); +struct PoEntry* u32_add_comment(struct PoEntry* entry, + const uint32_t* comment); struct PoEntry* u32_add_trans_comment(struct PoEntry* entry, const uint32_t* trans_comment); -struct PoEntry* add_ref_comment(struct PoEntry* entry, const char* comment); +struct PoEntry* add_ref_comment(struct PoEntry* entry, + const char* comment); int load_file(struct DrawState* state, long* lineno, long* col); -int save_file(const struct PoEntry* entries, const size_t real_msgid_count, - const char* filename, const int nplurals); +int save_file(struct DrawState* state); int parse_po_line(const char* line, struct DrawState* dstate, struct PoEntry** current_entry, ParseState* pstate, long* col, int* msgstr_index); -void update_statistics(const struct PoEntry* entries, struct DrawState* state, - struct PoEntry* entry); -void update_warning(const uint32_t* msgid, const uint32_t* msgstr, int* warning); +void update_statistics(const struct PoEntry* entries, + struct DrawState* state, struct PoEntry* entry); +void update_warning(const uint32_t* msgid, const uint32_t* msgstr, + int* warning); diff --git a/poe.c b/poe.c @@ -2,8 +2,10 @@ * any later version. Copyright (C) 2021-2026 Страхиња Радић. * See the file LICENSE for exact copyright and license details. */ +#include <assert.h> #include <errno.h> #include <fcntl.h> +#include <locale.h> #include <stdarg.h> #include <stdint.h> #include <stdio.h> @@ -26,38 +28,23 @@ " which is released under the terms of Expat license, see the file" \ " LICENSE.termbox.\n") #define POE_C_GLOBALS +#define TB_IMPL -#include "version.h" -#include "util.h" -#include "po.h" #include "draw.h" +#include "poe.h" #include "config.h" +#include "limit.h" +#include "po.h" +#include "util.h" +#include "version.h" -extern const char* backup_suffix; extern const char* errors[]; +extern const char* actions[]; extern const char* prompt_dirty; extern const char* prompt_cancel_dirty; extern const char* prompt_overwrite; -void cancel_callback(struct DrawState* state, struct tb_event* ev); -char* dirname(char* path); -void goto_msgid(struct DrawState* state, const size_t msgid_number); -void handle_key_event(struct tb_event* ev, struct DrawState* state); -int insert_char(struct DrawState* state, uint32_t ch); -void insert_search_char(struct DrawState* state, uint32_t ch); -struct DrawState* join_lines(struct DrawState* state, size_t first_line, - int forward); -struct BufferLine* load_info(struct DrawState* state); -struct BufferLine* load_msgstr(struct DrawState* state); -size_t next_msgid(struct DrawState* state, const int delta); -void overwrite_copy_msgid_callback(struct DrawState* state, struct tb_event* ev); -void overwrite_paste_callback(struct DrawState* state, struct tb_event* ev); -size_t prev_msgid(struct DrawState* state, const int delta); -int print_error(const int code, const char* msg, ...); -void quit_callback(struct DrawState* state, struct tb_event* ev); -size_t real_msgid_to_display_msgid(const struct DrawState* state); - -void +static int cancel_callback(struct DrawState* state, struct tb_event* ev) { if (ev->ch == 'y') @@ -65,41 +52,147 @@ cancel_callback(struct DrawState* state, struct tb_event* ev) else cancel_msgstr(state); state->prompt_callback = NULL; + return 0; } -char* -dirname(char* path) +#define MAXCOPYBUF 8192 + +static int +cp(const char* from, const char* to) { - static char* result = NULL; - static const char* dot = "."; - char* p = NULL; - char* slash = NULL; + struct stat st; + struct timespec times[2]; + char buf[MAXCOPYBUF]; + int fromfd, tofd; + int nread = 0; + int nwritten = 0; + int result; + int saved_errno; - if (!path || !*path) - return NULL; + assert((from != NULL) && (to != NULL)); - free(result); - result = malloc(MAXPATH); - memset(result, 0, MAXPATH); + tofd = 0; + fromfd = open(from, O_RDONLY); + if (fromfd < 0) + return 1; + if (fstat(fromfd, &st) < 0) + { + result = 1; + goto cp_cleanup; + } - STRLCPY(path, result, MAXPATH); - p = result + strlen(result) - 1; - if (*p == '/') - *p = 0; + tofd = open(to, O_WRONLY | O_CREAT | O_TRUNC, st.st_mode); + if (tofd < 0) + return 1; + if (fchmod(tofd, st.st_mode) < 0) + { + result = 1; + goto cp_cleanup; + } + times[0] = st.st_atim; + times[1] = st.st_mtim; - slash = strrchr(result, '/'); - if (!slash) - return (char*)dot; + result = 0; + while ((nread = read(fromfd, buf, MAXCOPYBUF)) > 0) + { + nwritten = write(tofd, buf, nread); + if (nwritten < nread) + { + result = 1; + goto cp_cleanup; + } + } - *slash = 0; + if (nread < 0) + { + result = 1; + goto cp_cleanup; + } + errno = 0; + if (futimens(tofd, times) < 0) + result = 1; + +cp_cleanup: + saved_errno = errno; + if (tofd) + close(tofd); + close(fromfd); + errno = saved_errno; return result; } -void -goto_msgid(struct DrawState* state, const size_t msgid_number) +/*static int +create_file(struct DrawState* state) { - size_t last_non_obsolete + struct BufferLine* newchunk = NULL; + uint32_t* newtext = NULL; + + assert(state != NULL); + newchunk = calloc(ALLOC_DELTA, sizeof(struct BufferLine)); + if (!newchunk) + { + state->running = 0; + return 1; + } + for (ssize_t row = 0; row < ALLOC_DELTA; row++) + { + init_bufferline(newchunk + row); + newtext = calloc(LINE_DEFAULT, sizeof(uint32_t)); + if (!newtext) + { + state->running = 0; + return 1; + } + newchunk[row].size = LINE_DEFAULT; + newchunk[row].text = newtext; + } + state->buffer = newchunk; + state->buffer_size = ALLOC_DELTA; + state->rows_count = 1; + recalculate_line_numbers(state); + state->coord.row = 0; + state->dirty = 1; + + return 0; +}*/ + +static char* +dirname(char* dir, char* path, const ssize_t dir_size) +{ + char* p = NULL; + + assert((dir != NULL) && (path != NULL) && (dir_size > 0)); + if (!*path) + return NULL; + + MEMCCPY(dir, path, dir_size, p); + + if (p > dir + 2 && *(p - 2) == '/') + *p = 0; + if (p > dir + 1) + p--; + + while (p != dir && *p != '/') + { + p--; + if (p != dir && *p == '/') + { + *p = 0; + return dir; + } + else if (p == dir && *p == '/') + return dir; + } + dir[0] = '.'; + dir[1] = 0; + return dir; +} + +static void +goto_msgid(struct DrawState* state, const ssize_t msgid_number) +{ + ssize_t last_non_obsolete = state->real_msgid_count - state->obsolete_at_end_count; if (msgid_number > state->real_msgid_count) @@ -117,20 +210,20 @@ goto_msgid(struct DrawState* state, const size_t msgid_number) state->first_shown_msgid = (last_non_obsolete > state->maxy - 2 ? last_non_obsolete - (state->maxy - 2) : 1); - else - ; } -void +static void handle_key_event(struct tb_event* ev, struct DrawState* state) { const struct Key* current = NULL; - *state->error = 0; + assert((state != NULL) && (ev != NULL)); + state->error = ERR_NONE; + errno = 0; if (state->in_prompt) { - if (ev->ch == 'y' || ev->ch == 'n') + if (ev->ch && strchr("YyNn", ev->ch)) { if (state->prompt_callback) state->prompt_callback(state, ev); @@ -142,37 +235,45 @@ handle_key_event(struct tb_event* ev, struct DrawState* state) return; } - /* Remember to also edit the following condition if you reassign motion - * keys */ + /* Remember to also edit the following condition if you + * reassign motion keys */ + /* TODO: Condition in sled also contains: + && ev->key != TB_KEY_PGDN && ev->key != TB_KEY_PGUP + && ev->key != TB_KEY_CTRL_B && ev->key != TB_KEY_CTRL_F + && !(IN(state->mode, M_COMMAND) && strchr("jkgGHLM", ev->ch))) + Check? + */ if (ev->key != TB_KEY_ARROW_DOWN && ev->key != TB_KEY_ARROW_UP && state->show_edit) state->saved_column = -1; - for (int key = 0; key < LEN(top_level_keys); key++) + for (size_t key = 0; key < LEN(top_level_keys); key++) { current = top_level_keys + key; - if (((current->ev.key == ev->key) && !ev->ch) + if (((current->ev.mod == ev->mod) + && (current->ev.key == ev->key) && !ev->ch) || (current->ev.ch && (current->ev.ch == ev->ch))) - if (current->callback && !current->callback(state)) + if (current->callback) + { + current->callback(state); return; + } } - if (state->show_help) - { - STRLCPY(errors[ERR_DLG_OPEN_QUIT], state->error, MAXBUFLINE); - return; - } - else if (state->show_edit) + if (state->show_edit) { - for (int key = 0; key < LEN(edit_keys); key++) + for (size_t key = 0; key < LEN(edit_keys); key++) { current = edit_keys + key; - if (((current->ev.key == ev->key) && !ev->ch) + if (((current->ev.mod == ev->mod) + && (current->ev.key == ev->key) && !ev->ch) || (current->ev.ch && (current->ev.ch == ev->ch))) - if (current->callback - && !current->callback(state)) + if (current->callback) + { + current->callback(state); return; + } } if (!state->edit_info_focused && ev->ch) { @@ -180,17 +281,21 @@ handle_key_event(struct tb_event* ev, struct DrawState* state) return; } } - else if (state->show_search) + + if (state->show_search) { - for (int key = 0; key < LEN(search_keys); key++) + for (size_t key = 0; key < LEN(search_keys); key++) { current = search_keys + key; - if (((current->ev.key == ev->key) && !ev->ch) + if (((current->ev.mod == ev->mod) + && (current->ev.key == ev->key) && !ev->ch) || (current->ev.ch && (current->ev.ch == ev->ch))) - if (current->callback - && !current->callback(state)) + if (current->callback) + { + current->callback(state); return; + } } if (ev->ch) { @@ -198,79 +303,141 @@ handle_key_event(struct tb_event* ev, struct DrawState* state) return; } } - else + + if (state->show_help) { - for (int key = 0; key < LEN(main_keys); key++) - { - current = main_keys + key; - if (((current->ev.key == ev->key) && !ev->ch) - || (current->ev.ch - && (current->ev.ch == ev->ch))) - if (current->callback) - { - if (!current->callback(state)) - return; - else - break; - } - } + state->error = ERR_DLG_OPEN; + return; + } + + for (size_t key = 0; key < LEN(main_keys); key++) + { + current = main_keys + key; + if (((current->ev.mod == ev->mod) + && (current->ev.key == ev->key) && !ev->ch) + || (current->ev.ch && (current->ev.ch == ev->ch))) + if (current->callback) + { + current->callback(state); + return; + } } if (state->edit_info_focused) - STRLCPY(errors[ERR_DLG_FOCUS_EDIT], state->error, MAXBUFLINE); + state->error = ERR_DLG_FOCUS_EDIT; else if (state->show_edit || state->show_search) - STRLCPY(errors[ERR_UNKNOWN_KEY], state->error, MAXBUFLINE); + state->error = ERR_UNKNOWN_KEY; else - STRLCPY(errors[ERR_UNKNOWN_KEY_HELP], state->error, MAXBUFLINE); + state->error = ERR_UNKNOWN_KEY_HELP; } -int +static int insert_char(struct DrawState* state, uint32_t ch) { + struct BufferLine* buffer_line; + ssize_t len; + assert(state != NULL); if (state->edit_info_focused) return 1; - struct BufferLine* buffer_line = &state->input_buffer[state->input_row]; - size_t len = buffer_line->length; - if (state->input_column < MAXBUFLINE - 1) + buffer_line = &state->input_buffer[state->coord.row]; + len = buffer_line->length; + if (state->coord.col < MAXBUFLINE - 1) { - size_t i = len + 1; - int add_null = buffer_line->text[state->input_column] == 0; - while (i < MAXBUFLINE - 2 && i > state->input_column) + ssize_t i = len + 1; + int add_null = buffer_line->text[state->coord.col] == 0; + while (i < MAXBUFLINE - 2 && i > state->coord.col) { buffer_line->text[i] = buffer_line->text[i - 1]; i--; } - buffer_line->text[state->input_column++] = ch; + buffer_line->text[state->coord.col++] = ch; buffer_line->length++; - if (state->input_column == MAXBUFLINE) - state->input_column = MAXBUFLINE - 1; - state->input_display_column = display_length(state, - state->input_buffer[state->input_row].text, - state->input_column); - if (state->input_display_column - state->input_first_shown_column + if (state->coord.col == MAXBUFLINE) + state->coord.col = MAXBUFLINE - 1; + state->input_display_column + = display_length(state->input_buffer[state->coord.row] + .text, + state->coord.col); + if (state->input_display_column - state->input_first_shown.col > state->edit_width - 4) { - state->input_first_shown_column + state->input_first_shown.col = state->input_display_column - state->edit_width + 4; - state->input_display_column = display_length(state, - state->input_buffer[state->input_row].text, - state->input_column); + state->input_display_column = display_length( + state->input_buffer[state->coord.row].text, + state->coord.col); } if (add_null) - buffer_line->text[state->input_column] = 0; + buffer_line->text[state->coord.col] = 0; + } + state->dirty = 1; + return 0; +} + +static int +insert_line(struct DrawState* state) +{ + struct BufferLine* newbuf = NULL; + + if (state->edit_info_focused) + return 1; + + state->input_rows_count++; + newbuf = realloc(state->input_buffer, + state->input_rows_count * sizeof(struct BufferLine)); + if (!newbuf) + { + state->error = ERR_CANT_ALLOC; + state->running = 0; + return 0; + } + + state->input_buffer = newbuf; + init_bufferline(&state->input_buffer[state->input_rows_count - 1]); + uint32_t* newtext = calloc(MAXBUFLINE, sizeof(uint32_t)); + if (!newtext) + { + state->error = ERR_CANT_ALLOC; + state->running = 0; + return 0; + } + state->input_buffer[state->input_rows_count - 1].text = newtext; + for (ssize_t i = state->input_rows_count - 1; i > state->coord.row + 1; + i--) + { + u32_strncpy(state->input_buffer[i].text, + state->input_buffer[i - 1].text, MAXBUFLINE); + state->input_buffer[i].length + = state->input_buffer[i - 1].length; } + u32_strncpy(state->input_buffer[state->coord.row + 1].text, + state->input_buffer[state->coord.row].text + state->coord.col, + MAXBUFLINE); + state->input_buffer[state->coord.row + 1].length + = u32_strlen(state->input_buffer[state->coord.row + 1].text); + state->input_buffer[state->coord.row].text[state->coord.col] = 0; + state->input_buffer[state->coord.row].length + = u32_strlen(state->input_buffer[state->coord.row].text); + + state->coord.row++; + if (state->coord.row + > state->input_first_shown.row + (EDIT_HEIGHT - 3) / 2) + state->input_first_shown.row + = state->coord.row - (EDIT_HEIGHT - 3) / 2; + move_start(state); state->dirty = 1; + return 0; } -void +static void insert_search_char(struct DrawState* state, uint32_t ch) { - size_t len = u32_strlen(state->search); + ssize_t len = u32_strlen(state->search); if (state->search_column < MAXSEARCH - 1) { - size_t i = len + 1; + ssize_t i = len + 1; int add_null = state->search[state->search_column] == 0; while (i < MAXSEARCH - 2 && i > state->search_column) { @@ -280,8 +447,8 @@ insert_search_char(struct DrawState* state, uint32_t ch) state->search[state->search_column++] = ch; if (state->search_column == MAXSEARCH) state->search_column = MAXSEARCH - 1; - state->search_display_column = display_length(state, - state->search, state->search_column); + state->search_display_column + = display_length(state->search, state->search_column); if (state->search_display_column - state->search_first_shown_column > state->search_width - 4) @@ -289,8 +456,9 @@ insert_search_char(struct DrawState* state, uint32_t ch) state->search_first_shown_column = state->search_display_column - state->search_width + 4; - state->search_display_column = display_length(state, - state->search, state->search_column); + state->search_display_column + = display_length(state->search, + state->search_column); } if (add_null) state->search[state->search_column] = 0; @@ -299,24 +467,24 @@ insert_search_char(struct DrawState* state, uint32_t ch) } } -struct DrawState* +static struct DrawState* join_lines(struct DrawState* state, size_t first_line, int forward) { - state->input_column = state->input_buffer[first_line].length; - state->input_display_column = display_length(state, - state->input_buffer[state->input_row].text, - state->input_column); + state->coord.col = state->input_buffer[first_line].length; + state->input_display_column + = display_length(state->input_buffer[state->coord.row].text, + state->coord.col); u32_strncat(state->input_buffer[first_line].text, state->input_buffer[first_line + 1].text, MAXBUFLINE); state->input_buffer[first_line].length = u32_strlen(state->input_buffer[first_line].text); if (!forward) { - state->input_row--; - if (state->input_row < 0) - state->input_row = 0; + state->coord.row--; + if (state->coord.row < 0) + state->coord.row = 0; } - for (size_t i = first_line + 1; i < state->input_rows_count - 1; i++) + for (ssize_t i = first_line + 1; i < state->input_rows_count - 1; i++) { u32_strncpy(state->input_buffer[i].text, state->input_buffer[i + 1].text, MAXBUFLINE); @@ -335,19 +503,21 @@ join_lines(struct DrawState* state, size_t first_line, int forward) return state; } -struct BufferLine* +static struct BufferLine* load_info(struct DrawState* state) { - struct PoEntry* first = &state->entries[0]; - struct PoEntry* current = &state->entries[state->msgid_number - 1]; - size_t msgid_num_lines = u32_lines_in_string(current->msgid, 0, 0); - size_t msgid_plural_num_lines - = u32_lines_in_string(current->msgid_plural, 0, 0); - size_t num_lines = 0; + struct PoEntry* first = &state->entries[0]; + struct PoEntry* current = &state->entries[state->msgid_number - 1]; struct BufferLine* newbuf = NULL; struct BufferLine* pnewbuf = NULL; + const uint32_t* pumsgid = NULL; + ssize_t msgid_num_lines = u32_lines_in_string(current->msgid, 0, 0); + ssize_t msgid_plural_num_lines + = u32_lines_in_string(current->msgid_plural, 0, 0); + ssize_t num_lines = 0; + ssize_t newlen = 0; - for (size_t i = 0; i < state->info_rows_count; i++) + for (ssize_t i = 0; i < state->info_rows_count; i++) free_bufferline(state->info_buffer + i); free(state->info_buffer); @@ -372,7 +542,7 @@ load_info(struct DrawState* state) return NULL; pnewbuf = newbuf; - for (size_t i = 0; i < num_lines; i++) + for (ssize_t i = 0; i < num_lines; i++) { init_bufferline(pnewbuf); uint32_t* newtext = calloc(MAXBUFLINE, sizeof(uint32_t)); @@ -384,9 +554,9 @@ load_info(struct DrawState* state) state->info_rows_count = num_lines; - pnewbuf = newbuf; - size_t newlen = 0; - const uint32_t* pumsgid = NULL; + pnewbuf = newbuf; + newlen = 0; + pumsgid = NULL; if (current->msgid) { pumsgid = current->msgid; @@ -394,8 +564,8 @@ load_info(struct DrawState* state) pnewbuf->length = newlen; if (newlen > state->info_rows_maxlen) state->info_rows_maxlen = newlen; - pnewbuf->fg = EDIT_MSGID_HEADING_FG; - pnewbuf->bg = EDIT_MSGID_HEADING_BG; + pnewbuf->fg = colors[C_EDIT_MSGID_HEADING].fg; + pnewbuf->bg = colors[C_EDIT_MSGID_HEADING].bg; pnewbuf++; while (*pumsgid) { @@ -419,8 +589,8 @@ load_info(struct DrawState* state) pnewbuf->length = newlen; if (newlen > state->info_rows_maxlen) state->info_rows_maxlen = newlen; - pnewbuf->fg = EDIT_MSGID_HEADING_FG; - pnewbuf->bg = EDIT_MSGID_HEADING_BG; + pnewbuf->fg = colors[C_EDIT_MSGID_HEADING].fg; + pnewbuf->bg = colors[C_EDIT_MSGID_HEADING].bg; pnewbuf++; pumsgid = current->msgid_plural; while (*pumsgid) @@ -445,8 +615,8 @@ load_info(struct DrawState* state) pnewbuf->length = newlen; if (newlen > state->info_rows_maxlen) state->info_rows_maxlen = newlen; - pnewbuf->fg = EDIT_MSGID_HEADING_FG; - pnewbuf->bg = EDIT_MSGID_HEADING_BG; + pnewbuf->fg = colors[C_EDIT_MSGID_HEADING].fg; + pnewbuf->bg = colors[C_EDIT_MSGID_HEADING].bg; pnewbuf++; newlen = u32_strncpy(pnewbuf->text, first->plural_forms, MAXBUFLINE); @@ -462,8 +632,8 @@ load_info(struct DrawState* state) pnewbuf->length = newlen; if (newlen > state->info_rows_maxlen) state->info_rows_maxlen = newlen; - pnewbuf->fg = EDIT_MSGID_HEADING_FG; - pnewbuf->bg = EDIT_MSGID_HEADING_BG; + pnewbuf->fg = colors[C_EDIT_MSGID_HEADING].fg; + pnewbuf->bg = colors[C_EDIT_MSGID_HEADING].bg; pnewbuf++; for (size_t i = 0; i < current->comment_lines; i++) { @@ -484,8 +654,8 @@ load_info(struct DrawState* state) pnewbuf->length = newlen; if (newlen > state->info_rows_maxlen) state->info_rows_maxlen = newlen; - pnewbuf->fg = EDIT_MSGID_HEADING_FG; - pnewbuf->bg = EDIT_MSGID_HEADING_BG; + pnewbuf->fg = colors[C_EDIT_MSGID_HEADING].fg; + pnewbuf->bg = colors[C_EDIT_MSGID_HEADING].bg; pnewbuf++; for (size_t i = 0; i < current->trans_comment_lines; i++) { @@ -506,8 +676,8 @@ load_info(struct DrawState* state) pnewbuf->length = newlen; if (newlen > state->info_rows_maxlen) state->info_rows_maxlen = newlen; - pnewbuf->fg = EDIT_MSGID_HEADING_FG; - pnewbuf->bg = EDIT_MSGID_HEADING_BG; + pnewbuf->fg = colors[C_EDIT_MSGID_HEADING].fg; + pnewbuf->bg = colors[C_EDIT_MSGID_HEADING].bg; pnewbuf++; for (size_t i = 0; i < current->ref_comment_lines; i++) { @@ -527,18 +697,25 @@ load_info(struct DrawState* state) return state->info_buffer; } -struct BufferLine* +static struct BufferLine* load_msgstr(struct DrawState* state) { - int msgstr_index = state->msgstr_index; - struct PoEntry* current = &state->entries[state->msgid_number - 1]; - size_t num_lines = current->msgstr_count == 0 - ? 1 - : u32_lines_in_string(*(current->msgstr + msgstr_index), 0, 0); - struct BufferLine* newbuf = NULL; - struct BufferLine* pnewbuf = NULL; - - for (size_t i = 0; i < state->input_rows_count; i++) + struct PoEntry* current; + struct BufferLine* newbuf; + struct BufferLine* pnewbuf; + size_t num_lines; + int msgstr_index; + + assert((state != NULL) && (state->entries != NULL)); + current = &state->entries[state->msgid_number - 1]; + newbuf = NULL; + pnewbuf = NULL; + msgstr_index = state->msgstr_index; + num_lines = current->msgstr_count == 0 + ? 1 + : u32_lines_in_string(*(current->msgstr + msgstr_index), 0, 0); + + for (ssize_t i = 0; i < state->input_rows_count; i++) free_bufferline(state->input_buffer + i); free(state->input_buffer); @@ -591,7 +768,7 @@ load_msgstr(struct DrawState* state) return state->input_buffer; } -size_t +static size_t next_msgid(struct DrawState* state, const int delta) { size_t ret = state->msgid_number; @@ -609,27 +786,29 @@ next_msgid(struct DrawState* state, const int delta) return ret == last_non_obsolete + 1 ? last_non_obsolete : ret; } -void +static int overwrite_copy_msgid_callback(struct DrawState* state, struct tb_event* ev) { if (ev->ch == 'y') copy_msgid_to_input(state); state->prompt_callback = NULL; + return 0; } -void +static int overwrite_paste_callback(struct DrawState* state, struct tb_event* ev) { if (ev->ch == 'y') paste_paste_buffer_to_input(state); state->prompt_callback = NULL; + return 0; } -size_t +static size_t prev_msgid(struct DrawState* state, const int delta) { - size_t ret = state->msgid_number; - size_t start = ret; + ssize_t ret = state->msgid_number; + ssize_t start = ret; if (start < delta) return state->real_msgid_count ? 1 : 0; @@ -652,44 +831,25 @@ print_error(const int code, const char* msg, ...) va_start(args, msg); vsnprintf(buf, sizeof(buf), msg, args); va_end(args); - fprintf(stderr, "%s: %s\n", PROGRAMNAME, buf); + fprintf(stderr, "%s: %s\n", PROGRAM_NAME, buf); return code; } -void +static int quit_callback(struct DrawState* state, struct tb_event* ev) { if (ev->ch == 'y') write_file(state); state->prompt_callback = NULL; state->running = 0; + return 0; } -size_t -real_msgid_to_display_msgid(const struct DrawState* state) -{ - struct PoEntry* current = state->entries; - size_t ret = 1; - - if (!state->msgid_count) - return 0; - - while (current != state->entries + state->msgid_number - 1) - { - if (current != state->entries + state->msgid_number - 1 - && !current->obsolete) - ret++; - current++; - } - - return ret; -} - -int +static int cancel_close(struct DrawState* state) { - *state->error = 0; - if (state->in_prompt) + assert(state != NULL); + if (!state->error && state->in_prompt) { state->in_prompt = 0; *state->prompt = 0; @@ -701,12 +861,12 @@ cancel_close(struct DrawState* state) else if (state->show_search) cancel_search(state); else - STRLCPY(errors[ERR_EXIT_KEY], state->error, MAXBUFLINE); + state->error = ERR_EXIT_KEY; return 0; } -int +static int cancel_msgstr(struct DrawState* state) { if (!state->in_prompt && state->dirty) @@ -720,13 +880,13 @@ cancel_msgstr(struct DrawState* state) state->show_edit = 0; state->edit_info_focused = 0; - for (size_t i = 0; i < state->input_rows_count; i++) + for (ssize_t i = 0; i < state->input_rows_count; i++) free_bufferline(state->input_buffer + i); free(state->input_buffer); state->input_buffer = NULL; state->input_rows_count = 0; if (state->info_buffer) - for (size_t i = 0; i < state->info_rows_count; i++) + for (ssize_t i = 0; i < state->info_rows_count; i++) free_bufferline(state->info_buffer + i); free(state->info_buffer); state->info_buffer = NULL; @@ -748,7 +908,7 @@ cancel_msgstr(struct DrawState* state) return 0; } -int +static int cancel_search(struct DrawState* state) { state->show_search = 0; @@ -757,9 +917,17 @@ cancel_search(struct DrawState* state) return 0; } -int +static int copy_msgid_to_input(struct DrawState* state) { + struct PoEntry* current; + struct BufferLine* newbuf; + struct BufferLine* pbuffer; + const uint32_t* pmsgid; + ssize_t msgid_num_lines; + ssize_t lines_to_init; + ssize_t lines_to_free; + if (state->edit_info_focused) return 0; @@ -773,34 +941,35 @@ copy_msgid_to_input(struct DrawState* state) if (state->msgid_number == 1) { - STRLCPY(errors[ERR_ILLEGAL_ON_FIRST], state->error, MAXBUFLINE); + state->error = ERR_ILLEGAL_ON_FIRST; return 0; } - struct PoEntry* current = &state->entries[state->msgid_number - 1]; - size_t msgid_num_lines = u32_lines_in_string(current->msgid, 0, 0); - size_t lines_to_init = state->input_rows_count < msgid_num_lines - ? msgid_num_lines - state->input_rows_count - : 0; - size_t lines_to_free = state->input_rows_count > msgid_num_lines - ? state->input_rows_count - msgid_num_lines - : 0; - for (size_t i = 0; i < lines_to_free; i++) + current = &state->entries[state->msgid_number - 1]; + msgid_num_lines = u32_lines_in_string(current->msgid, 0, 0); + lines_to_init = state->input_rows_count < msgid_num_lines + ? msgid_num_lines - state->input_rows_count + : 0; + lines_to_free = state->input_rows_count > msgid_num_lines + ? state->input_rows_count - msgid_num_lines + : 0; + for (ssize_t i = 0; i < lines_to_free; i++) free_bufferline( state->input_buffer + state->input_rows_count - i - 1); - struct BufferLine* newbuf = realloc(state->input_buffer, + newbuf = realloc(state->input_buffer, msgid_num_lines * sizeof(struct BufferLine)); - const uint32_t* pmsgid = current->msgid; + pmsgid = current->msgid; if (!newbuf) return 0; - struct BufferLine* pbuffer = NULL; - for (size_t i = 0; i < lines_to_init; i++) + pbuffer = NULL; + for (ssize_t i = 0; i < lines_to_init; i++) { + uint32_t* newtext; pbuffer = newbuf + msgid_num_lines - 1 - i; init_bufferline(pbuffer); - uint32_t* newtext = calloc(MAXBUFLINE, sizeof(uint32_t)); + newtext = calloc(MAXBUFLINE, sizeof(uint32_t)); if (!newtext) return 0; pbuffer->text = newtext; @@ -826,14 +995,14 @@ copy_msgid_to_input(struct DrawState* state) return 0; } -int +static int edit_page_down(struct DrawState* state) { if (state->edit_info_focused) { - if (state->info_first_shown_row + (EDIT_HEIGHT - 3) / 2 + if (state->info_first_shown.row + (EDIT_HEIGHT - 3) / 2 < state->info_rows_count - 1) - state->info_first_shown_row += (EDIT_HEIGHT - 3) / 2; + state->info_first_shown.row += (EDIT_HEIGHT - 3) / 2; return 0; } @@ -846,13 +1015,13 @@ edit_page_down(struct DrawState* state) return show_edit(state); } -int +static int edit_page_up(struct DrawState* state) { if (state->edit_info_focused) { - if (state->info_first_shown_row > (EDIT_HEIGHT - 3) / 2) - state->info_first_shown_row -= (EDIT_HEIGHT - 3) / 2; + if (state->info_first_shown.row > (EDIT_HEIGHT - 3) / 2) + state->info_first_shown.row -= (EDIT_HEIGHT - 3) / 2; else move_start(state); return 0; @@ -867,43 +1036,46 @@ edit_page_up(struct DrawState* state) return show_edit(state); } -int +static int erase_backwards(struct DrawState* state) { - struct BufferLine* buffer_line = &state->input_buffer[state->input_row]; + struct BufferLine* buffer_line; + assert(state != NULL); + buffer_line = &state->input_buffer[state->coord.row]; if (state->edit_info_focused) return 1; - if (state->input_column > 0) + if (state->coord.col > 0) { - size_t i = state->input_column; + ssize_t i = state->coord.col; do { buffer_line->text[i - 1] = buffer_line->text[i]; i++; + /* TODO: Use size? */ } while (buffer_line->text[i]); buffer_line->text[i - 1] = 0; buffer_line->length--; - state->input_column--; - if (state->input_first_shown_column > 0) + state->coord.col--; + if (state->input_first_shown.col > 0) { const struct BufferLine* current_line - = &state->input_buffer[state->input_row]; - if (display_length(state, current_line->text, + = &state->input_buffer[state->coord.row]; + if (display_length(current_line->text, current_line->length) >= state->edit_width - 4) - state->input_first_shown_column--; + state->input_first_shown.col--; } - state->input_display_column = display_length(state, - state->input_buffer[state->input_row].text, - state->input_column); - if (state->input_display_column - < state->input_first_shown_column) - state->input_first_shown_column + state->input_display_column + = display_length(state->input_buffer[state->coord.row] + .text, + state->coord.col); + if (state->input_display_column < state->input_first_shown.col) + state->input_first_shown.col = state->input_display_column; state->dirty = 1; } - else if (state->input_row > 0) + else if (state->coord.row > 0) { move_up(state); move_end(state); @@ -912,16 +1084,16 @@ erase_backwards(struct DrawState* state) return 0; } -int +static int erase_forward(struct DrawState* state) { - struct BufferLine* buffer_line = &state->input_buffer[state->input_row]; + struct BufferLine* buffer_line = &state->input_buffer[state->coord.row]; if (state->edit_info_focused) return 1; - if (buffer_line->text[state->input_column]) + if (buffer_line->text[state->coord.col]) { - size_t i = state->input_column; + size_t i = state->coord.col; while (i < MAXBUFLINE - 2 && buffer_line->text[i + 1]) { buffer_line->text[i] = buffer_line->text[i + 1]; @@ -931,28 +1103,31 @@ erase_forward(struct DrawState* state) buffer_line->length--; state->dirty = 1; } - else if (state->input_row < state->input_rows_count - 1) + else if (state->coord.row < state->input_rows_count - 1) { - if (!join_lines(state, state->input_row, 1)) + if (!join_lines(state, state->coord.row, 1)) { - STRLCPY(errors[ERR_CANT_ALLOC], state->error, - MAXBUFLINE); + state->error = ERR_CANT_ALLOC; state->running = 0; } } return 0; } -int +static int erase_prev_word(struct DrawState* state) { + uint32_t* pline; + ssize_t len; + int i; + int d; + if (state->edit_info_focused) return 1; - int i = state->input_column; - size_t len = state->input_buffer[state->input_row].length; - - uint32_t* pline = state->input_buffer[state->input_row].text; + i = state->coord.col; + len = state->input_buffer[state->coord.row].length; + pline = state->input_buffer[state->coord.row].text; if (i > 0 && u32_is_word_boundary(pline[i - 1], 0)) i--; @@ -962,25 +1137,25 @@ erase_prev_word(struct DrawState* state) && !u32_is_word_boundary(pline[i - 1], 0)) i--; - int d = state->input_column - i; + d = state->coord.col - i; - for (int j = i; j < len - d; j++) + for (ssize_t j = i; j < len - d; j++) pline[j] = pline[j + d]; len -= d; - state->input_buffer[state->input_row].length = len; + state->input_buffer[state->coord.row].length = len; pline[len] = 0; - state->input_column = i; - state->input_display_column = display_length(state, - state->input_buffer[state->input_row].text, - state->input_column); - if (state->input_display_column < state->input_first_shown_column) - state->input_first_shown_column = state->input_display_column; + state->coord.col = i; + state->input_display_column + = display_length(state->input_buffer[state->coord.row].text, + state->coord.col); + if (state->input_display_column < state->input_first_shown.col) + state->input_first_shown.col = state->input_display_column; state->dirty = 1; return 0; } -int +static int erase_search_backwards(struct DrawState* state) { if (state->search_column > 0) @@ -1002,15 +1177,15 @@ erase_search_backwards(struct DrawState* state) else state->search_first_shown_column = 0; } - state->search_display_column = display_length(state, - state->search, state->search_column); + state->search_display_column + = display_length(state->search, state->search_column); return next_match(state); } return 0; } -int +static int erase_search_forward(struct DrawState* state) { if (state->search[state->search_column]) @@ -1028,14 +1203,16 @@ erase_search_forward(struct DrawState* state) return 0; } -int +static int erase_search_prev_word(struct DrawState* state) { - int i = state->search_column; - size_t len = u32_strlen(state->search); - - uint32_t* pline = state->search; + uint32_t* pline; + ssize_t len; + int i; + pline = state->search; + len = u32_strlen(state->search); + i = state->search_column; if (i > 0 && u32_is_word_boundary(pline[i - 1], 0)) i--; while (i > 0 && u32_is_word_boundary(pline[i], 0)) @@ -1053,7 +1230,7 @@ erase_search_prev_word(struct DrawState* state) pline[len] = 0; state->search_column = i; state->search_display_column - = display_length(state, state->search, state->search_column); + = display_length(state->search, state->search_column); if (state->search_column < state->search_first_shown_column) { @@ -1064,12 +1241,12 @@ erase_search_prev_word(struct DrawState* state) state->search_first_shown_column = 0; } state->search_display_column - = display_length(state, state->search, state->search_column); + = display_length(state->search, state->search_column); return next_match(state); } -int +static int erase_search_to_end(struct DrawState* state) { state->search[state->search_column] = 0; @@ -1077,7 +1254,7 @@ erase_search_to_end(struct DrawState* state) return next_match(state); } -int +static int erase_search_to_start(struct DrawState* state) { size_t i = state->search_column; @@ -1092,7 +1269,7 @@ erase_search_to_start(struct DrawState* state) return next_match(state); } -int +static int erase_to_end(struct DrawState* state) { struct BufferLine* buffer_line = NULL; @@ -1100,43 +1277,46 @@ erase_to_end(struct DrawState* state) if (state->edit_info_focused) return 1; - buffer_line = &state->input_buffer[state->input_row]; - buffer_line->text[state->input_column] = 0; - buffer_line->length = state->input_column; + buffer_line = &state->input_buffer[state->coord.row]; + buffer_line->text[state->coord.col] = 0; + buffer_line->length = state->coord.col; move_end(state); state->dirty = 1; return 0; } -int +static int erase_to_start(struct DrawState* state) { - size_t i = state->input_column; - struct BufferLine* buffer_line = &state->input_buffer[state->input_row]; - size_t len = buffer_line->length; + struct BufferLine* buffer_line; + ssize_t i; + ssize_t len; + + i = state->coord.col; + buffer_line = &state->input_buffer[state->coord.row]; + len = buffer_line->length; if (state->edit_info_focused) return 1; do { - buffer_line->text[i - state->input_column] - = buffer_line->text[i]; + buffer_line->text[i - state->coord.col] = buffer_line->text[i]; i++; } while (buffer_line->text[i]); - buffer_line->text[len - state->input_column] = 0; - buffer_line->length = len - state->input_column; + buffer_line->text[len - state->coord.col] = 0; + buffer_line->length = len - state->coord.col; move_start(state); state->dirty = 1; return 0; } -int +static int exit_program(struct DrawState* state) { if (state->show_edit) { - STRLCPY(errors[ERR_DLG_OPEN], state->error, MAXBUFLINE); + state->error = ERR_DLG_OPEN; return 0; } if (state->dirty) @@ -1147,11 +1327,11 @@ exit_program(struct DrawState* state) } else state->running = 0; - *state->error = 0; + //*state->error = 0; return 0; } -int +static int hide_search(struct DrawState* state) { state->show_search = 0; @@ -1159,191 +1339,139 @@ hide_search(struct DrawState* state) return 0; } -int -insert_line(struct DrawState* state) -{ - struct BufferLine* newbuf = NULL; - - if (state->edit_info_focused) - return 1; - - state->input_rows_count++; - newbuf = realloc(state->input_buffer, - state->input_rows_count * sizeof(struct BufferLine)); - if (!newbuf) - { - STRLCPY(errors[ERR_CANT_ALLOC], state->error, MAXBUFLINE); - state->running = 0; - return 0; - } - - state->input_buffer = newbuf; - init_bufferline(&state->input_buffer[state->input_rows_count - 1]); - uint32_t* newtext = calloc(MAXBUFLINE, sizeof(uint32_t)); - if (!newtext) - { - STRLCPY(errors[ERR_CANT_ALLOC], state->error, MAXBUFLINE); - state->running = 0; - return 0; - } - state->input_buffer[state->input_rows_count - 1].text = newtext; - for (size_t i = state->input_rows_count - 1; i > state->input_row + 1; - i--) - { - u32_strncpy(state->input_buffer[i].text, - state->input_buffer[i - 1].text, MAXBUFLINE); - state->input_buffer[i].length - = state->input_buffer[i - 1].length; - } - u32_strncpy(state->input_buffer[state->input_row + 1].text, - state->input_buffer[state->input_row].text + state->input_column, - MAXBUFLINE); - state->input_buffer[state->input_row + 1].length - = u32_strlen(state->input_buffer[state->input_row + 1].text); - state->input_buffer[state->input_row].text[state->input_column] = 0; - state->input_buffer[state->input_row].length - = u32_strlen(state->input_buffer[state->input_row].text); - - state->input_row++; - if (state->input_row - > state->input_first_shown_row + (EDIT_HEIGHT - 3) / 2) - state->input_first_shown_row - = state->input_row - (EDIT_HEIGHT - 3) / 2; - move_start(state); - state->dirty = 1; - - return 0; -} - -int +static int insert_search_space(struct DrawState* state) { insert_search_char(state, (uint32_t)L' '); return 0; } -int +static int insert_search_tab(struct DrawState* state) { insert_search_char(state, (uint32_t)L'\t'); return 0; } -int +static int insert_space(struct DrawState* state) { insert_char(state, (uint32_t)L' '); return 0; } -int +static int insert_tab(struct DrawState* state) { insert_char(state, (uint32_t)L'\t'); return 0; } -int -move_end(struct DrawState* state) +static int +move_down(struct DrawState* state) { + ssize_t dlen; + if (state->edit_info_focused) { - if (state->info_rows_count > (EDIT_HEIGHT - 3) / 2) - state->info_first_shown_row = state->info_rows_count - - (EDIT_HEIGHT - 3) / 2; - state->info_first_shown_column = 0; + if (state->info_first_shown.row + (EDIT_HEIGHT - 3) / 2 + < state->info_rows_count) + state->info_first_shown.row++; return 0; } - size_t len = state->input_buffer[state->input_row].length; - state->input_column = len; - size_t dlen = display_length(state, - state->input_buffer[state->input_row].text, - state->input_column); - state->input_display_column = dlen; - if (dlen > state->edit_width - 4) - state->input_first_shown_column = dlen - state->edit_width + 4; + if (state->coord.row < state->input_rows_count - 1) + state->coord.row++; + if (state->input_first_shown.row + (EDIT_HEIGHT - 3) / 2 + < state->coord.row) + state->input_first_shown.row + = state->coord.row - (EDIT_HEIGHT - 3) / 2; + if (state->saved_column == -1) + state->saved_column = state->input_display_column; + dlen = display_length(state->input_buffer[state->coord.row].text, + state->input_buffer[state->coord.row].length); + if (dlen < state->saved_column) + state->input_display_column = dlen; else - state->input_first_shown_column = 0; + state->input_display_column = state->saved_column; + state->coord.col + = input_length(state->input_buffer[state->coord.row].text, + state->input_display_column); + if (state->input_first_shown.col > state->input_display_column) + state->input_first_shown.col = state->input_display_column; + if (state->input_first_shown.col + state->edit_width - 4 + < state->input_display_column) + state->input_first_shown.col + = state->input_display_column - state->edit_width + 4; + state->input_display_column + = display_length(state->input_buffer[state->coord.row].text, + state->coord.col); return 0; } -int -move_down(struct DrawState* state) +static int +move_end(struct DrawState* state) { + ssize_t len; + ssize_t dlen; + if (state->edit_info_focused) { - if (state->info_first_shown_row + (EDIT_HEIGHT - 3) / 2 - < state->info_rows_count) - state->info_first_shown_row++; + if (state->info_rows_count > (EDIT_HEIGHT - 3) / 2) + state->info_first_shown.row = state->info_rows_count + - (EDIT_HEIGHT - 3) / 2; + state->info_first_shown.col = 0; return 0; } - if (state->input_row < state->input_rows_count - 1) - state->input_row++; - if (state->input_first_shown_row + (EDIT_HEIGHT - 3) / 2 - < state->input_row) - state->input_first_shown_row - = state->input_row - (EDIT_HEIGHT - 3) / 2; - if (state->saved_column == -1) - state->saved_column = state->input_display_column; - size_t dlen = display_length(state, - state->input_buffer[state->input_row].text, - state->input_buffer[state->input_row].length); - if (dlen < state->saved_column) - state->input_display_column = dlen; + len = state->input_buffer[state->coord.row].length; + state->coord.col = len; + dlen = display_length(state->input_buffer[state->coord.row].text, + state->coord.col); + state->input_display_column = dlen; + if (dlen > state->edit_width - 4) + state->input_first_shown.col = dlen - state->edit_width + 4; else - state->input_display_column = state->saved_column; - state->input_column = input_length(state, - state->input_buffer[state->input_row].text, - state->input_display_column); - if (state->input_first_shown_column > state->input_display_column) - state->input_first_shown_column = state->input_display_column; - if (state->input_first_shown_column + state->edit_width - 4 - < state->input_display_column) - state->input_first_shown_column - = state->input_display_column - state->edit_width + 4; - state->input_display_column = display_length(state, - state->input_buffer[state->input_row].text, - state->input_column); + state->input_first_shown.col = 0; return 0; } -int +static int move_left(struct DrawState* state) { if (state->edit_info_focused) { - if (state->info_first_shown_column > 0) - state->info_first_shown_column--; + if (state->info_first_shown.col > 0) + state->info_first_shown.col--; return 0; } - if (state->input_column == 0) + if (state->coord.col == 0) return 0; - if (state->input_buffer[state->input_row].text[state->input_column - 1] + if (state->input_buffer[state->coord.row].text[state->coord.col - 1] == '\t') - state->input_display_column = display_length(state, - state->input_buffer[state->input_row].text, - state->input_column - 1); + state->input_display_column + = display_length(state->input_buffer[state->coord.row] + .text, + state->coord.col - 1); else state->input_display_column--; - state->input_column--; + state->coord.col--; - if (state->input_display_column < state->input_first_shown_column) - state->input_first_shown_column = state->input_display_column; + if (state->input_display_column < state->input_first_shown.col) + state->input_first_shown.col = state->input_display_column; return 0; } -int +static int move_list_down(struct DrawState* state) { goto_msgid(state, next_msgid(state, 1)); return 0; } -int +static int move_list_end(struct DrawState* state) { goto_msgid(state, @@ -1352,69 +1480,76 @@ move_list_end(struct DrawState* state) return 0; } -int +static int move_list_page_down(struct DrawState* state) { - *state->error = 0; + state->error = ERR_NONE; goto_msgid(state, next_msgid(state, state->maxy - 2)); return 0; } -int +static int move_list_page_up(struct DrawState* state) { - *state->error = 0; + state->error = ERR_NONE; goto_msgid(state, prev_msgid(state, state->maxy - 2)); return 0; } -int +static int move_list_start(struct DrawState* state) { goto_msgid(state, 1); return 0; } -int +static int move_list_up(struct DrawState* state) { goto_msgid(state, prev_msgid(state, 1)); return 0; } -int +static int move_next_word(struct DrawState* state) { + int i; + uint32_t* pline; + ssize_t len; + if (state->edit_info_focused) return 1; - int i = state->input_column; - uint32_t* pline = state->input_buffer[state->input_row].text; - size_t len = state->input_buffer[state->input_row].length; + i = state->coord.col; + pline = state->input_buffer[state->coord.row].text; + len = state->input_buffer[state->coord.row].length; while (i < len && !u32_is_word_boundary(pline[i], 0)) i++; while (i < len && u32_is_word_boundary(pline[i], 0)) i++; - state->input_column = i; - state->input_display_column = display_length(state, - state->input_buffer[state->input_row].text, - state->input_column); - if (state->input_display_column - state->input_first_shown_column + state->coord.col = i; + state->input_display_column + = display_length(state->input_buffer[state->coord.row].text, + state->coord.col); + if (state->input_display_column - state->input_first_shown.col > state->edit_width - 4) - state->input_first_shown_column + state->input_first_shown.col = state->input_display_column - state->edit_width + 4; return 0; } -int +static int move_prev_word(struct DrawState* state) { + int i; + uint32_t* pline; + if (state->edit_info_focused) return 1; - int i = state->input_column; - uint32_t* pline = state->input_buffer[state->input_row].text; + i = state->coord.col; + pline = state->input_buffer[state->coord.row].text; if (i > 0 && u32_is_word_boundary(pline[i - 1], 0)) i--; @@ -1424,53 +1559,54 @@ move_prev_word(struct DrawState* state) && !u32_is_word_boundary(pline[i - 1], 0)) i--; - state->input_column = i; - state->input_display_column = display_length(state, - state->input_buffer[state->input_row].text, - state->input_column); - if (state->input_display_column < state->input_first_shown_column) - state->input_first_shown_column = state->input_display_column; + state->coord.col = i; + state->input_display_column + = display_length(state->input_buffer[state->coord.row].text, + state->coord.col); + if (state->input_display_column < state->input_first_shown.col) + state->input_first_shown.col = state->input_display_column; return 0; } -int +static int move_right(struct DrawState* state) { + ssize_t len; + if (state->edit_info_focused) { - if (state->info_first_shown_column + state->edit_width - 3 + if (state->info_first_shown.col + state->edit_width - 3 < state->info_rows_maxlen + 1) - state->info_first_shown_column++; + state->info_first_shown.col++; return 0; } - size_t len = state->input_buffer[state->input_row].length; - if (state->input_column == len) + len = state->input_buffer[state->coord.row].length; + if (state->coord.col == len) return 0; - if (state->input_buffer[state->input_row].text[state->input_column] - == '\t') - state->input_display_column = display_length(state, - state->input_buffer[state->input_row].text, - state->input_column + 1); + if (state->input_buffer[state->coord.row].text[state->coord.col] == '\t') + state->input_display_column + = display_length(state->input_buffer[state->coord.row] + .text, + state->coord.col + 1); else state->input_display_column++; - state->input_column++; + state->coord.col++; - if (state->input_display_column - state->input_first_shown_column + if (state->input_display_column - state->input_first_shown.col > state->edit_width - 4) - state->input_first_shown_column + state->input_first_shown.col = state->input_display_column - state->edit_width + 4; return 0; } -int +static int move_search_end(struct DrawState* state) { - size_t len = u32_strlen(state->search); + ssize_t len = u32_strlen(state->search); state->search_column = len; - size_t dlen - = display_length(state, state->search, state->search_column); + ssize_t dlen = display_length(state->search, state->search_column); state->search_display_column = dlen; if (dlen > state->search_width - 4) state->search_first_shown_column @@ -1480,15 +1616,15 @@ move_search_end(struct DrawState* state) return 0; } -int +static int move_search_left(struct DrawState* state) { if (state->search_column == 0) return 0; if (state->search[state->search_column - 1] == '\t') - state->search_display_column = display_length(state, - state->search, state->search_column - 1); + state->search_display_column = display_length(state->search, + state->search_column - 1); else state->search_display_column--; state->search_column--; @@ -1498,12 +1634,12 @@ move_search_left(struct DrawState* state) return 0; } -int +static int move_search_next_word(struct DrawState* state) { int i = state->search_column; uint32_t* pline = state->search; - size_t len = u32_strlen(state->search); + ssize_t len = u32_strlen(state->search); while (i < len && !u32_is_word_boundary(pline[i], 0)) i++; @@ -1511,20 +1647,20 @@ move_search_next_word(struct DrawState* state) i++; state->search_column = i; state->search_display_column - = display_length(state, state->search, state->search_column); + = display_length(state->search, state->search_column); if (state->search_display_column - state->search_first_shown_column > state->search_width - 4) { state->search_first_shown_column = state->search_display_column - state->search_width + 4; - state->search_display_column = display_length(state, - state->search, state->search_column); + state->search_display_column + = display_length(state->search, state->search_column); } return 0; } -int +static int move_search_prev_word(struct DrawState* state) { int i = state->search_column; @@ -1540,7 +1676,7 @@ move_search_prev_word(struct DrawState* state) state->search_column = i; state->search_display_column - = display_length(state, state->search, state->search_column); + = display_length(state->search, state->search_column); if (state->search_column < state->search_first_shown_column) { @@ -1551,20 +1687,20 @@ move_search_prev_word(struct DrawState* state) state->search_first_shown_column = 0; } state->search_display_column - = display_length(state, state->search, state->search_column); + = display_length(state->search, state->search_column); return 0; } -int +static int move_search_right(struct DrawState* state) { - size_t len = u32_strlen(state->search); + ssize_t len = u32_strlen(state->search); if (state->search_column == len) return 0; if (state->search[state->search_column] == '\t') - state->search_display_column = display_length(state, - state->search, state->search_column + 1); + state->search_display_column = display_length(state->search, + state->search_column + 1); else state->search_display_column++; state->search_column++; @@ -1576,7 +1712,7 @@ move_search_right(struct DrawState* state) return 0; } -int +static int move_search_start(struct DrawState* state) { state->search_column = 0; @@ -1585,70 +1721,71 @@ move_search_start(struct DrawState* state) return 0; } -int +static int move_start(struct DrawState* state) { if (state->edit_info_focused) { - state->info_first_shown_row = 0; - state->info_first_shown_column = 0; + state->info_first_shown.row = 0; + state->info_first_shown.col = 0; return 0; } - state->input_column = 0; - state->input_display_column = 0; - state->input_first_shown_column = 0; + state->coord.col = 0; + state->input_display_column = 0; + state->input_first_shown.col = 0; return 0; } -int +static int move_up(struct DrawState* state) { + ssize_t dlen; + if (state->edit_info_focused) { - if (state->info_first_shown_row > 0) - state->info_first_shown_row--; + if (state->info_first_shown.row > 0) + state->info_first_shown.row--; return 0; } - if (state->input_row > 0) - state->input_row--; - if (state->input_first_shown_row > state->input_row) - state->input_first_shown_row = state->input_row; + if (state->coord.row > 0) + state->coord.row--; + if (state->input_first_shown.row > state->coord.row) + state->input_first_shown.row = state->coord.row; if (state->saved_column == -1) state->saved_column = state->input_display_column; - size_t dlen = display_length(state, - state->input_buffer[state->input_row].text, - state->input_buffer[state->input_row].length); + dlen = display_length(state->input_buffer[state->coord.row].text, + state->input_buffer[state->coord.row].length); if (dlen < state->saved_column) state->input_display_column = dlen; else state->input_display_column = state->saved_column; - state->input_column = input_length(state, - state->input_buffer[state->input_row].text, - state->input_display_column); - if (state->input_first_shown_column > state->input_display_column) - state->input_first_shown_column = state->input_display_column; - if (state->input_first_shown_column + state->edit_width - 4 + state->coord.col + = input_length(state->input_buffer[state->coord.row].text, + state->input_display_column); + if (state->input_first_shown.col > state->input_display_column) + state->input_first_shown.col = state->input_display_column; + if (state->input_first_shown.col + state->edit_width - 4 < state->input_display_column) - state->input_first_shown_column + state->input_first_shown.col = state->input_display_column - state->edit_width + 4; - state->input_display_column = display_length(state, - state->input_buffer[state->input_row].text, - state->input_column); + state->input_display_column + = display_length(state->input_buffer[state->coord.row].text, + state->coord.col); return 0; } -int +static int next_fuzzy(struct DrawState* state) { + struct PoEntry* current = NULL; + if (state->msgid_count == 0) return 0; - *state->error = 0; - - struct PoEntry* current = NULL; - for (size_t e = state->msgid_number; e < state->msgid_count; e++) + state->error = ERR_NONE; + for (ssize_t e = state->msgid_number; e < state->msgid_count; e++) { current = state->entries + e; if (current->flags & FL_FUZZY) @@ -1676,15 +1813,16 @@ next_fuzzy(struct DrawState* state) return 0; } -int +static int next_match(struct DrawState* state) { + struct PoEntry* current = NULL; + if (state->msgid_count == 0 || !state->search || !*state->search) return 0; - *state->error = 0; - struct PoEntry* current = NULL; - for (size_t e = state->msgid_number; e < state->real_msgid_count; e++) + state->error = ERR_NONE; + for (ssize_t e = state->msgid_number; e < state->real_msgid_count; e++) { current = state->entries + e; if (current->obsolete) @@ -1702,7 +1840,7 @@ next_match(struct DrawState* state) return 0; } -int +static int next_plural(struct DrawState* state) { struct PoEntry* current = NULL; @@ -1716,16 +1854,17 @@ next_plural(struct DrawState* state) return 0; } -int +static int next_untranslated(struct DrawState* state) { + struct PoEntry* current = NULL; + if (state->msgid_count == 0) return 0; - *state->error = 0; + state->error = ERR_NONE; - struct PoEntry* current = NULL; - for (size_t e = state->msgid_number; e < state->msgid_count; e++) + for (ssize_t e = state->msgid_number; e < state->msgid_count; e++) { current = state->entries + e; if (!current->msgstr || !*current->msgstr @@ -1754,9 +1893,15 @@ next_untranslated(struct DrawState* state) return 0; } -int +static int paste_paste_buffer_to_input(struct DrawState* state) { + struct BufferLine* pinput = NULL; + struct BufferLine* newbuf = NULL; + struct BufferLine* ppaste = NULL; + ssize_t lines_to_init = 0; + ssize_t lines_to_free = 0; + if (state->edit_info_focused) return 1; @@ -1770,64 +1915,56 @@ paste_paste_buffer_to_input(struct DrawState* state) if (state->msgid_number == 1) { - STRLCPY(errors[ERR_ILLEGAL_ON_FIRST], state->error, MAXBUFLINE); + state->error = ERR_ILLEGAL_ON_FIRST; return 0; } - size_t lines_to_init = 0; - size_t lines_to_free = 0; - struct BufferLine* pinput = NULL; - struct BufferLine* newbuf = NULL; - struct BufferLine* ppaste = NULL; - - if (!state->paste_buffer || !(*state->paste_buffer).text - || !*(*state->paste_buffer).text) + if (!state->paste || !(*state->paste).text || !*(*state->paste).text) { /* leave one row */ - for (size_t i = 1; i < state->input_rows_count; i++) + for (ssize_t i = 1; i < state->input_rows_count; i++) free_bufferline(state->input_buffer + i); - state->input_buffer = realloc(state->input_buffer, - sizeof(struct BufferLine)); - *(state->input_buffer->text) = 0; - state->input_column = 0; - state->input_display_column = 0; - state->input_first_shown_column = 0; - state->input_first_shown_row = 0; - state->input_row = 0; - state->input_rows_count = 1; - state->dirty = 1; + state->input_buffer = realloc(state->input_buffer, + sizeof(struct BufferLine)); + *(state->input_buffer->text) = 0; + init_coord(&state->coord, 0, 0); + state->input_display_column = 0; + init_coord(&state->input_first_shown, 0, 0); + state->input_rows_count = 1; + state->dirty = 1; return 0; } - lines_to_init = state->input_rows_count < state->paste_rows_count - ? state->paste_rows_count - state->input_rows_count + lines_to_init = state->input_rows_count < state->paste_count + ? state->paste_count - state->input_rows_count : 0; - lines_to_free = state->input_rows_count > state->paste_rows_count - ? state->input_rows_count - state->paste_rows_count + lines_to_free = state->input_rows_count > state->paste_count + ? state->input_rows_count - state->paste_count : 0; - for (size_t i = 0; i < lines_to_free; i++) + for (ssize_t i = 0; i < lines_to_free; i++) free_bufferline( state->input_buffer + state->input_rows_count - i - 1); newbuf = realloc(state->input_buffer, - state->paste_rows_count * sizeof(struct BufferLine)); - ppaste = state->paste_buffer; + state->paste_count * sizeof(struct BufferLine)); + ppaste = state->paste; if (!newbuf) return 0; - for (size_t i = 0; i < lines_to_init; i++) + for (ssize_t i = 0; i < lines_to_init; i++) { - pinput = newbuf + state->paste_rows_count - 1 - i; + uint32_t* newtext; + pinput = newbuf + state->paste_count - 1 - i; init_bufferline(pinput); - uint32_t* newtext = calloc(MAXBUFLINE, sizeof(uint32_t)); + newtext = calloc(MAXBUFLINE, sizeof(uint32_t)); if (!newtext) return 0; pinput->text = newtext; } if (ppaste) - for (size_t i = 0; i < state->paste_rows_count; i++) + for (ssize_t i = 0; i < state->paste_count; i++) { pinput = newbuf + i; u32_strncpy(pinput->text, ppaste->text, @@ -1836,22 +1973,23 @@ paste_paste_buffer_to_input(struct DrawState* state) ppaste++; } - state->input_rows_count = state->paste_rows_count; + state->input_rows_count = state->paste_count; state->input_buffer = newbuf; state->dirty = 1; return 0; } -int +static int prev_match(struct DrawState* state) { + struct PoEntry* current = NULL; + if (state->msgid_count == 0 || !state->search || !*state->search) return 0; - *state->error = 0; - struct PoEntry* current = NULL; - for (size_t e = state->msgid_number - 1; e > 0; e--) + state->error = ERR_NONE; + for (ssize_t e = state->msgid_number - 1; e > 0; e--) { current = state->entries + e - 1; if ((current->msgid && *current->msgid @@ -1867,7 +2005,7 @@ prev_match(struct DrawState* state) return 0; } -int +static int prev_plural(struct DrawState* state) { if (state->edit_info_focused) @@ -1879,9 +2017,10 @@ prev_plural(struct DrawState* state) return 0; } -int +static int save_msgstr(struct DrawState* state) { + struct BufferLine* pinput_buffer; uint32_t* to_save = NULL; size_t to_save_size = 0; struct PoEntry* current = NULL; @@ -1895,12 +2034,11 @@ save_msgstr(struct DrawState* state) to_save = calloc(to_save_size, sizeof(uint32_t)); if (!to_save) { - STRLCPY(errors[ERR_CANT_ALLOC], state->error, - MAXBUFLINE); + state->error = ERR_CANT_ALLOC; return 0; } - struct BufferLine* pinput_buffer = NULL; - for (size_t i = 0; i < state->input_rows_count; i++) + pinput_buffer = NULL; + for (ssize_t i = 0; i < state->input_rows_count; i++) { pinput_buffer = state->input_buffer + i; if (i == 0) @@ -1919,7 +2057,7 @@ save_msgstr(struct DrawState* state) free(state->input_buffer); state->input_buffer = NULL; state->input_rows_count = 0; - for (size_t i = 0; i < state->info_rows_count; i++) + for (ssize_t i = 0; i < state->info_rows_count; i++) free_bufferline(state->info_buffer + i); free(state->info_buffer); state->dirty = (state->dirty_before_edit || state->dirty); @@ -1938,7 +2076,7 @@ save_msgstr(struct DrawState* state) return 0; } -int +static int show_edit(struct DrawState* state) { if (state->msgstr_index + 1 @@ -1947,35 +2085,33 @@ show_edit(struct DrawState* state) if (!load_msgstr(state) || !load_info(state)) { - STRLCPY(errors[ERR_CANT_ALLOC], state->error, MAXBUFLINE); + state->error = ERR_CANT_ALLOC; state->running = 0; return 0; } - state->dirty_before_edit = state->dirty; - state->dirty = 0; - state->fuzzy_before_edit = state->fuzzy_count; - state->input_changed_fuzzy = UNCHANGED; - state->show_edit = 1; - state->input_row = 0; - state->input_first_shown_row = 0; - state->input_first_shown_column = 0; - state->info_first_shown_row = 0; - state->info_first_shown_column = 0; + state->dirty_before_edit = state->dirty; + state->dirty = 0; + state->fuzzy_before_edit = state->fuzzy_count; + state->input_changed_fuzzy = UNCHANGED; + state->show_edit = 1; + state->coord.row = 0; + init_coord(&state->input_first_shown, 0, 0); + init_coord(&state->info_first_shown, 0, 0); move_start(state); - *state->error = 0; + state->error = ERR_NONE; return 0; } -int +static int show_help(struct DrawState* state) { state->show_help = 1; - *state->error = 0; + state->error = ERR_NONE; return 0; } -int +static int show_search(struct DrawState* state) { state->show_search = 1; @@ -1990,12 +2126,12 @@ show_search(struct DrawState* state) return 0; } -int +static int toggle_fuzzy(struct DrawState* state) { if (state->msgid_number == 1) { - STRLCPY(errors[ERR_ILLEGAL_ON_FIRST], state->error, MAXBUFLINE); + state->error = ERR_ILLEGAL_ON_FIRST; return 0; } if (state->msgid_count > 0) @@ -2022,75 +2158,75 @@ toggle_fuzzy(struct DrawState* state) return 0; } -int +static int toggle_info_focused(struct DrawState* state) { state->edit_info_focused = !state->edit_info_focused; return 0; } -int +static int yank_input_to_paste_buffer(struct DrawState* state) { + struct BufferLine* ppaste = NULL; + struct BufferLine* newbuf = NULL; + struct BufferLine* pinput = NULL; + ssize_t lines_to_init = 0; + ssize_t lines_to_free = 0; + if (state->edit_info_focused) return 1; if (state->msgid_number == 1) { - STRLCPY(errors[ERR_ILLEGAL_ON_FIRST], state->error, MAXBUFLINE); + state->error = ERR_ILLEGAL_ON_FIRST; return 0; } - size_t lines_to_init = 0; - size_t lines_to_free = 0; - struct BufferLine* ppaste = NULL; - struct BufferLine* newbuf = NULL; - struct BufferLine* pinput = NULL; - if (!state->input_buffer || !(*state->input_buffer).text || !*(*state->input_buffer).text) { - for (size_t i = 0; i < state->paste_rows_count; i++) - free_bufferline(state->paste_buffer + i); - free(state->paste_buffer); - state->paste_buffer = NULL; - state->paste_rows_count = 0; + for (ssize_t i = 0; i < state->paste_count; i++) + free_bufferline(state->paste + i); + free(state->paste); + state->paste = NULL; + state->paste_count = 0; return 0; } - lines_to_init = state->paste_rows_count < state->input_rows_count - ? state->input_rows_count - state->paste_rows_count + lines_to_init = state->paste_count < state->input_rows_count + ? state->input_rows_count - state->paste_count : 0; - lines_to_free = state->paste_rows_count > state->input_rows_count - ? state->paste_rows_count - state->input_rows_count + lines_to_free = state->paste_count > state->input_rows_count + ? state->paste_count - state->input_rows_count : 0; - for (size_t i = 0; i < lines_to_free; i++) - free_bufferline( - state->paste_buffer + state->paste_rows_count - i - 1); - if (!state->paste_buffer) + for (ssize_t i = 0; i < lines_to_free; i++) + free_bufferline(state->paste + state->paste_count - i - 1); + if (!state->paste) newbuf = calloc(state->input_rows_count, sizeof(struct BufferLine)); else - newbuf = realloc(state->paste_buffer, + newbuf = realloc(state->paste, state->input_rows_count * sizeof(struct BufferLine)); pinput = state->input_buffer; if (!newbuf) return 0; - for (size_t i = 0; i < lines_to_init; i++) + for (ssize_t i = 0; i < lines_to_init; i++) { + uint32_t* newtext; ppaste = newbuf + state->input_rows_count - 1 - i; init_bufferline(ppaste); - uint32_t* newtext = calloc(MAXBUFLINE, sizeof(uint32_t)); + newtext = calloc(MAXBUFLINE, sizeof(uint32_t)); if (!newtext) return 0; ppaste->text = newtext; } if (pinput) - for (size_t i = 0; i < state->input_rows_count; i++) + for (ssize_t i = 0; i < state->input_rows_count; i++) { ppaste = newbuf + i; u32_strncpy(ppaste->text, pinput->text, @@ -2099,35 +2235,44 @@ yank_input_to_paste_buffer(struct DrawState* state) pinput++; } - state->paste_rows_count = state->input_rows_count; - state->paste_buffer = newbuf; + state->paste_count = state->input_rows_count; + state->paste = newbuf; return 0; } -int +static int write_file(struct DrawState* state) { + assert(state != NULL); + if (save_file(state)) + { + state->running = 0; + return 1; + } + /* TODO: Check int result = save_file(state->entries, state->real_msgid_count, state->filename, state->nplurals); switch (result) { case SAVE_ERR_CANT_ALLOC: - STRLCPY(errors[ERR_CANT_ALLOC], state->error, MAXBUFLINE); + state->error = ERR_CANT_ALLOC; state->running = 0; break; case SAVE_ERR_CANT_OPEN_FILE: - STRLCPY(errors[ERR_CANT_SAVE], state->error, MAXBUFLINE); + state->error = ERR_CANT_SAVE; state->running = 0; break; case SAVE_ERR_CANT_MOVE_FILE: - STRLCPY(strerror(errno), state->error, MAXBUFLINE); + state->error = ERR_ERRNO_SET; state->running = 0; break; default: - *state->error = 0; + state->error = ERR_NONE; state->dirty = 0; - } + }*/ + state->error = ERR_NONE; + state->dirty = 0; return 0; } @@ -2135,290 +2280,389 @@ int main(int argc, char** argv) { struct tb_event ev; - char error[MAXBUFLINE]; - char prompt[MAXBUFLINE]; struct DrawState state; - char filename[MAXPATH]; - int recalculate_size = 1; struct stat st; + char prompt[LINE_DEFAULT]; + char* dir = NULL; + char* filename = NULL; + char* pdir = NULL; + char* temp = NULL; + char* rfn = NULL; + const char* saved_tb_error = NULL; + char* wd = NULL; + ssize_t filename_size = 0; long lineno; long col; - int result; int dir_access; int dir_access_errno; + int optind = 1; + int result; int saved_errno = 0; - char* dir = NULL; + int tbires; - *error = 0; - *prompt = 0; - *filename = 0; + *prompt = 0; #ifdef __OpenBSD__ if (pledge("stdio tty unveil rpath wpath cpath fattr", NULL) < 0) { - perror(PROGRAMNAME ": pledge"); - exit(1); + perror(PROGRAM_NAME ": pledge"); + exit(errno); } #endif + + setlocale(LC_ALL, ""); + if (argc == 1) - return print_error(1, "No filename given"); + return print_error(CUSTOM_ERROR_START + ERR_NO_CL_ARG, + "No filename given"); - if (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) + // main_options_loop: + if (optind == argc) + return print_error(CUSTOM_ERROR_START + ERR_NO_CL_ARG, + "No filename given"); + + if (!strcmp(argv[optind], "-h") || !strcmp(argv[optind], "--help")) { printf("Usage:\t%s -h | --help | -V | --full-version | -v |" " --version\n" "\t%s filename.po\n", - PROGRAMNAME, PROGRAMNAME); + PROGRAM_NAME, PROGRAM_NAME); return 0; } - else if (!strcmp(argv[1], "-v") || !strcmp(argv[1], "--version")) + else if (!strcmp(argv[optind], "-v") + || !strcmp(argv[optind], "--version")) { - printf("%s %s, committed on %s\n", PROGRAMNAME, VERSION, DATE); + printf("%s %s, committed on %s\n", PROGRAM_NAME, VERSION, DATE); return 0; } - else if (!strcmp(argv[1], "-V") || !strcmp(argv[1], "--full-version")) + else if (!strcmp(argv[optind], "-V") + || !strcmp(argv[optind], "--full-version")) { - printf("%s %s, committed on %s\n", PROGRAMNAME, VERSION, DATE); + printf("%s %s, committed on %s\n", PROGRAM_NAME, VERSION, DATE); puts(COPYRIGHT); printf("Features set in config.h:\n"); -#ifdef CREATE_BACKUPS - printf(" CREATE_BACKUPS:\tset\n"); -#else - printf(" CREATE_BACKUPS:\tunset\n"); -#endif -#ifdef MATCH_MSGSTR_ENDING - printf(" MATCH_MSGSTR_ENDING:\tset\n"); -#else - printf(" MATCH_MSGSTR_ENDING:\tunset\n"); -#endif -#ifdef PLURAL_STRING - printf(" PLURAL_STRING:\t\"%s\"\n", PLURAL_STRING); -#else - printf(" PLURAL_STRING:\tunset\n"); -#endif - printf(" SAVE_WRAP_WIDTH:\t%d\n", SAVE_WRAP_WIDTH); - printf(" WRAP_FIRST_MSGSTR:\t%d\n", WRAP_FIRST_MSGSTR); - printf(" WARN_COUNT_DOTS:\t%d\n", WARN_COUNT_DOTS); + printf(" backup_fail:\t\t%s\n", backup_fail ? "set" : "unset"); + if (backup_suffix) + printf(" backup_suffix:\t\"%s\"\n", backup_suffix); + else + printf(" backup_suffix:\tunset\n"); + printf(" create_backups:\t%s\n", + create_backups ? "set" : "unset"); + printf(" match_msgstr_ending:\t%s\n", + match_msgstr_ending ? "set" : "unset"); + if (plural_string) + printf(" plural_string:\t\"%s\"\n", plural_string); + else + printf(" plural_string:\tunset\n"); + printf(" warn_count_dots:\t%s\n", + warn_count_dots ? "set" : "unset"); + printf(" wrap_first_msgstr:\t%s\n", + wrap_first_msgstr ? "set" : "unset"); + printf(" wrap_width:\t\t%d\n", wrap_width); return 0; } - else + + /* + * Chicken and egg problem: to get fpathconf(dirfd, _PC_PATH_MAX) + * we need a dir, which needs to be allocated, and to allocate it we + * need to know the value returned by fpathconf(dirfd, _PC_PATH_MAX)... + */ + /* argv[1] is the filename; BTW, "filename" is a misnomer */ + filename_size = strlen(argv[optind]) + 1; + if (!(dir = malloc(filename_size))) { - strncpy(filename, argv[1], MAXPATH - 1); - filename[MAXPATH - 1] = 0; - if (!*filename) - print_error(EINVAL, "No filename given"); + perror(PROGRAM_NAME ": malloc"); + exit(errno); } - init_drawstate(&state, error, filename, prompt); + (void)dirname(dir, argv[optind], filename_size); + if (init_limits(dir) < 0) + { + perror(PROGRAM_NAME ": init_limits"); + free(dir); + exit(errno); + } - if (stat(filename, &st) == -1 || access(filename, R_OK | W_OK) == -1) - goto file_error; + filename = calloc(limits[L_PATH_MAX], 1); + if (!filename) + { + perror(PROGRAM_NAME ": calloc"); + exit(errno); + } - if (!S_ISREG(st.st_mode)) + *filename = 0; + MEMCCPY(filename, argv[optind], limits[L_PATH_MAX], temp); + if (!*filename) { - result = print_error(EINVAL, "Is not a regular file"); - goto general_error; + free(filename); + free(dir); + return print_error(EINVAL, "No filename given"); } + init_drawstate(&state, filename, prompt); + #ifdef __OpenBSD__ if (unveil(state.filename, "rwc") < 0) { - perror(PROGRAMNAME ": unveil"); - exit(1); + perror(PROGRAM_NAME ": unveil"); + goto main_done; + } + if (unveil(dir, "rwc") < 0) + { + perror(PROGRAM_NAME ": unveil"); + goto main_done; } #endif - dir = dirname(state.filename); + if (!create_backups) + goto main_skip_backups; + + if (!(wd = malloc(limits[L_PATH_MAX]))) + { + perror(PROGRAM_NAME ": malloc"); + goto main_done; + } + if (!getcwd(wd, limits[L_PATH_MAX])) + { + perror(PROGRAM_NAME ": getcwd"); + goto main_done; + } #ifdef __OpenBSD__ if (unveil(dir, "rwc") < 0) { - perror(PROGRAMNAME ": unveil"); - exit(1); + perror(PROGRAM_NAME ": unveil"); + goto main_done; } #endif -#ifdef CREATE_BACKUPS + errno = 0; if (lstat(state.filename, &st) != -1) { if (S_ISLNK(st.st_mode)) { - if (!(state.real_filename = calloc(MAXPATH, 1))) - return print_error(1, - "Allocation failed (out of memory?)"); - state.rfn_len = readlink(state.filename, - state.real_filename, MAXPATH); + if (!(rfn = malloc(limits[L_PATH_MAX]))) + { + perror(PROGRAM_NAME ": malloc"); + goto main_done; + } + + if (!(state.real_filename = malloc(limits[L_PATH_MAX]))) + { + perror(PROGRAM_NAME ": malloc"); + goto main_done; + } + + state.rfn_len = readlink(state.filename, rfn, + limits[L_PATH_MAX]); if (state.rfn_len == -1) - goto file_error; - state.real_filename[state.rfn_len] = 0; + goto main_done; + + rfn[state.rfn_len] = 0; + + if (*rfn == '/') /* rfn is absolute */ + { + MEMCCPY(state.real_filename, rfn, + limits[L_PATH_MAX], temp); + state.rfn_len = strlen(rfn); + } + else /* rfn is relative */ + { + size_t rfn_dir_len; + + state.orig_real_filename + = strndup(rfn, state.rfn_len); + if (!state.orig_real_filename) + { + perror(PROGRAM_NAME ": strndup"); + goto main_done; + } + + MEMCCPY(state.real_filename, dir, + limits[L_PATH_MAX], temp); + if (!temp) + { + errno = ENAMETOOLONG; + goto main_done; + } + *(temp - 1) = '/'; /* overwrite NUL */ + rfn_dir_len = temp - state.real_filename - 1; + MEMCCPY_EXT(temp, state.real_filename, rfn, + limits[L_PATH_MAX] - rfn_dir_len, temp); + if (temp) + state.rfn_len = temp + - state.real_filename - 1; + } + free(rfn); + rfn = NULL; #ifdef __OpenBSD__ if (unveil(state.real_filename, "rwc") < 0) { - perror(PROGRAMNAME ": unveil"); - exit(1); + perror(PROGRAM_NAME ": unveil"); + goto main_done; } #endif if (stat(state.real_filename, &st) == -1) - goto file_error; - } - if (!(state.backup_filename = calloc(MAXPATH, 1))) - { - free(state.real_filename); - return print_error(1, - "Allocation failed (out of memory?)"); + goto main_done; } + } + else + goto main_done; - /* Backup filename is the target file + suffix if the file to - * write is a symlink, otherwise file + suffix */ - STRLCPY(state.rfn_len > 0 ? state.real_filename : state.filename, - state.backup_filename, MAXPATH); - STRLCAT(backup_suffix, state.backup_filename, MAXPATH); -#ifdef __OpenBSD__ - if (unveil(state.backup_filename, "rwc") < 0) - { - perror(PROGRAMNAME ": unveil"); - exit(1); - } -#endif + if (!(state.backup_filename = malloc(limits[L_PATH_MAX]))) + { + perror(PROGRAM_NAME ": malloc"); + goto main_done; } + + /* Backup filename is the target file + suffix if the file to + * write is a symlink, otherwise file + suffix */ + MEMCCPY(state.backup_filename, + state.rfn_len > 0 ? state.real_filename : state.filename, + limits[L_PATH_MAX], temp); + if (!temp) /* filename is full, turning off backups */ + state.backup_possible = 0; else - goto file_error; -#endif /* CREATE_BACKUPS */ + MEMCCPY_EXT((temp - 1), state.backup_filename, backup_suffix, + limits[L_PATH_MAX] + temp - state.backup_filename, + temp); +#ifdef __OpenBSD__ + if (unveil(state.backup_filename, "rwc") < 0) + { + perror(PROGRAM_NAME ": unveil"); + goto main_done; + } +#endif +main_skip_backups: if (state.rfn_len > 0) { - dir = dirname(state.real_filename); + if (!(pdir = realloc(dir, limits[L_PATH_MAX]))) + { + perror(PROGRAM_NAME ": realloc"); + goto main_done; + } + dir = pdir; + *dir = 0; + (void)dirname(dir, state.real_filename, limits[L_PATH_MAX]); #ifdef __OpenBSD__ if (unveil(dir, "rwc") < 0) { - perror(PROGRAMNAME ": unveil"); - exit(1); + perror(PROGRAM_NAME ": unveil"); + goto main_done; } #endif } - dir_access = access(dir, W_OK | X_OK); - dir_access_errno = errno; -#ifdef BACKUP_FAIL_OK - state.backup_possible = dir_access != -1; -#else /* !BACKUP_FAIL_OK */ - if (dir_access == -1) +#ifdef __OpenBSD__ + if (unveil_terminfo_path() < 0) { - errno = dir_access_errno; - goto file_error; + perror(PROGRAM_NAME ": unveil"); + goto main_done; } -#endif /* BACKUP_FAIL_OK */ - if (stat(state.filename, &st) == -1) + if (unveil(NULL, NULL) < 0) { - errno = dir_access_errno; - goto file_error; + perror(PROGRAM_NAME ": unveil"); + goto main_done; } +#endif - if (!S_ISREG(st.st_mode)) + dir_access = access(dir, W_OK | X_OK); + dir_access_errno = errno; + + if (!backup_fail && state.backup_possible) + state.backup_possible = dir_access != -1; + + result = stat(state.filename, &st); + if (result == -1) { - result = print_error(EINVAL, "Is not a regular file"); - goto general_error; + if (dir_access == -1) + { + errno = dir_access_errno; + goto main_done; + } + goto init_termbox; } -#ifdef __OpenBSD__ - if (unveil(NULL, NULL) < 0) + errno = 0; + + if (!S_ISREG(st.st_mode)) { - perror(PROGRAMNAME ": unveil"); - exit(1); + state.error = ERR_NOTREG_FILE; + goto main_done; } -#endif if (access(state.filename, F_OK | R_OK | W_OK) == -1) - goto file_error; + goto main_done; if (state.rfn_len > 0 && access(state.real_filename, F_OK | R_OK | W_OK) == -1) - goto file_error; + goto main_done; + + if (load_file(&state, &lineno, &col)) + goto main_done; - lineno = 1; - col = 1; - result = load_file(&state, &lineno, &col); - switch (result) - { - case LOAD_ERR_CANT_ALLOC: - case LOAD_ERR_P_ALLOC: - (void)print_error(result, "Allocation failed (out of memory?)"); - goto general_error; // TODO: Avoid free() - case LOAD_ERR_CANT_OPEN_FILE: - (void)print_error(result, "Cannot open file `%s'", filename); - goto general_error; - case LOAD_ERR_NOT_PO_FILE: - (void)print_error(result, "Not a .po file `%s'", filename); - goto general_error; - case LOAD_ERR_P_INVAL: - (void)print_error(result, "Invalid value"); - goto general_error; - case LOAD_ERR_P_SYNTAX: - (void)print_error(result, "%s:%ld:%ld: Syntax error", filename, - lineno, col); - goto general_error; - case LOAD_ERR_NONE: - if (*error) - { - result = print_error(1, error); - goto general_error; - } - break; - default: - result = print_error(1, "Unknown error (%d)", result); - goto general_error; - } state.msgid_number = state.msgid_count > 0 ? 1 : 0; state.first_shown_msgid = state.msgid_number; - goto init_termbox; - -file_error: - result = errno; - perror(PROGRAMNAME); - /* FALLTHROUGH */ -general_error: - free(state.real_filename); - free(state.backup_filename); - return result; + goto init_termbox; init_termbox: - tb_init(); - *error = 0; - state.maxx = 0; + tbires = 0; + if ((tbires = tb_init())) + { + saved_tb_error = tb_strerror(tbires); + errno = ERR_TB_ERROR; + goto main_done; + } + + errno = 0; + state.maxx = 0; + state.recalculate_size = 1; while (state.running) { + tb_set_clear_attrs(colors[C_CLEAR].fg, colors[C_CLEAR].bg); tb_clear(); - if (recalculate_size) + if (state.recalculate_size) { - state.maxx = tb_width(); - state.help_width = calculate_help_width(&state); - state.edit_width = calculate_edit_width(&state); - state.search_width = calculate_search_width(&state); - recalculate_size = 0; + recalculate_size(&state); + state.recalculate_size = 0; } - state.maxy = tb_height(); - - if (!draw(&state)) + if (draw(&state)) { - STRLCPY(errors[ERR_CANT_ALLOC], state.error, MAXBUFLINE); - saved_errno = ENOMEM; + state.running = 0; goto main_done; } tb_present(); result = tb_poll_event(&ev); if (result == TB_OK && ev.type == TB_EVENT_RESIZE) - recalculate_size = 1; + state.recalculate_size = 1; else if (result == TB_OK && ev.type == TB_EVENT_KEY) handle_key_event(&ev, &state); } main_done: + saved_errno = errno; + + if (saved_tb_error) + { + tb_shutdown(1); + print_error(1, saved_tb_error); + exit(CUSTOM_ERROR_START + saved_errno); + } + tb_shutdown(saved_errno != ENOMEM); - if (state.error && *state.error) - return print_error(1, state.error); - /*free_drawstate(&state);*/ - return 0; + if (saved_errno) + perror(PROGRAM_NAME); + + if (state.error == ERR_NOTREG_FILE) + print_error(1, errors[state.error], state.filename); + else if (state.error) + print_error(1, errors[state.error]); + + return saved_errno + ? saved_errno + : (state.error ? CUSTOM_ERROR_START + state.error : 0); } diff --git a/termbox.h b/termbox.h @@ -2302,7 +2302,9 @@ static int init_cellbuf(void); static int tb_deinit(int deallocate); static int load_terminfo(void); static int load_terminfo_from_path(const char *path, const char *term); +#ifdef __OpenBSD__ static int unveil_terminfo_path(void); +#endif static int read_terminfo_path(const char *path); static int parse_terminfo_caps(void); static int load_builtin_caps(void); diff --git a/util.c b/util.c @@ -1,7 +1,7 @@ /* This program is licensed under the terms of GNU GPL v3 or (at your option) * any later version. Copyright (C) 2021-2026 Страхиња Радић. * See the file LICENSE for exact copyright and license details. */ - +#include <assert.h> #include <stdarg.h> #include <stddef.h> #include <stdio.h> @@ -12,6 +12,11 @@ #include "po.h" #include "util.h" +/* Whitespace definition for determining word boundaries */ +#define B_WS " \v\t" +/* Full word boundary delimiter definition */ +#define B_FULL " \v\t()[]{},.;:/\\" + enum { UTLERRNONE = 0, UTLERROVFL = 1 }; int utlerrno = UTLERRNONE; @@ -26,14 +31,15 @@ u8_string_to_unicode(uint32_t* us, const char* s, const size_t max) int u8_len = 0; size_t added = 0; - while (s && *s && u8_len != TB_ERR) + assert(s != NULL); + while (*s) { u8_len = tb_utf8_char_to_unicode(&uch, s); if (added + 1 > max) break; - *pus++ = uch; - if (u8_len != TB_ERR) + if (u8_len > 0) { + *pus++ = uch; s += u8_len; added++; } @@ -45,48 +51,42 @@ u8_string_to_unicode(uint32_t* us, const char* s, const size_t max) /* max - max Unicode characters to convert to UTF-8 * Returns: number of processed Unicode characters */ size_t -unicode_string_to_u8(char* s, const size_t s_size, const uint32_t* us, - const size_t max) +unicode_string_to_u8(char* s, const uint32_t* us, const size_t max) { char ch[8]; + char* ps = NULL; const uint32_t* pus = us; size_t len; size_t added = 0; - if (!pus) - return added; - - *s = 0; + assert((s != NULL) && (us != NULL)); + ps = s; while (*pus) { - size_t res; - len = tb_utf8_unicode_to_char(ch, *pus); - ch[len] = 0; - res = strlcat(s, ch, s_size); - if (res >= s_size) - { - utlerrno = UTLERROVFL; + if (added + 1 > max) return added; - } + memccpy(ps, ch, 0, len); + ps += len; added++; pus++; - if (added + 1 > max) - break; } - utlerrno = UTLERRNONE; + *ps = 0; return added; } +// TODO: Check size_t u32_match_msgid_ending(const uint32_t* msgid, uint32_t* msgstr, size_t max) { - uint32_t* buf = calloc(max + 1, sizeof(uint32_t)); - if (!buf) - return 0; + uint32_t* buf = NULL; size_t buf_len = 0; size_t msgid_len = 0; + buf = calloc(max + 1, sizeof(uint32_t)); + if (!buf) + return 0; + buf_len = u32_strncpy(buf, msgstr, (max + 1) * sizeof(uint32_t)); msgid_len = u32_strlen(msgid); @@ -138,6 +138,7 @@ u32_count_chars(const uint32_t* s, const uint32_t ch) { size_t result = 0; const uint32_t* ps = s; + assert(s != NULL); while (*ps) { if (*ps == ch) @@ -154,9 +155,7 @@ u32_count_strings(const uint32_t* haystack, const uint32_t* needle) const uint32_t* phaystack = haystack; size_t needle_len = 0; - if (!haystack || !needle) - return 0; - + assert((haystack != NULL) && (needle != NULL)); needle_len = u32_strlen(needle); if (!needle_len) return 0; @@ -173,13 +172,16 @@ u32_count_strings(const uint32_t* haystack, const uint32_t* needle) size_t u32_encode_tabs(uint32_t* s, size_t max) { - uint32_t* buf = calloc(max + 1, sizeof(uint32_t)); - if (!buf) - return 0; - uint32_t* pbuf = buf; + uint32_t* buf = NULL; + uint32_t* pbuf = NULL; uint32_t* ps = s; size_t newlen = 0; + assert(s != NULL); + buf = calloc(max + 1, sizeof(uint32_t)); + if (!buf) + return 0; + pbuf = buf; while (*ps) { if (*ps == '\t') @@ -204,13 +206,16 @@ u32_encode_tabs(uint32_t* s, size_t max) size_t u32_decode_tabs(uint32_t* s, size_t max) { - uint32_t* buf = calloc(max + 1, sizeof(uint32_t)); - if (!buf) - return 0; - uint32_t* pbuf = buf; + uint32_t* buf = NULL; + uint32_t* pbuf = NULL; uint32_t* ps = s; size_t newlen = 0; + assert(s != NULL); + buf = calloc(max + 1, sizeof(uint32_t)); + if (!buf) + return 0; + pbuf = buf; while (*ps) { if (*ps == '\\' && *(ps + 1) == 't') @@ -232,12 +237,28 @@ u32_decode_tabs(uint32_t* s, size_t max) } size_t +u8_strlen(const char* s) +{ + const char* ps = s; + uint32_t uc; + size_t len; + size_t result = 0; + assert(s != NULL); + while (*ps) + { + len = tb_utf8_char_to_unicode(&uc, ps); + result += len; + ps++; + } + return result; +} + +size_t u32_strlen(const uint32_t* s) { const uint32_t* ps = s; size_t len = 0; - if (!s) - return 0; + assert(s != NULL); while (*ps++) len++; return len; @@ -247,6 +268,7 @@ const uint32_t* u32_strstr(const uint32_t* haystack, const uint32_t* needle) { const uint32_t* ph = haystack; + assert((haystack != NULL) && (needle != NULL)); while (*ph) { if (u32_starts_with(ph, needle)) @@ -321,14 +343,16 @@ u32_u8_strncpy(uint32_t* to, const char* from, size_t max) return len; } +// TODO: Check size_t u8_u32_strncpy(char* to, const uint32_t* from, size_t max) { - char* cfrom = calloc(max + 1, 1); + char* cfrom = NULL; size_t len; + cfrom = calloc(max + 1, 1); if (!cfrom) return 0; - unicode_string_to_u8(cfrom, max + 1, from, max); + unicode_string_to_u8(cfrom, from, max); len = strlen(cfrom); strncpy(to, cfrom, max); if (len && len < max) @@ -422,24 +446,31 @@ u32_next_line(const uint32_t* s, const uint32_t** next_line, return result; } -const int +int is_word_boundary(const char ch, const int strictly_whitespace) { - return strictly_whitespace ? strchr(" \v\t", ch) != NULL - : strchr(" \v\t()[]{},.;:/\\", ch) != NULL; + return strictly_whitespace ? strchr(B_WS, ch) != NULL + : strchr(B_FULL, ch) != NULL; } -const int +int u32_is_word_boundary(const uint32_t ch, const int strictly_whitespace) { - return strictly_whitespace - ? u32_strchr((uint32_t*)L" \v\t", ch) != NULL - : u32_strchr((uint32_t*)L" \v\t()[]{},.;:/\\", ch) != NULL; + static uint32_t uws[sizeof(B_WS) + 1]; + static uint32_t ufull[sizeof(B_FULL) + 1]; + if (!*uws) + { + (void)u8_string_to_unicode(uws, B_WS, sizeof(B_WS)); + (void)u8_string_to_unicode(ufull, B_FULL, sizeof(B_FULL)); + } + return strictly_whitespace ? u32_strchr(uws, ch) != NULL + : u32_strchr(ufull, ch) != NULL; } -const int +int starts_with(const char* s, const char* with) { + assert((s != NULL) && (with != NULL)); while (*s && *s == *with) { if (*s != *with) @@ -450,9 +481,10 @@ starts_with(const char* s, const char* with) return *with == 0; } -const int +int u32_starts_with(const uint32_t* s, const uint32_t* with) { + assert((s != NULL) && (with != NULL)); while (*s && *s == *with) { if (*s != *with) @@ -467,8 +499,7 @@ const char* u8_basename(const char* path) { const char* ppath; - if (!path) - return NULL; + assert(path != NULL); ppath = path + strlen(path); while (ppath != path && *ppath != '/') ppath--; diff --git a/util.h b/util.h @@ -4,11 +4,11 @@ #include <stdint.h> #include <sys/types.h> + #define ABS(a) ((a < 0) ? (-a) : (a)) #define LEN(x) (sizeof(x) / sizeof(x[0])) #define MAX(a, b) ((a > b) ? a : b) #define MIN(a, b) ((a < b) ? a : b) -#define PROGRAMNAME "poe" #define STRLCAT(tail, head, totallen) \ do \ { \ @@ -27,22 +27,112 @@ exit(1); \ } \ } while (0) +#define MEMCCPY(to, from, tolen, temp) \ + do \ + { \ + temp = memccpy(to, from, 0, tolen); \ + if (!temp) \ + to[tolen > 0 ? tolen - 1 : 0] = 0; \ + } while (0) +#define MEMCCPY_EXT(to, fallbackto, from, tolen, temp) \ + do \ + { \ + temp = memccpy(to, from, 0, tolen); \ + if (!temp) \ + fallbackto[tolen > 0 ? tolen - 1 : 0] = 0; \ + } while (0) +#define U32_MEMCCPY(to, from, tolen, temp) \ + do \ + { \ + temp = u32_memccpy(to, from, 0L, tolen); \ + if (!temp) \ + to[tolen > 0 ? tolen - 1 : 0] = 0; \ + } while (0) +#define U32_U8_MEMCCPY(to, from, tolen, temp) \ + do \ + { \ + temp = u32_u8_memccpy(to, from, 0L, tolen); \ + if (!temp) \ + to[tolen > 0 ? tolen - 1 : 0] = 0; \ + } while (0) +#define U32_MEMCCPY_EXT(to, fallbackto, from, tolen, fbtolen, temp) \ + do \ + { \ + temp = u32_memccpy(to, from, 0L, tolen); \ + if (!temp) \ + fallbackto[fbtolen > 0 ? fbtolen - 1 : 0] = 0; \ + } while (0) + +#define ENSURE_SIZE(to, temp, tosize, testsize, fromsize, label, type) \ + do \ + { \ + if (!(to) || (tosize) < (testsize)) \ + { \ + temp = realloc(to, (fromsize) * sizeof(type)); \ + if (!temp) \ + { \ + state->running = 0; \ + goto label; \ + } \ + tosize = fromsize; \ + to = temp; \ + } \ + } while (0) +/* + * - Make sure the size of the destination string is large enough to copy, and + * copy + * - Sizes are in bytes + * - We explicitly use fromsize in MEMCCPY, because we ensured that tosize is + * at least as big, and to allow a subset of from to be copied + */ +#define U8_SAFE_COPY(to, temp, tosize, from, fromsize, label) \ + do \ + { \ + ENSURE_SIZE(to, temp, tosize, fromsize, fromsize, label, char); \ + MEMCCPY(to, from, fromsize, temp); \ + } while (0) +/* + * - Make sure the size of the destination string is large enough to copy, and + * copy + * - Sizes are in uint32_t units + * - We explicitly use fromsize in U32_MEMCCPY, because we ensured that tosize + * is at least as big, and to allow a subset of from to be copied + */ +#define U32_SAFE_COPY(to, temp, tosize, from, fromsize, label) \ + do \ + { \ + ENSURE_SIZE(to, temp, tosize, fromsize, fromsize, label, \ + uint32_t); \ + U32_MEMCCPY(to, from, fromsize, temp); \ + } while (0) +#define UNUSED(x) ((void)(x)) + +#define CUSTOM_ERROR_START 200 enum { + ERR_NONE, + ERR_NO_CL_ARG, + ERR_NOTPO_FILE, + ERR_NOTREG_FILE, + ERR_TB_ERROR, ERR_UNKNOWN_KEY, ERR_UNKNOWN_KEY_HELP, ERR_EXIT_KEY, - ERR_DLG_OPEN_QUIT, ERR_DLG_OPEN, + ERR_DLG_OPEN_QUIT, ERR_DLG_FOCUS_EDIT, ERR_EMPTY_FILE, ERR_CANT_ALLOC, ERR_CANT_SAVE, ERR_CANT_MOVE, ERR_ILLEGAL_ON_FIRST, - ERR_NO_PLURAL_FORMS + ERR_SYNTAX, + ERR_NO_PLURAL_FORMS, + ERR_ERRNO_SET }; +enum { ACT_IDLE, ACT_WRITING, ACT_READING }; + #define MAXBUFLINE 4096 #define MAXCOPYBUF 8192 #define MAXDATEBUF 80 @@ -58,14 +148,14 @@ extern int utlerrno; size_t strlcpy(char* dst, const char* src, size_t dsize); size_t strlcat(char* dst, const char* src, size_t dsize); size_t u8_string_to_unicode(uint32_t* us, const char* s, const size_t max); -size_t unicode_string_to_u8(char* s, const size_t s_size, const uint32_t* us, - const size_t max); +size_t unicode_string_to_u8(char* s, const uint32_t* us, const size_t max); size_t u32_match_msgid_ending(const uint32_t* msgid, uint32_t* msgstr, size_t max); size_t u32_count_chars(const uint32_t* s, const uint32_t ch); size_t u32_count_strings(const uint32_t* haystack, const uint32_t* needle); size_t u32_encode_tabs(uint32_t* s, size_t max); size_t u32_decode_tabs(uint32_t* s, size_t max); +size_t u8_strlen(const char* s); size_t u32_strlen(const uint32_t* s); const uint32_t* u32_strstr(const uint32_t* haystack, const uint32_t* needle); const uint32_t* u32_strchr(const uint32_t* haystack, const uint32_t needle); @@ -77,9 +167,9 @@ size_t u32_lines_in_string(const uint32_t* s, const int include_eol, const size_t wrap); size_t u32_next_line(const uint32_t* s, const uint32_t** sptr, const int include_eol, const size_t wrap); -const int is_word_boundary(const char ch, const int strictly_whitespace); -const int u32_is_word_boundary(const uint32_t ch, const int strictly_whitespace); -const int starts_with(const char* s, const char* with); -const int u32_starts_with(const uint32_t* s, const uint32_t* with); +int is_word_boundary(const char ch, const int strictly_whitespace); +int u32_is_word_boundary(const uint32_t ch, const int strictly_whitespace); +int starts_with(const char* s, const char* with); +int u32_starts_with(const uint32_t* s, const uint32_t* with); const char* u8_basename(const char* path); /*void poe_log(const char* msg, ...);*/ diff --git a/version.h.in b/version.h.in @@ -2,5 +2,6 @@ * any later version. Copyright (C) 2021-2026 Страхиња Радић. * See the file LICENSE for exact copyright and license details. */ -#define VERSION "%VERSION%" -#define DATE "%DATE%" +#define PROGRAM_NAME "poe" +#define VERSION "%VERSION%" +#define DATE "%DATE%"