/* * 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 */ #include #include #include #include #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; }