aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSean Allred <code@seanallred.com>2014-12-30 17:47:42 -0500
committerSean Allred <code@seanallred.com>2014-12-30 17:47:42 -0500
commitb20f56b0c59b1e99fac745e408369d01de0f0bb5 (patch)
treefa30f72a8fbe94e5b319c9163b1d70d8768ad65e
parentc215e84da4dcfa63c7a0c05996cc131e031efe64 (diff)
parent1267f300c850173e74dda0b7f704261b4a25b85c (diff)
Merge branch 'master' into issue-151--dot-variables
Conflicts: sx.el
-rw-r--r--sx-interaction.el27
-rw-r--r--sx-question-list.el7
-rw-r--r--sx-question-mode.el2
-rw-r--r--sx-question.el29
-rw-r--r--sx.el93
5 files changed, 109 insertions, 49 deletions
diff --git a/sx-interaction.el b/sx-interaction.el
index c6f2639..372a5b1 100644
--- a/sx-interaction.el
+++ b/sx-interaction.el
@@ -107,7 +107,7 @@ Only fields contained in TO are copied."
;;; Visiting
-(defun sx-visit (data &optional copy-as-kill)
+(defun sx-visit-externally (data &optional copy-as-kill)
"Visit DATA in a web browser.
DATA can be a question, answer, or comment. Interactively, it is
derived from point position.
@@ -129,13 +129,31 @@ If DATA is a question, also mark it as read."
(sx-question--mark-read data)
(sx--maybe-update-display))))
+(defun sx-open-link (link)
+ "Visit element given by LINK inside Emacs.
+Element can be a question, answer, or comment."
+ (interactive
+ (let ((def (with-temp-buffer
+ (save-excursion (yank))
+ (thing-at-point 'url))))
+ (list (read-string (concat "Link (" def "): ") nil nil def))))
+ (let ((data (sx--link-to-data link)))
+ (sx-assoc-let data
+ (cl-case .type
+ (answer
+ (sx-display-question
+ (sx-question-get-from-answer .site .id) 'focus))
+ (question
+ (sx-display-question
+ (sx-question-get-question .site .id) 'focus))))))
+
;;; Displaying
(defun sx-display-question (&optional data focus window)
"Display question given by DATA, on WINDOW.
When DATA is nil, display question under point. When FOCUS is
non-nil (the default when called interactively), also focus the
-relevant window.
+relevant window.
If WINDOW nil, the window is decided by
`sx-question-mode-display-buffer-function'."
@@ -255,13 +273,13 @@ If SILENT is nil, message the user about this limit."
(defun sx--get-post (type site id)
"Find in the database a post identified by TYPE, SITE and ID.
-TYPE is `question' or `answer'.
+TYPE is `question' or `answer'.
SITE is a string.
ID is an integer."
(let ((db (cons sx-question-mode--data
sx-question-list--dataset)))
(setq db
- (cond
+ (cond
((string= type "question") db)
((string= type "answer")
(apply #'cl-map 'list #'identity
@@ -327,6 +345,7 @@ from context at point."
(sx-site-get-api-tokens) nil t nil nil
default)))
+;;;###autoload
(defun sx-ask (site)
"Start composing a question for SITE.
SITE is a string, indicating where the question will be posted."
diff --git a/sx-question-list.el b/sx-question-list.el
index 6537d2b..4bd6478 100644
--- a/sx-question-list.el
+++ b/sx-question-list.el
@@ -317,7 +317,7 @@ into consideration.
(":" sx-question-list-switch-site)
("t" sx-tab-switch)
("a" sx-ask)
- ("v" sx-visit)
+ ("v" sx-visit-externally)
("u" sx-toggle-upvote)
("d" sx-toggle-downvote)
("h" sx-question-list-hide)
@@ -333,6 +333,11 @@ Non-interactively, DATA is a question alist."
(tabulated-list-get-id)
(sx-user-error "Not in `sx-question-list-mode'"))))
(sx-question--mark-hidden data)
+ ;; The current entry will not be present after the list is
+ ;; redisplayed. To avoid `tabulated-list-mode' getting lost (and
+ ;; sending us to the top) we move to the next entry before
+ ;; redisplaying.
+ (forward-line 1)
(when (called-interactively-p 'any)
(sx-question-list-refresh 'redisplay 'noupdate)))
diff --git a/sx-question-mode.el b/sx-question-mode.el
index 8d06078..a60cf3a 100644
--- a/sx-question-mode.el
+++ b/sx-question-mode.el
@@ -224,7 +224,7 @@ Letters do not insert themselves; instead, they are commands.
("p" sx-question-mode-previous-section)
("g" sx-question-mode-refresh)
("c" sx-comment)
- ("v" sx-visit)
+ ("v" sx-visit-externally)
("u" sx-toggle-upvote)
("d" sx-toggle-downvote)
("q" quit-window)
diff --git a/sx-question.el b/sx-question.el
index 9fb31fc..03ebb4b 100644
--- a/sx-question.el
+++ b/sx-question.el
@@ -54,6 +54,20 @@ If QUESTION-ID doesn't exist on SITE, raise an error."
(error "Couldn't find question %S in %S"
question-id site))))
+(defun sx-question-get-from-answer (site answer-id)
+ "Get question from SITE to which ANSWER-ID belongs.
+If ANSWER-ID doesn't exist on SITE, raise an error."
+ (let ((res (sx-method-call 'answers
+ :id answer-id
+ :site site
+ :submethod 'questions
+ :auth t
+ :filter sx-browse-filter)))
+ (if (vectorp res)
+ (elt res 0)
+ (error "Couldn't find answer %S in %S"
+ answer-id site))))
+
;;; Question Properties
@@ -145,14 +159,13 @@ If no cache exists for it, initialize one with SITE."
(let ((site-cell (assoc .site sx-question--user-hidden-list)))
;; If question already hidden, do nothing.
(unless (memq .question_id site-cell)
- ;; First question from this site.
- (push (list .site .question_id) sx-question--user-hidden-list)
- ;; Question wasn't present.
- ;; Add it in, but make sure it's sorted (just in case we need
- ;; it later).
- (sx-sorted-insert-skip-first .question_id site-cell >)
- ;; This causes a small lag on `j' and `k' as the list gets large.
- ;; Should we do this on a timer?
+ (if (null site-cell)
+ ;; First question from this site.
+ (push (list .site .question_id) sx-question--user-hidden-list)
+ ;; Not first question and question wasn't present.
+ ;; Add it in, but make sure it's sorted (just in case we
+ ;; decide to rely on it later).
+ (sx-sorted-insert-skip-first .question_id site-cell >))
;; Save the results.
(sx-cache-set 'hidden-questions sx-question--user-hidden-list)))))
diff --git a/sx.el b/sx.el
index d93719e..508de46 100644
--- a/sx.el
+++ b/sx.el
@@ -61,7 +61,16 @@ DATA can also be the link itself."
(cdr (assoc 'link data)))))
(when (stringp link)
(replace-regexp-in-string
- "^https?://\\(?:\\(?1:[^/]+\\)\\.stackexchange\\|\\(?2:[^/]+\\)\\)\\.[^.]+/.*$"
+ (rx string-start
+ "http" (optional "s") "://"
+ (or
+ (sequence
+ (group-n 1 (+ (not (any "/"))))
+ ".stackexchange")
+ (group-n 2 (+ (not (any "/")))))
+ "." (+ (not (any ".")))
+ "/" (* any)
+ string-end)
"\\1\\2" link))))
(defun sx--ensure-site (data)
@@ -74,6 +83,54 @@ with a `link' property)."
(cdr data))))
data))
+(defun sx--link-to-data (link)
+ "Convert string LINK into data that can be displayed."
+ (let ((result (list (cons 'site (sx--site link)))))
+ ;; Try to strip a question or answer ID
+ (when (or
+ ;; Answer
+ (and (or (string-match
+ ;; From 'Share' button
+ (rx "/a/"
+ ;; Question ID
+ (group (+ digit))
+ ;; User ID
+ "/" (+ digit)
+ ;; Answer ID
+ (group (or (sequence "#" (* any)) ""))
+ string-end) link)
+ (string-match
+ ;; From URL
+ (rx "/questions/" (+ digit) "/"
+ (+ (not (any "/"))) "/"
+ ;; User ID
+ (optional (group (+ digit)))
+ (optional "/")
+ (group (or (sequence "#" (* any)) ""))
+ string-end) link))
+ (push '(type . answer) result))
+ ;; Question
+ (and (or (string-match
+ ;; From 'Share' button
+ (rx "/q/"
+ ;; Question ID
+ (group (+ digit))
+ ;; User ID
+ (optional "/" (+ digit))
+ ;; Answer or Comment ID
+ (group (or (sequence "#" (* any)) ""))
+ string-end) link)
+ (string-match
+ ;; From URL
+ (rx "/questions/"
+ ;; Question ID
+ (group (+ digit))
+ "/") link))
+ (push '(type . question) result)))
+ (push (cons 'id (string-to-number (match-string-no-properties 1 link)))
+ result))
+ result))
+
(defmacro sx-assoc-let (alist &rest body)
"Identical to `let-alist', except `.site' has a special meaning.
If ALIST doesn't have a `site' property, one is created using the
@@ -319,40 +376,6 @@ removed from the display name before it is returned."
string))
-;;; Site
-(defun sx--site (data)
- "Get the site in which DATA belongs.
-DATA can be a question, answer, comment, or user (or any object
-with a `link' property).
-DATA can also be the link itself."
- (let ((link (if (stringp data) data
- (cdr (assoc 'link data)))))
- (when (stringp link)
- (replace-regexp-in-string
- "^https?://\\(?:\\(?1:[^/]+\\)\\.stackexchange\\|\\(?2:[^/]+\\)\\)\\.[^.]+/.*$"
- "\\1\\2" link))))
-
-(defun sx--ensure-site (data)
- "Add a `site' property to DATA if it doesn't have one. Return DATA.
-DATA can be a question, answer, comment, or user (or any object
-with a `link' property)."
- (when data
- (unless (assq 'site data)
- (setcdr data (cons (cons 'site (sx--site data))
- (cdr data))))
- data))
-
-(defmacro sx-assoc-let (alist &rest body)
- "Identical to `let-alist', except `.site' has a special meaning.
-If ALIST doesn't have a `site' property, one is created using the
-`link' property."
- (declare (indent 1) (debug t))
- (require 'let-alist)
- `(progn
- (sx--ensure-site ,alist)
- ,(macroexpand
- `(let-alist ,alist ,@body))))
-
(defcustom sx-init-hook nil
"Hook run when SX initializes.
Run after `sx-init--internal-hook'."