From 49ead19d008dd54403b37818b21e79b8ccfa4b89 Mon Sep 17 00:00:00 2001
From: Vovodroid <vovodroid@users.noreply.github.com>
Date: Fri, 18 Aug 2023 02:13:10 +0300
Subject: [PATCH] =?UTF-8?q?=F0=9F=94=A7=20Reversible=20file=20alpha=20sort?=
 =?UTF-8?q?ing=20(#26130)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Co-authored-by: Scott Lahteine <thinkyhead@users.noreply.github.com>
---
 Marlin/Configuration_adv.h               |  5 +--
 Marlin/src/gcode/sd/M34.cpp              | 12 ++++++-
 Marlin/src/inc/Changes.h                 |  2 ++
 Marlin/src/inc/Conditionals_post.h       |  4 +--
 Marlin/src/lcd/e3v2/proui/dwin.cpp       |  4 +--
 Marlin/src/sd/SdBaseFile.cpp             | 14 ++++----
 Marlin/src/sd/cardreader.cpp             | 46 ++++++++++++------------
 Marlin/src/sd/cardreader.h               | 16 +++++----
 buildroot/tests/STM32F103VE_longer_maple |  6 ++--
 9 files changed, 65 insertions(+), 44 deletions(-)

diff --git a/Marlin/Configuration_adv.h b/Marlin/Configuration_adv.h
index 82cfc99b30..da9f3a66c6 100644
--- a/Marlin/Configuration_adv.h
+++ b/Marlin/Configuration_adv.h
@@ -1746,9 +1746,10 @@
 
   // SD Card Sorting options
   #if ENABLED(SDCARD_SORT_ALPHA)
+    #define SDSORT_REVERSE     false  // Default to sorting file names in reverse order.
     #define SDSORT_LIMIT       40     // Maximum number of sorted items (10-256). Costs 27 bytes each.
-    #define FOLDER_SORTING     -1     // -1=above  0=none  1=below
-    #define SDSORT_GCODE       false  // Allow turning sorting on/off with LCD and M34 G-code.
+    #define SDSORT_FOLDERS     -1     // -1=above  0=none  1=below
+    #define SDSORT_GCODE       false  // Enable G-code M34 to set sorting behaviors: M34 S<-1|0|1> F<-1|0|1>
     #define SDSORT_USES_RAM    false  // Pre-allocate a static array for faster pre-sorting.
     #define SDSORT_USES_STACK  false  // Prefer the stack for pre-sorting to give back some SRAM. (Negated by next 2 options.)
     #define SDSORT_CACHE_NAMES false  // Keep sorted items in RAM longer for speedy performance. Most expensive option.
diff --git a/Marlin/src/gcode/sd/M34.cpp b/Marlin/src/gcode/sd/M34.cpp
index 0a7d4d8c62..3a75442928 100644
--- a/Marlin/src/gcode/sd/M34.cpp
+++ b/Marlin/src/gcode/sd/M34.cpp
@@ -29,9 +29,19 @@
 
 /**
  * M34: Set SD Card Sorting Options
+ *
+ * S   - Default sorting (i.e., SDSORT_REVERSE)
+ * S-1 - Reverse alpha sorting
+ * S0  - FID Order (not always newest)
+ * S1  - Forward alpha sorting
+ * S2  - Alias for S-1 [deprecated]
+ *
+ * F-1 - Folders above files
+ * F0  - Sort According to 'S'
+ * F1  - Folders after files
  */
 void GcodeSuite::M34() {
-  if (parser.seen('S')) card.setSortOn(parser.value_bool());
+  if (parser.seen('S')) card.setSortOn(SortFlag(parser.ushortval('S', TERN(SDSORT_REVERSE, AS_REV, AS_FWD))));
   if (parser.seenval('F')) {
     const int v = parser.value_long();
     card.setSortFolders(v < 0 ? -1 : v > 0 ? 1 : 0);
diff --git a/Marlin/src/inc/Changes.h b/Marlin/src/inc/Changes.h
index b64b6a7ca7..942c5303d1 100644
--- a/Marlin/src/inc/Changes.h
+++ b/Marlin/src/inc/Changes.h
@@ -661,6 +661,8 @@
   #error "Z4_USE_ENDSTOP is obsolete. Instead set Z4_STOP_PIN directly. (e.g., 'Z4_USE_ENDSTOP _ZMAX_' becomes 'Z4_STOP_PIN Z_MAX_PIN')"
 #elif defined(INTEGRATED_BABYSTEPPING)
   #error "INTEGRATED_BABYSTEPPING is no longer needed and should be removed."
+#elif defined(FOLDER_SORTING)
+  #error "FOLDER_SORTING is now SDSORT_FOLDERS."
 #endif
 
 // L64xx stepper drivers have been removed
diff --git a/Marlin/src/inc/Conditionals_post.h b/Marlin/src/inc/Conditionals_post.h
index 8f3a2eee27..33b42e8511 100644
--- a/Marlin/src/inc/Conditionals_post.h
+++ b/Marlin/src/inc/Conditionals_post.h
@@ -3337,8 +3337,8 @@
     #define SDSORT_CACHE_NAMES true
     #define SDSORT_CACHE_LPC1768_WARNING 1
   #endif
-  #ifndef FOLDER_SORTING
-    #define FOLDER_SORTING     -1
+  #ifndef SDSORT_FOLDERS
+    #define SDSORT_FOLDERS     -1
   #endif
   #ifndef SDSORT_GCODE
     #define SDSORT_GCODE       false
diff --git a/Marlin/src/lcd/e3v2/proui/dwin.cpp b/Marlin/src/lcd/e3v2/proui/dwin.cpp
index 2c52b0cabc..824b730019 100644
--- a/Marlin/src/lcd/e3v2/proui/dwin.cpp
+++ b/Marlin/src/lcd/e3v2/proui/dwin.cpp
@@ -850,7 +850,7 @@ bool DWIN_lcd_sd_status = false;
 #if ENABLED(MEDIASORT_MENU_ITEM)
   void setMediaSort() {
     toggleCheckboxLine(hmiData.mediaSort);
-    card.setSortOn(hmiData.mediaSort);
+    card.setSortOn(hmiData.mediaSort ? TERN(SDSORT_REVERSE, AS_REV, AS_FWD) : AS_OFF);
   }
 #endif
 
@@ -1754,7 +1754,7 @@ void dwinSetDataDefaults() {
   #endif
   #if ENABLED(MEDIASORT_MENU_ITEM)
     hmiData.mediaSort = true;
-    card.setSortOn(true);
+    card.setSortOn(TERN(SDSORT_REVERSE, AS_REV, AS_FWD));
   #endif
   hmiData.mediaAutoMount = ENABLED(HAS_SD_EXTENDER);
   #if ALL(INDIVIDUAL_AXIS_HOMING_SUBMENU, MESH_BED_LEVELING)
diff --git a/Marlin/src/sd/SdBaseFile.cpp b/Marlin/src/sd/SdBaseFile.cpp
index 7180b675d7..94c1b02e0e 100644
--- a/Marlin/src/sd/SdBaseFile.cpp
+++ b/Marlin/src/sd/SdBaseFile.cpp
@@ -1422,11 +1422,13 @@ int16_t SdBaseFile::read(void * const buf, uint16_t nbyte) {
  *
  * \param[out] dir The dir_t struct that will receive the data.
  *
- * \return For success readDir() returns the number of bytes read.
- * A value of zero will be returned if end of file is reached.
- * If an error occurs, readDir() returns -1.  Possible errors include
- * readDir() called before a directory has been opened, this is not
- * a directory file or an I/O error occurred.
+ * \return For success return a non-zero value (number of bytes read).
+ *         A value of zero will be returned if end of dir is reached.
+ *         If an error occurs, readDir() returns -1. Possible errors:
+ *           - readDir() called on unopened dir
+ *           - not a directory file
+ *           - bad dir entry
+ *           - I/O error
  */
 int8_t SdBaseFile::readDir(dir_t * const dir, char * const longFilename) {
   int16_t n;
@@ -1488,7 +1490,7 @@ int8_t SdBaseFile::readDir(dir_t * const dir, char * const longFilename) {
                   longFilename[idx] = utf16_ch & 0xFF;
                   longFilename[idx + 1] = (utf16_ch >> 8) & 0xFF;
                 #else
-                  // Replace all multibyte characters to '_'
+                  // Replace multibyte character with '_'
                   longFilename[n + i] = (utf16_ch > 0xFF) ? '_' : (utf16_ch & 0xFF);
                 #endif
               }
diff --git a/Marlin/src/sd/cardreader.cpp b/Marlin/src/sd/cardreader.cpp
index e09bc5265a..8ec33d1f72 100644
--- a/Marlin/src/sd/cardreader.cpp
+++ b/Marlin/src/sd/cardreader.cpp
@@ -91,8 +91,8 @@ int16_t CardReader::nrItems = -1;
 
   int16_t CardReader::sort_count;
   #if ENABLED(SDSORT_GCODE)
-    bool CardReader::sort_alpha;
-    int CardReader::sort_folders;
+    SortFlag CardReader::sort_alpha;
+    int8_t CardReader::sort_folders;
     //bool CardReader::sort_reverse;
   #endif
 
@@ -160,8 +160,8 @@ CardReader::CardReader() {
   #if ENABLED(SDCARD_SORT_ALPHA)
     sort_count = 0;
     #if ENABLED(SDSORT_GCODE)
-      sort_alpha = true;
-      sort_folders = FOLDER_SORTING;
+      sort_alpha = TERN(SDSORT_REVERSE, AS_REV, AS_FWD);
+      sort_folders = SDSORT_FOLDERS;
       //sort_reverse = false;
     #endif
   #endif
@@ -993,7 +993,7 @@ void CardReader::selectFileByIndex(const int16_t nr) {
     if (nr < sort_count) {
       strcpy(filename, sortshort[nr]);
       strcpy(longFilename, sortnames[nr]);
-      flag.filenameIsDir = IS_DIR(nr);
+      TERN_(HAS_FOLDER_SORTING, flag.filenameIsDir = IS_DIR(nr));
       setBinFlag(strcmp_P(strrchr(filename, '.'), PSTR(".BIN")) == 0);
       return;
     }
@@ -1011,7 +1011,7 @@ void CardReader::selectFileByName(const char * const match) {
       if (strcasecmp(match, sortshort[nr]) == 0) {
         strcpy(filename, sortshort[nr]);
         strcpy(longFilename, sortnames[nr]);
-        flag.filenameIsDir = IS_DIR(nr);
+        TERN_(HAS_FOLDER_SORTING, flag.filenameIsDir = IS_DIR(nr));
         setBinFlag(strcmp_P(strrchr(filename, '.'), PSTR(".BIN")) == 0);
         return;
       }
@@ -1163,7 +1163,7 @@ void CardReader::cdroot() {
    * Get the name of a file in the working directory by sort-index
    */
   void CardReader::selectFileByIndexSorted(const int16_t nr) {
-    selectFileByIndex(TERN1(SDSORT_GCODE, sort_alpha) && (nr < sort_count) ? sort_order[nr] : nr);
+    selectFileByIndex(SortFlag(TERN1(SDSORT_GCODE, sort_alpha != AS_OFF)) && (nr < sort_count) ? sort_order[nr] : nr);
   }
 
   #if ENABLED(SDSORT_USES_RAM)
@@ -1210,7 +1210,7 @@ void CardReader::cdroot() {
     int16_t fileCnt = get_num_items();
 
     // Sorting may be turned off
-    if (TERN0(SDSORT_GCODE, !sort_alpha)) return;
+    if (TERN0(SDSORT_GCODE, sort_alpha == AS_OFF)) return;
 
     // If there are files, sort up to the limit
     if (fileCnt > 0) {
@@ -1239,9 +1239,9 @@ void CardReader::cdroot() {
         // Folder sorting needs 1 bit per entry for flags.
         #if HAS_FOLDER_SORTING
           #if ENABLED(SDSORT_DYNAMIC_RAM)
-            isDir = new uint8_t[(fileCnt + 7) >> 3];
+            isDir = new uint8_t[(fileCnt + 7) >> 3];  // Allocate space with 'new'
           #elif ENABLED(SDSORT_USES_STACK)
-            uint8_t isDir[(fileCnt + 7) >> 3];
+            uint8_t isDir[(fileCnt + 7) >> 3];        // Use stack in this scope
           #endif
         #endif
 
@@ -1291,18 +1291,18 @@ void CardReader::cdroot() {
             const int16_t o2 = sort_order[j + 1];
 
             // Compare names from the array or just the two buffered names
-            #if ENABLED(SDSORT_USES_RAM)
-              #define _SORT_CMP_NODIR() (strcasecmp(sortnames[o1], sortnames[o2]) > 0)
-            #else
-              #define _SORT_CMP_NODIR() (strcasecmp(name1, name2) > 0)
-            #endif
+            auto _sort_cmp_file = [](char * const n1, char * const n2) -> bool {
+              const bool sort = strcasecmp(n1, n2) > 0;
+              return (TERN(SDSORT_GCODE, card.sort_alpha == AS_REV, ENABLED(SDSORT_REVERSE))) ? !sort : sort;
+            };
+            #define _SORT_CMP_FILE() _sort_cmp_file(TERN(SDSORT_USES_RAM, sortnames[o1], name1), TERN(SDSORT_USES_RAM, sortnames[o2], name2))
 
             #if HAS_FOLDER_SORTING
               #if ENABLED(SDSORT_USES_RAM)
                 // Folder sorting needs an index and bit to test for folder-ness.
-                #define _SORT_CMP_DIR(fs) (IS_DIR(o1) == IS_DIR(o2) ? _SORT_CMP_NODIR() : IS_DIR(fs > 0 ? o1 : o2))
+                #define _SORT_CMP_DIR(fs) (IS_DIR(o1) == IS_DIR(o2) ? _SORT_CMP_FILE() : IS_DIR(fs > 0 ? o1 : o2))
               #else
-                #define _SORT_CMP_DIR(fs) ((dir1 == flag.filenameIsDir) ? _SORT_CMP_NODIR() : (fs > 0 ? dir1 : !dir1))
+                #define _SORT_CMP_DIR(fs) ((dir1 == flag.filenameIsDir) ? _SORT_CMP_FILE() : (fs > 0 ? dir1 : !dir1))
               #endif
             #endif
 
@@ -1318,12 +1318,12 @@ void CardReader::cdroot() {
             if (
               #if HAS_FOLDER_SORTING
                 #if ENABLED(SDSORT_GCODE)
-                  sort_folders ? _SORT_CMP_DIR(sort_folders) : _SORT_CMP_NODIR()
+                  sort_folders ? _SORT_CMP_DIR(sort_folders) : _SORT_CMP_FILE()
                 #else
-                  _SORT_CMP_DIR(FOLDER_SORTING)
+                  _SORT_CMP_DIR(SDSORT_FOLDERS)
                 #endif
               #else
-                _SORT_CMP_NODIR()
+                _SORT_CMP_FILE()
               #endif
             ) {
               // Reorder the index, indicate that sorting happened
@@ -1357,12 +1357,14 @@ void CardReader::cdroot() {
           #if ENABLED(SDSORT_DYNAMIC_RAM)
             sortnames = new char*[1];
             sortshort = new char*[1];
-            isDir = new uint8_t[1];
           #endif
           selectFileByIndex(0);
           SET_SORTNAME(0);
           SET_SORTSHORT(0);
-          isDir[0] = flag.filenameIsDir;
+          #if ALL(HAS_FOLDER_SORTING, SDSORT_DYNAMIC_RAM)
+            isDir = new uint8_t[1];
+            isDir[0] = flag.filenameIsDir;
+          #endif
         #endif
       }
 
diff --git a/Marlin/src/sd/cardreader.h b/Marlin/src/sd/cardreader.h
index 80e317ebcf..7dc140b317 100644
--- a/Marlin/src/sd/cardreader.h
+++ b/Marlin/src/sd/cardreader.h
@@ -31,7 +31,10 @@ extern const char M23_STR[], M24_STR[];
   #if ENABLED(SDSORT_DYNAMIC_RAM)
     #define SD_RESORT 1
   #endif
-  #if FOLDER_SORTING || ENABLED(SDSORT_GCODE)
+  #ifndef SDSORT_FOLDERS
+    #define SDSORT_FOLDERS 0
+  #endif
+  #if SDSORT_FOLDERS || ENABLED(SDSORT_GCODE)
     #define HAS_FOLDER_SORTING 1
   #endif
 #endif
@@ -84,6 +87,7 @@ typedef struct {
 } card_flags_t;
 
 enum ListingFlags : uint8_t { LS_LONG_FILENAME, LS_ONLY_BIN, LS_TIMESTAMP };
+enum SortFlag : int8_t { AS_REV = -1, AS_OFF, AS_FWD, AS_ALSO_REV };
 
 #if ENABLED(AUTO_REPORT_SD_STATUS)
   #include "../libs/autoreport.h"
@@ -199,8 +203,8 @@ public:
     static void presort();
     static void selectFileByIndexSorted(const int16_t nr);
     #if ENABLED(SDSORT_GCODE)
-      FORCE_INLINE static void setSortOn(bool b)        { sort_alpha   = b; presort(); }
-      FORCE_INLINE static void setSortFolders(int i)    { sort_folders = i; presort(); }
+      FORCE_INLINE static void setSortOn(const SortFlag f) { sort_alpha = (f == AS_ALSO_REV) ? AS_REV : f; presort(); }
+      FORCE_INLINE static void setSortFolders(const int8_t i) { sort_folders = i; presort(); }
       //FORCE_INLINE static void setSortReverse(bool b) { sort_reverse = b; }
     #endif
   #else
@@ -272,12 +276,12 @@ private:
   #if ENABLED(SDCARD_SORT_ALPHA)
     static int16_t sort_count;    // Count of sorted items in the current directory
     #if ENABLED(SDSORT_GCODE)
-      static bool sort_alpha;     // Flag to enable / disable the feature
-      static int sort_folders;    // Folder sorting before/none/after
+      static SortFlag sort_alpha; // Sorting: REV, OFF, FWD
+      static int8_t sort_folders; // Folder sorting before/none/after
       //static bool sort_reverse; // Flag to enable / disable reverse sorting
     #endif
 
-    // By default the sort index is static
+    // By default the sort index is statically allocated
     #if ENABLED(SDSORT_DYNAMIC_RAM)
       static uint8_t *sort_order;
     #else
diff --git a/buildroot/tests/STM32F103VE_longer_maple b/buildroot/tests/STM32F103VE_longer_maple
index 4570a3214d..9f594be61a 100755
--- a/buildroot/tests/STM32F103VE_longer_maple
+++ b/buildroot/tests/STM32F103VE_longer_maple
@@ -8,16 +8,16 @@ set -e
 
 use_example_configs Alfawise/U20
 opt_enable BAUD_RATE_GCODE
-exec_test $1 $2 "maple CLASSIC_UI U20 config" "$3"
+exec_test $1 $2 "Maple - Alfawise U20 - CLASSIC_UI" "$3"
 
 use_example_configs Alfawise/U20
 opt_enable BAUD_RATE_GCODE TFT_COLOR_UI
 opt_disable TFT_CLASSIC_UI CUSTOM_STATUS_SCREEN_IMAGE
-exec_test $1 $2 "maple COLOR_UI U20 config" "$3"
+exec_test $1 $2 "Maple - Alfawise U20 - COLOR_UI" "$3"
 
 use_example_configs Alfawise/U20-bltouch
 opt_enable BAUD_RATE_GCODE
-exec_test $1 $2 "maple BLTouch U20 config"
+exec_test $1 $2 "Maple - Alfawise U20 - BLTouch"
 
 # cleanup
 restore_configs