branch: elpa/git-commit commit 68be0584f464b6aa2a7d026339835ee70f472e92 Author: Jonas Bernoulli <jo...@bernoul.li> Commit: Jonas Bernoulli <jo...@bernoul.li>
Implement numeric and version sort for repository lists --- docs/magit.org | 50 +++++++++++++++++++++++++----------- docs/magit.texi | 50 +++++++++++++++++++++++++----------- lisp/magit-repos.el | 68 ++++++++++++++++++++++++++++++++++++++++--------- lisp/magit-submodule.el | 38 +++++++++++++++++++++------ 4 files changed, 156 insertions(+), 50 deletions(-) diff --git a/docs/magit.org b/docs/magit.org index bd0d5ccd18..a88dde8c56 100644 --- a/docs/magit.org +++ b/docs/magit.org @@ -2481,18 +2481,25 @@ buffers. Each element has the form ~(HEADER WIDTH FORMAT PROPS)~. - HEADER is the string displayed in the header. WIDTH is the width of - the column. FORMAT is a function that is called with one argument, - the repository identification (usually its basename), and with - ~default-directory~ bound to the toplevel of its working tree. It - has to return a string to be inserted or nil. PROPS is an alist - that supports the keys ~:right-align~ and ~:pad-right~. + HEADER is the string displayed in the header. WIDTH is the width + of the column. FORMAT is a function that is called with one + argument, the repository identification (usually its basename), + and with ~default-directory~ bound to the toplevel of its working + tree. It has to return a string to be inserted or nil. PROPS is + an alist that supports the keys ~:right-align~, ~:pad-right~ and + ~:sort~. + + The ~:sort~ function has a weird interface described in the + docstring of ~tabulated-list--get-sort~. Alternatively ~<~ and + ~magit-repolist-version<~ can be used as those functions are + automatically replaced with functions that satisfy the interface. + Set ~:sort~ to ~nil~ to inhibit sorting; if unspecifed, then the + column is sortable using the default sorter. You may wish to display a range of numeric columns using just one character per column and without any padding between columns, in - which case you should use an appropriate HEADER, set WIDTH to 1, - and set ~:pad-right~ to 0. ~+~ is substituted for numbers higher - than 9. + which case you should use an appropriat HEADER, set WIDTH to 1, + and set ~:pad-right~ to 9. ~+~ is substituted for numbers higher than 9. #+texinfo: @noindent The following functions can be added to the above option: @@ -6864,12 +6871,25 @@ the super-repository by adding ~magit-insert-modules~ to the hook Each element has the form ~(HEADER WIDTH FORMAT PROPS)~. - HEADER is the string displayed in the header. WIDTH is the width of - the column. FORMAT is a function that is called with one argument, - the repository identification (usually its basename), and with - ~default-directory~ bound to the toplevel of its working tree. It - has to return a string to be inserted or nil. PROPS is an alist - that supports the keys ~:right-align~ and ~:pad-right~. + HEADER is the string displayed in the header. WIDTH is the width + of the column. FORMAT is a function that is called with one + argument, the repository identification (usually its basename), + and with ~default-directory~ bound to the toplevel of its working + tree. It has to return a string to be inserted or nil. PROPS is + an alist that supports the keys ~:right-align~, ~:pad-right~ and + ~:sort~. + + The ~:sort~ function has a weird interface described in the + docstring of ~tabulated-list--get-sort~. Alternatively ~<~ and + ~magit-repolist-version<~ can be used as those functions are + automatically replaced with functions that satisfy the interface. + Set ~:sort~ to ~nil~ to inhibit sorting; if unspecifed, then the + column is sortable using the default sorter. + + You may wish to display a range of numeric columns using just one + character per column and without any padding between columns, in + which case you should use an appropriat HEADER, set WIDTH to 1, + and set ~:pad-right~ to 9. ~+~ is substituted for numbers higher than 9. *** Submodule Transient diff --git a/docs/magit.texi b/docs/magit.texi index 0a8028fbb5..b171dc9d5b 100644 --- a/docs/magit.texi +++ b/docs/magit.texi @@ -3094,18 +3094,25 @@ This option controls what columns are displayed by the command Each element has the form @code{(HEADER WIDTH FORMAT PROPS)}. -HEADER is the string displayed in the header. WIDTH is the width of -the column. FORMAT is a function that is called with one argument, -the repository identification (usually its basename), and with -@code{default-directory} bound to the toplevel of its working tree. It -has to return a string to be inserted or nil. PROPS is an alist -that supports the keys @code{:right-align} and @code{:pad-right}. +HEADER is the string displayed in the header. WIDTH is the width +of the column. FORMAT is a function that is called with one +argument, the repository identification (usually its basename), +and with @code{default-directory} bound to the toplevel of its working +tree. It has to return a string to be inserted or nil. PROPS is +an alist that supports the keys @code{:right-align}, @code{:pad-right} and +@code{:sort}. + +The @code{:sort} function has a weird interface described in the +docstring of @code{tabulated-list--get-sort}. Alternatively @code{<} and +@code{magit-repolist-version<} can be used as those functions are +automatically replaced with functions that satisfy the interface. +Set @code{:sort} to @code{nil} to inhibit sorting; if unspecifed, then the +column is sortable using the default sorter. You may wish to display a range of numeric columns using just one character per column and without any padding between columns, in -which case you should use an appropriate HEADER, set WIDTH to 1, -and set @code{:pad-right} to 0. @code{+} is substituted for numbers higher -than 9. +which case you should use an appropriat HEADER, set WIDTH to 1, +and set @code{:pad-right} to 9. @code{+} is substituted for numbers higher than 9. @end defopt @noindent @@ -8582,12 +8589,25 @@ This option controls what columns are displayed by the command Each element has the form @code{(HEADER WIDTH FORMAT PROPS)}. -HEADER is the string displayed in the header. WIDTH is the width of -the column. FORMAT is a function that is called with one argument, -the repository identification (usually its basename), and with -@code{default-directory} bound to the toplevel of its working tree. It -has to return a string to be inserted or nil. PROPS is an alist -that supports the keys @code{:right-align} and @code{:pad-right}. +HEADER is the string displayed in the header. WIDTH is the width +of the column. FORMAT is a function that is called with one +argument, the repository identification (usually its basename), +and with @code{default-directory} bound to the toplevel of its working +tree. It has to return a string to be inserted or nil. PROPS is +an alist that supports the keys @code{:right-align}, @code{:pad-right} and +@code{:sort}. + +The @code{:sort} function has a weird interface described in the +docstring of @code{tabulated-list--get-sort}. Alternatively @code{<} and +@code{magit-repolist-version<} can be used as those functions are +automatically replaced with functions that satisfy the interface. +Set @code{:sort} to @code{nil} to inhibit sorting; if unspecifed, then the +column is sortable using the default sorter. + +You may wish to display a range of numeric columns using just one +character per column and without any padding between columns, in +which case you should use an appropriat HEADER, set WIDTH to 1, +and set @code{:pad-right} to 9. @code{+} is substituted for numbers higher than 9. @end defopt @node Submodule Transient diff --git a/lisp/magit-repos.el b/lisp/magit-repos.el index 8fd9e1569f..d99373a33a 100644 --- a/lisp/magit-repos.el +++ b/lisp/magit-repos.el @@ -69,13 +69,16 @@ This option controls which repositories are being listed by (defcustom magit-repolist-columns '(("Name" 25 magit-repolist-column-ident nil) - ("Version" 25 magit-repolist-column-version nil) + ("Version" 25 magit-repolist-column-version + ((:sort magit-repolist-version<))) ("B<U" 3 magit-repolist-column-unpulled-from-upstream - ((:right-align t) - (:help-echo "Upstream changes not in branch"))) + (;; (:help-echo "Upstream changes not in branch") + (:right-align t) + (:sort <))) ("B>U" 3 magit-repolist-column-unpushed-to-upstream - ((:right-align t) - (:help-echo "Local changes not in upstream"))) + (;; (:help-echo "Local changes not in upstream") + (:right-align t) + (:sort <))) ("Path" 99 magit-repolist-column-path nil)) "List of columns displayed by `magit-list-repositories'. @@ -86,9 +89,15 @@ of the column. FORMAT is a function that is called with one argument, the repository identification (usually its basename), and with `default-directory' bound to the toplevel of its working tree. It has to return a string to be inserted or nil. PROPS is -an alist that supports the keys `:right-align' and `:pad-right'. -Some entries also use `:help-echo', but `tabulated-list' does not -actually support that yet. +an alist that supports the keys `:right-align', `:pad-right' and +`:sort'. + +The `:sort' function has a weird interface described in the +docstring of `tabulated-list--get-sort'. Alternatively `<' and +`magit-repolist-version<' can be used as those functions are +automatically replaced with functions that satisfy the interface. +Set `:sort' to nil to inhibit sorting; if unspecifed, then the +column is sortable using the default sorter. You may wish to display a range of numeric columns using just one character per column and without any padding between columns, in @@ -105,6 +114,7 @@ than 9." (list (choice :tag "Property" (const :right-align) (const :pad-right) + (const :sort) (symbol)) (sexp :tag "Value")))))) @@ -287,10 +297,23 @@ If it contains \"%s\" then the directory is substituted for that." (caar magit-repolist-columns)) flip)))) (setq tabulated-list-format - (vconcat (mapcar (pcase-lambda (`(,title ,width ,_fn ,props)) - (nconc (list title width t) - (-flatten props))) - magit-repolist-columns)))) + (vconcat (-map-indexed + (lambda (idx column) + (pcase-let* ((`(,title ,width ,_fn ,props) column) + (sort-set (assoc :sort props)) + (sort-fn (cadr sort-set))) + (nconc (list title width + (cond ((eq sort-fn '<) + (magit-repolist-make-sorter + sort-fn #'string-to-number idx)) + ((eq sort-fn 'magit-repolist-version<) + (magit-repolist-make-sorter + sort-fn #'identity idx)) + (sort-fn sort-fn) + (sort-set nil) + (t t))) + (-flatten props)))) + magit-repolist-columns)))) (defun magit-repolist-refresh () (setq tabulated-list-entries @@ -316,6 +339,18 @@ If it contains \"%s\" then the directory is substituted for that." ;;;; Columns +(defun magit-repolist-make-sorter (sort-predicate convert-cell column-idx) + "Return a function suitable as a sorter for tabulated lists. +See `tabulated-list--get-sorter'. Given a more reasonable API +this would not be necessary and one could just use SORT-PREDICATE +directly. CONVERT-CELL can be used to turn the cell value, which +is always a string back into e.g. a number. COLUMN-IDX has to be +the index of the column that uses the returned sorter function." + (lambda (a b) + (funcall sort-predicate + (funcall convert-cell (aref (cadr a) column-idx)) + (funcall convert-cell (aref (cadr b) column-idx))))) + (defun magit-repolist-column-ident (spec) "Insert the identification of the repository. Usually this is just its basename." @@ -357,6 +392,15 @@ Usually this is just its basename." (magit--put-face 0 (match-end 0) 'shadow v)) v)))) +(defun magit-repolist-version< (a b) + (save-match-data + (let ((re "[0-9]+\\(\\.[0-9]*\\)*")) + (setq a (and (string-match re a) (match-string 0 a))) + (setq b (and (string-match re b) (match-string 0 b))) + (cond ((and a b) (version< a b)) + (b nil) + (t t))))) + (defun magit-repolist-column-branch (_) "Insert the current branch." (let ((branch (magit-get-current-branch))) diff --git a/lisp/magit-submodule.el b/lisp/magit-submodule.el index c43c6beb7d..ffd2c82216 100644 --- a/lisp/magit-submodule.el +++ b/lisp/magit-submodule.el @@ -66,14 +66,27 @@ is inserted. If it is nil, then all sections listed in (defcustom magit-submodule-list-columns '(("Path" 25 magit-modulelist-column-path nil) - ("Version" 25 magit-repolist-column-version nil) + ("Version" 25 magit-repolist-column-version + ((:sort magit-repolist-version<))) ("Branch" 20 magit-repolist-column-branch nil) - ("B<U" 3 magit-repolist-column-unpulled-from-upstream ((:right-align t))) - ("B>U" 3 magit-repolist-column-unpushed-to-upstream ((:right-align t))) - ("B<P" 3 magit-repolist-column-unpulled-from-pushremote ((:right-align t))) - ("B>P" 3 magit-repolist-column-unpushed-to-pushremote ((:right-align t))) - ("B" 3 magit-repolist-column-branches ((:right-align t))) - ("S" 3 magit-repolist-column-stashes ((:right-align t)))) + ("B<U" 3 magit-repolist-column-unpulled-from-upstream + ((:right-align t) + (:sort <))) + ("B>U" 3 magit-repolist-column-unpushed-to-upstream + ((:right-align t) + (:sort <))) + ("B<P" 3 magit-repolist-column-unpulled-from-pushremote + ((:right-align t) + (:sort <))) + ("B>P" 3 magit-repolist-column-unpushed-to-pushremote + ((:right-align t) + (:sort <))) + ("B" 3 magit-repolist-column-branches + ((:right-align t) + (:sort <))) + ("S" 3 magit-repolist-column-stashes + ((:right-align t) + (:sort <)))) "List of columns displayed by `magit-list-submodules'. Each element has the form (HEADER WIDTH FORMAT PROPS). @@ -83,7 +96,15 @@ of the column. FORMAT is a function that is called with one argument, the repository identification (usually its basename), and with `default-directory' bound to the toplevel of its working tree. It has to return a string to be inserted or nil. PROPS is -an alist that supports the keys `:right-align' and `:pad-right'. +an alist that supports the keys `:right-align', `:pad-right' and +`:sort'. + +The `:sort' function has a weird interface described in the +docstring of `tabulated-list--get-sort'. Alternatively `<' and +`magit-repolist-version<' can be used as those functions are +automatically replaced with functions that satisfy the interface. +Set `:sort' to nil to inhibit sorting; if unspecifed, then the +column is sortable using the default sorter. You may wish to display a range of numeric columns using just one character per column and without any padding between columns, in @@ -100,6 +121,7 @@ than 9." (list (choice :tag "Property" (const :right-align) (const :pad-right) + (const :sort) (symbol)) (sexp :tag "Value"))))))