reddit / emacs

Org Speed Keys! BOOM! Great org-mode's feature! And a good OPPORTUNITY to talk about self-insert-command

tonyaldon · 2022-03-22 17:51
(This post in Org format -> [https://github.com/tonyaldon/posts](https://github.com/tonyaldon/posts)) Hey Emacsers, How are you doing? This post is "pretty" special to me because I can still remember and feel: 1. the first time I discovered Org Speed Keys and, 2. the first time I took a look at its implementation. It was two AHA moments, one in my Emacs journey and the other in my Elisp journey. I hope you'll enjoy it :) Table of Contents 1. Org Speed Keys 2. How does org-mode implement Org Speed keys? 1. remapping `self-insert-command` 2. if it is magic, this is `org-self-insert-command` 3. `org-self-insert-command` implementation 4. `org-speed-command-activate` 3. `self-insert-command` 1. Bindings of printing characters to `self-insert-command` in the keymap `global-map` 2. Remapping `self-insert-command` to `undefined` in `Info-mode`, `help-mode` and `special-mode` 3. Best function name ever: `bitch_at_user` You can get the source code of org-mode and emacs by running the following commands: git clone https://git.savannah.gnu.org/git/emacs/org-mode.git git clone git://git.sv.gnu.org/emacs.git # Org Speed Keys Org speed keys is not a minor mode but an org-mode feature that can be turned on by setting the variable `org-use-speed-commands` to `t`. It allows when the cursor is at a the beginning of a heading to call a command by pressing a single "printing" key (keys that would normally insert a character in the buffer). For instance, if we are at the beginning of a heading, and we press `n`, the cursor moves to the next visible heading (or stays at the current heading if it's the last one in the buffer) if we use the default bindings provided by org-mode in the variable `org-speed-commands`. It is "almost" the same that pressing `C-c C-n` but in SHORTER. Isn't it super cool??? If you've never tried it, go for it. Just run: M-x eval-expression RET (setq org-use-speed-commands t) Open one of your org documents then with the cursor at at the very beginning of a heading press `n` or `p` repeatedly. We can see org speed command bindings, by typing `?` (which call `org-speed-command-help`) with the cursor at the very beginning of a heading. This pops up the following help buffer: Speed commands ============== Outline Navigation ------------------ n (org-speed-move-safe 'org-next-visible-heading) p (org-speed-move-safe 'org-previous-visible-heading) f (org-speed-move-safe 'org-forward-heading-same-level) b (org-speed-move-safe 'org-backward-heading-same-level) F org-next-block B org-previous-block u (org-speed-move-safe 'outline-up-heading) j org-goto g (org-refile '(4)) Outline Visibility ------------------ c org-cycle C org-shifttab org-display-outline-path s org-toggle-narrow-to-subtree k org-cut-subtree = org-columns ... THERE ARE MORE ... ? org-speed-command-help If we want to use our own bindings, we can modify: * the variable `org-speed-commands` since org-mode 9.5, * the variable `org-speed-commands-user` for previous versions. Note: the variable `org-speed-commands-user` has been made obsolete since org-mode 9.5 but it is still supported and should be removed from org-mode 9.6 as we can read in the source code `lisp/org-keys.el`: ;; FIXME: don't check `org-speed-commands-user' past 9.6 # How does org-mode implement Org Speed keys? ## remapping self-insert-command When org-mode is turned on, among other initialization tasks it remaps the command `self-insert-command` to the command `org-self-insert-command` in the keymap `org-mode-map` as we can see in the file `lisp/org-keys.el`: (org-remap org-mode-map 'self-insert-command 'org-self-insert-command 'delete-char 'org-delete-char 'delete-backward-char 'org-delete-backward-char 'kill-line 'org-kill-line 'open-line 'org-open-line 'yank 'org-yank 'comment-dwim 'org-comment-dwim 'move-beginning-of-line 'org-beginning-of-line 'move-end-of-line 'org-end-of-line 'forward-paragraph 'org-forward-paragraph 'backward-paragraph 'org-backward-paragraph 'backward-sentence 'org-backward-sentence 'forward-sentence 'org-forward-sentence 'fill-paragraph 'org-fill-paragraph 'delete-indentation 'org-delete-indentation 'transpose-words 'org-transpose-words) In the case of `self-insert-command`, the preceding remapping can be written like this (this is what `org-remap` does): (define-key org-mode-map [remap self-insert-command] 'org-self-insert-command) As a consequence, in org-mode, when we press a printing key (that would by default insert a character in the buffer), let say we press `n`, the "command loop editor" does several things: 1. it performs a key lookup for `n` in the current active maps and find that `n` is bound to `self-insert-command` (due to the default binding in the keymap `global-map` which is the global map by default), then, 2. it checks if the command `self-insert-command` has a remapping in the current active maps (with the function `command-remapping`), 3. it finds that `self-insert-command` is remapped to `org-self-insert-command` (due to the remapping in the keymap `org-mode-map`) and instead of calling `self-insert-command`, it calls `org-self-insert-command`. Now, the command `org-self-insert-command` has the complete power over the actions to perform. Nothing forces it to insert the character `n` in the buffer. ## if it is magic, this is org-self-insert-command Before talking about how `org-self-insert-command` performs speed keys, let me ask you some questions: * Have you ever noticed that when you modify the title of a headline the tags are automatically re-aligned? Guess what? This is `org-self-insert-command` in action (calling `org-fix-tags-on-the-fly` just after inserting a character). * Have you ever noticed that inserting less characters in a table field than its width doesn't move the right bar of the field? This is `org-self-insert-command` in action. * Did you know that in a table, if you type printing keys just after moving with `TAB`, `S-TAB`, `RET`, the table field is made blank before inserting the characters (with `org-table-auto-blank-field` set to `t`)? This is also `org-self-insert-command` (see [Inside Emacs #6 (part 5) Why is it so fast to edit tables with org-table?](https://www.youtube.com/watch?v=w4wxGOijyZs)). ## org-self-insert-command implementation Let's get back to our example. We've pressed the "printing" key `n` and "the command loop editor" has called `org-self-insert-command`. The command `org-self-insert-command` does the following (assuming we have set `org-use-speed-commands` to `t`): 1. locally set the variable `kv` to the vector `[?n]` corresponding to the key sequence that invoked the command `org-self-insert-command` (the "printing" key `n` we've pressed), 2. run the hook `org-speed-command-hook` "until success" with the argument `"n"` (a string, corresponding to the evaluation of `(make-string 1 (aref [?n] 0))` knowing that `kv` is `[?n]`). Considering that the hook `org-speed-command-hook` is equal to the list `(org-speed-command-activate org-babel-speed-command-activate)` by default, the function `run-hook-with-args-until-success` evaluate successively the forms (stopping at the first one that returns non-nil, and return that value, or return \``nil` if both evaluate to \``nil`): 1. `(org-speed-command-activate "n")`, 2. `(org-babel-speed-command-activate "n")`, 3. Then, this returned value becomes the value of the variable `org-speed-command`, 4. if `org-speed-command` is either a command, a function or a non empty list, `org-speed-command` is called or evaluated (and `org-self-insert-command` returns "maybe" witout inserting `n` in the buffer). If `org-speed-command` is neither a command, a function nor a non empty list, the command `org-self-insert-command` is called with the variable `org-use-speed-commands` locally set to `nil` which leads to skip the first clause of the special form `cond` in the body of `org-self-insert-command`, and `org-self-insert-command` among other "actions" will insert the character `n` in the buffer. Below you can see the parts of the command `org-self-insert-command` related to the speed keys feature that we have just discussed: (defun org-self-insert-command (N) ;; ... (interactive "p") (cond ((and org-use-speed-commands (let ((kv (this-command-keys-vector))) (setq org-speed-command (run-hook-with-args-until-success 'org-speed-command-hook (make-string 1 (aref kv (1- (length kv)))))))) (cond ((commandp org-speed-command) (setq this-command org-speed-command) (call-interactively org-speed-command)) ((functionp org-speed-command) (funcall org-speed-command)) ((and org-speed-command (listp org-speed-command)) (eval org-speed-command)) (t (let (org-use-speed-commands) (call-interactively 'org-self-insert-command))))) ((and ;; ... (org-at-table-p) ;; .... ) (self-insert-command N) ;; ... ) (t ;; ... (self-insert-command N) (org-fix-tags-on-the-fly) ;; ... ))) ## org-speed-command-activate We still have one function to cover: `org-speed-command-activate` (knowing that `org-babel-speed-command-activate` does "almost the same thing" but for the cursor at the beginning of source blocks). Indeed, continuing with our example, if evaluating `(org-speed-command-activate "n")` returns a command (a function or a non empty list) this command will be called instead of inserting the character `"n"`. In the simple case, when `org-use-speed-commands` is set to `t` (it could also be a function), `org-speed-command-activate` checks: 1. if the point (the cursor) is at the beginning of line `(bolp)` and, 2. if this line is a heading `(looking-at org-outline-regexp)`. If true, `org-speed-command-activate` looks for the key `"n"` in the alist `org-speed-commands` defined by default like this: (defcustom org-speed-commands '(("Outline Navigation") ("n" . (org-speed-move-safe 'org-next-visible-heading)) ("p" . (org-speed-move-safe 'org-previous-visible-heading)) ("f" . (org-speed-move-safe 'org-forward-heading-same-level)) ("b" . (org-speed-move-safe 'org-backward-heading-same-level)) ("F" . org-next-block) ("B" . org-previous-block) ;; ... )) and returns the s-exp associated with the key `"n"` which is by default: (org-speed-move-safe 'org-next-visible-heading) As we left aside the case where `org-use-speed-commands` is a function and we anticipate the org-mode version 9.6, we can write a simplified version of `org-speed-command-activate`, that we call `org-speed-command-activate-SIMPLE`, like this: (defun org-speed-command-activate-SIMPLE (keys) (when (and (bolp) (looking-at org-outline-regexp)) (cdr (assoc keys org-speed-commands)))) So, when we presse the key `n` (in org-mode): org-babel-next-src-block 1. if we are at the beginning of a heading, the cursor is moved "safely" to the next visible heading, 2. if we are at the beginning of a source block (due to `org-babel-speed-command-activate` in the hook `org-speed-command-hook`), the cursor is moved to the next source block, 3. and if we were anywhere else, the character `n` is inserted in the buffer. We are done with the mechanism of Org Speed Keys. Let's talk a bit about the command `self-insert-command`. # self-insert-command ## Bindings of printing characters to self-insert-command in the keymap global-map In this section, our goal is to see what are the printing characters that are bound to the command `self-insert-command`. In the info node `M-x eval-expression RET (info "(elisp)Controlling Active Maps")`, we can read the following: -- Variable: global-map This variable contains the default global keymap that maps Emacs keyboard input to commands. The global keymap is normally this keymap. The default global keymap is a full keymap that binds ‘self-insert-command’ to all of the printing characters. Indeed, the variable `global-map` is defined in the file `lisp/subr.el` and is set to be the global keymap (using `use-global-map`) as we can see: (defvar global-map (let ((map (make-keymap))) ;; ... (define-key map "\C-i" #'self-insert-command) (let* ((vec1 (make-vector 1 nil)) (f (lambda (from to) (while (< from to) (aset vec1 0 from) (define-key map vec1 #'self-insert-command) (setq from (1+ from)))))) (funcall f #o040 #o0177) (when (eq system-type 'ms-dos) ;FIXME: Why? (funcall f #o0200 #o0240)) (funcall f #o0240 #o0400)) (define-key map "\C-a" #'beginning-of-line) (define-key map "\C-b" #'backward-char) (define-key map "\C-e" #'end-of-line) (define-key map "\C-f" #'forward-char) ;; ... map) "..." ) (use-global-map global-map) Many bindings in the keymap `global-map` are added in other files. For instance the key sequences `C-g`, `C-u`, `C-k`, `C-w` and `C-y` are added to `global-map` in the file `lisp/bindings.el` like this: (define-key global-map "\C-g" 'keyboard-quit) (define-key global-map "\C-u" 'universal-argument) (define-key global-map "\C-k" 'kill-line) (define-key global-map "\C-w" 'kill-region) (define-key global-map "\C-y" 'yank) Now, let's focus in the `let*` binding in the definition of the keymap `global-map`. In this `let*` binding, we define a function `f` that binds in the local keymap `map` all the characters (represented as integer) between `from` to `to` (excluded), and we apply it to the following limits (excluding the ms-dos case): 1. from `#o040` to `#o0177`, 2. from `#o0240` to `#o0400`. If it is not clear what are the printing characters that are bound to `self-insert-command` in the keymap `global-map` we can "list" them like this: (with-temp-buffer (dotimes (i (- #o0177 #o040)) (insert (+ #o040 i))) (buffer-string)) ;; " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~" (with-temp-buffer (dotimes (i (- #o0400 #o0240)) (insert (+ #o0240 i))) (buffer-string)) ;; " ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ" ## Remapping self-insert-command to undefined in Info-mode, help-mode and special-mode In some cases (like in `Info-mode` and `help-mode`) we want to override all printing keys to be undefined before remapping some of them to specific commands. This is done by remapping in the local keymap the command `self-insert-command` to the function `undefined`. Note that remapping `self-insert-command` to `nil` in the local keymaps won't override `self-insert-command` in the global map as we can read in the info node (`M-x eval-expression RET (info "(elisp)Key Lookup")`): The symbol ‘undefined’ is worth special mention: it means to treat the key as undefined. Strictly speaking, the key is defined, and its binding is the command ‘undefined’; but that command does the same thing that is done automatically for an undefined key: it rings the bell (by calling ‘ding’) but does not signal an error. ‘undefined’ is used in local keymaps to override a global key binding and make the key undefined locally. A local binding of ‘nil’ would fail to do this because it would not override the global binding. In the case of `Info-mode` this is done by calling the function `suppress-keymap` (which overrides all printing keys to be undefined, maps `-` to the command `negative-argument` and the numbers 0,1,..., 9 to the command `digit-argument`), when defining the keymap `Info-mode-map` as follow: (defvar Info-mode-map (let ((map (make-keymap))) (suppress-keymap map) ;; ... (define-key map "1" 'Info-nth-menu-item) (define-key map "2" 'Info-nth-menu-item) ;;... (define-key map "n" 'Info-next) (define-key map "p" 'Info-prev) (define-key map "q" 'quit-window) ;;... map) "...") In the case of `help-mode` this is done by setting the parent keymap of `help-mode-map` to be a composed keymap of `button-buffer-map` and the parent map `special-mode-map` (which overrides all printing keys to be undefined, maps `-` to the command `negative-argument` and the numbers 0,1,..., 9 to the command `digit-argument`), as we can see below: (defvar help-mode-map (let ((map (make-sparse-keymap))) (set-keymap-parent map (make-composed-keymap button-buffer-map special-mode-map)) (define-key map "n" 'help-goto-next-page) (define-key map "p" 'help-goto-previous-page) ;; ... map) "..." ) ## Best function name ever: bitch_at_user `bitch_at_user`!!! What a beautiful name for a function! This is a `C` function we can find in Emacs source code. If I'm not wrong it is called in the body of 3 functions. Mainly, this function when called will ring the bell. This function is defined in the file `src/dispnew.c` as follow: void bitch_at_user (void) { if (noninteractive) putchar (07); else if (!INTERACTIVE) /* Stop executing a keyboard macro. */ { const char *msg = "Keyboard macro terminated by a command ringing the bell"; Fsignal (Quser_error, list1 (build_string (msg))); } else ring_bell (XFRAME (selected_frame)); } Why talking about this function? Because its name is AMAZING and because I encounter it when I was reading the source code of `self-insert-command`. Indeed, the function `bitch_at_user` is called in the body of `self-insert-command` when the command `self-insert-command` is invoked by a key sequence that is not a printing key. For instance, we can trigger it by binding for the key sequence `C-<f1>` (pick any key sequence you are not using) to `self-insert-command` like this: (global-set-key (kbd "C-<f1>") 'self-insert-command) And now, if we press `C-<f1>`, we trigger the function `bitch_at_user` and: 1. we see `Wrong type argument: characterp, C-f1` in the echo area and, 2. depending on the "setting of the bell" (see info node `M-x eval-expression RET (info "(elisp)Beeping")`): a) nothing more happens, b) we hear the bell ringing, or c) we see the screen flashing. AS NO SENTENCE CAN BEAT THE SOURCE CODE, here is the source code of `self-insert-command` defined in the file `src/cmds.c`: DEFUN ("self-insert-command", Fself_insert_command, Sself_insert_command, 1, 2, "(list (prefix-numeric-value current-prefix-arg) last-command-event)", doc: /* Insert the character you type. Whichever character C you type to run this command is inserted. The numeric prefix argument N says how many times to repeat the insertion. Before insertion, `expand-abbrev' is executed if the inserted character does not have word syntax and the previous character in the buffer does. After insertion, `internal-auto-fill' is called if `auto-fill-function' is non-nil and if the `auto-fill-chars' table has a non-nil value for the inserted character. At the end, it runs `post-self-insert-hook'. */) (Lisp_Object n, Lisp_Object c) { CHECK_FIXNUM (n); /* Backward compatibility. */ if (NILP (c)) c = last_command_event; if (XFIXNUM (n) < 0) error ("Negative repetition argument %"pI"d", XFIXNUM (n)); if (XFIXNAT (n) < 2) call0 (Qundo_auto_amalgamate); /* Barf if the key that invoked this was not a character. */ if (!CHARACTERP (c)) bitch_at_user (); else { int character = translate_char (Vtranslation_table_for_input, XFIXNUM (c)); int val = internal_self_insert (character, XFIXNAT (n)); if (val == 2) Fset (Qundo_auto__this_command_amalgamating, Qnil); frame_make_pointer_invisible (SELECTED_FRAME ()); } return Qnil; } WE ARE DONE!!!
sunnyata · 2022-03-22 19:43
Reddit is a terrible format for reading a long blog post, just link to your blog.
tonyaldon · 2022-03-22 20:03
Thank you for your comment u/sunnyata. I quite don't get the difference between reading a "long" blog post in a blog (a website) and reading a "long" blog post on Reddit (also a website). Can you help me understand what's wrong with Reddit? Maybe you can show me some examples of "long" blog posts not on Reddit that you like and have a better format than on Reddit? Maybe your blog? This way I might think about hosting my own blog(website)! Have a nice day.
sunnyata · 2022-03-22 20:21
Why are you putting long in quotes? Don't you think your post is long? Lol. Partly my fault - I'm using old Reddit on my phone, not a good set up for a long read. There's a sidebar so your post is squeezed in to the two thirds of remaining screen. The version on GitHub is much more readable. But I generally think if you've written a tutorial or essay or whatnot just post a link to it, and as far as I can see that's what this site is for... but that's just me. You should *also* have a nice day.
rdmgntn · 2022-03-22 20:37
Nice piece of research. Thanks!
a-c-d-w · 2022-03-22 20:42
I agree with u/sunnyata -- the biggest issues with hosting a "blog" on reddit are as follows: 1. someone else (e.g., reddit) owns your comment. a mod could decide to just delete this post. reddit could go under. etc. 2. there's a character limit on reddit posts --- i think something like, 10,000 characters? that might seem like a lot but you could hit it and have to write it all out again. this seems like interesting content, but i'm loathe to read it b/c it's not on a blog. silly? yes. facts? also yes.
CommonFiveLinedSkink · 2022-03-22 22:10
Hey, thread, that's some weird push-back about someone's effort post being too long. And here I am thinking "Oh thank goodness, it's an instructional that ISN'T just a video of someone mumbling through code." My question: I came to org-emacs from vim, and I'm extremely dependent on evil-mode. How are speed keys different from/better than evil mode? And, it seems pretty obvious that I can't really use speed keys bindings if I'm using evil mode -- am I right?
meesloo · 2022-03-22 22:33
Agreed - I'm just happy to read content on emacs that provides such a deep-dive regardless of form- I haven't found anything quite like these posts. As someone who has used emacs for years but was intimidated to dive into specific things under the hood, these posts are really cool. Thanks again!
putainrespectueuse · 2022-03-23 01:28
this is amazing! thank you for sharing. I had used the single-key mappings in, e.g., the TODO buffer (with hitting 't' to cycle through todo statuses) but had no idea you could do this much stuff
Hooxen · 2022-03-23 04:04
upvoted for “bitch_at_user” that is a god tier name
kaushalmodi · 2022-03-23 04:31
If I may, having your blog can allow you to format the blog how you like. Here's a recent blog post of mine as an example: https://scripter.co/using-emacs-advice-to-silence-messages-from-functions/ I author my content in Org mode and publish to that blog.
Horrih · 2022-03-23 06:08
I think you can ! Basically, this mode fixes stuff or performs action while typing text. This is different from vim command mode which requires you to type escape. This also means that you should be able to remap self-insert-command, used in evil insert mode, to org-self-insert and have the best of both worlds. It might even work out of the box depending on how evil has been implemented.
tonyaldon · 2022-03-23 07:01
Hey u/kaushalmodi, thank you for sharing your post. Your blog is beautiful. I like the layout and the navigation is great.
tonyaldon · 2022-03-23 07:05
Thank you! Yes Org speed keys is super super Cool.
tonyaldon · 2022-03-23 07:08
Yessss this is definitely a GOD NAME :)
tonyaldon · 2022-03-23 07:22
Thank you u/a-c-d-w for your comment. I haven't thought about ownership of the comments. It's a good point. Regarding this part of your comment: >the biggest issues with hosting a "blog" on reddit... I think that a way BIGGER ISSUE than using Reddit as a blog platform to share some stuff I find interesting would be to share nothing because some people doesn't like Reddit format but are still using it (maybe everyday). By the way, the first line of this post is the link to the git repository containing all my posts in Org format. So, as in this subreddit (r/emacs), I think we all love Emacs and its user interface, anyone CAN CHOOSE not to read the post on Reddit and read it inside Emacs.
tonyaldon · 2022-03-23 07:24
Big thank you!
tonyaldon · 2022-03-23 11:08
Hey, I'm glad that you appreciate the post. Thank you for asking about `evil-mode` and speed keys, it forces me to try `evil-mode` (first time ever) and also to read a bit of its source code. Let's try to answer to your questions: # quick answer: 1. if we are in evil normal state `<N>`, Org speed keys won't work (because most of the printing keys are used for something else, specifically they are bound in `evil-normal-state-map`), 2. if we are in evil insert state `<I>` or in evil emacs state `<E>`, Org speed keys works (because the printing keys are not bound by `evil-mode` in `evil-insert-state-map` nor in `evil-emacs-state-map`). # answer with more details: Org speed keys are not better than `evil-mode` nor the other way, they are really different: ## Org speed keys Org speed keys are a kind of a "hack" that hijack the "inserting emacs process". It can be seen as: when we try to insert a character, do not use the standard command for inserting, use another one that check for the position of the cursor in the buffer, if it is in a specific place, perform a lookup for the command to call, if we find one, call it, if none, insert the character. It is all about one command `self-insert-command` and one remapping `self-insert-command` to `org-self-insert-command`. ## evil-mode `evil-mode` manipulates the hierarchy of all the keymaps that are actives, making the current evil "state" map to win over the others. We can check this by inspecting the list of the current active keymaps with the function `current-active-maps`. If the same key sequence is bound several times to different commands in the list returned by `current-active-maps`, the first binding in the list wins over the others. For instance, when we are in evil normal state `<N>`, the bindings in the keymap `evil-normal-state-map` takes precedence (over almost all) the other binding in the current active maps, and will appear at the beginning of the list returned by `current-active-maps`. Assuming we are in evil normal state `<N>`, to check this previous assertion, we can run: M-x pp-eval-expression RET (current-active-maps) Now, if we switch to the evil insert state `<I>`, the bindings in the keymap `evil-insert-state-map` takes precedence (over almost all) the other binding in the current active maps, and will appear at the beginning of the list returned by `current-active-maps`. Assuming we are in evil normal state `<I>`, to check this previous assertion, we can run: M-x pp-eval-expression RET (current-active-maps) Now, what's interesting, is that evil insert state `<I>` doesn't bind the printing keys nor remap the command `self-insert-command`. So, when we are in evil insert state `<I>`, and we press the key `n` for instance, the "command loop editor", when performing the key lookup in the current active maps, won't find the binding coming from the keymaps `evil-insert-state-map` but the binding coming form the current global map which resolves (by default) to the command `self-insert-command`. And, if you've remapped `self-insert-command` to `org-self-insert-command`, (which is the case in org-mode with `org-use-speed-commands` set to `t`), the "command loop editor" will call `org-self-insert-command`. I hope this is helpful. Have a nice day
tonyaldon · 2022-03-23 11:15
Really thank you u/meesloo. I'm glad that those posts are helpful :)
tonyaldon · 2022-03-23 11:19
Hey u/Horrih, I've detailed an answer to the case of evil-mode above. You might be interested in reading it. Have a nice day.
T_Verron · 2022-03-23 12:39
Nice, I had no idea this existed. Do you know if there is a good reason for the entry point to be a variable switch, and for the bindings to be managed by a list, instead of having a minor mode layering its keymap onto standard org-mode bindings?
tonyaldon · 2022-03-23 13:30
Thank you! I think that the benefit of this approach (remapping `self-insert-command` to `org-self-insert-command`) over having a minor mode layering its keymap onto standard `org-mode` is that you don't have to switch between keymaps or minor modes to get the feature. Let's say we define a minor mode `X-mode` with the keymap `X-mode-map` that bind the key `n` to `org-next-visible-heading`. Now, when `X-mode` is turned on, `X-mode-map` "wins" over `org-mode-map` and typing `n` will get us (from anywhere in the buffer) to the next heading. Now, what should we do to insert the character `n` in the buffer? We have to turned off `X-mode` to remove the binding from `X-mode-map`. With the remapping method you don't have to switch between minor modes before moving to the next heading by typing `n`. The only restriction is to be at the beginning of a heading. There is always a trade off. I don't know if it is a good reason but this the only one that I see.
T_Verron · 2022-03-23 15:37
You're right, `n` should definitely not be bound to `org-next-visible-heading`. With the least amount of work, all speed keys could be bound to a new function doing the `org-speed-command` part of `org-self-insert-command`, and falling back to `org-self-insert-command` if no valid speed command is found. Or, `n` could be bound to a function `org-speed-command-n` (bad name) which would again take care of falling back to insertion (but that would lead to some degree of repetition). One last possibility (but I'm way less familiar with that one) would be to bind `n` to `org-next-visible-heading` in the text properties of heading stars, or in an overlay spanning heading stars. In that case the fall back would be done by Emacs keybinding lookup. It's probably the cleanest, but I'm not sure how easy to customize it would be. There might of course be speed commands which need more advanced tests than "am I in a specific structural markup?", and those would still have to handle their own fall back.
a-c-d-w · 2022-03-23 18:50
> I think that a way BIGGER ISSUE than using Reddit as a blog platform to share some stuff I find interesting would be to share nothing because some people doesn't like Reddit format but are still using it (maybe everyday). You're right about that. Either way, great content, thank you! Oh one more thing about "real" blog stuff: RSS!
tonyaldon · 2022-03-24 06:10
I'm glad that you find the content interesting :) Have a nice day
tonyaldon · 2022-03-24 06:19
In Emacs, there is always so many ways to implement a given task/feature. This is its absolute beauty but also its trap. Fortunately, many people have already made implementation choices for many features of Emacs. If I'm not wrong, Org speed commands is more 10 years old and it works well with a little amount of code (comparing to the total size of org-mode). We are lucky :)
757DrDuck · 2022-03-24 11:42
Character limits are 10k for comments and 40k for the main post.