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 <nobody@lumidify.org>
Date:   Fri,  5 Nov 2021 19:06:35 +0100

Implement word movement commands

Diffstat:
Mbuffer.c | 284+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mbuffer.h | 9+++++++++
Mkeys_basic.c | 56++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mkeys_basic_config.h | 12++++++++++++
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},