complete-computing-environment/nemesis.org

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>"))))