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:
parent
84393d6c4f
commit
4be625f5c4
11 changed files with 415 additions and 53 deletions
2
TODO.md
2
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")
|
||||
|
|
|
|||
6
buffer.c
6
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;
|
||||
}
|
||||
|
||||
|
|
|
|||
13
cutils.h
13
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);
|
||||
|
|
|
|||
33
qe-manual.md
33
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.
|
||||
|
|
|
|||
311
qe.c
311
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;
|
||||
|
|
|
|||
5
qe.h
5
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);
|
||||
|
|
|
|||
39
qeconfig.h
39
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",
|
||||
|
|
|
|||
15
search.c
15
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();
|
||||
}
|
||||
|
|
|
|||
23
util.c
23
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;
|
||||
|
|
|
|||
19
util.h
19
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
|
||||
|
||||
|
|
|
|||
|
|
@ -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 )
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue