tvl-depot/configs/shared/emacs/.emacs.d/elpa/slack-20180712.2222/slack-message-buffer.el
William Carroll 17ee0e400b Support Vim, Tmux, Emacs with Stow
After moving off of Meta, Dotfiles has a greater responsibility to
manage configs. Vim, Tmux, and Emacs are now within Stow's purview.
2018-09-10 14:53:23 -04:00

434 lines
19 KiB
EmacsLisp

;;; slack-message-buffer.el --- -*- lexical-binding: t; -*-
;; Copyright (C) 2017
;; Author: <yuya373@yuya373>
;; Keywords:
;; 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:
;;
;;; Code:
(require 'eieio)
(require 'slack-room)
(require 'slack-util)
(require 'slack-room-buffer)
(require 'slack-buffer)
(require 'slack-request)
(defface slack-new-message-marker-face
'((t (:foreground "#d33682"
:weight bold
:height 0.8)))
"Face used to New Message Marker."
:group 'slack)
(define-derived-mode slack-message-buffer-mode slack-mode "Slack Message Buffer"
(add-hook 'lui-pre-output-hook 'slack-buffer-buttonize-link nil t)
(add-hook 'lui-pre-output-hook 'slack-add-face-lazy nil t)
(add-hook 'lui-pre-output-hook 'slack-search-code-block nil t)
(add-hook 'lui-post-output-hook 'slack-display-image t t)
;; TODO move to `slack-room-buffer' ?
(cursor-sensor-mode)
(setq-local lui-max-buffer-size nil)
)
(defclass slack-message-buffer (slack-room-buffer)
((oldest :initform nil :type (or null string))
(latest :initform nil :type (or null string))
(marker-overlay :initform nil)
(update-mark-timer :initform '(nil . nil)) ;; (timestamp . timer)
))
(defmethod slack-buffer-last-read ((this slack-message-buffer))
(with-slots (room) this
(oref room last-read)))
(cl-defmethod slack-buffer-update-mark ((this slack-message-buffer) &key (force nil))
(with-slots (room update-mark-timer) this
(let* ((ts (slack-get-ts))
(timer-timeout-sec (or (and force 0) 5))
(prev-mark (or (car update-mark-timer)
(slack-buffer-last-read this)))
(prev-timer (cdr update-mark-timer)))
(when (or force (or (string< prev-mark ts)
(string= prev-mark ts)))
(slack-log (format "%s: update mark to %s"
(slack-room-name room)
ts)
(oref this team))
(when (timerp prev-timer)
(cancel-timer prev-timer))
(cl-labels
((update-mark ()
(slack-buffer-update-mark-request this ts)))
(setq update-mark-timer
(cons ts (run-at-time timer-timeout-sec nil #'update-mark))))))))
(defmethod slack-buffer-update-mark-request ((this slack-message-buffer) ts &optional after-success)
(with-slots (room team) this
(when (slack-room-member-p room)
(cl-labels ((on-update-mark (&key data &allow-other-keys)
(slack-request-handle-error
(data "slack-buffer-update-mark-request")
(oset room last-read ts)
(slack-buffer-update-marker-overlay this)
(when (functionp after-success)
(funcall after-success)))))
(with-slots (id) room
(slack-request
(slack-request-create
(slack-room-update-mark-url room)
team
:type "POST"
:params (list (cons "channel" id)
(cons "ts" ts))
:success #'on-update-mark)))))))
(defmethod slack-buffer-send-message ((this slack-message-buffer) message)
(with-slots (room team) this
(slack-message-send-internal message (oref room id) team)))
(defmethod slack-buffer-latest-ts ((this slack-message-buffer))
(with-slots (room) this
(slack-if-let* ((latest (oref room latest)))
(oref latest ts))))
(defmethod slack-buffer-buffer ((this slack-message-buffer))
(let ((buffer-already-exists-p (get-buffer (slack-buffer-name this)))
(buffer (call-next-method))
(last-read (slack-buffer-last-read this)))
(with-current-buffer buffer
(if (slack-team-mark-as-read-immediatelyp (oref this team))
(progn
(unless buffer-already-exists-p
(goto-char (marker-position lui-input-marker)))
(and (slack-buffer-latest-ts this)
(slack-buffer-update-mark-request this
(slack-buffer-latest-ts this))))
(unless (string= "0" last-read)
(unless buffer-already-exists-p
(slack-buffer-goto last-read))
(slack-buffer-update-marker-overlay this))))
buffer))
(defmethod slack-buffer-display-unread-threads ((this slack-message-buffer))
(with-slots (room team) this
(let* ((threads (mapcar #'(lambda (m) (oref m thread))
(cl-remove-if
#'(lambda (m)
(or (not (slack-message-thread-parentp m))
(not (< 0 (oref (oref m thread) unread-count)))))
(oref room messages))))
(alist (mapcar #'(lambda (thread)
(cons (slack-thread-title thread team) thread))
(cl-sort threads
#'string>
:key #'(lambda (thread) (oref thread thread-ts)))))
(selected (slack-select-from-list (alist "Select Thread: "))))
(slack-thread-show-messages selected room team))))
(defmethod slack-buffer-start-thread ((this slack-message-buffer) ts)
(with-slots (room team) this
(let* ((message (slack-room-find-message room ts))
(buf (slack-create-thread-message-buffer room team ts)))
(when (slack-reply-broadcast-message-p message)
(error "Can't start thread from broadcasted message"))
(when (slack-file-comment-message-p message)
(error "Can't start thread from file comment message"))
(when (slack-file-share-message-p message)
(error "Can't start thread from file share message"))
(slack-buffer-display buf))))
(defmethod slack-buffer-major-mode ((this slack-message-buffer))
'slack-message-buffer-mode)
(defmethod slack-buffer-init-buffer ((this slack-message-buffer))
(let ((buf (call-next-method)))
(with-current-buffer buf
(funcall (slack-buffer-major-mode this))
(slack-buffer-set-current-buffer this)
(goto-char (point-min))
(slack-buffer-insert-load-more this)
(with-slots (room team latest) this
(let* ((messages (slack-room-sorted-messages room))
(latest-message (car (last messages)))
(oldest-message (car messages)))
(cl-loop for m in messages
do (if (and (or (null latest)
(string< latest (oref m ts)))
(not (slack-thread-message-p m)))
(slack-buffer-insert this m t)))
(when latest-message
(slack-buffer-update-lastest this (oref latest-message ts)))
(when oldest-message
(slack-buffer-update-oldest this oldest-message))))
)
(with-slots (room team) this
(let* ((class (eieio-object-class-name this)))
(slack-buffer-push-new-3 class room team)))
buf))
(cl-defmethod slack-buffer-update ((this slack-message-buffer) message &key replace)
(with-slots (room team) this
(let ((buffer (get-buffer (slack-buffer-name this))))
(when (and (slack-team-mark-as-read-immediatelyp team)
(slack-buffer-in-current-frame buffer))
(slack-buffer-update-mark-request this (oref message ts)))
(if replace (slack-buffer-replace this message)
(slack-buffer-update-lastest this (oref message ts))
(with-current-buffer buffer
(slack-buffer-insert this message))))))
(defmethod slack-buffer-display-message-compose-buffer ((this slack-message-buffer))
(with-slots (room team) this
(let ((buf (slack-create-room-message-compose-buffer room team)))
(slack-buffer-display buf))))
(defmethod slack-buffer-update-lastest ((this slack-message-buffer) latest)
(with-slots ((prev-latest latest)) this
(if (or (null prev-latest)
(string< prev-latest latest))
(setq prev-latest latest))))
(defmethod slack-buffer-display-thread ((this slack-message-buffer) ts)
(with-slots (room team) this
(let ((thread (slack-room-find-thread room ts)))
(if thread (slack-thread-show-messages thread room team)
(slack-thread-start)))))
(defmethod slack-buffer-display-edit-message-buffer ((this slack-message-buffer) ts)
(with-slots (room team) this
(let ((buf (slack-create-edit-message-buffer room team ts)))
(slack-buffer-display buf))))
(defmethod slack-create-message-buffer ((room slack-room) team)
(slack-if-let* ((buffer (slack-buffer-find 'slack-message-buffer
room
team)))
buffer
(slack-message-buffer :room room :team team)))
(defmethod slack-buffer-share-message ((this slack-message-buffer) ts)
(with-slots (room team) this
(let ((buf (slack-create-message-share-buffer room team ts)))
(slack-buffer-display buf))))
(defmethod slack-buffer-add-reaction-to-message
((this slack-message-buffer) reaction ts)
(with-slots (room team) this
(slack-message-reaction-add reaction ts room team)))
(defmethod slack-buffer-add-reaction-to-file-comment
((this slack-message-buffer) reaction id)
(with-slots (team) this
(slack-file-comment-add-reaction id reaction team)))
(defmethod slack-buffer-remove-reaction-from-message
((this slack-message-buffer) ts &optional file-comment-id)
(with-slots (room team) this
(let* ((message (slack-room-find-message room ts))
;; TODO
(reactions (if file-comment-id
(slack-message-reactions
(oref (oref message file) initial-comment))
(slack-message-reactions message)))
(reaction (slack-message-reaction-select reactions)))
(slack-message-reaction-remove reaction ts room team))))
(defmethod slack-buffer-pins-remove ((this slack-message-buffer) ts)
(with-slots (room team) this
(slack-message-pins-request slack-message-pins-remove-url
room team ts)))
(defmethod slack-buffer-pins-add ((this slack-message-buffer) ts)
(with-slots (room team) this
(slack-message-pins-request slack-message-pins-add-url
room team ts)))
(defmethod slack-buffer-remove-star ((this slack-message-buffer) ts)
(with-slots (room team) this
(slack-if-let* ((message (slack-room-find-message room ts)))
(slack-message-star-api-request slack-message-stars-remove-url
(list (cons "channel" (oref room id))
(slack-message-star-api-params message))
team))))
(defmethod slack-buffer-add-star ((this slack-message-buffer) ts)
(with-slots (room team) this
(slack-if-let* ((message (slack-room-find-message room ts)))
(slack-message-star-api-request slack-message-stars-add-url
(list (cons "channel" (oref room id))
(slack-message-star-api-params message))
team))))
(defmethod slack-buffer-update-oldest ((this slack-message-buffer) message)
(when (and message (or (null (oref this oldest))
(string< (oref message ts) (oref this oldest))))
(oset this oldest (oref message ts))))
(defmethod slack-buffer-load-missing-messages ((this slack-message-buffer))
(with-slots (room team) this
(cl-labels
((request-messages (latest)
(slack-room-history-request room team
:latest latest
:count 100
:after-success #'after-success))
(after-success (has-more)
(let* ((messages (slack-room-sorted-messages room))
(oldest-message (car messages))
(latest-message (car (last messages))))
(if has-more
(request-messages (oref latest-message ts))
(progn
(with-current-buffer (slack-buffer-buffer this)
(let ((inhibit-read-only t))
(slack-buffer-delete-overlay this)
(delete-region (point-min)
(marker-position lui-output-marker)))
(slack-buffer-prepare-marker-for-history this)
(slack-buffer-insert-load-more this)
(cl-loop for m in messages
do (slack-buffer-insert this m t))
(slack-buffer-goto (slack-buffer-last-read this))
(slack-buffer-update-marker-overlay this))
(when oldest-message
(slack-buffer-update-oldest this
oldest-message)))))))
(oset room messages nil)
(request-messages nil))))
(defmethod slack-buffer-load-more ((this slack-message-buffer))
(with-slots (room team oldest) this
(let ((current-ts (let ((change (next-single-property-change (point) 'ts)))
(when change
(get-text-property change 'ts))))
(cur-point (point)))
(cl-labels
((update-buffer
(messages)
(with-current-buffer (slack-buffer-buffer this)
(slack-buffer-widen
(let ((inhibit-read-only t))
(goto-char (point-min))
(slack-if-let* ((loading-message-end
(slack-buffer-loading-message-end-point this)))
(progn
(slack-buffer-delete-overlay this)
(delete-region (point-min) loading-message-end))
(message "loading-message-end not found, oldest: %s" oldest))
(set-marker lui-output-marker (point-min))
(if (and messages (< 0 (length messages)))
(slack-buffer-insert-load-more this)
(let ((lui-time-stamp-position nil))
(lui-insert "(no more messages)\n")))
(cl-loop for m in messages
do (slack-buffer-insert this m t))
(lui-recover-output-marker)
(slack-buffer-update-marker-overlay this)
))
(if current-ts
(slack-buffer-goto current-ts)
(goto-char cur-point))))
(after-success (&rest _ignore)
(let ((messages (cl-remove-if #'(lambda (e)
(or (string< oldest e)
(string= oldest e)))
(slack-room-sorted-messages room)
:key #'(lambda (e) (oref e ts)))))
(update-buffer messages)
(slack-buffer-update-oldest this (car messages)))))
(slack-room-history-request room team
:oldest oldest
:after-success #'after-success)))))
(defmethod slack-buffer-display-pins-list ((this slack-message-buffer))
(with-slots (room team) this
(cl-labels
((on-pins-list (&key data &allow-other-keys)
(slack-request-handle-error
(data "slack-room-pins-list")
(let* ((buf (slack-create-pinned-items-buffer
room team (plist-get data :items))))
(slack-buffer-display buf)))))
(slack-request
(slack-request-create
slack-room-pins-list-url
team
:params (list (cons "channel" (oref room id)))
:success #'on-pins-list)))))
(defmethod slack-buffer-display-user-profile ((this slack-message-buffer))
(with-slots (room team) this
(let* ((members (cl-remove-if
#'(lambda (e)
(or (slack-user-self-p e team)
(slack-user-hidden-p
(slack-user--find e team))))
(slack-room-get-members room)))
(user-alist (mapcar #'(lambda (u) (cons (slack-user-name u team) u))
members))
(user-id (if (eq 1 (length members))
(car members)
(slack-select-from-list (user-alist "Select User: ")))))
(let ((buf (slack-create-user-profile-buffer team user-id)))
(slack-buffer-display buf)))))
(defmethod slack-buffer-execute-slash-command ((this slack-message-buffer) command)
(with-slots (team) this
(slack-slash-commands-execute command team)))
(defmethod slack-buffer-delete-overlay ((this slack-message-buffer))
(when (oref this marker-overlay)
(delete-overlay (oref this marker-overlay))))
(defmethod slack-buffer-update-marker-overlay ((this slack-message-buffer))
(let ((buf (get-buffer (slack-buffer-name this))))
(and buf (with-current-buffer buf
(let* ((last-read (slack-buffer-last-read this))
(beg (slack-buffer-ts-eq (point-min) (point-max) last-read))
(end (and beg (next-single-property-change beg 'ts))))
(when (and beg end)
(if (oref this marker-overlay)
(move-overlay (oref this marker-overlay)
beg end)
(progn
(oset this marker-overlay (make-overlay beg end))
(let ((after-string
(propertize "New Message"
'face
'slack-new-message-marker-face)))
(overlay-put (oref this marker-overlay)
'after-string
(format "\n%s" after-string)))))))))))
(defmethod slack-buffer-replace ((this slack-message-buffer) message)
(call-next-method)
(slack-buffer-update-marker-overlay this))
(provide 'slack-message-buffer)
;;; slack-message-buffer.el ends here