summaryrefslogtreecommitdiffstats
path: root/lib/mbsedit.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/mbsedit.c')
-rw-r--r--lib/mbsedit.c227
1 files changed, 227 insertions, 0 deletions
diff --git a/lib/mbsedit.c b/lib/mbsedit.c
new file mode 100644
index 0000000..ecfa9f4
--- /dev/null
+++ b/lib/mbsedit.c
@@ -0,0 +1,227 @@
+/*
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * Very simple multibyte buffer editor. Allows to maintaine the current
+ * position in the string, add and remove chars on the current position.
+ *
+ * This file may be distributed under the terms of the
+ * GNU Lesser General Public License.
+ *
+ * Copyright (C) 2017 Karel Zak <kzak@redhat.com>
+ */
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <stdio.h>
+
+#include "mbsalign.h"
+#include "mbsedit.h"
+
+struct mbs_editor *mbs_new_edit(char *buf, size_t bufsz, size_t ncells)
+{
+ struct mbs_editor *edit = calloc(1, sizeof(*edit));
+
+ if (edit) {
+ edit->buf = buf;
+ edit->max_bytes = bufsz;
+ edit->max_cells = ncells;
+ edit->cur_cells = mbs_safe_width(buf);
+ edit->cur_bytes = strlen(buf);
+ }
+ return edit;
+}
+
+char *mbs_free_edit(struct mbs_editor *edit)
+{
+ char *ret = edit ? edit->buf : NULL;
+
+ free(edit);
+ return ret;
+}
+
+static size_t mbs_next(const char *str, size_t *ncells)
+{
+#ifdef HAVE_WIDECHAR
+ wchar_t wc;
+ size_t n = 0;
+
+ if (!str || !*str)
+ return 0;
+
+ n = mbrtowc(&wc, str, MB_CUR_MAX, NULL);
+ *ncells = wcwidth(wc);
+ return n;
+#else
+ if (!str || !*str)
+ return 0;
+ *ncells = 1;
+ return 1;
+#endif
+}
+
+static size_t mbs_prev(const char *start, const char *end, size_t *ncells)
+{
+#ifdef HAVE_WIDECHAR
+ wchar_t wc = 0;
+ const char *p, *prev;
+ size_t n = 0;
+
+ if (!start || !end || start == end || !*start)
+ return 0;
+
+ prev = p = start;
+ while (p < end) {
+ n = mbrtowc(&wc, p, MB_CUR_MAX, NULL);
+ prev = p;
+
+ if (n == (size_t) -1 || n == (size_t) -2)
+ p++;
+ else
+ p += n;
+ }
+
+ if (prev == end)
+ return 0;
+ *ncells = wcwidth(wc);
+ return n;
+#else
+ if (!start || !end || start == end || !*start)
+ return 0;
+ *ncells = 1;
+ return 1;
+#endif
+}
+
+int mbs_edit_goto(struct mbs_editor *edit, int where)
+{
+ switch (where) {
+ case MBS_EDIT_LEFT:
+ if (edit->cursor == 0)
+ return 1;
+ else {
+ size_t n, cells;
+ n = mbs_prev(edit->buf, edit->buf + edit->cursor, &cells);
+ if (n) {
+ edit->cursor -= n;
+ edit->cursor_cells -= cells;
+ }
+ }
+ break;
+ case MBS_EDIT_RIGHT:
+ if (edit->cursor_cells >= edit->cur_cells)
+ return 1;
+ else {
+ size_t n, cells;
+ n = mbs_next(edit->buf + edit->cursor, &cells);
+ if (n) {
+ edit->cursor += n;
+ edit->cursor_cells += cells;
+ }
+ }
+ break;
+ case MBS_EDIT_HOME:
+ edit->cursor = 0;
+ edit->cursor_cells = 0;
+ break;
+ case MBS_EDIT_END:
+ edit->cursor = edit->cur_bytes;
+ edit->cursor_cells = edit->cur_cells;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/* Remove next MB from @str, returns number of removed bytes */
+static size_t remove_next(char *str, size_t *ncells)
+{
+ /* all in bytes! */
+ size_t bytes, move_bytes, n;
+
+ n = mbs_next(str, ncells);
+ bytes = strlen(str);
+ move_bytes = bytes - n;
+
+ memmove(str, str + n, move_bytes);
+ str[bytes - n] = '\0';
+ return n;
+}
+
+static size_t mbs_insert(char *str, wint_t c, size_t *ncells)
+{
+ /* all in bytes! */
+ size_t n = 1, bytes;
+ char *in;
+
+#ifdef HAVE_WIDECHAR
+ wchar_t wc = (wchar_t) c;
+ char in_buf[MB_CUR_MAX];
+
+ n = wctomb(in_buf, wc);
+ if (n == (size_t) -1)
+ return n;
+ *ncells = wcwidth(wc);
+ in = in_buf;
+#else
+ *ncells = 1;
+ in = (char *) &c;
+#endif
+ bytes = strlen(str);
+
+ memmove(str + n, str, bytes);
+ memcpy(str, in, n);
+ str[bytes + n] = '\0';
+ return n;
+}
+
+static int mbs_edit_remove(struct mbs_editor *edit)
+{
+ size_t n, ncells;
+
+ if (edit->cur_cells == 0 || edit->cursor >= edit->cur_bytes)
+ return 1;
+
+ n = remove_next(edit->buf + edit->cursor, &ncells);
+ if (n == (size_t)-1)
+ return 1;
+
+ edit->cur_bytes -= n;
+ edit->cur_cells = mbs_safe_width(edit->buf);
+ return 0;
+}
+
+int mbs_edit_delete(struct mbs_editor *edit)
+{
+ if (edit->cursor >= edit->cur_bytes
+ && mbs_edit_goto(edit, MBS_EDIT_LEFT) == 1)
+ return 1;
+
+ return mbs_edit_remove(edit);
+}
+
+int mbs_edit_backspace(struct mbs_editor *edit)
+{
+ if (mbs_edit_goto(edit, MBS_EDIT_LEFT) == 0)
+ return mbs_edit_remove(edit);
+ return 1;
+}
+
+int mbs_edit_insert(struct mbs_editor *edit, wint_t c)
+{
+ size_t n, ncells;
+
+ if (edit->cur_bytes + MB_CUR_MAX > edit->max_bytes)
+ return 1;
+
+ n = mbs_insert(edit->buf + edit->cursor, c, &ncells);
+ if (n == (size_t)-1)
+ return 1;
+
+ edit->cursor += n;
+ edit->cursor_cells += ncells;
+ edit->cur_bytes += n;
+ edit->cur_cells = mbs_safe_width(edit->buf);
+ return 0;
+}