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.

Drew
  • 75,699
  • 9
  • 109
  • 225
Eric Brown
  • 3,212
  • 4
  • 16
  • 20
  • 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. – Jordon Biondo Sep 25 '14 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 Sep 25 '14 at 21:25
  • 1
    Are there efforts to allow us to bind to events? I want this feature. – wdkrnls Oct 22 '14 at 19:47
  • Check out `dired-display-file` and `dired-view-file` (`M-RET` and `go` in doomemacs). – Romeo Valentin May 11 '23 at 23:07

7 Answers7

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 :)

Scott Weldon
  • 2,695
  • 1
  • 17
  • 31
Sigma
  • 4,510
  • 21
  • 27
  • 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 Sep 25 '14 at 21:39
  • what you suggest is comparable with functionality in Midnight Commander (F3) – Eric Brown Sep 25 '14 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 _ Sep 25 '14 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 Sep 25 '14 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 Nov 10 '22 at 08: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.**

Scott Weldon
  • 2,695
  • 1
  • 17
  • 31
Jordon Biondo
  • 12,332
  • 2
  • 41
  • 62
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.

Scott Weldon
  • 2,695
  • 1
  • 17
  • 31
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.)

nispio
  • 8,175
  • 2
  • 35
  • 73
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))
Drew
  • 75,699
  • 9
  • 109
  • 225
  • 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 Sep 25 '14 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 Sep 26 '14 at 02: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 Sep 26 '14 at 13:42
  • http://chat.stackexchange.com/rooms/17451/chat-between-drew-and-nispio – nispio Sep 26 '14 at 13:54
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
Ehvince
  • 1,091
  • 10
  • 14
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)))
politza
  • 3,316
  • 14
  • 16