diff --git a/README.md b/README.md index 08b44c8..fee4dc7 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,8 @@ The implementation is messy, full of hacky C pointer arithmetic, and probably of * **Substitute:** `:%s/old/new/[g]` and `:s/old/new/`, including patterns and replacements that contain spaces. * **Indenting:** `>>` and `<<` with `:set sw=N`. * **Window chords:** `C-w w`/`C-w C-w` cycle focus, `C-w h/j/k/l` jump directionally — vim-style, with `C-w ESC` cancelling the chord. They work from text buffers *and* from the Dired pane (which isn't vi-modal; the binding table covers it), so `C-x C-d` followed by `C-w w` ping-pongs focus between directory and buffer. -* **Ex command line:** `:w [file]`, `:q`, `:q!`, `:wq`, `:x`, `:` (goto line), `:set sw=N`, `:%s/.../.../[g]`, `:quotes [N]` (random coder/philosophy quote popup, `N` is 0-based index, `q` to close — including from normal mode if focus moved elsewhere). +* **Ex command line:** `:w [file]`, `:q`, `:q!`, `:wq`, `:x`, `:` (goto line), `:set sw=N`, `:%s/.../.../[g]`, `:wisdom [N]` (`:quotes` alias; random coder/philosophy quote popup, `N` is 0-based index, `q` to close — including from normal mode if focus moved elsewhere). +* **Wisdom over help:** `F1` or `M-x wisdom` shows a random quote. The old QEmacs `C-h` help tree is gone (it never fired on standard terminals anyway — `^H` arrives as `KEY_DEL`; all `describe-*`/manual/faq commands remain on `M-x` with no key bindings); `help-for-help` was deleted. * **Terminal theme auto-detect:** queries your terminal's foreground/background via OSC 10/11 at startup and adopts the palette, falling back to peach-on-black if the terminal doesn't answer. ### Not Yet (the roadmap) diff --git a/extras.c b/extras.c index 73be3fc..0a3e29e 100644 --- a/extras.c +++ b/extras.c @@ -3758,7 +3758,10 @@ static const CmdDef extra_commands[] = { "Swap words before and after point", do_transpose, ESi, "*" "v", CMD_TRANSPOSE_WORDS) - CMD2( "show-bindings", "C-h B, f5", + /* NOTE: the C-h help key tree was removed in favor of the `wisdom` + * command (F1, M-x wisdom). The commands below remain + * available via M-x with no default key bindings (except f5). */ + CMD2( "show-bindings", "f5", "Show current bindings for a given command", do_show_bindings, ESs, "s{Show bindings of command: }[command]|command|") @@ -3774,55 +3777,55 @@ static const CmdDef extra_commands[] = { "v", 1) // XXX: the commands below should use do_load_file_from_path() - CMDx( "qemacs-hello", "C-h h", + CMDx( "qemacs-hello", "", "Create a test buffer with various charsets", do_qemacs_hello) - CMDx( "qemacs-manual", "C-h m", + CMDx( "qemacs-manual", "", "Show the Quick Emacs manual", do_qemacs_manual) - CMDx( "qemacs-faq", "C-h C-f", + CMDx( "qemacs-faq", "", "Show the Quick Emacs FAQ", do_qemacs_faq) - CMD0( "about-qemacs", "C-h q", + CMD0( "about-qemacs", "", "Display information about Quick Emacs", do_about_qemacs) - CMD2( "apropos", "C-h a, C-h C-a", + CMD2( "apropos", "", "List commands and variables matching a topic", do_apropos, ESs, "s{Apropos: }[symbol]|apropos|") - CMD2( "wall-chart", "C-h w", + CMD2( "wall-chart", "", "List local and global key bindings as a wall chart", do_wall_chart, ESi, "p") - CMD2( "describe-bindings", "C-h b", + CMD2( "describe-bindings", "", "List local and global key bindings", do_describe_bindings, ESi, "p") - CMD2( "describe-buffer", "C-h C-b", + CMD2( "describe-buffer", "", "Show information about the current buffer", do_describe_buffer, ESi, "p") - CMD2( "describe-function", "C-h f", + CMD2( "describe-function", "", "Show information and bindings for a command", do_describe_function, ESs, "s{Describe function: }[command]|command|") - CMD2( "describe-screen", "C-h s, C-h C-s", + CMD2( "describe-screen", "", "Show information about the current screen", do_describe_screen, ESi, "p") - CMD2( "describe-window", "C-h C-w", + CMD2( "describe-window", "", "Show information about the current window", do_describe_window, ESi, "p") - CMD2( "describe-prefix-bindings", "C-h p", + CMD2( "describe-prefix-bindings", "", "Display a list of commands with a common binding prefix", do_describe_prefix_bindings, ESs, "s{Binding prefix: }") - CMD2( "describe-C-h", "C-h ?", + CMD2( "describe-C-h", "", "Display a summary of C-h commands", do_describe_prefix_bindings, ESs, "@{C-h}") - CMD2( "describe-C-c", "C-c ?, C-c C-h", + CMD2( "describe-C-c", "", "Display a summary of C-c commands", do_describe_prefix_bindings, ESs, "@{C-c}") - CMD2( "describe-C-x", "C-x ?, C-x C-h", + CMD2( "describe-C-x", "", "Display a summary of C-x commands", do_describe_prefix_bindings, ESs, "@{C-x}") - CMD2( "describe-C-x-RET", "C-x RET ?, C-x RET C-h", + CMD2( "describe-C-x-RET", "", "Display a summary of C-x RET commands", do_describe_prefix_bindings, ESs, "@{C-x RET}") diff --git a/modes/fractal.c b/modes/fractal.c index df693b2..8c4283e 100644 --- a/modes/fractal.c +++ b/modes/fractal.c @@ -1246,7 +1246,7 @@ static void do_mandelbrot_test(EditState *s, int argval) { } static const CmdDef fractal_global_commands[] = { - CMD2( "mandelbrot-test", "C-h m", + CMD2( "mandelbrot-test", "", "Explore the Mandelbrot set in fractal-mode", do_mandelbrot_test, ESi, "#" "p") }; diff --git a/modes/vi.c b/modes/vi.c index 2b15b33..c8f7527 100644 --- a/modes/vi.c +++ b/modes/vi.c @@ -351,15 +351,15 @@ static int vi_parse_substitute(EditState *s, const char *cmd) return 0; } -/* Quotes popup: static table of open-source / philosophy quotes. +/* Wisdom popup: static table of open-source / philosophy quotes. * Display format is always "\"text\" - author" (double quotes outside, * single quotes for any quotation inside the text). Index N is 0-based. */ typedef struct { const char *text; const char *author; -} ViQuote; +} ViWisdom; -static const ViQuote vi_quotes[] = { +static const ViWisdom vi_wisdom[] = { { "Talk is cheap. Show me the code.", "Linus Torvalds" }, { "Every program attempts to expand until it can read mail. Those that can't do this, are replaced by ones that can.", "Jamie Zawinski" }, { "Programs must be written for people to read, and only incidentally for machines to execute.", "Harold Abelson" }, @@ -367,6 +367,7 @@ static const ViQuote vi_quotes[] = { { "The best way to predict the future is to invent it.", "Alan Kay" }, { "Free software is a matter of liberty, not price; think of 'free speech', not 'free beer'.", "Richard M. Stallman" }, { "Given enough eyeballs, all bugs are shallow.", "Eric S. Raymond" }, + { "Abstraction is for complexity; silicon is for speed.", "MARKMENTAL" }, { "Stay hungry, stay foolish.", "Stewart Brand" }, { "The question of whether a computer can think is no more interesting than the question of whether a submarine can swim.", "Edsger W. Dijkstra" }, { "First, solve the problem. Then, write the code.", "John Johnson" }, @@ -375,22 +376,22 @@ static const ViQuote vi_quotes[] = { { "A primary cause of complexity is that software vendors uncritically adopt almost any feature that users want.", "Niklaus Wirth" }, }; -static int vi_quotes_seeded = 0; +static int vi_wisdom_seeded = 0; /* Show one quote in a help-style popup (q to close, buffer navigation). - * arg: "" for random, otherwise a 0-based index into vi_quotes. */ -static void do_vi_quotes(EditState *s, const char *arg) + * arg: "" for random, otherwise a 0-based index into vi_wisdom. */ +static void do_vi_wisdom(EditState *s, const char *arg) { - int n = countof(vi_quotes); + int n = countof(vi_wisdom); int idx; EditBuffer *b; while (*arg == ' ' || *arg == '\t') arg++; if (*arg == '\0') { - if (!vi_quotes_seeded) { + if (!vi_wisdom_seeded) { srand((unsigned)time(NULL)); - vi_quotes_seeded = 1; + vi_wisdom_seeded = 1; } idx = rand() % n; } else { @@ -399,40 +400,49 @@ static void do_vi_quotes(EditState *s, const char *arg) while (*end == ' ' || *end == '\t') end++; if (end == arg || *end != '\0' || val < 0 || val >= n) { - put_status(s, "Invalid quotes index '%s' (0..%d)", arg, n - 1); + put_status(s, "Invalid wisdom index '%s' (0..%d)", arg, n - 1); return; } idx = (int)val; } - b = qe_new_buffer(s->qs, "*Quotes*", BF_SYSTEM | BF_UTF8); + b = qe_new_buffer(s->qs, "*Wisdom*", BF_SYSTEM | BF_UTF8); if (!b) return; eb_printf(b, "\"%s\" - %s\n\n-- press q to close --\n", - vi_quotes[idx].text, vi_quotes[idx].author); + vi_wisdom[idx].text, vi_wisdom[idx].author); { - EditState *e = show_popup(s, b, "Quotes"); + EditState *e = show_popup(s, b, "Wisdom"); if (e) e->wrap = WRAP_WORD; /* long quotes word-wrap instead of truncating */ } } -/* Find the topmost Quotes popup, if any. Used so bare `q` in vi normal +/* Find the topmost Wisdom popup, if any. Used so bare `q` in vi normal * mode can dismiss it even after focus moved back to a text window * (e.g. via C-w w / C-x o), where the popup's own `q` binding would * otherwise never fire because dispatch is active-window-only. */ -static EditState *vi_find_quotes_popup(QEmacsState *qs) +static EditState *vi_find_wisdom_popup(QEmacsState *qs) { EditState *e, *found = NULL; for (e = qs->first_window; e; e = e->next_window) { if ((e->flags & WF_POPUP) && e->b && - !strcmp(e->b->name, "*Quotes*")) { + !strcmp(e->b->name, "*Wisdom*")) { found = e; } } return found; } +/* Global `wisdom` command body: show a random quote (F1, M-x wisdom). + * NOTE: no C-h binding on purpose: on standard terminals the tty layer + * translates ^H to KEY_DEL (KBS_CONTROL_H), so a "C-h ..." binding would + * never fire -- the same reason the old C-h help tree felt dead. */ +static void do_wisdom(EditState *s) +{ + do_vi_wisdom(s, ""); +} + /* Ex command prompt callback */ static void vi_ex_callback(void *opaque, char *buf, CompletionDef *completion) { @@ -504,8 +514,8 @@ static void vi_ex_callback(void *opaque, char *buf, CompletionDef *completion) (strstart(cmd, "s", &arg) && *arg == '/')) { if (vi_parse_substitute(s, cmd) < 0) put_status(s, "Invalid substitute command"); - } else if (strcmp(cmd, "quotes") == 0) { - do_vi_quotes(s, arg); + } else if (strcmp(cmd, "wisdom") == 0 || strcmp(cmd, "quotes") == 0) { + do_vi_wisdom(s, arg); } else if (cmd[0] >= '0' && cmd[0] <= '9') { /* : goes to that line */ do_goto_line(s, strtol(cmd, NULL, 10), 0); @@ -808,12 +818,12 @@ static int vi_normal_key(EditState *s, int key) minibuffer_edit(s, "", ":", NULL, NULL, vi_ex_callback, s); return 1; case 'q': { - /* Dismiss a stranded Quotes popup when focus has moved back to + /* Dismiss a stranded Wisdom popup when focus has moved back to * a text window. Guard on no pending / no visual so `dq` and * visual flows keep their existing behavior. */ EditState *qp; if (!s->vi_visual_active && !s->vi_pending) { - qp = vi_find_quotes_popup(s->qs); + qp = vi_find_wisdom_popup(s->qs); if (qp) { do_popup_exit(qp); return 1; @@ -886,6 +896,7 @@ static const CmdDef vi_commands[] = { CMD0("vi-mode", "", "Toggle Vi/Evil modal editing", do_vi_mode) CMD0("vi-normal-mode", "", "Enter Vi normal mode", do_vi_normal_mode) CMD0("vi-insert-mode", "", "Enter Vi insert mode", do_vi_insert_mode) + CMD0("wisdom", "f1", "Show a random wisdom quote", do_wisdom) }; static int vi_init(QEmacsState *qs) diff --git a/qe-doc.html b/qe-doc.html index e65d945..d28e851 100644 --- a/qe-doc.html +++ b/qe-doc.html @@ -272,15 +272,15 @@ is simply repeated N times. [Index] [ ? ] -

3.2 Help

+

3.2 Wisdom

-

You can press C-h b to have the list of all the currently active -bindings, including the ones of the current editing mode. +

Press F1 for a random wisdom quote, +or M-x wisdom. In vi normal mode, :wisdom [N] shows +quote number N (0-based), :quotes is kept as an alias. +Press q to close the popup.

-
C-h C-h, F1             : help-for-help
-C-h b                   : describe-bindings
-C-h c                   : describe-key-briefly
+
F1                    : wisdom
 

@@ -524,12 +524,10 @@ A-= : compare-windows [Index] [ ? ] -

3.10 Help functions

+

3.10 Wisdom

-
C-h C-h, f1             : help-for-help
-C-h b                   : describe-bindings
-C-h c, C-h k            : describe-key-briefly
+
F1                    : wisdom
 

diff --git a/qe-doc.texi b/qe-doc.texi index 03f6d39..c11387d 100644 --- a/qe-doc.texi +++ b/qe-doc.texi @@ -132,15 +132,15 @@ optional number is used to provide a numeric argument to the commands which can handle them. If the command cannot handle a numerical argument, it is simply repeated @code{N} times. -@section Help +@section Wisdom -You can press @kbd{C-h b} to have the list of all the currently active -bindings, including the ones of the current editing mode. +Press @kbd{F1} for a random wisdom quote, +or @kbd{M-x wisdom}. In vi normal mode, @code{:wisdom [N]} shows +quote number @code{N} (0-based), @code{:quotes} is kept as an alias. +Press @kbd{q} to close the popup. @example -C-h C-h, F1 : help-for-help -C-h b : describe-bindings -C-h c : describe-key-briefly +F1 : wisdom @end example @section Simple commands @@ -248,12 +248,10 @@ C-x f : toggle-full-screen A-= : compare-windows @end example -@section Help functions +@section Wisdom @example -C-h C-h, f1 : help-for-help -C-h b : describe-bindings -C-h c, C-h k : describe-key-briefly +F1 : wisdom @end example @section International diff --git a/qe.c b/qe.c index 1630a2f..f0dc177 100644 --- a/qe.c +++ b/qe.c @@ -10728,23 +10728,6 @@ EditBuffer *new_help_buffer(EditState *s) return qe_new_buffer(s->qs, "*Help*", flags); } -void do_help_for_help(EditState *s) -{ - EditBuffer *b; - - b = new_help_buffer(s); - if (!b) - return; - - eb_style_puts(b, DESCRIBE_STYLE_HEAD, "QEmacs help for help - Press q to quit:\n\n"); - eb_puts(b, - "C-h C-h Show this help\n" - "C-h b Display table of all key bindings\n" - "C-h c Describe key briefly\n" - ); - show_popup(s, b, "QEmacs help for help"); -} - #ifdef CONFIG_WIN32 void qe_event_init(QEmacsState *qs) @@ -12166,22 +12149,22 @@ static const CmdDef basic_commands[] = { #endif /*---------------- Help ----------------*/ + /* NOTE: the C-h help tree was removed in favor of the `wisdom` + * command (F1, M-x wisdom). The describe/trace commands + * below remain available via M-x with no default key bindings. */ - CMD2( "toggle-trace-mode", "C-h d", + CMD2( "toggle-trace-mode", "", "Enable or disable trace mode: show the *Trace* buffer with debugging info", do_toggle_trace_mode, ESi, "P") - CMD2( "set-trace-options", "C-h t", + CMD2( "set-trace-options", "", "Select the trace options: all, none, command, debug, emulate, shell, tty, pty", do_set_trace_options, ESs, "s{Trace options: }[trace]|trace|") - CMD2( "describe-key-briefly", "C-h c, C-h k, f6", + CMD2( "describe-key-briefly", "f6", "Describe a key binding", do_describe_key_briefly, ESsi, "s{Describe key: }|keys|" "P") - CMD0( "help-for-help", "C-h C-h, f1", - "Show the qemacs help window", - do_help_for_help) /*---------------- International ----------------*/ @@ -12655,9 +12638,9 @@ static int qe_init(QEmacsState *qs, int argc, char **argv) } #endif #ifdef CONFIG_TINY - put_status(s, "Tiny Evil QEmacs %s - Press F1 for help", QE_VERSION); + put_status(s, "Tiny Evil QEmacs %s", QE_VERSION); #else - put_status(s, "Evil QEmacs %s - Press F1 for help", QE_VERSION); + put_status(s, "Evil QEmacs %s", QE_VERSION); b = qe_find_buffer_name(qs, "*errors*"); if (b != NULL) { show_popup(s, b, "Errors"); diff --git a/qe.h b/qe.h index 230e540..2b659ce 100644 --- a/qe.h +++ b/qe.h @@ -1793,7 +1793,6 @@ void do_hide_window(EditState *s, int set); void do_delete_hidden_windows(EditState *s); void do_describe_key_briefly(EditState *s, const char *keystr, int argval); EditBuffer *new_help_buffer(EditState *s); -void do_help_for_help(EditState *s); void qe_event_init(QEmacsState *qs); void window_get_min_size(EditState *s, int *w_ptr, int *h_ptr); int window_resize(EditState *s, int target_w, int target_h); diff --git a/search.c b/search.c index 058f460..8eccd42 100644 --- a/search.c +++ b/search.c @@ -1545,7 +1545,7 @@ static const CmdDef isearch_commands[] = { CMD2( "isearch-exit", "RET, QUIT", "Exit isearch, leave point at location found", isearch_exit, ESi, "k") - CMDx( "isearch-mode-help", "f1, C-h", + CMDx( "isearch-mode-help", "", "show the help page for isearch", isearch_mode_help, ES, "") CMD2( "isearch-printing-char", "TAB, C-j", diff --git a/tests/test_vi.py b/tests/test_vi.py index dc3874d..f1c45cf 100644 --- a/tests/test_vi.py +++ b/tests/test_vi.py @@ -287,9 +287,14 @@ def run_tqe(binary, keys, content, timeout, keep_dir=None, want_path=None): # in one chunk while it is busy drawing, and the terminal input layer # then composes ESC + following byte into a META key, which changes # what the vi layer sees. + # A test may pass a list of byte strings instead: each element is + # written atomically, which is how real terminals deliver multi-byte + # sequences such as function keys (ESC O P) or META chords (ESC x). + segs = keys if isinstance(keys, list) else [keys[i:i + 1] + for i in range(len(keys))] idle = 0.0 - for i in range(len(keys)): - os.write(fd, keys[i:i + 1]) + for seg in segs: + os.write(fd, seg) idle = 0.0 while idle < 0.08: data = read_avail(0.02) @@ -351,15 +356,22 @@ class T: def __init__(self, name, keys, init, want, cat='general', status=None, timeout=5.0, - want_path=None): + want_path=None, want_out=None): self.name = name - self.keys = keys + self.keys = keys # bytes (sent byte-at-a-time) or list of + # bytes (each element written atomically, + # for F-keys / META chords) self.init = init self.want = want self.cat = cat self.status = status # str or list of str, or None self.timeout = timeout self.want_path = want_path # may contain {DIR} + self.want_out = want_out # bytes or list of bytes that must + # appear in the ANSI-stripped + # terminal output (for popups, + # whose body never hits the file + # or the status line) # ------------------------------------------------------------------------- @@ -529,11 +541,36 @@ TESTS = [ cat='status', status='-- NORMAL --'), T('status_insert', b'i\x1b:wq\r', 'x\n', b'x\n', cat='status', status=['-- INSERT --', '-- NORMAL --']), + + # ---- wisdom quotes ---- + T('wisdom_ex', b':wisdom 0\rq:wq\r', 'hello\n', b'hello\n', + cat='wisdom', + want_out=[b'Wisdom', b'Talk is cheap', b'press q to close']), + T('wisdom_alias', b':quotes 0\rq:wq\r', 'hello\n', b'hello\n', + cat='wisdom', + want_out=[b'Wisdom', b'Talk is cheap']), + T('wisdom_bad_index', b':wisdom 99\r:wq\r', 'hello\n', b'hello\n', + cat='wisdom', status='Invalid wisdom index'), + # F1 arrives as one SS3 sequence on real terminals; send atomically + # (byte-at-a-time would let vi eat the leading ESC). + T('wisdom_f1', [b'\x1bOP', b'q:wq\r'], 'hello\n', b'hello\n', + cat='wisdom', + want_out=[b'Wisdom', b'press q to close']), + # M-x itself is unreachable as a META chord in evil mode (the vi + # layer consumes META-x as `x`), but F2 is bound to execute-command, + # so drive M-x wisdom through F2 (vt100 SS3 Q, sent atomically). + T('wisdom_mx', [b'\x1bOQ', b'wisdom\r', b'q:wq\r'], + 'hello\n', b'hello\n', + cat='wisdom', + want_out=[b'Wisdom', b'press q to close']), ] def run_one(t, binary, workdir, default_timeout): - keys = t.keys.replace(b'{DIR}', workdir.encode()) + if isinstance(t.keys, list): + keys = [k.replace(b'{DIR}', workdir.encode()) for k in t.keys] + else: + keys = t.keys.replace(b'{DIR}', workdir.encode()) want_path = t.want_path.replace('{DIR}', workdir) if t.want_path else None file_bytes, term_out, frames, status = run_tqe( binary, keys, t.init, t.timeout or default_timeout, @@ -553,6 +590,13 @@ def run_one(t, binary, workdir, default_timeout): if not any(w in line for line in frames): errors.append('status %r not seen on the status line' % (w,)) + if t.want_out: + clean = strip_ansi(term_out) + wanted = t.want_out if isinstance(t.want_out, list) else [t.want_out] + for w in wanted: + if w not in clean: + errors.append('output %r not seen on the terminal' + % (w,)) return errors diff --git a/variables.c b/variables.c index fd8484e..32d422a 100644 --- a/variables.c +++ b/variables.c @@ -703,11 +703,11 @@ static const CmdDef var_commands[] = { "Set the value of a variable", do_set_variable, ESss, "s{Set variable: }[variable]|variable|s{to value: }[value]|value|") - CMD2( "describe-variable", "C-h v", + CMD2( "describe-variable", "", "Show information for a variable", do_describe_variable, ESs, "s{Describe variable: }[variable]|variable|") - CMD0( "show-all-variables", "C-h C-v", + CMD0( "show-all-variables", "", "Show value and information for all variables", do_show_all_variables) }; diff --git a/x11.c b/x11.c index 50c569f..858c15f 100644 --- a/x11.c +++ b/x11.c @@ -2036,7 +2036,7 @@ static void x11_list_fonts(EditState *s, int argval) } static const CmdDef x11_commands[] = { - CMD2( "x11-list-fonts", "C-h C-f", + CMD2( "x11-list-fonts", "", "List the X11 fonts in a popup window", x11_list_fonts, ESi, "P") };