1

In term-mode I've configured the keybindings for term-line-mode and term-char-mode as follows:

(setq term-bind-key-alist
      (list (cons "C-c C-j" '(lambda () (interactive)
                               (term-line-mode)
                               (evil-normal-state)))
            (cons "C-c C-k" '(lambda () (interactive)
                               (term-char-mode)
                               (evil-emacs-state)
                               (end-of-buffer)))

This gives the expected behavior when I type "C-c C-j" but only term-char-mode seems to be called when I type "C-c C-k". All of them are called if I type "C-c C-k" for a 2nd time. How can I get them to work on the first key press?


Edit 1

Here's the full configuration for term. Also, I mistakenly thought that this is a variable for term-mode, when it's actually a variable defined in multi-term. However, if I place this in the multi-term configuration it produces the same behavior

(use-package term
  :after helm
  :config
  (defun expose-global-binding-in-term (binding)
    (define-key term-raw-map binding
      (lookup-key (current-global-map) binding)))
  (expose-global-binding-in-term (kbd "C-x"))
  (define-key term-raw-map (kbd "M-x") 'helm-M-x)
  (setq term-scroll-to-bottom-on-output t)
  (setq term-scroll-show-maximum-output t))

(use-package multi-term
  :bind (("<C-next>" . multi-term-next)
         ("<C-prior>" . multi-term-prev)
         ("<f1>" . multi-term))
  :config
  (require 'multi-term-ext)
  (setq multi-term-program "/bin/bash")
  (setq term-bind-key-alist
        (list (cons "C-c C-c" 'term-interrupt-subjob)
              (cons "C-z" 'term-stop-subjob)
              (cons "C-l" 'term-send-raw)
              (cons "C-r" 'term-send-raw)
              (cons "C-s" 'term-send-raw)
              (cons "M-f" 'term-send-forward-word)
              (cons "M-b" 'term-send-backward-word)
              (cons "C-c C-j" '(lambda () (interactive)
                                 (term-line-mode)
                                 (evil-normal-state)))
              (cons "C-c C-k" '(lambda () (interactive)
                                 (evil-emacs-state)
                                 (term-char-mode)
                                 (end-of-buffer)))
              (cons "M-DEL" 'term-send-backward-kill-word)
              (cons "M-d" 'term-send-forward-kill-word)
              (cons "<C-left>" 'term-send-backward-word)
              (cons "<C-right>" 'term-send-forward-word)
              (cons "C-y" 'term-paste))))

Edit 2

I've modified the multi-term configuration so that these keybindings are outside of term-bind-key-alist but I'm still getting the same behavior.

(use-package multi-term
  :bind (("<C-next>" . multi-term-next)
         ("<C-prior>" . multi-term-prev)
         ("<f1>" . multi-term)
         ("C-c C-j" . (lambda () (interactive)
                        (term-line-mode)
                        (evil-normal-state)))
         ("C-c C-k" . (lambda () (interactive)
                        (evil-emacs-state)
                        (term-char-mode)
                        (end-of-buffer))))
  :config
  (require 'multi-term-ext)
  (setq multi-term-program "/bin/bash")
  (setq term-bind-key-alist
    (list (cons "C-c C-c" 'term-interrupt-subjob)
          (cons "C-z" 'term-stop-subjob)
          (cons "C-l" 'term-send-raw)
          (cons "C-r" 'term-send-raw)
          (cons "C-s" 'term-send-raw)
          (cons "M-f" 'term-send-forward-word)
          (cons "M-b" 'term-send-backward-word)
          (cons "M-DEL" 'term-send-backward-kill-word)
          (cons "M-d" 'term-send-forward-kill-word)
          (cons "<C-left>" 'term-send-backward-word)
          (cons "<C-right>" 'term-send-forward-word)
          (cons "C-y" 'term-paste))))

Edit 3

The version below now works. The trick, as pointed out by Phil in his answer, is that term-bind-key-alist doesn't bind keys for term-mode-map (used by term-line-mode). I believe it only binds keys for term-raw-map, or some combination of that and term-raw-escape-map. The reason that term-char-mode was still called (and the other two functions weren't) when using "C-c C-k" in term-line-mode was that it's the default keybinding in term-line-mode.

(use-package term
  :after (helm evil)
  :bind (:map term-mode-map
              ("C-c C-k" . (lambda () (interactive)
                             (evil-emacs-state)
                             (term-char-mode)
                             (goto-char (point-max)))))
  :config
  (defun expose-global-binding-in-term (binding)
    (define-key term-raw-map binding
      (lookup-key (current-global-map) binding)))
  (expose-global-binding-in-term (kbd "C-x"))
  (define-key term-raw-map (kbd "M-x") 'helm-M-x)
  (setq term-scroll-to-bottom-on-output t)
  (setq term-scroll-show-maximum-output t))

(use-package multi-term
  :bind (("<C-next>" . multi-term-next)
         ("<C-prior>" . multi-term-prev)
         ("<f1>" . multi-term))
  :config
  (require 'multi-term-ext)
  (setq multi-term-program "/bin/bash")
  (setq term-bind-key-alist
        (list (cons "C-c C-c" 'term-interrupt-subjob)
              (cons "C-z" 'term-stop-subjob)
              (cons "C-l" 'term-send-raw)
              (cons "C-r" 'term-send-raw)
              (cons "C-s" 'term-send-raw)
              (cons "M-f" 'term-send-forward-word)
              (cons "M-b" 'term-send-backward-word)
              ;; I've defined "C-c C-k" in term's configuration since it must be defined in
              ;; term-mode-map.  It could be placed here as a define-key in config, but this way we
              ;; get to keep use-package's typical syntax.
              (cons "C-c C-j" '(lambda () (interactive)
                                 (term-line-mode)
                                 (evil-normal-state)))
              (cons "M-DEL" 'term-send-backward-kill-word)
              (cons "M-d" 'term-send-forward-kill-word)
              (cons "<C-left>" 'term-send-backward-word)
              (cons "<C-right>" 'term-send-forward-word)
              (cons "C-y" 'term-paste))))
MattHusz
  • 177
  • 8

1 Answers1

1

Emacs 26.1 does not define term-bind-key-alist. If you don't show us what you're doing with that, we can't see what the problem is.

That said, it sounds like you're only binding keys in term-raw-map, and not in term-mode-map.

n.b. For clarity, I do not believe "lambda only calls 1 of several functions called in body" is something which is happening. I think you'll find that your custom commands are not being called at all when you are in line mode (because the keys sequence in question is not bound to your custom command in that scenario).


Try this.

(with-eval-after-load "term"
  (require 'evil)

  (defun my-term-line-mode ()
    "Switch to `term-line-mode' and enable `evil-normal-state'."
    (interactive)
    (term-line-mode)
    (evil-normal-state))

  (defun my-term-char-mode ()
    "Switch to `term-char-mode' and enable `evil-emacs-state'."
    (interactive)
    (term-char-mode)
    (evil-emacs-state))

  (define-key term-mode-map (kbd "C-c C-j") 'my-term-line-mode)
  (define-key term-mode-map (kbd "C-c C-k") 'my-term-char-mode)
  (define-key term-raw-escape-map (kbd "C-j") 'my-term-line-mode)
  (define-key term-raw-escape-map (kbd "C-k") 'my-term-char-mode))
phils
  • 48,657
  • 3
  • 76
  • 115
  • I've edited the original post to add more context. Also please see my note about this actually being a variable for multi-term. My mistake for incorrectly assuming this was part of term mode. – MattHusz Feb 23 '19 at 07:41
  • In that case it sounds like `multi-term` is using it to bind keys in `term-raw-map`, and not in `term-mode-map`. I suggest that you ignore this variable, and just bind the keys you want in the keymaps you need using the standard `define-key` method. – phils Feb 23 '19 at 08:55
  • i.e. I suspect the answer to your question is simply that `term-bind-key-alist` doesn't actually do what you thought it did. – phils Feb 23 '19 at 08:56
  • I pulled these keybindings out of `term-bind-key-alist` and edited the question to reflect that. Unfortunately the behavior seems to be identical. – MattHusz Feb 23 '19 at 18:20
  • I've no idea what `use-package` does with that new config, but it will again surely only bind the keys in (at most) *one* of the two keymaps you're interested in. You need to bind these particular key sequences in *both* `term-mode-map` and `term-raw-map`, because they need to work in *both* line mode and char mode. – phils Feb 23 '19 at 22:12
  • In fact I was wrong -- in char mode, if overriding sequences starting with the escape key `C-c`, one actually uses `term-raw-escape-map`. I've added code to the answer. – phils Feb 23 '19 at 22:31
  • Ok the final version in my edits now works. I wasn't able to get `term-raw-escape-map` to work for some reason.. In any event you're right that I was failing to bind anything in `term-mode-map`. – MattHusz Feb 24 '19 at 04:10
  • If you run `emacs -Q` and evaluate the code I suggested (and `M-x load-file RET ~/.emacs.d/elpa/evil*/evil-autoloads.el`), then it should be working. – phils Feb 24 '19 at 05:17