aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--sx-question-list.el12
-rw-r--r--sx-question-mode.el128
-rw-r--r--sx-question.el1
-rw-r--r--sx.el58
4 files changed, 101 insertions, 98 deletions
diff --git a/sx-question-list.el b/sx-question-list.el
index fc6d16c..ce6c1d6 100644
--- a/sx-question-list.el
+++ b/sx-question-list.el
@@ -287,7 +287,7 @@ into consideration.
("K" sx-question-list-previous-far)
("g" sx-question-list-refresh)
(":" sx-question-list-switch-site)
- ("v" sx-question-list-visit)
+ ("v" sx-visit)
("h" sx-question-list-hide)
("m" sx-question-list-mark-read)
([?\r] sx-question-list-display-question)))
@@ -383,16 +383,6 @@ a new list before redisplaying."
(cl-remove-if #'sx-question--hidden-p question-list))))
(when redisplay (tabulated-list-print 'remember)))
-(defun sx-question-list-visit (&optional data)
- "Visits question under point (or from DATA) using `browse-url'."
- (interactive)
- (unless data (setq data (tabulated-list-get-id)))
- (unless data (error "No question here!"))
- (sx-assoc-let data
- (browse-url .link))
- (sx-question--mark-read data)
- (sx-question-list-refresh 'redisplay 'no-update))
-
(defcustom sx-question-list-ago-string " ago"
"String appended to descriptions of the time since something happened.
Used in the questions list to indicate a question was updated
diff --git a/sx-question-mode.el b/sx-question-mode.el
index a9e14e7..59313d1 100644
--- a/sx-question-mode.el
+++ b/sx-question-mode.el
@@ -84,10 +84,6 @@ If WINDOW is given, use that to display the buffer."
;;; Printing a question's content
;;;; Faces and Variables
-(defvar sx-question-mode--overlays nil
- "Question mode overlays.")
-(make-variable-buffer-local 'sx-question-mode--overlays)
-
(defface sx-question-mode-header
'((t :inherit font-lock-variable-name-face))
"Face used on the question headers in the question buffer."
@@ -194,12 +190,12 @@ replaced with the comment."
QUESTION must be a data structure returned by `json-read'."
(setq sx-question-mode--data question)
;; Clear the overlays
- (mapc #'delete-overlay sx-question-mode--overlays)
- (setq sx-question-mode--overlays nil)
+ (mapc #'delete-overlay sx--overlays)
+ (setq sx--overlays nil)
;; Print everything
(sx-question-mode--print-section question)
(sx-assoc-let question
- (mapc #'sx-question-mode--print-section .answers))
+ (mapc #'sx-question-mode--print-section .answers))
(goto-char (point-min))
(with-selected-window sx-question-mode--window
(sx-question-mode-next-section)))
@@ -221,11 +217,10 @@ QUESTION must be a data structure returned by `json-read'."
(defun sx-question-mode--print-section (data)
"Print a section corresponding to DATA.
DATA can represent a question or an answer."
- ;; This makes `data' accessible through
- ;; `(get-text-property (point) 'sx-question-mode--data-here)'
- (sx-question-mode--wrap-in-text-property
- (list 'sx-question-mode--data-here data)
- (sx-assoc-let data
+ ;; This makes `data' accessible through `sx--data-here'.
+ (sx-assoc-let data
+ (sx--wrap-in-text-property
+ (list 'sx--data-here data)
(insert sx-question-mode-header-title
(apply
#'propertize
@@ -238,7 +233,7 @@ DATA can represent a question or an answer."
;; face, action and help-echo
sx-question-mode--title-properties))
;; Sections can be hidden with overlays
- (sx-question-mode--wrap-in-overlay
+ (sx--wrap-in-overlay
'(sx-question-mode--section-content t)
(sx-question-mode--insert-header
;; Author
@@ -265,28 +260,29 @@ DATA can represent a question or an answer."
(propertize sx-question-mode-separator
'face 'sx-question-mode-header
'sx-question-mode--section 4))
- (sx-question-mode--wrap-in-overlay
+ (sx--wrap-in-overlay
'(face sx-question-mode-content-face)
(insert "\n"
(sx-question-mode--fill-and-fontify
.body_markdown)
"\n"
(propertize sx-question-mode-separator
- 'face 'sx-question-mode-header))))
- ;; Comments
- (when .comments
- (insert "\n"
- (apply #'propertize
- sx-question-mode-comments-title
- 'face 'sx-question-mode-title-comments
- 'sx-question-mode--section 3
- sx-question-mode--title-properties))
- (sx-question-mode--wrap-in-overlay
- '(sx-question-mode--section-content t)
- (insert "\n")
- (sx-question-mode--wrap-in-overlay
- '(face sx-question-mode-content-face)
- (mapc #'sx-question-mode--print-comment .comments)))))))
+ 'face 'sx-question-mode-header)))))
+ ;; Comments have their own `sx--data-here' property (so they can
+ ;; be upvoted too).
+ (when .comments
+ (insert "\n"
+ (apply #'propertize
+ sx-question-mode-comments-title
+ 'face 'sx-question-mode-title-comments
+ 'sx-question-mode--section 3
+ sx-question-mode--title-properties))
+ (sx--wrap-in-overlay
+ '(sx-question-mode--section-content t)
+ (insert "\n")
+ (sx--wrap-in-overlay
+ '(face sx-question-mode-content-face)
+ (mapc #'sx-question-mode--print-comment .comments))))))
(defun sx-question-mode--propertize-display-name (author)
"Return display_name of AUTHOR with `sx-question-mode-author' face."
@@ -298,46 +294,21 @@ DATA can represent a question or an answer."
"Print the comment described by alist COMMENT-DATA.
The comment is indented, filled, and then printed according to
`sx-question-mode-comments-format'."
- (sx-assoc-let comment-data
- (insert
- (format
- sx-question-mode-comments-format
- (sx-question-mode--propertize-display-name .owner)
- (substring
- ;; We fill with three spaces at the start, so the comment is
- ;; slightly indented.
- (sx-question-mode--fill-and-fontify
- (concat " " .body_markdown))
- ;; Then we remove the spaces from the first line, since we'll
- ;; add the username there anyway.
- 3)))))
-
-(defmacro sx-question-mode--wrap-in-overlay (properties &rest body)
- "Start a scope with overlay PROPERTIES and execute BODY.
-Overlay is pushed on `sx-question-mode--overlays' and given
-PROPERTIES.
-
-Return the result of BODY."
- (declare (indent 1)
- (debug t))
- `(let ((p (point-marker))
- (result (progn ,@body)))
- (let ((ov (make-overlay p (point)))
- (props ,properties))
- (while props
- (overlay-put ov (pop props) (pop props)))
- (push ov sx-question-mode--overlays))
- result))
-
-(defmacro sx-question-mode--wrap-in-text-property (properties &rest body)
- "Start a scope with PROPERTIES and execute BODY.
-Return the result of BODY."
- (declare (indent 1)
- (debug t))
- `(let ((p (point-marker))
- (result (progn ,@body)))
- (add-text-properties p (point) ,properties)
- result))
+ (sx--wrap-in-text-property
+ (list 'sx--data-here comment-data)
+ (sx-assoc-let comment-data
+ (insert
+ (format
+ sx-question-mode-comments-format
+ (sx-question-mode--propertize-display-name .owner)
+ (substring
+ ;; We fill with three spaces at the start, so the comment is
+ ;; slightly indented.
+ (sx-question-mode--fill-and-fontify
+ (concat " " .body_markdown))
+ ;; Then we remove the spaces from the first line, since we'll
+ ;; add the username there anyway.
+ 3))))))
(defun sx-question-mode--insert-header (&rest args)
"Insert propertized ARGS.
@@ -345,9 +316,7 @@ ARGS is a list of repeating values -- `header', `value', and
`face'. `header' is given `sx-question-mode-header' as a face,
where `value' is given `face' as its face.
-Syntax:
-
- \(fn HEADER VALUE FACE [HEADER VALUE FACE] [HEADER VALUE FACE] ...)"
+\(fn HEADER VALUE FACE [HEADER VALUE FACE] [HEADER VALUE FACE] ...)"
(while args
(insert
(propertize (pop args) 'face 'sx-question-mode-header)
@@ -572,7 +541,7 @@ Letters do not insert themselves; instead, they are commands.
`(("n" sx-question-mode-next-section)
("p" sx-question-mode-previous-section)
("g" sx-question-mode-refresh)
- ("v" sx-question-mode-visit)
+ ("v" sx-visit)
("q" quit-window)
(" " scroll-up-command)
(,(kbd "S-SPC") scroll-down-command)
@@ -583,19 +552,6 @@ Letters do not insert themselves; instead, they are commands.
(,(kbd "<backtab>") backward-button)
([return] push-button)))
-(defun sx-question-mode-visit ()
- "Visit the currently displayed question."
- (interactive)
- (sx-question-mode--ensure-mode)
- (sx-assoc-let
- ;; This allows us to visit the thing-at-point. Which could be a
- ;; question or an answer. We use `append', so that if one
- ;; doesn't have a `link' item we can fallback to
- ;; `sx-question-mode--data'.
- (append (get-text-property (point) 'sx-question-mode--data-here)
- sx-question-mode--data)
- (browse-url .link)))
-
(defun sx-question-mode-refresh ()
"Refresh currently displayed question.
Queries the API for any changes to the question or its answers or
diff --git a/sx-question.el b/sx-question.el
index c66d253..06e8648 100644
--- a/sx-question.el
+++ b/sx-question.el
@@ -39,6 +39,7 @@
comment.owner
comment.body_markdown
comment.body
+ comment.link
answer.last_editor
answer.link
answer.owner
diff --git a/sx.el b/sx.el
index 061c85d..0bab861 100644
--- a/sx.el
+++ b/sx.el
@@ -27,6 +27,7 @@
;; StackMode.
;;; Code:
+(require 'tabulated-list)
(defconst sx-version "0.1" "Version of the `sx' package.")
@@ -129,7 +130,62 @@ would yield
data))))
-;;; Interpreting request data
+;;; Printing request data
+(defvar sx--overlays nil
+ "Overlays created by sx on this buffer.")
+(make-variable-buffer-local 'sx--overlays)
+
+(defmacro sx--wrap-in-overlay (properties &rest body)
+ "Start a scope with overlay PROPERTIES and execute BODY.
+Overlay is pushed on the buffer-local variable `sx--overlays' and
+given PROPERTIES.
+
+Return the result of BODY."
+ (declare (indent 1)
+ (debug t))
+ `(let ((p (point-marker))
+ (result (progn ,@body)))
+ (let ((ov (make-overlay p (point)))
+ (props ,properties))
+ (while props
+ (overlay-put ov (pop props) (pop props)))
+ (push ov sx--overlays))
+ result))
+
+(defmacro sx--wrap-in-text-property (properties &rest body)
+ "Start a scope with PROPERTIES and execute BODY.
+Return the result of BODY."
+ (declare (indent 1)
+ (debug t))
+ `(let ((p (point-marker))
+ (result (progn ,@body)))
+ (add-text-properties p (point) ,properties)
+ result))
+
+
+;;; Using data in buffer
+(defun sx--data-here ()
+ "Get the text property `sx--data-here'."
+ (or (get-text-property (point) 'sx--data-here)
+ (and (derived-mode-p 'sx-question-list-mode)
+ (tabulated-list-get-id))))
+
+(defun sx-visit (data)
+ "Visit DATA in a web browser.
+DATA can be a question, answer, or comment. Interactively, it is
+derived from point position.
+If DATA is a question, also mark it as read."
+ (interactive (list (sx--data-here)))
+ (sx-assoc-let data
+ (when (stringp .link)
+ (browse-url .link))
+ (when (and .title (fboundp 'sx-question--mark-read))
+ (sx-question--mark-read data)
+ (when ((derived-mode-p 'sx-question-list-mode))
+ (sx-question-list-refresh 'redisplay 'no-update)))))
+
+
+;;; Assoc-let
(defun sx--deep-dot-search (data)
"Find symbols somewhere inside DATA which start with a `.'.
Returns a list where each element is a cons cell. The car is the