feat: add minibuffer conversation switcher with fuzzy completion
This commit is contained in:
@@ -0,0 +1,110 @@
|
||||
;;; noise-switcher.el --- Minibuffer conversation switcher for Noise -*- lexical-binding: t; -*-
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;; Provides minibuffer completion for switching conversations.
|
||||
;; Collects conversations and contacts from the database, presents them
|
||||
;; with fuzzy matching in a vertico-style minibuffer completion.
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'noise-db)
|
||||
|
||||
(defun noise-switcher ()
|
||||
"Switch to a Signal conversation via minibuffer completion.
|
||||
Shows existing conversations and contacts without conversations."
|
||||
(interactive)
|
||||
(noise-db-init)
|
||||
(let* ((collection (noise-switcher--collection))
|
||||
(choice (completing-read
|
||||
"Switch to chat: "
|
||||
(lambda (string pred action)
|
||||
(if (eq action 'metadata)
|
||||
'(metadata
|
||||
(display-sort-function . identity)
|
||||
(annotation-function . noise-switcher--annotate))
|
||||
(complete-with-action action collection string pred)))
|
||||
nil t)))
|
||||
(when choice
|
||||
(let ((item (cdr (assoc choice collection))))
|
||||
(if item
|
||||
(if (gethash "id" item)
|
||||
;; It's a conversation (has an id)
|
||||
(message "Open conversation: %s (not yet implemented)" (gethash "name" item))
|
||||
;; It's just a contact — create conversation stub
|
||||
(message "New conversation with: %s (not yet implemented)" choice))
|
||||
(message "Unknown selection: %s" choice))))))
|
||||
|
||||
(defun noise-switcher--collection ()
|
||||
"Build the completion collection.
|
||||
Returns an alist of (display-string . hash-table)."
|
||||
(let ((conversations (noise-db-get-all-conversations))
|
||||
(contacts (noise-db-get-all-contacts))
|
||||
(collection nil))
|
||||
;; Add conversations
|
||||
(dolist (conv conversations)
|
||||
(let ((name (gethash "name" conv)))
|
||||
(push (cons name conv) collection)))
|
||||
;; Add contacts without conversations
|
||||
(let ((conv-contacts (make-hash-table :test 'equal)))
|
||||
(dolist (conv conversations)
|
||||
(let ((members (noise-db-get-conversation-members (gethash "id" conv))))
|
||||
(dolist (m members)
|
||||
(puthash (gethash "contact_id" m) t conv-contacts))))
|
||||
(dolist (contact contacts)
|
||||
(let ((contact-id (gethash "id" contact))
|
||||
(name (gethash "name" contact)))
|
||||
(unless (gethash contact-id conv-contacts)
|
||||
;; Contact without conversation - add a stub
|
||||
(let ((ht (make-hash-table :test 'equal)))
|
||||
(puthash "name" name ht)
|
||||
(puthash "type" "direct" ht)
|
||||
(puthash "unread_count" 0 ht)
|
||||
(puthash "last_message_time" 0 ht)
|
||||
(push (cons name ht) collection))))))
|
||||
collection))
|
||||
|
||||
(defun noise-switcher--annotate (candidate)
|
||||
"Annotate CANDIDATE with type, unread, and time info for completing-read."
|
||||
(let* ((collection (noise-switcher--collection))
|
||||
(item (cdr (assoc candidate collection))))
|
||||
(when item
|
||||
(let* ((type (gethash "type" item "direct"))
|
||||
(unread (gethash "unread_count" item 0))
|
||||
(time (gethash "last_message_time" item 0))
|
||||
(time-str (noise-switcher--format-time-annot time))
|
||||
(annotation (format "%s" type)))
|
||||
(when (> unread 0)
|
||||
(setq annotation (concat annotation (format " · %d unread" unread))))
|
||||
(when (> time 0)
|
||||
(setq annotation (concat annotation (format " · %s" time-str))))
|
||||
(format " %s" annotation)))))
|
||||
|
||||
(defun noise-switcher--format-time-annot (timestamp)
|
||||
"Format TIMESTAMP for annotation display."
|
||||
(if (<= timestamp 0)
|
||||
""
|
||||
(let* ((msg-time (seconds-to-time timestamp))
|
||||
(diff-days (/ (float-time (time-subtract (current-time) msg-time)) 86400)))
|
||||
(cond
|
||||
((< diff-days 1) (format-time-string "%H:%M" msg-time))
|
||||
((< diff-days 7) (format-time-string "%a" msg-time))
|
||||
(t (format-time-string "%b %e" msg-time))))))
|
||||
|
||||
(defun noise-switcher--format-candidate (name info)
|
||||
"Format a switcher candidate for display."
|
||||
(let* ((type (or (cdr (assoc "type" info)) "direct"))
|
||||
(unread (or (cdr (assoc "unread_count" info)) 0))
|
||||
(time (or (cdr (assoc "last_message_time" info)) 0))
|
||||
(time-str (if (> time 0) (noise-switcher--format-time-annot time) ""))
|
||||
(annotation (concat type (if (> unread 0) (format " · %d unread" unread) "")
|
||||
(if (> time 0) (format " · %s" time-str) ""))))
|
||||
(format "%-25s %s" name annotation)))
|
||||
|
||||
(defun noise-switcher--filter (query candidates)
|
||||
"Filter CANDIDATES by QUERY using substring matching."
|
||||
(let ((q (downcase query)))
|
||||
(seq-filter (lambda (c) (string-match-p (regexp-quote q) (downcase c))) candidates)))
|
||||
|
||||
(provide 'noise-switcher)
|
||||
;;; noise-switcher.el ends here
|
||||
Reference in New Issue
Block a user