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.
This commit is contained in:
Lars Ingebrigtsen 2020-08-06 11:51:22 +02:00
parent 7a56e5a44a
commit 361baa451a
5 changed files with 191 additions and 20 deletions

View file

@ -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

View file

@ -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
+++

View file

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

View file

@ -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.

View file

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