19

I would like to create a function that does a "quick view" of a file the is under point in dired.

The way that I would like for this to work is that I hold down a function key, which then makes the file visible in a buffer, but when I let off of the key, the buffer closes and the dired buffer returns. I do not want to have to close the temporary buffer with C-x k.

Is there a way to make this functionality in Emacs? It seems possible if I can bind functions to keypress / depress.

4
  • 1
    You cannot bind to press depress events, but this functionality can definitely be hacked in with the use a custom map, and a timer. Commented Sep 25, 2014 at 21:05
  • Are you talking about a new window popping up with a preview? Or would the contents of the buffer be displayed in the dired window while the key is depressed?
    – nispio
    Commented Sep 25, 2014 at 21:25
  • 1
    Are there efforts to allow us to bind to events? I want this feature.
    – wdkrnls
    Commented Oct 22, 2014 at 19:47
  • 1
    Check out dired-display-file and dired-view-file (M-RET and go in doomemacs). Commented May 11, 2023 at 23:07

7 Answers 7

9

As pointed in comments, functions are bound to keys, not events. But to take a step back, I'm not sure I understand why it's important for you to keep the key down while you're (presumably) reading the content of the file. It would also be incompatible with basic (and reasonable) actions like scrolling to get more of it. Not to mention the fact that if it takes a while, it might get uncomfortable :)

What about repeating a key instead ? Something like the following could be the basic skeleton of a functional equivalent:

