aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--sx-question-list.el222
-rw-r--r--sx-view.el82
2 files changed, 226 insertions, 78 deletions
diff --git a/sx-question-list.el b/sx-question-list.el
index 9e94536..e1ea349 100644
--- a/sx-question-list.el
+++ b/sx-question-list.el
@@ -93,10 +93,115 @@
:group 'sx-question-list-faces)
+;;; Backend variables
+(defvar sx-question-list--print-function #'sx-question-list--print-info
+ "Function to convert a question alist into a tabulated-list entry.
+Used by `sx-question-list-mode', the default value is
+`sx-question-list--print-info'.
+
+If this is set to a different value, it may be necessary to
+change `tabulated-list-format' accordingly.")
+(make-variable-buffer-local 'sx-question-list--print-function)
+
+(defun sx-question-list--print-info (question-data)
+ "Convert `json-read' QUESTION-DATA into tabulated-list format.
+
+This is the default printer used by `sx-question-list'. It
+assumes QUESTION-DATA is an alist containing (at least) the
+elements:
+ `site', `score', `upvoted', `answer_count', `title',
+ `last_activity_date', `tags', `uestion_id'.
+
+Also see `sx-question-list-refresh'."
+ (sx-assoc-let question-data
+ (let ((favorite (if (member .question_id
+ (assoc .site
+ sx-favorites--user-favorite-list))
+ (if (char-displayable-p ?\x2b26) "\x2b26" "*") " ")))
+ (list
+ question-data
+ (vector
+ (list (int-to-string .score)
+ 'face (if .upvoted 'sx-question-list-score-upvoted
+ 'sx-question-list-score))
+ (list (int-to-string .answer_count)
+ 'face (if (sx-question--accepted-answer-id question-data)
+ 'sx-question-list-answers-accepted
+ 'sx-question-list-answers))
+ (concat
+ (propertize
+ .title
+ 'face (if (sx-question--read-p question-data)
+ 'sx-question-list-read-question
+ ;; Increment `sx-question-list--unread-count' for
+ ;; the mode-line.
+ (cl-incf sx-question-list--unread-count)
+ 'sx-question-list-unread-question))
+ (propertize " " 'display "\n ")
+ (propertize favorite 'face 'sx-question-list-favorite)
+ " "
+ (propertize (concat (sx-time-since .last_activity_date)
+ sx-question-list-ago-string)
+ 'face 'sx-question-list-date)
+ " "
+ (propertize (mapconcat #'sx-question--tag-format .tags " ")
+ 'face 'sx-question-list-tags)
+ (propertize " " 'display "\n")))))))
+
+(defvar sx-question-list--refresh-function
+ (lambda ()
+ (sx-question-get-questions
+ sx-question-list--current-site))
+ "Function used to refresh the list of questions to be displayed.
+Used by `sx-question-list-mode', this is a function, called with
+no arguments, which returns a list questions to be displayed,
+like the one returned by `sx-question-get-questions'.
+
+If this is not set, the value of `sx-question-list--dataset' is
+used, and the list is simply redisplayed.")
+(make-variable-buffer-local 'sx-question-list--refresh-function)
+
+(defvar sx-question-list--next-page-function nil
+ "Function used to fetch the next page of questions to be displayed.
+Used by `sx-question-list-mode'. This is a function, called with
+no arguments, which returns a list questions to be displayed,
+like the one returned by `sx-question-get-questions'.
+
+This function will be called when the user presses \\<sx-question-list-mode-map>\\[sx-question-list-next] at the end
+of the question list. It should either return nil (indicating
+\"no more questions\") or return a list of questions which will
+appended to the currently displayed list.
+
+If this is not set, it's the same as a function which always
+returns nil.")
+(make-variable-buffer-local 'sx-question-list--next-page-function)
+
+(defvar sx-question-list--dataset nil
+ "The logical data behind the displayed list of questions.
+This dataset contains even questions that are hidden by the user,
+and thus not displayed in the list of questions.
+
+This is ignored if `sx-question-list--refresh-function' is set.")
+(make-variable-buffer-local 'sx-question-list--dataset)
+
+
;;; Mode Definition
-(define-derived-mode sx-question-list-mode tabulated-list-mode "Question List"
+(define-derived-mode sx-question-list-mode
+ tabulated-list-mode "Question List"
"Major mode for browsing a list of questions from StackExchange.
Letters do not insert themselves; instead, they are commands.
+
+To use this mode, activate it and then optionally set some of the
+following variables:
+
+ - `sx-question-list--print-function'
+ - `sx-question-list--refresh-function' or `sx-question-list--dataset'
+ - `sx-question-list--next-page-function'
+
+If none of these is configured, the behaviour is that of a
+\"Frontpage\", for the site given by
+`sx-question-list--current-site'.
+
\\<sx-question-list>
\\{sx-question-list}"
(hl-line-mode 1)
@@ -209,11 +314,6 @@ Non-interactively, DATA is a question alist."
(defvar sx-question-list--current-site "emacs"
"Site being displayed in the *question-list* buffer.")
-(defvar sx-question-list--current-dataset nil
- "The logical data behind the displayed list of questions.
-This dataset contains even questions that are hidden by the user,
-and thus not displayed in the list of questions.")
-
(defun sx-question-list-refresh (&optional redisplay no-update)
"Update the list of questions.
If REDISPLAY is non-nil (or if interactive), also call `tabulated-list-print'.
@@ -223,14 +323,14 @@ a new list before redisplaying."
;; Reset the mode-line unread count (we rebuild it here).
(setq sx-question-list--unread-count 0)
(let ((question-list
- (if (and no-update sx-question-list--current-dataset)
- sx-question-list--current-dataset
- (sx-question-get-questions
- sx-question-list--current-site))))
- (setq sx-question-list--current-dataset question-list)
+ (if (or no-update
+ (null (functionp sx-question-list--refresh-function)))
+ sx-question-list--dataset
+ (funcall sx-question-list--refresh-function))))
+ (setq sx-question-list--dataset question-list)
;; Print the result.
(setq tabulated-list-entries
- (mapcar #'sx-question-list--print-info
+ (mapcar sx-question-list--print-function
(cl-remove-if #'sx-question--hidden-p question-list))))
(when redisplay (tabulated-list-print 'remember)))
@@ -251,44 +351,6 @@ Used in the questions list to indicate a question was updated
:type 'string
:group 'sx-question-list)
-(defun sx-question-list--print-info (question-data)
- "Convert `json-read' QUESTION-DATA into tabulated-list format.
-See `sx-question-list-refresh'."
- (sx-assoc-let question-data
- (let ((favorite (if (member .question_id
- (assoc .site
- sx-favorites--user-favorite-list))
- (if (char-displayable-p ?\x2b26) "\x2b26" "*") " ")))
- (list
- question-data
- (vector
- (list (int-to-string .score)
- 'face (if .upvoted 'sx-question-list-score-upvoted
- 'sx-question-list-score))
- (list (int-to-string .answer_count)
- 'face (if (sx-question--accepted-answer-id question-data)
- 'sx-question-list-answers-accepted
- 'sx-question-list-answers))
- (concat
- (propertize
- .title
- 'face (if (sx-question--read-p question-data)
- 'sx-question-list-read-question
- ;; Increment `sx-question-list--unread-count' for
- ;; the mode-line.
- (cl-incf sx-question-list--unread-count)
- 'sx-question-list-unread-question))
- (propertize " " 'display "\n ")
- (propertize favorite 'face 'sx-question-list-favorite)
- " "
- (propertize (concat (sx-time-since .last_activity_date)
- sx-question-list-ago-string)
- 'face 'sx-question-list-date)
- " "
- (propertize (mapconcat #'sx-question--tag-format .tags " ")
- 'face 'sx-question-list-tags)
- (propertize " " 'display "\n")))))))
-
(defun sx-question-list-view-previous (n)
"Move cursor up N questions up and display this question.
Displayed in `sx-question-mode--window', replacing any question
@@ -308,7 +370,29 @@ that may currently be there."
"Move cursor down N questions.
This does not update `sx-question-mode--window'."
(interactive "p")
- (forward-line n))
+ (if (and (< n 0) (bobp))
+ (sx-question-list-refresh 'redisplay)
+ (let ((line (line-number-at-pos (point))))
+ (forward-line n)
+ ;; If we were trying to move forward, but we hit the end.
+ (when (and (> n 0) (= line (line-number-at-pos (point))))
+ ;; Try to get more questions.
+ (sx-question-list-next-page)))))
+
+(defun sx-question-list-next-page ()
+ "Fetch and display the next page of questions."
+ (interactive)
+ (let ((list (when sx-question-list--next-page-function
+ (funcall sx-question-list--next-page-function))))
+ (if (null list)
+ (progn (message "No further questions.")
+ (forward-line 0))
+ ;; Try to be at the right place.
+ (goto-char (point-max))
+ (setq sx-question-list--dataset
+ (append sx-question-list--dataset list))
+ (sx-question-list-refresh 'redisplay 'no-update)
+ (forward-line 1))))
(defun sx-question-list-previous (n)
"Move cursor up N questions.
@@ -356,36 +440,18 @@ relevant window."
(defun sx-question-list-switch-site (site)
"Switch the current site to SITE and display its questions.
-Uses `ido-completing-read' if variable `ido-mode' is active. Retrieves
-completions from `sx-site-get-api-tokens'. Sets
-`sx-question-list--current-site' and then
+Use `ido-completing-read' if variable `ido-mode' is active.
+Retrieve completions from `sx-site-get-api-tokens'.
+Sets `sx-question-list--current-site' and then call
`sx-question-list-refresh' with `redisplay'."
(interactive
(list (funcall (if ido-mode #'ido-completing-read #'completing-read)
- "Switch to site: " (sx-site-get-api-tokens)
- (lambda (site)
- (not (equal site sx-question-list--current-site)))
- t)))
- (setq sx-question-list--current-site site)
- (sx-question-list-refresh 'redisplay))
-
-(defvar sx-question-list--buffer nil
- "Buffer where the list of questions is displayed.")
-
-(defun list-questions (no-update)
- "Display a list of StackExchange questions.
-NO-UPDATE is passed to `sx-question-list-refresh'."
- (interactive "P")
- (sx-initialize)
- (unless (buffer-live-p sx-question-list--buffer)
- (setq sx-question-list--buffer
- (generate-new-buffer "*question-list*")))
- (with-current-buffer sx-question-list--buffer
- (sx-question-list-mode)
- (sx-question-list-refresh 'redisplay no-update))
- (switch-to-buffer sx-question-list--buffer))
-
-(defalias 'sx-list-questions #'list-questions)
+ "Switch to site: " (sx-site-get-api-tokens)
+ (lambda (site) (not (equal site sx-question-list--current-site)))
+ t)))
+ (when (and (stringp site) (> (length site) 0))
+ (setq sx-question-list--current-site site)
+ (sx-question-list-refresh 'redisplay)))
(provide 'sx-question-list)
;;; sx-question-list.el ends here
diff --git a/sx-view.el b/sx-view.el
new file mode 100644
index 0000000..69060ea
--- /dev/null
+++ b/sx-view.el
@@ -0,0 +1,82 @@
+;;; sx-view.el --- User-level functions for viewing frontpages. -*- 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:
+
+;;
+
+
+;;; Code:
+
+(require 'sx)
+(require 'sx-question-list)
+
+(defcustom sx-view-default-site "emacs"
+ "Name of the site to use by default when listing questions."
+ :type 'string
+ :group 'stack-exchange)
+
+(defmacro sx-view--define-page (page)
+ "Define a stack-exchange page called PAGE.
+Page is a capitalized string.
+
+This defines a command `sx-view-PAGE' for displaying the page,
+and a variable `sx-view--PAGE-buffer' for holding the bufer."
+ (declare (indent 1) (debug t))
+ (let* ((name (downcase page))
+ (buffer-variable
+ (intern (concat "sx-view--" name "-buffer"))))
+ `(progn
+ (defvar ,buffer-variable nil
+ ,(format "Buffer where the %s questions are displayed."
+ page))
+ (defun
+ ,(intern (concat "sx-view-" name))
+ (&optional no-update site)
+ ,(format "Display a list of %s questions for SITE.
+
+NO-UPDATE (the prefix arg) is passed to `sx-question-list-refresh'.
+If SITE is nil, use `sx-view-default-site'."
+ page)
+ (interactive
+ (list current-prefix-arg
+ (funcall (if ido-mode #'ido-completing-read #'completing-read)
+ (format "Site (%s): " sx-view-default-site)
+ (sx-site-get-api-tokens) nil t nil nil
+ sx-view-default-site)))
+ (sx-initialize)
+ (unless site (setq site sx-view-default-site))
+ ;; Create the buffer
+ (unless (buffer-live-p ,buffer-variable)
+ (setq ,buffer-variable
+ (generate-new-buffer "*question-list*")))
+ ;; Fill the buffer with content.
+ (with-current-buffer ,buffer-variable
+ (sx-question-list-mode)
+ (setq sx-question-list--current-site site)
+ (setq sx-question-list--current-page ,page)
+ (sx-question-list-refresh 'redisplay no-update))
+ (switch-to-buffer ,buffer-variable)))))
+
+
+;;; FrontPage
+(sx-view--define-page "FrontPage")
+
+(provide 'sx-view)
+;;; sx-view.el ends here