Improve macro handling

* add `macro-counter` variable
* add `QEState` fields `nb_macro_keys_run`, `macro_counter`, `macro_format`.
* add `int find_key_suffix(const char *str, char c)`
* pass `argval` to `do_call_last_kbd_macro` to handle early exit
* add `clear_macro`, `stop_macro`
* add `int check_format_string(const char *fmt, const char *template)`
* do not use `alloca` for `eb_vprintf`
* add `dbuf_str(db)`: null terminate the buffer and return a string pointer.
* add macro commands:
  `edit-last-kbd-macro`, `name-last-kbd-macro`, `insert-kbd-macro`,
  `read-kbd-macro`, `macro-add-counter`, `macro-set-counter`,
  `macro-insert-counter`, `macro-set-format`
* `start-keyboard-macro` restarts macro learning mode if learning already
* add documentation on macro commands
* stop macros upon errors (when ringing the bell)
* stop macros if prompting the user for confirmation
This commit is contained in:
Charlie Gordon 2024-07-25 14:08:13 +02:00
commit 4be625f5c4
11 changed files with 415 additions and 53 deletions

View file

@ -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")

View file

@ -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;
}

View file

@ -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);

View file

@ -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.

311
qe.c
View file

@ -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;

5
qe.h
View file

@ -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);

View file

@ -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",

View file

@ -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();
}

23
util.c
View file

@ -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;

19
util.h
View file

@ -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

View file

@ -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 )