From d04c538e7c331a643c61c4af5070288ce220ebfb Mon Sep 17 00:00:00 2001 From: shynur Date: Sat, 2 Sep 2023 22:39:00 +0800 Subject: [PATCH] `dired-next-line' movement style (bug#65621) Point will skips empty lines and optionally goto the other end when encountering a boundary. * lisp/dired.el (dired-movement-style): Control whether to skip empty lines and whether to cycle through non-empty lines. * lisp/dired.el (dired-next-line): Add a new movement style controlled by `dired-movement-style'. * etc/NEWS (dired-movement-style): --- etc/NEWS | 9 ++++++ lisp/dired.el | 76 ++++++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 78 insertions(+), 7 deletions(-) diff --git a/etc/NEWS b/etc/NEWS index 51e89fc96dd..59b8b610276 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -290,6 +290,15 @@ This allows changing which type of whitespace changes are ignored when regenerating hunks with 'diff-ignore-whitespace-hunk'. Defaults to the previously hard-coded "-b". +** Dired + +--- +*** New user option 'dired-movement-style'. +When non-nil, make 'dired-next-line' and 'dired-previous-line' skip +empty lines. It also controls how to move point when encountering a +boundary (e.g., if every line is visible, invoking 'dired-next-line' +at the last line will move to the first line). + ** Ediff --- diff --git a/lisp/dired.el b/lisp/dired.el index e96b85a173a..88f856350b0 100644 --- a/lisp/dired.el +++ b/lisp/dired.el @@ -495,6 +495,21 @@ to nil: a pipe using `zcat' or `gunzip -c' will be used." (string :tag "Switches")) :version "29.1") +(defcustom dired-movement-style nil + "Non-nil means point skips empty lines when moving. +This affects only `dired-next-line' and `dired-previous-line'. + +Possible non-nil values: + * `cycle': the next/previous line of the last/first visible line is + the first/last visible line. + * `bounded': cannot move up/down if the current line is the + first/last visible line." + :type '(choice (const :tag "Move to any line" nil) + (const :tag "Loop through non-empty lines" cycle) + (const :tag "Only to non-empty line" bounded)) + :group 'dired + :version "30.1") + (defcustom dired-hide-details-preserved-columns nil "List of columns which are not hidden in `dired-hide-details-mode'." :type '(repeat integer) @@ -2666,22 +2681,69 @@ Otherwise, toggle `read-only-mode'." (wdired-change-to-wdired-mode) (read-only-mode 'toggle))) -(defun dired-next-line (arg) - "Move down lines then position at filename. -Optional prefix ARG says how many lines to move; default is one line." - (interactive "^p") +(defun dired--trivial-next-line (arg) + "Move down ARG lines then position at filename." (let ((line-move-visual) - (goal-column)) + (goal-column)) (line-move arg t)) ;; We never want to move point into an invisible line. (while (and (invisible-p (point)) - (not (if (and arg (< arg 0)) (bobp) (eobp)))) + (not (if (and arg (< arg 0)) (bobp) (eobp)))) (forward-char (if (and arg (< arg 0)) -1 1))) (dired-move-to-filename)) +(defun dired-next-line (arg) + "Move down lines then position at filename. +Optional prefix ARG says how many lines to move; default is one line. + +Whether to skip empty lines and how to move when encountering a +boundary are controlled by `dired-movement-style'." + (interactive "^p") + (if dired-movement-style + (let ((old-position (progn + ;; It's always true that we should move + ;; to the filename when possible. + (dired-move-to-filename) + (point))) + ;; Up/Down indicates the direction. + (moving-down (if (cl-plusp arg) + 1 ; means Down. + -1))) ; means Up. + ;; Line by line in case we forget to skip empty lines. + (while (not (zerop arg)) + (dired--trivial-next-line moving-down) + (when (= old-position (point)) + ;; Now point is at beginning/end of movable area, + ;; but it still wants to move farther. + (if (eq dired-movement-style 'cycle) + ;; `cycle': go to the other end. + (goto-char (if (cl-plusp moving-down) + (point-min) + (point-max))) + ;; `bounded': go back to the last non-empty line. + (while (string-match-p "\\`[[:blank:]]*\\'" + (buffer-substring-no-properties + (line-beginning-position) + (line-end-position))) + (dired--trivial-next-line (- moving-down))) + ;; Encountered a boundary, so let's stop movement. + (setq arg moving-down))) + (when (not (string-match-p "\\`[[:blank:]]*\\'" + (buffer-substring-no-properties + (line-beginning-position) + (line-end-position)))) + ;; Has moved to a non-empty line. This movement does + ;; make sense. + (cl-decf arg moving-down)) + (setq old-position (point)))) + (dired--trivial-next-line arg))) + (defun dired-previous-line (arg) "Move up lines then position at filename. -Optional prefix ARG says how many lines to move; default is one line." +Optional prefix ARG says how many lines to move; default is one line. + +Whether to skip empty lines and how to move when encountering a +boundary are controlled by `dired-movement-style'." (interactive "^p") (dired-next-line (- (or arg 1)))) -- 2.39.5