0

I use evil-mode on emacs, and recently I started using eshell, I really like how I can leave insert mode and move on the eshell buffer to copy content or other goodies, but when entering insert mode again it does it in the cursor current position, what I would like is that when I enter insert mode automatically move the currsor to the prompt line (last line, end of line).

What I did was:

(add-hook 'eshell-mode-hook
      (lambda()
         (define-key evil-normal-state-map (kbd "i") (lambda () (interactive) (evil-goto-line) (evil-append-line nil)))))

However it applies this mapping in all other buffers, I just want to make it active on an eshell buffer.

How to define a key binding that works differently in eshell?

Stefan
  • 27,908
  • 4
  • 53
  • 82
zzantares
  • 341
  • 3
  • 12
  • Does this link help any?: http://stackoverflow.com/a/26587651/2112489 Perhaps there are some other evil local maps you can use. I don't use evil, but that's the general idea -- i.e., using local stuff. – lawlist May 14 '16 at 19:08

2 Answers2

3

The current accepted answer satisfies the stated requirements, but has two major limitations:

  1. It only triggers when entering insert mode with i. Jumping to the last line with I, A, a, or any custom functions/keybindings would require additional scripting.
  2. If point is already on the last line (e.g., when editing the current command), there is no way to enter insert mode at the current position; i is effectively rebound to A.

The first limitation can be eliminated by hooking first on eshell-mode then second on evil-insert-state-entry.

The second limitation can be addressed by setting point's position based first on the line number and second on the read-only text-property:

  1. If point is not already on the last line, then it is moved to point-max (which is always read-write).
  2. Otherwise if the text at point is read-only, point is moved to the position in the current line where the read-only text-property changes.

The following code negates the limitations of the accepted answer.

(defun move-point-to-writeable-last-line ()
  "Move the point to a non-read-only part of the last line.
If point is not on the last line, move point to the maximum position
in the buffer.  Otherwise if the point is in read-only text, move the
point forward out of the read-only sections."
  (interactive)
  (let* ((curline (line-number-at-pos))
         (endline (line-number-at-pos (point-max))))
    (if (= curline endline)
        (if (not (eobp))
            (let (
                  ;; Get text-properties at the current location
                  (plist (text-properties-at (point)))
                  ;; Record next change in text-properties
                  (next-change
                   (or (next-property-change (point) (current-buffer))
                       (point-max))))
              ;; If current text is read-only, go to where that property changes
              (if (plist-get plist 'read-only)
                  (goto-char next-change))))
      (goto-char (point-max)))))

(defun move-point-on-insert-to-writeable-last-line ()
  "Only edit the current command in insert mode."
  (add-hook 'evil-insert-state-entry-hook
        'move-point-to-writeable-last-line
        nil
        t))

(add-hook 'eshell-mode-hook
      'move-point-on-insert-to-writeable-last-line)
  • But is there a way to make it impossible to enter insert mode in the middle of the shell prompt? Have you ever used `:term` in neovim? that's exactly what I want in eshell, it doesn't let you modify things you shouldn't. – zzantares Oct 31 '17 at 04:51
  • @ZzAntáres I have not yet found out how to detect if the point is in a read-only part of a buffer. Once I have discovered how to do so I intend to update my code and my answer to this question. – lafrenierejm Oct 31 '17 at 19:11
  • @ZzAntáres [Revision 4](https://stackoverflow.com/posts/46937891/revisions) of the answer addresses entering insert mode in the shell prompt. – lafrenierejm Nov 01 '17 at 20:37
2

Thanks to @lawlist for pointing me in the right direction, the solution is just as easy as:

;; Insert at prompt only on eshell
(add-hook 'eshell-mode-hook
      '(lambda ()
         (define-key evil-normal-state-local-map (kbd "i") (lambda () (interactive) (evil-goto-line) (evil-append-line nil)))))

Thanks!

zzantares
  • 341
  • 3
  • 12
  • 1
    You almost never want to quote a lambda form like that, as it can't be byte-compiled. `lambda` is self-quoting (with `function`), so just remove the `'`. (Also not the best idea to put a lambda form on a hook variable at all, as updating such values is usually awkward. Named functions are easier to maintain.) – phils Nov 01 '17 at 22:32
  • Thanks! didn't know that!, so this is not byte compiled? `(add-hook 'after-init-hook #'(lambda () (setq gc-cons-threshold 800000)))` – zzantares Nov 03 '17 at 04:15
  • `(lambda ...)` and `#'(lambda ...)` are equivalent, both quoted with `function` (implicitly and explicitly respectively). `'(lambda ...)` is different, being quoted with `quote` and thus cannot be byte-compiled. – phils Nov 05 '17 at 04:57