diff --git a/TODO.md b/TODO.md index e5d666c..940b743 100644 --- a/TODO.md +++ b/TODO.md @@ -283,7 +283,6 @@ insert_window_left() deletes some left-most windows ## Macros -* `start-keyboard-macro` should clear current macro if called recursively * allow redefining the keyboard macro embedding itself * `show-macro`, `dump-macro` to ease macro debugging and timing * fix macro slowliness in 6000 line buffer **junk/dirs/x** @@ -293,7 +292,6 @@ insert_window_left() deletes some left-most windows * improve `dump-macro` to convert macro to string * fix `dump-macro` to save/restore `last-kbd-macro` to/from session * do not store messages during repeated macro execution or limit buffer size -* `name-last-kbd-macro` * fix source syntax issues ```lisp (define-macro "last-kbd-macro" "A-fA-fC-FC-FC-@C-EA-wC-AC-NC-XnC-YC-MC-Xp") diff --git a/buffer.c b/buffer.c index f4f208b..38f71fa 100644 --- a/buffer.c +++ b/buffer.c @@ -2341,11 +2341,7 @@ int eb_vprintf(EditBuffer *b, const char *fmt, va_list ap) { va_end(ap2); if (len >= size) { size = len + 1; -#ifdef CONFIG_WIN32 buf = qe_malloc_bytes(size); -#else - buf = alloca(size); -#endif vsnprintf(buf, size, fmt, ap); } /* CG: insert buf encoding according to b->charset and b->eol_type. @@ -2353,10 +2349,8 @@ int eb_vprintf(EditBuffer *b, const char *fmt, va_list ap) { * XXX: %c does not encode non ASCII characters as utf8. */ written = eb_insert_utf8_buf(b, b->offset, buf, len); -#ifdef CONFIG_WIN32 if (buf != buf0) qe_free(&buf); -#endif return written; } diff --git a/cutils.h b/cutils.h index 41fd0ca..e5d08c8 100644 --- a/cutils.h +++ b/cutils.h @@ -773,6 +773,19 @@ static inline void dbuf_set_error(DynBuf *s) { s->error = TRUE; } +static inline const char *dbuf_str(DynBuf *s) { + /*@API utils.dbuf + Get a pointer to a C string for the contents of a dynamic buffer + @argument `s` a valid pointer to an uninitialized dynamic buffer object. + @note: a null terminator is set at the end of the buffer and an empty + string is returned if the buffer has not been allocated. + */ + if (s->size == 0) + return ""; + s->buf[s->size] = '\0'; + return cs8(s->buf); +} + #define UTF8_CHAR_LEN_MAX 6 int unicode_to_utf8(uint8_t *buf, unsigned int c); diff --git a/qe-manual.md b/qe-manual.md index 47c8713..8815515 100644 --- a/qe-manual.md +++ b/qe-manual.md @@ -62,9 +62,10 @@ package for image parsing. # Commands -### `call-last-kbd-macro()` +### `call-last-kbd-macro(argval)` Run the last keyboard macro recorded by `start-kbd-macro`. +Repeat `argval` times. ### `end-kbd-macro()` @@ -175,8 +176,13 @@ FILENAME. ### `start-kbd-macro()` -Start recording a keyboard macro. Every key typed is stored into -a list for later replay using `call-last-kbd-macro` +Record subsequent keyboard input, defining a keyboard macro. +The commands are recorded even as they are executed. +Use `end-kbd-macro` (bound to `C-x )`) to finish recording and +make the macro available. +Use `name-last-kbd-macro` to give it a permanent name. +Use `call-last-kbd-macro` (bound to `C-x e` or `C-\`) to replay +the keystrokes. # Implementation @@ -931,6 +937,15 @@ Set the dynamic buffer current error code * argument `s` a valid pointer to an uninitialized dynamic buffer object. +### `const char *dbuf_str(DynBuf *s);` + +Get a pointer to a C string for the contents of a dynamic buffer + +* argument `s` a valid pointer to an uninitialized dynamic buffer object. + +Note: a null terminator is set at the end of the buffer and an empty +string is returned if the buffer has not been allocated. + ### `int dbuf_write(DynBuf *s, size_t offset, const uint8_t *data, size_t len);` Write a block of data at a given offset in a dynamic buffer @@ -1491,6 +1506,18 @@ Test if a parenthesis follows optional white space Return a boolean success value +### `int check_format_string(const char *fmt1, const char *fmt2, int max_width);` + +Check that a format string is compatible with a set of parameters. + +* argument `fmt1` a valid pointer to a C format string. + +* argument `fmt2` a valid pointer to a C format string with a minimal +set of conversion specifiers without flags, width, precision. + +Return the number of conversions matched or `-1` if there is a +type mismatch or too many conversions in the `fmt` string. + ### `int clamp_int(int a, int b, int c);` Clamp an integer value within a given range. diff --git a/qe.c b/qe.c index 351a577..d01e401 100644 --- a/qe.c +++ b/qe.c @@ -5264,7 +5264,7 @@ static void parse_arguments(ExecCmdState *es) /* if no argument specified, try to ask it to the user */ if (get_arg && cas.prompt[0] != '\0') { char def_input[1024]; - + StringArray *hist = qe_get_history(cas.history); /* XXX: currently, default input is handled non generically */ /* XXX: should use completion function for default input? */ def_input[0] = '\0'; @@ -5279,14 +5279,17 @@ static void parse_arguments(ExecCmdState *es) else b = s->b; pstrcpy(es->default_input, sizeof(es->default_input), b->name); + } else + if (strequal(cas.history, "macrokeys")) { + if (hist && hist->nb_items) + pstrcpy(def_input, sizeof(def_input), hist->items[hist->nb_items - 1]->str); } if (es->default_input[0] != '\0') { pstrcat(cas.prompt, sizeof(cas.prompt), "(default "); pstrcat(cas.prompt, sizeof(cas.prompt), es->default_input); pstrcat(cas.prompt, sizeof(cas.prompt), ") "); } - minibuffer_edit(s, def_input, cas.prompt, qe_get_history(cas.history), - cas.completion, arg_edit_cb, es); + minibuffer_edit(s, def_input, cas.prompt, hist, cas.completion, arg_edit_cb, es); return; } } @@ -5514,15 +5517,45 @@ void edit_display(QEmacsState *qs) qs->complete_refresh = 0; } -/* macros */ +/*---------------- Keyboard macros ----------------*/ + +/* XXX: missing macro commands: + `macro-edit-lossage` C-x C-k l + Edit most recent 300 keystrokes as a keyboard macro. + `kbd-macro-query` C-x q + Query user during kbd macro execution. + `show-macro`, `dump-macro` to ease macro debugging and timing + */ + +static void clear_macro(QEmacsState *qs) { + qe_free(&qs->macro_keys); + qs->macro_keys_size = 0; + qs->nb_macro_keys = 0; + qs->nb_macro_keys_run = 0; +} + +static void stop_macro(QEmacsState *qs) { + // XXX: should output a message + if (qs->defining_macro) { + qs->defining_macro = 0; + clear_macro(qs); + } + qs->executing_macro = 0; + qs->macro_key_index = -1; +} void do_start_kbd_macro(EditState *s) { /*@CMD start-kbd-macro ### `start-kbd-macro()` - Start recording a keyboard macro. Every key typed is stored into - a list for later replay using `call-last-kbd-macro` + Record subsequent keyboard input, defining a keyboard macro. + The commands are recorded even as they are executed. + Use `end-kbd-macro` (bound to `C-x )`) to finish recording and + make the macro available. + Use `name-last-kbd-macro` to give it a permanent name. + Use `call-last-kbd-macro` (bound to `C-x e` or `C-\`) to replay + the keystrokes. */ QEmacsState *qs = s->qe_state; @@ -5531,19 +5564,220 @@ void do_start_kbd_macro(EditState *s) } else { put_status(s, "Defining kbd macro..."); } + clear_macro(qs); qs->defining_macro = 1; - qe_free(&qs->macro_keys); - qs->nb_macro_keys = 0; - qs->macro_keys_size = 0; + qs->macro_counter = 0; } +#ifdef CONFIG_TINY +#define save_last_kbd_macro(s) +#else static void save_last_kbd_macro(EditState *s) { -#ifndef CONFIG_TINY - // XXX: should save the keyboard macro with `name-kbd-macro` - // XXX: should save macro keys to add_string(qe_get_history("macrokeys"), buf, 0) -#endif + QEmacsState *qs = s->qe_state; + char buf[32]; + buf_t out[1]; + DynBuf db[1]; + int i, len, haskey = 1; + const char *p; + StringArray *hist; + + if (qs->defining_macro || !qs->nb_macro_keys) + return; + + dbuf_init(db); + for (i = 0; i < qs->nb_macro_keys; i++) { + buf_init(out, buf, sizeof(buf)); + len = buf_put_key(out, qs->macro_keys[i]); + if (len != 1 + || haskey + || find_key_suffix(dbuf_str(db), out->buf[0]) != -1) { + if (i > 0) + dbuf_putc(db, ' '); + } + dbuf_putstr(db, out->buf); + haskey = (len != 1); + } + p = dbuf_str(db); + hist = qe_get_history("macrokeys"); + remove_string(hist, p); + add_string(hist, p, 0); + dbuf_free(db); } +static void macro_add_key(int key); + +static void do_edit_last_kbd_macro(EditState *s, const char *keys) { + QEmacsState *qs = s->qe_state; + const char *p; + + if (!keys || !*keys) + return; + + clear_macro(qs); + qs->macro_counter = 0; + + p = keys; + while (qe_skip_spaces(&p)) { + int key = strtokey(&p); + macro_add_key(key); + } + save_last_kbd_macro(s); + put_status(s, "Keyboard macro redefined"); +} + +static void do_name_last_kbd_macro(EditState *s, const char *name) +{ + StringArray *hist = qe_get_history("macrokeys"); + if (hist && hist->nb_items) { + do_define_kbd_macro(s, name, hist->items[hist->nb_items - 1]->str, NULL); + } +} + +static void do_insert_kbd_macro(EditState *s, const char *name) +{ + EditBuffer *b = s->b; + if (name && *name) { + const CmdDef *d = qe_find_cmd(name); + if (d && d->action.ESs == do_execute_macro_keys) { + const char *keys = d->spec + 2; /* skip @{ */ + b->offset = s->offset; + eb_printf(b, "define_kbd_macro(\"%s\", \"", name); + while (keys[1]) { /* stop at } */ + char32_t c = utf8_decode(&keys); + if (c == '\\' || c == '"') + eb_putc(b, '\\'); + eb_putc(b, c); + } + eb_puts(b, "\", \"\");\n"); + s->offset = b->offset; + } + } else { + StringArray *hist = qe_get_history("macrokeys"); + if (hist && hist->nb_items) { + const char *keys = hist->items[hist->nb_items - 1]->str; + b->offset = s->offset; + eb_printf(b, "edit_last_kbd_macro(\""); + while (keys[0]) { + char32_t c = utf8_decode(&keys); + if (c == '\\' || c == '"') + eb_putc(b, '\\'); + eb_putc(b, c); + } + eb_puts(b, "\");\n"); + s->offset = b->offset; + } + } +} + +static void do_read_kbd_macro(EditState *s, int mark, int offset) { + char buf[1024]; + int start = min_offset(mark, offset); + int stop = max_offset(mark, offset); + eb_get_region_contents(s->b, start, stop, buf, sizeof buf, 0); + do_edit_last_kbd_macro(s, buf); +} + +static void show_macro_counter(EditState *s) { + QEmacsState *qs = s->qe_state; + put_status(s, "new macro counter: %d", qs->macro_counter); +} + +static void do_macro_add_counter(EditState *s, int arg) { + QEmacsState *qs = s->qe_state; + qs->macro_counter += arg; + show_macro_counter(s); +} + +static void do_macro_set_counter(EditState *s, int arg) { + QEmacsState *qs = s->qe_state; + qs->macro_counter = arg; + show_macro_counter(s); +} + +static int check_format_string(const char *fmt1, const char *fmt2, int max_width) { + /*@API utils + Check that a format string is compatible with a set of parameters. + @argument `fmt1` a valid pointer to a C format string. + @argument `fmt2` a valid pointer to a C format string with a minimal + set of conversion specifiers without flags, width, precision. + @return the number of conversions matched or `-1` if there is a + type mismatch or too many conversions in the `fmt` string. + */ + const char *p; + const char *q; + int found = 0; + for (p = fmt1, q = fmt2; (p = strchr(p, '%')) != NULL; p++, q++) { + p++; + if (*p == '%') + continue; + if (*q++ != '%') + return -1; + p += strspn(p, "+- #0123456789"); + if (*p == '.') + p += 1 + strspn(p + 1, "0123456789"); + switch (*p) { + case 'h': + p += 1 + (p[1] == 'h'); + break; + case 'l': + if (*q++ != 'l') + return -1; + p++; + if (*p == 'l') { + p++; + if (*q++ != 'l') + return -1; + } else + if (*p == 'c' && *q != *p) + return -1; + break; + case 'L': + case 'j': + case 't': + case 'z': + if (*p++ != *q++) + return -1; + break; + } + if (*p == '\0' || *q == '\0') + return -1; + if (*p != *q) { + if ((memchr("bBcdiouxX", *p, 9) && memchr("bBcdiouxX", *q, 9)) + || (memchr("aAeEfFgG", *p, 8) && memchr("aAeEfFgG", *q, 8))) + continue; + return -1; + } + found++; + } + return found; +} + +static void do_macro_insert_counter(EditState *s, int arg) { + QEmacsState *qs = s->qe_state; + int (*eb_printf_fun)(EditBuffer *b, const char *fmt, ...) = eb_printf; + const char *fmt = qs->macro_format; + int n = qs->macro_counter; + if (!fmt || !*fmt) + fmt = "%d"; + if (check_format_string(fmt, "%d%d%d%d", 1024) < 0) { + put_status(s, "Invalid macro format: %s", fmt); + return; + } + s->b->offset = s->offset; + eb_printf_fun(s->b, fmt, n, n, n, n); + s->offset = s->b->offset; + qs->macro_counter += arg; + show_macro_counter(s); +} + +static void do_macro_set_format(EditState *s, const char *fmt) { + QEmacsState *qs = &qe_state; + qe_free(&qs->macro_format); + if (fmt) + qs->macro_format = qe_strdup(fmt); +} +#endif + void do_end_kbd_macro(EditState *s) { /*@CMD end-kbd-macro @@ -5564,16 +5798,19 @@ void do_end_kbd_macro(EditState *s) return; } qs->defining_macro = 0; + /* remove the last key(s) that invoked this function */ + qs->nb_macro_keys = qs->nb_macro_keys_run; save_last_kbd_macro(s); put_status(s, "Keyboard macro defined"); } -void do_call_last_kbd_macro(EditState *s) +void do_call_last_kbd_macro(EditState *s, int argval) { /*@CMD call-last-kbd-macro - ### `call-last-kbd-macro()` + ### `call-last-kbd-macro(argval)` Run the last keyboard macro recorded by `start-kbd-macro`. + Repeat `argval` times. */ QEmacsState *qs = s->qe_state; int set_repeat = (qs->last_key == 'e'); @@ -5586,16 +5823,22 @@ void do_call_last_kbd_macro(EditState *s) } if (qs->nb_macro_keys > 0) { - /* CG: should share code with do_execute_macro */ - for (qs->macro_key_index = 0; - qs->macro_key_index < qs->nb_macro_keys; - qs->macro_key_index++) - { - int key = qs->macro_keys[qs->macro_key_index]; - qe_key_process(key); + while (argval-- > 0) { + /* CG: should share code with do_execute_macro */ + for (qs->macro_key_index = 0; + qs->macro_key_index < qs->nb_macro_keys; + qs->macro_key_index++) + { + int key = qs->macro_keys[qs->macro_key_index]; + qe_key_process(key); + if (qs->macro_key_index < 0) { + // After 0 kbd macro iterations: Keyboard macro terminated by a command ringing the bell + argval = 0; + break; + } + } } qs->macro_key_index = -1; - qe_free_bindings(&qs->first_transient_key); if (set_repeat) qe_register_transient_binding(qs, "call-last-kbd-macro", "e"); @@ -5618,8 +5861,12 @@ void do_execute_macro_keys(EditState *s, const char *keys) while (qe_skip_spaces(&p)) { key = strtokey(&p); qe_key_process(key); + if (!qs->executing_macro) { + // After 0 kbd macro iterations: Keyboard macro terminated by a command ringing the bell + } } - qs->executing_macro--; + if (qs->executing_macro) + qs->executing_macro--; } void do_define_kbd_macro(EditState *s, const char *name, const char *keys, @@ -5805,11 +6052,15 @@ void qe_grab_keys(void (*cb)(void *opaque, int key), void *opaque) */ void qe_ungrab_keys(void) { + QEmacsState *qs = &qe_state; QEKeyContext *c = &key_ctx; /* CG: Should have an indicator to free previous grab */ c->grab_key_cb = NULL; c->grab_key_opaque = NULL; + if (qs->defining_macro) { + qs->nb_macro_keys_run = qs->nb_macro_keys; + } } /* init qe key handling context */ @@ -5991,6 +6242,9 @@ static void qe_key_process(int key) } exec_command(s, d, argval, key); } + if (qs->defining_macro) { + qs->nb_macro_keys_run = qs->nb_macro_keys; + } qe_key_init(c); // XXX: should delay until after macro execution edit_display(qs); @@ -6152,6 +6406,7 @@ void put_status(EditState *s, const char *fmt, ...) if (!silent && qe_skip_spaces(&p)) eb_format_message(qs, "*messages*", p); if (beep) { + stop_macro(qs); if (s) dpy_sound_bell(s->screen); } @@ -7556,6 +7811,7 @@ static void kill_buffer_confirm_cb(void *opaque, char *reply, CompletionDef *com void do_kill_buffer(EditState *s, const char *bufname, int force) { + QEmacsState *qs = s->qe_state; char buf[1024]; EditBuffer *b; @@ -7565,6 +7821,7 @@ void do_kill_buffer(EditState *s, const char *bufname, int force) } else { /* if modified and associated to a filename, then ask */ if (!force && b->modified && b->filename[0] != '\0') { + stop_macro(qs); snprintf(buf, sizeof(buf), "Buffer %s modified; kill anyway? (yes or no) ", bufname); minibuffer_edit(s, NULL, buf, NULL, NULL, @@ -8306,6 +8563,7 @@ void do_exit_qemacs(EditState *s, int argval) is->state = QS_ASK; is->b = qs->first_buffer; + stop_macro(qs); qe_grab_keys(quit_key, is); quit_examine_buffers(is); } @@ -8341,6 +8599,7 @@ static void quit_examine_buffers(QuitState *is) /* now asks for confirmation or exit directly */ if (is->modified) { + stop_macro(qs); minibuffer_edit(qs->active_window, NULL, "Modified buffers exist; exit anyway? (yes or no) ", NULL, NULL, quit_confirm_cb, NULL); @@ -8389,9 +8648,9 @@ static void quit_key(void *opaque, int ch) break; case KEY_CTRL('g'): /* abort */ + qe_ungrab_keys(); put_status(NULL, "\007Quit"); dpy_flush(&global_screen); - qe_ungrab_keys(); return; default: /* get another key */ @@ -10236,6 +10495,8 @@ int main(int argc, char **argv) free_font_cache(&global_screen); // before dpy_close()? qe_free(&qs->buffer_cache); qs->buffer_cache_size = qs->buffer_cache_len = 0; + clear_macro(qs); + qe_free(&qs->macro_format); } #endif return 0; diff --git a/qe.h b/qe.h index a1e7b0f..36c8b35 100644 --- a/qe.h +++ b/qe.h @@ -973,8 +973,11 @@ struct QEmacsState { int executing_macro; unsigned short *macro_keys; int nb_macro_keys; + int nb_macro_keys_run; int macro_keys_size; int macro_key_index; /* -1 means no macro is being executed */ + int macro_counter; + char *macro_format; int ungot_key; int last_key; int last_argval; @@ -1595,7 +1598,7 @@ void window_display(EditState *s); void do_prefix_argument(EditState *s, int key); void do_start_kbd_macro(EditState *s); void do_end_kbd_macro(EditState *s); -void do_call_last_kbd_macro(EditState *s); +void do_call_last_kbd_macro(EditState *s, int argval); void do_execute_macro_keys(EditState *s, const char *keys); void do_define_kbd_macro(EditState *s, const char *name, const char *keys, const char *key_bind); diff --git a/qeconfig.h b/qeconfig.h index 78d4db3..97d29a3 100644 --- a/qeconfig.h +++ b/qeconfig.h @@ -2,7 +2,7 @@ * QEmacs, tiny but powerful multimode editor * * Copyright (c) 2000-2001 Fabrice Bellard. - * Copyright (c) 2000-2023 Charlie Gordon. + * Copyright (c) 2000-2024 Charlie Gordon. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -291,21 +291,54 @@ static const CmdDef basic_commands[] = { "none", "An unknown key was pressed", do_unknown_key) + CMD0( "start-kbd-macro", "C-x (", "Start recording a keyboard macro", do_start_kbd_macro) CMD0( "end-kbd-macro", "C-x )", "End recording a keyboard macro", do_end_kbd_macro) - CMD0( "call-last-kbd-macro", "C-x e, C-\\", + CMD2( "call-last-kbd-macro", "C-x e, C-\\", "Run the last recorded keyboard macro", - do_call_last_kbd_macro) + do_call_last_kbd_macro, ESi, "p") CMD2( "define-kbd-macro", "", "Define a named keyboard macro", do_define_kbd_macro, ESsss, "s{Macro name: }[command]" "s{Macro keys: }|macrokeys|" "s{Bind to key: }[key]") +#ifndef CONFIG_TINY + CMD2( "edit-last-kbd-macro", "C-x *, C-x C-k C-e, C-x C-k e", + "Edit the last keyboard macro", + do_edit_last_kbd_macro, ESs, + "s{Macro keys: }|macrokeys|") + CMD2( "name-last-kbd-macro", "C-x C-k C-n, C-x C-k n", + "Define a named command from the last keyboard macro", + do_name_last_kbd_macro, ESs, + "s{Macro name: }[command]") + CMD2( "insert-kbd-macro", "C-x C-k i", + "Insert in buffer the definition of kbd macro MACRONAME, as qescript code", + do_insert_kbd_macro, ESs, + "*s{Macro name: }[command]") + CMD2( "read-kbd-macro", "C-x C-k r", + "Read the region as a keyboard macro definition", + do_read_kbd_macro, ESii, "m" "d") + CMD2( "macro-add-counter", "C-x C-k C-a, C-x C-k a", + "Add the value of numeric prefix arg (prompt if missing) to `macro-counter`", + do_macro_add_counter, ESi, + "N{Macro increment: }") + CMD2( "macro-set-counter", "C-x C-k C-c, C-x C-k c", + "Set the value of `macro-counter' to ARG, or prompt for value if no argument", + do_macro_set_counter, ESi, + "N{Macro counter: }") + CMD2( "macro-insert-counter", "C-x C-k TAB, C-x C-k =", + "Insert current value of `macro-counter`, then increment it by ARG", + do_macro_insert_counter, ESi, "*p") + CMD2( "macro-set-format", "C-x C-k C-f, C-x C-k f", + "Set the printf-like format for `macro-insert-counter`", + do_macro_set_format, ESs, + "s{Format: }|macroformat|") +#endif /* set/unset key? */ CMD3( "global-set-key", "f4", "Register a global key binding", diff --git a/search.c b/search.c index 32a4e07..b606f68 100644 --- a/search.c +++ b/search.c @@ -311,6 +311,8 @@ static int eb_search(EditBuffer *b, int dir, int flags, } } +static void isearch_exit(EditState *s, int key); + static int search_abort_func(qe__unused__ void *opaque) { return is_user_input_pending(); @@ -508,9 +510,15 @@ static void isearch_run(ISearchState *is) { /* display search string */ out = buf_init(&outbuf, ubuf, sizeof(ubuf)); - if (is->found_offset < 0 && len > 0) + if (is->found_offset < 0 && len > 0) { + if (is->s->qe_state->macro_key_index >= 0) { + /* if macro is running, abort search and macro */ + isearch_exit(is->s, KEY_RET); + put_status(s, "\007"); + return; + } buf_puts(out, "\007Failing "); - else + } else if (is->search_flags & SEARCH_FLAG_WRAPPED) { buf_puts(out, "Wrapped "); is->search_flags &= ~SEARCH_FLAG_WRAPPED; @@ -825,7 +833,8 @@ static void isearch_key(void *opaque, int key) { } if (is->search_flags & SEARCH_FLAG_ACTIVE) { isearch_run(is); - } else { + } + if (!(is->search_flags & SEARCH_FLAG_ACTIVE)) { /* This should free the ISearchState grab data if allocated */ qe_ungrab_keys(); } diff --git a/util.c b/util.c index fd1cc03..e4cbd70 100644 --- a/util.c +++ b/util.c @@ -1513,6 +1513,28 @@ static const char * const keystr[countof(keycodes)] = { "LB", "RB", "VB", }; +int find_key_suffix(const char *str, char c) +{ + size_t len = strlen(str); + int i; + + if (len >= 2 && str[len - 1] == '-') { + if (str[len - 2] == 'M') + return KEY_META((u8)c); + if (str[len - 2] == 'C') + return KEY_CTRL((u8)c); + if (qe_isalpha((u8)str[len - 2])) + return 1; + } + for (i = 0; i < countof(keycodes); i++) { + const char *key = keystr[i]; + size_t klen = strlen(key) - 1; + if (*key && klen <= len && key[klen] == c && !memcmp(key, str + len - klen, klen)) + return keycodes[i]; + } + return -1; +} + int compose_keys(unsigned int *keys, int *nb_keys) { unsigned int *keyp; @@ -2008,7 +2030,6 @@ char *qe_strdup(const char *str) { */ size_t size = strlen(str) + 1; char *p = (malloc)(size); - if (p) memcpy(p, str, size); return p; diff --git a/util.h b/util.h index 44fd8a5..d5c203d 100644 --- a/util.h +++ b/util.h @@ -665,6 +665,7 @@ void qe_qsort_r(void *base, size_t nmemb, size_t size, void *thunk, /*---- key definitions and functions ----*/ +int find_key_suffix(const char *str, char c); int compose_keys(unsigned int *keys, int *nb_keys); int strtokey(const char **pp); int strtokeys(const char *keystr, unsigned int *keys, int max_keys, const char **endp); @@ -684,17 +685,17 @@ int buf_quote_byte(buf_t *out, unsigned char ch); - E200: M- modifier - E400: S- modifier (S- modified non function keys unavailable from terminal) - E800: C- modifier (C- modified non function keys unavailable from terminal) - - F000..1FFFF: Unicode code points + - F000..1FFFFF: Unicode code points If using 32-bit key codes, could use higher modifier bits: Make these bits consistent X11 modifier keys. - - 010000: Shift (S-) - ShiftMask (1<<0) - - 020000: Lock (L-) - LockMask (1<<1) - - 040000: Ctrl (C-) - ControlMask (1<<2) - - 080000: Meta (M-) - Mod1Mask (1<<3) - - 100000: Alt (A-) - Mod2Mask (1<<4) - - 200000: Super (s-) - Mod3Mask (1<<5) - - 400000: Hyper (H-) - Mod4Mask (1<<6) - - 800000: Extra (E-) - Mod5Mask (1<<7) +- - 00200000: Shift (S-) - ShiftMask (1<<0) +- - 00400000: Lock (L-) - LockMask (1<<1) +- - 00800000: Ctrl (C-) - ControlMask (1<<2) +- - 01000000: Meta (M-) - Mod1Mask (1<<3) +- - 02000000: Alt (A-) - Mod2Mask (1<<4) +- - 04000000: Super (s-) - Mod3Mask (1<<5) +- - 08000000: Hyper (H-) - Mod4Mask (1<<6) +- - 10000000: Extra (E-) - Mod5Mask (1<<7) X11 function keys are mapped in the range FF00..FFFF, including prefix keys diff --git a/variables.c b/variables.c index 56655f3..8e10d3b 100644 --- a/variables.c +++ b/variables.c @@ -84,6 +84,8 @@ static VarDef var_table[] = { "Set to prevent automatic backups of modified files" ) S_VAR( "c-label-indent", c_label_indent, VAR_NUMBER, VAR_RW_SAVE, "Number of columns to adjust indentation of C labels." ) + S_VAR( "macro-counter", macro_counter, VAR_NUMBER, VAR_RW_SAVE, + "Macro counter: insert with C-x C-k TAB, set with C-x C-k C-c." ) //B_VAR( "screen-charset", charset, VAR_NUMBER, VAR_RW, NULL )