From 361baa451adac7333e1037c3bc73bd95afa9b769 Mon Sep 17 00:00:00 2001 From: Lars Ingebrigtsen Date: Thu, 6 Aug 2020 11:51:22 +0200 Subject: [PATCH] Make 'n'/'p' in image mode buffers respect dired sorting The commands now also now work on archive and tar mode parent buffers. * doc/emacs/files.texi (Image Mode): Document it. * lisp/arc-mode.el (archive-goto-file): New function (bug#38647). (archive-next-file-displayer): Ditto. * lisp/image-mode.el (image-next-file): Reimplement to work on displayed dired buffers and the like. This means that `n' and `p' now works on the displayed ordering in the dired buffer, so if you've reversed the sorting, `n' picks the right "next" file. (image-mode--directory-buffers): New function. (image-mode--next-file): Ditto. * lisp/tar-mode.el (tar-goto-file): New function. (tar-next-file-displayer): Ditto. --- doc/emacs/files.texi | 7 +++- etc/NEWS | 9 ++++ lisp/arc-mode.el | 47 +++++++++++++++++++++ lisp/image-mode.el | 98 +++++++++++++++++++++++++++++++++++--------- lisp/tar-mode.el | 50 ++++++++++++++++++++++ 5 files changed, 191 insertions(+), 20 deletions(-) diff --git a/doc/emacs/files.texi b/doc/emacs/files.texi index 5998326ffef..2fa1ecc003d 100644 --- a/doc/emacs/files.texi +++ b/doc/emacs/files.texi @@ -2149,7 +2149,12 @@ To reset all transformations to the initial state, use @findex image-previous-file You can press @kbd{n} (@code{image-next-file}) and @kbd{p} (@code{image-previous-file}) to visit the next image file and the -previous image file in the same directory, respectively. +previous image file in the same directory, respectively. These +commands will consult the ``parent'' dired buffer to determine what +the next/previous image file is. These commands also work when +opening a file from archive files (like zip or tar files), and will +then instead consult the archive mode buffer. If neither an archive +nor a dired ``parent'' buffer can be found, a dired buffer is opened. @findex image-mode-mark-file @findex image-mode-unmark-file diff --git a/etc/NEWS b/etc/NEWS index 8c6e3e78139..cbb1842e139 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -538,6 +538,15 @@ took more than two seconds to display. The new algorithm maintains a decaying average of delays, and if this number gets too high, the animation is stopped. ++++ +*** The 'n' and 'p' commands (next/previous image) now respects dired order. +These commands would previously display the next/previous image in +alphabetical order, but will now find the "parent" dired buffer and +select the next/previous image file according to how the files are +sorted there. The commands have also been extended to work when the +"parent" buffer is an archive mode (i.e., zip file or the like) or tar +mode buffer. + ** EWW +++ diff --git a/lisp/arc-mode.el b/lisp/arc-mode.el index 6781c292d82..901f09302ef 100644 --- a/lisp/arc-mode.el +++ b/lisp/arc-mode.el @@ -989,6 +989,53 @@ using `make-temp-file', and the generated name is returned." (kill-local-variable 'buffer-file-coding-system) (after-insert-file-set-coding (- (point-max) (point-min)))))) +(defun archive-goto-file (file) + "Go to FILE in the current buffer. +FILE should be a relative file name. If FILE can't be found, +return nil. Otherwise point is returned." + (let ((start (point)) + found) + (goto-char (point-min)) + (while (and (not found) + (not (eobp))) + (forward-line 1) + (when-let ((descr (archive-get-descr t))) + (when (equal (archive--file-desc-ext-file-name descr) file) + (setq found t)))) + (if (not found) + (progn + (goto-char start) + nil) + (point)))) + +(defun archive-next-file-displayer (file regexp n) + "Return a closure to display the next file after FILE that matches REGEXP." + (let ((short (replace-regexp-in-string "\\`.*:" "" file)) + next) + (archive-goto-file short) + (while (and (not next) + ;; Stop if we reach the end/start of the buffer. + (if (> n 0) + (not (eobp)) + (not (save-excursion + (beginning-of-line) + (bobp))))) + (archive-next-line n) + (when-let ((descr (archive-get-descr t))) + (let ((candidate (archive--file-desc-ext-file-name descr)) + (buffer (current-buffer))) + (when (and candidate + (string-match-p regexp candidate)) + (setq next (lambda () + (kill-buffer (current-buffer)) + (switch-to-buffer buffer) + (archive-extract))))))) + (unless next + ;; If we didn't find a next/prev file, then restore + ;; point. + (archive-goto-file short)) + next)) + (defun archive-extract (&optional other-window-p event) "In archive mode, extract this entry of the archive into its own buffer." (interactive (list nil last-input-event)) diff --git a/lisp/image-mode.el b/lisp/image-mode.el index c417be43da5..948e62e10d0 100644 --- a/lisp/image-mode.el +++ b/lisp/image-mode.el @@ -40,6 +40,7 @@ (require 'image) (require 'exif) +(require 'dired) (eval-when-compile (require 'cl-lib)) ;;; Image mode window-info management. @@ -1085,28 +1086,87 @@ replacing the current Image mode buffer." (error "The buffer is not in Image mode")) (unless buffer-file-name (error "The current image is not associated with a file")) - (let* ((file (file-name-nondirectory buffer-file-name)) - (images (image-mode--images-in-directory file)) - (idx 0)) - (catch 'image-visit-next-file - (dolist (f images) - (if (string= f file) - (throw 'image-visit-next-file (1+ idx))) - (setq idx (1+ idx)))) - (setq idx (mod (+ idx (or n 1)) (length images))) - (let ((image (nth idx images)) - (dir (file-name-directory buffer-file-name))) - (find-alternate-file image) - ;; If we have dired buffer(s) open to where this image is, then - ;; place point on it. + (let ((next (image-mode--next-file buffer-file-name n))) + (unless next + (user-error "No %s file in this directory" + (if (> n 0) + "next" + "prev"))) + (if (stringp next) + (find-alternate-file next) + (funcall next)))) + +(defun image-mode--directory-buffers (file) + "Return a alist of type/buffer for all \"parent\" buffers to image FILE. +This is normally a list of dired buffers, but can also be archive and +tar mode buffers." + (let ((buffers nil) + (dir (file-name-directory file))) + (cond + ((and (boundp 'tar-superior-buffer) + tar-superior-buffer) + (when (buffer-live-p tar-superior-buffer) + (push (cons 'tar tar-superior-buffer) buffers))) + ((and (boundp 'archive-superior-buffer) + archive-superior-buffer) + (when (buffer-live-p archive-superior-buffer) + (push (cons 'archive archive-superior-buffer) buffers))) + (t + ;; Find a dired buffer. (dolist (buffer (buffer-list)) - (with-current-buffer buffer - (when (and (derived-mode-p 'dired-mode) + (with-current-buffer buffer + (when (and (derived-mode-p 'dired-mode) (equal (file-truename dir) (file-truename default-directory))) - (save-window-excursion - (switch-to-buffer (current-buffer) t t) - (dired-goto-file (expand-file-name image dir))))))))) + (push (cons 'dired (current-buffer)) buffers)))) + ;; If we can't find any buffers to navigate in, we open a dired + ;; buffer. + (unless buffers + (push (cons 'dired (find-file-noselect dir)) buffers) + (message "Opened a dired buffer on %s" dir)))) + buffers)) + +(declare-function archive-next-file-displayer "arc-mode") +(declare-function tar-next-file-displayer "tar-mode") + +(defun image-mode--next-file (file n) + "Go to the next image file in the parent buffer of FILE. +This is typically a dired buffer, but may also be a tar/archive buffer. +Return the next image file from that buffer. +If N is negative, go to the previous file." + (let ((regexp (image-file-name-regexp)) + (buffers (image-mode--directory-buffers file)) + next) + (dolist (buffer buffers) + ;; We do this traversal for all the dired buffers open on this + ;; directory. There probably is just one, but we want to move + ;; point in all of them. + (save-window-excursion + (switch-to-buffer (cdr buffer) t t) + (cl-case (car buffer) + ('dired + (dired-goto-file file) + (let (found) + (while (and (not found) + ;; Stop if we reach the end/start of the buffer. + (if (> n 0) + (not (eobp)) + (not (bobp)))) + (dired-next-line n) + (let ((candidate (dired-get-filename nil t))) + (when (and candidate + (string-match-p regexp candidate)) + (setq found candidate)))) + (if found + (setq next found) + ;; If we didn't find a next/prev file, then restore + ;; point. + (dired-goto-file file)))) + ('archive + (setq next (archive-next-file-displayer file regexp n))) + ('tar + (setq next (tar-next-file-displayer file regexp n)))))) + next)) (defun image-previous-file (&optional n) "Visit the preceding image in the same directory as the current file. diff --git a/lisp/tar-mode.el b/lisp/tar-mode.el index 73978ffc4a7..5cf09f9055e 100644 --- a/lisp/tar-mode.el +++ b/lisp/tar-mode.el @@ -922,6 +922,56 @@ actually appear on disk when you save the tar-file's buffer." (setq buffer-undo-list nil)))) buffer)) +(defun tar-goto-file (file) + "Go to FILE in the current buffer. +FILE should be a relative file name. If FILE can't be found, +return nil. Otherwise point is returned." + (let ((start (point)) + found) + (goto-char (point-min)) + (while (and (not found) + (not (eobp))) + (forward-line 1) + (when-let ((descriptor (ignore-errors (tar-get-descriptor)))) + (when (equal (tar-header-name descriptor) file) + (setq found t)))) + (if (not found) + (progn + (goto-char start) + nil) + (point)))) + +(defun tar-next-file-displayer (file regexp n) + "Return a closure to display the next file after FILE that matches REGEXP." + (let ((short (replace-regexp-in-string "\\`.*!" "" file)) + next) + ;; The tar buffer chops off leading "./", so do the same + ;; here. + (setq short (replace-regexp-in-string "\\`\\./" "" file)) + (tar-goto-file short) + (while (and (not next) + ;; Stop if we reach the end/start of the buffer. + (if (> n 0) + (not (eobp)) + (not (save-excursion + (beginning-of-line) + (bobp))))) + (tar-next-line n) + (when-let ((descriptor (ignore-errors (tar-get-descriptor)))) + (let ((candidate (tar-header-name descriptor)) + (buffer (current-buffer))) + (when (and candidate + (string-match-p regexp candidate)) + (setq next (lambda () + (kill-buffer (current-buffer)) + (switch-to-buffer buffer) + (tar-extract))))))) + (unless next + ;; If we didn't find a next/prev file, then restore + ;; point. + (tar-goto-file short)) + next)) + (defun tar-extract (&optional other-window-p) "In Tar mode, extract this entry of the tar file into its own buffer." (interactive) -- 2.39.2