emacs-evil / evil

The extensible vi layer for Emacs.
GNU General Public License v3.0
3.37k stars 281 forks source link

evil-replace should take into account current input method #605

Open TheBB opened 8 years ago

TheBB commented 8 years ago

Originally reported by: Arthur Fayzrakhmanov (Bitbucket: Geraldus, GitHub: Geraldus)


Hi friends!

Emacs 24.5.1, Evil from MELPA.

This is what we have:

Result: char under cursor will be replaced with latin char ignoring current input method.
Expected: char should be replaced respecting to current input method.


TheBB commented 8 years ago

Original comment by Frank Fischer (Bitbucket: lyro, GitHub: lyro):


As I said above the correct way to implement the character argument of these functions is to replace the function evil-read-key. This function is used to determine the character argument. Your version will only work in normal state but will probably fail in operator state or visual state.

Furthermore, I do not agree that the user should be forced to press another key. That's just inconvenient. I want to see that point moved to the anticipated point before entering further commands.

One way to handle this would be to define some timeout, after which the current sequence of characters is finished automatically (because you will usually type the keys that make up a character quite quickly). Not sure if this is a good idea. Another one would be to define some "accept" keys like SPACE or RET that end the sequence (would be better than typing C-\ or whatever twice). And however it works, it must work with the repeat system (the dot-command, could be an issue with the timeout approach).

In any case you should remember that there might be input methods that work with more than just two keys (I have no idea how those Japanese or Chinese input methods work). We cannot accept any changes that work with one input-method but fail with many others.

TheBB commented 8 years ago

Original comment by d-g (Bitbucket: d-g, GitHub: d-g):


Back to the this issue.

As I am very new to Elisp (and GNU Emacs in general), I even could not get how evil-define-motion evil-find-char actually works, neither how to temporary enable input method in evil-normal-state, however I could make the following dirty hack for evil-find-char:

