From 2d4b14482c92997b3c97731795e1eb6f054fecf6 Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Thu, 15 Jan 2015 19:13:33 -0200 Subject: Hitting RET on link buttons calls sx-open-link --- sx-button.el | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sx-button.el b/sx-button.el index 5a2f052..e00b086 100644 --- a/sx-button.el +++ b/sx-button.el @@ -98,9 +98,9 @@ usually part of a code-block." (defun sx-button-follow-link (&optional pos) "Follow link at POS. If POS is nil, use `point'." (interactive) - (browse-url - (or (get-text-property (or pos (point)) 'sx-button-url) - (sx-user-error "No url under point: %s" (or pos (point)))))) + (let ((url (or (get-text-property (or pos (point)) 'sx-button-url) + (sx-user-error "No url under point: %s" (or pos (point)))))) + (sx-open-link url))) ;;; Help-echo definitions -- cgit v1.2.3 From 2ee72cc4361031f3ab34e312fca191125ce18bfc Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Thu, 15 Jan 2015 20:04:24 -0200 Subject: Move tag-format to sx-tag.el --- sx-question-list.el | 2 +- sx-question-print.el | 2 +- sx-question.el | 4 ---- sx-tag.el | 6 ++++++ 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/sx-question-list.el b/sx-question-list.el index 884f994..306d39f 100644 --- a/sx-question-list.el +++ b/sx-question-list.el @@ -170,7 +170,7 @@ Also see `sx-question-list-refresh'." " " ;; @TODO: Make this width customizable. (Or maybe just make ;; the whole thing customizable) - (propertize (format "%-40s" (mapconcat #'sx-question--tag-format .tags " ")) + (propertize (format "%-40s" (mapconcat #'sx-tag--format .tags " ")) 'face 'sx-question-list-tags) " " (sx-user--format "%15d %4r" .owner) diff --git a/sx-question-print.el b/sx-question-print.el index 9a51efb..b258fde 100644 --- a/sx-question-print.el +++ b/sx-question-print.el @@ -223,7 +223,7 @@ DATA can represent a question or an answer." ;; Tags (sx-question-mode--insert-header sx-question-mode-header-tags - (mapconcat #'sx-question--tag-format .tags " ") + (mapconcat #'sx-tag--format .tags " ") 'sx-question-mode-tags)) ;; Body (insert "\n" diff --git a/sx-question.el b/sx-question.el index 1adbc24..0f73795 100644 --- a/sx-question.el +++ b/sx-question.el @@ -183,10 +183,6 @@ If no cache exists for it, initialize one with SITE." (and (integerp .accepted_answer_id) .accepted_answer_id))) -(defun sx-question--tag-format (tag) - "Formats TAG for display." - (concat "[" tag "]")) - (provide 'sx-question) ;;; sx-question.el ends here diff --git a/sx-tag.el b/sx-tag.el index b2ad375..d69f330 100644 --- a/sx-tag.el +++ b/sx-tag.el @@ -133,6 +133,12 @@ tags." (push input list)) (reverse list))) + +;;; Printing +(defun sx-tag--format (tag) + "Formats TAG for display." + (concat "[" tag "]")) + (provide 'sx-tag) ;;; sx-tag.el ends here -- cgit v1.2.3 From c08dc31400711fbebea2c7ca650bd72dc3f92392 Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Thu, 15 Jan 2015 21:56:45 -0200 Subject: Implement sx-search-tag-at-point command --- sx-search.el | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/sx-search.el b/sx-search.el index aefd12e..c22a554 100644 --- a/sx-search.el +++ b/sx-search.el @@ -32,6 +32,7 @@ (require 'sx) (require 'sx-question-list) +(require 'sx-question-mode) (require 'sx-tag) (defvar sx-search--query-history nil @@ -103,6 +104,19 @@ prefix argument, the user is asked for everything." (sx-question-list-refresh 'redisplay) (switch-to-buffer (current-buffer)))) + +;;; Tag +(defun sx-search-tag-at-point (&optional pos) + "Follow tag under position POS or point." + (interactive) + (let ((tag (save-excursion + (when pos (goto-char pos)) + (or (get-text-property (point) 'sx-tag) + (thing-at-point 'symbol))))) + (sx-search (or sx-question-list--site + (sx-assoc-let sx-question-mode--data .site_par)) + nil tag))) + (provide 'sx-search) ;;; sx-search.el ends here -- cgit v1.2.3 From 2fd860b5a17fe3f8b7bc3759a79bb7a2ae343bf2 Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Thu, 15 Jan 2015 21:57:03 -0200 Subject: Define button-tag --- sx-button.el | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/sx-button.el b/sx-button.el index 5a2f052..69e8a7e 100644 --- a/sx-button.el +++ b/sx-button.el @@ -158,6 +158,13 @@ usually part of a code-block." 'face 'sx-user-name :supertype 'sx-button) +(declare-function sx-search-tag-at-point "sx-tag") +(define-button-type 'sx-button-tag + 'action #'sx-search-tag-at-point + 'help-echo sx-button--tag-help-echo + 'face 'sx-tag + :supertype 'sx-button) + (define-button-type 'sx-button-comment 'help-echo (concat "mouse-1, RET" (propertize ": write a comment" -- cgit v1.2.3 From feb8082bac11154d4f6cc768add49bd9b7c0c107 Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Thu, 15 Jan 2015 22:02:41 -0200 Subject: Turn tags into buttons --- sx-tag.el | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/sx-tag.el b/sx-tag.el index d69f330..ba32544 100644 --- a/sx-tag.el +++ b/sx-tag.el @@ -137,7 +137,12 @@ tags." ;;; Printing (defun sx-tag--format (tag) "Formats TAG for display." - (concat "[" tag "]")) + (with-temp-buffer + (insert-text-button (concat "[" tag "]") + 'sx-button-copy tag + 'sx-tag tag + :type 'sx-button-tag) + (buffer-string))) (provide 'sx-tag) ;;; sx-tag.el ends here -- cgit v1.2.3 From 1dfa97db27a72f0121001b1d85c13d1cdee37f31 Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Thu, 15 Jan 2015 22:07:09 -0200 Subject: sx-question-mode--insert-header accepts a nil face as third arg --- sx-question-print.el | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/sx-question-print.el b/sx-question-print.el index b258fde..0ae0f04 100644 --- a/sx-question-print.el +++ b/sx-question-print.el @@ -299,7 +299,10 @@ where `value' is given `face' as its face. (while args (insert (propertize (pop args) 'face 'sx-question-mode-header) - (propertize (pop args) 'face (pop args))))) + (let ((header (pop args)) + (face (pop args))) + (if face (propertize header 'face face) + header))))) ;;;; Printing and Font-locking the content (body) -- cgit v1.2.3 From 74fce2c36f803abe232eebe9f542696b8d0fac23 Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Thu, 15 Jan 2015 22:07:48 -0200 Subject: Always use sx-tag face for tags --- sx-question-list.el | 8 +------- sx-question-print.el | 7 +------ sx-tag.el | 6 ++++++ 3 files changed, 8 insertions(+), 13 deletions(-) diff --git a/sx-question-list.el b/sx-question-list.el index 306d39f..256bdb4 100644 --- a/sx-question-list.el +++ b/sx-question-list.el @@ -81,11 +81,6 @@ "" :group 'sx-question-list-faces) -(defface sx-question-list-tags - '((t :inherit sx-question-mode-tags)) - "" - :group 'sx-question-list-faces) - (defface sx-question-list-date '((t :inherit font-lock-comment-face)) "" @@ -170,8 +165,7 @@ Also see `sx-question-list-refresh'." " " ;; @TODO: Make this width customizable. (Or maybe just make ;; the whole thing customizable) - (propertize (format "%-40s" (mapconcat #'sx-tag--format .tags " ")) - 'face 'sx-question-list-tags) + (format "%-40s" (mapconcat #'sx-tag--format .tags " ")) " " (sx-user--format "%15d %4r" .owner) (propertize " " 'display "\n"))))))) diff --git a/sx-question-print.el b/sx-question-print.el index 0ae0f04..6791222 100644 --- a/sx-question-print.el +++ b/sx-question-print.el @@ -79,11 +79,6 @@ Some faces of this mode might be defined in the `sx-user' group." :type 'string :group 'sx-question-mode) -(defface sx-question-mode-tags - '((t :underline nil :inherit font-lock-function-name-face)) - "Face used on the question tags in the question buffer." - :group 'sx-question-mode-faces) - (defface sx-question-mode-score '((t)) "Face used for the score in the question buffer." @@ -224,7 +219,7 @@ DATA can represent a question or an answer." (sx-question-mode--insert-header sx-question-mode-header-tags (mapconcat #'sx-tag--format .tags " ") - 'sx-question-mode-tags)) + nil)) ;; Body (insert "\n" (propertize sx-question-mode-separator diff --git a/sx-tag.el b/sx-tag.el index ba32544..d9e2877 100644 --- a/sx-tag.el +++ b/sx-tag.el @@ -24,6 +24,12 @@ (require 'sx) (require 'sx-method) +(defface sx-tag + '((t :underline nil :inherit font-lock-function-name-face)) + "Face used on the question tags in the question buffer." + :group 'sx-question-mode-faces + :group 'sx-question-list-faces) + ;;; Getting the list from a site (defconst sx-tag-filter -- cgit v1.2.3 From faddde5ff2c4f41c4844ee4ae88819dcfab5d8c8 Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Thu, 15 Jan 2015 22:17:20 -0200 Subject: Define sx-button--tag-help-echo --- sx-button.el | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/sx-button.el b/sx-button.el index 69e8a7e..1d4eb4f 100644 --- a/sx-button.el +++ b/sx-button.el @@ -117,6 +117,12 @@ usually part of a code-block." "link") "Help echoed in the minibuffer when point is on a user.") +(defconst sx-button--tag-help-echo + (format sx-button--help-echo + "Tag search" + "tag") + "Help echoed in the minibuffer when point is on a tag.") + (defconst sx-button--question-title-help-echo (format sx-button--help-echo "hide content" -- cgit v1.2.3 From 33746908e639e53320fe1412ae01b01854a98605 Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Thu, 15 Jan 2015 22:31:08 -0200 Subject: Define sx-tag--insert --- sx-tag.el | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/sx-tag.el b/sx-tag.el index d9e2877..009c8b1 100644 --- a/sx-tag.el +++ b/sx-tag.el @@ -142,14 +142,18 @@ tags." ;;; Printing (defun sx-tag--format (tag) - "Formats TAG for display." + "Format and return TAG for display." (with-temp-buffer - (insert-text-button (concat "[" tag "]") - 'sx-button-copy tag - 'sx-tag tag - :type 'sx-button-tag) + (sx-tag--insert tag) (buffer-string))) +(defun sx-tag--insert (tag) + "Insert TAG button." + (insert-text-button (concat "[" tag "]") + 'sx-button-copy tag + 'sx-tag tag + :type 'sx-button-tag)) + (provide 'sx-tag) ;;; sx-tag.el ends here -- cgit v1.2.3 From b4c850e313e51f27196de473e1bcfc546c0abb6a Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Thu, 15 Jan 2015 22:31:53 -0200 Subject: Turn tags inside body into buttons. Fix #229 --- sx-question-print.el | 33 ++++++++++++++++++++------------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/sx-question-print.el b/sx-question-print.el index 6791222..d7c2a20 100644 --- a/sx-question-print.el +++ b/sx-question-print.el @@ -315,7 +315,8 @@ E.g.: (defconst sx-question-mode--link-regexp ;; Done at compile time. - (rx (or (and "[" (group-n 1 (1+ (not (any "]")))) "]" + (rx (or (and "[tag:" (group-n 5 (+ (not (any " ]")))) "]") + (and "[" (group-n 1 (1+ (not (any "]")))) "]" (or (and "(" (group-n 2 (1+ (not (any ")")))) ")") (and "[" (group-n 3 (1+ (not (any "]")))) "]"))) (group-n 4 (and (and "http" (opt "s") "://") "" @@ -363,18 +364,24 @@ E.g.: (save-excursion (goto-char (point-min)) (while (search-forward-regexp sx-question-mode--link-regexp nil t) - (let* ((text (match-string-no-properties 1)) - (url (or (match-string-no-properties 2) - (match-string-no-properties 4) - (sx-question-mode-find-reference - (match-string-no-properties 3) - text))) - (full-text (match-string-no-properties 0))) - (when (stringp url) - (replace-match "") - (sx-question-mode--insert-link - (or (if sx-question-mode-pretty-links text full-text) url) - url)))))) + ;; Tags are tag-buttons. + (let ((tag (match-string-no-properties 5))) + (if (and tag (> (length tag) 0)) + (progn (replace-match "") + (sx-tag--insert tag)) + ;; Other links are link-buttons. + (let* ((text (match-string-no-properties 1)) + (url (or (match-string-no-properties 2) + (match-string-no-properties 4) + (sx-question-mode-find-reference + (match-string-no-properties 3) + text))) + (full-text (match-string-no-properties 0))) + (when (stringp url) + (replace-match "") + (sx-question-mode--insert-link + (or (if sx-question-mode-pretty-links text full-text) url) + url)))))))) (defun sx-question-mode--insert-link (text url) "Return a link propertized version of string TEXT. -- cgit v1.2.3 From 058cca312d48ad44dc8b633be74e5731446d158e Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Thu, 15 Jan 2015 22:46:32 -0200 Subject: require 'sx-button --- sx-tag.el | 1 + 1 file changed, 1 insertion(+) diff --git a/sx-tag.el b/sx-tag.el index 009c8b1..316226b 100644 --- a/sx-tag.el +++ b/sx-tag.el @@ -23,6 +23,7 @@ (require 'sx) (require 'sx-method) +(require 'sx-button) (defface sx-tag '((t :underline nil :inherit font-lock-function-name-face)) -- cgit v1.2.3 From bed5191a0137cb115656baf6ae3535b2a646d0b2 Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Fri, 16 Jan 2015 12:56:02 -0200 Subject: Fix and improve tests --- test/test-printing.el | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/test/test-printing.el b/test/test-printing.el index 7384829..52fe5be 100644 --- a/test/test-printing.el +++ b/test/test-printing.el @@ -28,11 +28,18 @@ after being run through `sx-question--tag-format'." ;;; Tests (ert-deftest question-list-tag () - "Test `sx-question--tag-format'." + "Test `sx-tag--format'." (should - (string= - (sx-question--tag-format "tag") - "[tag]"))) + (string= (sx-tag--format "tag") "[tag]")) + (with-temp-buffer + (insert (sx-tag--format "tag")) + (should (get-char-property (point-min) 'button)) + (should + (eq (get-char-property (point-min) 'face) 'sx-tag)) + (should + (string= (get-char-property (point-min) 'sx-tag) "tag")) + (should + (string= (get-char-property (point-min) 'sx-button-copy) "tag")))) (ert-deftest question-list-display () (cl-letf (((symbol-function #'sx-request-make) -- cgit v1.2.3 From 9bd67acab3c4c81cf4bad14b2da8be835ae086e0 Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Fri, 23 Jan 2015 23:29:54 -0200 Subject: Refactor part of request-get-data into request-get-url --- sx-request.el | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/sx-request.el b/sx-request.el index 2650c55..77ae1d7 100644 --- a/sx-request.el +++ b/sx-request.el @@ -221,18 +221,14 @@ Currently returns nil." "https://raw.githubusercontent.com/vermiculus/sx.el/data/data/%s.el" "Url of the \"data\" directory inside the SX `data' branch.") -(defun sx-request-get-data (file) - "Fetch and return data stored online by SX. -FILE is a string or symbol, the name of the file which holds the -desired data, relative to `sx-request--data-url-format'. For -instance, `tags/emacs' returns the list of tags on Emacs.SE." +(defun sx-request-get-url (url) + "Fetch and return data stored online at URL." (let* ((url-automatic-caching t) (url-inhibit-uncompression t) - (request-url (format sx-request--data-url-format file)) (url-request-method "GET") (url-request-extra-headers '(("Content-Type" . "application/x-www-form-urlencoded"))) - (response-buffer (url-retrieve-synchronously request-url))) + (response-buffer (url-retrieve-synchronously url))) (if (not response-buffer) (error "Something went wrong in `url-retrieve-synchronously'") (with-current-buffer response-buffer @@ -241,9 +237,17 @@ instance, `tags/emacs' returns the list of tags on Emacs.SE." (if (not (search-forward "\n\n" nil t)) (error "Headers missing; response corrupt") (when (looking-at-p "Not Found") (error "Page not found.")) - (prog1 (read (current-buffer)) + (prog1 (buffer-substring (point) (point-max)) (kill-buffer (current-buffer))))))))) +(defun sx-request-get-data (file) + "Fetch and return data stored online by SX. +FILE is a string or symbol, the name of the file which holds the +desired data, relative to `sx-request--data-url-format'. For +instance, `tags/emacs' returns the list of tags on Emacs.SE." + (read (sx-request-get-url + (format sx-request--data-url-format file)))) + ;;; Support Functions (defun sx-request--build-keyword-arguments (alist &optional kv-sep) -- cgit v1.2.3 From 4bc72b0f622b2565e7b0d0263bb3053ca5252f63 Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Fri, 23 Jan 2015 23:38:06 -0200 Subject: sx-request-get-url error unless code 200 --- sx-request.el | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sx-request.el b/sx-request.el index 77ae1d7..7f18a2b 100644 --- a/sx-request.el +++ b/sx-request.el @@ -234,9 +234,10 @@ Currently returns nil." (with-current-buffer response-buffer (progn (goto-char (point-min)) + (unless (string-match "200" (thing-at-point 'line)) + (error "Page not found.")) (if (not (search-forward "\n\n" nil t)) (error "Headers missing; response corrupt") - (when (looking-at-p "Not Found") (error "Page not found.")) (prog1 (buffer-substring (point) (point-max)) (kill-buffer (current-buffer))))))))) -- cgit v1.2.3 From 76bfce36a4808e71aec27fdc2f7f8cef8462500f Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Fri, 23 Jan 2015 23:41:13 -0200 Subject: Add tests for sx-request-get-... --- test/test-api.el | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/test/test-api.el b/test/test-api.el index b7d5dbb..30590d7 100644 --- a/test/test-api.el +++ b/test/test-api.el @@ -14,3 +14,11 @@ (ert-deftest test-method-get-all () "Tests sx-method interface to `sx-request-all-items'" (should (< 250 (length (sx-method-call 'sites :get-all t))))) + +(ert-deftest request-get () + (should (sx-request-get-url "http://google.com")) + (should-error (sx-request-get-url "http://github.com/Bruce-Connor/does-not-exist")) + (should-error (sx-request-get-data "tags/emacs-does-not-exist")) + (let ((emacs-tags (length (sx-request-get-data 'tags/emacs)))) + (should (> emacs-tags 450)) + (should (not (cl-remove-if #'stringp emacs-tags))))) -- cgit v1.2.3 From 684d093b24c0c1bb4627176109611980afad6617 Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Sat, 24 Jan 2015 00:49:15 -0200 Subject: sx-question-mode--insert-link takes images too --- sx-question-print.el | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/sx-question-print.el b/sx-question-print.el index b53b86a..7d1d69e 100644 --- a/sx-question-print.el +++ b/sx-question-print.el @@ -388,20 +388,24 @@ E.g.: (or (if sx-question-mode-pretty-links text full-text) url) url)))))))) -(defun sx-question-mode--insert-link (text url) - "Return a link propertized version of string TEXT. +(defun sx-question-mode--insert-link (text-or-image url) + "Return a link propertized version of TEXT-OR-IMAGE. URL is used as 'help-echo and 'url properties." - (insert-text-button - text - ;; Mouse-over - 'help-echo - (format sx-button--link-help-echo - (propertize (sx--shorten-url url) - 'face 'font-lock-function-name-face)) - ;; For visiting and stuff. - 'sx-button-url url - 'sx-button-copy url - :type 'sx-button-link)) + (let ((imagep (not (stringp text-or-image)))) + (apply #'insert-text-button + (if imagep " " text-or-image) + ;; Mouse-over + 'help-echo + (format sx-button--link-help-echo + (propertize (sx--shorten-url url) + 'face 'font-lock-function-name-face)) + ;; For visiting and stuff. + 'sx-button-url url + 'sx-button-copy url + :type 'sx-button-link + ;; The last argument of `apply' is a list. + (when imagep + `(face default display ,text-or-image))))) (defun sx-question-mode-find-reference (id &optional fallback-id) "Find url identified by reference ID in current buffer. -- cgit v1.2.3 From f54ab739c432af99356fd57d5abb6bd63a39e667 Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Sat, 24 Jan 2015 00:49:38 -0200 Subject: Detect if link is an image, download it, and pass it to sx-question-mode--insert-link --- sx-question-print.el | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/sx-question-print.el b/sx-question-print.el index 7d1d69e..63bfaa4 100644 --- a/sx-question-print.el +++ b/sx-question-print.el @@ -157,6 +157,16 @@ replaced with the comment." (const :tag "More active first" sx-answer-more-active-p)) :group 'sx-question-mode) +(defcustom sx-question-mode-use-images t + "Non-nil if SX should download and display images." + :type 'boolean + :group 'sx-question-mode) + +(defcustom sx-question-mode-image-max-width 500 + "Maximum width, in pixels, of images in the question buffer." + :type 'integer + :group 'sx-question-mode) + ;;; Functions ;;;; Printing the general structure @@ -321,7 +331,7 @@ E.g.: (defconst sx-question-mode--link-regexp ;; Done at compile time. (rx (or (and "[tag:" (group-n 5 (+ (not (any " ]")))) "]") - (and "[" (group-n 1 (1+ (not (any "]")))) "]" + (and (opt "!") "[" (group-n 1 (1+ (not (any "]")))) "]" (or (and "(" (group-n 2 (1+ (not (any ")")))) ")") (and "[" (group-n 3 (1+ (not (any "]")))) "]"))) (group-n 4 (and (and "http" (opt "s") "://") "" @@ -365,7 +375,9 @@ E.g.: ;;; Handling links (defun sx-question-mode--process-links-in-buffer () - "Turn all markdown links in this buffer into compact format." + "Turn all markdown links in this buffer into compact format. +Image links are downloaded and displayed, if +`sx-question-mode-use-images' is non-nil." (save-excursion (goto-char (point-min)) (while (search-forward-regexp sx-question-mode--link-regexp nil t) @@ -385,7 +397,13 @@ E.g.: (when (stringp url) (replace-match "") (sx-question-mode--insert-link - (or (if sx-question-mode-pretty-links text full-text) url) + (if (and sx-question-mode-use-images (eq ?! (elt full-text 0))) + ;; Is it an image? + (create-image (sx-request-get-url url) 'imagemagick t + :width (min sx-question-mode-image-max-width + (window-body-width nil 'pixel))) + ;; Or a regular link + (or (if sx-question-mode-pretty-links text full-text) url)) url)))))))) (defun sx-question-mode--insert-link (text-or-image url) -- cgit v1.2.3 From a7a83411ccf68114847112bf27dced3d833cd524 Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Sat, 24 Jan 2015 00:52:03 -0200 Subject: Test downloading image. --- test/test-api.el | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/test/test-api.el b/test/test-api.el index 30590d7..f7f54c1 100644 --- a/test/test-api.el +++ b/test/test-api.el @@ -21,4 +21,10 @@ (should-error (sx-request-get-data "tags/emacs-does-not-exist")) (let ((emacs-tags (length (sx-request-get-data 'tags/emacs)))) (should (> emacs-tags 450)) - (should (not (cl-remove-if #'stringp emacs-tags))))) + (should (not (cl-remove-if #'stringp emacs-tags)))) + (should + ;; If image is not recognized, this returns nil. + (create-image (sx-request-get-url "https://raw.githubusercontent.com/vermiculus/sx.el/master/list-and-question.png") + 'imagemagick t + :width (min sx-question-mode-image-max-width + (window-body-width nil 'pixel))))) -- cgit v1.2.3 From d0a09664737d82985c0db040afeb89f69c5b61b3 Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Sat, 24 Jan 2015 01:07:10 -0200 Subject: sx-question-mode--reference-regexp no longer matches newlines --- sx-question-print.el | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sx-question-print.el b/sx-question-print.el index 63bfaa4..963bfb4 100644 --- a/sx-question-print.el +++ b/sx-question-print.el @@ -323,7 +323,7 @@ where `value' is given `face' as its face. (defconst sx-question-mode--reference-regexp (rx line-start (0+ blank) "[%s]:" (0+ blank) - (group-n 1 (1+ (not blank)))) + (group-n 1 (1+ (not (any blank "\n\r"))))) "Regexp used to find the url of labeled links. E.g.: [1]: https://...") -- cgit v1.2.3 From 5a4c4dba75ad605110a4415478072cdb4a907b20 Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Sat, 24 Jan 2015 01:18:42 -0200 Subject: With nested links, just do the inner one. --- sx-question-print.el | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sx-question-print.el b/sx-question-print.el index 963bfb4..e76e006 100644 --- a/sx-question-print.el +++ b/sx-question-print.el @@ -331,7 +331,7 @@ E.g.: (defconst sx-question-mode--link-regexp ;; Done at compile time. (rx (or (and "[tag:" (group-n 5 (+ (not (any " ]")))) "]") - (and (opt "!") "[" (group-n 1 (1+ (not (any "]")))) "]" + (and (opt "!") "[" (group-n 1 (1+ (not (any "[]")))) "]" (or (and "(" (group-n 2 (1+ (not (any ")")))) ")") (and "[" (group-n 3 (1+ (not (any "]")))) "]"))) (group-n 4 (and (and "http" (opt "s") "://") "" -- cgit v1.2.3 From e8becf0aa8fd823a73a16292fffc261be7c4b9f5 Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Sat, 24 Jan 2015 01:25:29 -0200 Subject: Sorround images with newlines --- sx-question-print.el | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/sx-question-print.el b/sx-question-print.el index e76e006..924608b 100644 --- a/sx-question-print.el +++ b/sx-question-print.el @@ -410,6 +410,9 @@ Image links are downloaded and displayed, if "Return a link propertized version of TEXT-OR-IMAGE. URL is used as 'help-echo and 'url properties." (let ((imagep (not (stringp text-or-image)))) + ;; Images need to be at the start of a line. + (when (and imagep (not (looking-at-p "^"))) + (insert "\n")) (apply #'insert-text-button (if imagep " " text-or-image) ;; Mouse-over @@ -423,7 +426,9 @@ URL is used as 'help-echo and 'url properties." :type 'sx-button-link ;; The last argument of `apply' is a list. (when imagep - `(face default display ,text-or-image))))) + `(face default display ,text-or-image))) + ;; Images need to be at the end of a line too. + (insert "\n"))) (defun sx-question-mode-find-reference (id &optional fallback-id) "Find url identified by reference ID in current buffer. -- cgit v1.2.3 From ba80f6594f39b09a6212f1288297ad1a6124dcbe Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Sat, 24 Jan 2015 01:25:41 -0200 Subject: Clean up nested links --- sx-question-print.el | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/sx-question-print.el b/sx-question-print.el index 924608b..0528944 100644 --- a/sx-question-print.el +++ b/sx-question-print.el @@ -409,6 +409,14 @@ Image links are downloaded and displayed, if (defun sx-question-mode--insert-link (text-or-image url) "Return a link propertized version of TEXT-OR-IMAGE. URL is used as 'help-echo and 'url properties." + ;; For now, the only way to handle nested links is to remove them. + (when (eq (char-before) ?\[) + (insert "a") + (forward-char -2) + (if (looking-at sx-question-mode--link-regexp) + (replace-match "") + (forward-char 1) + (delete-char 1))) (let ((imagep (not (stringp text-or-image)))) ;; Images need to be at the start of a line. (when (and imagep (not (looking-at-p "^"))) -- cgit v1.2.3 From 458231a11d0d54f8d7011addd9246661898cb2e0 Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Sat, 24 Jan 2015 01:58:32 -0200 Subject: Fix and reorganize tests --- test/test-api.el | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/test/test-api.el b/test/test-api.el index f7f54c1..0715a2e 100644 --- a/test/test-api.el +++ b/test/test-api.el @@ -15,16 +15,18 @@ "Tests sx-method interface to `sx-request-all-items'" (should (< 250 (length (sx-method-call 'sites :get-all t))))) -(ert-deftest request-get () +(ert-deftest request-get-url () (should (sx-request-get-url "http://google.com")) (should-error (sx-request-get-url "http://github.com/Bruce-Connor/does-not-exist")) - (should-error (sx-request-get-data "tags/emacs-does-not-exist")) - (let ((emacs-tags (length (sx-request-get-data 'tags/emacs)))) - (should (> emacs-tags 450)) - (should (not (cl-remove-if #'stringp emacs-tags)))) (should ;; If image is not recognized, this returns nil. (create-image (sx-request-get-url "https://raw.githubusercontent.com/vermiculus/sx.el/master/list-and-question.png") 'imagemagick t :width (min sx-question-mode-image-max-width (window-body-width nil 'pixel))))) + +(ert-deftest request-get-data () + (should-error (sx-request-get-data "tags/emacs-does-not-exist")) + (let ((emacs-tags (sx-request-get-data 'tags/emacs))) + (should (> (length emacs-tags) 450)) + (should (not (cl-remove-if #'stringp emacs-tags))))) -- cgit v1.2.3 From 62592e73af739b898fa401fd3c3503f43e8bb267 Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Sat, 24 Jan 2015 02:51:50 -0200 Subject: Larger max width --- sx-question-print.el | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sx-question-print.el b/sx-question-print.el index 0528944..fe34d9c 100644 --- a/sx-question-print.el +++ b/sx-question-print.el @@ -162,7 +162,7 @@ replaced with the comment." :type 'boolean :group 'sx-question-mode) -(defcustom sx-question-mode-image-max-width 500 +(defcustom sx-question-mode-image-max-width 550 "Maximum width, in pixels, of images in the question buffer." :type 'integer :group 'sx-question-mode) -- cgit v1.2.3 From d53dc5e22d6ebd7905c194b9dcd006e763a1aa3d Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Sat, 24 Jan 2015 02:52:09 -0200 Subject: Only shrink images, don't enlarge them. --- sx-question-print.el | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/sx-question-print.el b/sx-question-print.el index fe34d9c..3a2eedf 100644 --- a/sx-question-print.el +++ b/sx-question-print.el @@ -399,13 +399,23 @@ Image links are downloaded and displayed, if (sx-question-mode--insert-link (if (and sx-question-mode-use-images (eq ?! (elt full-text 0))) ;; Is it an image? - (create-image (sx-request-get-url url) 'imagemagick t - :width (min sx-question-mode-image-max-width - (window-body-width nil 'pixel))) + (sx-question-mode--create-image url) ;; Or a regular link (or (if sx-question-mode-pretty-links text full-text) url)) url)))))))) +(defun sx-question-mode--create-image (url) + "Get and create an image from URL. +Its size is bound by `sx-question-mode-image-max-width' and +`window-body-width'." + (let* ((image + (create-image (sx-request-get-url url) 'imagemagick t)) + (image-width (car (image-size image 'pixels)))) + (append image + (list :width (min sx-question-mode-image-max-width + (window-body-width nil 'pixel) + image-width))))) + (defun sx-question-mode--insert-link (text-or-image url) "Return a link propertized version of TEXT-OR-IMAGE. URL is used as 'help-echo and 'url properties." -- cgit v1.2.3 From f6b00de6b91b2f52845d33a7ccf195409ad5c9f4 Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Sat, 24 Jan 2015 03:06:09 -0200 Subject: Do link handling per paragraph, so we don't linkify code blocks --- sx-question-print.el | 67 +++++++++++++++++++++++++++------------------------- 1 file changed, 35 insertions(+), 32 deletions(-) diff --git a/sx-question-print.el b/sx-question-print.el index 3a2eedf..c10c43e 100644 --- a/sx-question-print.el +++ b/sx-question-print.el @@ -357,52 +357,55 @@ E.g.: (group-n 1 (and "@" (1+ (not space)))) symbol-end) 1 font-lock-builtin-face))) - ;; Everything. + ;; Fontify. (font-lock-fontify-region (point-min) (point-max)) - ;; Compact links. - (sx-question-mode--process-links-in-buffer) - ;; And now the filling + ;; And now the content handling: (goto-char (point-min)) + ;; Handle one paragraph at a time. (while (null (eobp)) - ;; Don't fill pre blocks. + ;; Some things are not paragraphs, and shouldn't be filled. (unless (sx-question-mode--dont-fill-here) (let ((beg (point))) (skip-chars-forward "\r\n[:blank:]") (forward-paragraph) + (let ((end (point-marker))) + ;; Compact links. + (sx-question-mode--process-links beg end) + (goto-char end)) (fill-region beg (point))))) (replace-regexp-in-string "[[:blank:]]+\\'" "" (buffer-string)))) ;;; Handling links -(defun sx-question-mode--process-links-in-buffer () - "Turn all markdown links in this buffer into compact format. +(defun sx-question-mode--process-links (beg end) + "Turn all markdown links between BEG and ENG into compact format. +END must be a marker. Image links are downloaded and displayed, if `sx-question-mode-use-images' is non-nil." - (save-excursion - (goto-char (point-min)) - (while (search-forward-regexp sx-question-mode--link-regexp nil t) - ;; Tags are tag-buttons. - (let ((tag (match-string-no-properties 5))) - (if (and tag (> (length tag) 0)) - (progn (replace-match "") - (sx-tag--insert tag)) - ;; Other links are link-buttons. - (let* ((text (match-string-no-properties 1)) - (url (or (match-string-no-properties 2) - (match-string-no-properties 4) - (sx-question-mode-find-reference - (match-string-no-properties 3) - text))) - (full-text (match-string-no-properties 0))) - (when (stringp url) - (replace-match "") - (sx-question-mode--insert-link - (if (and sx-question-mode-use-images (eq ?! (elt full-text 0))) - ;; Is it an image? - (sx-question-mode--create-image url) - ;; Or a regular link - (or (if sx-question-mode-pretty-links text full-text) url)) - url)))))))) + (goto-char beg) + (while (search-forward-regexp sx-question-mode--link-regexp end t) + ;; Tags are tag-buttons. + (let ((tag (match-string-no-properties 5))) + (if (and tag (> (length tag) 0)) + (progn (replace-match "") + (sx-tag--insert tag)) + ;; Other links are link-buttons. + (let* ((text (match-string-no-properties 1)) + (url (or (match-string-no-properties 2) + (match-string-no-properties 4) + (sx-question-mode-find-reference + (match-string-no-properties 3) + text))) + (full-text (match-string-no-properties 0))) + (when (stringp url) + (replace-match "") + (sx-question-mode--insert-link + (if (and sx-question-mode-use-images (eq ?! (elt full-text 0))) + ;; Is it an image? + (sx-question-mode--create-image url) + ;; Or a regular link + (or (if sx-question-mode-pretty-links text full-text) url)) + url))))))) (defun sx-question-mode--create-image (url) "Get and create an image from URL. -- cgit v1.2.3 From 7ce3968e25c8c7a076f83f0b0d9d3b3b7d8afadc Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Sat, 24 Jan 2015 04:00:46 -0200 Subject: Handle the `code` html tag --- sx-question-print.el | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/sx-question-print.el b/sx-question-print.el index c10c43e..5be3133 100644 --- a/sx-question-print.el +++ b/sx-question-print.el @@ -369,12 +369,46 @@ E.g.: (skip-chars-forward "\r\n[:blank:]") (forward-paragraph) (let ((end (point-marker))) + ;; Compact links. + (sx-question-mode--process-html-tags beg end) ;; Compact links. (sx-question-mode--process-links beg end) (goto-char end)) (fill-region beg (point))))) (replace-regexp-in-string "[[:blank:]]+\\'" "" (buffer-string)))) + +;;; HTML tags +(defconst sx-question-mode--html-tag-regexp + (rx "<" (group-n 1 "%s") (* (not (any ">"))) ">")) + +(defun sx-question-mode--process-html-tags (beg end) + "Hide all html tags between BEG and END and possibly interpret them. +END should be a marker." + ;; This code understands nested html, but not if the same tag is + ;; nested in itself (e.g., ). + (goto-char beg) + (while (search-forward-regexp + (format sx-question-mode--html-tag-regexp "[[:alpha:]]+") + end 'noerror) + (unless (save-match-data (markdown-code-at-point-p)) + (let ((tag (match-string 1)) + (l (match-beginning 0))) + (replace-match "") + (when (search-forward-regexp + (format sx-question-mode--html-tag-regexp (concat "/" tag)) + ;; Searching for a match has no bounds. + nil 'noerror) + (let ((r (copy-marker (match-beginning 0)))) + ;; The code tag is special, because it quotes everything in + ;; the middle. + (if (string= tag "quote") + (progn (replace-match "`") + (save-excursion (goto-char l) (insert "`"))) + (replace-match "") + ;; Handle stuff between the two tags. + (save-match-data (sx-question-mode--process-html-tags l r))))))))) + ;;; Handling links (defun sx-question-mode--process-links (beg end) -- cgit v1.2.3 From 6f64e45d5a957b768fb238406a64167753edd434 Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Sat, 24 Jan 2015 04:05:12 -0200 Subject: Handle the `sub` and `sup` html tag --- sx-question-print.el | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/sx-question-print.el b/sx-question-print.el index 5be3133..3b4069d 100644 --- a/sx-question-print.el +++ b/sx-question-print.el @@ -95,6 +95,11 @@ Some faces of this mode might be defined in the `sx-user' group." "Face used for downvoted score in the question buffer." :group 'sx-question-mode-faces) +(defface sx-question-mode-sub-sup + '((t :height 0.7)) + "Face used on and tags." + :group 'sx-question-mode-faces) + (defcustom sx-question-mode-header-tags "\nTags: " "String used before the question tags at the header." :type 'string @@ -407,7 +412,14 @@ END should be a marker." (save-excursion (goto-char l) (insert "`"))) (replace-match "") ;; Handle stuff between the two tags. - (save-match-data (sx-question-mode--process-html-tags l r))))))))) + (save-match-data (sx-question-mode--process-html-tags l r)) + (cond + ((string= tag "sub") + (add-text-properties + l r '(face sx-question-mode-sub-sup display (raise -0.3)))) + ((string= tag "sup") + (add-text-properties + l r '(face sx-question-mode-sub-sup display (raise +0.3)))))))))))) ;;; Handling links -- cgit v1.2.3 From a359f7cca7751372437b853fc648081acffc5b63 Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Sat, 24 Jan 2015 04:14:27 -0200 Subject: Handle the `kbd` html tag --- sx-question-print.el | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sx-question-print.el b/sx-question-print.el index 3b4069d..84dbe44 100644 --- a/sx-question-print.el +++ b/sx-question-print.el @@ -414,6 +414,8 @@ END should be a marker." ;; Handle stuff between the two tags. (save-match-data (sx-question-mode--process-html-tags l r)) (cond + ((string= tag "kbd") + (add-text-properties l r '(face markdown-inline-code-face))) ((string= tag "sub") (add-text-properties l r '(face sx-question-mode-sub-sup display (raise -0.3)))) -- cgit v1.2.3 From 57a165e1efa37c651cfa93e88c0c61d2f9d479bd Mon Sep 17 00:00:00 2001 From: Sean Allred Date: Mon, 26 Jan 2015 07:58:17 -0600 Subject: Fix tag-format function name in tests --- test/test-printing.el | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test-printing.el b/test/test-printing.el index 52fe5be..3125314 100644 --- a/test/test-printing.el +++ b/test/test-printing.el @@ -22,7 +22,7 @@ after being run through `sx-question--tag-format'." (+ (any whitespace digit)) (or "y" "d" "h" "m" "mo" "s") " ago" (+ whitespace) - (eval (mapconcat #'sx-question--tag-format + (eval (mapconcat #'sx-tag--format (list ,@tags) " ")))) -- cgit v1.2.3 From 6f83f1ef10f1316b489eba926aebecb51f37a6e7 Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Mon, 26 Jan 2015 14:52:21 -0200 Subject: Fix test --- test/test-printing.el | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/test/test-printing.el b/test/test-printing.el index 52fe5be..a6815e2 100644 --- a/test/test-printing.el +++ b/test/test-printing.el @@ -13,7 +13,7 @@ (defmacro question-list-regex (title votes answers &rest tags) "Construct a matching regexp for TITLE, VOTES, and ANSWERS. Each element of TAGS is appended at the end of the expression -after being run through `sx-question--tag-format'." +after being run through `sx-tag--format'." `(rx line-start (+ whitespace) ,(number-to-string votes) (+ whitespace) ,(number-to-string answers) @@ -22,8 +22,7 @@ after being run through `sx-question--tag-format'." (+ (any whitespace digit)) (or "y" "d" "h" "m" "mo" "s") " ago" (+ whitespace) - (eval (mapconcat #'sx-question--tag-format - (list ,@tags) " ")))) + (eval (mapconcat #'sx-tag--format (list ,@tags) " ")))) ;;; Tests -- cgit v1.2.3 From 131f48d0407f9721a467c84ea4aaecc4c3db62ba Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Mon, 26 Jan 2015 15:18:38 -0200 Subject: Only use images if imagemagick is available. --- sx-question-print.el | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/sx-question-print.el b/sx-question-print.el index 3a2eedf..e21c998 100644 --- a/sx-question-print.el +++ b/sx-question-print.el @@ -157,8 +157,13 @@ replaced with the comment." (const :tag "More active first" sx-answer-more-active-p)) :group 'sx-question-mode) -(defcustom sx-question-mode-use-images t - "Non-nil if SX should download and display images." +(defcustom sx-question-mode-use-images + (eval-when-compile + (image-type-available-p 'imagemagick)) + "Non-nil if SX should download and display images. +By default, this is `t' if the `imagemagick' image type is +available (checked with `image-type-available-p'). If this image +type is not available, images won't work." :type 'boolean :group 'sx-question-mode) -- cgit v1.2.3 From 75c4a7d4e02e81c42a4914e2b798f60415a3b505 Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Mon, 26 Jan 2015 15:28:19 -0200 Subject: Check image-type-available-p in the tests --- test/test-api.el | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/test/test-api.el b/test/test-api.el index 0715a2e..faf2e0a 100644 --- a/test/test-api.el +++ b/test/test-api.el @@ -18,12 +18,19 @@ (ert-deftest request-get-url () (should (sx-request-get-url "http://google.com")) (should-error (sx-request-get-url "http://github.com/Bruce-Connor/does-not-exist")) - (should - ;; If image is not recognized, this returns nil. - (create-image (sx-request-get-url "https://raw.githubusercontent.com/vermiculus/sx.el/master/list-and-question.png") - 'imagemagick t - :width (min sx-question-mode-image-max-width - (window-body-width nil 'pixel))))) + (when sx-question-mode-use-images + (should + ;; If image is not recognized, this returns nil. + (create-image (sx-request-get-url "https://raw.githubusercontent.com/vermiculus/sx.el/master/list-and-question.png") + 'imagemagick t + :width sx-question-mode-image-max-width))) + ;; In case imagemacgick is not available, let's try png so we at + ;; least test the function. + (when (image-type-available-p 'png) + (should + (create-image (sx-request-get-url "https://raw.githubusercontent.com/vermiculus/sx.el/master/list-and-question.png") + 'png t + :width sx-question-mode-image-max-width)))) (ert-deftest request-get-data () (should-error (sx-request-get-data "tags/emacs-does-not-exist")) -- cgit v1.2.3 From 21d1b2594efaff4089d2bab736e0a18a1ee1a9db Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Thu, 12 Feb 2015 14:16:50 -0200 Subject: Get rid of sx-question-list--update-mode-line --- sx-inbox.el | 6 +----- sx-question-list.el | 24 ++++-------------------- 2 files changed, 5 insertions(+), 25 deletions(-) diff --git a/sx-inbox.el b/sx-inbox.el index 21589fb..3048509 100644 --- a/sx-inbox.el +++ b/sx-inbox.el @@ -127,11 +127,7 @@ These are identified by their links.") (setq tabulated-list-format [("Type" 30 t nil t) ("Date" 10 t :right-align t) ("Title" 0)]) (setq mode-line-format sx-inbox--mode-line) - (setq header-line-format sx-inbox--header-line) - ;; @TODO: This will no longer be necessary once we properly - ;; refactor sx-question-list-mode. - (remove-hook 'tabulated-list-revert-hook - #'sx-question-list--update-mode-line t)) + (setq header-line-format sx-inbox--header-line)) ;;; Keybinds diff --git a/sx-question-list.el b/sx-question-list.el index 7757503..de15704 100644 --- a/sx-question-list.el +++ b/sx-question-list.el @@ -308,7 +308,8 @@ into consideration. The same holds for `sx-question-list--order'. \\{sx-question-list-mode-map}" (hl-line-mode 1) - (sx-question-list--update-mode-line) + (setq mode-line-format + sx-question-list--mode-line-format) (setq sx-question-list--pages-so-far 0) (setq tabulated-list-format [(" V" 3 t :right-align t) @@ -320,8 +321,6 @@ into consideration. The same holds for `sx-question-list--order'. (setq tabulated-list-sort-key nil) (add-hook 'tabulated-list-revert-hook #'sx-question-list-refresh nil t) - (add-hook 'tabulated-list-revert-hook - #'sx-question-list--update-mode-line nil t) (setq header-line-format sx-question-list--header-line)) (defcustom sx-question-list-date-sort-method 'last_activity_date @@ -400,14 +399,8 @@ Non-interactively, DATA is a question alist." ;; "Unanswered", etc. "Variable describing current tab being viewed.") -(defvar sx-question-list--total-count 0 - "Holds the total number of questions in the current buffer.") -(make-variable-buffer-local 'sx-question-list--total-count) - (defconst sx-question-list--mode-line-format - '(" " - mode-name - " " + '(" " mode-name ": " (:propertize sx-question-list--current-tab face mode-line-buffer-id) " [" @@ -418,7 +411,7 @@ Non-interactively, DATA is a question alist." ", " "Total: " (:propertize - (:eval (int-to-string sx-question-list--total-count)) + (:eval (int-to-string (length tabulated-list-entries))) face mode-line-buffer-id) "] ") "Mode-line construct to use in question-list buffers.") @@ -429,15 +422,6 @@ Non-interactively, DATA is a question alist." (cl-count-if-not #'sx-question--read-p sx-question-list--dataset))) -(defun sx-question-list--update-mode-line () - "Fill the mode-line with useful information." - ;; All the data we need is right in the buffer. - (when (derived-mode-p 'sx-question-list-mode) - (setq mode-line-format - sx-question-list--mode-line-format) - (setq sx-question-list--total-count - (length tabulated-list-entries)))) - (defvar sx-question-list--site nil "Site being displayed in the *question-list* buffer.") (make-variable-buffer-local 'sx-question-list--site) -- cgit v1.2.3 From ade5fe434b5fd031db8cd4e601f26f2933354ab2 Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Thu, 12 Feb 2015 14:18:55 -0200 Subject: Compiler warnings --- sx-button.el | 10 +++++----- sx.el | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/sx-button.el b/sx-button.el index 5a2f052..46855e7 100644 --- a/sx-button.el +++ b/sx-button.el @@ -77,23 +77,23 @@ This is usually a link's URL, or the content of a code block." (point) 'sx-button-copy-type) content))))) -(defun sx-button-edit-this (text-or-marker &optional major-mode) - "Open a temp buffer populated with the string TEXT-OR-MARKER using MAJOR-MODE. +(defun sx-button-edit-this (text-or-marker &optional majormode) + "Open a temp buffer populated with the string TEXT-OR-MARKER using MAJORMODE. When given a marker (or interactively), use the 'sx-button-copy and the 'sx-mode text-properties under the marker. These are usually part of a code-block." (interactive (list (point-marker))) ;; Buttons receive markers. (when (markerp text-or-marker) - (setq major-mode (get-text-property text-or-marker 'sx-mode)) + (setq majormode (get-text-property text-or-marker 'sx-mode)) (unless (setq text-or-marker (get-text-property text-or-marker 'sx-button-copy)) (sx-message "Nothing of interest here."))) (with-current-buffer (pop-to-buffer (generate-new-buffer "*sx temp buffer*")) (insert text-or-marker) - (when major-mode - (funcall major-mode)))) + (when majormode + (funcall majormode)))) (defun sx-button-follow-link (&optional pos) "Follow link at POS. If POS is nil, use `point'." diff --git a/sx.el b/sx.el index 33b36b6..73d874f 100644 --- a/sx.el +++ b/sx.el @@ -204,7 +204,7 @@ is intentionally skipped." (while (and ;; We're not at the end. (cdr-safe tail) ;; We're not at the right place. - (,(or predicate #'<) x (cadr tail))) + (funcall (or ,predicate #'<) x (cadr tail))) (setq tail (cdr tail))) (setcdr tail (cons x (cdr tail))))) -- cgit v1.2.3 From 85109b7dd2ffb896151ccef2c014c7d9ea33e682 Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Thu, 12 Feb 2015 14:59:46 -0200 Subject: Define possible values for answer sorting --- sx-question-print.el | 19 +++++++++++++++---- sx.el | 6 ++++++ 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/sx-question-print.el b/sx-question-print.el index 778b580..4f50560 100644 --- a/sx-question-print.el +++ b/sx-question-print.el @@ -153,13 +153,24 @@ replaced with the comment." :type 'boolean :group 'sx-question-mode) +(defconst sx-question-mode--sort-methods + (let ((methods + '(("Higher-scoring" . sx-answer-higher-score-p) + ("Newer" . sx-answer-newer-p) + ("More active" . sx-answer-more-active-p)))) + (append (mapcar (lambda (x) (cons (concat (car x) " first") (cdr x))) + methods) + (mapcar (lambda (x) (cons (concat (car x) " last") + (sx--invert-predicate (cdr x)))) + methods)))) + (defcustom sx-question-mode-answer-sort-function #'sx-answer-higher-score-p "Function used to sort answers in the question buffer." - :type '(choice - (const :tag "Higher-scoring first" sx-answer-higher-score-p) - (const :tag "Newer first" sx-answer-newer-p) - (const :tag "More active first" sx-answer-more-active-p)) + :type + (cons 'choice + (mapcar (lambda (x) `(const :tag ,(car x) ,(cdr x))) + sx-question-mode--sort-methods)) :group 'sx-question-mode) diff --git a/sx.el b/sx.el index 33b36b6..381f78e 100644 --- a/sx.el +++ b/sx.el @@ -335,6 +335,12 @@ GET-FUNC and performs the actual comparison." "Return STRING with consecutive whitespace squashed together." (replace-regexp-in-string "[ \r\n]+" " " string)) +(defun sx--invert-predicate (predicate) + "Return PREDICATE function with arguments inverted. +For instance (sx--invert-predicate #'<) is the same as #'>. +Note this is not the same as negating PREDICATE." + (lambda (&rest args) (apply predicate (reverse args)))) + ;;; Printing request data (defvar sx--overlays nil -- cgit v1.2.3 From fe7e31a55cdd9c25f3d0c1fba088d5499a887af9 Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Thu, 12 Feb 2015 15:00:05 -0200 Subject: Sort answers with O --- sx-question-mode.el | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/sx-question-mode.el b/sx-question-mode.el index 6125416..846ad7f 100644 --- a/sx-question-mode.el +++ b/sx-question-mode.el @@ -214,6 +214,7 @@ Letters do not insert themselves; instead, they are commands. ("v" sx-visit-externally) ("u" sx-upvote) ("d" sx-downvote) + ("O" sx-question-mode-order-by) ("q" quit-window) (" " scroll-up-command) ("a" sx-answer) @@ -256,6 +257,18 @@ query the api." (unless (derived-mode-p 'sx-question-mode) (error "Not in `sx-question-mode'"))) +(defun sx-question-mode-order-by (sort) + "Order answers in the current buffer by the method SORT. +Sets `sx-question-list--order' and then calls +`sx-question-list-refresh' with `redisplay'." + (interactive + (list (let ((order (sx-completing-read "Order answers by: " + (mapcar #'car sx-question-mode--sort-methods)))) + (cdr-safe (assoc-string order sx-question-mode--sort-methods))))) + (when (and sort (functionp sort)) + (setq sx-question-mode-answer-sort-function sort) + (sx-question-mode-refresh 'no-update))) + (provide 'sx-question-mode) ;;; sx-question-mode.el ends here -- cgit v1.2.3 From b7d1d7c47e6bbb907df90ee078ef18ba47412b75 Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Thu, 12 Feb 2015 18:54:41 -0200 Subject: Try sx-open-link, THEN do sx-visit-externally --- sx-button.el | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/sx-button.el b/sx-button.el index e00b086..992c654 100644 --- a/sx-button.el +++ b/sx-button.el @@ -100,7 +100,12 @@ usually part of a code-block." (interactive) (let ((url (or (get-text-property (or pos (point)) 'sx-button-url) (sx-user-error "No url under point: %s" (or pos (point)))))) - (sx-open-link url))) + ;; If we didn't recognize the link, this errors immediately. If + ;; we mistakenly recognize it, it will error when we try to fetch + ;; whatever we thought it was. + (condition-case nil (sx-open-link url) + ;; When it errors, don't blame the user, just visit externally. + (error (sx-visit-externally url))))) ;;; Help-echo definitions -- cgit v1.2.3 From 25f8929c91050332f972dca42862e65bc22608b3 Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Thu, 12 Feb 2015 20:32:39 -0200 Subject: Refactor fill-and-fontify to sx-question-mode--insert-markdown --- sx-question-mode.el | 2 +- sx-question-print.el | 94 +++++++++++++++++++++++++++++++--------------------- 2 files changed, 57 insertions(+), 39 deletions(-) diff --git a/sx-question-mode.el b/sx-question-mode.el index 6125416..44e96a5 100644 --- a/sx-question-mode.el +++ b/sx-question-mode.el @@ -195,7 +195,7 @@ Letters do not insert themselves; instead, they are commands. (set-window-parameter nil 'quit-restore `(other window nil ,(current-buffer)))) - ;; We call font-lock-region manually. See `sx-question-mode--fill-and-fontify' + ;; We call font-lock-region manually. See `sx-question-mode--insert-markdown'. (font-lock-mode -1) (remove-hook 'after-change-functions 'markdown-check-change-for-wiki-link t) (remove-hook 'window-configuration-change-hook diff --git a/sx-question-print.el b/sx-question-print.el index e21c998..e47bc3a 100644 --- a/sx-question-print.el +++ b/sx-question-print.el @@ -246,10 +246,9 @@ DATA can represent a question or an answer." 'face 'sx-question-mode-header)) (sx--wrap-in-overlay '(face sx-question-mode-content-face) + (insert "\n") + (sx-question-mode--insert-markdown .body_markdown) (insert "\n" - (sx-question-mode--fill-and-fontify - .body_markdown) - "\n" (propertize sx-question-mode-separator 'face 'sx-question-mode-header))) ;; Comments have their own `sx--data-here' property (so they can @@ -296,10 +295,13 @@ The comment is indented, filled, and then printed according to (format sx-question-mode-comments-format (sx-user--format "%d" .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)) + ;; We use temp buffer, so that image overlays don't get + ;; inserted with the comment. + (with-temp-buffer + ;; We fill with three spaces at the start, so the comment is + ;; slightly indented. + (sx-question-mode--insert-markdown (concat " " .body_markdown)) + (buffer-string)) ;; Then we remove the spaces from the first line, since we'll ;; add the username there anyway. 3)))))) @@ -345,37 +347,53 @@ E.g.: (>= 2 (any lower numeric "/._%&#?=;")))))) "Regexp matching markdown links.") -(defun sx-question-mode--fill-and-fontify (text) - "Return TEXT filled according to `markdown-mode'." - (with-temp-buffer - (insert text) - (delay-mode-hooks (markdown-mode)) - (font-lock-mode -1) - (when sx-question-mode-bullet-appearance - (font-lock-add-keywords ;; Bullet items. - nil - `((,(rx line-start (0+ blank) (group-n 1 (any "*+-")) blank) - 1 '(face nil display ,sx-question-mode-bullet-appearance) prepend)))) - (font-lock-add-keywords ;; Highlight usernames. - nil - `((,(rx (or blank line-start) - (group-n 1 (and "@" (1+ (not space)))) - symbol-end) - 1 font-lock-builtin-face))) - ;; Everything. - (font-lock-fontify-region (point-min) (point-max)) - ;; Compact links. - (sx-question-mode--process-links-in-buffer) - ;; And now the filling - (goto-char (point-min)) - (while (null (eobp)) - ;; Don't fill pre blocks. - (unless (sx-question-mode--dont-fill-here) - (let ((beg (point))) - (skip-chars-forward "\r\n[:blank:]") - (forward-paragraph) - (fill-region beg (point))))) - (replace-regexp-in-string "[[:blank:]]+\\'" "" (buffer-string)))) +(defun sx-question-mode--process-markdown-in-region (beg end) + "Process Markdown text between BEG and END. +This does not do Markdown font-locking. Instead, it fills text, +propertizes links, inserts images, cleans up html comments, and +font-locks code-blocks according to mode." + (save-restriction + (save-excursion + (narrow-to-region beg end) + ;; Compact links. + (sx-question-mode--process-links-in-buffer) + ;; And now the filling and other handlings. + (goto-char (point-min)) + (while (null (eobp)) + ;; Don't fill pre blocks. + (unless (sx-question-mode--dont-fill-here) + (let ((beg (point))) + (skip-chars-forward "\r\n[:blank:]") + (forward-paragraph) + (fill-region beg (point)))))))) + +(defun sx-question-mode--insert-markdown (text) + "Return TEXT fontified according to `markdown-mode'." + (let ((beg (point))) + (insert + ;; Font-locking needs to be done in a temp buffer, because it + ;; affects the entire buffer even if we narrow. + (with-temp-buffer + (insert text) + (delay-mode-hooks (markdown-mode)) + (font-lock-mode -1) + (when sx-question-mode-bullet-appearance + (font-lock-add-keywords ;; Bullet items. + nil + `((,(rx line-start (0+ blank) (group-n 1 (any "*+-")) blank) + 1 '(face nil display ,sx-question-mode-bullet-appearance) prepend)))) + (font-lock-add-keywords ;; Highlight usernames. + nil + `((,(rx (or blank line-start) + (group-n 1 (and "@" (1+ (not space)))) + symbol-end) + 1 font-lock-builtin-face))) + ;; Everything. + (font-lock-fontify-region (point-min) (point-max)) + (replace-regexp-in-string "[[:blank:]]+\\'" "" (buffer-string)))) + ;; This part can and should be done in place, this way it can + ;; create overlays. + (sx-question-mode--process-markdown-in-region beg (point)))) ;;; Handling links -- cgit v1.2.3 From 7ec0b2de5f1458354db7068e936d58ba9914c0a8 Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Thu, 12 Feb 2015 21:05:03 -0200 Subject: Make sx-request-get-url support asynchronous fetching --- sx-request.el | 47 +++++++++++++++++++++++++++++++++-------------- 1 file changed, 33 insertions(+), 14 deletions(-) diff --git a/sx-request.el b/sx-request.el index 7f18a2b..2e650b4 100644 --- a/sx-request.el +++ b/sx-request.el @@ -221,25 +221,44 @@ Currently returns nil." "https://raw.githubusercontent.com/vermiculus/sx.el/data/data/%s.el" "Url of the \"data\" directory inside the SX `data' branch.") -(defun sx-request-get-url (url) - "Fetch and return data stored online at URL." +(defun sx-request--read-buffer-data () + "Return the buffer contents after any url headers. +Error if url headers are absent or if they indicate something +went wrong." + (goto-char (point-min)) + (unless (string-match "200" (thing-at-point 'line)) + (error "Page not found.")) + (if (not (search-forward "\n\n" nil t)) + (error "Headers missing; response corrupt") + (prog1 (buffer-substring (point) (point-max)) + (kill-buffer (current-buffer))))) + +(defun sx-request-get-url (url &optional callback) + "Fetch and return data stored online at URL. +If CALLBACK is nil, fetching is done synchronously and the +data (buffer contents sans headers) is returned as a string. + +Otherwise CALLBACK must be a function of a single argument. Then +`url-retrieve' is called asynchronously and CALLBACK is passed +the retrieved data." (let* ((url-automatic-caching t) (url-inhibit-uncompression t) (url-request-method "GET") (url-request-extra-headers '(("Content-Type" . "application/x-www-form-urlencoded"))) - (response-buffer (url-retrieve-synchronously url))) - (if (not response-buffer) - (error "Something went wrong in `url-retrieve-synchronously'") - (with-current-buffer response-buffer - (progn - (goto-char (point-min)) - (unless (string-match "200" (thing-at-point 'line)) - (error "Page not found.")) - (if (not (search-forward "\n\n" nil t)) - (error "Headers missing; response corrupt") - (prog1 (buffer-substring (point) (point-max)) - (kill-buffer (current-buffer))))))))) + (callback-internal + (when callback + ;; @TODO: Error check in STATUS. + (lambda (_status) + (funcall callback (sx-request--read-buffer-data))))) + (response-buffer + (if callback (url-retrieve url callback-internal nil 'silent) + (url-retrieve-synchronously url)))) + (unless callback + (if (not response-buffer) + (error "Something went wrong in `url-retrieve-synchronously'") + (with-current-buffer response-buffer + (sx-request--read-buffer-data)))))) (defun sx-request-get-data (file) "Fetch and return data stored online by SX. -- cgit v1.2.3 From 9356a6a039f0d8cf8d9f31e42e8007617c58577d Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Thu, 12 Feb 2015 21:06:25 -0200 Subject: Fetch images asynchronously. --- sx-question-print.el | 81 +++++++++++++++++++++++++++------------------------- 1 file changed, 42 insertions(+), 39 deletions(-) diff --git a/sx-question-print.el b/sx-question-print.el index e47bc3a..b5b7201 100644 --- a/sx-question-print.el +++ b/sx-question-print.el @@ -416,30 +416,38 @@ Image links are downloaded and displayed, if (sx-question-mode-find-reference (match-string-no-properties 3) text))) - (full-text (match-string-no-properties 0))) + (full-text (match-string-no-properties 0)) + (image-p (and sx-question-mode-use-images + (eq ?! (elt full-text 0))))) (when (stringp url) (replace-match "") (sx-question-mode--insert-link - (if (and sx-question-mode-use-images (eq ?! (elt full-text 0))) - ;; Is it an image? - (sx-question-mode--create-image url) - ;; Or a regular link - (or (if sx-question-mode-pretty-links text full-text) url)) - url)))))))) - -(defun sx-question-mode--create-image (url) - "Get and create an image from URL. + (unless image-p + (or (if sx-question-mode-pretty-links text full-text) + url)) + url) + (when image-p + (sx-question-mode--create-image url (1- (point))))))))))) + +(defun sx-question-mode--create-image (url point) + "Get and create an image from URL and insert it at POINT. +The image will take the place of the character at POINT. Its size is bound by `sx-question-mode-image-max-width' and `window-body-width'." - (let* ((image - (create-image (sx-request-get-url url) 'imagemagick t)) - (image-width (car (image-size image 'pixels)))) - (append image - (list :width (min sx-question-mode-image-max-width - (window-body-width nil 'pixel) - image-width))))) - -(defun sx-question-mode--insert-link (text-or-image url) + (let* ((ov (make-overlay point (1+ point) (current-buffer) t nil)) + (callback + (lambda (data) + (let* ((image (create-image data 'imagemagick t)) + (image-width (car (image-size image 'pixels)))) + (overlay-put + ov 'display + (append image + (list :width (min sx-question-mode-image-max-width + (window-body-width nil 'pixel) + image-width)))))))) + (sx-request-get-url url callback))) + +(defun sx-question-mode--insert-link (text url) "Return a link propertized version of TEXT-OR-IMAGE. URL is used as 'help-echo and 'url properties." ;; For now, the only way to handle nested links is to remove them. @@ -450,26 +458,21 @@ URL is used as 'help-echo and 'url properties." (replace-match "") (forward-char 1) (delete-char 1))) - (let ((imagep (not (stringp text-or-image)))) - ;; Images need to be at the start of a line. - (when (and imagep (not (looking-at-p "^"))) - (insert "\n")) - (apply #'insert-text-button - (if imagep " " text-or-image) - ;; Mouse-over - 'help-echo - (format sx-button--link-help-echo - (propertize (sx--shorten-url url) - 'face 'font-lock-function-name-face)) - ;; For visiting and stuff. - 'sx-button-url url - 'sx-button-copy url - :type 'sx-button-link - ;; The last argument of `apply' is a list. - (when imagep - `(face default display ,text-or-image))) - ;; Images need to be at the end of a line too. - (insert "\n"))) + ;; Images need to be at the start of a line. + (when (and imagep (not (looking-at-p "^"))) + (insert "\n")) + (insert-text-button (or text " ") + ;; Mouse-over + 'help-echo + (format sx-button--link-help-echo + (propertize (sx--shorten-url url) + 'face 'font-lock-function-name-face)) + ;; For visiting and stuff. + 'sx-button-url url + 'sx-button-copy url + :type 'sx-button-link) + ;; Images need to be at the end of a line too. + (insert "\n")) (defun sx-question-mode-find-reference (id &optional fallback-id) "Find url identified by reference ID in current buffer. -- cgit v1.2.3 From 1f73185f411de81a5e26152377c499498d732d8c Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Thu, 12 Feb 2015 21:26:03 -0200 Subject: require 'sx-tag --- sx-question-list.el | 1 + 1 file changed, 1 insertion(+) diff --git a/sx-question-list.el b/sx-question-list.el index 07355d0..333fd83 100644 --- a/sx-question-list.el +++ b/sx-question-list.el @@ -27,6 +27,7 @@ (require 'sx) (require 'sx-time) +(require 'sx-tag) (require 'sx-site) (require 'sx-question) (require 'sx-question-mode) -- cgit v1.2.3 From 66af19c7df5b1ef2d3252593b4eb33a101dc1582 Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Thu, 12 Feb 2015 21:48:40 -0200 Subject: fix images --- sx-question-print.el | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/sx-question-print.el b/sx-question-print.el index 7244a6a..160074d 100644 --- a/sx-question-print.el +++ b/sx-question-print.el @@ -427,7 +427,7 @@ Image links are downloaded and displayed, if url)) url) (when image-p - (sx-question-mode--create-image url (1- (point))))))))))) + (sx-question-mode--create-image url (- (point) 2)))))))))) (defun sx-question-mode--create-image (url point) "Get and create an image from URL and insert it at POINT. @@ -445,7 +445,8 @@ Its size is bound by `sx-question-mode-image-max-width' and (list :width (min sx-question-mode-image-max-width (window-body-width nil 'pixel) image-width)))))))) - (sx-request-get-url url callback))) + (sx-request-get-url url callback) + (overlay-put ov 'face 'default))) (defun sx-question-mode--insert-link (text url) "Return a link propertized version of TEXT-OR-IMAGE. @@ -459,7 +460,7 @@ URL is used as 'help-echo and 'url properties." (forward-char 1) (delete-char 1))) ;; Images need to be at the start of a line. - (when (and imagep (not (looking-at-p "^"))) + (unless (or text (looking-at-p "^")) (insert "\n")) (insert-text-button (or text " ") ;; Mouse-over @@ -472,7 +473,7 @@ URL is used as 'help-echo and 'url properties." 'sx-button-copy url :type 'sx-button-link) ;; Images need to be at the end of a line too. - (insert "\n")) + (unless text (insert "\n"))) (defun sx-question-mode-find-reference (id &optional fallback-id) "Find url identified by reference ID in current buffer. -- cgit v1.2.3 From dc4d2bee678428eb004d963bd21e08a347ef622e Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Thu, 12 Feb 2015 22:01:17 -0200 Subject: Fix test --- test/test-printing.el | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test/test-printing.el b/test/test-printing.el index 8016444..850edd8 100644 --- a/test/test-printing.el +++ b/test/test-printing.el @@ -167,8 +167,9 @@ after being run through `sx-tag--format'." "Check complicated questions are filled correctly." (should (equal - (sx-question-mode--fill-and-fontify - "Creating an account on a new site requires you to log into that site using *the same credentials you used on existing sites.* For instance, if you used the Stack Exchange login method, you'd... + (with-temp-buffer + (sx-question-mode--insert-markdown + "Creating an account on a new site requires you to log into that site using *the same credentials you used on existing sites.* For instance, if you used the Stack Exchange login method, you'd... 1. Click the \"Log in using Stack Exchange\" button: @@ -192,6 +193,7 @@ after being run through `sx-tag--format'." [1]: http://i.stack.imgur.com/ktFTs.png [2]: http://i.stack.imgur.com/5l2AY.png [3]: http://i.stack.imgur.com/22myl.png") + (buffer-string)) "Creating an account on a new site requires you to log into that site using *the same credentials you used on existing sites.* For instance, if you used the Stack Exchange login method, you'd... -- cgit v1.2.3 From cb29144eb54eb8541f469995a13909ec0bdb1c1d Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Sat, 14 Feb 2015 15:46:39 -0200 Subject: Fix declare-function --- sx-button.el | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sx-button.el b/sx-button.el index 8a4bcc0..9f6d3b3 100644 --- a/sx-button.el +++ b/sx-button.el @@ -169,7 +169,7 @@ usually part of a code-block." 'face 'sx-user-name :supertype 'sx-button) -(declare-function sx-search-tag-at-point "sx-tag") +(declare-function sx-search-tag-at-point "sx-search") (define-button-type 'sx-button-tag 'action #'sx-search-tag-at-point 'help-echo sx-button--tag-help-echo -- cgit v1.2.3 From 9140a717b13b2e4919164b249f6d4b83b47ba53e Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Sat, 14 Feb 2015 15:48:53 -0200 Subject: Add an sx-tag-meta property to tags. --- sx-search.el | 12 +++++++++--- sx-tag.el | 13 ++++++++----- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/sx-search.el b/sx-search.el index b33efff..b245cbe 100644 --- a/sx-search.el +++ b/sx-search.el @@ -126,9 +126,15 @@ prefix argument, the user is asked for everything." (let ((tag (save-excursion (when pos (goto-char pos)) (or (get-text-property (point) 'sx-tag) - (thing-at-point 'symbol))))) - (sx-search (or sx-question-list--site - (sx-assoc-let sx-question-mode--data .site_par)) + (thing-at-point 'symbol)))) + (meta (save-excursion + (when pos (goto-char pos)) + (get-text-property (point) 'sx-tag-meta))) + (site (replace-regexp-in-string + (rx string-start "meta.") "" + (or sx-question-list--site + (sx-assoc-let sx-question-mode--data .site_par))))) + (sx-search (concat (when meta "meta.") site) nil tag))) (provide 'sx-search) diff --git a/sx-tag.el b/sx-tag.el index 316226b..a59e0d7 100644 --- a/sx-tag.el +++ b/sx-tag.el @@ -142,17 +142,20 @@ tags." ;;; Printing -(defun sx-tag--format (tag) - "Format and return TAG for display." +(defun sx-tag--format (tag &optional meta) + "Format and return TAG for display. +If META is non-nil, the tag is for the meta site." (with-temp-buffer - (sx-tag--insert tag) + (sx-tag--insert tag meta) (buffer-string))) -(defun sx-tag--insert (tag) - "Insert TAG button." +(defun sx-tag--insert (tag &optional meta) + "Insert TAG button. +If META is non-nil, the tag is for the meta site." (insert-text-button (concat "[" tag "]") 'sx-button-copy tag 'sx-tag tag + 'sx-tag-meta meta :type 'sx-button-tag)) (provide 'sx-tag) -- cgit v1.2.3 From ded9c419cbbbe77446681460e4352168a8b33e80 Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Sat, 14 Feb 2015 15:58:01 -0200 Subject: Define sx-tag--format-tags to format multiple tags. --- sx-tag.el | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/sx-tag.el b/sx-tag.el index a59e0d7..3c00ae2 100644 --- a/sx-tag.el +++ b/sx-tag.el @@ -158,6 +158,19 @@ If META is non-nil, the tag is for the meta site." 'sx-tag-meta meta :type 'sx-button-tag)) +(defun sx-tag--format-tags (tags &optional site) + "Format and concatenate a sequence of TAGS. +Returns a string of all tags in TAGS, separated by a space. + +SITE is the site to which the tags refer, it is only used to +decide whether they are main or meta tags. SITE can also be t or +nil, which respectively indicate meta and main." + (let ((is-meta + (if (stringp site) (string-match (rx string-start "meta.") site) + site))) + (mapconcat (lambda (tag) (sx-tag--format tag is-meta)) + tags " "))) + (provide 'sx-tag) ;;; sx-tag.el ends here -- cgit v1.2.3 From b8124d4056632ae3eb1381b19db743625a715548 Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Sat, 14 Feb 2015 15:58:17 -0200 Subject: Use sx-tag--format-tags --- sx-question-list.el | 2 +- sx-question-print.el | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sx-question-list.el b/sx-question-list.el index 333fd83..6bae225 100644 --- a/sx-question-list.el +++ b/sx-question-list.el @@ -166,7 +166,7 @@ Also see `sx-question-list-refresh'." " " ;; @TODO: Make this width customizable. (Or maybe just make ;; the whole thing customizable) - (format "%-40s" (mapconcat #'sx-tag--format .tags " ")) + (format "%-40s" (sx-tag--format-tags .tags sx-question-list--site)) " " (sx-user--format "%15d %4r" .owner) (propertize " " 'display "\n"))))))) diff --git a/sx-question-print.el b/sx-question-print.el index 190c924..e535091 100644 --- a/sx-question-print.el +++ b/sx-question-print.el @@ -223,7 +223,7 @@ DATA can represent a question or an answer." ;; Tags (sx-question-mode--insert-header sx-question-mode-header-tags - (mapconcat #'sx-tag--format .tags " ") + (sx-tag--format-tags .tags .site_par) nil)) ;; Body (insert "\n" -- cgit v1.2.3 From 3b0008b6fc32548905510cf86ab5651e529b84ee Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Sat, 14 Feb 2015 16:02:01 -0200 Subject: Understand [meta-tag:TAG] links --- sx-question-print.el | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/sx-question-print.el b/sx-question-print.el index e535091..abf3236 100644 --- a/sx-question-print.el +++ b/sx-question-print.el @@ -320,7 +320,8 @@ E.g.: (defconst sx-question-mode--link-regexp ;; Done at compile time. - (rx (or (and "[tag:" (group-n 5 (+ (not (any " ]")))) "]") + (rx (or (and "[" (optional (group-n 6 "meta-")) "tag:" + (group-n 5 (+ (not (any " ]")))) "]") (and "[" (group-n 1 (1+ (not (any "]")))) "]" (or (and "(" (group-n 2 (1+ (not (any ")")))) ")") (and "[" (group-n 3 (1+ (not (any "]")))) "]"))) @@ -373,7 +374,8 @@ E.g.: (let ((tag (match-string-no-properties 5))) (if (and tag (> (length tag) 0)) (progn (replace-match "") - (sx-tag--insert tag)) + ;; `match-string' 6 is the "meta-" prefix. + (sx-tag--insert tag (match-string 6))) ;; Other links are link-buttons. (let* ((text (match-string-no-properties 1)) (url (or (match-string-no-properties 2) -- cgit v1.2.3 From 2f398913b77d190f2e0c96ba15296c231ba21e18 Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Sat, 14 Feb 2015 16:44:01 -0200 Subject: Show the site name on the mode-lien --- sx-question-list.el | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/sx-question-list.el b/sx-question-list.el index de15704..06af161 100644 --- a/sx-question-list.el +++ b/sx-question-list.el @@ -400,7 +400,13 @@ Non-interactively, DATA is a question alist." "Variable describing current tab being viewed.") (defconst sx-question-list--mode-line-format - '(" " mode-name ": " + '(" " + (:propertize + (:eval (mapconcat #'capitalize + (split-string sx-question-list--site "\\.") + " ")) + face mode-line-buffer-id) + " " mode-name ": " (:propertize sx-question-list--current-tab face mode-line-buffer-id) " [" -- cgit v1.2.3 From e19068da5bc6ab29b3a0cd21daed3cf98708df39 Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Sun, 15 Feb 2015 12:57:04 -0200 Subject: Fix paragraph filling --- sx-question-print.el | 34 ++++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/sx-question-print.el b/sx-question-print.el index 160074d..e42e983 100644 --- a/sx-question-print.el +++ b/sx-question-print.el @@ -352,20 +352,26 @@ E.g.: This does not do Markdown font-locking. Instead, it fills text, propertizes links, inserts images, cleans up html comments, and font-locks code-blocks according to mode." - (save-restriction - (save-excursion - (narrow-to-region beg end) - ;; Compact links. - (sx-question-mode--process-links-in-buffer) - ;; And now the filling and other handlings. - (goto-char (point-min)) - (while (null (eobp)) - ;; Don't fill pre blocks. - (unless (sx-question-mode--dont-fill-here) - (let ((beg (point))) - (skip-chars-forward "\r\n[:blank:]") - (forward-paragraph) - (fill-region beg (point)))))))) + ;; Paragraph filling + (let ((paragraph-start + "\f\\|[ \t]*$\\|[ \t]*[*+-] \\|[ \t]*[0-9]+\\.[ \t]\\|[ \t]*: ") + (paragraph-separate "\\(?:[ \t\f]*\\|.* \\)$") + (adaptive-fill-first-line-regexp "\\`[ \t]*>[ \t]*?\\'") + (adaptive-fill-function #'markdown-adaptive-fill-function)) + (save-restriction + (save-excursion + (narrow-to-region beg end) + ;; Compact links. + (sx-question-mode--process-links-in-buffer) + ;; And now the filling and other handlings. + (goto-char (point-min)) + (while (null (eobp)) + ;; Don't fill pre blocks. + (unless (sx-question-mode--dont-fill-here) + (let ((beg (point))) + (skip-chars-forward "\r\n[:blank:]") + (forward-paragraph) + (fill-region beg (point))))))))) (defun sx-question-mode--insert-markdown (text) "Return TEXT fontified according to `markdown-mode'." -- cgit v1.2.3 From d4531c7a605e4d442632c7b54accbefc465a4601 Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Fri, 20 Feb 2015 20:01:27 -0200 Subject: Fix "Asked on" for answers --- sx-question-print.el | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/sx-question-print.el b/sx-question-print.el index abf3236..056c265 100644 --- a/sx-question-print.el +++ b/sx-question-print.el @@ -63,7 +63,7 @@ Some faces of this mode might be defined in the `sx-user' group." :type 'string :group 'sx-question-mode) -(defcustom sx-question-mode-header-author-format "\nAuthor: %d %r" +(defcustom sx-question-mode-header-author-format "\nAuthor: %d %r" "String used to display the question author at the header. % constructs have special meaning here. See `sx-user--format'." :type 'string @@ -74,7 +74,7 @@ Some faces of this mode might be defined in the `sx-user' group." "Face used on the question date in the question buffer." :group 'sx-question-mode-faces) -(defcustom sx-question-mode-header-date "\nAsked on: " +(defcustom sx-question-mode-header-date "\nPosted on: " "String used before the question date at the header." :type 'string :group 'sx-question-mode) @@ -95,12 +95,12 @@ Some faces of this mode might be defined in the `sx-user' group." "Face used for downvoted score in the question buffer." :group 'sx-question-mode-faces) -(defcustom sx-question-mode-header-tags "\nTags: " +(defcustom sx-question-mode-header-tags "\nTags: " "String used before the question tags at the header." :type 'string :group 'sx-question-mode) -(defcustom sx-question-mode-header-score "\nScore: " +(defcustom sx-question-mode-header-score "\nScore: " "String used before the question score at the header." :type 'string :group 'sx-question-mode) -- cgit v1.2.3 From 3ff90f54e74d034224d70fe90bc2fc67aae6d8f4 Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Fri, 20 Feb 2015 20:02:22 -0200 Subject: Improve legibility of sx-question-mode--print-section --- sx-question-print.el | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/sx-question-print.el b/sx-question-print.el index 056c265..8ba0157 100644 --- a/sx-question-print.el +++ b/sx-question-print.el @@ -193,17 +193,20 @@ DATA can represent a question or an answer." 'sx-question-mode--section (if .title 1 2) 'sx-button-copy .share_link :type 'sx-question-mode-title) + ;; Sections can be hidden with overlays (sx--wrap-in-overlay '(sx-question-mode--section-content t) + ;; Author (insert (sx-user--format (propertize sx-question-mode-header-author-format 'face 'sx-question-mode-header) .owner)) + + ;; Date (sx-question-mode--insert-header - ;; Date sx-question-mode-header-date (concat (sx-time-seconds-to-date .creation_date) @@ -212,6 +215,8 @@ DATA can represent a question or an answer." (sx-time-since .last_edit_date) (sx-user--format "%d" .last_editor)))) 'sx-question-mode-date) + + ;; Score and upvoted/downvoted status. (sx-question-mode--insert-header sx-question-mode-header-score (format "%s" .score) @@ -219,6 +224,8 @@ DATA can represent a question or an answer." ((eq .upvoted t) 'sx-question-mode-score-upvoted) ((eq .downvoted t) 'sx-question-mode-score-downvoted) (t 'sx-question-mode-score))) + + ;; Tags (when .title ;; Tags (sx-question-mode--insert-header -- cgit v1.2.3 From 60df6a23b27bc1ff1dfa8f3bdd37c1c4543d980f Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Fri, 20 Feb 2015 20:08:53 -0200 Subject: Up and Down arrows when you vote --- sx-question-print.el | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/sx-question-print.el b/sx-question-print.el index 8ba0157..bd764da 100644 --- a/sx-question-print.el +++ b/sx-question-print.el @@ -219,11 +219,11 @@ DATA can represent a question or an answer." ;; Score and upvoted/downvoted status. (sx-question-mode--insert-header sx-question-mode-header-score - (format "%s" .score) - (cond - ((eq .upvoted t) 'sx-question-mode-score-upvoted) - ((eq .downvoted t) 'sx-question-mode-score-downvoted) - (t 'sx-question-mode-score))) + (format "%s%s" .score + (cond ((eq .upvoted t) "↑") ((eq .downvoted t) "↓") (t ""))) + (cond ((eq .upvoted t) 'sx-question-mode-score-upvoted) + ((eq .downvoted t) 'sx-question-mode-score-downvoted) + (t 'sx-question-mode-score))) ;; Tags (when .title -- cgit v1.2.3 From bbc6383dea772a62c7ddc8bbcfec72e2ddd14969 Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Fri, 20 Feb 2015 20:09:18 -0200 Subject: Indicate which answer is accepted --- sx-question-print.el | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/sx-question-print.el b/sx-question-print.el index bd764da..62253a7 100644 --- a/sx-question-print.el +++ b/sx-question-print.el @@ -131,6 +131,16 @@ the editor's name." :type 'string :group 'sx-question-mode) +(defface sx-question-mode-accepted + '((t :foreground "ForestGreen" :inherit sx-question-mode-title)) + "Face used for accepted answers in the question buffer." + :group 'sx-question-mode-faces) + +(defcustom sx-question-mode-answer-accepted-title "Accepted Answer" + "Title used at the start of accepted \"Answer\" section." + :type 'string + :group 'sx-question-mode) + (defcustom sx-question-mode-comments-title " Comments" "Title used at the start of \"Comments\" sections." :type 'string @@ -188,10 +198,14 @@ DATA can represent a question or an answer." (insert sx-question-mode-header-title) (insert-text-button ;; Questions have title, Answers don't - (or .title sx-question-mode-answer-title) + (cond (.title) + ((eq .is_accepted t) sx-question-mode-answer-accepted-title) + (t sx-question-mode-answer-title)) ;; Section level 'sx-question-mode--section (if .title 1 2) 'sx-button-copy .share_link + 'face (if (eq .is_accepted t) 'sx-question-mode-accepted + 'sx-question-mode-title) :type 'sx-question-mode-title) ;; Sections can be hidden with overlays -- cgit v1.2.3 From fb70d2798482057943f4301c75ad09fb8653f27c Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Sat, 21 Feb 2015 15:23:56 -0200 Subject: Define sx-method-post-from-data --- sx-interaction.el | 23 +++-------------------- sx-method.el | 28 ++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 20 deletions(-) diff --git a/sx-interaction.el b/sx-interaction.el index 3d60cbe..e534984 100644 --- a/sx-interaction.el +++ b/sx-interaction.el @@ -230,14 +230,7 @@ Interactively, it is guessed from context at point. With the UNDO prefix argument, unfavorite the question instead." (interactive (list (sx--error-if-unread (sx--data-here 'question)) current-prefix-arg)) - (sx-assoc-let data - (sx-method-call 'questions - :id .question_id - :submethod (if undo 'favorite/undo 'favorite) - :auth 'warn - :site .site_par - :url-method 'post - :filter sx-browse-filter))) + (sx-method-post-from-data data (if undo 'favorite/undo 'favorite))) (defalias 'sx-star #'sx-favorite) @@ -268,18 +261,8 @@ DATA can be a question, answer, or comment. TYPE can be Besides posting to the api, DATA is also altered to reflect the changes." (let ((result - (sx-assoc-let data - (sx-method-call - (cond - (.comment_id "comments") - (.answer_id "answers") - (.question_id "questions")) - :id (or .comment_id .answer_id .question_id) - :submethod (concat type (unless status "/undo")) - :auth 'warn - :url-method 'post - :filter sx-browse-filter - :site .site_par)))) + (sx-method-post-from-data + data (concat type (unless status "/undo"))))) ;; The api returns the new DATA. (when (> (length result) 0) (sx--copy-data (elt result 0) data) diff --git a/sx-method.el b/sx-method.el index 9d61e60..f2e68b3 100644 --- a/sx-method.el +++ b/sx-method.el @@ -142,6 +142,34 @@ Return the entire response as a complex alist." url-method (or get-all process-function)))) +(defun sx-method-post-from-data (data &rest keys) + "Make a POST `sx-method-call', deriving parameters from DATA. +KEYS are [KEYWORD VALUE] pairs passed to `sx-method-call', except +the following which are decided by this function: + + METHOD :site and :id are derived from DATA, where METHOD is + either \"answers\", \"comments\", or \"questions\". + :url-method is post. + :filter is `sx-browse-filter'. + :auth is warn. + +As a special exception, if KEYS is a single argument, it is +assumed to be the :submethod argument." + (declare (indent 1)) + (sx-assoc-let data + (apply #'sx-method-call + (cond (.comment_id "comments") + (.answer_id "answers") + (.question_id "questions")) + :id (or .comment_id .answer_id .question_id) + :auth 'warn + :url-method 'post + :filter sx-browse-filter + :site .site_par + (if (= 1 (length keys)) + (cons :submethod keys) + keys)))) + (provide 'sx-method) ;;; sx-method.el ends here -- cgit v1.2.3 From 4788ac7655a93fbcc423feaf60eb1e887b2f41c2 Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Sat, 21 Feb 2015 15:25:11 -0200 Subject: Define sx-delete --- sx-interaction.el | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/sx-interaction.el b/sx-interaction.el index e534984..8eae96f 100644 --- a/sx-interaction.el +++ b/sx-interaction.el @@ -269,6 +269,17 @@ changes." ;; Display the changes in `data'. (sx--maybe-update-display)))) + +;;; Delete +(defun sx-delete (data &optional undo) + "Delete an object given by DATA. +DATA can be a question, answer, or comment. Interactively, it is +guessed from context at point. +With UNDO prefix argument, undelete instead." + (interactive (list (sx--error-if-unread (sx--data-here)) + current-prefix-arg)) + (sx-method-post-from-data data (if undo 'delete/undo 'delete))) + ;;; Commenting (defun sx-comment (data &optional text) -- cgit v1.2.3 From 755322c4ba96c6ce5a836cfa601da993f8031ac1 Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Sat, 21 Feb 2015 10:43:00 -0200 Subject: Configure question-mode mode-line --- sx-question-mode.el | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/sx-question-mode.el b/sx-question-mode.el index 6125416..2d06e5b 100644 --- a/sx-question-mode.el +++ b/sx-question-mode.el @@ -69,6 +69,7 @@ Returns the question buffer." (defun sx-question-mode--erase-and-print-question (data) "Erase contents of buffer and print question given by DATA. Also marks the question as read with `sx-question--mark-read'." + (sx--ensure-site data) (sx-question--mark-read data) (let ((inhibit-read-only t)) (erase-buffer) @@ -183,6 +184,34 @@ property." ": Quit") "Header-line used on the question list.") +(defconst sx-question-mode--mode-line + '(" " + ;; `sx-question-mode--data' is guaranteed to have through + ;; `sx--ensure-site' already, so we use `let-alist' instead of + ;; `sx-assoc-let' to improve performance (since the mode-line is + ;; updated a lot). + (:propertize + (:eval (let-alist sx-question-mode--data .site_par)) + face mode-line-buffer-id) + " " mode-name + " [" + "Answers: " + (:propertize + (:eval (number-to-string (let-alist sx-question-mode--data .answer_count))) + face mode-line-buffer-id) + ", " + "Stars: " + (:propertize + (:eval (number-to-string (or (let-alist sx-question-mode--data .favorite_count) 0))) + face mode-line-buffer-id) + ", " + "Views: " + (:propertize + (:eval (number-to-string (let-alist sx-question-mode--data .view_count))) + face mode-line-buffer-id) + "] ") + "Mode-line construct to use in `sx-question-mode' buffers.") + (define-derived-mode sx-question-mode special-mode "Question" "Major mode to display and navigate a question and its answers. Letters do not insert themselves; instead, they are commands. @@ -190,6 +219,7 @@ Letters do not insert themselves; instead, they are commands. \\ \\{sx-question-mode}" (setq header-line-format sx-question-mode--header-line) + (setq header-line-format sx-question-mode--mode-line) ;; Determine how to close this window. (unless (window-parameter nil 'quit-restore) (set-window-parameter -- cgit v1.2.3 From d73ba0621e1e73df155a09126ecef8df8c2d232a Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Sat, 21 Feb 2015 10:48:28 -0200 Subject: Standardize prettification of site names. --- sx-question-list.el | 4 +--- sx-question-mode.el | 9 +++++++-- sx.el | 6 ++++++ 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/sx-question-list.el b/sx-question-list.el index def490b..32bc140 100644 --- a/sx-question-list.el +++ b/sx-question-list.el @@ -397,9 +397,7 @@ Non-interactively, DATA is a question alist." (defconst sx-question-list--mode-line-format '(" " (:propertize - (:eval (mapconcat #'capitalize - (split-string sx-question-list--site "\\.") - " ")) + (:eval (sx--pretty-site-parameter sx-question-list--site)) face mode-line-buffer-id) " " mode-name ": " (:propertize sx-question-list--current-tab diff --git a/sx-question-mode.el b/sx-question-mode.el index 2d06e5b..d4b7f8d 100644 --- a/sx-question-mode.el +++ b/sx-question-mode.el @@ -191,7 +191,8 @@ property." ;; `sx-assoc-let' to improve performance (since the mode-line is ;; updated a lot). (:propertize - (:eval (let-alist sx-question-mode--data .site_par)) + (:eval (sx--pretty-site-parameter + (let-alist sx-question-mode--data .site_par))) face mode-line-buffer-id) " " mode-name " [" @@ -216,10 +217,14 @@ property." "Major mode to display and navigate a question and its answers. Letters do not insert themselves; instead, they are commands. +Don't activate this mode directly. Instead, to print a question +on the current buffer use +`sx-question-mode--erase-and-print-question'. + \\ \\{sx-question-mode}" (setq header-line-format sx-question-mode--header-line) - (setq header-line-format sx-question-mode--mode-line) + (setq mode-line-format sx-question-mode--mode-line) ;; Determine how to close this window. (unless (window-parameter nil 'quit-restore) (set-window-parameter diff --git a/sx.el b/sx.el index 73d874f..fc0af92 100644 --- a/sx.el +++ b/sx.el @@ -187,6 +187,12 @@ If ALIST doesn't have a `site' property, one is created using the ,(macroexpand `(let-alist ,alist ,@body)))) +(defun sx--pretty-site-parameter (site) + "Returned a pretty and capitalized version of string SITE." + (mapconcat #'capitalize + (split-string site "\\.") + " ")) + ;;; Utility Functions (defun sx-completing-read (&rest args) -- cgit v1.2.3 From 83538b215f75256b86987b999504a2d87d0db307 Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Sat, 21 Feb 2015 16:23:37 -0200 Subject: Make question-mode--data buffer local --- sx-question-mode.el | 1 + 1 file changed, 1 insertion(+) diff --git a/sx-question-mode.el b/sx-question-mode.el index d4b7f8d..6d62e80 100644 --- a/sx-question-mode.el +++ b/sx-question-mode.el @@ -48,6 +48,7 @@ Common values for this variable are `pop-to-buffer' and `switch-to-buffer'." (defvar sx-question-mode--data nil "The data of the question being displayed.") +(make-variable-buffer-local 'sx-question-mode--data) (defun sx-question-mode--get-window () "Return a window displaying a question, or nil." -- cgit v1.2.3 From 0b8a09dadb3f90303e7c77ac0e644bc393c00700 Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Sat, 21 Feb 2015 15:29:53 -0200 Subject: Don't print deleted posts. --- sx-interaction.el | 6 +++++- sx-question-print.el | 55 +++++++++++++++++++++++++++++----------------------- sx.el | 4 ++++ 3 files changed, 40 insertions(+), 25 deletions(-) diff --git a/sx-interaction.el b/sx-interaction.el index 8eae96f..d2129f8 100644 --- a/sx-interaction.el +++ b/sx-interaction.el @@ -278,7 +278,11 @@ guessed from context at point. With UNDO prefix argument, undelete instead." (interactive (list (sx--error-if-unread (sx--data-here)) current-prefix-arg)) - (sx-method-post-from-data data (if undo 'delete/undo 'delete))) + (sx-method-post-from-data data (if undo 'delete/undo 'delete)) + ;; Indicate to ourselves this has been deleted. + (setcdr data (cons (car data) (cdr data))) + (setcar data 'deleted) + (sx--maybe-update-display)) ;;; Commenting diff --git a/sx-question-print.el b/sx-question-print.el index abf3236..39aad20 100644 --- a/sx-question-print.el +++ b/sx-question-print.el @@ -163,6 +163,8 @@ replaced with the comment." (defun sx-question-mode--print-question (question) "Print a buffer describing QUESTION. QUESTION must be a data structure returned by `json-read'." + (when (sx--deleted-p question) + (sx-user-error "This is a deleted question")) (setq sx-question-mode--data question) ;; Clear the overlays (mapc #'delete-overlay sx--overlays) @@ -171,7 +173,9 @@ QUESTION must be a data structure returned by `json-read'." (sx-question-mode--print-section question) (sx-assoc-let question (mapc #'sx-question-mode--print-section - (cl-sort .answers sx-question-mode-answer-sort-function))) + (cl-remove-if + #'sx--deleted-p + (cl-sort .answers sx-question-mode-answer-sort-function)))) (insert "\n\n ") (insert-text-button "Write an Answer" :type 'sx-button-answer) ;; Go up @@ -182,9 +186,9 @@ QUESTION must be a data structure returned by `json-read'." "Print a section corresponding to DATA. DATA can represent a question or an answer." ;; This makes `data' accessible through `sx--data-here'. - (sx-assoc-let data - (sx--wrap-in-overlay - (list 'sx--data-here data) + (sx--wrap-in-overlay + (list 'sx--data-here data) + (sx-assoc-let data (insert sx-question-mode-header-title) (insert-text-button ;; Questions have title, Answers don't @@ -237,29 +241,32 @@ DATA can represent a question or an answer." "\n" (propertize sx-question-mode-separator 'face 'sx-question-mode-header))) - ;; Comments have their own `sx--data-here' property (so they can - ;; be upvoted too). - (when .comments - (insert "\n") - (insert-text-button - sx-question-mode-comments-title - 'face 'sx-question-mode-title-comments - 'sx-question-mode--section 3 - 'sx-button-copy .share_link - :type 'sx-question-mode-title) - (sx--wrap-in-overlay - '(sx-question-mode--section-content t) + ;; Clean up commments manually deleted. The `append' call is + ;; to ensure `comments' is a list and not a vector. + (let ((comments (cl-remove-if #'sx--deleted-p (append .comments nil)))) + (when comments (insert "\n") + (insert-text-button + sx-question-mode-comments-title + 'face 'sx-question-mode-title-comments + 'sx-question-mode--section 3 + 'sx-button-copy .share_link + :type 'sx-question-mode-title) (sx--wrap-in-overlay - '(face sx-question-mode-content-face) - (mapc #'sx-question-mode--print-comment .comments)) - ;; If there are comments, we want part of this margin to go - ;; inside them, so the button get's placed beside the - ;; "Comments" header when you hide them. + '(sx-question-mode--section-content t) + (insert "\n") + (sx--wrap-in-overlay + '(face sx-question-mode-content-face) + ;; Comments have their own `sx--data-here' property (so they can + ;; be upvoted too). + (mapc #'sx-question-mode--print-comment comments)) + ;; If there are comments, we want part of this margin to go + ;; inside them, so the button get's placed beside the + ;; "Comments" header when you hide them. + (insert " "))) + ;; If there are no comments, we have to add this margin here. + (unless comments (insert " "))) - ;; If there are no comments, we have to add this margin here. - (unless .comments - (insert " ")) (insert " ") ;; This is where the "add a comment" button is printed. (insert-text-button "Add a Comment" diff --git a/sx.el b/sx.el index 33b36b6..9ad9744 100644 --- a/sx.el +++ b/sx.el @@ -335,6 +335,10 @@ GET-FUNC and performs the actual comparison." "Return STRING with consecutive whitespace squashed together." (replace-regexp-in-string "[ \r\n]+" " " string)) +(defun sx--deleted-p (data) + "Return non-nil if DATA represents a deleted object." + (eq (car data) 'deleted)) + ;;; Printing request data (defvar sx--overlays nil -- cgit v1.2.3 From fcd6d94743a59cd51f7ac1fbed8eaba87ed2e5b6 Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Sat, 21 Feb 2015 17:28:44 -0200 Subject: Define sx--key-definitions-to-header-line --- sx.el | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/sx.el b/sx.el index 33b36b6..0ea8e4d 100644 --- a/sx.el +++ b/sx.el @@ -418,6 +418,39 @@ if ALIST contains a different string at the ?% entry." (delete-char 1))) (buffer-string)))) + +;;; Key definitions +(defun sx--key-definitions-to-header-line (definitions) + "Return a `header-line-format' from DEFINITIONS. +DEFINITIONS is a list where each element has one of the following two forms + (KEY COMMAND) + (KEY COMMAND DESCRIPTION) + +The latter are used to build the return value, the former are +ignored." + (let ((ptize (lambda (x) `(:propertize ,x face mode-line-buffer-id))) + alist out) + (dolist (it definitions) + (when (> (length it) 2) + (let* ((key (car it)) + (desc (elt it 2)) + (cell (assoc desc alist))) + (if cell (push key (cdr cell)) + (push (cons desc (list key)) alist))))) + (dolist (it alist out) + (let ((desc (car it)) + (keys (cdr it))) + (push (list " " + (cons (funcall ptize (car keys)) + (mapcar (lambda (k) `("," ,(funcall ptize k))) (cdr keys))) + (let ((match + (and (= 1 (length keys)) + (string-match (regexp-quote (car keys)) desc)))) + (if (and (numberp match) (= 0 match)) + (substring desc (length (car keys))) + (concat ":" desc)))) + out))))) + (defcustom sx-init-hook nil "Hook run when SX initializes. -- cgit v1.2.3 From 435a7d910ea930aa9055b9e91c121cc339f0e980 Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Sat, 21 Feb 2015 17:29:51 -0200 Subject: Define and use sx-question-mode--key-definitions --- sx-question-mode.el | 80 +++++++++++++++++++++++------------------------------ 1 file changed, 35 insertions(+), 45 deletions(-) diff --git a/sx-question-mode.el b/sx-question-mode.el index 6125416..dd231bc 100644 --- a/sx-question-mode.el +++ b/sx-question-mode.el @@ -161,28 +161,40 @@ property." pos 'sx-question-mode--section-content nil))) -;;; Major-mode +;;; Major-mode constants +(defconst sx-question-mode--key-definitions + '( + ("" sx-question-mode-next-section) + ("" sx-question-mode-previous-section) + ("n" sx-question-mode-next-section "Navigate") + ("p" sx-question-mode-previous-section "Navigate") + ("g" sx-question-mode-refresh) + ("v" sx-visit-externally) + ("u" sx-upvote "upvote") + ("d" sx-downvote "downvote") + ("q" quit-window) + ("SPC" scroll-up-command) + ("c" sx-comment "comment") + ("a" sx-answer "answer") + ("e" sx-edit "edit") + ("S" sx-search "Search") + ("s" sx-switchto-map "switch-to") + ("*" sx-favorite "star") + ("TAB" forward-button "Navigate") + ("" backward-button) + ("" backward-button) + ("" backward-button)) + "List of key definitions for `sx-question-mode'. +This list must follow the form described in +`sx--key-definitions-to-header-line'.") + (defconst sx-question-mode--header-line - '(" " - (:propertize "n p TAB" face mode-line-buffer-id) - ": Navigate" - " " - (:propertize "u d" face mode-line-buffer-id) - ": Up/Down Vote" - " " - (:propertize "c" face mode-line-buffer-id) - ": Comment" - " " - (:propertize "a" face mode-line-buffer-id) - ": Answer" - " " - (:propertize "e" face mode-line-buffer-id) - ": Edit" - " " - (:propertize "q" face mode-line-buffer-id) - ": Quit") + (sx--key-definitions-to-header-line + sx-question-mode--key-definitions) "Header-line used on the question list.") + +;;; Major-mode definition (define-derived-mode sx-question-mode special-mode "Question" "Major mode to display and navigate a question and its answers. Letters do not insert themselves; instead, they are commands. @@ -201,32 +213,10 @@ Letters do not insert themselves; instead, they are commands. (remove-hook 'window-configuration-change-hook 'markdown-fontify-buffer-wiki-links t)) -(mapc - (lambda (x) (define-key sx-question-mode-map - (car x) (cadr x))) - `( - ([down] sx-question-mode-next-section) - ([up] sx-question-mode-previous-section) - ("n" sx-question-mode-next-section) - ("p" sx-question-mode-previous-section) - ("g" sx-question-mode-refresh) - ("c" sx-comment) - ("v" sx-visit-externally) - ("u" sx-upvote) - ("d" sx-downvote) - ("q" quit-window) - (" " scroll-up-command) - ("a" sx-answer) - ("e" sx-edit) - ("S" sx-search) - ("s" sx-switchto-map) - ("*" sx-favorite) - (,(kbd "S-SPC") scroll-down-command) - ([backspace] scroll-down-command) - ([tab] forward-button) - (,(kbd "") backward-button) - (,(kbd "") backward-button) - (,(kbd "") backward-button))) +;; We need this quote+eval combo because `kbd' was a macro in 24.2. +(mapc (lambda (x) (eval `(define-key sx-question-mode-map + (kbd ,(car x)) #',(cadr x)))) + sx-question-mode--key-definitions) (defun sx-question-mode-refresh (&optional no-update) "Refresh currently displayed question. -- cgit v1.2.3 From f46b4c3efddf15b76fcafdd0d35969dadc2881f4 Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Sat, 21 Feb 2015 17:35:03 -0200 Subject: Define and use sx-question-list--key-definitions --- sx-question-list.el | 76 +++++++++++++++++++++++++---------------------------- 1 file changed, 36 insertions(+), 40 deletions(-) diff --git a/sx-question-list.el b/sx-question-list.el index 6bae225..6786ca2 100644 --- a/sx-question-list.el +++ b/sx-question-list.el @@ -210,19 +210,39 @@ 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) +(defconst sx-question-list--key-definitions + '( + ;; S-down and S-up would collide with `windmove'. + ("" sx-question-list-next) + ("" sx-question-list-previous) + ("RET" sx-display "Display") + ("n" sx-question-list-next "Navigate") + ("p" sx-question-list-previous "Navigate") + ("j" sx-question-list-view-next "Navigate") + ("k" sx-question-list-view-previous "Navigate") + ("N" sx-question-list-next-far) + ("P" sx-question-list-previous-far) + ("J" sx-question-list-next-far) + ("K" sx-question-list-previous-far) + ("g" sx-question-list-refresh) + ("t" sx-tab-switch "tab") + ("a" sx-ask "ask") + ("S" sx-search "Search") + ("s" sx-switchto-map "switch-to") + ("v" sx-visit-externally "visit") + ("u" sx-upvote) + ("d" sx-downvote) + ("h" sx-question-list-hide "hide") + ("m" sx-question-list-mark-read "mark-read") + ("*" sx-favorite) + ) + "List of key definitions for `sx-question-list-mode'. +This list must follow the form described in +`sx--key-definitions-to-header-line'.") + (defconst sx-question-list--header-line - '(" " - (:propertize "n p j k" face mode-line-buffer-id) - ": Navigate" - " " - (:propertize "RET" face mode-line-buffer-id) - ": View question" - " " - (:propertize "v" face mode-line-buffer-id) - ": Visit externally" - " " - (:propertize "q" face mode-line-buffer-id) - ": Quit") + (sx--key-definitions-to-header-line + sx-question-list--key-definitions) "Header-line used on the question list.") (defconst sx-question-list--order-methods @@ -333,34 +353,10 @@ into consideration. The same holds for `sx-question-list--order'. ;;; Keybinds -(mapc - (lambda (x) (define-key sx-question-list-mode-map - (car x) (cadr x))) - '( - ;; S-down and S-up would collide with `windmove'. - ([down] sx-question-list-next) - ([up] sx-question-list-previous) - ("n" sx-question-list-next) - ("p" sx-question-list-previous) - ("j" sx-question-list-view-next) - ("k" sx-question-list-view-previous) - ("N" sx-question-list-next-far) - ("P" sx-question-list-previous-far) - ("J" sx-question-list-next-far) - ("K" sx-question-list-previous-far) - ("g" sx-question-list-refresh) - ("t" sx-tab-switch) - ("a" sx-ask) - ("S" sx-search) - ("s" sx-switchto-map) - ("v" sx-visit-externally) - ("u" sx-upvote) - ("d" sx-downvote) - ("h" sx-question-list-hide) - ("m" sx-question-list-mark-read) - ("*" sx-favorite) - ([?\r] sx-display) - )) +;; We need this quote+eval combo because `kbd' was a macro in 24.2. +(mapc (lambda (x) (eval `(define-key sx-question-list-mode-map + (kbd ,(car x)) #',(cadr x)))) + sx-question-list--key-definitions) (defun sx-question-list-hide (data) "Hide question under point. -- cgit v1.2.3 From d13f4115beeda50a598f2161100a16fa21455e62 Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Sat, 21 Feb 2015 18:23:03 -0200 Subject: Bind delete to K --- sx-question-mode.el | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/sx-question-mode.el b/sx-question-mode.el index dd231bc..fd7f026 100644 --- a/sx-question-mode.el +++ b/sx-question-mode.el @@ -177,9 +177,10 @@ property." ("c" sx-comment "comment") ("a" sx-answer "answer") ("e" sx-edit "edit") - ("S" sx-search "Search") - ("s" sx-switchto-map "switch-to") + ("S" sx-search) ("*" sx-favorite "star") + ("K" sx-delete "Delete") + ("s" sx-switchto-map "switch-to") ("TAB" forward-button "Navigate") ("" backward-button) ("" backward-button) -- cgit v1.2.3 From d8968604c6d2e11e5365031759069fb5a671f688 Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Sat, 21 Feb 2015 18:25:21 -0200 Subject: Confirm before deletion --- sx-interaction.el | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/sx-interaction.el b/sx-interaction.el index d2129f8..368da09 100644 --- a/sx-interaction.el +++ b/sx-interaction.el @@ -278,11 +278,16 @@ guessed from context at point. With UNDO prefix argument, undelete instead." (interactive (list (sx--error-if-unread (sx--data-here)) current-prefix-arg)) - (sx-method-post-from-data data (if undo 'delete/undo 'delete)) - ;; Indicate to ourselves this has been deleted. - (setcdr data (cons (car data) (cdr data))) - (setcar data 'deleted) - (sx--maybe-update-display)) + (when (y-or-n-p (format "DELETE this %s? " + (let-alist data + (cond (.comment_id "comment") + (.answer_id "answer") + (.question_id "question"))))) + (sx-method-post-from-data data (if undo 'delete/undo 'delete)) + ;; Indicate to ourselves this has been deleted. + (setcdr data (cons (car data) (cdr data))) + (setcar data 'deleted) + (sx--maybe-update-display))) ;;; Commenting -- cgit v1.2.3 From 24090d48422233b31f9eef041814e99c47c2534d Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Thu, 26 Feb 2015 11:28:48 -0300 Subject: Manually string-trim for older emacsen. Fix #267 --- sx-compose.el | 6 ++---- sx.el | 5 +++++ 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/sx-compose.el b/sx-compose.el index eb5e2eb..ae13fb6 100644 --- a/sx-compose.el +++ b/sx-compose.el @@ -193,8 +193,7 @@ tags. Return a list of already inserted tags." 'noerror) (error "No Tags header found")) (save-match-data - (split-string (match-string 1) (rx (any space ",;")) - 'omit-nulls (rx space)))) + (sx--split-string (match-string 1) (rx (any space ",;"))))) (defun sx-compose--check-tags () "Check if tags in current compose buffer are valid." @@ -313,8 +312,7 @@ other keywords are read from the header " (unless (search-forward-regexp "^Tags : *\\([^[:space:]].*\\) *$" header-end 'noerror) (error "No Tags header found")) - (push (cons 'tags (split-string (match-string 1) - "[[:space:],;]" 'omit-nulls)) + (push (cons 'tags (sx--split-string (match-string 1) "[[:space:],;]")) keywords) ;; And erase the header so it doesn't get sent. (delete-region diff --git a/sx.el b/sx.el index 33b36b6..ee7f0a6 100644 --- a/sx.el +++ b/sx.el @@ -189,6 +189,11 @@ If ALIST doesn't have a `site' property, one is created using the ;;; Utility Functions +(defun sx--split-string (string &optional separators) + "Split STRING into substrings bounded by matches for SEPARATORS." + (mapcar (lambda (s) (replace-regexp-in-string "\\` +\\| +\\'" "" s)) + (split-string string separators 'omit-nulls))) + (defun sx-completing-read (&rest args) "Like `completing-read', but possibly use ido. All ARGS are passed to `completing-read' or `ido-completing-read'." -- cgit v1.2.3 From ee52cccc8f674ac6df35b44c229cd16377c38629 Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Thu, 26 Feb 2015 23:25:25 -0300 Subject: Fix quote -> code --- sx-question-print.el | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/sx-question-print.el b/sx-question-print.el index a575407..2ac30d2 100644 --- a/sx-question-print.el +++ b/sx-question-print.el @@ -443,9 +443,8 @@ END should be a marker." ;; Searching for a match has no bounds. nil 'noerror) (let ((r (copy-marker (match-beginning 0)))) - ;; The code tag is special, because it quotes everything in - ;; the middle. - (if (string= tag "quote") + ;; The code tag is special, because it quotes everything inside. + (if (string= tag "code") (progn (replace-match "`") (save-excursion (goto-char l) (insert "`"))) (replace-match "") -- cgit v1.2.3 From 382fd490f71e00cc293e2ab98cfb7b7b1c59c201 Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Fri, 27 Feb 2015 17:48:59 -0300 Subject: Print a header for closed questions. --- sx-question-print.el | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/sx-question-print.el b/sx-question-print.el index 5799c96..32a0813 100644 --- a/sx-question-print.el +++ b/sx-question-print.el @@ -136,6 +136,11 @@ the editor's name." "Face used for accepted answers in the question buffer." :group 'sx-question-mode-faces) +(defface sx-question-mode-closed + '((t :box 2 :inherit font-lock-warning-face)) + "Face used for closed question header in the question buffer." + :group 'sx-question-mode-faces) + (defcustom sx-question-mode-answer-accepted-title "Accepted Answer" "Title used at the start of accepted \"Answer\" section." :type 'string @@ -206,8 +211,10 @@ QUESTION must be a data structure returned by `json-read'." (mapc #'delete-overlay sx--overlays) (setq sx--overlays nil) ;; Print everything - (sx-question-mode--print-section question) (sx-assoc-let question + (when .closed_reason + (sx-question-mode--print-close-reason .closed_reason .closed_date)) + (sx-question-mode--print-section question) (mapc #'sx-question-mode--print-section (cl-remove-if #'sx--deleted-p @@ -218,6 +225,15 @@ QUESTION must be a data structure returned by `json-read'." (goto-char (point-min)) (sx-question-mode-next-section)) +(defun sx-question-mode--print-close-reason (reason date) + "Print a header explaining REASON and DATE. +DATE is an integer." + (insert "\n " + (propertize + (format "Closed %s ago because %s" reason (sx-time-since date)) + 'face 'sx-question-mode-closed) + "\n\n")) + (defun sx-question-mode--print-section (data) "Print a section corresponding to DATA. DATA can represent a question or an answer." -- cgit v1.2.3 From e63a29e71d645e67275cb962ec0839e61da90863 Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Fri, 27 Feb 2015 17:59:54 -0300 Subject: Add mode-line info for closed questions. --- sx-question-print.el | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/sx-question-print.el b/sx-question-print.el index 32a0813..9e726b4 100644 --- a/sx-question-print.el +++ b/sx-question-print.el @@ -201,6 +201,10 @@ type is not available, images won't work." ;;; Functions ;;;; Printing the general structure +(defconst sx-question-mode--closed-mode-line-string + '(:propertize " [CLOSED] " face font-lock-warning-face) + "String indicating closed questions in the mode-line.") + (defun sx-question-mode--print-question (question) "Print a buffer describing QUESTION. QUESTION must be a data structure returned by `json-read'." @@ -213,6 +217,7 @@ QUESTION must be a data structure returned by `json-read'." ;; Print everything (sx-assoc-let question (when .closed_reason + (add-to-list 'mode-line-format sx-question-mode--closed-mode-line-string) (sx-question-mode--print-close-reason .closed_reason .closed_date)) (sx-question-mode--print-section question) (mapc #'sx-question-mode--print-section -- cgit v1.2.3 From dc106bbfdab8ed87847218e0216cc75f5b10cad1 Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Mon, 2 Mar 2015 20:37:34 -0300 Subject: Swap date and reason --- sx-question-print.el | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sx-question-print.el b/sx-question-print.el index 9e726b4..3309f3a 100644 --- a/sx-question-print.el +++ b/sx-question-print.el @@ -235,7 +235,7 @@ QUESTION must be a data structure returned by `json-read'." DATE is an integer." (insert "\n " (propertize - (format "Closed %s ago because %s" reason (sx-time-since date)) + (format "Closed %s ago because %s" (sx-time-since date) reason) 'face 'sx-question-mode-closed) "\n\n")) -- cgit v1.2.3 From 5ca0e4fe40410555ee38bde39390a501689ef025 Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Mon, 2 Mar 2015 20:41:00 -0300 Subject: Improved grammar --- sx-question-print.el | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sx-question-print.el b/sx-question-print.el index 3309f3a..aaa3cd0 100644 --- a/sx-question-print.el +++ b/sx-question-print.el @@ -235,9 +235,9 @@ QUESTION must be a data structure returned by `json-read'." DATE is an integer." (insert "\n " (propertize - (format "Closed %s ago because %s" (sx-time-since date) reason) + (format " Closed %s ago. Reason: %s " (sx-time-since date) reason) 'face 'sx-question-mode-closed) - "\n\n")) + "\n")) (defun sx-question-mode--print-section (data) "Print a section corresponding to DATA. -- cgit v1.2.3 From 40bb7c20c733f2e734b5379d42a924ce76550e8b Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Mon, 2 Mar 2015 21:10:24 -0300 Subject: Add closed details to filter --- sx-filter.el | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sx-filter.el b/sx-filter.el index 1ccf611..5848e34 100644 --- a/sx-filter.el +++ b/sx-filter.el @@ -111,6 +111,9 @@ return the compiled filter." bounty_amount comments creation_date + closed_reason + closed_date + closed_details answers answer_count score -- cgit v1.2.3 From 5c0f594a5ee0cab2a277fe1a1541b44a492e57fb Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Mon, 2 Mar 2015 21:20:43 -0300 Subject: Add details to reason. --- sx-question-print.el | 42 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 34 insertions(+), 8 deletions(-) diff --git a/sx-question-print.el b/sx-question-print.el index aaa3cd0..4de3d72 100644 --- a/sx-question-print.el +++ b/sx-question-print.el @@ -141,6 +141,15 @@ the editor's name." "Face used for closed question header in the question buffer." :group 'sx-question-mode-faces) +(defface sx-question-mode-closed-reason + `((t :box (:line-width 2 :color ,(face-attribute 'sx-question-mode-closed + :foreground nil t)) + :inherit sx-question-mode-title)) + "Face used for closed question header in the question buffer. +Aesthetically, it's important that the color of this face's :box +attribute match the color of the face `sx-question-mode-closed'." + :group 'sx-question-mode-faces) + (defcustom sx-question-mode-answer-accepted-title "Accepted Answer" "Title used at the start of accepted \"Answer\" section." :type 'string @@ -218,7 +227,7 @@ QUESTION must be a data structure returned by `json-read'." (sx-assoc-let question (when .closed_reason (add-to-list 'mode-line-format sx-question-mode--closed-mode-line-string) - (sx-question-mode--print-close-reason .closed_reason .closed_date)) + (sx-question-mode--print-close-reason .closed_reason .closed_date .closed_details)) (sx-question-mode--print-section question) (mapc #'sx-question-mode--print-section (cl-remove-if @@ -230,14 +239,31 @@ QUESTION must be a data structure returned by `json-read'." (goto-char (point-min)) (sx-question-mode-next-section)) -(defun sx-question-mode--print-close-reason (reason date) +(defun sx-question-mode--print-close-reason (reason date &optional details) "Print a header explaining REASON and DATE. -DATE is an integer." - (insert "\n " - (propertize - (format " Closed %s ago. Reason: %s " (sx-time-since date) reason) - 'face 'sx-question-mode-closed) - "\n")) +DATE is an integer. + +DETAILS, when given is an alist further describing the close." + (let ((l (point))) + (let-alist details + (insert "\n " + (propertize (format " %s as %s, %s ago. " + (if .on_hold "Put on hold" "Closed") + reason + (sx-time-since date)) + 'face 'sx-question-mode-closed) + "\n") + (when .description + (insert (replace-regexp-in-string "<[^>]+>" "" .description) + "\n"))) + (save-excursion + (goto-char l) + (search-forward " as " nil 'noerror) + (setq l (point)) + (skip-chars-forward "^,") + (let ((ov (make-overlay l (point)))) + (overlay-put ov 'face 'sx-question-mode-closed-reason) + (push ov sx--overlays))))) (defun sx-question-mode--print-section (data) "Print a section corresponding to DATA. -- cgit v1.2.3 From 31b051e52950aa57c025f13650d4af430b6ef343 Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Wed, 4 Mar 2015 11:00:49 -0300 Subject: Hotfix paragraph-separate --- sx-question-print.el | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sx-question-print.el b/sx-question-print.el index dd1c151..634b66d 100644 --- a/sx-question-print.el +++ b/sx-question-print.el @@ -447,7 +447,7 @@ font-locks code-blocks according to mode." ;; Paragraph filling (let ((paragraph-start "\f\\|[ \t]*$\\|[ \t]*[*+-] \\|[ \t]*[0-9]+\\.[ \t]\\|[ \t]*: ") - (paragraph-separate "\\(?:[ \t\f]*\\|.* \\)$") + (paragraph-separate "\\(?:[ \t\f]*\\|.* \\)$") (adaptive-fill-first-line-regexp "\\`[ \t]*>[ \t]*?\\'") (adaptive-fill-function #'markdown-adaptive-fill-function)) (save-restriction -- cgit v1.2.3 From 809cbbbfcd21d289dff241d91af66edaf4e9fc1a Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Wed, 4 Mar 2015 15:27:25 +0000 Subject: Hotfix question printing. --- sx-question-print.el | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/sx-question-print.el b/sx-question-print.el index 634b66d..e872d01 100644 --- a/sx-question-print.el +++ b/sx-question-print.el @@ -451,20 +451,20 @@ font-locks code-blocks according to mode." (adaptive-fill-first-line-regexp "\\`[ \t]*>[ \t]*?\\'") (adaptive-fill-function #'markdown-adaptive-fill-function)) (save-restriction - (save-excursion - (narrow-to-region beg end) - ;; Compact links. - (sx-question-mode--process-links-in-buffer) - (sx-question-mode--process-html-tags (point-min) (point-max)) - ;; And now the filling and other handlings. - (goto-char (point-min)) - (while (null (eobp)) - ;; Don't fill pre blocks. - (unless (sx-question-mode--dont-fill-here) - (let ((beg (point))) - (skip-chars-forward "\r\n[:blank:]") - (forward-paragraph) - (fill-region beg (point))))))))) + (narrow-to-region beg end) + ;; Compact links. + (sx-question-mode--process-links-in-buffer) + (sx-question-mode--process-html-tags (point-min) (point-max)) + ;; And now the filling and other handlings. + (goto-char (point-min)) + (while (null (eobp)) + ;; Don't fill pre blocks. + (unless (sx-question-mode--dont-fill-here) + (let ((beg (point))) + (skip-chars-forward "\r\n[:blank:]") + (forward-paragraph) + (fill-region beg (point))))) + (goto-char (point-max))))) (defun sx-question-mode--insert-markdown (text) "Return TEXT fontified according to `markdown-mode'." -- cgit v1.2.3 From 6936df82bbc79870fe9c61479e848a0cfbe6f1a0 Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Sun, 8 Mar 2015 18:31:57 -0300 Subject: Hot fix #227. Add "-" to allowed chars in raw links --- sx-question-print.el | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sx-question-print.el b/sx-question-print.el index 634b66d..f257e45 100644 --- a/sx-question-print.el +++ b/sx-question-print.el @@ -436,7 +436,7 @@ E.g.: (group-n 4 (and (and "http" (opt "s") "://") "" (>= 2 (any lower numeric "_%")) "." - (>= 2 (any lower numeric "/._%&#?=;")))))) + (>= 2 (any lower numeric "/-._%&#?=;")))))) "Regexp matching markdown links.") (defun sx-question-mode--process-markdown-in-region (beg end) -- cgit v1.2.3 From 76353dbbfdb307f22942769567dafffc1b9e1d62 Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Sun, 8 Mar 2015 18:41:37 -0300 Subject: Raw links are shortened. Affects #227 --- sx-question-print.el | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/sx-question-print.el b/sx-question-print.el index f257e45..3a8dffc 100644 --- a/sx-question-print.el +++ b/sx-question-print.el @@ -562,9 +562,10 @@ Image links are downloaded and displayed, if (when (stringp url) (replace-match "") (sx-question-mode--insert-link - (unless image-p - (or (if sx-question-mode-pretty-links text full-text) - url)) + (cond (image-p nil) + ((and sx-question-mode-pretty-links text)) + ((not text) (sx--shorten-url url)) + (t full-text)) url) (when image-p (sx-question-mode--create-image url (- (point) 2)))))))))) @@ -606,7 +607,9 @@ URL is used as 'help-echo and 'url properties." ;; Mouse-over 'help-echo (format sx-button--link-help-echo - (propertize (sx--shorten-url url) + ;; If TEXT is a shortened url, we don't shorten URL. + (propertize (if (string-match "^https?:" (or text "")) + url (sx--shorten-url url)) 'face 'font-lock-function-name-face)) ;; For visiting and stuff. 'sx-button-url url -- cgit v1.2.3 From ad86033d0d425bae8a034bfeadc2940b38f6a733 Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Sun, 8 Mar 2015 19:06:01 -0300 Subject: Define sx-ensure-authentication --- sx-interaction.el | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/sx-interaction.el b/sx-interaction.el index 368da09..00c2a45 100644 --- a/sx-interaction.el +++ b/sx-interaction.el @@ -44,6 +44,7 @@ (require 'sx-question-mode) (require 'sx-question-list) (require 'sx-compose) +(require 'sx-cache) ;;; Using data in buffer @@ -105,6 +106,18 @@ Only fields contained in TO are copied." (setcar to (car from)) (setcdr to (cdr from))) +(defun sx-ensure-authentication () + "Signal user-error if the user refuses to authenticate. +Note that `sx-method-call' already does authentication checking. +This function is meant to be used by commands that don't +immediately perform method calls, such as `sx-ask'. This way, +the unauthenticated user will be prompted before going through +the trouble of composing an entire question." + (unless (sx-cache-get 'auth) + (if (y-or-n-p "This command requires authentication, would you like to authenticate? ") + (sx-authenticate) + (sx-user-error "This command requires authentication, please run `M-x sx-authenticate' and try again.")))) + ;;; Visiting (defun sx-visit-externally (data &optional copy-as-kill) -- cgit v1.2.3 From a9555faec10dbdde9c27888fa3892406a325d90d Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Sun, 8 Mar 2015 19:15:10 -0300 Subject: Require authentication on any of the compose commands. Affects #271 --- sx-interaction.el | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sx-interaction.el b/sx-interaction.el index 00c2a45..43b1efc 100644 --- a/sx-interaction.el +++ b/sx-interaction.el @@ -312,6 +312,7 @@ If DATA is a comment, the comment is posted as a reply to it. TEXT is a string. Interactively, it is read from the minibufer." (interactive (list (sx--error-if-unread (sx--data-here)) 'query)) + (sx-ensure-authentication) ;; When clicking the "Add a Comment" button, first arg is a marker. (when (markerp data) (setq data (sx--data-here)) @@ -409,6 +410,7 @@ OBJECT can be a question or an answer." DATA is an answer or question alist. Interactively, it is guessed from context at point." (interactive (list (sx--data-here))) + (sx-ensure-authentication) ;; If we ever make an "Edit" button, first arg is a marker. (when (markerp data) (setq data (sx--data-here))) (sx-assoc-let data @@ -457,6 +459,7 @@ If nil, use `sx--interactive-site-prompt' anyway." "Start composing a question for SITE. SITE is a string, indicating where the question will be posted." (interactive (list (sx--interactive-site-prompt))) + (sx-ensure-authentication) (let ((buffer (current-buffer))) (pop-to-buffer (sx-compose-create @@ -474,6 +477,7 @@ context at point. " ;; probaby hit the button by accident. (interactive (list (sx--error-if-unread (sx--data-here 'question)))) + (sx-ensure-authentication) ;; When clicking the "Write an Answer" button, first arg is a marker. (when (markerp data) (setq data (sx--data-here))) (let ((buffer (current-buffer))) -- cgit v1.2.3 From 62e8ced2af638f3646ae53b0472b4ec3284a9cc7 Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Sun, 8 Mar 2015 19:21:49 -0300 Subject: Don't erase question headers when sending. Fix #271 --- sx-compose.el | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/sx-compose.el b/sx-compose.el index ae13fb6..e3f9c00 100644 --- a/sx-compose.el +++ b/sx-compose.el @@ -294,15 +294,14 @@ Keywords meant to be used in `sx-method-call'. `body' is read as the `buffer-string'. If IS-QUESTION is non-nil, other keywords are read from the header " + (goto-char (point-min)) `(,@(when is-question (let ((inhibit-point-motion-hooks t) - (inhibit-read-only t) (header-end (next-single-property-change (point-min) 'sx-compose-separator)) keywords) ;; Read the Title. - (goto-char (point-min)) (unless (search-forward-regexp "^Title: *\\(.*\\) *$" header-end 'noerror) (error "No Title header found")) @@ -314,13 +313,11 @@ other keywords are read from the header " (error "No Tags header found")) (push (cons 'tags (sx--split-string (match-string 1) "[[:space:],;]")) keywords) - ;; And erase the header so it doesn't get sent. - (delete-region - (point-min) - (next-single-property-change - header-end 'sx-compose-separator)) + ;; And move past the header so it doesn't get sent. + (goto-char (next-single-property-change + header-end 'sx-compose-separator)) keywords)) - (body . ,(buffer-string)))) + (body . ,(buffer-substring-no-properties (point) (point-max))))) (defun sx-compose--get-buffer-create (site data) "Get or create a buffer for use with `sx-compose-mode'. -- cgit v1.2.3 From 6dc7c3c1ae61188e46fff00f9880a5ee27905df3 Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Sun, 22 Mar 2015 19:51:57 +0000 Subject: Shorten separator --- sx-question-print.el | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sx-question-print.el b/sx-question-print.el index 51c047c..f8d7d17 100644 --- a/sx-question-print.el +++ b/sx-question-print.el @@ -126,7 +126,7 @@ the editor's name." :group 'sx-question-mode) (defcustom sx-question-mode-separator - (concat (make-string 80 ?_) "\n") + (concat (make-string 69 ?_) "\n") "Separator used between header and body." :type 'string :group 'sx-question-mode) -- cgit v1.2.3 From 043675e4522634a2e10e58f8ffdc710518154813 Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Thu, 26 Mar 2015 20:00:24 +0000 Subject: Change a failed sx-open-link from message to error. This way sx-button-follow-link can catch it. --- sx-button.el | 2 +- sx-interaction.el | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/sx-button.el b/sx-button.el index d32314d..2292ac9 100644 --- a/sx-button.el +++ b/sx-button.el @@ -105,7 +105,7 @@ usually part of a code-block." ;; whatever we thought it was. (condition-case nil (sx-open-link url) ;; When it errors, don't blame the user, just visit externally. - (error (sx-visit-externally url))))) + (error (browse-url url))))) ;;; Help-echo definitions diff --git a/sx-interaction.el b/sx-interaction.el index 43b1efc..8754c54 100644 --- a/sx-interaction.el +++ b/sx-interaction.el @@ -167,9 +167,8 @@ Element can be a question, answer, or comment." (question (sx-display-question (sx-question-get-question .site_par .id) 'focus)) - (t (sx-message - "Don't know how to open this link, please file a bug report: %s" - link) + (t (error "Don't know how to open this link, please file a bug report: %s" + link) nil)))))) -- cgit v1.2.3 From ce6600296875e17009e2b85c769956cde55a2d53 Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Thu, 26 Mar 2015 20:27:26 +0000 Subject: Fix link regexp --- sx-question-print.el | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/sx-question-print.el b/sx-question-print.el index f8d7d17..7f312eb 100644 --- a/sx-question-print.el +++ b/sx-question-print.el @@ -433,10 +433,11 @@ E.g.: (and (opt "!") "[" (group-n 1 (1+ (not (any "]")))) "]" (or (and "(" (group-n 2 (1+ (not (any ")")))) ")") (and "[" (group-n 3 (1+ (not (any "]")))) "]"))) - (group-n 4 (and (and "http" (opt "s") "://") "" + (group-n 4 (and "http" (opt "s") "://" (>= 2 (any lower numeric "_%")) "." - (>= 2 (any lower numeric "/-._%&#?=;")))))) + (>= 2 (any lower numeric "_%")) + (* (any lower numeric "-/._%&#?=;")))))) "Regexp matching markdown links.") (defun sx-question-mode--process-markdown-in-region (beg end) -- cgit v1.2.3