(defun my-dired-view-file ()
  (interactive)
  (dired-view-file)
  (local-set-key (kbd "<f5>") 'View-quit))

(define-key dired-mode-map (kbd "<f5>") 'my-dired-view-file)

Anyway, I'm more challenging your use-case than answering your question at this point, as this has nothing to do with keypress/depress bindings :)

5
  • I am imagining a long list of files in dired. I am unsure what the contents are of each of the files. if i could F5 to view, let off F5 to stop viewing and be back in dired, then continue to the next candidate, etc. I don't think you've disproved my use case, but you may have suggested a good alternative based on pressing F5 again to stop viewing.
    – Eric Brown
    Commented Sep 25, 2014 at 21:39
  • what you suggest is comparable with functionality in Midnight Commander (F3)
    – Eric Brown
    Commented Sep 25, 2014 at 21:41
  • 1
    The answer submitted by @Sigma is interesting. But when browsing a directory using dired, you can already press v to view the file, and when viewing that file, press q to quit and return to the directory. I guess, pressing the same key for viewing and quitting is easier.
    – Nsukami _
    Commented Sep 25, 2014 at 21:48
  • @LeMeteore thanks for reminding me of dired-view-file ! I have edited my code to leverage it. Yes, I guess in this kind of scenario, not having to move to a different key is important.
    – Sigma
    Commented Sep 25, 2014 at 21:53
  • I was looking for this answer by googling "emacs dired preview". Thank you a lot : it works as expected, and it shows simple emacs mechanics.
    – Hettomei
    Commented Nov 10, 2022 at 8:27
9

Here is my super hacky way to simulate key down/up event binding by taking advantage of timers.

Overall I would suggest going by Sigma's answer, but you asked for a way to close the preview by letting go so I'm obliged to try.

Basically what you can do is bind some function that will be your "keydown" function to a keybinding and inside that action, start an idle timer that that executes a function that is your "keyup" function, as long as you are holding down the given keys, "keydown" function will fire over and over and this will inhibit the idle timers from running. Of course you need to compensate for the fact that the command will fire over and over, likely by rebinding the key to some sort of noop function in your "keydown" function, then rebinding the "keydown" function in the "keyup" function.

So for your use case your "keydown" function will open up a preview buffer with the contents of the file at point and in that preview buffer bind the same key combo to some noop like command. You "keydown" function will also start an idle timer that will delete your preview buffer and restore you back where you were.

Long story short here is the code:

Bind this function to a key combo (I used C-M-v), when you press it down on top of a file name it will open a new buffer displaying the contents of the file at point, when you let go you will be switched back to the original buffer.

(setq lexical-binding t)

(defun quick-view-file-at-point ()
  "Preview the file at point then jump back after some idle time.

In order for this to work you need to bind this function to a key combo, 
you cannot call it from the minibuffer and let it work.

The reason it works is that by holding the key combo down, you inhibit
idle timers from running so as long as you hold the key combo, the 
buffer preview will still display."
  (interactive)
  (let* ((buffer (current-buffer))
         (file (thing-at-point 'filename t))
         (file-buffer-name (format "*preview of %s*" file)))
    (if (and file (file-exists-p file))
        (let ((contents))
          (if (get-buffer file)
              (setq contents (save-excursion
                               (with-current-buffer (get-buffer file)
                                 (font-lock-fontify-buffer)
                                 (buffer-substring (point-min) (point-max)))))
            (let ((new-buffer (find-file-noselect file)))
              (with-current-buffer new-buffer
                (font-lock-mode t)
                (font-lock-fontify-buffer)
                (setq contents (buffer-substring (point-min) (point-max))))
              (kill-buffer new-buffer)))
          (switch-to-buffer (get-buffer-create file-buffer-name))
          (setq-local header-line-format "%60b")
          (delete-region (point-min) (point-max))
          (save-excursion (insert contents))
          (local-set-key (kbd "C-M-v") (lambda () (interactive) (sit-for .2)))
          (run-with-idle-timer
           .7 
           nil
           (lambda ()
             (switch-to-buffer buffer)
             (kill-buffer file-buffer-name))))
      (message "no file to preview at point!"))))

Also here is a gif of it in action, all I do is:

  • place my cursor over the file
  • press and hold my keybinding
  • the preview is displayed
  • when I let go, the preview is killed and you're back where you were.

enter image description here

One important thing to note is the idle timer's seconds, in my code I used .7 but it's kind of a magic number, you want to keep it really small, but if you see the preview flashing twice try raising it 1/10th a second each time until your find the right spot for your machine.

*Also note that in the function I try to do some fontification of the preview buffer but I couldn't get it to work, that'll be the next step in making it more useful.**

4
  • Nice! This is kind of thing we need to see here.
    – Malabarba
    Commented Sep 28, 2014 at 9:06
  • I get a Error running timer: (void-variable buffer) with this, it looks like the buffer var is not available inside of the run-with-idle-timer?
    – Lee H
    Commented Oct 23, 2014 at 16:06
  • Ensure lexical-binding is t Commented Oct 23, 2014 at 16:09
  • You might want to repeat this answer over there.
    – Malabarba
    Commented Oct 27, 2014 at 23:21
4

Instead of showing the file while a key is pressed, which would be very difficult to implement, I suggest showing the file until the next key is pressed.

(defun dired-find-file-until-key ()
  (interactive)
  (let ((filename (dired-file-name-at-point))
    (buffer-count (length (buffer-list))))
    (dired-find-file)
    (message "Showing %s temporarily..." filename)
    (isearch-unread-key-sequence (list (read-event)))
    (if (= (length (buffer-list)) buffer-count)
    (bury-buffer)
      (kill-buffer))))

Here's a variant that shows the file in another window, which I think is a more convenient user interface.

(defun dired-find-file-other-window-until-key ()
  (interactive)
  (let ((buffer-count (length (buffer-list))))
    (dired-find-file-other-window)
    (isearch-unread-key-sequence (list (read-event)))
    (if (= (length (buffer-list)) buffer-count)
    (delete-window)
      (kill-buffer-and-window))))

You won't be able to do so much as scroll in the buffer. It might make more sense to implement a “quick view” mode where scrolling commands are accepted, but other input events cause the quick view mode to exit and are interpreted according to the previous mode, like Isearch.

With v (dired-view-file), you get something intermediate: the buffer is edited in View mode, where you can scroll around, search, etc. but closing the buffer is the simple keystroke q.

2

From a dired buffer, v will visit a file in view-only mode, and q will quit view mode and take you back to the dired buffer. This is a quick way to preview a file, and it gives the possibility of scrolling and even searching the buffer.

I don't think that emacs has the ability to pass along any low-level keypress messages it receives from the operating system. This may be partly for historical reasons. The terminals available to hackers (read "programmers") at the time that emacs was being developed back in the 1970s-1980s did not work with real-time key up/down events, but rather a simple input of characters and escape sequences. To this day emacs can still operate impressively well in the confines of a simple terminal or SSH session using only simple ASCII characters and escape sequences.

That is not to say that the functionality has not grown significantly over the years to include features such as menus, multiple frame management, and mouse interaction. There is no reason (that I am aware) that emacs could not be altered (at its core) to make low-level key messages available to extensions, but I wouldn't hold your breath.

(Disclaimer: This post should be taken as opinion and speculation, rather than hard fact.)

2

Another possibility, if you use a mouse, is to put the preview you want in a tooltip. Then, when you move the mouse over a file name (with property help-echo) the preview will pop up.

I use this technique in Dired+, for example, to (optionally) show previews of the images associated with image files, when you mouseover the file names.

You can see the effect of this by doing this after loading dired+.el:

  • Ensure that tooltip-mode is turned on: (tooltip-mode 1).

  • Ensure that option diredp-image-preview-in-tooltip has a non-nil value (either the thumbnail size or full for a full-size image).

  • Place the mouse pointer over an image-file name in Dired.

You can use the code in of function diredp-mouseover-help as inspiration for doing what you want (display your "quick view" on mouseover). See the calls of that function for how to use it. Here is one such call:

 (add-text-properties (line-beginning-position) (line-end-position)
                      '(mouse-face highlight help-echo diredp-mouseover-help))
4
  • You lost me at "if you use a mouse." ;-) This is not working for me. image-dired works fine, but all I see when I mouse over is mouse-1: visit this file/dir in another window
    – nispio
    Commented Sep 25, 2014 at 23:41
  • You won't see what I said if you do not load dired+.el and follow the other directions I gave. It is not a vanilla Emacs feature. I was trying to describe how you can go about rolling your own code to do what you want. The dired+.el code is very close, I think, to what you say you want. But yes, mouseover tooltips require that you use a mouse. If you do not, then the suggestion to use a tooltip won't help much. ;-)
    – Drew
    Commented Sep 26, 2014 at 2:10
  • I understand how to to load packages and follow directions, but it is not working for me. I'm not sure how to resume this conversation in chat, but perhaps we should do that.
    – nispio
    Commented Sep 26, 2014 at 13:42
1

I gave my solution in this SO question https://stackoverflow.com/questions/26409768/how-to-show-buffer-content-in-real-time-in-other-window-when-focus-is-in-buffer

and my answer is to change the behaviour of the navigation keys n and p to show the file at point in another window. Focus stays in the dired buffer and we kill the visited buffer when we carry on browsing.

I created a minor-mode to enable/disable this feature easily. Note that we still have the arrows keys for «normal» navigation. Call M-x dired-show-mode (or ranger-mode since this is a feature I discovered in the ranger file manager).

The code: (any review and bug report appreciated !) https://gitlab.com/emacs-stuff/my-elisp/blob/master/dired-show.el

(defgroup dired-show nil
  "See the file at point when browsing in a Dired buffer."
  :group 'dired
  )

(setq show-next-current-buffer nil)

(defun show-next ()
     (interactive)
     (next-line 1)
     (dired-find-file-other-window)
     (if show-next-current-buffer (kill-buffer show-next-current-buffer))
     (setq show-next-current-buffer (current-buffer))
     (other-window 1)
     )

(defun show-previous ()
     (interactive)
     (previous-line 1)
     (dired-find-file-other-window)
     (if show-next-current-buffer (kill-buffer show-next-current-buffer))
     (setq show-next-current-buffer (current-buffer))
     (other-window 1)
     )


(define-minor-mode dired-show-mode
  "Toggle preview of files when browsing in a Dired buffer."
  :global t
  :group 'dired-show
  (if dired-show-mode
      (progn
        (define-key dired-mode-map "n" 'show-next)
        (define-key dired-mode-map "p" 'show-previous)
        )
  (define-key dired-mode-map "n" 'diredp-next-line)
  (define-key dired-mode-map "p" 'diredp-previous-line)
  ))

(defalias 'ranger-mode 'dired-show-mode)

(provide 'dired-show)
;;; dired-show ends here
0

You'd need to poll the event queue until a different event, or none, was read. CPU load is noticable, though reasonably low.

(defun dired-preview-command ()
  (interactive)
  (let* ((file (or (dired-get-filename nil t)
                   (error "No file here")))
         (visited-p (get-file-buffer file))
         (buffer (or visited-p (find-file-noselect file)))
         (window
          (display-buffer buffer '(nil . ((inhibit-same-window . t)))))
         (event (read-event)))
    (while (and event (eq last-command-event event))
      (setq event (read-event nil nil 0.1)))
    (when event
      (setq unread-command-events
            (list event)))
    (quit-window (not visited-p) window)))

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.