чување 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 | + |
| M | README | | | 2 | +- |
| M | TODO | | | 4 | ++-- |
| M | config.def.h | | | 4 | +++- |
| M | config.h | | | 4 | +++- |
| M | default.h.do | | | 10 | +++++----- |
| A | default.pdf.do | | | 3 | +++ |
| M | draw.c | | | 316 | +++++++++++++++++++++++++++++++++++++++++++++++++++---------------------------- |
| M | draw.h | | | 54 | +++++++++++++++++++++++++++++++++++++++++------------- |
| M | po.c | | | 74 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| M | po.h | | | 11 | +++++++++-- |
| A | poe-test.sh | | | 7 | +++++++ |
| M | poe.1.in | | | 46 | +++++++++++++++++++++++++++++++++++++++++++--- |
| M | poe.c | | | 620 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------- |
| M | util.c | | | 241 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++- |
| M | util.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, ...);