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