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 250e61a764d867385a09548558b0df6c28af8036
parent 3d0707ecc637d39e0f853d036c592bbcbe7675f7
Author: lumidify <nobody@lumidify.org>
Date:   Sat,  6 Nov 2021 23:42:41 +0100

Add commands for jumping to character

Diffstat:
Mbuffer.c | 16++++++++++++++++
Mbuffer.h | 1+
Mkeys_basic.c | 161+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
Mkeys_basic_config.h | 8++++++++
Mkeys_command_config.h | 2--
5 files changed, 179 insertions(+), 9 deletions(-)

diff --git a/buffer.c b/buffer.c @@ -917,6 +917,22 @@ ledit_buffer_next_cursor_pos(ledit_buffer *buffer, int line, int byte) { return ll->len; } +int +ledit_buffer_prev_cursor_pos(ledit_buffer *buffer, int line, int byte) { + int nattrs; + ledit_line *ll = ledit_buffer_get_line(buffer, line); + int c = line_byte_to_char(ll, byte); + int cur_byte = ledit_line_prev_utf8(ll, byte); + const PangoLogAttr *attrs = + pango_layout_get_log_attrs_readonly(ll->layout, &nattrs); + for (int i = c - 1; i >= 0; i--) { + if (attrs[i].is_cursor_position) + return cur_byte; + cur_byte = ledit_line_prev_utf8(ll, cur_byte); + } + return 0; +} + 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; diff --git a/buffer.h b/buffer.h @@ -58,6 +58,7 @@ 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_buffer_next_cursor_pos(ledit_buffer *buffer, int line, int byte); +int ledit_buffer_prev_cursor_pos(ledit_buffer *buffer, int 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); diff --git a/keys_basic.c b/keys_basic.c @@ -56,6 +56,8 @@ static struct { struct repetition_stack_elem *tmp_stack; } repetition_stack = {0, 0, 0, 0, NULL, 0, 0, NULL}; +typedef void (*motion_callback)(ledit_buffer *buffer, int line, int char_pos, enum key_type type); + struct key_stack_elem { enum key_type key; enum key_type followup; /* allowed keys to complete the keybinding */ @@ -63,7 +65,7 @@ struct key_stack_elem { * line and char_pos already include the repetition stored in this stack * element; type is the type of motion command (used to determine if * the command should operate on lines or chars) */ - void (*motion_cb)(ledit_buffer *buffer, int line, int char_pos, enum key_type type); + motion_callback motion_cb; int count; /* number of repetitions */ int data1; /* misc. data 1 */ int data2; /* misc. data 2 */ @@ -74,6 +76,8 @@ static struct { struct key_stack_elem *stack; } key_stack = {0, 0, NULL}; +static struct action (*grab_char_cb)(ledit_buffer *buffer, char *text, int len) = NULL; + void basic_key_cleanup(void) { /* this should be safe since push_repetition_stack sets all new @@ -1590,12 +1594,157 @@ clippaste(ledit_buffer *buffer, char *text, int len) { return (struct action){ACTION_NONE, NULL}; } +static int +get_key_repeat_and_motion_cb(motion_callback *cb_ret) { + 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); + } + if (cb_ret != NULL && e != NULL && e->motion_cb != NULL) + *cb_ret = e->motion_cb; + return num; +} + +/* FIXME: make sure the found position is valid cursor position? */ +static int +search_str_backwards(char *haystack, int hlen, char *needle, int nlen, int start_index) { + if (start_index < 0 || start_index > hlen) + return -1; + int new_index = start_index; + for (new_index--; new_index >= 0; new_index--) { + if (!strncmp(haystack + new_index, needle, nlen)) + return new_index; + } + return -1; +} + +static int +search_str_forwards(char *haystack, int hlen, char *needle, int nlen, int start_index) { + if (start_index < 0 || start_index >= hlen) + return -1; + /* duplicate so it is nul-terminated */ + char *search_str = ledit_strndup(needle, nlen); + char *res = strstr(haystack + start_index + 1, search_str); + free(search_str); + /* FIXME: is this legal? */ + if (res) + return (int)(res - haystack); + else + return -1; +} + +/* just to make the macro below works for all cases */ +/* FIXME: is there a more elegant way to do this? */ +static int +dummy_cursor_helper(ledit_buffer *buffer, int line, int index) { + (void)buffer; + (void)line; + return index; +} + +/* FIXME: call normalize on line first? */ +/* FIXME: add checks to functions that current mode is supported */ + +/* name is the name of the generated pair of functions + search_func is used to get the next index (possibly called + repeatedly if there is a repeat number on the key stack) + funcm = func motion, funcn = func normal, funcv = func visual + -> these are called to modify the index returned by search_func + cur_funcm is called to get the index for a motion callback + cur_funcn is called to position the cursor in normal mode + cur_funcv is called to position the cursor in visual mode */ +#define GEN_MOVE_TO_CHAR(name, search_func, cur_funcm, cur_funcn, cur_funcv) \ +static struct action \ +name##_cb(ledit_buffer *buffer, char *text, int len) { \ + motion_callback cb = NULL; \ + int num = get_key_repeat_and_motion_cb(&cb); \ + ledit_line *ll = ledit_buffer_get_line(buffer, buffer->cur_line); \ + int new_index = buffer->cur_index; \ + for (int i = 0; i < num; i++) { \ + new_index = search_func(ll->text, ll->len, text, len, new_index); \ + if (new_index == -1) \ + break; \ + } \ + if (new_index >= 0) { \ + if (cb != NULL) { \ + new_index = cur_funcm( \ + buffer, buffer->cur_line, new_index \ + ); \ + cb(buffer, buffer->cur_line, new_index, KEY_MOTION_CHAR); \ + } else { \ + if (buffer->common->mode == VISUAL) { \ + buffer->cur_index = cur_funcv( \ + buffer, buffer->cur_line, new_index \ + ); \ + ledit_buffer_set_selection( \ + buffer, \ + buffer->sel.line1, buffer->sel.byte1, \ + buffer->cur_line, buffer->cur_index \ + ); \ + } else { \ + buffer->cur_index = cur_funcn( \ + buffer, buffer->cur_line, new_index \ + ); \ + ledit_buffer_set_line_cursor_attrs( \ + buffer, buffer->cur_line, buffer->cur_index \ + ); \ + } \ + discard_repetition_stack(); \ + } \ + } \ + clear_key_stack(); \ + grab_char_cb = NULL; \ + return (struct action){ACTION_NONE, NULL}; \ +} \ + \ +static struct action \ +name(ledit_buffer *buffer, char *text, int len) { \ + (void)buffer; \ + (void)text; \ + (void)len; \ + grab_char_cb = &name##_cb; \ + return (struct action){ACTION_NONE, NULL}; \ +} + +/* FIXME: more sensible names */ +/* FIXME: dummy_cursor_helper is kind of ugly */ +GEN_MOVE_TO_CHAR( + find_next_char_forwards, search_str_forwards, + dummy_cursor_helper, ledit_buffer_prev_cursor_pos, dummy_cursor_helper +) +GEN_MOVE_TO_CHAR( + find_next_char_backwards, search_str_backwards, + ledit_buffer_next_cursor_pos, ledit_buffer_next_cursor_pos, ledit_buffer_next_cursor_pos +) +GEN_MOVE_TO_CHAR( + find_char_forwards, search_str_forwards, + ledit_buffer_next_cursor_pos, dummy_cursor_helper, dummy_cursor_helper +) +GEN_MOVE_TO_CHAR( + find_char_backwards, search_str_backwards, + dummy_cursor_helper, dummy_cursor_helper, dummy_cursor_helper +) + static struct action handle_key(ledit_buffer *buffer, char *key_text, int len, KeySym sym, unsigned int key_state, int lang_index, int *found) { - struct action act = {ACTION_NONE, NULL}; struct key *cur_keys = keys[lang_index].keys; int num_keys = keys[lang_index].num_keys; struct key_stack_elem *e = peek_key_stack(); + /* FIXME: allow to escape this grabbing somehow */ + /* -> I guess escape *does* actually work because it + results in ascii 1b (escape) in this string, which + will usually not be found in the text (but this is + a bit of a hack) */ + if (len > 0 && grab_char_cb) { + *found = 1; + return grab_char_cb(buffer, key_text, len); + } *found = 0; for (int i = 0; i < num_keys; i++) { if (cur_keys[i].text) { @@ -1607,19 +1756,17 @@ handle_key(ledit_buffer *buffer, char *key_text, int len, KeySym sym, unsigned i cur_keys[i].text[0] == '\0')) { /* FIXME: seems a bit hacky to remove shift, but it is needed to make keys that use shift match */ - act = cur_keys[i].func(buffer, key_text, len); *found = 1; - break; + return cur_keys[i].func(buffer, key_text, len); } } else if ((cur_keys[i].modes & buffer->common->mode) && cur_keys[i].keysym == sym && match_key(cur_keys[i].mods, key_state)) { - act = cur_keys[i].func(buffer, key_text, len); *found = 1; - break; + return cur_keys[i].func(buffer, key_text, len); } } - return act; + return (struct action){ACTION_NONE, NULL}; } static struct action diff --git a/keys_basic_config.h b/keys_basic_config.h @@ -80,6 +80,10 @@ static struct action append_after_eol(ledit_buffer *buffer, char *text, int len) static struct action append_after_cursor(ledit_buffer *buffer, char *text, int len); static struct action append_line_above(ledit_buffer *buffer, char *text, int len); static struct action append_line_below(ledit_buffer *buffer, char *text, int len); +static struct action find_next_char_forwards(ledit_buffer *buffer, char *text, int len); +static struct action find_next_char_backwards(ledit_buffer *buffer, char *text, int len); +static struct action find_char_forwards(ledit_buffer *buffer, char *text, int len); +static struct action find_char_backwards(ledit_buffer *buffer, char *text, int len); /* FIXME: maybe sort these and use binary search -> but that would mess with the catch-all keys */ @@ -153,6 +157,10 @@ static struct key keys_en[] = { {"o", 0, 0, NORMAL, KEY_ANY, KEY_ANY, &append_line_below}, {"m", 0, 0, NORMAL, KEY_ANY, KEY_ANY, &mark_line}, {"'", 0, 0, NORMAL, KEY_ANY, KEY_ANY, &jump_to_mark}, + {"t", 0, 0, NORMAL|VISUAL, KEY_ANY, KEY_NUMBERALLOWED, &find_next_char_forwards}, + {"T", 0, 0, NORMAL|VISUAL, KEY_ANY, KEY_NUMBERALLOWED, &find_next_char_backwards}, + {"f", 0, 0, NORMAL|VISUAL, KEY_ANY, KEY_NUMBERALLOWED, &find_char_forwards}, + {"F", 0, 0, NORMAL|VISUAL, KEY_ANY, KEY_NUMBERALLOWED, &find_char_backwards}, {"", 0, 0, INSERT, KEY_ANY, KEY_ANY, &insert_mode_insert_text} }; diff --git a/keys_command_config.h b/keys_command_config.h @@ -29,8 +29,6 @@ static struct key keys_en[] = { {"", 0, 0, CMD_EDIT, &edit_insert_text}, {"", 0, 0, CMD_EDITSEARCH, &edit_insert_text}, {"", 0, 0, CMD_EDITSEARCHB, &edit_insert_text}, - /* FIXME: also allow non-text keys for marks? - OpenBSD nvi seems to allow it, at least sort of. */ {"", 0, 0, CMD_MARKLINE, &mark_line}, {"", 0, 0, CMD_JUMPTOMARK, &jump_to_mark} };