aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSean Allred <code@seanallred.com>2014-12-26 17:55:48 -0500
committerSean Allred <code@seanallred.com>2014-12-26 17:55:48 -0500
commitb006537bb48201cf7996958ef0cc3b918268ff33 (patch)
tree1b3663727ecbfb1559f3140593ff4b189d4e4551
parent7d2cccd82cf6c658e330767d0e20e48e42ff1ac6 (diff)
parenta919c72f2b58d889bf3fbdde100f9912a90c64ab (diff)
Merge branch 'master' into issue-151--dot-variables
Conflicts: sx.el
-rw-r--r--README.org8
-rw-r--r--list-and-question.pngbin0 -> 450796 bytes
-rw-r--r--sx-babel.el2
-rw-r--r--sx-question-list.el2
-rw-r--r--sx-question-mode.el6
-rw-r--r--sx-question-print.el95
-rw-r--r--sx-request.el80
-rw-r--r--sx.el34
8 files changed, 146 insertions, 81 deletions
diff --git a/README.org b/README.org
index db47904..b9888a7 100644
--- a/README.org
+++ b/README.org
@@ -4,9 +4,11 @@
[[https://gitter.im/vermiculus/sx.el?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge][https://badges.gitter.im/Join Chat.svg]]
[[https://www.waffle.io/vermiculus/sx.el][https://badge.waffle.io/vermiculus/sx.el.svg]]
-SX will be a full featured Stack Exchange mode for GNU Emacs 24+. Using the
-official API, we aim to create a more versatile experience for the Stack
-Exchange network within Emacs itself.
+SX is a full featured Stack Exchange mode for GNU Emacs 24+. Using the official
+API, it provides a versatile experience for the Stack Exchange network within
+Emacs itself.
+
+[[file:list-and-question.png]]
* Features
** Viewing Questions
diff --git a/list-and-question.png b/list-and-question.png
new file mode 100644
index 0000000..9e89fec
--- /dev/null
+++ b/list-and-question.png
Binary files differ
diff --git a/sx-babel.el b/sx-babel.el
index 24e56c2..b30a044 100644
--- a/sx-babel.el
+++ b/sx-babel.el
@@ -57,7 +57,7 @@ on a match.")
(setq indent (sx-babel--unindent-buffer))
(goto-char (point-min))
(setq mode (sx-babel--determine-major-mode))
- (setq copy (string-trim-right (buffer-string)))
+ (setq copy (replace-regexp-in-string "[[:space:]]+\\'" "" (buffer-string)))
(when mode
(delay-mode-hooks (funcall mode)))
(font-lock-fontify-region (point-min) (point-max))
diff --git a/sx-question-list.el b/sx-question-list.el
index 94b5be4..6537d2b 100644
--- a/sx-question-list.el
+++ b/sx-question-list.el
@@ -315,7 +315,7 @@ into consideration.
("K" sx-question-list-previous-far)
("g" sx-question-list-refresh)
(":" sx-question-list-switch-site)
- ("t" sx-question-list-switch-tab)
+ ("t" sx-tab-switch)
("a" sx-ask)
("v" sx-visit)
("u" sx-toggle-upvote)
diff --git a/sx-question-mode.el b/sx-question-mode.el
index 8fe6dfb..8d06078 100644
--- a/sx-question-mode.el
+++ b/sx-question-mode.el
@@ -30,11 +30,13 @@
;;; Displaying a question
-(defcustom sx-question-mode-display-buffer-function #'switch-to-buffer
+(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]."
+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)
diff --git a/sx-question-print.el b/sx-question-print.el
index f11449b..07378e8 100644
--- a/sx-question-print.el
+++ b/sx-question-print.el
@@ -182,9 +182,6 @@ QUESTION must be a data structure returned by `json-read'."
(mapc #'sx-question-mode--print-section .answers))
(insert "\n\n ")
(insert-text-button "Write an Answer" :type 'sx-button-answer)
- ;; Display weird chars correctly
- (set-buffer-multibyte nil)
- (set-buffer-multibyte t)
;; Go up
(goto-char (point-min))
(sx-question-mode-next-section))
@@ -289,11 +286,11 @@ The comment is indented, filled, and then printed according to
(sx--wrap-in-overlay
(list 'sx--data-here comment-data)
(sx-assoc-let comment-data
- (when (> .score 0)
+ (when (and (numberp .score) (> .score 0))
(insert (number-to-string .score)
(if (eq .upvoted t) "^" "")
" "))
- (insert
+ (insert
(format
sx-question-mode-comments-format
(sx-question-mode--propertize-display-name .owner)
@@ -371,21 +368,8 @@ E.g.:
(fill-region beg (point)))))
(replace-regexp-in-string "[[:blank:]]+\\'" "" (buffer-string))))
-(defun sx-question-mode--dont-fill-here ()
- "If text shouldn't be filled here, return t and skip over it."
- (or (sx-question-mode--skip-and-fontify-pre)
- ;; Skip headers and references
- (let ((pos (point)))
- (skip-chars-forward "\r\n[:blank:]")
- (goto-char (line-beginning-position))
- (if (or (looking-at-p (format sx-question-mode--reference-regexp ".+"))
- (looking-at-p "^#"))
- ;; Returns non-nil
- (forward-paragraph)
- ;; Go back and return nil
- (goto-char pos)
- nil))))
-
+
+;;; Handling links
(defun sx-question-mode--process-links-in-buffer ()
"Turn all markdown links in this buffer into compact format."
(save-excursion
@@ -397,10 +381,11 @@ E.g.:
(match-string-no-properties 3)
text)))
(full-text (match-string-no-properties 0)))
- (replace-match "")
- (sx-question-mode--insert-link
- (if sx-question-mode-pretty-links text full-text)
- url)))))
+ (when (stringp url)
+ (replace-match "")
+ (sx-question-mode--insert-link
+ (if sx-question-mode-pretty-links text full-text)
+ url))))))
(defun sx-question-mode--insert-link (text url)
"Return a link propertized version of string TEXT.
@@ -429,25 +414,57 @@ If ID is nil, use FALLBACK-ID instead."
nil t)
(match-string-no-properties 1)))))
+
+;;; Things we don't fill
+(defun sx-question-mode--dont-fill-here ()
+ "If text shouldn't be filled here, return t and skip over it."
+ (catch 'sx-question-mode-done
+ (let ((before (point)))
+ (skip-chars-forward "\r\n[:blank:]")
+ (let ((first-non-blank (point)))
+ (dolist (it '(sx-question-mode--skip-and-fontify-pre
+ sx-question-mode--skip-headline
+ sx-question-mode--skip-references
+ sx-question-mode--skip-comments))
+ ;; If something worked, keep point where it is and return t.
+ (if (funcall it) (throw 'sx-question-mode-done t)
+ ;; Before calling each new function. Go back to the first
+ ;; non-blank char.
+ (goto-char first-non-blank)))
+ ;; If nothing matched, go back to the very beginning.
+ (goto-char before)
+ ;; And return nil
+ nil))))
+
(defun sx-question-mode--skip-and-fontify-pre ()
"If there's a pre block ahead, handle it, skip it and return t.
Handling means to turn it into a button and remove erroneous
font-locking."
- (let ((before (point))
- beg end)
- (if (markdown-match-pre-blocks
- (save-excursion
- (skip-chars-forward "\r\n[:blank:]")
- (setq beg (point))))
- (progn
- (setq end (point))
- (sx-babel--make-pre-button
- (save-excursion
- (goto-char beg)
- (line-beginning-position))
- end))
- (goto-char before)
- nil)))
+ (let ((beg (line-beginning-position)))
+ ;; To identify code-blocks we need to be at start of line.
+ (goto-char beg)
+ (when (markdown-match-pre-blocks (line-end-position))
+ (sx-babel--make-pre-button beg (point))
+ t)))
+
+(defun sx-question-mode--skip-comments ()
+ "If there's an html comment ahead, skip it and return t."
+ ;; @TODO: Handle the comment.
+ ;; "Handling means to store any relevant metadata it might be holding."
+ (markdown-match-comments (line-end-position)))
+
+(defun sx-question-mode--skip-headline ()
+ "If there's a headline ahead, skip it and return non-nil."
+ (when (or (looking-at-p "^#+ ")
+ (progn (forward-line 1) (looking-at-p "===\\|---")))
+ ;; Returns non-nil.
+ (forward-line 1)))
+
+(defun sx-question-mode--skip-references ()
+ "If there's a reference ahead, skip it and return non-nil."
+ (while (looking-at-p (format sx-question-mode--reference-regexp ".+"))
+ ;; Returns non-nil
+ (forward-line 1)))
(provide 'sx-question-print)
;;; sx-question-print.el ends here
diff --git a/sx-request.el b/sx-request.el
index 2d894f0..1031ea7 100644
--- a/sx-request.el
+++ b/sx-request.el
@@ -70,7 +70,11 @@
(defcustom sx-request-unzip-program
"gunzip"
"Program used to unzip the response if it is compressed.
-This program must accept compressed data on standard input."
+This program must accept compressed data on standard input.
+
+This is only used (and necessary) if the function
+`zlib-decompress-region' is not defined, which is the case for
+Emacs versions < 24.4."
:group 'sx
:type 'string)
@@ -121,40 +125,46 @@ the main content of the response is returned."
(url-request-extra-headers
'(("Content-Type" . "application/x-www-form-urlencoded")))
(response-buffer (url-retrieve-synchronously request-url)))
- (if (not response-buffer)
- (error "Something went wrong in `url-retrieve-synchronously'")
- (with-current-buffer response-buffer
- (let* ((data (progn
- ;; @TODO use url-http-end-of-headers
- (goto-char (point-min))
- (if (not (search-forward "\n\n" nil t))
- (error "Headers missing; response corrupt")
- (delete-region (point-min) (point))
- (buffer-string))))
- (response-zipped-p (sx-encoding-gzipped-p data))
- (data (if (not response-zipped-p) data
- (shell-command-on-region
- (point-min) (point-max)
- sx-request-unzip-program
- nil t)
- (buffer-string)))
- ;; @TODO should use `condition-case' here -- set
- ;; RESPONSE to 'corrupt or something
- (response (with-demoted-errors "`json' error: %S"
- (json-read-from-string data))))
- (when (and (not response) (string-equal data "{}"))
- (sx-message "Unable to parse response: %S" response)
- (error "Response could not be read by `json-read-from-string'"))
- ;; If we get here, the response is a valid data structure
- (sx-assoc-let response
- (when .error_id
- (error "Request failed: (%s) [%i %s] %S"
- .method .error_id .error_name .error_message))
- (when (< (setq sx-request-remaining-api-requests .quota_remaining)
- sx-request-remaining-api-requests-message-threshold)
- (sx-message "%d API requests reamining"
- sx-request-remaining-api-requests))
- (sx-encoding-clean-content-deep .items)))))))
+ (if (not response-buffer)
+ (error "Something went wrong in `url-retrieve-synchronously'")
+ (with-current-buffer response-buffer
+ (let* ((data (progn
+ ;; @TODO use url-http-end-of-headers
+ (goto-char (point-min))
+ (if (not (search-forward "\n\n" nil t))
+ (error "Headers missing; response corrupt")
+ (delete-region (point-min) (point))
+ (buffer-string))))
+ (response-zipped-p (sx-encoding-gzipped-p data))
+ (data
+ ;; Turn string of bytes into string of characters. See
+ ;; http://emacs.stackexchange.com/q/4100/50
+ (decode-coding-string
+ (if (not response-zipped-p) data
+ (if (fboundp 'zlib-decompress-region)
+ (zlib-decompress-region (point-min) (point-max))
+ (shell-command-on-region
+ (point-min) (point-max)
+ sx-request-unzip-program nil t))
+ (buffer-string))
+ 'utf-8 'nocopy))
+ ;; @TODO should use `condition-case' here -- set
+ ;; RESPONSE to 'corrupt or something
+ (response (with-demoted-errors "`json' error: %S"
+ (json-read-from-string data))))
+ (when (and (not response) (string-equal data "{}"))
+ (sx-message "Unable to parse response: %S" response)
+ (error "Response could not be read by `json-read-from-string'"))
+ ;; If we get here, the response is a valid data structure
+ (sx-assoc-let response
+ (when .error_id
+ (error "Request failed: (%s) [%i %s] %S"
+ .method .error_id .error_name .error_message))
+ (when (< (setq sx-request-remaining-api-requests .quota_remaining)
+ sx-request-remaining-api-requests-message-threshold)
+ (sx-message "%d API requests reamining"
+ sx-request-remaining-api-requests))
+ (sx-encoding-clean-content-deep .items)))))))
(defun sx-request-fallback (_method &optional _args _request-method)
"Fallback method when authentication is not available.
diff --git a/sx.el b/sx.el
index 97a6d61..d93719e 100644
--- a/sx.el
+++ b/sx.el
@@ -51,6 +51,40 @@
(browse-url "https://github.com/vermiculus/sx.el/issues/new"))
+;;; 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))
+ `(progn
+ (require 'let-alist)
+ (sx--ensure-site ,alist)
+ (let-alist ,alist ,@body)))
+
+
;;; Browsing filter
(defvar sx-browse-filter
'((question.body_markdown