poe

Уређивач .po фајлова
Дневник | Датотеке | Референце | ПРОЧИТАЈМЕ | ЛИЦЕНЦА

чување 552d2f3660382506a30f8fc54f235ef52b280117
родитељ e47787e7b26c8f65c6cd42d1156367ccaca49cb5
Аутор: Страхиња Радић <contact@strahinja.org>
Датум:   Sun, 27 Jun 2021 19:21:32 +0200

Merge branch 'wip-editing'

Signed-off-by: Страхиња Радић <contact@strahinja.org>

Diffstat:
M.gitignore | 1+
MREADME | 2+-
MTODO | 4++--
Mconfig.def.h | 4+++-
Mconfig.h | 4+++-
Mdefault.h.do | 10+++++-----
Adefault.pdf.do | 3+++
Mdraw.c | 316+++++++++++++++++++++++++++++++++++++++++++++++++++----------------------------
Mdraw.h | 54+++++++++++++++++++++++++++++++++++++++++-------------
Mpo.c | 74++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mpo.h | 11+++++++++--
Apoe-test.sh | 7+++++++
Mpoe.1.in | 46+++++++++++++++++++++++++++++++++++++++++++---
Mpoe.c | 620++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------
Mutil.c | 241++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Mutil.h | 23+++++++++++++++++++++--
измењених датотека: 16, додавања: 1227(+), брисања: 193(-)

