tAdd basic (buggy) support for command repetition - 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 bc282da2c2bfd820f810d1e03bc829fe3928f3e5 | |
parent 9df066a3d594aeeca41677744f0f29a81901f124 | |
Author: lumidify <[email protected]> | |
Date: Sun, 24 Oct 2021 13:48:53 +0200 | |
Add basic (buggy) support for command repetition | |
Diffstat: | |
M keys_basic.c | 186 ++++++++++++++++++++++++++---… | |
M keys_basic_config.h | 2 ++ | |
M memory.c | 8 ++++++++ | |
M memory.h | 1 + | |
4 files changed, 171 insertions(+), 26 deletions(-) | |
--- | |
diff --git a/keys_basic.c b/keys_basic.c | |
t@@ -28,6 +28,22 @@ | |
/* this is supposed to be global for all buffers */ | |
txtbuf *paste_buffer = NULL; | |
+struct repetition_stack_elem { | |
+ char *key_text; | |
+ int len; | |
+ KeySym sym; | |
+ unsigned int key_state; | |
+ int lang_index; | |
+}; | |
+ | |
+static struct { | |
+ int replaying; | |
+ size_t len, alloc, cur_pos; | |
+ struct repetition_stack_elem *stack; | |
+ size_t tmp_len, tmp_alloc; | |
+ struct repetition_stack_elem *tmp_stack; | |
+} repetition_stack = {0, 0, 0, 0, NULL, 0, 0, NULL}; | |
+ | |
struct key_stack_elem { | |
enum key_type key; | |
enum key_type followup; /* allowed keys to complete the keybinding */ | |
t@@ -46,6 +62,14 @@ static struct { | |
struct key_stack_elem *stack; | |
} key_stack = {0, 0, NULL}; | |
+/* No, this isn't actually a stack. So what? */ | |
+static struct repetition_stack_elem *push_repetition_stack(void); | |
+static void finalize_repetition_stack(void); | |
+static struct repetition_stack_elem *get_cur_repetition_stack_elem(void); | |
+static void unwind_repetition_stack(void); | |
+static void advance_repetition_stack(void); | |
+static void discard_repetition_stack(void); | |
+ | |
static struct key_stack_elem *push_key_stack(void); | |
static struct key_stack_elem *peek_key_stack(void); | |
static struct key_stack_elem *pop_key_stack(void); | |
t@@ -86,7 +110,7 @@ push_key_stack(void) { | |
e->data1 = 0; | |
e->data2 = 0; | |
key_stack.len++; | |
- return &key_stack.stack[key_stack.len - 1]; | |
+ return e; | |
} | |
/* Note: for peek and pop, the returned element is only valid | |
t@@ -113,6 +137,67 @@ clear_key_stack(void) { | |
key_stack.len = 0; | |
} | |
+static struct repetition_stack_elem * | |
+push_repetition_stack(void) { | |
+ struct repetition_stack_elem *e; | |
+ if (repetition_stack.tmp_len >= repetition_stack.tmp_alloc) { | |
+ size_t new_alloc = repetition_stack.tmp_alloc > 0 ? repetition… | |
+ repetition_stack.tmp_stack = ledit_realloc( | |
+ repetition_stack.tmp_stack, | |
+ new_alloc * sizeof(struct repetition_stack_elem) | |
+ ); | |
+ repetition_stack.tmp_alloc = new_alloc; | |
+ } | |
+ e = &repetition_stack.tmp_stack[repetition_stack.tmp_len]; | |
+ e->key_text = NULL; | |
+ e->len = 0; | |
+ e->sym = 0; | |
+ e->key_state = 0; | |
+ e->lang_index = 0; | |
+ repetition_stack.tmp_len++; | |
+ return e; | |
+} | |
+ | |
+static struct repetition_stack_elem * | |
+get_cur_repetition_stack_elem(void) { | |
+ if (repetition_stack.cur_pos >= repetition_stack.len) | |
+ return NULL; | |
+ return &repetition_stack.stack[repetition_stack.cur_pos]; | |
+} | |
+ | |
+static void | |
+unwind_repetition_stack(void) { | |
+ repetition_stack.cur_pos = 0; | |
+} | |
+ | |
+static void | |
+discard_repetition_stack(void) { | |
+ if (repetition_stack.replaying) | |
+ return; | |
+ repetition_stack.tmp_len = 0; | |
+} | |
+ | |
+static void | |
+advance_repetition_stack(void) { | |
+ repetition_stack.cur_pos++; | |
+} | |
+ | |
+static void | |
+finalize_repetition_stack(void) { | |
+ if (repetition_stack.replaying) | |
+ return; | |
+ size_t tmp; | |
+ struct repetition_stack_elem *tmpstack; | |
+ repetition_stack.len = repetition_stack.tmp_len; | |
+ repetition_stack.tmp_len = 0; | |
+ tmp = repetition_stack.alloc; | |
+ repetition_stack.alloc = repetition_stack.tmp_alloc; | |
+ repetition_stack.tmp_alloc = tmp; | |
+ tmpstack = repetition_stack.stack; | |
+ repetition_stack.stack = repetition_stack.tmp_stack; | |
+ repetition_stack.tmp_stack = tmpstack; | |
+} | |
+ | |
/* get the new line and softline when moving 'movement' softlines up or | |
down (negative means up, positive means down) */ | |
static void | |
t@@ -295,6 +380,7 @@ key_d_cb(ledit_buffer *buffer, int line, int char_pos, enu… | |
line, char_pos | |
); | |
ledit_buffer_set_line_cursor_attrs(buffer, buffer->cur_line, buffer->c… | |
+ finalize_repetition_stack(); | |
} | |
static struct action | |
t@@ -523,6 +609,7 @@ move_cursor_left_right(ledit_buffer *buffer, int dir) { | |
} else if (buffer->common->mode == NORMAL) { | |
ledit_buffer_set_line_cursor_attrs(buffer, buffer->cur… | |
} | |
+ discard_repetition_stack(); | |
} | |
clear_key_stack(); | |
} | |
t@@ -621,6 +708,8 @@ escape_key(ledit_buffer *buffer, char *text, int len) { | |
(void)text; | |
(void)len; | |
clear_key_stack(); /* just in case... */ | |
+ if (buffer->common->mode == INSERT) | |
+ finalize_repetition_stack(); | |
if (buffer->common->mode == INSERT && | |
(buffer->sel.line1 != buffer->sel.line2 || | |
buffer->sel.byte1 != buffer->sel.byte2)) { | |
t@@ -708,6 +797,7 @@ move_cursor_up_down(ledit_buffer *buffer, int dir) { | |
} else if (buffer->common->mode == NORMAL) { | |
ledit_buffer_set_line_cursor_attrs(buffer, buffer->cur… | |
} | |
+ discard_repetition_stack(); | |
} | |
clear_key_stack(); | |
} | |
t@@ -749,6 +839,7 @@ cursor_to_beginning(ledit_buffer *buffer, char *text, int … | |
buffer, buffer->cur_line, buffer->cur_index | |
); | |
} | |
+ discard_repetition_stack(); | |
} | |
clear_key_stack(); | |
return (struct action){ACTION_NONE, NULL}; | |
t@@ -788,6 +879,7 @@ enter_commandedit(ledit_buffer *buffer, char *text, int le… | |
ledit_window_set_bottom_bar_cursor(buffer->window, 1); | |
ledit_command_set_type(CMD_EDIT); | |
ledit_window_set_bottom_bar_text_shown(buffer->window, 1); | |
+ discard_repetition_stack(); | |
return (struct action){ACTION_GRABKEY, &ledit_command_key_handler}; | |
} | |
t@@ -799,6 +891,7 @@ enter_searchedit_forward(ledit_buffer *buffer, char *text,… | |
ledit_window_set_bottom_bar_cursor(buffer->window, 1); | |
ledit_command_set_type(CMD_EDITSEARCH); | |
ledit_window_set_bottom_bar_text_shown(buffer->window, 1); | |
+ discard_repetition_stack(); | |
return (struct action){ACTION_GRABKEY, &ledit_command_key_handler}; | |
} | |
t@@ -810,6 +903,7 @@ enter_searchedit_backward(ledit_buffer *buffer, char *text… | |
ledit_window_set_bottom_bar_cursor(buffer->window, 1); | |
ledit_command_set_type(CMD_EDITSEARCHB); | |
ledit_window_set_bottom_bar_text_shown(buffer->window, 1); | |
+ discard_repetition_stack(); | |
return (struct action){ACTION_GRABKEY, &ledit_command_key_handler}; | |
} | |
t@@ -819,6 +913,7 @@ key_search_next(ledit_buffer *buffer, char *text, int len)… | |
(void)text; | |
(void)len; | |
search_next(buffer); | |
+ discard_repetition_stack(); | |
return (struct action){ACTION_NONE, NULL}; | |
} | |
t@@ -827,6 +922,7 @@ key_search_prev(ledit_buffer *buffer, char *text, int len)… | |
(void)text; | |
(void)len; | |
search_prev(buffer); | |
+ discard_repetition_stack(); | |
return (struct action){ACTION_NONE, NULL}; | |
} | |
t@@ -838,6 +934,7 @@ show_line(ledit_buffer *buffer, char *text, int len) { | |
char *str = ledit_malloc(textlen + 1); | |
snprintf(str, textlen + 1, "Line %d of %d", buffer->cur_line + 1, buff… | |
ledit_window_show_message(buffer->window, str, textlen); | |
+ discard_repetition_stack(); | |
return (struct action){ACTION_NONE, NULL}; | |
} | |
t@@ -850,6 +947,7 @@ undo(ledit_buffer *buffer, char *text, int len) { | |
ledit_buffer_wipe_line_cursor_attrs(buffer, buffer->cur_line); | |
ledit_buffer_undo(buffer); | |
ledit_buffer_set_line_cursor_attrs(buffer, buffer->cur_line, buffer->c… | |
+ finalize_repetition_stack(); | |
return (struct action){ACTION_NONE, NULL}; | |
} | |
t@@ -861,6 +959,7 @@ redo(ledit_buffer *buffer, char *text, int len) { | |
ledit_buffer_wipe_line_cursor_attrs(buffer, buffer->cur_line); | |
ledit_buffer_redo(buffer); | |
ledit_buffer_set_line_cursor_attrs(buffer, buffer->cur_line, buffer->c… | |
+ finalize_repetition_stack(); | |
return (struct action){ACTION_NONE, NULL}; | |
} | |
t@@ -877,6 +976,7 @@ clipcopy(ledit_buffer *buffer, char *text, int len) { | |
(void)len; | |
/* FIXME: abstract this through buffer */ | |
clipboard_primary_to_clipboard(buffer->window); | |
+ discard_repetition_stack(); | |
return (struct action){ACTION_NONE, NULL}; | |
} | |
t@@ -885,53 +985,87 @@ clippaste(ledit_buffer *buffer, char *text, int len) { | |
(void)text; | |
(void)len; | |
ledit_buffer_paste_clipboard(buffer); | |
+ finalize_repetition_stack(); | |
return (struct action){ACTION_NONE, NULL}; | |
} | |
-struct action | |
-basic_key_handler(ledit_buffer *buffer, XEvent *event, int lang_index) { | |
- char buf[64]; | |
- KeySym sym; | |
- int n; | |
- | |
+static struct action | |
+handle_key(ledit_buffer *buffer, char *key_text, int len, KeySym sym, unsigned… | |
+ struct action act = {ACTION_NONE, NULL}; | |
struct key *cur_keys = keys[lang_index].keys; | |
int num_keys = keys[lang_index].num_keys; | |
- unsigned int key_state = event->xkey.state; | |
- preprocess_key(buffer->window, event, &sym, buf, sizeof(buf), &n); | |
- | |
- int found = 0; | |
struct key_stack_elem *e = peek_key_stack(); | |
- /* FIXME: only hide when actually necessary */ | |
- ledit_window_hide_message(buffer->window); | |
- struct action act; | |
+ *found = 0; | |
for (int i = 0; i < num_keys; i++) { | |
if (cur_keys[i].text) { | |
- if (n > 0 && | |
+ if (len > 0 && | |
(cur_keys[i].modes & buffer->common->mode) && | |
(!e || (e->key & cur_keys[i].prev_keys)) && | |
- ((!strncmp(cur_keys[i].text, buf, n) && | |
+ ((!strncmp(cur_keys[i].text, key_text, len) && | |
match_key(cur_keys[i].mods, key_state & ~ShiftM… | |
cur_keys[i].text[0] == '\0')) { | |
/* FIXME: seems a bit hacky to remove shift, b… | |
is needed to make keys that use shift match… | |
- act = cur_keys[i].func(buffer, buf, n); | |
- found = 1; | |
+ act = cur_keys[i].func(buffer, key_text, len); | |
+ *found = 1; | |
break; | |
} | |
} 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, buf, n); | |
- found = 1; | |
+ act = cur_keys[i].func(buffer, key_text, len); | |
+ *found = 1; | |
break; | |
} | |
} | |
+ return act; | |
+} | |
+ | |
+static struct action | |
+repeat_command(ledit_buffer *buffer, char *text, int len) { | |
+ (void)buffer; | |
+ (void)text; | |
+ (void)len; | |
+ int found; | |
+ repetition_stack.replaying = 1; | |
+ clear_key_stack(); | |
+ unwind_repetition_stack(); | |
+ struct repetition_stack_elem *e = get_cur_repetition_stack_elem(); | |
+ while (e) { | |
+ (void)handle_key(buffer, e->key_text, e->len, e->sym, e->key_s… | |
+ advance_repetition_stack(); | |
+ e = get_cur_repetition_stack_elem(); | |
+ } | |
+ repetition_stack.replaying = 0; | |
+ discard_repetition_stack(); | |
+ clear_key_stack(); | |
+ return (struct action){ACTION_NONE, NULL}; | |
+} | |
+ | |
+struct action | |
+basic_key_handler(ledit_buffer *buffer, XEvent *event, int lang_index) { | |
+ char buf[64]; | |
+ KeySym sym; | |
+ int n; | |
+ | |
+ unsigned int key_state = event->xkey.state; | |
+ preprocess_key(buffer->window, event, &sym, buf, sizeof(buf), &n); | |
+ | |
+ struct repetition_stack_elem *re = push_repetition_stack(); | |
+ re->key_text = ledit_strndup(buf, (size_t)n); | |
+ re->len = n; | |
+ re->sym = sym; | |
+ re->key_state = key_state; | |
+ re->lang_index = lang_index; | |
+ | |
+ /* FIXME: only hide when actually necessary */ | |
+ ledit_window_hide_message(buffer->window); | |
+ int found = 0; | |
+ struct action act = handle_key(buffer, buf, n, sym, key_state, lang_in… | |
+ | |
/* FIXME: only do this when necessary */ | |
- if (found) { | |
+ if (found) | |
ledit_buffer_ensure_cursor_shown(buffer); | |
- return act; | |
- } else { | |
- /* FIXME: maybe show error */ | |
- return (struct action){ACTION_NONE, NULL}; | |
- } | |
+ /* FIXME: maybe show error if not found */ | |
+ return act; | |
} | |
diff --git a/keys_basic_config.h b/keys_basic_config.h | |
t@@ -56,6 +56,7 @@ static struct action key_search_prev(ledit_buffer *buffer, c… | |
static struct action undo(ledit_buffer *buffer, char *text, int len); | |
static struct action redo(ledit_buffer *buffer, char *text, int len); | |
static struct action insert_mode_insert_text(ledit_buffer *buffer, char *text,… | |
+static struct action repeat_command(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@@ -98,6 +99,7 @@ static struct key keys_en[] = { | |
{"N", 0, 0, NORMAL|VISUAL, KEY_ANY, KEY_ANY, &key_search_prev}, | |
{"u", 0, 0, NORMAL|VISUAL, KEY_ANY, KEY_ANY, &undo}, | |
{"U", 0, 0, NORMAL|VISUAL, KEY_ANY, KEY_ANY, &redo}, | |
+ {".", 0, 0, NORMAL, KEY_ANY, KEY_ANY, &repeat_command}, /* FIXME: onl… | |
{"z", ControlMask, 0, INSERT, KEY_ANY, KEY_ANY, &undo}, | |
{"y", ControlMask, 0, INSERT, KEY_ANY, KEY_ANY, &redo}, | |
{"", 0, 0, INSERT, KEY_ANY, KEY_ANY, &insert_mode_insert_text} | |
diff --git a/memory.c b/memory.c | |
t@@ -16,6 +16,14 @@ ledit_strdup(const char *s) { | |
return str; | |
} | |
+char * | |
+ledit_strndup(const char *s, size_t n) { | |
+ char *str = strndup(s, n); | |
+ if (!str) | |
+ fatal_err("Out of memory.\n"); | |
+ return str; | |
+} | |
+ | |
void * | |
ledit_malloc(size_t size) { | |
void *ptr = malloc(size); | |
diff --git a/memory.h b/memory.h | |
t@@ -1,4 +1,5 @@ | |
char *ledit_strdup(const char *s); | |
+char *ledit_strndup(const char *s, size_t n); | |
void *ledit_malloc(size_t size); | |
void *ledit_calloc(size_t nmemb, size_t size); | |
void *ledit_realloc(void *ptr, size_t size); |