226 lines
4.2 KiB
C
226 lines
4.2 KiB
C
|
/*
|
||
|
* 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;
|
||
|
}
|