diff --git a/.gitignore b/.gitignore @@ -15,6 +15,7 @@ nohup.out *.xz *.1 poe +poe.1 date version version.h diff --git a/README b/README @@ -3,7 +3,7 @@ poe - .po file editor Q: Why? A: - There should be no need for GNOME to edit .po files. - - Some folks prefer CLI to GUI. + - Some folks prefer terminal to GUI. - No, plugin for vi(m) isn't enough. Q: This doesn't do anything! diff --git a/TODO b/TODO @@ -3,9 +3,9 @@ [x] Flags display in list - [~] Line editing with cursor + [x] Line editing with cursor [x] Horizontal movement and editing in a single line - [~] Multiline editing + [x] Multiline editing [x] Data structures and parsing from string [x] Save to string, dynamic entry allocation [x] Horizontal movement and editing in multiline diff --git a/config.def.h b/config.def.h @@ -21,6 +21,8 @@ #define LIST_MSGSTR_BG TB_DEFAULT #define LIST_MSGSTR_SEL_FG TB_WHITE | TB_BOLD #define LIST_MSGSTR_SEL_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 @@ -38,7 +40,7 @@ void format_msgs(char* result, size_t result_size, const char* format, const struct DrawState* state); static const struct StatusSegment status_segments[] = { - { .format = "%s", .callback = format_filename, + { .format = "%s%s", .callback = format_filename, .fg = TB_BLACK, .bg = TB_WHITE, .alignment = LEFT }, { .format = "%ld/%ld (%ld fuzzy)", .callback = format_msgs, diff --git a/config.h b/config.h @@ -21,6 +21,8 @@ #define LIST_MSGSTR_BG TB_DEFAULT #define LIST_MSGSTR_SEL_FG TB_WHITE | TB_BOLD #define LIST_MSGSTR_SEL_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 @@ -38,7 +40,7 @@ void format_msgs(char* result, size_t result_size, const char* format, const struct DrawState* state); static const struct StatusSegment status_segments[] = { - { .format = "%s", .callback = format_filename, + { .format = "%s%s", .callback = format_filename, .fg = TB_BLACK, .bg = TB_WHITE, .alignment = LEFT }, { .format = "%ld/%ld (%ld fuzzy)", .callback = format_msgs, diff --git a/default.h.do b/default.h.do @@ -1,9 +1,9 @@ if [ -r $1.in ]; then - redo-ifchange $1.in version date - read VERSION <version - read DATE <date - sed -e "s/%VERSION%/$VERSION/g" \ - -e "s/%DATE%/$DATE/g" <$1.in + redo-ifchange $1.in version date + read VERSION <version + read DATE <date + sed -e "s/%VERSION%/$VERSION/g" \ + -e "s/%DATE%/$DATE/g" <$1.in else if [ -f $2.def.h ]; then redo-ifchange $2.def.h diff --git a/default.pdf.do b/default.pdf.do @@ -0,0 +1,3 @@ +redo-ifchange $2.1 +groff -mandoc -t -T pdf $2.1 >$3 + diff --git a/draw.c b/draw.c @@ -1,5 +1,4 @@ /* See the file LICENSE for copyright and license details. */ -#include <bits/stdint-uintn.h> #include <stdlib.h> #include <stdio.h> #include <string.h> @@ -9,16 +8,17 @@ #include "po.h" #include "draw.h" #include "config.h" +#include "version.h" static const char* help_title = "poe - .po File Editor"; static const char* help[] = { - "ESC - Close dialog, clear error", - "ENTER - Edit entry", + "Esc - Close dialog, clear error", + "Enter - Edit entry", "H or F1 - Show this screen", - "g or Home - First entry", - "k or \u2191 - Previous entry", - "j or \u2193 - Next entry", - "G or End - Last entry", + "g or Home - Go to first entry", + "k or \u2191 - Go to previous entry", + "j or \u2193 - Go to next entry", + "G or End - Go to last entry", "f or C-F - Toggle fuzzy flag", "q or C-Q - Quit" }; @@ -28,7 +28,8 @@ const char* errors[] = { [ERR_EXIT_KEY] = "Press q or C-Q to quit", [ERR_DLG_OPEN] = "Press Esc to close dialog or q/C-Q to quit", [ERR_DLG_OPEN_QUIT] = "Press Esc to close dialog", - [ERR_EMPTY_FILE] = "No msgids to edit" + [ERR_EMPTY_FILE] = "No msgids to edit", + [ERR_CANT_ALLOC] = "Memory allocation failed - exit advised" }; const char* flag_strings[] = { [FL_C_FORMAT] = "c-format", @@ -38,6 +39,28 @@ const char* empty_file_text = "*** Empty file ***"; const char* program_name = "poe"; const char* prompt_dirty = "File changed, save [y/n/ESC]?"; +void +init_bufferline(struct BufferLine* bl) +{ + if (!bl) + return; + bl->text = NULL; + bl->length = 0; +} + +void +free_bufferline(struct BufferLine* bl) +{ + if (!bl) + return; + if (bl->text) + { + free(bl->text); + bl->text = NULL; + } + bl->length = 0; +} + void draw_rect(const int startx, const int endx, const int starty, const int endy, const uint16_t fg, const uint16_t bg) @@ -53,45 +76,59 @@ draw_box(const int startx, const int endx, const int starty, const int endy, { int x, y; - tb_change_cell(startx+1, starty, BORDER_NW, fg, bg); - for (x = startx+2; x < endx-2; x++) + 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-2, starty, BORDER_NE, fg, bg); + tb_change_cell(endx-1, starty, BORDER_NE, fg, bg); for (y = starty+1; y < endy-2; y++) { - tb_change_cell(startx+1, y, BORDER_VER, fg, bg); - for (x = startx+2; x < endx-2; x++) + 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-2, y, BORDER_VER, fg, bg); - tb_change_cell(endx-1, y, ' ', SHADOW_FG, SHADOW_BG); + tb_change_cell(endx-1, y, BORDER_VER, fg, bg); + tb_change_cell(endx, y, ' ', SHADOW_FG, SHADOW_BG); } - tb_change_cell(startx+1, endy-2, BORDER_SW, fg, bg); - for (x = startx+2; x < endx-2; x++) + 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-2, endy-2, BORDER_SE, fg, bg); - tb_change_cell(endx-1, endy-2, ' ', SHADOW_FG, SHADOW_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+2; x < endx; x++) + for (x = startx+1; x < endx+1; x++) tb_change_cell(x, endy-1, ' ', SHADOW_FG, SHADOW_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) +{ + uint32_t* us = calloc(strlen(s)+1, sizeof(uint32_t)); + if (!us) + return NULL; + u8_string_to_unicode(us, s); + u32_draw_string(x, y, fg, bg, us, max_cols, fill_cols, + padding_left, padding_right, align); + free(us); + return s; +} + /* max_cols - pack everything into this many columns * fill_cols - this many columns from the left/right/center will be filled * padding_* - this many cells will be skipped from * side */ void -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, +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) { int col = 0; - uint32_t* us = calloc(strlen(s)+1, sizeof(uint32_t)); - uint32_t* pus = NULL; - size_t us_len = 0; - us_len = u8_string_to_unicode(us, s); - pus = us; + const uint32_t* ps = NULL; + size_t len = u32_strlen(s); + ps = s; if (align == LEFT) { @@ -103,7 +140,7 @@ draw_string(const int x, const int y, const uint16_t fg, const uint16_t bg, { for (col = max_cols-fill_cols-1; col < max_cols; col++) tb_change_cell(x+col, y, ' ', fg, bg); - col = max_cols-us_len-padding_right-1; + col = max_cols-len-padding_right-1; } else if (align == CENTER) { @@ -113,35 +150,44 @@ draw_string(const int x, const int y, const uint16_t fg, const uint16_t bg, if (s) { - if (us_len > max_cols) + if (len > max_cols) { - pus += (us_len-max_cols)/2; + ps += (len-max_cols)/2; col = 0; } else - col = (max_cols-us_len)/2-padding_left; + col = (max_cols-len)/2-padding_left; } } - while (*pus && col < max_cols-padding_right) + while (*ps && col < max_cols-padding_right) { - tb_change_cell(x+col+padding_left, y, *pus, fg, bg); - pus++; - col++; + if (*ps == '\t') + { + for (int i = 0; i < TAB_SIZE - (col % TAB_SIZE); i++) + tb_change_cell(x+col+i+padding_left, y, ' ', + fg, bg); + col += TAB_SIZE - (col % TAB_SIZE); + ps++; + } + else + { + tb_change_cell(x+col+padding_left, y, *ps, fg, bg); + ps++; + col++; + } } - if (us) - free(us); } -void +const struct DrawState* draw_help(const struct DrawState* state) { if (!state) - return; + return NULL; int startx, endx, starty, endy; - int w = 50; - int h = 20; + int w = HELP_WIDTH; + int h = HELP_HEIGHT; int row = 0; int maxx = state->maxx; int maxy = state->maxy; @@ -153,37 +199,34 @@ draw_help(const struct DrawState* state) endy = h > maxy-1 ? maxy-2 : maxy-starty; draw_box(startx, endx, starty, endy, DLG_FG, DLG_BG); - draw_string(startx+1, starty, TITLE_FG, TITLE_BG, - help_title, w-2, strlen(help_title)+2, 1, 1, CENTER); + if (!draw_string(startx, starty, TITLE_FG, TITLE_BG, + help_title, w-2, strlen(help_title)+2, 1, 1, CENTER)) + return NULL; while (row < LEN(help)) { - draw_string(startx+2, starty+3+row, DLG_FG, DLG_BG, - help[row], w-4, w-4, 2, 2, LEFT); + if (!draw_string(startx+1, starty+3+row, DLG_FG, DLG_BG, + help[row], w-2, w-2, 2, 2, LEFT)) + return NULL; row++; } + return state; } -void +const struct DrawState* draw_editbox(const struct DrawState* state) { if (!state) - return; + return NULL; int startx, endx, starty, endy; - /* TODO: Ruler? At least output (X,Y) */ - int w = 70; - int h = 20; + int w = EDIT_WIDTH; + int h = EDIT_HEIGHT; int maxx = state->maxx; int maxy = state->maxy; - char buf[MSGSTR_MAX]; + //uint32_t buf[MSGSTR_MAX]; char flags_buf[MAXFLAGSBUF+4]; struct PoEntry* entry = &state->entries[state->msgid_number-1]; - if (entry->msgstr) - strncpy(buf, entry->msgstr, MSGSTR_MAX-1); - else - buf[0] = 0; - strcpy(flags_buf, "[ "); format_flags(flags_buf+2, MAXFLAGSBUF, entry); strcpy(flags_buf+2+MAXFLAGSBUF-1, " ]"); @@ -195,67 +238,105 @@ draw_editbox(const struct DrawState* state) endy = h > maxy-1 ? maxy-2 : maxy-starty; draw_box(startx, endx, starty, endy, DLG_FG, DLG_BG); - draw_string(startx+1, starty, TITLE_FG, TITLE_BG, editbox_title, w-2, - strlen(editbox_title)+2, 1, 1, CENTER); - - draw_rect(startx+2, endx-2, starty+1, endy-11, EDIT_MSGID_FG, - EDIT_MSGID_BG); - draw_string(startx+2, starty+1, EDIT_MSGID_FG, EDIT_MSGID_BG, + if (!draw_string(startx+1, starty, TITLE_FG, TITLE_BG, + editbox_title, w-2, + strlen(editbox_title)+2, 1, 1, CENTER)) + return NULL; + + draw_rect(startx+1, endx-1, starty+1, endy-h/2-1, + EDIT_MSGID_FG, EDIT_MSGID_BG); + u32_draw_string(startx+1, starty+1, EDIT_MSGID_FG, EDIT_MSGID_BG, state->entries[state->msgid_number-1].msgid, w-2, - strlen(state->entries[state->msgid_number-1].msgid)+2, - 1, 1, LEFT); + w-2, 1, 1, LEFT); + + draw_rect(startx+1, endx-1, endy-h/2-1, endy-2, + EDIT_MSGSTR_FG, EDIT_MSGSTR_BG); - draw_rect(startx+2, endx-2, endy-11, endy-2, EDIT_MSGSTR_FG, - EDIT_MSGSTR_BG); - draw_string(startx+2, endy-11, EDIT_MSGSTR_FG, EDIT_MSGSTR_BG, - buf, w-2, strlen(buf)+2, 1, 1, LEFT); + 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; + if (curline->length > state->input_first_shown_column) + u32_draw_string(startx+1, endy-h/2-1+i, + EDIT_MSGSTR_FG, EDIT_MSGSTR_BG, curline->text + + state->input_first_shown_column, + w-3, w-3, 1, 1, LEFT); + } + 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]; + sprintf(sbuf, "%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, w-2, w-2, 1, 1, LEFT); if (entry->flags != FL_NONE) - draw_string(endx-MAXFLAGSBUF-4-5, endy-2, DLG_FG, DLG_BG, - flags_buf, w-2, strlen(flags_buf), 0, 0, LEFT); + if (!draw_string(endx-MAXFLAGSBUF-4-5, endy-2, DLG_FG, DLG_BG, + flags_buf, MAXFLAGSBUF+4-1, MAXFLAGSBUF+4-1, 0, + 0, LEFT)) + return NULL; + return state; } -void +const struct DrawState* draw_status(const struct DrawState* state) { if (!state) - return; + return NULL; - char buf[MAXBUF]; + 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]) - draw_string(0, state->maxy-1, PROMPT_FG, PROMPT_BG, + { + if (!draw_string(0, state->maxy-1, PROMPT_FG, PROMPT_BG, state->prompt, state->maxx, state->maxx, 1, 1, - CENTER); + CENTER)) + return NULL; + } else if (state->error && state->error[0]) - draw_string(0, state->maxy-1, ERROR_FG, ERROR_BG, state->error, - state->maxx, state->maxx, 1, 1, CENTER); + { + if (!draw_string(0, state->maxy-1, ERROR_FG, ERROR_BG, state->error, + state->maxx, state->maxx, 1, 1, CENTER)) + return NULL; + } else while (pseg < status_segments + LEN(status_segments)) { buf[0] = 0; if (pseg->callback) - pseg->callback(buf, MAXBUF, pseg->format, + pseg->callback(buf, MAXBUFLINE, pseg->format, state); else - strncpy(buf, pseg->format, MAXBUF); - buf[MAXBUF-1] = 0; - draw_string(current_start, state->maxy-1, pseg->fg, pseg->bg, - buf, seg_size, seg_size, 1, 1, - pseg->alignment); + strncpy(buf, pseg->format, MAXBUFLINE); + buf[MAXBUFLINE-1] = 0; + if (!draw_string(current_start, state->maxy-1, pseg->fg, + pseg->bg, buf, seg_size, seg_size, 1, 1, + pseg->alignment)) + return NULL; pseg++; current_start += seg_size; } + return state; } -void +const struct DrawState* draw_list_visible(const struct PoEntry* start, const struct DrawState* state) { if (!state) - return; + return NULL; const struct PoEntry* current = start; int max_screen_entries = state->maxy-2; @@ -264,10 +345,11 @@ draw_list_visible(const struct PoEntry* start, const struct DrawState* state) if (!start) { - draw_string(0, cy, LIST_MSGID_FG, LIST_MSGID_BG, + if (!draw_string(0, cy, LIST_MSGID_FG, LIST_MSGID_BG, empty_file_text, state->maxx, state->maxx, - 2, 2, CENTER); - return; + 2, 2, CENTER)) + return NULL; + return state; } while (i <= state->msgid_count && cy < max_screen_entries) @@ -275,53 +357,65 @@ draw_list_visible(const struct PoEntry* start, const struct DrawState* state) int sel = i == state->msgid_number; char flags_buf[MAXFLAGSBUF]; + if (sel) + draw_rect(0, state->maxx-1, cy, cy+1, LIST_SEL_FG, + LIST_SEL_BG); + format_flags(flags_buf, MAXFLAGSBUF, current); - draw_string(0, cy, sel ? LIST_FLAGS_SEL_FG : LIST_FLAGS_FG, + if (!draw_string(0, cy, sel ? LIST_FLAGS_SEL_FG : LIST_FLAGS_FG, sel ? LIST_FLAGS_SEL_BG : LIST_FLAGS_BG, flags_buf, MAXFLAGSBUF-1+2, MAXFLAGSBUF-1+2, - 2, 0, LEFT); - draw_string(MAXFLAGSBUF-1+2, cy, sel ? LIST_MSGID_SEL_FG : + 2, 0, LEFT)) + return NULL; + u32_draw_string(MAXFLAGSBUF-1+2, cy, sel ? LIST_MSGID_SEL_FG : LIST_MSGID_FG, sel ? LIST_MSGID_SEL_BG : LIST_MSGID_BG, current->msgid, state->maxx/2-MAXFLAGSBUF+1-2, state->maxx/2-MAXFLAGSBUF+1-2, 2, 0, LEFT); - draw_string(state->maxx/2, cy, - sel ? LIST_MSGSTR_SEL_FG : LIST_MSGSTR_FG, - sel ? LIST_MSGSTR_SEL_BG : LIST_MSGSTR_BG, - current->msgstr - ? current->msgstr : "", - state->maxx/2, state->maxx/2, 2, 2, LEFT); + if (current->msgstr && current->msgstr[0]) + u32_draw_string(state->maxx/2, cy, + sel ? LIST_MSGSTR_SEL_FG + : LIST_MSGSTR_FG, + sel ? LIST_MSGSTR_SEL_BG + : LIST_MSGSTR_BG, + current->msgstr, + state->maxx/2, state->maxx/2, 2, 2, + LEFT); current++; i++; cy++; } + return state; } -void +const struct DrawState* draw(const struct DrawState* state) { if (!state) - return; + return NULL; if (state->first_shown_msgid > 0) - draw_list_visible(&state->entries[state->first_shown_msgid-1], state); + { + if (!draw_list_visible(&state->entries[ + state->first_shown_msgid-1], state)) + return NULL; + } else - draw_list_visible(NULL, state); - /* - *if (state->msgid_number > 0 - * && state->msgid_number < state->msgid_count+1) - * draw_entry(&state->entries[state->msgid_number-1], state); - *else - * draw_entry(NULL, state); - */ + if (!draw_list_visible(NULL, state)) + return NULL; if (state->show_help) - draw_help(state); + if (!draw_help(state)) + return NULL; if (state->show_edit) - draw_editbox(state); + if (!draw_editbox(state)) + return NULL; + + if (!draw_status(state)) + return NULL; - draw_status(state); + return state; } diff --git a/draw.h b/draw.h @@ -1,11 +1,20 @@ /* See the file LICENSE for copyright and license details. */ +#include <stddef.h> + #include "termbox.h" -#define BORDER_NW ((uint32_t)L'\u250c') -#define BORDER_NE ((uint32_t)L'\u2510') -#define BORDER_SW ((uint32_t)L'\u2514') -#define BORDER_SE ((uint32_t)L'\u2518') -#define BORDER_VER ((uint32_t)L'\u2502') -#define BORDER_HOR ((uint32_t)L'\u2500') + +#define BORDER_NW ((uint32_t)L'\u250c') +#define BORDER_NE ((uint32_t)L'\u2510') +#define BORDER_SW ((uint32_t)L'\u2514') +#define BORDER_SE ((uint32_t)L'\u2518') +#define BORDER_VER ((uint32_t)L'\u2502') +#define BORDER_HOR ((uint32_t)L'\u2500') + +#define EDIT_WIDTH 70 +#define EDIT_HEIGHT 20 +#define HELP_WIDTH 50 +#define HELP_HEIGHT 20 +#define TAB_SIZE 8 typedef enum { LEFT, @@ -13,10 +22,23 @@ typedef enum { RIGHT } Alignment; +struct BufferLine { + uint32_t* text; + size_t length; +}; + struct DrawState { char* error; char* prompt; char* filename; + struct BufferLine* input_buffer; + int input_column; + int input_row; + int input_first_shown_column; + int input_first_shown_row; + int input_rows_count; + int input_display_column; + int saved_column; struct PoEntry* entries; void (*prompt_callback)(struct DrawState*, struct tb_event*); int running; @@ -41,18 +63,24 @@ struct StatusSegment { extern struct PoEntry entries[]; +void init_bufferline(struct BufferLine* bl); +void free_bufferline(struct BufferLine* bl); void draw_rect(const int startx, const int endx, const int starty, const int endy, const uint16_t fg, const uint16_t bg); void draw_box(const int startx, const int endx, const int starty, const int endy, const uint16_t fg, const uint16_t bg); -void draw_string(const int x, const int y, const uint16_t fg, const uint16_t bg, - const char* s, const int max_cols, const int fill_cols, +const char* 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); +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); -void draw_help(const struct DrawState* state); -void draw_editbox(const struct DrawState* state); -void draw_status(const struct DrawState* state); -void draw_list_visible(const struct PoEntry* start, +const struct DrawState* draw_help(const struct DrawState* state); +const struct DrawState* draw_editbox(const struct DrawState* state); +const struct DrawState* draw_status(const struct DrawState* state); +const struct DrawState* draw_list_visible(const struct PoEntry* start, const struct DrawState* state); -void draw(const struct DrawState* state); +const struct DrawState* draw(const struct DrawState* state); diff --git a/po.c b/po.c @@ -1,6 +1,7 @@ #include <stdio.h> #include "po.h" +#include "util.h" char* format_flags(char* buffer, const size_t max, const struct PoEntry* entry) @@ -11,3 +12,76 @@ format_flags(char* buffer, const size_t max, const struct PoEntry* entry) return buffer; } +struct PoEntry* +set_flags(struct PoEntry* entry, const Flags flags) +{ + if (!entry) + return NULL; + entry->flags = flags; + return entry; +} + +struct PoEntry* +set_msgid(struct PoEntry* entry, const char* msgid) +{ + uint32_t umsgid[MAXMSG]; + if (!entry) + return NULL; + u8_string_to_unicode(umsgid, msgid); + if (!entry->msgid) + { + entry->msgid = calloc(MAXMSG, sizeof(uint32_t)); + if (!entry->msgid) + return NULL; + } + u32_strncpy(entry->msgid, umsgid, MAXMSG); + return entry; +} + +struct PoEntry* +set_msgstr(struct PoEntry* entry, const char* msgstr) +{ + uint32_t umsgstr[MAXMSG]; + if (!entry) + return NULL; + u8_string_to_unicode(umsgstr, msgstr); + if (!entry->msgstr) + { + entry->msgstr = calloc(MAXMSG, sizeof(uint32_t)); + if (!entry->msgstr) + return NULL; + } + u32_strncpy(entry->msgstr, umsgstr, MAXMSG); + return entry; +} + +struct PoEntry* +u32_set_msgid(struct PoEntry* entry, const uint32_t* msgid) +{ + if (!entry) + return NULL; + if (!entry->msgid) + { + entry->msgid = calloc(MAXMSG, sizeof(uint32_t)); + if (!entry->msgid) + return NULL; + } + u32_strncpy(entry->msgid, msgid, MAXMSG); + return entry; +} + +struct PoEntry* +u32_set_msgstr(struct PoEntry* entry, const uint32_t* msgstr) +{ + if (!entry) + return NULL; + if (!entry->msgstr) + { + entry->msgstr = calloc(MAXMSG, sizeof(uint32_t)); + if (!entry->msgstr) + return NULL; + } + u32_strncpy(entry->msgstr, msgstr, MAXMSG); + return entry; +} + diff --git a/po.h b/po.h @@ -1,5 +1,7 @@ /* See the file LICENSE for copyright and license details. */ +#include <stdint.h> +#include <stdlib.h> #define MSGSTR_MAX 4096 typedef enum { @@ -10,9 +12,14 @@ typedef enum { struct PoEntry { Flags flags; - char* msgid; - char* msgstr; + uint32_t* msgid; + uint32_t* msgstr; }; char* format_flags(char* buffer, const size_t max, const struct PoEntry* entry); +struct PoEntry* set_flags(struct PoEntry* entry, const Flags flags); +struct PoEntry* set_msgid(struct PoEntry* entry, const char* msgid); +struct PoEntry* set_msgstr(struct PoEntry* entry, const char* msgstr); +struct PoEntry* u32_set_msgid(struct PoEntry* entry, const uint32_t* msgid); +struct PoEntry* u32_set_msgstr(struct PoEntry* entry, const uint32_t* msgstr); diff --git a/poe-test.sh b/poe-test.sh @@ -0,0 +1,7 @@ +#!/bin/sh + +#tmux new-session -d -s poe-test +tmux send-keys -t poe-test Enter End +tmux send-keys -t poe-test -N 30 asdf +tmux send-keys -t poe-test Home End + diff --git a/poe.1.in b/poe.1.in @@ -1,5 +1,5 @@ -'\" -.\" +'\" t +.\" Manpage for poe(1) . .mso an-ext.tmac . @@ -32,6 +32,47 @@ in the poe repository for details. .B poe is a .po (GNU gettext) file editor with a terminal UI. . +.SH "KEY BINDINGS" +. +.LP +Main window key bindings are as follows: +. +.TS +box tab(@); +l l. +\fBKey\fR@\fBAction\fR +_ +\fCEscape\fR@Close dialog or clear error +\fCEnter\fR@Edit entry +\fCH\fR or \fCF1\fR@Show help dialog +\fCg\fR or \fCHome\fR@First entry +\fCk\fR or \fC\[ua]\fR@Previous entry +\fCj\fR or \fC\[da]\fR@Next entry +\fCG\fR or \fCEnd\fR@Last entry +\fCf\fR or \fCC-F\fR@Toggle fuzzy flag +\fCq\fR or \fCC-Q\fR@Quit +.TE +. +.LP +Edit dialog key bindings are as follows: +. +.TS +box tab(@); +l l. +\fBKey\fR@\fBAction\fR +_ +\fCC-S\fR@Update entry and close dialog +\fCEscape\fR@Cancel changes and close dialog +\fCC-F\fR@Toggle fuzzy flag +\fCC-A\fR or \fCHome\fR@Go to start of line +\fCC-E\fR or \fCEnd\fR@Go to end of line +\fCC-U\fR@Erase to start of line +\fC\[ua]\fR@Go to previous line +\fC\[<-]\fR@Go to previous character +\fC\[->]\fR@Go to next character +\fC\[da]\fR@Go to next line +.TE +. .SH "KNOWN LIMITATIONS" .IP \(bu 4 This project is currently (Jun 2021) still a work in progress. @@ -46,6 +87,5 @@ runtime. This is a design decision. Bugs can be reported using the ticket tracker at: .UR https://\:sr.ht/\:~strahinja/\:poe/\:trackers .UE - .\" vim: set filetype=groff: diff --git a/poe.c b/poe.c @@ -2,14 +2,12 @@ #define _POSIX_C_SOURCE 200809L -#include <bits/stdint-uintn.h> #include <errno.h> #include <stdarg.h> #include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <string.h> -#include <sys/stat.h> #include <time.h> #include "version.h" @@ -26,7 +24,7 @@ extern const char* program_name; int print_error(const int code, const char* msg, ...) { - char buf[MAXBUF]; + char buf[MAXBUFLINE]; va_list args; va_start(args, msg); vsnprintf(buf, sizeof(buf), msg, args); @@ -57,7 +55,8 @@ format_filename(char* result, size_t result_size, const char* format, if (!result) return; - snprintf(result, result_size, format, state->filename); + snprintf(result, result_size, format, state->filename, + state->dirty ? "*" : ""); } void @@ -92,13 +91,506 @@ quit_callback(struct DrawState* state, struct tb_event* ev) } } +void move_up(struct DrawState* state); +void move_down(struct DrawState* state); +void move_start(struct DrawState* state); +void move_left(struct DrawState* state); +void move_right(struct DrawState* state); +void move_end(struct DrawState* state); +size_t display_length(const struct DrawState* state, const size_t max_index); +size_t input_length(const struct DrawState* state, const size_t max_index); +void insert_char(struct DrawState* state, uint32_t ch); +struct DrawState* insert_line(struct DrawState* state); +struct DrawState* join_lines(struct DrawState* state, size_t first_line); + +struct BufferLine* +load_msgstr(struct DrawState* state) +{ + + size_t num_lines = u32_lines_in_string(state->entries[ + state->msgid_number-1].msgstr); + struct BufferLine* newbuf = NULL; + struct BufferLine* pnewbuf = NULL; + + newbuf = calloc(num_lines, sizeof(struct BufferLine)); + if (!newbuf) + return NULL; + + pnewbuf = newbuf; + for (size_t i = 0; i < num_lines; i++) + { + init_bufferline(pnewbuf); + uint32_t* newtext = calloc(MAXBUFLINE, sizeof(uint32_t)); + if (!newtext) + return NULL; + pnewbuf->text = newtext; + pnewbuf++; + } + + state->input_rows_count = num_lines; + + uint32_t* pumsgstr = state->entries[state->msgid_number-1].msgstr; + if (pumsgstr) + { + pnewbuf = newbuf; + for (size_t i = 0; i < num_lines; i++) + { + size_t len = u32_line_length(pumsgstr); + size_t newlen = 0; + u32_strncpy(pnewbuf->text, pumsgstr, MIN(len+1, + MAXBUFLINE)); + newlen = u32_decode_tabs(pnewbuf->text, MAXBUFLINE); + pnewbuf->length = newlen; + pumsgstr += len+(pumsgstr[len] ? 2 : 0); + pnewbuf++; + } + } + else + newbuf[0].length = 0; + + state->input_buffer = newbuf; + return state->input_buffer; +} + +void +save_msgstr(struct DrawState* state) +{ + uint32_t to_save[MAXMSG]; + if (state->input_buffer) + { + struct BufferLine* pinput_buffer = NULL; + for (size_t i = 0; i < state->input_rows_count; i++) + { + pinput_buffer = state->input_buffer+i; + if (i == 0) + u32_strncpy(to_save, pinput_buffer->text, + MAXMSG); + else + { + u32_strncat(to_save, (uint32_t*)L"\\n", MAXMSG); + u32_strncat(to_save, pinput_buffer->text, + MAXMSG); + } + free_bufferline(pinput_buffer); + } + } + free(state->input_buffer); + state->input_buffer = NULL; + u32_encode_tabs(to_save, MAXBUFLINE); + u32_set_msgstr(&state->entries[state->msgid_number-1], to_save); + state->dirty = 1; +} + +void +cancel_msgstr(struct DrawState* state) +{ + if (state->input_buffer) + for (size_t i = 0; i < state->input_rows_count; i++) + free_bufferline(state->input_buffer+i); + free(state->input_buffer); + state->input_buffer = NULL; +} + +void +move_up(struct DrawState* state) +{ + 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->saved_column == -1) + state->saved_column = state->input_display_column; + size_t dlen = display_length(state, state->input_buffer[ + state->input_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_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 + EDIT_WIDTH-5 < + state->input_display_column) + state->input_first_shown_column = state->input_display_column - + EDIT_WIDTH+5; + state->input_display_column = display_length(state, state->input_column); +} + +void +move_down(struct DrawState* state) +{ + 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].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_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 + EDIT_WIDTH-5 < + state->input_display_column) + state->input_first_shown_column = state->input_display_column - + EDIT_WIDTH+5; + state->input_display_column = display_length(state, state->input_column); +} + +void +move_start(struct DrawState* state) +{ + state->input_column = 0; + state->input_display_column = 0; + state->input_first_shown_column = 0; +} + +void +move_left(struct DrawState* state) +{ + if (state->input_column == 0) + return; + + if (state->input_buffer[state->input_row].text[ + state->input_column-1] == '\t') + state->input_display_column = display_length(state, + state->input_column-1); + else + state->input_display_column--; + state->input_column--; + + if (state->input_display_column < state->input_first_shown_column) + state->input_first_shown_column = state->input_display_column; +} + +void +move_right(struct DrawState* state) +{ + size_t len = state->input_buffer[state->input_row].length; + if (state->input_column == len) + return; + + if (state->input_buffer[state->input_row].text[ + state->input_column] == '\t') + state->input_display_column = display_length(state, + state->input_column+1); + else + state->input_display_column++; + state->input_column++; + + if (state->input_display_column - state->input_first_shown_column + > EDIT_WIDTH-5) + state->input_first_shown_column = state->input_display_column + - EDIT_WIDTH+5; +} + +void +move_end(struct DrawState* state) +{ + + size_t len = state->input_buffer[state->input_row].length; + state->input_column = len; + size_t dlen = display_length(state, state->input_column); + state->input_display_column = dlen; + if (dlen > EDIT_WIDTH-5) + state->input_first_shown_column = dlen - EDIT_WIDTH+5; + else + state->input_first_shown_column = 0; +} + +/* 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, const size_t max_index) +{ + size_t len = 0; + size_t i = 0; + uint32_t* pbufline = state->input_buffer[state->input_row].text; + while (*pbufline && i < max_index) + { + if (*pbufline == '\t') + len += TAB_SIZE - (len % TAB_SIZE); + else + len++; + pbufline++; + i++; + } + + 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, const size_t max_index) +{ + size_t len = 0; + size_t i = 0; + uint32_t* pbufline = state->input_buffer[state->input_row].text; + while (*pbufline && len < max_index) + { + if (*pbufline == '\t') + len += TAB_SIZE - (len % TAB_SIZE); + else + len++; + pbufline++; + i++; + } + + return i; +} + +void +insert_char(struct DrawState* state, uint32_t ch) +{ + struct BufferLine* buffer_line = &state->input_buffer[state->input_row]; + size_t len = buffer_line->length; + if (state->input_column < MAXBUFLINE-1) + { + size_t i = len; + int add_null = buffer_line->text[state->input_column] == 0; + while (i < MAXBUFLINE-2 && i > state->input_column) + { + buffer_line->text[i] = buffer_line->text[i-1]; + i--; + } + buffer_line->text[state->input_column++] = ch; + buffer_line->length++; + if (state->input_column == MAXBUFLINE) + state->input_column = MAXBUFLINE-1; + state->input_display_column = display_length(state, + state->input_column); + if (add_null) + buffer_line->text[state->input_column] = 0; + } + if (state->input_display_column - state->input_first_shown_column + > EDIT_WIDTH-5) + state->input_first_shown_column = state->input_display_column + - EDIT_WIDTH+5; +} + +struct DrawState* +insert_line(struct DrawState* state) +{ + state->input_rows_count++; + struct BufferLine* newbuf = realloc( + state->input_buffer, state->input_rows_count * + sizeof(struct BufferLine)); + if (!newbuf) + return NULL; + + state->input_buffer = newbuf; + init_bufferline(&state->input_buffer[state->input_rows_count-1]); + uint32_t* newtext = calloc(MAXBUFLINE, sizeof(uint32_t)); + if (!newtext) + return NULL; + 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); + + return state; +} + +struct DrawState* +join_lines(struct DrawState* state, size_t first_line) +{ + if (!state) + return NULL; + + state->input_column = state->input_buffer[first_line].length; + state->input_display_column = display_length(state, + state->input_column); + 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); + state->input_row--; + if (state->input_row < 0) + state->input_row = 0; + for (size_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); + state->input_buffer[i].length = state->input_buffer[i+1].length; + } + free_bufferline(state->input_buffer+state->input_rows_count-1); + state->input_rows_count--; + struct BufferLine* newbuf = realloc( + state->input_buffer, state->input_rows_count * + sizeof(struct BufferLine)); + if (!newbuf) + return NULL; + state->input_buffer = newbuf; + + return state; +} + +void +handle_editbox_key_event(struct tb_event* ev, struct DrawState* state) +{ + if (state->msgid_count < 1) + return; + + struct PoEntry* entry = &state->entries[state->msgid_number-1]; + struct BufferLine* buffer_line = &state->input_buffer[state->input_row]; + size_t len = buffer_line->length; + + if (state->error[0]) + state->error[0] = 0; + + if (ev->key != TB_KEY_ARROW_DOWN && + ev->key != TB_KEY_ARROW_UP) + state->saved_column = -1; + + if (ev->key == TB_KEY_ARROW_DOWN) + move_down(state); + else if (ev->key == TB_KEY_ARROW_LEFT + || ev->key == TB_KEY_CTRL_P) + move_left(state); + else if (ev->key == TB_KEY_ARROW_RIGHT + || ev->key == TB_KEY_CTRL_N) + move_right(state); + else if (ev->key == TB_KEY_ARROW_UP) + move_up(state); + else if (ev->key == TB_KEY_BACKSPACE + || ev->key == TB_KEY_BACKSPACE2) + { + if (state->input_column > 0) + { + size_t i = state->input_column; + do + { + buffer_line->text[i-1] = buffer_line->text[i]; + i++; + } + while (buffer_line->text[i]); + buffer_line->text[i-1] = 0; + buffer_line->length--; + state->input_column--; + if (state->input_column < + state->input_first_shown_column) + { + if (state->input_column > EDIT_WIDTH-6) + state->input_first_shown_column = + state->input_column + - EDIT_WIDTH+6; + else + state->input_first_shown_column = 0; + } + state->input_display_column = display_length(state, + state->input_column); + } + else if (state->input_row > 0) + if (!join_lines(state, state->input_row-1)) + strcpy(state->error, errors[ERR_CANT_ALLOC]); + } + else if (ev->key == TB_KEY_CTRL_F) + { + entry->flags ^= FL_FUZZY; + state->dirty = 1; + } + else if (ev->key == TB_KEY_CTRL_Q) + strcpy(state->error, errors[ERR_DLG_OPEN_QUIT]); + else if (ev->key == TB_KEY_CTRL_S) + { + state->show_edit = 0; + save_msgstr(state); + tb_set_cursor(TB_HIDE_CURSOR, TB_HIDE_CURSOR); + } + else if (ev->key == TB_KEY_CTRL_U) + { + size_t i = state->input_column; + do + { + buffer_line->text[i-state->input_column] = + 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; + move_start(state); + } + else if (ev->key == TB_KEY_DELETE) + { + if (buffer_line->text[state->input_column]) + { + size_t i = state->input_column; + while (i < MAXBUFLINE-2 && buffer_line->text[i+1]) + { + buffer_line->text[i] = buffer_line->text[i+1]; + i++; + } + buffer_line->text[i] = 0; + buffer_line->length--; + } + else if (state->input_row < state->input_rows_count-1) + if (!join_lines(state, state->input_row)) + strcpy(state->error, errors[ERR_CANT_ALLOC]); + } + else if (ev->key == TB_KEY_END + || ev->key == TB_KEY_CTRL_E) + move_end(state); + else if (ev->key == TB_KEY_ENTER) + { + if (!insert_line(state)) + strcpy(state->error, errors[ERR_CANT_ALLOC]); + } + else if (ev->key == TB_KEY_ESC) + { + state->show_edit = 0; + cancel_msgstr(state); + tb_set_cursor(TB_HIDE_CURSOR, TB_HIDE_CURSOR); + } + else if (ev->key == TB_KEY_HOME + || ev->key == TB_KEY_CTRL_A) + move_start(state); + else if (ev->key == TB_KEY_SPACE) + insert_char(state, ' '); + else if (ev->key == TB_KEY_TAB) + insert_char(state, '\t'); + else + insert_char(state, ev->ch); +} + void handle_key_event(struct tb_event* ev, struct DrawState* state) { if (!state) return; - if (ev->key == TB_KEY_ESC) + if (state->show_edit) + handle_editbox_key_event(ev, state); + else if (ev->key == TB_KEY_ESC) { if (state->in_prompt) { @@ -112,9 +604,16 @@ handle_key_event(struct tb_event* ev, struct DrawState* state) strcpy(state->error, errors[ERR_EXIT_KEY]); if (state->show_help) state->show_help = 0; - if (state->show_edit) - state->show_edit = 0; } + else if ((ev->ch == 'y' || ev->ch == 'n') && state->in_prompt) + { + if (state->prompt_callback) + state->prompt_callback(state, ev); + state->in_prompt = 0; + state->prompt[0] = 0; + } + else if (state->in_prompt) + return; else if (ev->ch == 'q' || ev->key == TB_KEY_CTRL_Q) { if (state->dirty) @@ -126,25 +625,13 @@ handle_key_event(struct tb_event* ev, struct DrawState* state) else state->running = 0; } - else if ((ev->ch == 'y' || ev->ch == 'n') && state->in_prompt) - { - if (state->prompt_callback) - state->prompt_callback(state, ev); - state->in_prompt = 0; - state->prompt[0] = 0; - } else if (state->show_help) strcpy(state->error, errors[ERR_DLG_OPEN]); else if (ev->key == TB_KEY_F1 || ev->ch == 'H') { - if (state->show_edit) - strcpy(state->error, errors[ERR_DLG_OPEN]); - else - { - state->show_help = 1; - state->error[0] = 0; - } + state->show_help = 1; + state->error[0] = 0; } else if (ev->key == TB_KEY_ENTER) { @@ -152,8 +639,16 @@ handle_key_event(struct tb_event* ev, struct DrawState* state) strcpy(state->error, errors[ERR_EMPTY_FILE]); else { - state->show_edit = 1; - state->error[0] = 0; + if (load_msgstr(state)) + { + state->show_edit = 1; + state->input_row = 0; + state->input_first_shown_row = 0; + move_start(state); + state->error[0] = 0; + } + else + strcpy(state->error, errors[ERR_CANT_ALLOC]); } } else if (ev->key == TB_KEY_CTRL_F @@ -165,8 +660,7 @@ handle_key_event(struct tb_event* ev, struct DrawState* state) state->dirty = 1; } } - else if (ev->key == TB_KEY_HOME - || ev->ch == 'g') + else if (ev->key == TB_KEY_HOME || ev->ch == 'g') { state->error[0] = 0; state->msgid_number = state->msgid_count ? 1 : 0; @@ -204,22 +698,41 @@ main(int argc, char** argv) { struct tb_event ev; struct timespec sleep_spec; - char error[MAXBUF]; - char prompt[MAXBUF]; + char error[MAXBUFLINE]; + char prompt[MAXBUFLINE]; struct DrawState state; FILE* input = NULL; char filename[MAXPATH]; + state.msgid_count = 5; + struct PoEntry* entries = calloc(state.msgid_count, + sizeof(struct PoEntry)); + if (!entries) + return print_error(ENOMEM, "Not enough memory"); + /* Test */ - struct PoEntry entries[] = { - { .flags = FL_C_FORMAT | FL_FUZZY, - .msgid = "First msgid: %s", .msgstr = "Први msgid: %s" }, - { .flags = FL_NONE, - .msgid = "Test msgid", .msgstr = "Пробни msgstr" }, - { .flags = FL_NONE, - .msgid = "Empty msgstr", .msgstr = NULL }, - { .flags = FL_FUZZY, - .msgid = "Final text", .msgstr = "Последњи текст" }, - }; + set_flags(&entries[0], FL_C_FORMAT | FL_FUZZY); + set_msgid(&entries[0], "First msgid: %s"); + set_msgstr(&entries[0], "Први msgid: %s"); + + set_flags(&entries[1], FL_NONE); + set_msgid(&entries[1], "Test msgid"); + set_msgstr(&entries[1], "Пробни msgstr"); + + set_flags(&entries[2], FL_NONE); + set_msgid(&entries[2], "Empty msgstr"); + entries[2].msgstr = NULL; + + set_flags(&entries[3], FL_NONE); + set_msgid(&entries[3], + "Multiline string\\nThis is the second line.\\n" + "\\nAnd the last line."); + set_msgstr(&entries[3], + "Multiline string\\nThis is the second line.\\n" + "\\nAnd the last line."); + + set_flags(&entries[4], FL_FUZZY); + set_msgid(&entries[4], "Final text"); + set_msgstr(&entries[4], "Последњи текст"); error[0] = 0; prompt[0] = 0; @@ -251,19 +764,23 @@ main(int argc, char** argv) tb_init(); error[0] = 0; - state.error = error; - state.filename = filename; - state.prompt = prompt; - state.entries = &entries[0]; - state.prompt_callback = NULL; - state.running = 1; - state.dirty = 0; - state.in_prompt = 0; - state.show_edit = 0; - state.show_help = 0; - state.msgid_count = LEN(entries); - state.msgid_number = state.msgid_count > 0 ? 1 : 0; - state.first_shown_msgid = state.msgid_number; + state.error = error; + state.filename = filename; + state.input_buffer = NULL; + state.input_column = 0; + state.input_first_shown_column = 0; + state.input_display_column = 0; + state.saved_column = -1; + state.prompt = prompt; + state.entries = &entries[0]; + state.prompt_callback = NULL; + state.running = 1; + state.dirty = 0; + state.in_prompt = 0; + state.show_edit = 0; + state.show_help = 0; + state.msgid_number = state.msgid_count > 0 ? 1 : 0; + state.first_shown_msgid = state.msgid_number; while (state.running) { @@ -273,7 +790,8 @@ main(int argc, char** argv) if (state.msgid_number - state.first_shown_msgid > state.maxx-2) state.first_shown_msgid = state.msgid_number; - draw(&state); + if (!draw(&state)) + strcpy(state.error, errors[ERR_CANT_ALLOC]); tb_present(); if (tb_peek_event(&ev, 20) > 0 && ev.type == TB_EVENT_KEY) handle_key_event(&ev, &state); diff --git a/util.c b/util.c @@ -1,6 +1,10 @@ /* See the file LICENSE for copyright and license details. */ #include <bits/stdint-uintn.h> +#include <stdarg.h> +#include <stddef.h> +#include <stdio.h> #include <stdlib.h> +#include <string.h> #include "termbox.h" #include "po.h" @@ -14,7 +18,7 @@ u8_string_to_unicode(uint32_t* us, const char* s) int u8_len = 0; size_t result = 0; - while (*s && u8_len != TB_EOF) + while (s && *s && u8_len != TB_EOF) { u8_len = tb_utf8_char_to_unicode(&uch, s); *pus++ = uch; @@ -24,6 +28,241 @@ u8_string_to_unicode(uint32_t* us, const char* s) result++; } } + *pus++ = 0; return result; } +char* +unicode_string_to_u8(char* s, const uint32_t* us) +{ + char ch[8]; + const uint32_t* pus = us; + + while (*pus) + { + int len = tb_utf8_unicode_to_char(ch, *pus); + ch[len] = 0; + strcat(s, ch); + pus++; + } + return s; +} + +size_t +u32_encode_tabs(uint32_t* s, size_t max) +{ + uint32_t* buf = calloc(max, sizeof(uint32_t)); + if (!buf) + return 0; + uint32_t* pbuf = buf; + uint32_t* ps = s; + size_t newlen = 0; + + while (*ps) + { + if (*ps == '\t') + { + *pbuf++ = '\\'; + *pbuf++ = 't'; + ps++; + newlen += 2; + } + else + { + *pbuf++ = *ps++; + newlen++; + } + } + *pbuf = 0; + u32_strncpy(s, buf, max); + free(buf); + return newlen; +} + +size_t +u32_decode_tabs(uint32_t* s, size_t max) +{ + uint32_t* buf = calloc(max, sizeof(uint32_t)); + if (!buf) + return 0; + uint32_t* pbuf = buf; + uint32_t* ps = s; + size_t newlen = 0; + + while (*ps) + { + if (*ps == '\\' && *(ps+1) == 't') + { + *pbuf++ = '\t'; + ps += 2; + newlen++; + } + else + { + *pbuf++ = *ps++; + newlen++; + } + } + *pbuf = 0; + u32_strncpy(s, buf, max); + free(buf); + return newlen; +} + +size_t +u32_strlen(const uint32_t* s) +{ + const uint32_t* ps = s; + size_t len = 0; + if (!s) + return 0; + while (*ps++) + len++; + return len; +} + +size_t +u32_strncpy(uint32_t* to, const uint32_t* from, size_t max) +{ + const uint32_t* pfrom = from; + size_t copied = 0; + while (*pfrom && copied < max-1) + { + *to++ = *pfrom++; + copied++; + } + *to = 0; + return copied; +} + +/* + * to - u32 string to concatenate to + * from - u32 string to concatenate + * max - total combined string size */ +size_t +u32_strncat(uint32_t* to, const uint32_t* from, size_t max) +{ + const uint32_t* pfrom = from; + size_t copied = 0; + while (*to) + { + to++; + copied++; + } + while (*pfrom && copied < max-1) + { + *to++ = *pfrom++; + copied++; + } + *to = 0; + return copied; +} + +size_t +u32_u8_strncpy(uint32_t* to, const char* from, size_t max) +{ + uint32_t* ufrom = calloc(strlen(from)+1, sizeof(uint32_t)); + if (!ufrom) + return 0; + u8_string_to_unicode(ufrom, from); + size_t len = u32_strncpy(to, ufrom, max); + free(ufrom); + return len; +} + +size_t +u8_u32_strncpy(char* to, const uint32_t* from, size_t max) +{ + char* cfrom = calloc(u32_strlen(from)*6, 1); + if (!cfrom) + return 0; + unicode_string_to_u8(cfrom, from); + strncpy(to, cfrom, max); + to[max-1] = 0; + free(cfrom); + return strlen(to); +} + +size_t +u8_line_length(const char* s) +{ + size_t len = 0; + if (!s) + return len; + const char* ps = s; + while (*ps) + { + if (*ps == '\\' && *(ps+1) == 'n') + return len; + ps++; + len++; + } + return len; +} + +size_t +u32_line_length(const uint32_t* s) +{ + size_t len = 0; + if (!s) + return len; + const uint32_t* ps = s; + while (*ps) + { + if (*ps == '\\' && *(ps+1) == 'n') + return len; + ps++; + len++; + } + return len; +} + +size_t +u8_lines_in_string(const char* s) +{ + size_t result = 1; + const char* ps = s; + if (!s) + return result; + while (*ps) + { + if (*ps == '\\' && *(ps+1) == 'n') + { + result++; + ps++; + } + ps++; + } + return result; +} + +size_t +u32_lines_in_string(const uint32_t* s) +{ + size_t result = 1; + const uint32_t* ps = s; + if (!s) + return result; + while (*ps) + { + if (*ps == '\\' && *(ps+1) == 'n') + { + result++; + ps++; + } + ps++; + } + return result; +} + +void +poe_log(const char* msg, ...) +{ + va_list args; + va_start(args, msg); + FILE* log = fopen("/tmp/poe.log", "at"); + vfprintf(log, msg, args); + va_end(args); + fclose(log); +} + diff --git a/util.h b/util.h @@ -1,16 +1,35 @@ +#include <bits/stdint-uintn.h> +#include <sys/types.h> #define LEN(x) (sizeof(x) / sizeof(x[0])) +#define MAX(a,b) ((a > b) ? a : b) +#define MIN(a,b) ((a < b) ? a : b) enum { ERR_UNKNOWN_KEY, ERR_EXIT_KEY, ERR_DLG_OPEN, ERR_DLG_OPEN_QUIT, - ERR_EMPTY_FILE + ERR_EMPTY_FILE, + ERR_CANT_ALLOC }; -#define MAXBUF 255 +#define MAXBUFLINE 1024 +#define MAXMSG 1024 #define MAXPATH 1024 #define MAXFLAGSBUF 2+1 size_t u8_string_to_unicode(uint32_t* us, const char* s); +char* unicode_string_to_u8(char* s, const uint32_t* us); +size_t u32_encode_tabs(uint32_t* s, size_t max); +size_t u32_decode_tabs(uint32_t* s, size_t max); +size_t u32_strlen(const uint32_t* s); +size_t u32_strncpy(uint32_t* to, const uint32_t* from, size_t max); +size_t u32_strncat(uint32_t* to, const uint32_t* from, size_t max); +size_t u32_u8_strncpy(uint32_t* to, const char* from, size_t max); +size_t u8_u32_strncpy(char* to, const uint32_t* from, size_t max); +size_t u8_line_length(const char* s); +size_t u32_line_length(const uint32_t* s); +size_t u8_lines_in_string(const char* s); +size_t u32_lines_in_string(const uint32_t* s); +void poe_log(const char* msg, ...);