11 KiB
Emacs Nemesis System
,#+ARROYO_MODULE_WANTS: cce/evil_mode.org ,#+ARROYO_MODULE_WANTS: cce/exwm_evil_firefox.org ,#+ARROYO_MODULE_WANTS: cce/exwm.org ,#+ARROYO_EMACS_MODULE: nemesis
No, not that Nemesis System. This Nemesis System brings more Evil in to my Emacs world. It's a simple method to build Evil Mode bindings for my X11 applications running in EXWM, inspired by exwm-evil-firefox. I really like this paradigm so I am spending some time generally trying to adapt this to most of my applications. A Hypergoal is an OS where all of my GUI applications, and web apps have customised overlay bindings that I can maintain. Right now the goal is not to build fully functional bindings but to make normal mode useful for navigating these applications. When I am in a focused text box or an interactive page, it's fine and rational to drop in to insert mode.
Why couldn't I eventually also have smarter modes for certain web-sites, as well? (need TabFS or fix Plasma Browser Integration)
(provide 'cce/nemesis)
Nemesis: a library which can generate EXWM+Evil minor modes at runtime
Nemesis exists in three parts:
A macro which can be used to generate interactive fake-key functions
- State "DONE" from "NEXT" [2021-06-25 Fri 13:58]
this is taken from my macro define-evil-firefox-key
in exwm-evil-firefox
Putting ESC
in exwm-input-prefix-keys
will make all other EXWM applications pretty sad, but I fnorded that a while ago, and this will help me fnord it the rest of the way.
(defun nemesis-intercept-next-ret ()
(interactive)
(setq-local nemesis-next-ret-goto-normal t))
(defun nemesis-intercept-return ()
(interactive)
(exwm-input--fake-key (aref (kbd "<return>") 0))
(when (and (boundp 'nemesis-next-ret-goto-normal)
nemesis-next-ret-goto-normal)
(nemesis-normal-state)
(setq-local nemesis-next-ret-goto-normal nil)))
(evil-define-key 'insert exwm-mode-map (kbd "<return>") 'nemesis-intercept-return)
(evil-define-key 'normal exwm-mode-map (kbd "<return>") 'nemesis-intercept-return)
(push (aref (kbd "<return>") 0) exwm-input-prefix-keys)
(defun nemesis-fake-key (&rest args)
(eval (macroexpand (apply #'nemesis--fake-key args))))
(defun nemesis--fake-key (app-mode state command-name input-key mapped-key insert-after &optional docstring)
(let ((map-name (intern (format "nemesis-%s-mode-map" app-mode)))
(fname (intern (format "nemesis-%s-%s" app-mode command-name))))
`(progn
(defun ,fname ()
,docstring
(interactive)
(exwm-input--fake-key (aref ,mapped-key 0))
,(when insert-after
'(nemesis-insert-state)))
(evil-define-key* 'normal ,map-name ,input-key #',fname)
,(when insert-after
`(advice-add #',fname :after #'nemesis-intercept-next-ret)))))
(defun nemesis-normal-state ()
"Pass every key directly to Emacs."
(interactive)
(setq-local exwm-input-line-mode-passthrough t)
(evil-normal-state))
(defun nemesis-insert-state ()
"Pass every key to firefox."
(interactive)
(setq-local exwm-input-line-mode-passthrough nil)
(evil-insert-state))
(defun nemesis-exit-visual ()
"Exit visual state properly."
(interactive)
;; Unmark any selection
(exwm-firefox-core-left)
(exwm-firefox-core-right)
(exwm-firefox-evil-normal))
(defun nemesis-visual-change ()
"Change text in visual mode."
(interactive)
(exwm-firefox-core-cut)
(exwm-firefox-evil-insert))
A minor-mode definition which has keybindings defined within
- State "DONE" from "NEXT" [2021-06-25 Fri 13:58]
This is embedded in the macro itself below, but the definition looks something like this
(pp (macroexpand
'(nemesis-make-mode-form "element" '("Element")
'((:cmd "paste" :state normal :user-key (kbd "p") :mapped-key (kbd "C-v"))
(:cmd "room-list" :state normal :user-key (kbd "C-k") :mapped-key (kbd "C-k") :insert-after t)))))
(progn (progn (setq nemesis-element-mode-map (make-sparse-keymap)) (define-minor-mode nemesis-element-map nil nil nil nemesis-element-mode-map (if nemesis-element-map (progn (nemesis-normal-state)))) (setq nemesis-element-class-name '("Element")) (defun nemesis-element-evil-activate-maybe nil (interactive) (if (member exwm-class-name nemesis-element-class-name) (nemesis-element-map 1)))) (seq-map (lambda (defi) (let ((cmd-name (plist-get defi :cmd)) (state (plist-get defi :state)) (user-key (eval (plist-get defi :user-key))) (mapped-key (eval (plist-get defi :mapped-key))) (insert-after\? (plist-get defi :insert-after))) (nemesis-fake-key "element" state cmd-name user-key mapped-key insert-after\?))) '((:cmd "paste" :state normal :user-key (kbd "p") :mapped-key (kbd "C-v")) (:cmd "room-list" :state normal :user-key (kbd "C-k") :mapped-key (kbd "C-k") :insert-after t))) (progn (define-key nemesis-element-mode-map [remap evil-exit-visual-state] 'nemesis-element-exit-visual) (define-key nemesis-element-mode-map [remap evil-normal-state] 'nemesis-normal-state) (define-key nemesis-element-mode-map [remap evil-force-normal-state] 'nemesis-normal-state) (define-key nemesis-element-mode-map [remap evil-insert-state] 'nemesis-insert-state) (define-key nemesis-element-mode-map [remap evil-insert] 'nemesis-insert-state) (define-key nemesis-element-mode-map [remap evil-substitute] 'nemesis-insert-state) (define-key nemesis-element-mode-map [remap evil-append] 'nemesis-insert-state)))
A macro which can generate the minor-mode
- State "DONE" from "NEXT" [2021-06-25 Fri 13:59]
This thing returns a progn
which will be evaluated by Emacs to set up the bindings.
(defmacro nemesis-make-mode-form (app-name window-classes bindings)
(let ((mode-name (intern (format"nemesis-%s-mode" app-name)))
(mode-map-name (intern (format "nemesis-%s-mode-map" app-name)))
(activate-name (intern (format "nemesis-%s-evil-activate-maybe" app-name)))
(class-var (intern (format "nemesis-%s-class-name" app-name))))
(list 'progn
`(progn
(setq ,mode-map-name (make-sparse-keymap))
(define-minor-mode ,mode-name ""
:keymap ,mode-map-name
:lighter ,app-name
(if ,mode-name
(progn
(nemesis-normal-state))))
(setq ,class-var ,window-classes)
(defun ,activate-name ()
(interactive)
(if (member exwm-class-name ,class-var)
(,mode-name 1)))
(add-hook 'exwm-manage-finish-hook #',activate-name))
`(seq-map (lambda (defi)
(let ((cmd-name (plist-get defi :cmd))
(state (plist-get defi :state))
(user-key (eval (plist-get defi :user-key)))
(mapped-key (eval (plist-get defi :mapped-key)))
(insert-after? (plist-get defi :insert-after)))
(nemesis-fake-key ,app-name state cmd-name user-key mapped-key insert-after?)))
,bindings)
`(progn
;; Bind normal
(define-key ,mode-map-name [remap evil-exit-visual-state] 'nemesis-element-exit-visual)
(define-key ,mode-map-name [remap evil-normal-state] 'nemesis-normal-state)
(define-key ,mode-map-name [remap evil-force-normal-state] 'nemesis-normal-state)
;; ,mode-map-name insert
(define-key ,mode-map-name [remap evil-insert-state] 'nemesis-insert-state)
(define-key ,mode-map-name [remap evil-insert] 'nemesis-insert-state)
(define-key ,mode-map-name [remap evil-substitute] 'nemesis-insert-state)
(define-key ,mode-map-name [remap evil-append] 'nemesis-insert-state)))))
X11 mode bindings
Matrix.org's Element client
More keybindings can be seen/added using C-/
.
(nemesis-make-mode-form "element" '("Element")
'((:cmd "paste" :state normal :user-key (kbd "p") :mapped-key (kbd "C-v"))
(:cmd "cut" :state normal :user-key (kbd "x") :mapped-key (kbd "C-x"))
(:cmd "up" :state normal :user-key (kbd "k") :mapped-key (kbd "<up>"))
(:cmd "down" :state normal :user-key (kbd "j") :mapped-key (kbd "<down>"))
(:cmd "left" :state normal :user-key (kbd "h") :mapped-key (kbd "<left>"))
(:cmd "right" :state normal :user-key (kbd "l") :mapped-key (kbd "<right>"))
(:cmd "room-list" :state normal :user-key (kbd "C-k") :mapped-key (kbd "C-k") :insert-after t)
(:cmd "prev-room" :state normal :user-key (kbd "J") :mapped-key (kbd "M-<down>"))
(:cmd "next-room" :state normal :user-key (kbd "K") :mapped-key (kbd "M-<up>"))
(:cmd "prev-unread-room" :state normal :user-key (kbd "M-K") :mapped-key (kbd "M-S-<up>"))
(:cmd "next-unread-room" :state normal :user-key (kbd "M-J") :mapped-key (kbd "M-S-<down>"))))
Signal's client
Luckily chat clients are basically identical.
(nemesis-make-mode-form "signal" '("Signal")
'((:cmd "paste" :state normal :user-key (kbd "p") :mapped-key (kbd "C-v"))
(:cmd "cut" :state normal :user-key (kbd "x") :mapped-key (kbd "C-x"))
(:cmd "focus-talk" :state normal :user-key (kbd "t") :mapped-key (kbd "C-T") :insert-after t)
(:cmd "room-list" :state normal :user-key (kbd "/") :mapped-key (kbd "C-f") :insert-after t)
(:cmd "prev-room" :state normal :user-key (kbd "J") :mapped-key (kbd "M-<down>"))
(:cmd "next-room" :state normal :user-key (kbd "K") :mapped-key (kbd "M-<up>"))
(:cmd "prev-unread-room" :state normal :user-key (kbd "M-K") :mapped-key (kbd "M-S-<up>"))
(:cmd "next-unread-room" :state normal :user-key (kbd "M-J") :mapped-key (kbd "M-S-<down>"))))