Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
194 changes: 91 additions & 103 deletions README.org
Original file line number Diff line number Diff line change
@@ -1,75 +1,84 @@
* Reverse-im.el
* Reverse-im.el ®

[[https://melpa.org/#/reverse-im][https://melpa.org/packages/reverse-im-badge.svg]]
[[https://github.com/a13/reverse-im.el/stargazers][https://img.shields.io/github/stars/a13/reverse-im.el.svg]]
[[https://github.com/a13/reverse-im.el/issues][https://img.shields.io/github/issues/a13/reverse-im.el.svg]]
[[https://github.com/vshymanskyy/StandWithUkraine/blob/main/docs/README.md][https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/badges/StandWithUkraine.svg]]

Overrides [[https://www.gnu.org/software/emacs/manual/html_node/elisp/Translation-Keymaps.html][function-key-map]] for preferred input-method(s) to translate input sequences
to English, so we can use Emacs bindings while a non-default system layout is active.
** Rationale
Are you using a non-Latin keyboard layout? Have you ever seen Emacs display a message such as "C-є is undefined" instead of executing a desired command? If so, then this package is probably for you!

💡 It's just a collection of hacks, and definitely not a magic bullet, so if you have any issues, please take a look at [[https://github.com/a13/reverse-im.el/issues][known ones]] first.

** Quick start
💡 I use [[https://www.gnu.org/software/emacs/manual/html_node/use-package/][use-package]] for all the examples here. Since it's a built-in feature of the latest version of Emacs, they should work fine there.
*** Simple setup
#+BEGIN_SRC emacs-lisp
(use-package reverse-im
:ensure t ; install `reverse-im' using package.el
:demand t ; always load it
;; translate these methods, use M-x `list-input-methods'
;; if you're not sure which one to use
(reverse-im-input-methods '("ukrainian-computer"))
:config
(reverse-im-mode t)) ; turn the global minor mode on
#+END_SRC

#+BEGIN_SRC emacs-lisp
;; Needed for `:after char-fold' to work
(use-package char-fold
:custom
(char-fold-symmetric t)
(search-default-mode #'char-fold-to-regexp))

(use-package reverse-im
:ensure t ; install `reverse-im' using package.el
:demand t ; always load it
:after char-fold ; but only after `char-fold' is loaded
:bind
("M-T" . reverse-im-translate-word) ; fix a word in wrong layout
:custom
;; cache generated keymaps
(reverse-im-cache-file (locate-user-emacs-file "reverse-im-cache.el"))
;; use lax matching
(reverse-im-char-fold t)
(reverse-im-read-char-advice-function #'reverse-im-read-char-include)
;; translate these methods
(reverse-im-input-methods '("ukrainian-computer"))
:config
(reverse-im-mode t)) ; turn the mode on
#+END_SRC

* Installation and usage
*** More complex configuration
💡 See below for details.

** Manual installation
#+BEGIN_SRC emacs-lisp
;; Needed for `:after char-fold' to work
(use-package char-fold
:custom
(char-fold-symmetric t)
(search-default-mode #'char-fold-to-regexp))

#+BEGIN_SRC emacs-lisp
M-x package-install RET reverse-im RET
#+END_SRC
(use-package reverse-im
:ensure t ; install `reverse-im' using package.el
:demand t ; always load it
:after char-fold ; but only after `char-fold' is loaded
:bind
("M-T" . reverse-im-translate-word) ; to fix a word written in the wrong layout
:custom
;; cache generated keymaps
(reverse-im-cache-file (locate-user-emacs-file "reverse-im-cache.el"))
;; use lax matching
(reverse-im-char-fold t)
;; advice read-char to fix commands that use their own shortcut mechanism
(reverse-im-read-char-advice-function #'reverse-im-read-char-include)
;; translate these methods
(reverse-im-input-methods '("ukrainian-computer"))
:config
(reverse-im-mode t)) ; turn the mode on
#+END_SRC

These examples should be sufficient in most cases. However, if you need to go deeper:



* Manual Installation and usage

** Using use-package /:ensure/
** Manual installation
This one is for Package.el users. Straight and Elpaca users should already know how to do this.

Using provided minor mode instead (see [[Settings][Settings]] for details):
#+BEGIN_SRC emacs-lisp
(use-package reverse-im
:ensure t
:custom
(reverse-im-input-methods '("ukrainian-computer"))
;; (reverse-im-activate "ukrainian-computer") ; the legacy way
:config
(reverse-im-mode t))
M-x package-install RET reverse-im RET
#+END_SRC

** Usage
Reverse-im provides a simple minor mode that activates/deactivates translations for all
input methods from ([[https://www.gnu.org/software/emacs/manual/html_node/emacs/Easy-Customization.html][customizable]]) ~reverse-im-input-methods~ list (empty by default).

If you have [[https://github.com/justbur/emacs-which-key][which-key]] installed, you can examine how an input method will be remapped by calling
If you use [[https://github.com/justbur/emacs-which-key][which-key]] (a built-in feature in the latest Emacs versions), you can examine how an input method will be remapped by calling

#+BEGIN_SRC emacs-lisp
M-x reverse-im-which-key-show
#+END_SRC


*** Manual customization

NB: I highly recommend the ~use-package~ method.
💡 I highly recommend the ~use-package~ method.

#+BEGIN_SRC emacs-lisp
;; standard customization interface, note that this will turn on the mode immediately
Expand All @@ -80,7 +89,7 @@
#+END_SRC

Since version 0.0.2 all possible bindings with Ctrl, Meta, and Super are translated.
If you want to change it (e.g. you don't use Super)
If you want to change the behavior, for example, if you don't use Super,
#+BEGIN_SRC emacs-lisp
(setq reverse-im-modifiers '(control meta))
;; or
Expand All @@ -95,90 +104,69 @@
(reverse-im-mode t) ; call with a negative argument to disable
#+END_SRC


Alternatively, you can directly call translation function:
#+BEGIN_SRC emacs-lisp
(reverse-im-activate "ukrainian-computer")
#+END_SRC

Since version 0.0.3 it supports multiple input method translation:
As an alternative, you can call the translation function directly. Since version 0.0.3, it has supported multiple input methods.
#+BEGIN_SRC emacs-lisp
(reverse-im-activate '("ukrainian-computer" "arabic"))
#+END_SRC

If something goes wrong or you just want to turn translation off.

If something goes wrong, or if you simply want to turn the translation off,
#+BEGIN_SRC emacs-lisp
M-x reverse-im-deactivate
;; or
(reverse-im-deactivate)
(reverse-im-deactivate t) ; to reset translation tables cache
(reverse-im-deactivate t) ; to reset the translation tables cache
#+END_SRC

*** Advising read-char
* Extra features
** Advising read-char

Reverse-im doesn't work with custom dispatchers like ~org-export~, ~org-capture~ , ~mu4e~ etc. You can try to fix it by advising ~read-char~ and ~read-char-exclusive~. Do it on your own risk since the feature is experimental, hacky and wasn't tested good enough.
💡 This feature since is experimental, hacky and wasn't tested good enough, so use it on your own risk.

There are two versions of advice functions - ~reverse-im-read-char-include~ (the less risky one) translates input iff current command matches (or equals) any element of customizable ~reverse-im-read-char-include-commands~ list, while ~reverse-im-read-char-exclude~ (the more risky one) translates input unless current command does match ~reverse-im-read-char-exclude-commands~.
Some commands such as ~org-export~, ~org-capture~, and ~mu4e~, use custom dispatchers, don't work out of the box in a non-latin layout.
Fortunately most of them use ~read-char~ or ~read-char-exclusive~ for [[https://www.gnu.org/software/emacs/manual/html_node/elisp/Reading-One-Event.html][reading input]], so we can advice the function to translate input characters. The simplest way is to set ~reverse-im-read-char-advice-function~ defcustom, though you can advice then #manually if you want to.
There are two possible values - ~reverse-im-read-char-include~ (the less risky one) translates the input iff current command matches (or equals) any element in the customizable ~reverse-im-read-char-include-commands~ list. The more lazy (and risky) version ~reverse-im-read-char-exclude~ translates the input unless the current command matches any element in the ~reverse-im-read-char-exclude-commands~.

You can choose which one to use by customizing ~reverse-im-read-char-advice-function~ before ~reverse-im-mode~ is enabled (see Examples above) or by advicing ~read-char~/~read-char-exclusive~ manually.

#+BEGIN_SRC emacs-lisp
(advice-add #'read-char-exclusive #'reverse-im-read-char-include)
(advice-add #'read-char #'reverse-im-read-char-include)
#+END_SRC
or
#+BEGIN_SRC emacs-lisp
(advice-add 'read-char-exclusive #'reverse-im-read-char)
(advice-add 'read-char #'reverse-im-read-char)
#+END_SRC
💡 set these before ~reverse-im-mode~ is enabled, or advice the functions manually

If something goes wrong, remove the advices by
*** Manual configuration
#+BEGIN_SRC emacs-lisp
(advice-remove 'read-char-exclusive #'reverse-im-read-char-include)
(advice-remove 'read-char #'reverse-im-read-char-include)
(advice-add #'read-char-exclusive :around #'reverse-im-read-char-include)
(advice-add #'read-char :around #'reverse-im-read-char-include)
#+END_SRC
or
#+BEGIN_SRC emacs-lisp
(advice-remove 'read-char-exclusive #'reverse-im-read-char)
(advice-remove 'read-char #'reverse-im-read-char)
(advice-add 'read-char-exclusive :around #'reverse-im-read-char)
(advice-add 'read-char :around #'reverse-im-read-char)
#+END_SRC
*** Char folding
[[./screenshots/char-fold.png]]
Emacs supports [[https://www.gnu.org/software/emacs/manual/html_node/emacs/Lax-Search.html#Lax-Search][Lax Matching During Searching]] and since version 27 you can include your own search substitutions. Reverse-im adds substitutions to ~char-fold-include~ generated using ~reverse-im-char-fold-include~ if ~reverse-im-char-fold~ is set to ~t~ (before ~reverse-im-mode~ is activated).

#+BEGIN_SRC emacs-lisp
(use-package char-fold
:custom
(char-fold-symmetric t)
(search-default-mode #'char-fold-to-regexp))
#+END_SRC
** Char folding
[[./screenshots/char-fold.png]]
Emacs supports [[https://www.gnu.org/software/emacs/manual/html_node/emacs/Lax-Search.html#Lax-Search][Lax Matching During Searching]] and since version 27 you can include your own search substitutions. Reverse-im adds substitutions to ~char-fold-include~ generated using ~reverse-im-char-fold-include~ if ~reverse-im-char-fold~ is set to ~t~ (before ~reverse-im-mode~ is activated).

*** pre-27 versions
You can download a new version of char-fold.el manually, or using something like
#+BEGIN_SRC emacs-lisp
(use-package char-fold
:custom
(char-fold-symmetric t)
(search-default-mode #'char-fold-to-regexp)
:quelpa (char-fold :url "https://raw.githubusercontent.com/emacs-mirror/emacs/master/lisp/char-fold.el"
:fetcher url))
#+END_SRC
#+BEGIN_SRC emacs-lisp
(use-package char-fold
:custom
(char-fold-symmetric t)
(search-default-mode #'char-fold-to-regexp))
#+END_SRC

** Interactive translation
If you want to fix a region or a word which was typed using incorrect layout, you can use interactive functions ~reverse-im-translate-region~ and ~reverse-im-translate-word~ respectively.

*** [[https://github.com/abo-abo/avy][Avy]] integration
** [[https://github.com/abo-abo/avy][Avy]] integration

[[./screenshots/avy.png]]

[[./screenshots/avy.png]]
If avy is installed, reverse-im adds ~avy-action-reverse-im-translate~ to ~avy-dispatch-alist~ (bound to ~reverse-im-avy-action-char~, ~?T~ is default one), so it's possible to translate words and lines which are you jumping to. To disable the functionality ~reverse-im-avy-action-char~ should be set to ~nil~.

If avy is installed, reverse-im adds ~avy-action-reverse-im-translate~ to ~avy-dispatch-alist~ (bound to ~reverse-im-avy-action-char~, ~?T~ is default one), so it's possible to translate words and lines which are you jumping to. To disable the functionality ~reverse-im-avy-action-char~ should be set to ~nil~.

* How it works
Reverse-im overrides [[https://www.gnu.org/software/emacs/manual/html_node/elisp/Translation-Keymaps.html][function-key-map]] for preferred input-method(s) to translate input sequences. The original idea came from [[https://ru-emacs.livejournal.com/82428.html][this blog post]] by [[https://github.com/jurta][Juri Linkov]].

** Known issues:
* [[https://github.com/a13/reverse-im.el/issues][Known issues]]:

- Bindings with AltGr (as Meta) [[https://github.com/a13/reverse-im.el/issues/4#issuecomment-308143947][don't work]] well on Windows.
- [[https://github.com/a13/reverse-im.el/issues/21][Doesn't]] [[https://github.com/a13/reverse-im.el/issues/6][work]] well for punctuation keys if they are placed on different keys than in English layout.
- "Buffer is read-only:" error
Reverse-im doesn't work for /self-insert-command/ (obviously), but in read-only modes one may want to use single key shortcuts. In this case it's possible to /suppress-keymap/ to undefine /self-insert-command/, so /function-key-map/ override it's behavior.
- Single key shortcuts (i.e. without modifiers) [[https://github.com/a13/reverse-im.el/issues/17][don't work with]] in Hydra
- Bindings with AltGr (as Meta) [[https://github.com/a13/reverse-im.el/issues/4#issuecomment-308143947][don't work]] well on Windows.
- [[https://github.com/a13/reverse-im.el/issues/21][Doesn't]] [[https://github.com/a13/reverse-im.el/issues/6][work]] well for punctuation keys if they are placed on different keys than in English layout.
- "Buffer is read-only:" error
Reverse-im doesn't work for ~self-insert-command~ (obviously), but sometimes one may want to use single key shortcuts in read-only modes. In this case it's possible to ~suppress-keymap~ to undefine ~self-insert-command~, so ~function-key-map~ overrides its behavior.
- Single key shortcuts (i.e. without modifiers) [[https://github.com/a13/reverse-im.el/issues/17][don't work with]] in Hydra
7 changes: 5 additions & 2 deletions reverse-im.el
Original file line number Diff line number Diff line change
Expand Up @@ -81,13 +81,13 @@
(const :tag "Exclude mode" reverse-im-read-char-exclude))
:group 'reverse-im)

(defcustom reverse-im-read-char-exclude-commands

Check warning on line 84 in reverse-im.el

View workflow job for this annotation

GitHub Actions / check (snapshot, false)

custom-declare-variable ‘reverse-im-read-char-exclude-commands’ docstring wider than 80 characters
'("^avy-.*")
"List of regexes/commands to match `this-command' to exclude when using `reverse-im-read-char-exclude'."
:group 'reverse-im
:type `(repeat (choice regexp symbol)))

(defcustom reverse-im-read-char-include-commands

Check warning on line 90 in reverse-im.el

View workflow job for this annotation

GitHub Actions / check (snapshot, false)

custom-declare-variable ‘reverse-im-read-char-include-commands’ docstring wider than 80 characters
'("^mu4e-.*" org-capture org-export-dispatch)
"List of regexes/commands to match `this-command' to include when using `reverse-im-read-char-include'."
:group 'reverse-im
Expand All @@ -95,7 +95,7 @@

(defcustom reverse-im-cache-file
nil
"File to cache translation keymap. Don't cache if `nil'."
"File to cache translation keymap. Don't cache if nil."
:group 'reverse-im
:type 'file)

Expand Down Expand Up @@ -198,10 +198,12 @@

;; https://stackoverflow.com/questions/2321904/elisp-how-to-save-data-in-a-file
(defun reverse-im--print-to-file (filename data)
"Print Lisp DATA to FILENAME."
(with-temp-file filename
(prin1 data (current-buffer))))

(defun reverse-im--read-from-file (filename)
"Read Lisp data from FILENAME."
(with-temp-buffer
(insert-file-contents filename)
(cl-assert (eq (point) (point-min)))
Expand Down Expand Up @@ -255,7 +257,8 @@
"Show translation bindings for INPUT-METHOD using `which-key'."
(interactive
(list (read-input-method-name "Translate input method: ")))
(if (require 'which-key nil t)
(if (and (require 'which-key nil t)
(fboundp 'which-key--show-keymap))
(which-key--show-keymap input-method
(reverse-im--im-to-keymap input-method))
(message "which-key is not installed.")))
Expand All @@ -281,7 +284,7 @@

(defun reverse-im-char-fold-include ()
"Generate a substitutions list for `char-fold-include'."
(if-let ((translation-keymap (reverse-im--translation-keymap)))

Check warning on line 287 in reverse-im.el

View workflow job for this annotation

GitHub Actions / check (snapshot, false)

‘if-let’ is an obsolete macro (as of 31.1); use ‘if-let*’ instead.
(reverse-im--generate-char-fold translation-keymap)
(message "Keymap is nil, is reverse-im-mode enabled?")
nil))
Expand All @@ -304,7 +307,7 @@
(let ((this-command-name (symbol-name this-command)))
(string-match-p command this-command-name)))))

(defun reverse-im-read-char-include (orig-fun &rest args)

Check warning on line 310 in reverse-im.el

View workflow job for this annotation

GitHub Actions / check (snapshot, false)

docstring wider than 80 characters
"An advice for `read-char' compatible ORIG-FUN called with ARGS.
Translate chars only when `this-command' is in `reverse-im-read-char-include-commands'."
(let ((res (apply orig-fun args)))
Expand All @@ -312,7 +315,7 @@
(reverse-im--translate-char res t)
res)))

(defun reverse-im-read-char-exclude (orig-fun &rest args)

Check warning on line 318 in reverse-im.el

View workflow job for this annotation

GitHub Actions / check (snapshot, false)

docstring wider than 80 characters
"An advice for `read-char' compatible ORIG-FUN called with ARGS.
Translate all chars, unless `this-command' is not in `reverse-im-read-char-exclude-commands'."
(let ((res (apply orig-fun args)))
Expand Down Expand Up @@ -346,7 +349,7 @@

;;; Translation functions

(defun reverse-im--translate-char-internal (keymap c strict)

Check warning on line 352 in reverse-im.el

View workflow job for this annotation

GitHub Actions / check (snapshot, false)

docstring wider than 80 characters
"Try to translate C using KEYMAP. Set STRICT if reverse translation is not needed."
(let ((to))
(map-keymap (lambda (from value)
Expand All @@ -361,10 +364,10 @@
keymap)
(or to c)))

(defun reverse-im--translate-char (c &optional strict)

Check warning on line 367 in reverse-im.el

View workflow job for this annotation

GitHub Actions / check (snapshot, false)

docstring wider than 80 characters
"Try to translate C using active translation. Set STRICT if reverse translation is not needed."
(and c
(if-let ((translation-keymap (reverse-im--translation-keymap)))

Check warning on line 370 in reverse-im.el

View workflow job for this annotation

GitHub Actions / check (snapshot, false)

‘if-let’ is an obsolete macro (as of 31.1); use ‘if-let*’ instead.
(reverse-im--translate-char-internal translation-keymap c strict)
(message "Keymap is nil, is reverse-im-mode enabled?")
c)))
Expand All @@ -379,7 +382,7 @@
;; loosely based on `cider--format-region'
;; TODO: prefix argument to store selection
;;;###autoload
(defun reverse-im-translate-region (start end &optional force)

Check warning on line 385 in reverse-im.el

View workflow job for this annotation

GitHub Actions / check (snapshot, false)

docstring wider than 80 characters
"Translate active region from START to END. FORCE translate even if the region isn't active."
(interactive "r")
(when (or (region-active-p)
Expand Down Expand Up @@ -414,7 +417,7 @@
(goto-char pos1))))

;;;###autoload
(defun reverse-im-translate-word (arg)

Check warning on line 420 in reverse-im.el

View workflow job for this annotation

GitHub Actions / check (snapshot, false)

docstring wider than 80 characters
"Translate word before the point. With prefix ARG translates ARG words instead of the last one, if ARG is 0 - translate until the beginning of line."
(interactive "p")
(if (zerop arg)
Expand Down
Loading