tImplement word movement commands - ledit - Text editor (WIP) | |
git clone git://lumidify.org/ledit.git (fast, but not encrypted) | |
git clone https://lumidify.org/git/ledit.git (encrypted, but very slow) | |
Log | |
Files | |
Refs | |
README | |
LICENSE | |
--- | |
commit 335e5d61cc5d4876fb7240543fb55afb55f00cfb | |
parent 657c25540c4b349068c4c24f82b6b5bb1f94c459 | |
Author: lumidify <[email protected]> | |
Date: Fri, 5 Nov 2021 19:06:35 +0100 | |
Implement word movement commands | |
Diffstat: | |
M buffer.c | 284 +++++++++++++++++++++++++++++… | |
M buffer.h | 9 +++++++++ | |
M keys_basic.c | 56 +++++++++++++++++++++++++++++… | |
M keys_basic_config.h | 12 ++++++++++++ | |
4 files changed, 361 insertions(+), 0 deletions(-) | |
--- | |
diff --git a/buffer.c b/buffer.c | |
t@@ -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 */ | |
t@@ -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, i… | |
+ 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_… | |
+ 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_lin… | |
+ 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 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 uni… | |
+ | |
+#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_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->… | |
+ *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)) ==… | |
+ 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 *en… | |
+} | |
+*/ | |
+ | |
/* FIXME: no idea why this exists */ | |
/* | |
static void | |
diff --git a/buffer.h b/buffer.h | |
t@@ -57,6 +57,15 @@ void ledit_pos_to_x_softline(ledit_line *line, int pos, int… | |
void ledit_x_softline_to_pos(ledit_line *line, int x, int softline, int *pos_r… | |
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_… | |
+void ledit_buffer_next_word_end(ledit_buffer *buffer, int line, int byte, int … | |
+void ledit_buffer_next_bigword(ledit_buffer *buffer, int line, int byte, int n… | |
+void ledit_buffer_next_bigword_end(ledit_buffer *buffer, int line, int byte, i… | |
+void ledit_buffer_prev_word(ledit_buffer *buffer, int line, int byte, int num_… | |
+void ledit_buffer_prev_bigword(ledit_buffer *buffer, int line, int byte, int n… | |
+ | |
size_t ledit_buffer_textlen(ledit_buffer *buffer, int line1, int byte1, int li… | |
void ledit_buffer_copy_text(ledit_buffer *buffer, char *dst, int line1, int by… | |
void ledit_buffer_copy_text_to_txtbuf( | |
diff --git a/keys_basic.c b/keys_basic.c | |
t@@ -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 | |
t@@ -70,6 +70,12 @@ static struct action change(ledit_buffer *buffer, char *tex… | |
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 le… | |
+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 */ | |
t@@ -128,6 +134,12 @@ static struct key keys_en[] = { | |
{"d", ControlMask, 0, NORMAL, KEY_ANY, KEY_NUMBERALLOWED, &scroll_lin… | |
{"u", ControlMask, 0, NORMAL, KEY_ANY, KEY_NUMBERALLOWED, &scroll_lin… | |
{"$", 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_… | |
+ {"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}, |