ement.el/ement-room-list.el

243 lines
10 KiB
EmacsLisp
Raw Normal View History

2020-12-03 20:56:59 -06:00
;;; ement-room-list.el --- Ement room list buffer -*- lexical-binding: t; -*-
;; Copyright (C) 2020 Adam Porter
;; Author: Adam Porter <adam@alphapapa.net>
;; Keywords: comm
2020-12-03 20:56:59 -06:00
;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <https://www.gnu.org/licenses/>.
;;; Commentary:
;; This library implements a room list buffer.
;; NOTE: It doesn't appear that there is a way to get the number of
;; members in a room other than by retrieving the list of members and
;; counting them. For a large room (e.g. the Spacemacs Gitter room or
;; #debian:matrix.org), that means thousands of users, none of the
;; details of which we care about. So it seems impractical to know
;; the number of members when using lazy-loading. So I guess we just
;; won't show the number of members.
2020-12-04 19:17:58 -06:00
;; TODO: (Or maybe there is, see m.joined_member_count).
2021-07-28 20:12:12 -05:00
;; NOTE: The tabulated-list API is awkward here. When the
;; `tabulated-list-format' is changed, we have to make the change in 4
;; or 5 other places, and if one forgets to, bugs with non-obvious
;; causes happen. I think library using EIEIO or structs would be
;; very helpful.
2020-12-03 20:56:59 -06:00
;;; Code:
;;;; Requirements
(require 'cl-lib)
(require 'tabulated-list)
;; FIXME: Depend on ts.
(require 'ts)
(require 'ement)
;;;; Variables
(defvar ement-room-list-map
(let ((map (make-sparse-keymap)))
(define-key map (kbd "g") #'tabulated-list-revert)
(define-key map (kbd "q") #'bury-buffer)
(define-key map (kbd "S") #'tabulated-list-sort)))
(defvar ement-sessions)
;;;; Customization
;;;; Bookmark support
;; Especially useful with Burly: <https://github.com/alphapapa/burly.el>
(require 'bookmark)
(defun ement-room-list-bookmark-make-record ()
"Return a bookmark record for the `ement-room-list' buffer."
(pcase-let* (((cl-struct ement-session user) ement-session)
((cl-struct ement-user (id session-id)) user))
;; MAYBE: Support bookmarking specific events in a room.
(list (concat "Ement room list (" session-id ")")
(cons 'session-id session-id)
(cons 'handler #'ement-room-list-bookmark-handler))))
(defun ement-room-list-bookmark-handler (bookmark)
"Show Ement room list buffer for BOOKMARK."
(pcase-let* (((map session-id) bookmark))
(unless (cl-loop for session in ement-sessions
thereis (equal session-id (ement-user-id (ement-session-user session))))
;; MAYBE: Automatically connect.
(user-error "Session %s not connected: call `ement-connect' first" session-id))
;; FIXME: Support multiple sessions.
(ement-room-list)))
2020-12-03 20:56:59 -06:00
;;;; Commands
;;;###autoload
(defun ement-room-list (&rest _ignore)
"Show buffer listing joined rooms.
Calls `pop-to-buffer-same-window'. Interactively, with prefix,
call `pop-to-buffer'."
2020-12-03 20:56:59 -06:00
(interactive)
(with-current-buffer (get-buffer-create "*Ement Rooms*")
(ement-room-list-mode)
(setq-local bookmark-make-record-function #'ement-room-list-bookmark-make-record)
;; FIXME: There must be a better way to handle this.
(funcall (if current-prefix-arg
#'pop-to-buffer #'pop-to-buffer-same-window)
(current-buffer))))
2020-12-03 20:56:59 -06:00
;;;###autoload
(defalias 'ement-list-rooms 'ement-room-list)
(define-derived-mode ement-room-list-mode tabulated-list-mode
"Ement room list"
:group 'ement
(setf tabulated-list-format (vector ;; '("U" 1 t) '("🐱" 4 t)
2021-07-28 23:22:03 -05:00
'("D" 1 t) ; Direct
2020-12-03 20:56:59 -06:00
'("Name" 25 t) '("Topic" 35 t)
'("Latest" 20 ement-room-list-latest<)
2021-07-28 20:12:12 -05:00
'("Members" 7 ement-room-list-members<)
2021-07-28 23:22:03 -05:00
;; '("P" 1 t) '("Tags" 15 t)
2020-12-03 20:56:59 -06:00
'("Session" 15 t))
tabulated-list-sort-key '("Latest" . t))
(add-hook 'tabulated-list-revert-hook #'ement-room-list--set-entries nil 'local)
(tabulated-list-init-header)
(ement-room-list--set-entries)
(tabulated-list-revert))
(defun ement-room-list-action (event)
"Show buffer for room at EVENT or point."
2020-12-03 20:56:59 -06:00
(interactive "e")
(mouse-set-point event)
(pcase-let* ((room (tabulated-list-get-id))
2021-07-28 23:22:03 -05:00
(`[,_direct ,_name ,_topic ,_latest ,_members ,user-id]
2020-12-03 20:56:59 -06:00
(tabulated-list-get-entry))
(session (cl-loop for session in ement-sessions
when (equal user-id (ement-user-id (ement-session-user session)))
return session)))
(pop-to-buffer-same-window
2020-12-03 20:56:59 -06:00
(ement-room--buffer session room (ement--room-buffer-name room)))))
;;;; Functions
(defun ement-room-list--set-entries ()
"Set `tabulated-list-entries'."
;; Reset avatar size in case default font size has changed.
;; TODO: After implementing avatars.
;; (customize-set-variable 'ement-room-avatar-in-buffer-name-size ement-room-avatar-in-buffer-name-size)
;; NOTE: From Emacs docs:
;; This buffer-local variable specifies the entries displayed in the
;; Tabulated List buffer. Its value should be either a list, or a
;; function.
;;
;; If the value is a list, each list element corresponds to one entry,
;; and should have the form (ID CONTENTS), where
;;
;; • ID is either nil, or a Lisp object that identifies the
;; entry. If the latter, the cursor stays on the same entry when
;; re-sorting entries. Comparison is done with equal.
;;
;; • CONTENTS is a vector with the same number of elements as
;; tabulated-list-format. Each vector element is either a
;; string, which is inserted into the buffer as-is, or a list
;; (LABEL . PROPERTIES), which means to insert a text button by
;; calling insert-text-button with LABEL and PROPERTIES as
;; arguments (*note Making Buttons::).
;;
;; There should be no newlines in any of these strings.
(setf tabulated-list-entries
(cl-loop for session in ement-sessions
append (mapcar (apply-partially #'ement-room-list--entry session)
(ement-session-rooms session)))))
(defun ement-room-list--entry (session room)
"Return entry for ROOM in SESSION for `tabulated-list-entries'."
2021-07-28 20:12:12 -05:00
(pcase-let* (((cl-struct ement-room id canonical-alias display-name topic latest-ts summary) room)
((map ('m.joined_member_count member-count)) summary)
2020-12-03 20:56:59 -06:00
(e-alias (or canonical-alias
(setf (ement-room-canonical-alias room)
(ement--room-alias room))
id))
(topic (or topic
(setf (ement-room-topic room) (ement--room-topic room))))
;; FIXME: Figure out how to track unread status cleanly.
;; (e-unread (if (buffer-modified-p buffer) "U" ""))
;; (e-avatar (if avatar (ement-resize-avatar avatar) ""))
(e-name (list (propertize (or display-name
(setf (ement-room-display-name room)
(ement--room-display-name room)))
'help-echo e-alias)
'action #'ement-room-list-action))
(e-topic (if topic
;; Remove newlines from topic. Yes, this can happen.
(replace-regexp-in-string "\n" "" topic t t)
""))
(formatted-timestamp (ts-human-format-duration (- (ts-unix (ts-now)) (/ latest-ts 1000))
t))
(e-latest (progn
(when (string-empty-p formatted-timestamp)
(display-warning 'ement-room-list--entry
(format "Room's formatted latest timestamp is empty: %s (%s)" id display-name))
(setf formatted-timestamp "[empty latest timestamp?]"))
(propertize formatted-timestamp 'value latest-ts)))
2020-12-03 20:56:59 -06:00
(e-session (propertize (ement-user-id (ement-session-user session))
'value session))
;; ((e-tags favorite-p low-priority-p) (ement-room-list--tags room))
2021-07-28 23:22:03 -05:00
(e-direct-p (if (ement-room--direct-p room session)
(propertize "D" 'help-echo "Direct room")
""))
2020-12-03 20:56:59 -06:00
;; (e-priority (cond (favorite-p "F")
;; (low-priority-p "l")
;; ("N")))
2021-07-28 20:12:12 -05:00
(e-members (number-to-string member-count)))
2021-07-28 23:22:03 -05:00
(list room (vector ;; e-unread
e-direct-p
2021-07-28 20:12:12 -05:00
e-name e-topic e-latest e-members
2021-07-28 23:22:03 -05:00
;; e-priority e-tags
2021-07-28 20:12:12 -05:00
e-session
2020-12-03 20:56:59 -06:00
;; e-avatar
))))
;; TODO: Define sorters with a macro? This gets repetitive and hard to update.
2021-07-28 20:12:12 -05:00
(defun ement-room-list-members< (a b)
"Return non-nil if entry A has fewer members than room B.
A and B should be entries from `tabulated-list-mode'."
2021-07-28 23:22:03 -05:00
(pcase-let* ((`(,_room [,_direct ,_name-for-list ,_topic ,_latest ,a-members ,_session]) a)
(`(,_room [,_direct ,_name-for-list ,_topic ,_latest ,b-members ,_session]) b))
2021-07-28 20:12:12 -05:00
(< (string-to-number a-members) (string-to-number b-members))))
2020-12-03 20:56:59 -06:00
(defun ement-room-list-latest< (a b)
"Return non-nil if entry A has fewer members than room B.
A and B should be entries from `tabulated-list-mode'."
2021-07-28 23:22:03 -05:00
(pcase-let* ((`(,_room-a [,_direct ,_name-for-list ,_topic ,a-latest ,_a-members ,_session]) a)
(`(,_room-b [,_direct ,_name-for-list ,_topic ,b-latest ,_b-members ,_session]) b))
2020-12-03 20:56:59 -06:00
(< (get-text-property 0 'value a-latest)
(get-text-property 0 'value b-latest))))
;;;; Footer
(provide 'ement-room-list)
;;; ement-room-list.el ends here