aboutsummaryrefslogtreecommitdiff
path: root/text-motions.c
diff options
context:
space:
mode:
authorMarc André Tanner <mat@brain-dump.org>2016-02-13 12:22:18 +0100
committerMarc André Tanner <mat@brain-dump.org>2016-02-13 13:32:43 +0100
commitcc7ce30fa6a2ebdb2e14d589b11520757e5a20e3 (patch)
tree8852d4330cf14f4cff292dd1b7e7c14da1aabb9d /text-motions.c
parente0b157f56694a5b5e535083c8fc0bc0e1194c9dc (diff)
downloadvis-cc7ce30fa6a2ebdb2e14d589b11520757e5a20e3.tar.gz
vis-cc7ce30fa6a2ebdb2e14d589b11520757e5a20e3.tar.xz
text-motion: add functions to get/set position based on display width
This is inherently a tricky thing to do because we cannot rely on the current display state. The reason being that the position/cursor which is modified might not currently be in the visible area. Tabs are a particular problem because they have a variable display width. However this new code is certainly not worse than the current behaviour which relies on text_line_char_{get,set} and thus simply counts graphemes. Not yet completely convinced that this is the right approach.
Diffstat (limited to 'text-motions.c')
-rw-r--r--text-motions.c77
1 files changed, 77 insertions, 0 deletions
diff --git a/text-motions.c b/text-motions.c
index 6ca2f8f..04ccab1 100644
--- a/text-motions.c
+++ b/text-motions.c
@@ -15,6 +15,9 @@
*/
#include <ctype.h>
#include <string.h>
+#include <stdlib.h>
+#include <wchar.h>
+#include <errno.h>
#include "text-motions.h"
#include "text-util.h"
#include "util.h"
@@ -218,6 +221,80 @@ int text_line_char_get(Text *txt, size_t pos) {
return count;
}
+int text_line_width_get(Text *txt, size_t pos) {
+ int width = 0;
+ mbstate_t ps = { 0 };
+ size_t bol = text_line_begin(txt, pos);
+ Iterator it = text_iterator_get(txt, bol);
+
+ while (it.pos < pos) {
+ char buf[MB_CUR_MAX];
+ size_t len = text_bytes_get(txt, it.pos, sizeof buf, buf);
+ if (len == 0 || buf[0] == '\r' || buf[0] == '\n')
+ break;
+ wchar_t wc;
+ size_t wclen = mbrtowc(&wc, buf, len, &ps);
+ if (wclen == (size_t)-1 && errno == EILSEQ) {
+ /* assume a replacement symbol will be displayed */
+ width++;
+ } else if (wclen == (size_t)-2) {
+ /* do nothing, advance to next character */
+ } else if (wclen == 0) {
+ /* assume NUL byte will be displayed as ^@ */
+ width += 2;
+ } else if (buf[0] == '\t') {
+ width++;
+ } else {
+ int w = wcwidth(wc);
+ if (w == -1)
+ w = 2; /* assume non-printable will be displayed as ^{char} */
+ width += w;
+ }
+
+ if (!text_iterator_codepoint_next(&it, NULL))
+ break;
+ }
+
+ return width;
+}
+
+size_t text_line_width_set(Text *txt, size_t pos, int width) {
+ int cur_width = 0;
+ mbstate_t ps = { 0 };
+ size_t bol = text_line_begin(txt, pos);
+ Iterator it = text_iterator_get(txt, bol);
+
+ for (;;) {
+ char buf[MB_CUR_MAX];
+ size_t len = text_bytes_get(txt, it.pos, sizeof buf, buf);
+ if (len == 0 || buf[0] == '\r' || buf[0] == '\n')
+ break;
+ wchar_t wc;
+ size_t wclen = mbrtowc(&wc, buf, len, &ps);
+ if (wclen == (size_t)-1 && errno == EILSEQ) {
+ /* assume a replacement symbol will be displayed */
+ cur_width++;
+ } else if (wclen == (size_t)-2) {
+ /* do nothing, advance to next character */
+ } else if (wclen == 0) {
+ /* assume NUL byte will be displayed as ^@ */
+ cur_width += 2;
+ } else if (buf[0] == '\t') {
+ cur_width++;
+ } else {
+ int w = wcwidth(wc);
+ if (w == -1)
+ w = 2; /* assume non-printable will be displayed as ^{char} */
+ cur_width += w;
+ }
+
+ if (cur_width >= width || !text_iterator_codepoint_next(&it, NULL))
+ break;
+ }
+
+ return it.pos;
+}
+
size_t text_line_char_next(Text *txt, size_t pos) {
char c;
Iterator it = text_iterator_get(txt, pos);