oantolin / embark

Emacs Mini-Buffer Actions Rooted in Keymaps
GNU General Public License v3.0
939 stars 56 forks source link

A word target finder? #506

Open hmelman opened 2 years ago

hmelman commented 2 years ago

It's been mentioned in a few other issues that led to sentence and paragraph targets but doesn't seem to be followed up on. I'm not sure if that a conscious decision or not. I think a word target finder for prose would be useful.

In addition to the obvious standard commands foreward/backward, case, kill, delete-region, mark, transpose; it could have:

I'm not sure if it should be limited to text-modes. I would want some of these available in comments and strings of prog-modes.

oantolin commented 2 years ago

We have most of this already in some form. The word at point in a text mode is recognized as an "identifier" (because it uses the same target finder as for identifiers in programming modes). It has: $ for ispell-word, n and p to go the next or previous ocurrence, o for occur, C-s for isearch (but it's not in symbol mode).

You can certainly use M-s M-w for eww and M-s . for isearch in symbol mode, even though they are not specifically bound in Embark maps (you can always use any key binding, and are not limited to things in the Embark action maps). We can certainly add more ergonomic bindings.

All of this is also available in prog modes, of course.

I'm not sure how necessary it is to have a separate target type for words in text modes.

oantolin commented 2 years ago

One thing I certainly don't want to is to try to decide in prog modes when a string of letters is an identifier and when it is a word! Identifiers can appear in comments, just like words, and even in strings in languages with string interpolation. An LSP server can tell the difference, but not regexps and syntax classes.

minad commented 2 years ago

On 5/9/22 20:32, Omar Antolín Camarena wrote:

One thing I certainly don't want to is to try to decide in prog modes when a string of letters is an identifier and when it is a word! Identifiers can appear in comments, just like words, and even in strings in languages with string interpolation. An LSP can tell the difference, but not regexps and syntax classes.

I also thought about adding a word target finder before, but it is probably not a good idea, since we cannot meaningfully distinguish from identifiers as you point out. If we find more interesting commands, we can just bind them in the identifier map.

I use some of the third party packages mentioned by @hmelman and I also add them to the identifier map, symbol-overlay, string-inflection, casing, ...

Generally it seems okay if target finders are a little bit fuzzy (identifiers = words). We also allow arbitrary keybindings or M-x commands on targets to protect the freedom of the user, as every good free software is supposed to do :-P

hmelman commented 2 years ago

There is another advantage in having a word target in text-modes before the identifier target, all the symbol stuff doesn't pollute the keymap. I don't have to look through xref bindings when writing prose and I can reuse keys like "h" for dictionary-lookup-definition which seems kinda slick.

minad commented 2 years ago

There is another advantage in having a word target in text-modes before the identifier target, all the symbol stuff doesn't pollute the keymap. I don't have to look through xref bindings when writing prose and I can reuse keys like "h" for dictionary-lookup-definition which seems kinda slick.

I must admit, I don't really buy this key map pollution argument. This only matters for which-key. Reusing or shadowing bindings seems like a reasonable argument. On the other hand if we start to distinguish words from identifiers then executing identifier actions in text files will not work anymore. We should not do this.

hmelman commented 2 years ago

On the other hand if we start to distinguish words from identifiers then executing identifier actions in text files will not work anymore.

I'm just proposing having an embark-target-word-at-point finder before embark-target-custom-variable-at-point. You could still cycle to an identifier in prose.

oantolin commented 2 years ago

I can reuse keys like "h" for dictionary-lookup-definition which seems kinda slick.

You can already reuse that in word modes!

(with-eval-after-load 'markdown-mode
  (define-key markdown-mode-map [remap display-local-help] #'dictionary-lookup-definition))

Like @minad, I also don't worry too much about keymap pollution. It's only a problem if you try to read through all the bindings, like which-key and Embark's verbose indicator wrong-mindedly encourage people to do. So, just don't that and problem solved! You never see people suggest that if you want to remember a global key binding you read through the entire describe-bindings buffer... I think for Embark actions maps the better solution is to switch to the completing-read prompter and type a bit to narrow to what you want.

So I'm also inclined to keep words and identifiers together in a single map, so that you can use the prose-y word actions in prog modes too.

minad commented 2 years ago

@hmelman

I'm just proposing having an embark-target-word-at-point finder before embark-target-custom-variable-at-point. You could still cycle to an identifier in prose.

Yes, but I think this is just too inconvenient. This is what I meant with Embark being "fuzzy". It is okay to be fuzzy and in that way allow quick access to many functions even if there is the risk of getting it wrong from time to time.

The remap mentioned by @oantolin seems like a possible solution for "semantic remapping", e.g., if there is a better command available locally. However in this case we would then lose the possibility to cycle to the identifier action.

oantolin commented 2 years ago

I'm just proposing having an embark-target-word-at-point finder before embark-target-custom-variable-at-point. You could still cycle to an identifier in prose.

If the only point of having a separate word map is to get rid of these four actions, I'm not really in favor of the idea: display-local-help, info-lookup-symbol, xref-find-apropos, xref-find-references.

Or would you want further differences beyond just not having those four actions, @hmelman?

If it is just to remove those four, we can always just put up a solution on the wiki.

oantolin commented 2 years ago

Well, on second thought, maybe it's perfectly sensible to make an embark-word-map without those four programmery actions, and have it be the parent of the identifier map. We'd only recognize words in text modes, and for prog modes stick to the current system.

minad commented 2 years ago

But I often want to jump to identifiers from text modes. How would you solve that? Look for quotes, tildes or equal signs in Markdown/Org and treat those directly as identifiers?

oantolin commented 2 years ago

But I often want to jump to identifiers from text modes. How would you solve that? Look for quotes, tildes or equal signs in Markdown/Org and treat those directly as identifiers?

Well, @hmelman only want to add a word target, not take away the identifier target in text modes. Oh, but you mean having word with higher priority would change embark-dwim = goto definition? That is a problem, I use that too.

hmelman commented 2 years ago

I've just started playing with this so I think I need to experiment. Having seen the sentence and paragraph targets I was suprise to not see word and as I said looking through the issues I didn't see a conscious choice to not include a word target.

I do use identifiers in some text-modes but I'm fine cycling once to the map since I use words far more often. I could see a word target finder that returned not found if the "word" at point had symbol syntax characters in it so if point were in forward-word it would skip right to identifier. Though that's a little unusual in how emacs treats words in that case.

(embark-define-keymap embark-word-map
  "Keymap for Embark actions for dealing with words."
  :parent embark-prose-map
  ("n" forward-word)
  ("p" backward-word)
  ("k" kill-word)
  ("t" transpose-words)
  ("u" upcase-word)
  ("l" downcase-word)
  ("c" capitalize-word)
  ("SPC" mark)
  ("DEL" delete-region)
  ("C-s" isearch-forward-symbol-at-point)
  ("o" occur)
  ("$" ispell-word)         ; flyspell-word?
  ("d" dictionary-lookup-definition)
  ("TAB" ispell-complete-word)
  ("a" inverse-add-global-abbrev)
  ("w" eww)

  ;; These would be nice but need functions that
  ;; work in different modes: org, markdown, html, etc.
  ;; ("b" make-face-bold)
  ;; ("i" make-face-italic)

  ;; Some 3rd party packages
  ("h" osx-dictionary-search-word-at-point) ; a mac specific package
  ("_" symbol-overlay-put)
  (">" mc/mark-next-word-like-this)
  ("<" mc/mark-previous-word-like-this)
  ("C-<" mc/mark-all-words-like-this)
  ("g" google-this-word)        ; from package google-this
)
minad commented 2 years ago

@oantolin

Oh, but you mean having word with higher priority would change embark-dwim = goto definition? That is a problem, I use that too.

Yes. This is what I mean.

Thinking about this a bit more, it seems that for Embark targets one should use these rough guidelines:

We don't adhere to these guidelines everywhere and I think the addition of paragraph, sentence and heading (for which I was responsible) made the situation worse. We should be careful about such changes. I think I'd be less opposed to such changes if the newly added targets are of lower priority.

@hmelman Why not add a word keymap and bind it to the identifier keymap under the prefix key w. If you don't like that you could also turn it around and add i in a word keymap in your user configuration and bind that to the original identifier keymap. I also don't think there is something wrong if you add your own target finders in your configuration, e.g., a word finder only for text modes before the identifier target finder. We could just document this in the wiki.

hmelman commented 2 years ago

I'll try to think these things through. I'm not (yet) an embark-dwim user.

Putting a w keymap on identifier seems like more fuss than I want. I use word commands in text-modes way more than I use identifier commands. It remains to be seen if I access them via embark as much as via their regular bindings. My hope was that I'd use embark's sentence and paragraph targets more and having a word target might help reinforce those. And I'll try playing with embark-completing-read-prompter.

oantolin commented 2 years ago

Some comments on your word map, @hmelman:

You don't need these since they are present in embark-prose-map:

  ("SPC" mark)
  ("DEL" delete-region)

You also inherit useable u, l and c actions for embark-prose-map, so you could also remove these:

  ("u" upcase-word)
  ("l" downcase-word)
  ("c" capitalize-word)

If you want to keep them, you should be aware that they don't act on the entire word, just on the portion from point to the end of the word. If you want them to act on the entire word you can (1) remove them and use the -region case commands inherited from embark-prose-map, or (2) you can add '(upcase-word embark--beginning-of-target) to embark-pre-action-hooks (and similarly for the others).

  ("k" kill-word)

Similarly, kill-word only really kills from point to the end of the word, so you can either (1) change it to kill-region (Embark is configured to mark targets before executing kill-region), or (2) add '(kill-word embark--beginning-of-target) to embark-pre-action-hooks.

Finally, this is just personal, but I'm not sure I would find these more useful than the existing bindings for identifiers:

  ("n" forward-word)
  ("p" backward-word)

The existing bindings move you to the next or previous occurrence of the same word.

hmelman commented 2 years ago

Some comments on your word map, @hmelman:

You don't need these since they are present in embark-prose-map:

  ("SPC" mark)
  ("DEL" delete-region)

Yeah I originally had mark-word and hadn't quite settled on delete-region at first. I'll remove.

You also inherit useable u, l and c actions for embark-prose-map, so you could also remove these:

  ("u" upcase-word)
  ("l" downcase-word)
  ("c" capitalize-word)

If you want to keep them, you should be aware that they don't act on the entire word, just on the portion from point to the end of the word. If you want them to act on the entire word you can (1) remove them and use the -region case commands inherited from embark-prose-map, or (2) you can add '(upcase-word embark--beginning-of-target) to embark-pre-action-hooks (and similarly for the others).

I had setup the embark-pre-action-hooks (and for kill-word) and am considering making these repeat. If not I'll remove them all and use the prose map variants. I use M-u, M-l, and M-c frequently so I don't know if I'll use these. I bound titlecase-region in embark and use that, so I might find myself wanting to use these from embark.

Finally, this is just personal, but I'm not sure I would find these more useful than the existing bindings for identifiers:

  ("n" forward-word)
  ("p" backward-word)

The existing bindings move you to the next or previous occurrence of the word.

I just added them for consistency with the other targets and to remind myself to not bind these keys to other commands unless I really wanted to. I doubt I'd ever use them over M-f and M-b. I also use M-a and M-e for sentence moving but not so much M-{ and M-} for paragraphs. My question was if I'd start using the embark versions for these commands.

oantolin commented 2 years ago

My question was if I'd start using the embark versions for these commands.

What I do with motion commands like forward-sentence is that I only use them from embark-act if I am already in embark-act for some other reason. One reason that does come up often is that I want to reorder a bunch of sentences. In that case I do use embark-act with a series of n, p, t and - t to achieve the rearrangement I want.

hmelman commented 2 years ago

So how am I supposed to use the completing prompter? When I change to it via customize I see this on invocation:

Screen Shot 2022-05-09 at 5 18 05 PM
oantolin commented 2 years ago

Woah, why do you have two verbose indicators in addition to the completing-read prompter in the minibuffer?

There are two ways to access the completing-read prompter:

  1. What you did: set it as the default. In that case you should probably also remove the verbose or mixed indicator and use the minimal indicator.
  2. Leave the keymap prompter as the default and just press C-h (configurable, this is embark-help-key) when you want to summon the completing-read prompter.

As for your specific question, how do you use it, it's a normal minibuffer completion session. Type to narrow the candidate list, select a candidate when you see the one you want, press RET.

hmelman commented 2 years ago
  1. What you did: set it as the default. In that case you should probably also remove the verbose or mixed indicator and use the minimal indicator.

Yes this worked. I think you should add these instructions to the README section "Selecting commands via completions instead of key bindings". I found it unexpected that to use one setting I had to also change a second setting ;)

oantolin commented 2 years ago

I found it unexpected that to use one setting I had to also change a second setting ;)

Technically you don't need to change the indicators. And in fact, if you were a user of default completion or of Vertico's unobtrusive mode, you'd probably want to keep the verbose or mixed indicator. Maybe you'd even want to keep it if you used Vertico's flat mode, icomplete-mode or fido-mode, but did not use icomplete-vertical-mode or fido-vertical-mode.

hmelman commented 2 years ago

I'm just proposing having an embark-target-word-at-point finder before embark-target-custom-variable-at-point. You could still cycle to an identifier in prose.

If the only point of having a separate word map is to get rid of these four actions, I'm not really in favor of the idea: display-local-help, info-lookup-symbol, xref-find-apropos, xref-find-references.

Or would you want further differences beyond just not having those four actions, @hmelman?

It turns out to be a little more complicated. In a text mode, if the word target is a symbol or function or command you'll get those actions too. That may be what you want if you're writing about fido-vertical-mode, it's probably not if you're using the words length, function, dictionary, member, and, or, not, when, push, set, woman, format, etc. and seeing actions like elp-instrument-function.