Related article
Taming term-mode // Bodacious Blog

Summary

Basically, I am creating a program-agnostic method of rebinding keys.

Given an arbitrary shell command, such as mc (midnight commander), I can create my own keybindings and macros for this program as if I was customising an emacs mode.

It’s all part of trying to control everything from emacs.

More specifically, this article is about creating buffer-local minor modes which are enabled only for specific commands started through term-mode.

Create a snippet for creating buffer-local minor modes

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# -*- mode: snippet -*-
# name: minor mode for overriding keybindings
# group:
# key: mm
# --
(defvar ${1:my}-minor-mode-map (make-sparse-keymap)
  "Keymap for \`$1-minor-mode'.")
;; (makunbound '$1-minor-mode)
(defvar-local $1-minor-mode nil)

(define-minor-mode $1-minor-mode
  "A minor mode so that my key settings override annoying major modes."
  :global nil
  :init-value nil
  :lighter " $1-minor-mode"
  :keymap $1-minor-mode-map)

;; We don't want a global mode
;; (define-globalized-minor-mode global-$1-minor-mode $1-minor-mode $1-minor-mode)

;; This is definitely a bad solution
(define-key $1-minor-mode-map (kbd "<up>") (lm (tsk "C-p")))
(define-key $1-minor-mode-map (kbd "<down>") (lm (tsk "C-n")))
(define-key $1-minor-mode-map (kbd "<right>") (lm (tsk "M-3")))
$0
(provide '$1-minor-mode)

Create the midnight commander minor mode

This will create a minor mode which rebinds up, down and right to useful midnight commander bindings

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
(defvar mc-minor-mode-map (make-sparse-keymap)
  "Keymap for `mc-minor-mode'.")
;; (makunbound 'mc-minor-mode)
(defvar-local mc-minor-mode nil)

(define-minor-mode mc-minor-mode
  "A minor mode so that my key settings override annoying major modes."
  :global nil
  :init-value nil
  :lighter " mc-minor-mode"
  :keymap mc-minor-mode-map)

;; We don't want a global mode
;; (define-globalized-minor-mode global-mc-minor-mode mc-minor-mode mc-minor-mode)

;; This is definitely a bad solution
(define-key mc-minor-mode-map (kbd "<up>") (lm (tsk "C-p")))
(define-key mc-minor-mode-map (kbd "<down>") (lm (tsk "C-n")))
;; (define-key mc-minor-mode-map (kbd "<right>") (lm (tsk "M-3")))
(define-key mc-minor-mode-map (kbd "<left>") (lm (tsk "PgUp")))
(define-key mc-minor-mode-map (kbd "<right>") (lm (tsk "PgDn")))

(provide 'mc-minor-mode)

Create the support functions

This is just a shorthand for creating lambdas and a function to make tmux send some key bindings.

1
2
3
4
5
6
7
8
9
(defmacro lm (&rest body)
  "Interactive lambda with no arguments."
  `(lambda () (interactive) ,@body))

(defun tm/send-keys (keys)
  (interactive "P")
  ;; (sh-notty (concat "tmux send-keys " (q keys)))
  (sh-notty (concat "tmux send-keys " keys)))
(da 'tsk 'tm/send-keys)

Create the emc script

1
2
3
4
#!/bin/bash
export TTY

sp -e "(term-nsfa $(aqf "midnight-commander"))(mc-minor-mode)"

Demonstration

asciinema recording

Enhancements

When improving term mode (see taming-term), I put the logic for starting minor modes into the (my/term) wrapper function along with the option to close the emacs frame upon the process ending.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
#!/bin/bash
export TTY

# sp -e "(term-nsfa $(aqf "midnight-commander"))(mc-minor-mode)"

# sp -she midnight-commander -a "(mc-minor-mode)"

# Is set the modes here now
# vim +/"(if (string-equal program \"midnight-commander\")" "$EMACSD/config/my-term.el"

sp -she midnight-commander

It looks like this now. See taming-term for details.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
(defun my/term (program &optional closeframe)
  (interactive)
  (with-current-buffer (term program)
    (message (concat "term " (str termframe)))
    (if closeframe
        ;; (defset-local termframe-local (with-current-buffer "*scratch*" termframe))
        (defset-local termframe-local termframe))
    (if (string-equal program "midnight-commander")
        (mc-minor-mode)))
  ;; (add-hook 'after-make-frame-functions 'set-termframe)
  )