diff options
author | Sean Allred <code@seanallred.com> | 2014-12-26 17:55:48 -0500 |
---|---|---|
committer | Sean Allred <code@seanallred.com> | 2014-12-26 17:55:48 -0500 |
commit | b006537bb48201cf7996958ef0cc3b918268ff33 (patch) | |
tree | 1b3663727ecbfb1559f3140593ff4b189d4e4551 | |
parent | 7d2cccd82cf6c658e330767d0e20e48e42ff1ac6 (diff) | |
parent | a919c72f2b58d889bf3fbdde100f9912a90c64ab (diff) |
Merge branch 'master' into issue-151--dot-variables
Conflicts:
sx.el
-rw-r--r-- | README.org | 8 | ||||
-rw-r--r-- | list-and-question.png | bin | 0 -> 450796 bytes | |||
-rw-r--r-- | sx-babel.el | 2 | ||||
-rw-r--r-- | sx-question-list.el | 2 | ||||
-rw-r--r-- | sx-question-mode.el | 6 | ||||
-rw-r--r-- | sx-question-print.el | 95 | ||||
-rw-r--r-- | sx-request.el | 80 | ||||
-rw-r--r-- | sx.el | 34 |
8 files changed, 146 insertions, 81 deletions
@@ -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 Binary files differnew file mode 100644 index 0000000..9e89fec --- /dev/null +++ b/list-and-question.png 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. @@ -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 |