;;; slack-buffer.el --- slack buffer -*- lexical-binding: t; -*- ;; Copyright (C) 2015 南優也 ;; Author: 南優也 ;; 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 . ;;; Commentary: ;; ;;; Code: (require 'eieio) (require 'lui) (require 'slack-room) (defvar lui-prompt-string "> ") (defvar slack-mode-map (let ((map (make-sparse-keymap))) ;; (define-key map (kbd "C-s C-r") #'slack-room-update-messages) ;; (define-key map (kbd "C-s C-b") #'slack-message-write-another-buffer) map)) (define-derived-mode slack-mode lui-mode "Slack" "" (lui-set-prompt lui-prompt-string) (setq lui-input-function 'slack-message--send)) (define-derived-mode slack-info-mode lui-mode "Slack Info" "" (lui-set-prompt lui-prompt-string)) (defvar slack-current-room-id) (defvar slack-current-team-id) (defvar slack-current-message nil) (defcustom slack-buffer-emojify nil "Show emoji with `emojify' if true." :group 'slack) (defmacro slack-buffer-widen (&rest body) `(save-excursion (save-restriction (widen) ,@body))) (defun slack-get-buffer-create (room) (let* ((buf-name (slack-room-buffer-name room)) (buffer (get-buffer buf-name))) (unless buffer (setq buffer (generate-new-buffer buf-name)) (with-current-buffer buffer (slack-mode) (slack-buffer-insert-previous-link room) (add-hook 'kill-buffer-hook 'slack-reset-room-last-read nil t) (add-hook 'lui-pre-output-hook 'slack-buffer-add-last-ts-property nil t) (add-hook 'lui-post-output-hook 'slack-buffer-add-ts-property nil t))) buffer)) (defmethod slack-buffer-set-current-room-id ((room slack-room)) (set (make-local-variable 'slack-current-room-id) (oref room id))) (defun slack-buffer-set-current-team-id (team) (set (make-local-variable 'slack-current-team-id) (oref team id))) (defun slack-buffer-enable-emojify () (if slack-buffer-emojify (let ((emojify (require 'emojify nil t))) (unless emojify (error "Emojify is not installed")) (emojify-mode t)))) (defun slack-buffer-goto (ts) (let ((point (slack-buffer-ts-eq (point-min) (point-max) ts))) (when point (goto-char point)))) (defmethod slack-buffer-insert-previous-link ((room slack-room)) (let ((oldest (slack-room-prev-link-info room))) (if oldest (slack-buffer-widen (let ((inhibit-read-only t)) (goto-char (point-min)) (insert (concat (propertize "(load more message)" 'face '(:underline t) 'oldest oldest 'keymap (let ((map (make-sparse-keymap))) (define-key map (kbd "RET") #'slack-room-load-prev-messages) map)) "\n\n")) (set-marker lui-output-marker (point))))))) (defmethod slack-buffer-insert-prev-messages ((room slack-room) team oldest-ts) (slack-buffer-widen (let ((messages (slack-room-prev-messages room oldest-ts))) (if messages (progn (slack-buffer-insert-previous-link room) (cl-loop for m in messages do (slack-buffer-insert m team t))) (set-marker lui-output-marker (point-min)) (lui-insert "(no more messages)\n")) (slack-buffer-recover-lui-output-marker)))) (cl-defun slack-buffer-create (room team &key (insert-func #'slack-buffer-insert-messages) (type 'message)) (cl-labels ((get-buffer (type room) (cl-ecase type (message (slack-get-buffer-create room)) (info (slack-get-info-buffer-create room))))) (let* ((buffer (get-buffer type room))) (with-current-buffer buffer (if insert-func (funcall insert-func room team)) (slack-buffer-set-current-room-id room) (slack-buffer-set-current-team-id team) (slack-buffer-enable-emojify)) buffer))) (defun slack-buffer-add-last-ts-property () (when slack-current-message (add-text-properties (point-min) (point-max) `(slack-last-ts ,lui-time-stamp-last)))) (defun slack-buffer-add-ts-property () (when slack-current-message (add-text-properties (point-min) (point-max) `(ts ,(oref slack-current-message ts))))) (defun slack-buffer-insert (message team &optional not-tracked-p) (let ((lui-time-stamp-time (slack-message-time-stamp message)) (beg lui-input-marker) (inhibit-read-only t)) (let ((slack-current-message message)) (lui-insert (slack-message-to-string message team) not-tracked-p)))) (defun slack-buffer-insert-messages (room team) (let* ((sorted (slack-room-sorted-messages room)) (messages (slack-room-latest-messages room sorted))) (if messages (progn ;; (slack-buffer-insert-previous-link room) (cl-loop for m in messages do (slack-buffer-insert m team t)) (let ((latest-message (car (last messages)))) (slack-room-update-last-read room latest-message) (slack-room-update-mark room team latest-message))) (unless (eq 0 (oref room unread-count-display)) (let ((latest-message (car (last sorted)))) (slack-room-update-mark room team latest-message)))))) (defun slack-buffer-show-typing-p (buffer) (cl-case slack-typing-visibility ('frame (slack-buffer-in-current-frame buffer)) ('buffer (slack-buffer-current-p buffer)) ('never nil))) (defun slack-buffer-current-p (buffer) (if buffer (string= (buffer-name buffer) (buffer-name (current-buffer))))) (defun slack-buffer-in-current-frame (buffer) (if buffer (cl-member (buffer-name buffer) (mapcar #'buffer-name (mapcar #'window-buffer (window-list))) :test #'string=))) (cl-defun slack-buffer-update (room msg team &key replace) (let* ((buf-name (slack-room-buffer-name room)) (buffer (get-buffer buf-name))) (if buffer (progn (if (slack-buffer-in-current-frame buffer) (slack-room-update-mark room team msg) (slack-room-inc-unread-count room)) (if replace (slack-buffer-replace buffer msg) (with-current-buffer buffer (slack-room-update-last-read room msg) (slack-buffer-insert msg team)))) (slack-room-inc-unread-count room)))) (defmacro slack-buffer-goto-char (find-point &rest else) `(let* ((cur-point (point)) (ts (get-text-property cur-point 'ts))) (let ((next-point ,find-point)) (if next-point (goto-char next-point) (if (< 0 (length ',else)) ,@else))))) (defun slack-buffer-goto-next-message () (interactive) (slack-buffer-goto-char (slack-buffer-next-point cur-point (point-max) ts) (slack-buffer-goto-first-message))) (defun slack-buffer-goto-prev-message () (interactive) (slack-buffer-goto-char (slack-buffer-prev-point cur-point (point-min) ts) (slack-buffer-goto-last-message))) (defun slack-buffer-goto-first-message () (interactive) (goto-char (slack-buffer-next-point (point-min) (point-max) "0"))) (defun slack-buffer-goto-last-message () (interactive) (goto-char (slack-buffer-prev-point (point-max) (point-min) (format-time-string "%s")))) (defun slack-buffer-header-p (point) (let ((face (get-text-property point 'face))) (string= (format "%s" face) "slack-message-output-header"))) (defun slack-buffer-next-point (start end ts) (cl-loop for i from start to end if (and (string< ts (get-text-property i 'ts)) (slack-buffer-header-p i)) return i)) (defun slack-buffer-prev-point (start end ts) (cl-loop for i from start downto end if (and (string< (get-text-property i 'ts) ts) (slack-buffer-header-p i)) return i)) (defun slack-buffer-ts-eq (start end ts) (if (and start end) (cl-loop for i from start to end if (string= (get-text-property i 'ts) ts) return i))) (defun slack-buffer-ts-not-eq (start end ts) (if (and start end) (cl-loop for i from start to end if (not (string= (get-text-property i 'ts) ts)) return i))) (defun slack-buffer-replace (buffer msg) (with-current-buffer buffer (slack-buffer-widen (let* ((cur-point (point)) (ts (oref msg ts)) (beg (slack-buffer-ts-eq (point-min) (point-max) ts)) (end (slack-buffer-ts-not-eq beg (point-max) ts))) (if (and beg end) (let ((inhibit-read-only t) (lui-time-stamp-last (get-text-property beg 'slack-last-ts))) (delete-region beg end) (set-marker lui-output-marker beg) (slack-buffer-insert msg (slack-team-find slack-current-team-id)) (slack-buffer-recover-lui-output-marker) (slack-buffer-goto ts))))))) (defun slack-buffer-recover-lui-output-marker () (set-marker lui-output-marker (- (marker-position lui-input-marker) (length lui-prompt-string)))) (defun slack-get-info-buffer-create (room) (let* ((buf-name (slack-room-buffer-name room)) (buffer (get-buffer buf-name))) (unless buffer (setq buffer (generate-new-buffer buf-name)) (with-current-buffer buffer (slack-info-mode) (slack-buffer-insert-previous-link room) (add-hook 'kill-buffer-hook 'slack-reset-room-last-read nil t) (add-hook 'lui-pre-output-hook 'slack-buffer-add-last-ts-property nil t) (add-hook 'lui-post-output-hook 'slack-buffer-add-ts-property nil t))) buffer)) (defun slack-buffer-create-info (buf-name insert-func) (let ((buf (slack-get-info-buffer-create buf-name))) (with-current-buffer buf (setq buffer-read-only nil) (erase-buffer) (goto-char (point-min)) (funcall insert-func) (goto-char (point-max)) (setq buffer-read-only t) (slack-buffer-enable-emojify)) buf)) (defun slack-reset-room-last-read () (let ((room (slack-room-find slack-current-room-id (slack-team-find slack-current-team-id)))) (slack-room-update-last-read room (slack-message "msg" :ts "0")))) (provide 'slack-buffer) ;;; slack-buffer.el ends here