3

Is there a way to make dired group directories and symlinks to directories first?

If I add --group-directories, like this:

(setq dired-listing-switches "--group-directories-first -al")

then it groups directories first, but symlinks still get sorted under files.

If I also add --dereference, like:

(setq dired-listing-switches "--group-directories-first --dereference -al")

then symlinks get resolved, and the directories do all show up together. But there's no indication anymore that they are symlinks anymore. I'd like them to be still displayed as symlinks, but get grouped first too.

  • Note that some ls versions do not support the --group-directories-first option. – lawlist May 16 '16 at 1:48
0

Yes, that is what you get if you load standard library ls-lisp.el and you customize option ls-lisp-dirs-first to non-nil.

(If the value of option ls-lisp-emulation is MS-Windows then ls-lisp-dirs-first is non-nil by default. This is the case if you use Emacs on MS Windows, but you can set it this way on any platform.)

If you also use library Dired Sort Menu then you get a S-mouse-2 popup menu in Dired (and a menu-bar menu) that provides all of the ls-lisp.el sorting possibilities.

  • Thanks! That does work, but ls-lisp seems to lack a "-v" flag to sort numbers naturally (ie 1 2 10 instead of 1 10 2). I guess I could hack that in, but ls-lisp is also noticeably slower, so it'd be easier to use a modified ls instead. (It does bother me in ls too, after all, so that might just be the better choice.) – stef May 17 '16 at 2:35
  • Yes, ls-lisp is coded in Lisp, so it can be slower. Yes, it does not support all of the switches of all versions of ls. Remember too that you can define your own commands that call other commands, and some of those can explicitly make use of ls-lisp. IOW, you can mix and match. You need load ls-lisp only once (in any of those commands: (require 'ls-lisp). Then bind variable ls-lisp-use-insert-directory-program as needed, to get the ls behavior you want: from ls or from ls-lisp. – Drew May 17 '16 at 4:13
  • I gave the ls patch a try and posted that as an answer as well. Your answer is probably the better one in general because it's portable, more straightforward and directly answers the question. I would've probably just used ls-lisp as a "good enough" solution if it supported -v as well. – stef May 17 '16 at 11:37
0

The ls-lisp solution is portable and straightforward, but unfortunately lacks some important (to me) flags and is considerably slower.

So instead, one can also modify ls directly. Because I wanted the same feature in ls in the terminal anyway, I wrote a patch for the ls in GNU's coreutils. That's certainly a lot less portable, but it was a much smaller and more desirable change than extending ls-lisp with the missing flags. (Still, ls-lisp can be used as a fallback as well.)

This patch for coreutils (8.25) sorts symlinked directories like directories when --group-directories-first is provided:

diff --git a/src/ls.c b/src/ls.c
index 3572060..8dd6395 100644
--- a/src/ls.c
+++ b/src/ls.c
@@ -2969,10 +2969,10 @@ gobble_file (char const *name, enum filetype type, ino_t inode,
               || is_colored (C_STICKY_OTHER_WRITABLE)))
       /* When dereferencing symlinks, the inode and type must come from
          stat, but readdir provides the inode and type of lstat.  */
-      || ((print_inode || format_needs_type)
+      || ((print_inode || format_needs_type || directories_first)
           && (type == symbolic_link || type == unknown)
           && (dereference == DEREF_ALWAYS
-              || color_symlink_as_referent || check_symlink_color))
+              || color_symlink_as_referent || check_symlink_color || directories_first))
       /* Command line dereferences are already taken care of by the above
          assertion that the inode number is not yet known.  */
       || (print_inode && inode == NOT_AN_INODE_NUMBER)
@@ -3113,7 +3113,7 @@ gobble_file (char const *name, enum filetype type, ino_t inode,
         }

       if (S_ISLNK (f->stat.st_mode)
-          && (format == long_format || check_symlink_color))
+          && (format == long_format || check_symlink_color || directories_first))
         {
           struct stat linkstats;

@@ -3123,7 +3123,7 @@ gobble_file (char const *name, enum filetype type, ino_t inode,
           /* Avoid following symbolic links when possible, ie, when
              they won't be traced and when no indicator is needed.  */
           if (linkname
-              && (file_type <= indicator_style || check_symlink_color)
+              && (file_type <= indicator_style || check_symlink_color || directories_first)
               && stat (linkname, &linkstats) == 0)
             {
               f->linkok = true;
@@ -3131,7 +3131,7 @@ gobble_file (char const *name, enum filetype type, ino_t inode,
               /* Symbolic links to directories that are mentioned on the
                  command line are automatically traced if not being
                  listed as files.  */
-              if (!command_line_arg || format == long_format
+              if (!command_line_arg || format == long_format || directories_first
                   || !S_ISDIR (linkstats.st_mode))
                 {
                   /* Get the linked-to file's mode for the filetype indicator
@@ -3251,6 +3251,15 @@ is_directory (const struct fileinfo *f)
   return f->filetype == directory || f->filetype == arg_directory;
 }

+/* Return true if F refers to a directory or a symlink pointing at a directory.  */
+static bool
+is_directory_or_symlink_to_directory (const struct fileinfo *f)
+{
+  return f->filetype == directory
+    || f->filetype == arg_directory
+    || (f->filetype == symbolic_link && S_ISDIR(f->linkmode));
+}
+
 /* Put the name of the file that FILENAME is a symbolic link to
    into the LINKNAME field of 'f'.  COMMAND_LINE_ARG indicates whether
    FILENAME is a command-line argument.  */
@@ -3396,8 +3405,8 @@ typedef int (*qsortFunc)(V a, V b);
 #define DIRFIRST_CHECK(a, b)                       \
   do                                   \
     {                                  \
-      bool a_is_dir = is_directory ((struct fileinfo const *) a);  \
-      bool b_is_dir = is_directory ((struct fileinfo const *) b);  \
+      bool a_is_dir = is_directory_or_symlink_to_directory ((struct fileinfo const *) a); \
+      bool b_is_dir = is_directory_or_symlink_to_directory ((struct fileinfo const *) b);  \
       if (a_is_dir && !b_is_dir)                   \
         return -1;         /* a goes before b */           \
       if (!a_is_dir && b_is_dir)                   \

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.