diff options
-rw-r--r-- | sx-question-mode.el | 2 | ||||
-rw-r--r-- | sx-question-print.el | 175 | ||||
-rw-r--r-- | sx-request.el | 47 |
3 files changed, 132 insertions, 92 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 9f51b4c..7244a6a 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 - (sx-question-mode--fill-and-fontify - ;; We fill with three spaces at the start, so the comment is - ;; slightly indented. - (concat " " (sx--squash-whitespace .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 " " (sx--squash-whitespace .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 @@ -398,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. @@ -432,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. diff --git a/sx-request.el b/sx-request.el index d7fd058..3bcea21 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. |