diff options
Diffstat (limited to 'sx-question-list.el')
-rw-r--r-- | sx-question-list.el | 222 |
1 files changed, 144 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 |