From 8ddfb99a9213414e438fc4946ce3935b21b6ca3d Mon Sep 17 00:00:00 2001
From: shynur <one.last.kiss@outlook.com>
Date: Fri, 1 Sep 2023 02:12:25 +0800
Subject: [PATCH] `dired-next-line' go to meaningful line

---
 lisp/dired.el | 95 +++++++++++++++++++++++++++++++++++++++++++++++----
 1 file changed, 88 insertions(+), 7 deletions(-)

diff --git a/lisp/dired.el b/lisp/dired.el
index e96b85a..3d7d6a6 100644
--- a/lisp/dired.el
+++ b/lisp/dired.el
@@ -495,6 +495,30 @@ to nil: a pipe using `zcat' or `gunzip -c' will be used."
                  (string :tag "Switches"))
   :version "29.1")
 
+(defcustom dired-cursor-goto-meaningful-line nil
+  "Non-nil means moving cursor only to a pathname line.
+This option makes `dired-next-line' and `dired-previous-line' move
+cursor to:
+ - a line containing filename if such a line does exist.
+ - or, a header line if `dired-headerline-is-meaningful' is non-nil.
+
+Possible non-nil values:
+ *   `cycle': the next/previous line of the last/first line is the
+              first/last line.
+ * `bounded': cursor cannot move up/down if the current line is the
+              first/last line.
+ * any other symbol: the effect is the same as `bounded', but it is
+              not recommended for use."
+  :type '(choice (const :tag "Move to any line" nil)
+                 (const :tag "Loop through pathname lines" cycle)
+                 (const :tag "Only to pathname line" bounded))
+  :group 'dired)
+
+(defcustom dired-headerline-is-meaningful t
+  "Non-nil means never skip header line when moving cursor."
+  :type 'boolean
+  :group 'dired)
+
 (defcustom dired-hide-details-preserved-columns nil
   "List of columns which are not hidden in `dired-hide-details-mode'."
   :type '(repeat integer)
@@ -2666,22 +2690,79 @@ 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--meaningful-line-p ()
+  "Return t if the current line contains a filename, or is a header
+line if `dired-headerline-is-meaningful' is non-nil."
+  (save-excursion
+    ;; Move to BOL or filename.
+    (dired-move-to-filename)
+    (or (get-char-property (point) 'dired-filename)
+        (when dired-headerline-is-meaningful
+          (skip-chars-forward "[[:blank:]]")
+          (eq (get-text-property (point) 'face) 'dired-header)))))
+
+(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.
+
+Cursor won't go to empty line when `dired-cursor-goto-meaningful-line'
+is non-nil; further, it also skips dired header lines if
+`dired-headerline-is-meaningful' is nil."
+  (interactive "^p")
+  (if dired-cursor-goto-meaningful-line
+      (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 (natnump arg)
+                              1   ; means Down.
+                            -1))  ; means Up.
+             ;; The following while-loop runs this many times at
+             ;; most, unless there is not even a meaningful line,
+             ;; but this is almost impossible.
+             (total-moves (* 3 arg moving-down)))
+        ;; Line by line in case we forget to skip meaningless lines.
+        (while (and (not (zerop arg))
+                    ;; If there's no meaningful lines, stop the
+                    ;; endless while-loop.
+                    (natnump total-moves))
+          (cl-decf total-moves)
+          (dired--trivial-next-line moving-down)
+          (when (= old-position (point))
+            ;; Now cursor is at beginning/end of buffer,
+            ;; but the cursor still wants to move farther.
+            (if (eq dired-cursor-goto-meaningful-line 'cycle)
+                (goto-char (if (= 1 moving-down)
+                               (point-min)
+                             (point-max)))
+              (unless (dired--meaningful-line-p)
+                (dired--trivial-next-line (- moving-down))
+                (setq arg moving-down))))
+          (setq old-position (point))
+          (when (dired--meaningful-line-p)
+            (cl-decf arg moving-down))))
+    (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.
+
+Cursor won't go to empty line when `dired-cursor-goto-meaningful-line'
+is non-nil; further, it also skips dired header lines if
+`dired-headerline-is-meaningful' is nil."
   (interactive "^p")
   (dired-next-line (- (or arg 1))))
 
-- 
2.41.0.windows.3

