branch: elpa/jabber
commit 9c6a6911cb1a1675cceb0167253f92fabea7c7d5
Author: Thanos Apollo <[email protected]>
Commit: Thanos Apollo <[email protected]>

    util: Sort JID completion candidates by availability
---
 lisp/jabber-util.el       | 31 +++++++++++++++++++++++++--
 tests/jabber-test-util.el | 53 +++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 82 insertions(+), 2 deletions(-)

diff --git a/lisp/jabber-util.el b/lisp/jabber-util.el
index 9a9f8c7fd9..d2f98d3196 100644
--- a/lisp/jabber-util.el
+++ b/lisp/jabber-util.el
@@ -237,12 +237,37 @@ is either a JID or display name depending on 
`jabber-jid-completion-display'."
                 (cons (if (and use-names name) name jid) item)))
             roster-items)))
 
+(defconst jabber--presence-sort-order '("chat" "" "away" "dnd" "xa")
+  "Presence shows from most to least available, for contact sorting.
+Offline contacts (nil show) sort last.")
+
+(defun jabber--presence-rank (sym)
+  "Return sort rank of roster symbol SYM; lower is more available."
+  (let ((tail (and sym (member (get sym 'show) jabber--presence-sort-order))))
+    (if tail
+        (- (length jabber--presence-sort-order) (length tail))
+      (length jabber--presence-sort-order))))
+
+(defun jabber--jid-candidate-lessp (a b table)
+  "Order completion candidates A and B by availability, then name.
+TABLE maps candidate strings to roster symbols."
+  (let ((rank-a (jabber--presence-rank (cdr (assoc-string a table t))))
+        (rank-b (jabber--presence-rank (cdr (assoc-string b table t)))))
+    (if (= rank-a rank-b)
+        (string-lessp (downcase a) (downcase b))
+      (< rank-a rank-b))))
+
 (defun jabber--jid-completion-with-metadata (table)
   "Wrap TABLE as a completion table matching both JIDs and names.
 Candidates follow `jabber-jid-completion-display'; the other form
-is shown as an annotation.  Both are matchable regardless of mode."
+is shown as an annotation.  Both are matchable regardless of mode.
+Candidates display sorted by availability, then name."
   (let ((alt-to-candidate (make-hash-table :test 'equal))
-        (use-names (eq jabber-jid-completion-display 'name)))
+        (use-names (eq jabber-jid-completion-display 'name))
+        (sort-candidates
+         (lambda (candidates)
+           (sort (copy-sequence candidates)
+                 (lambda (a b) (jabber--jid-candidate-lessp a b table))))))
     ;; Build reverse lookup: alternate form -> candidate string.
     (dolist (entry table)
       (let* ((candidate (car entry))
@@ -256,6 +281,8 @@ is shown as an annotation.  Both are matchable regardless 
of mode."
       (cond
        ((eq action 'metadata)
         `(metadata
+          (display-sort-function . ,sort-candidates)
+          (cycle-sort-function . ,sort-candidates)
           (annotation-function
            . ,(lambda (candidate)
                 (when-let* ((sym (cdr (assoc-string candidate table t))))
diff --git a/tests/jabber-test-util.el b/tests/jabber-test-util.el
index 6189839090..f75df5f379 100644
--- a/tests/jabber-test-util.el
+++ b/tests/jabber-test-util.el
@@ -298,5 +298,58 @@
     (should-not (jabber-roster-contact-p nil "[email protected]"))
     (should-not (jabber-roster-contact-p jc nil))))
 
+;;; Group: JID completion sorting
+
+(defun jabber-test-util--contact (jid show &optional name)
+  "Return an uninterned roster symbol JID with SHOW and NAME set."
+  (let ((sym (make-symbol jid)))
+    (put sym 'show show)
+    (when name (put sym 'name name))
+    sym))
+
+(ert-deftest jabber-test-util-presence-rank-orders-shows ()
+  "Available shows rank ahead of away shows, offline ranks last."
+  (let ((online (jabber-test-util--contact "[email protected]" ""))
+        (chatty (jabber-test-util--contact "[email protected]" "chat"))
+        (away (jabber-test-util--contact "[email protected]" "away"))
+        (offline (jabber-test-util--contact "[email protected]" nil)))
+    (should (< (jabber--presence-rank chatty)
+               (jabber--presence-rank online)))
+    (should (< (jabber--presence-rank online)
+               (jabber--presence-rank away)))
+    (should (< (jabber--presence-rank away)
+               (jabber--presence-rank offline)))))
+
+(ert-deftest jabber-test-util-jid-candidate-lessp-availability-first ()
+  "An online contact sorts before an offline one regardless of name."
+  (let* ((online (jabber-test-util--contact "[email protected]" ""))
+         (offline (jabber-test-util--contact "[email protected]" nil))
+         (table (list (cons "[email protected]" online)
+                      (cons "[email protected]" offline))))
+    (should (jabber--jid-candidate-lessp "[email protected]" "[email protected]" table))
+    (should-not (jabber--jid-candidate-lessp "[email protected]" "[email protected]" 
table))))
+
+(ert-deftest jabber-test-util-jid-candidate-lessp-name-tiebreak ()
+  "Same availability falls back to case-insensitive name order."
+  (let* ((a (jabber-test-util--contact "[email protected]" ""))
+         (b (jabber-test-util--contact "[email protected]" ""))
+         (table (list (cons "[email protected]" a)
+                      (cons "[email protected]" b))))
+    (should (jabber--jid-candidate-lessp "[email protected]" "[email protected]" table))
+    (should-not (jabber--jid-candidate-lessp "[email protected]" "[email protected]" 
table))))
+
+(ert-deftest jabber-test-util-completion-metadata-sorts-by-availability ()
+  "The completion table's display sort puts available contacts first."
+  (let* ((jabber-jid-completion-display 'jid)
+         (table (list (cons "[email protected]" (jabber-test-util--contact 
"[email protected]" nil))
+                      (cons "[email protected]" (jabber-test-util--contact 
"[email protected]" "away"))
+                      (cons "[email protected]" (jabber-test-util--contact 
"[email protected]" ""))))
+         (collection (jabber--jid-completion-with-metadata table))
+         (metadata (funcall collection "" nil 'metadata))
+         (sorter (cdr (assq 'display-sort-function (cdr metadata)))))
+    (should (functionp sorter))
+    (should (equal '("[email protected]" "[email protected]" "[email protected]")
+                   (funcall sorter '("[email protected]" "[email protected]" 
"[email protected]"))))))
+
 (provide 'jabber-test-util)
 ;;; jabber-test-util.el ends here

Reply via email to