Related articles
minor mode wrappers for shell commands // Bodacious Blog

Here I make an emacs mode for Dwarf Fortress and create some keybindings in emacs which runs a generated tcl/expect script over Dwarf Fortress using tmux to attach itself to the tty inside term- mode.

Write the emacs lisp

This macro creates minor modes from a list of shell command names.

These shell commands may be arbitrary terminal programs.

 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
27
28
29
30
31
32
33
34
(defmacro defcmdmode (cmd)
  (setq cmd (str cmd))
  (let* ((cmdslug (slugify (str cmd)))
         (modestr (concat cmdslug "-term-mode"))
         (modesym (str2sym modestr))
         (mapsym (str2sym (concat modestr "-map"))))
    `(progn
       (defvar ,mapsym (make-sparse-keymap)
         ,(concat "Keymap for `" modestr "'."))
       (defvar-local ,modesym nil)

       (define-minor-mode ,modesym
         ,(concat "A minor mode for the '" cmd "' term command.")
         :global nil
         :init-value nil
         :lighter ,(s-upcase cmdslug)
         :keymap ,mapsym)
       (provide ',modesym))))

(cl-loop for cmd in '(mc df-bay12) do (eval `(defcmdmode ,cmd)))

(defalias 'midnight-commander-term-mode 'mc-term-mode)

;; 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-term-mode-map (kbd "<up>") (lm (tsk "C-p")))
(define-key mc-term-mode-map (kbd "<down>") (lm (tsk "C-n")))
;; (define-key mc-term-mode-map (kbd "<right>") (lm (tsk "M-3")))
(define-key mc-term-mode-map (kbd "<left>") (lm (tsk "PgUp")))
(define-key mc-term-mode-map (kbd "<right>") (lm (tsk "PgDn")))

(define-key df-bay12-term-mode-map (kbd "M-!") (lm (tsk "Bob")))

I extended the my/term function to look for modes for the program that is run

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
(defun my/term (program &optional closeframe modename)
  (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))
    (let ((modefun (str2sym (concat (slugify (or modename program)) "-term-mode"))))
      (if (function-p modefun)
          (funcall modefun)))
  ;; (add-hook 'after-make-frame-functions 'set-termframe)
    ))

The emacs wrapper script e was extended so that the program name is inferred

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
-shE|-E) {
    shift
    script="$(nsfa -E "$1" t)"
    shift
    if test -n "$1"; then
        modename=" \"$1\""
    fi

    bufname="$(printf -- "%s" "term $script" | slugify)"

    elisp="(my/term \"$script\" t $modename)$elisp"
}
;;

Demonstration

Now I can create bindings such as the following which will bind M-! to a function which tells tmux to type “Bob” into the terminal.

Now I can name my dwarves “Bob” with the click of a button!

But I’m going to create some tcl/expect scripts in the future.

Dwarf Fortress will be truly automated!

1
(define-key df-bay12-term-mode-map (kbd "M-!") (lm (tsk "Bob")))

Even cooler demonstration; Generating an expect script bound to emacs keys!

The generated expect script works on its own!

1
2
# This generates the expect script
x -tmw -se "222" -sl 0.2 -s 8 -c m -e "to continue" -sl 1 -s "\\"

Emacs DF mode key binding

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
(defun df-create-world ()
    (interactive)
  (sh
   (concat "x -tma \"" (tm-get-window) "\""
           " -se 222"
           " -sl 0.2"
           " -s 8"
           " -c m"
           " -e \"to continue\""
           " -sl 1"
           " -s \"\\\\\""
           " -e \"Enter title\""
           " -se t"
           " -s Shane"
           " -c m")))

(define-key df-bay12-term-mode-map (kbd "M-$") #'df-create-world)

Generated output

 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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#!/home/shane/scripts/tm-expect -f
log_user 0
#trap sigwinch and pass it to the child we spawned
trap {
    set rows [stty rows]
    set cols [stty columns]
    stty rows $rows columns $cols < $spawn_out(slave,name)
} WINCH

proc getctrl {char} {
    set ctrl [expr ("$char" & 01xF)]
    return $ctrl
}

set force_conservative 0
if {$force_conservative} {
    set send_slow {1 .1}
    proc send {ignore arg} {
        sleep .1
        exp_send -s -- $arg
    }
}

# For send -h
set send_human {.4 .4 .2 .5 100}
set ::env(PATH) "/home/shane/scripts:/home/shane/var/smulliga/source/git/google-cloud-sdk/google-cloud-sdk/bin:/home/shane/.cargo/bin:/usr/local/toolchains/clang+llvm-9.0.0-x86_64-linux-gnu-ubuntu-16.04/bin:/usr/local/toolchains/clang/bin:/usr/local/toolchains/boost/bin:/home/shane/.nix-profile/bin:/home/shane/.cask/bin:/home/shane/.opam/system/bin:/home/shane/notes2018/ws/codelingo/scripts:/home/shane/scripts:scripts:/home/shane/.pyenv/bin:/opt/apache-maven-3.5.3/bin:/home/shane/bin:/usr/local/racket/bin:/home/shane/local/emacs26/bin:/home/shane/local/bin:/usr/sbin:/sbin:/usr/local/sbin:/usr/local/bin:/usr/sbin/sbin:/bin:/usr/games:/usr/local/games:/snap/bin:/usr/lib/jvm/java-8-oracle/bin:/usr/lib/jvm/java-8-oracle/db/bin:/usr/lib/jvm/java-8-oracle/jre/bin:/home/shane/source/git/fzf/bin/fzf:/home/shane/go/bin:/home/shane/go1.6.2/bin:/downloads/node-v9.9.0-linux-x64/bin:/home/shane/node_modules/.bin/:/home/shane/.cabal/bin:/home/shane/.local/bin:/home/shane/.config/composer/vendor/bin:/usr/local/go/bin:/home/shane/dump/downloads/node-v9.9.0-linux-x64/bin:/home/shane/.config/composer/vendor/bin/home/shane/source/git/fzf/bin/fzf/home/shane/source/git/fzf/bin/fzf:/usr/bin"
set timeout -1
match_max 100000
set SHELL "tmux"
set win [exec tm-new-hidden-link-window "@1628"]
spawn "$SHELL" "attach" "-t" "$win"
sleep 5
sleep 0.1
send -- "2"
expect "."
sleep 0.2
send -- "2"
expect "."
sleep 0.2
send -- "2"
expect "."
sleep 0.2
sleep 0.2
send -- "8"
send -- \015
expect -exact "to continue"
sleep 1
send -- "\\"
expect -exact "Enter title"
send -- "t"
expect "."
sleep 0.2
send -- "Shane"
sleep 0.2
close