Files
noise/noise-switcher.el
T

111 lines
4.7 KiB
EmacsLisp

;;; 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