(evil-define-motion evil-find-char (count) ; ←
"Move to the next COUNT'th occurrence of CHAR."
:jump t
:type inclusive
(interactive) ; ←
(evil-emacs-state) ; ←
(setq char (read-char "f" t)) ; ←
(evil-normal-state) ; ←
(setq count (or count 1))
(let ((fwd (> count 0)))
    (setq evil-last-find (list #'evil-find-char char fwd))
    (when fwd (forward-char))
    (let ((case-fold-search nil))
    (unless (prog1
                (search-forward (char-to-string char)
                                (unless evil-cross-lines
                                    (if fwd
                                        (line-end-position)
                                    (line-beginning-position)))
                                t count)
                (when fwd (backward-char)))
        (user-error "Can't find %c" char)))))

...and for t / F / T motions in the same manner.

The key point is the second argument to read-char which is exactly an option that enables input method support.

That seems to work pretty OK for russian-* input methods. As for german-postfix, I cannot spoke for Germans, but I guess, the resulting behaviour — you have to finalize sequence either with a following command (for instance foa is append after o and foea is append after ö) or with double C-\ — at least is not unexpected since I did not invent it, that’s how Emacs built-in function handles it.

By the way, the first argument for read-char is a prompt message, and this is what I would also like to see in Evil, since Vim does show f / t / F / T at the bottom of the window.

To make full use of it there also would be necessarily to stop ignoring input mode switching (namely toggle-input-method, set-input-method and deactivate-input-method functions) while in normal state.

TheBB commented 8 years ago

Original comment by d-g (Bitbucket: d-g, GitHub: d-g):


input-method is ignored by t and f also. Which is pretty obvious, but I have to mention it since these commands are much more important than r, imho.

TheBB commented 8 years ago

Original comment by Arthur Fayzrakhmanov (Bitbucket: Geraldus, GitHub: Geraldus):


I'm not familiar with input methods other than default and russian-computer, but I would be happy to contribute if I will have free time (I love elisp but have some troubles with time management).

TheBB commented 8 years ago

Original comment by Arthur Fayzrakhmanov (Bitbucket: Geraldus, GitHub: Geraldus):


Well, maybe we can start from simple corner case: support simple input methods, and then try to support complex ones. What do you think?

TheBB commented 8 years ago

Original comment by Frank Fischer (Bitbucket: lyro, GitHub: lyro):


The problem is that I do not know how to do it right. Sometimes the character to be read is composed of several keys. However, sometimes you just want the plain first character, without waiting for another one. For instance, in german-postfix one can use "ae" to type "ä". But if you only want a plain "a", you must press another key. The problem is what to do with the second key: this second key will generate a key-event on its own and might cause further unintended actions.

In fact, you just have to reimplement evil-read-key. I'm open for suggestions ;)

khaoos-abominable commented 7 years ago

I am a new evil (spacemacs) user. I use a russian-computer input method for writing comments and notes. And this issue causes a little pain in the neck while I am editing them.

I am going to fix it in my envirionment with minimal effort and invasion. Could anyone suggest a function that translates characters according to the current input method (or passed as a parameter)? For example, which translates "asdf" to "фыва" for russian-* input methods. Is this possible? Thanks in advance.

ninrod commented 7 years ago

I'm pretty sure that this is closely related to #726, maybe almost a duplicate.

khaoos-abominable commented 7 years ago

By the way, could anyone explain me a part of documentation of a function read-char which says:

Read a character from the command input (keyboard or macro).

What does it mean to read a character from a macro? How to make the function read from a macro?

duianto commented 7 years ago

@khaoos-abominable This page seems to show an example of the read-char function reading from a macro: https://www.gnu.org/software/emacs/manual/html_node/elisp/Reading-One-Event.html

In the description for the second example in the function:

Function: read-char &optional prompt inherit-input-method seconds

The second example shows a keyboard macro definition that calls read-char from the minibuffer using eval-expression. read-char reads the keyboard macro’s very next character, which is 1. Then eval-expression displays its return value in the echo area.

;; We assume here you use M-: to evaluate this.
(symbol-function 'foo)
     ⇒ "^[:(read-char)^M1"
(execute-kbd-macro 'foo)
     -| 49
     ⇒ nil
khaoos-abominable commented 7 years ago

@duianto thanks. I saw it. I was confused why it was mentioned specially for that function. I thought that any function that prompts user input could do such a thing.

By the way I solved the problem in my envirionment. So commands r, f, F, t, T now respect the current input method (russian-computer namely). The solution should (or could) work for simple input methods that are in a family of quail input methods. It doesn't touch the evil core, it only uses hook and advice mechanisms. If it works for a while without any surprises I'll share it.

lonelyelk commented 7 years ago

@khaoos-abominable does it still work without surprises?

khaoos-abominable commented 7 years ago

@lonelyelk actually there are some that disturb occasionally :). There are rare situations when emacs stops interpreting commands because it thinks I emit them in russian (it could be seen in an echo line) . But reswitching input method cures it immediately. I think it is possible to fix it but I haven't found time to do it. And I'll do it for sure.

If you are interested, you could watch my dotspacemacs file. You need to pick into your dotfile a variable khaoos-input-method-last-raw-key and functions khaoos-capture-input-mode-raw-key, khaoos-activate-input-method, khaoos-evil-read-key-respect-input-method, khaoos-run-evil-command-respect-input-method.

Then you need to hook an input method activation:

(advice-add 'activate-input-method :after #'khaoos-activate-input-method)

Then you need to wrap some commands via khaoos-run-evil-command-respect-input-method like I did in sections with-eval-after-load "evil-macros" and with-eval-after-load "evil-integration". I did it only for commands that I use.

And then you are ready to rebind commands that your previously wrapped. You do it somewhere in your dotspacemacs/user-config, for example:

;; Rebind commands that don't respect input method
  (define-key evil-normal-state-map (kbd "r") 'khaoos-evil-replace)
  (define-key evil-motion-state-map (kbd "f") 'khaoos-evil-find-char)
  (define-key evil-motion-state-map (kbd "t") 'khaoos-evil-find-char-to)
  (define-key evil-motion-state-map (kbd "F") 'khaoos-evil-find-char-backward)
  (define-key evil-motion-state-map (kbd "T") 'khaoos-evil-find-char-to-backward)
  (spacemacs/set-leader-keys "jj" 'evil-khaoos-avy-goto-char)
  (spacemacs/set-leader-keys "jJ" 'evil-khaoos-avy-goto-char-2)
  (spacemacs/set-leader-keys "jw" 'evil-khaoos-avy-goto-word-or-subword-1)

If you use evil-escape key sequence there is also a solution to make it understand russian. But it's quite invasive and reimplements core functions but I am happy with it though :). Just search khaoos-evil-escape in my dotfile.

wasamasa commented 7 years ago

@khaoos-abominable I've hoped your solution to be appliable to #798, but it isn't really as looking up the last key with quail will only work for single-char input methods (russian-pc) as opposed to multi-char input methods (japanese-katakana).

khaoos-abominable commented 7 years ago

@wasamasa Of course, I pointed it out when I wrote

The solution should (or could) work for simple input methods that are in a family of quail input methods

AcaDemIQ commented 1 year ago

knock-knock, have you solved this problem?

wasamasa commented 1 year ago

No.