Subject: Re: bug#44068: 28.0.50; Faulty uses of tabulated-list-format

From: Stephen Berman <[email-masked]>
Date: Tue, 20 Oct 2020 18:09:56 +0200

On Mon, 19 Oct 2020 21:12:44 +0200 Stephen Berman <[email-masked]> wrote:

> On Mon, 19 Oct 2020 21:43:57 +0300 Eli Zaretskii <[email-masked]> wrote:
>
>>> From: Stephen Berman <[email-masked]>
>>> Cc: [email-masked]
>>> Date: Mon, 19 Oct 2020 20:20:16 +0200
>>> 
>>> > Instead of manually fine-tuning each column's width, wouldn't it be
>>> > better to use the string-trim capabilities that replace excess
>>> > characters with an ellipsis?
>>> 
>>> I'm not sure I understand your suggestion.  If you mean to truncate the
>>> column label in the header line when displaying the sort indicator,
>>> e.g. change "Status" to "Sta… ▼", I'm dubious it's worth the effort,
>>> since most of the problematic cases in the Emacs sources are with the
>>> final column, where there's always enough space, but due to the
>>> misleading description in tabulated-list-format's doc string, many modes
>>> have made it unnecessarily narrow, preventing the display of the sort
>>> indicator.  So to avoid the final column being labelled e.g. either
>>> "File" or "Fi… ▼" instead of "File ▼", it is necessary to change the
>>> width manually anyway.  In other words, the truncation proposal would be
>>> an addition to manual fine-tuning (for non-final columns), not a
>>> substitute for it.  Or did you mean something else?
>>
>> Yes, I meant it as an addition, which will make this issue much more
>> future proof.  Documentation update is definitely fine (and should
>> probably go to emacs-27, not to master, right?), and adjusting the
>> column widths to eliminate the ellipsis is also okay.  I just don't
>> want us to end with these two and nothing else.
>
> Ok.  I can try, but anyone should feel free to beat me to it.

I was wrong that adjusting column widths in tabulated-list-format is
necessary: with the attached patch, when a non-final column whose label
is at least 3 characters long is selected, the label will get truncated
and a sort indicator added; but the final column label will never get
truncated and always get a sort indicator when selected.  The question
remains whether the truncated display in current (non-adjusted) uses of
tabulated-list-mode is acceptable: if so, the patch below is all that's
needed, if not, then the adjustments in my first patch can be made as
well.  Those interested should try the patch with list-buffers,
list-bookmarks, list-packages, list-processes, list-dynamic-libraries,
org-lint and flymake-diagnostics-buffer-mode to see how the display
looks.  Depending on the decision about that, the doc string of
tabulated-list-format should be reworked accordingly.

Steve Berman

diff --git a/lisp/emacs-lisp/tabulated-list.el b/lisp/emacs-lisp/tabulated-list.el
index b13f609f88..aff7f985b9 100644
--- a/lisp/emacs-lisp/tabulated-list.el
+++ b/lisp/emacs-lisp/tabulated-list.el
@@ -269,15 +269,18 @@ tabulated-list-init-header
   ;; FIXME: Should share code with tabulated-list-print-col!
   (let ((x (max tabulated-list-padding 0))
 	(button-props `(help-echo "Click to sort by column"
-			mouse-face header-line-highlight
-			keymap ,tabulated-list-sort-button-map))
+			          mouse-face header-line-highlight
+			          keymap ,tabulated-list-sort-button-map))
+        (len (length tabulated-list-format))
 	(cols nil))
     (if display-line-numbers
         (setq x (+ x (tabulated-list-line-number-width))))
     (push (propertize " " 'display `(space :align-to ,x)) cols)
-    (dotimes (n (length tabulated-list-format))
+    (dotimes (n len)
       (let* ((col (aref tabulated-list-format n))
+             (not-last-col (< n (1- len)))
 	     (label (nth 0 col))
+             (pname label)
 	     (width (nth 1 col))
 	     (props (nthcdr 3 col))
 	     (pad-right (or (plist-get props :pad-right) 1))
@@ -287,24 +290,27 @@ tabulated-list-init-header
 	 (cond
 	  ;; An unsortable column
 	  ((not (nth 2 col))
-	   (propertize label 'tabulated-list-column-name label))
+	   (propertize label 'tabulated-list-column-name pname))
 	  ;; The selected sort column
 	  ((equal (car col) (car tabulated-list-sort-key))
-	   (apply 'propertize
-		  (concat label
-			  (cond
-			   ((> (+ 2 (length label)) width) "")
-			   ((cdr tabulated-list-sort-key)
-                            (format " %c"
-                                    tabulated-list-gui-sort-indicator-desc))
-			   (t (format " %c"
-                                      tabulated-list-gui-sort-indicator-asc))))
-		  'face 'bold
-		  'tabulated-list-column-name label
-		  button-props))
+           (let* ((l (length label)))
+             (when (and (>= l 3) (> (+ 2 l) width) not-last-col)
+               (setq label (truncate-string-to-width label (- l 2) nil nil t)))
+	     (apply 'propertize
+                    (concat label
+                            (cond
+                             ((and (< l 3) not-last-col) "")
+                             ((cdr tabulated-list-sort-key)
+                              (format " %c"
+                                      tabulated-list-gui-sort-indicator-desc))
+                             (t (format " %c"
+                                        tabulated-list-gui-sort-indicator-asc))))
+                    'face 'bold
+                    'tabulated-list-column-name pname
+                    button-props)))
 	  ;; Unselected sortable column.
 	  (t (apply 'propertize label
-		    'tabulated-list-column-name label
+		    'tabulated-list-column-name pname
 		    button-props)))
 	 cols)
         (when right-align