my-emacs-d/elpa/sx-20160125.1601/sx-question-mode.el

310 lines
11 KiB
EmacsLisp
Raw Normal View History

2016-09-22 15:58:26 +00:00
;;; sx-question-mode.el --- major-mode for displaying questions -*- lexical-binding: t; -*-
;; Copyright (C) 2014 Artur Malabarba
;; Author: Artur Malabarba <bruce.connor.am@gmail.com>
;; 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 <http://www.gnu.org/licenses/>.
;;; Commentary:
;; This file provides a means to print questions with their answers
;; and all comments. See the customizable group `sx-question-mode'.
;;; Code:
(eval-when-compile
(require 'rx))
(require 'sx)
(require 'sx-switchto)
(require 'sx-question)
(require 'sx-question-print)
;;; Displaying a question
(defcustom sx-question-mode-display-buffer-function #'pop-to-buffer
"Function used to display the question buffer.
Called, for instance, when hitting \\<sx-question-list-mode-map>`\\[sx-question-list-display-question]' on an entry in the
question list.
This is not used when navigating the question list with `\\[sx-question-list-view-next].
Common values for this variable are `pop-to-buffer' and `switch-to-buffer'."
:type 'function
:group 'sx-question-mode)
(defvar sx-question-mode--buffer nil
"Buffer being used to display questions.")
(defvar sx-question-mode--data nil
"The data of the question being displayed.")
(make-variable-buffer-local 'sx-question-mode--data)
(defun sx-question-mode--get-window ()
"Return a window displaying a question, or nil."
(car-safe
(cl-member-if
(lambda (x) (with-selected-window x
(derived-mode-p 'sx-question-mode)))
(window-list nil 'never nil))))
(defun sx-question-mode--display (data &optional window)
"Display question given by DATA on WINDOW.
If WINDOW is nil, use selected one.
Returns the question buffer."
(with-current-buffer
(sx-question-mode--display-buffer window)
(sx-question-mode--erase-and-print-question data)))
(defun sx-question-mode--erase-and-print-question (data)
"Erase contents of buffer and print question given by DATA.
Also marks the question as read with `sx-question--mark-read'."
(sx--ensure-site data)
(sx-question--mark-read data)
(let ((inhibit-read-only t))
(erase-buffer)
(sx-question-mode)
(sx-question-mode--print-question data)
(current-buffer)))
(defun sx-question-mode--display-buffer (window)
"Display and return the buffer used for displaying a question.
Create `sx-question-mode--buffer' if necessary.
If WINDOW is given, use that to display the buffer."
;; Create the buffer if necessary.
(unless (buffer-live-p sx-question-mode--buffer)
(setq sx-question-mode--buffer
(generate-new-buffer "*sx-question*")))
(cond
;; Window was given, use it.
((window-live-p window)
(set-window-buffer window sx-question-mode--buffer))
;; No window, but the buffer is already being displayed somewhere.
((get-buffer-window sx-question-mode--buffer 'visible))
;; Neither, so we create the window.
(t (funcall sx-question-mode-display-buffer-function
sx-question-mode--buffer)))
sx-question-mode--buffer)
;;; Movement commands
;; Sections are headers placed above a question's content or an
;; answer's content, or above the list of comments. They are
;; identified with the `sx-question-mode--section' text property.
;; To move between sections, just search for the property. The value
;; of the text-property is the depth of the section (1 for contents, 2
;; for comments).
(defcustom sx-question-mode-recenter-line 0
"Screen line to which we recenter after moving between sections.
This is used as an argument to `recenter', only used if the end
of section is outside the window.
If nil, no recentering is performed."
:type '(choice (const :tag "Don't recenter" nil)
integer)
:group 'sx-question-mode)
(defun sx-question-mode-next-section (&optional n)
"Move down to next section (question or answer) of this buffer.
Prefix argument N moves N sections down or up."
(interactive "p")
(let ((count (if n (abs n) 1)))
(while (> count 0)
;; This will either move us to the next section, or move out of
;; the current one.
(unless (sx--goto-property-change 'sx-question-mode--section n)
;; If all we did was move out the current one, then move again
;; and we're guaranteed to reach the next section.
(sx--goto-property-change 'sx-question-mode--section n))
(unless (get-char-property (point) 'invisible)
(cl-decf count))))
(when (equal (selected-window) (get-buffer-window))
(when sx-question-mode-recenter-line
(let ((ov (sx-question-mode--section-overlays-at (line-end-position))))
(when (and (overlayp ov) (> (overlay-end ov) (window-end)))
(recenter sx-question-mode-recenter-line))))
(sx-message-help-echo)))
(defun sx-question-mode-previous-section (&optional n)
"Move down to previous section (question or answer) of this buffer.
Prefix argument moves N sections up or down."
(interactive "p")
(sx-question-mode-next-section (- (or n 1))))
(defun sx-question-mode-hide-show-section (&optional _)
"Hide or show section under point.
Optional argument _ is for `push-button'."
(interactive)
(let ((ov (or (sx-question-mode--section-overlays-at
(line-end-position))
(sx-question-mode--section-overlays-at (point)))))
(unless (overlayp ov)
(sx-user-error "Not inside a question or answer"))
(goto-char (overlay-start ov))
(forward-line 0)
(overlay-put
ov 'invisible
(null (overlay-get ov 'invisible)))))
(defun sx-question-mode--section-overlays-at (pos)
"Return the highest priority section overlay at POS.
A section overlay has a `sx-question-mode--section-content'
property."
(cdr-safe (get-char-property-and-overlay
pos 'sx-question-mode--section-content nil)))
;;; Major-mode constants
(defconst sx-question-mode--key-definitions
'(
("<down>" sx-question-mode-next-section)
("<up>" sx-question-mode-previous-section)
("n" sx-question-mode-next-section "Navigate")
("p" sx-question-mode-previous-section "Navigate")
("g" sx-question-mode-refresh)
("v" sx-visit-externally)
("u" sx-upvote "upvote")
("d" sx-downvote "downvote")
("q" quit-window)
("SPC" scroll-up-command)
("e" sx-edit "edit")
("S" sx-search)
("*" sx-favorite "star")
("K" sx-delete "Delete")
("s" sx-switchto-map "switch-to")
("O" sx-question-mode-order-by "Order")
("c" sx-comment "comment")
("a" sx-answer "answer")
("TAB" forward-button "Navigate")
("<S-iso-lefttab>" backward-button)
("<S-tab>" backward-button)
("<backtab>" backward-button))
"List of key definitions for `sx-question-mode'.
This list must follow the form described in
`sx--key-definitions-to-header-line'.")
(defconst sx-question-mode--header-line
(sx--key-definitions-to-header-line
sx-question-mode--key-definitions)
"Header-line used on the question list.")
;;; Major-mode definition
(defconst sx-question-mode--mode-line
'(" "
;; `sx-question-mode--data' is guaranteed to have through
;; `sx--ensure-site' already, so we use `let-alist' instead of
;; `sx-assoc-let' to improve performance (since the mode-line is
;; updated a lot).
(:propertize
(:eval (sx--pretty-site-parameter
(let-alist sx-question-mode--data .site_par)))
face mode-line-buffer-id)
" " mode-name
" ["
"Answers: "
(:propertize
(:eval (number-to-string (let-alist sx-question-mode--data .answer_count)))
face mode-line-buffer-id)
", "
"Stars: "
(:propertize
(:eval (number-to-string (or (let-alist sx-question-mode--data .favorite_count) 0)))
face mode-line-buffer-id)
", "
"Views: "
(:propertize
(:eval (number-to-string (let-alist sx-question-mode--data .view_count)))
face mode-line-buffer-id)
"] ")
"Mode-line construct to use in `sx-question-mode' buffers.")
(define-derived-mode sx-question-mode special-mode "Question"
"Major mode to display and navigate a question and its answers.
Letters do not insert themselves; instead, they are commands.
Don't activate this mode directly. Instead, to print a question
on the current buffer use
`sx-question-mode--erase-and-print-question'.
\\<sx-question-mode>
\\{sx-question-mode}"
(setq header-line-format sx-question-mode--header-line)
(setq mode-line-format sx-question-mode--mode-line)
(buffer-disable-undo (current-buffer))
(set (make-local-variable 'nobreak-char-display) nil)
;; Determine how to close this window.
(unless (window-parameter nil 'quit-restore)
(set-window-parameter
nil 'quit-restore
`(other window nil ,(current-buffer))))
;; We call font-lock-region manually. See `sx-question-mode--insert-markdown'.
(font-lock-mode -1)
(remove-hook 'after-change-functions 'markdown-check-change-for-wiki-link t)
(remove-hook 'window-configuration-change-hook
'markdown-fontify-buffer-wiki-links t))
;; We need this quote+eval combo because `kbd' was a macro in 24.2.
(mapc (lambda (x) (eval `(define-key sx-question-mode-map
(kbd ,(car x)) #',(cadr x))))
sx-question-mode--key-definitions)
(defun sx-question-mode-refresh (&optional no-update)
"Refresh currently displayed question.
Queries the API for any changes to the question or its answers or
comments, and redisplays it.
With non-nil prefix argument NO-UPDATE, just redisplay, don't
query the api."
(interactive "P")
(sx-question-mode--ensure-mode)
(let ((point (point))
(line (count-screen-lines
(window-start) (point))))
(sx-question-mode--erase-and-print-question
(if no-update
sx-question-mode--data
(sx-assoc-let sx-question-mode--data
(sx-question-get-question .site_par .question_id))))
(goto-char point)
(when (equal (selected-window)
(get-buffer-window (current-buffer)))
(recenter line)))
(sx-message "Done."))
(defun sx-question-mode--ensure-mode ()
"Ensures we are in question mode, erroring otherwise."
(unless (derived-mode-p 'sx-question-mode)
(error "Not in `sx-question-mode'")))
(defun sx-question-mode-order-by (sort)
"Order answers in the current buffer by the method SORT.
Sets `sx-question-list--order' and then calls
`sx-question-list-refresh' with `redisplay'."
(interactive
(list (let ((order (sx-completing-read "Order answers by: "
(mapcar #'car sx-question-mode--sort-methods))))
(cdr-safe (assoc-string order sx-question-mode--sort-methods)))))
(when (and sort (functionp sort))
(setq sx-question-mode-answer-sort-function sort)
(sx-question-mode-refresh 'no-update)))
(provide 'sx-question-mode)
;;; sx-question-mode.el ends here
;; Local Variables:
;; indent-tabs-mode: nil
;; End: