6

My default shell is eshell. I just discovered iimage-mode that allows me to display images in buffers that contain some reference to an image file. Unfortunately, I doesn't seem to have any effect when it is enabled in eshell.

In any other buffer something like file:///home/rekado/Pictures/test.png is replaced with an inlined image loaded from the file test.png. In an eshell buffer, however, I cannot seem to make this work.

I tried echoing the string file:///home/rekado/Pictures/test.png, but this just results in a boring string and no pretty test image. I also made sure to use /bin/echo instead of the elisp/eshell implementation of echo, but with no success.

When I disable and reenable iimage-mode it works all right (the file name is replaced with an inline image), but that's a rather inconvenient workaround.

If there's no way to make eshell work with iimage-mode maybe there is another method to display images right in the eshell buffer?

4 Answers 4

6

Based on what happens behind the scenes when you call iimage-mode, you can define a function that takes care of displaying images for you, and have Emacs run this function every time you issue an echo command in Eshell:

(defun iimage-mode-refresh (command args)
  (when (string= command "echo")
    (let ((image-path (cons default-directory iimage-mode-image-search-path))
          file)
      (with-silent-modifications
        (save-excursion
          (eshell-previous-prompt 0) ; Start scanning from most recent prompt
                                     ; to avoid having to re-display all images.
          (dolist (pair iimage-mode-image-regex-alist)
            (while (re-search-forward (car pair) nil t)
              (when (and (setq file (match-string (cdr pair)))
                         (setq file (locate-file file image-path)))
                (add-text-properties (match-beginning 0) (match-end 0)
                                     `(display ,(create-image file)
                                               modification-hooks
                                               (iimage-modification-hook)))))))))))

(add-hook 'eshell-named-command-hook 'iimage-mode-refresh)

The main difference between this function and the function that gets run when you enable iimage-mode (iimage-mode-buffer) is that it does not check the whole buffer for image references. Instead, it starts scanning from the most recent prompt, which means that calling the function does not become more expensive as the *eshell* buffer grows in size. Each invocation of the function will cause only a single additional image (i.e., the most recent image you requested) to be displayed.

Note that existing images stay visible, i.e., they are not "reset" to strings representing file paths.

4
  • 1
    Thanks, this doesn't look bad. I think it would be even better to turn this into a separate eshell command instead of a hook that's linked to the execution of echo.
    – user2005
    Nov 12, 2014 at 14:56
  • 1
    It's a pity that iimage.el doesn't expose some other function that we could cleanly reuse. As it stands, iimage-mode-refresh is almost a complete copy of iimage-mode-buffer.
    – user2005
    Nov 12, 2014 at 14:59
  • @rekado "I think it would be even better...": You might want to consider adding your own answer with a modified version of the code. It sounds like you're up to the task. Also, we should delete the first four comments, as the information they contain has become misleading.
    – itsjeyd
    Nov 12, 2014 at 16:37
  • I just posted an answer in which eshell/cat is extended to work with images.
    – user2005
    Mar 2, 2015 at 15:06
3

Based on itsjeyd's answer I advised eshell/cat to display images when an argument is the filename of an image file supported by iimage-mode.

(defun my/iimage-mode-refresh--eshell/cat (orig-fun &rest args)
  "Display image when using cat on it."
  (let ((image-path (cons default-directory iimage-mode-image-search-path)))
    (dolist (arg args)
      (let ((imagep nil)
            file)
        (with-silent-modifications
          (save-excursion
            (dolist (pair iimage-mode-image-regex-alist)
              (when (and (not imagep)
                         (string-match (car pair) arg)
                         (setq file (match-string (cdr pair) arg))
                         (setq file (locate-file file image-path)))
                (setq imagep t)
                (add-text-properties 0 (length arg)
                                     `(display ,(create-image file)
                                               modification-hooks
                                               (iimage-modification-hook))
                                     arg)
                (eshell-buffered-print arg)
                (eshell-flush)))))
        (when (not imagep)
          (apply orig-fun (list arg)))))
    (eshell-flush)))

(advice-add 'eshell/cat :around #'my/iimage-mode-refresh--eshell/cat)

This allows me to display images right in the eshell buffer and to concatenate images and text alike. There's probably a more elegant way to do this (and a safer way in case iimage-mode is not enabled), but I'll just leave it like this for now until it bites me.

0

I have the similar problem at markdown-mode ,when I want to display images in xxx.md alive. I just glance at the iimage.el , And I find out (iimage-mode-buffer iimage-mode) this works fine for me.

(defun my-md-screenshot ()
   "Take a screenshot into a time stamped unique-named file in the
same directory as the org-buffer and insert a link to this file."
(interactive)
(setq filename
    (concat
     (make-temp-name
      (concat (file-name-base)
              "_"
              (format-time-string "%Y%m%d_%H%M%S_")) ) ".png"))
(call-process-shell-command "screencapture" nil nil nil
                          (concat " -i ~/public/img/" filename))
(insert (concat "![image](/public/img/" filename ")"))
(iimage-mode-buffer iimage-mode)
)
0

I did something similar, but using the standard library image capabilities of emacs instead of iimage mode. As a result, you don't need to have the mode enabled:

;; To know if I need to print as image or I need to default to the default eshell/cat
(defun my-is-imagep (filename)
  (let ((extension (file-name-extension filename))
        (image-extensions '("png" "jpg" "bmp")))
    (member extension image-extensions)))

;; Creates a space with display properties. Feel free to change `eshell/println` to `insert` and use it in a normal emacs buffer, it will inline the path given in `file`.
(defun my-print-image-eshell (file)
  (eshell/printnl (propertize " " 'display (create-image file))))


;; If image, use `my-print-image-eshell`. Otherwise, just use `eshell/cat`.
(defun eshell/mycat (&rest args)
  (interactive)
  (mapc (lambda (arg)
          (if (my-is-imagep arg)
              (my-print-image-eshell arg)
            (eshell/cat arg)
            )
          ) (-flatten args))
  nil)

The key is to create some bogus output (a space in this case), add some magic display properties (read more here) and use eshell/printnl to print that magic space that displays as your image.

Then, you can alias cat mycat $* in your .emacs/eshell/alias file.

You'll quickly face the problem of very big images slowing down emacs and not fitting in the screen, which forces the point to scroll down; they won't even show. This is some quick and dirty code that generates a thumbnail sized image for them and displays that instead. It could give you some ideas to implement something more robust (checking for errors, etc.). It requires imagemagick to be installed. If you emacs was compiled with support for it, you don't need the shell commands and you can use library functions instead:

(defun my-is-imagep (filename)
  (let ((extension (file-name-extension filename))
        (image-extensions '("png" "jpg" "bmp")))
    (member extension image-extensions)))

(defun my-get-image-width (filename)
  (string-to-number
   (shell-command-to-string
    (format "convert '%s' -ping -format \"%%w\" info:" filename))))

(defun my-rescale-image (filename)
  "Rescale an image to a maximum width, or leave untouched if already small. Returns the new file path."
  (let ((file (make-temp-file "resized_emacs"))
        (max-width 350))
    (if (> (my-get-image-width filename) max-width)
        (progn
          (shell-command-to-string
           (format "convert -resize %dx '%s' '%s'" max-width filename file))
          file)
      filename)))

(defun my-print-image-eshell (file)
  (eshell/printnl (propertize " " 'display (create-image file))))

(defun eshell/mycat (&rest args)
  (interactive)
  (mapc (lambda (arg)
          (if (my-is-imagep arg)
              (my-print-image-eshell (my-rescale-image arg))
            (eshell/cat arg)
            )
          ) (-flatten args))
  nil)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.