commit 335e5d61cc5d4876fb7240543fb55afb55f00cfb
parent 657c25540c4b349068c4c24f82b6b5bb1f94c459
Author: lumidify <nobody@lumidify.org>
Date: Fri, 5 Nov 2021 19:06:35 +0100
Implement word movement commands
Diffstat:
4 files changed, 361 insertions(+), 0 deletions(-)
diff --git a/buffer.c b/buffer.c
@@ -866,6 +866,8 @@ ledit_buffer_copy_text_to_txtbuf(
int
ledit_line_prev_utf8(ledit_line *line, int index) {
+ if (index <= 0)
+ return 0;
int i = index - 1;
/* find valid utf8 char - this probably needs to be improved */
/* FIXME: don't go off end or beginning */
@@ -876,12 +878,294 @@ ledit_line_prev_utf8(ledit_line *line, int index) {
int
ledit_line_next_utf8(ledit_line *line, int index) {
+ if (index >= line->len)
+ return line->len;
int i = index + 1;
while (i < line->len && ((LINE_CHAR(line, i) & 0xC0) == 0x80))
i++;
return i;
}
+/* Warning: this is very inefficient! */
+/* FIXME: at least attempt to be more efficient by starting from the beginning
+ or end based on approximately where in the line the byte is */
+static int
+line_byte_to_char(ledit_line *line, int byte) {
+ int c = 0;
+ int i = 0;
+ int b = byte > line->len ? line->len : byte; /* maybe not necessary */
+ while (i < b) {
+ c++;
+ i = ledit_line_next_utf8(line, i);
+ }
+ return c;
+}
+
+static int
+line_next_word(ledit_line *line, int byte, int char_index, int wrapped_line, int *char_ret, int *real_byte_ret) {
+ int c, nattrs;
+ if (char_index >= 0)
+ c = char_index;
+ else
+ c = line_byte_to_char(line, byte);
+ int cur_byte = wrapped_line ? byte : ledit_line_next_utf8(line, byte);
+ const PangoLogAttr *attrs =
+ pango_layout_get_log_attrs_readonly(line->layout, &nattrs);
+ for (int i = wrapped_line ? c : c + 1; i < nattrs; i++) {
+ if (attrs[i].is_word_start) {
+ if (char_ret)
+ *char_ret = i;
+ if (real_byte_ret)
+ *real_byte_ret = cur_byte;
+ return cur_byte;
+ }
+ cur_byte = ledit_line_next_utf8(line, cur_byte);
+ }
+ return -1;
+}
+
+static int
+line_prev_word(ledit_line *line, int byte, int char_index, int *char_ret) {
+ int c, nattrs;
+ if (char_index >= 0)
+ c = char_index;
+ else
+ c = line_byte_to_char(line, byte);
+ int cur_byte = ledit_line_prev_utf8(line, byte);
+ const PangoLogAttr *attrs =
+ pango_layout_get_log_attrs_readonly(line->layout, &nattrs);
+ if (c > nattrs)
+ return -1;
+ for (int i = c - 1; i >= 0; i--) {
+ if (attrs[i].is_word_start) {
+ if (char_ret)
+ *char_ret = i;
+ return cur_byte;
+ }
+ cur_byte = ledit_line_prev_utf8(line, cur_byte);
+ }
+ return -1;
+}
+
+static int
+line_prev_bigword(ledit_line *line, int byte, int char_index, int *char_ret) {
+ int c, nattrs;
+ if (char_index >= 0)
+ c = char_index;
+ else
+ c = line_byte_to_char(line, byte);
+ int cur_byte = ledit_line_prev_utf8(line, byte);
+ const PangoLogAttr *attrs =
+ pango_layout_get_log_attrs_readonly(line->layout, &nattrs);
+ int next_cursorb = byte;
+ int next_cursorc = c;
+ int found_word = 0;
+ for (int i = c - 1; i >= 0; i--) {
+ if (!found_word && !attrs[i].is_white) {
+ found_word = 1;
+ } else if (found_word && attrs[i].is_white) {
+ if (char_ret)
+ *char_ret = next_cursorc;
+ return next_cursorb;
+ }
+ if (found_word && c == 0) {
+ if (char_ret)
+ *char_ret = 0;
+ return 0;
+ }
+ if (attrs[i].is_cursor_position) {
+ next_cursorc = i;
+ next_cursorb = cur_byte;
+ }
+ cur_byte = ledit_line_prev_utf8(line, cur_byte);
+ }
+ return -1;
+}
+
+int
+line_next_bigword_end(ledit_line *line, int byte, int char_index, int wrapped_line, int *char_ret, int *real_byte_ret) {
+ int c, nattrs;
+ if (char_index >= 0)
+ c = char_index;
+ else
+ c = line_byte_to_char(line, byte);
+ const PangoLogAttr *attrs =
+ pango_layout_get_log_attrs_readonly(line->layout, &nattrs);
+ int last_cursorb, last_cursorc;
+ if (wrapped_line) {
+ last_cursorb = byte;
+ last_cursorc = c;
+ } else {
+ last_cursorb = -1;
+ last_cursorc = -1;
+ }
+ int found_word = 0;
+ int cur_byte = byte;
+ for (int i = c; i < nattrs; i++) {
+ if (last_cursorb != -1 && !found_word && !attrs[i].is_white) {
+ found_word = 1;
+ } else if (found_word && attrs[i].is_white) {
+ if (char_ret)
+ *char_ret = last_cursorc;
+ if (real_byte_ret)
+ *real_byte_ret = cur_byte;
+ return last_cursorb;
+ }
+ if (attrs[i].is_cursor_position) {
+ last_cursorc = i;
+ last_cursorb = cur_byte;
+ }
+ cur_byte = ledit_line_next_utf8(line, cur_byte);
+ }
+ return -1;
+}
+
+static int
+line_next_word_end(ledit_line *line, int byte, int char_index, int wrapped_line, int *char_ret, int *real_byte_ret) {
+ int c, nattrs;
+ if (char_index >= 0)
+ c = char_index;
+ else
+ c = line_byte_to_char(line, byte);
+ int cur_byte = ledit_line_next_utf8(line, byte);
+ const PangoLogAttr *attrs =
+ pango_layout_get_log_attrs_readonly(line->layout, &nattrs);
+ int last_cursorb, last_cursorc;
+ if (wrapped_line) {
+ last_cursorb = byte;
+ last_cursorc = c;
+ } else {
+ last_cursorb = -1;
+ last_cursorc = -1;
+ }
+ for (int i = c + 1; i < nattrs; i++) {
+ if (last_cursorb != -1 && attrs[i].is_word_end) {
+ if (char_ret)
+ *char_ret = last_cursorc;
+ if (real_byte_ret)
+ *real_byte_ret = cur_byte;
+ return last_cursorb;
+ }
+ if (attrs[i].is_cursor_position) {
+ last_cursorc = i;
+ last_cursorb = cur_byte;
+ }
+ cur_byte = ledit_line_next_utf8(line, cur_byte);
+ }
+ return -1;
+}
+
+static int
+line_next_bigword(ledit_line *line, int byte, int char_index, int wrapped_line, int *char_ret, int *real_byte_ret) {
+ int c, nattrs;
+ if (char_index >= 0)
+ c = char_index;
+ else
+ c = line_byte_to_char(line, byte);
+ int cur_byte = byte;
+ const PangoLogAttr *attrs =
+ pango_layout_get_log_attrs_readonly(line->layout, &nattrs);
+ int found_ws = wrapped_line;
+ for (int i = c; i < nattrs; i++) {
+ if (!found_ws && attrs[i].is_white) {
+ found_ws = 1;
+ } else if (found_ws && !attrs[i].is_white) {
+ if (char_ret)
+ *char_ret = i;
+ if (real_byte_ret)
+ *real_byte_ret = cur_byte;
+ return cur_byte;
+ }
+ cur_byte = ledit_line_next_utf8(line, cur_byte);
+ }
+ return -1;
+}
+
+/* FIXME: document that word and bigword are a bit weird because word uses unicode semantics */
+
+#define GEN_NEXT_WORD(name, func) \
+void \
+ledit_buffer_next_##name( \
+ ledit_buffer *buffer, \
+ int line, int byte, int num_repeat, \
+ int *line_ret, int *byte_ret, int *real_byte_ret) { \
+ int cur_line = line; \
+ int cur_byte = byte; \
+ int cur_char = -1; \
+ int real_byte = -1; \
+ int wrapped_line; \
+ ledit_line *ll = ledit_buffer_get_line(buffer, cur_line); \
+ for (int i = 0; i < num_repeat; i++) { \
+ wrapped_line = 0; \
+ while ((cur_byte = func(ll, cur_byte, cur_char, wrapped_line, &cur_char, &real_byte)) == -1 && \
+ cur_line < buffer->lines_num - 1) { \
+ cur_line++; \
+ ll = ledit_buffer_get_line(buffer, cur_line); \
+ cur_byte = 0; \
+ wrapped_line = 1; \
+ } \
+ if (cur_byte == -1 && cur_line == buffer->lines_num - 1) \
+ break; \
+ } \
+ if (cur_byte == -1) { \
+ *line_ret = buffer->lines_num - 1; \
+ *byte_ret = ledit_buffer_get_legal_normal_pos(buffer, buffer->lines_num - 1, ll->len); \
+ *real_byte_ret = ll->len; \
+ } else { \
+ *line_ret = cur_line; \
+ *byte_ret = cur_byte; \
+ *real_byte_ret = real_byte; \
+ } \
+}
+
+#define GEN_PREV_WORD(name, func) \
+void \
+ledit_buffer_prev_##name( \
+ ledit_buffer *buffer, \
+ int line, int byte, int num_repeat, \
+ int *line_ret, int *byte_ret, int *real_byte_ret) { \
+ int cur_line = line; \
+ int cur_byte = byte; \
+ int cur_char = -1; \
+ ledit_line *ll = ledit_buffer_get_line(buffer, cur_line); \
+ for (int i = 0; i < num_repeat; i++) { \
+ while ((cur_byte = func(ll, cur_byte, cur_char, &cur_char)) == -1 && cur_line > 0) { \
+ cur_line--; \
+ ll = ledit_buffer_get_line(buffer, cur_line); \
+ cur_byte = ll->len; \
+ } \
+ if (cur_byte == -1 && cur_line == 0) \
+ break; \
+ } \
+ if (cur_byte == -1) { \
+ *line_ret = 0; \
+ *byte_ret = 0; \
+ *real_byte_ret = 0; \
+ } else { \
+ *line_ret = cur_line; \
+ *byte_ret = cur_byte; \
+ *real_byte_ret = cur_byte; \
+ } \
+}
+
+GEN_NEXT_WORD(word, line_next_word)
+GEN_NEXT_WORD(word_end, line_next_word_end)
+GEN_NEXT_WORD(bigword, line_next_bigword)
+GEN_NEXT_WORD(bigword_end, line_next_bigword_end)
+GEN_PREV_WORD(word, line_prev_word)
+GEN_PREV_WORD(bigword, line_prev_bigword)
+
+/* FIXME: implement */
+/*
+int
+ledit_line_nearest_cursor_pos(ledit_line *line, int byte) {
+}
+
+void
+ledit_line_word_boundaries(ledit_line *line, int byte, int *start_ret, int *end_ret) {
+}
+*/
+
/* FIXME: no idea why this exists */
/*
static void
diff --git a/buffer.h b/buffer.h
@@ -57,6 +57,15 @@ void ledit_pos_to_x_softline(ledit_line *line, int pos, int *x_ret, int *softlin
void ledit_x_softline_to_pos(ledit_line *line, int x, int softline, int *pos_ret);
int ledit_line_next_utf8(ledit_line *line, int index);
int ledit_line_prev_utf8(ledit_line *line, int index);
+int ledit_line_byte_to_char(ledit_line *line, int byte);
+
+void ledit_buffer_next_word(ledit_buffer *buffer, int line, int byte, int num_repeat, int *line_ret, int *byte_ret, int *real_byte_ret);
+void ledit_buffer_next_word_end(ledit_buffer *buffer, int line, int byte, int num_repeat, int *line_ret, int *byte_ret, int *real_byte_ret);
+void ledit_buffer_next_bigword(ledit_buffer *buffer, int line, int byte, int num_repeat, int *line_ret, int *byte_ret, int *real_byte_ret);
+void ledit_buffer_next_bigword_end(ledit_buffer *buffer, int line, int byte, int num_repeat, int *line_ret, int *byte_ret, int *real_byte_ret);
+void ledit_buffer_prev_word(ledit_buffer *buffer, int line, int byte, int num_repeat, int *line_ret, int *byte_ret, int *real_byte_ret);
+void ledit_buffer_prev_bigword(ledit_buffer *buffer, int line, int byte, int num_repeat, int *line_ret, int *byte_ret, int *real_byte_ret);
+
size_t ledit_buffer_textlen(ledit_buffer *buffer, int line1, int byte1, int line2, int byte2);
void ledit_buffer_copy_text(ledit_buffer *buffer, char *dst, int line1, int byte1, int line2, int byte2);
void ledit_buffer_copy_text_to_txtbuf(
diff --git a/keys_basic.c b/keys_basic.c
@@ -1040,6 +1040,62 @@ move_to_eol(ledit_buffer *buffer, char *text, int len) {
return (struct action){ACTION_NONE, NULL};
}
+#define GEN_WORD_MOVEMENT(name, func) \
+static struct action \
+name(ledit_buffer *buffer, char *text, int len) { \
+ (void)text; \
+ (void)len; \
+ int num = 1; \
+ struct key_stack_elem *e = pop_key_stack(); \
+ if (e != NULL) { \
+ if (e->key & KEY_NUMBER) { \
+ num = e->count > 0 ? e->count : 1; \
+ e = pop_key_stack(); \
+ } \
+ if (e != NULL) \
+ num *= (e->count > 0 ? e->count : 1); \
+ } \
+ int new_line, new_index, new_real_index; \
+ func( \
+ buffer, \
+ buffer->cur_line, buffer->cur_index, num, \
+ &new_line, &new_index, &new_real_index \
+ ); \
+ if (e != NULL && e->motion_cb != NULL) { \
+ e->motion_cb(buffer, new_line, new_real_index, KEY_MOTION_CHAR); \
+ } else { \
+ if (buffer->common->mode == VISUAL) { \
+ ledit_buffer_set_selection( \
+ buffer, \
+ buffer->sel.line1, buffer->sel.byte1, \
+ new_line, new_real_index \
+ ); \
+ buffer->cur_line = new_line; \
+ buffer->cur_index = new_real_index; \
+ } else { \
+ if (new_line != buffer->cur_line) \
+ ledit_buffer_wipe_line_cursor_attrs( \
+ buffer, buffer->cur_line \
+ ); \
+ buffer->cur_line = new_line; \
+ buffer->cur_index = new_index; \
+ ledit_buffer_set_line_cursor_attrs( \
+ buffer, buffer->cur_line, buffer->cur_index \
+ ); \
+ } \
+ discard_repetition_stack(); \
+ } \
+ clear_key_stack(); \
+ return (struct action){ACTION_NONE, NULL}; \
+}
+
+GEN_WORD_MOVEMENT(next_word, ledit_buffer_next_word)
+GEN_WORD_MOVEMENT(next_word_end, ledit_buffer_next_word_end)
+GEN_WORD_MOVEMENT(next_bigword, ledit_buffer_next_bigword)
+GEN_WORD_MOVEMENT(next_bigword_end, ledit_buffer_next_bigword_end)
+GEN_WORD_MOVEMENT(prev_word, ledit_buffer_prev_word)
+GEN_WORD_MOVEMENT(prev_bigword, ledit_buffer_prev_bigword)
+
static void
move_cursor_left_right(ledit_buffer *buffer, int dir, int allow_illegal_index) {
int num = 1;
diff --git a/keys_basic_config.h b/keys_basic_config.h
@@ -70,6 +70,12 @@ static struct action change(ledit_buffer *buffer, char *text, int len);
static struct action move_to_eol(ledit_buffer *buffer, char *text, int len);
static struct action mark_line(ledit_buffer *buffer, char *text, int len);
static struct action jump_to_mark(ledit_buffer *buffer, char *text, int len);
+static struct action next_word(ledit_buffer *buffer, char *text, int len);
+static struct action next_word_end(ledit_buffer *buffer, char *text, int len);
+static struct action next_bigword(ledit_buffer *buffer, char *text, int len);
+static struct action next_bigword_end(ledit_buffer *buffer, char *text, int len);
+static struct action prev_word(ledit_buffer *buffer, char *text, int len);
+static struct action prev_bigword(ledit_buffer *buffer, char *text, int len);
/* FIXME: maybe sort these and use binary search
-> but that would mess with the catch-all keys */
@@ -128,6 +134,12 @@ static struct key keys_en[] = {
{"d", ControlMask, 0, NORMAL, KEY_ANY, KEY_NUMBERALLOWED, &scroll_lines_down},
{"u", ControlMask, 0, NORMAL, KEY_ANY, KEY_NUMBERALLOWED, &scroll_lines_up},
{"$", 0, 0, NORMAL|VISUAL, KEY_ANY, KEY_NUMBERALLOWED, &move_to_eol},
+ {"w", 0, 0, NORMAL|VISUAL, KEY_ANY, KEY_NUMBERALLOWED, &next_word},
+ {"e", 0, 0, NORMAL|VISUAL, KEY_ANY, KEY_NUMBERALLOWED, &next_word_end},
+ {"W", 0, 0, NORMAL|VISUAL, KEY_ANY, KEY_NUMBERALLOWED, &next_bigword},
+ {"E", 0, 0, NORMAL|VISUAL, KEY_ANY, KEY_NUMBERALLOWED, &next_bigword_end},
+ {"b", 0, 0, NORMAL|VISUAL, KEY_ANY, KEY_NUMBERALLOWED, &prev_word},
+ {"B", 0, 0, NORMAL|VISUAL, KEY_ANY, KEY_NUMBERALLOWED, &prev_bigword},
{"G", 0, 0, NORMAL, KEY_ANY, KEY_NUMBERALLOWED, &move_to_line},
{"p", 0, 0, NORMAL, KEY_ANY, KEY_ANY, &paste_normal},
{"P", 0, 0, NORMAL, KEY_ANY, KEY_ANY, &paste_normal_backwards},