aboutsummaryrefslogtreecommitdiff
path: root/sx-question-mode.el
diff options
context:
space:
mode:
authorArtur Malabarba <bruce.connor.am@gmail.com>2014-11-14 23:11:13 +0000
committerArtur Malabarba <bruce.connor.am@gmail.com>2014-11-14 23:11:13 +0000
commita85c9c3331dd1c2bf265e684a39d8841cc6905b3 (patch)
tree72d077d83a6046f329c8ae1c52d25155239939cf /sx-question-mode.el
parent66a3fc78b49bc6ea87fb19ef718210447e3df695 (diff)
Finish link implementation
Diffstat (limited to 'sx-question-mode.el')
-rw-r--r--sx-question-mode.el170
1 files changed, 111 insertions, 59 deletions
diff --git a/sx-question-mode.el b/sx-question-mode.el
index 09872a4..28a786f 100644
--- a/sx-question-mode.el
+++ b/sx-question-mode.el
@@ -24,6 +24,8 @@
;;; Code:
(require 'markdown-mode)
+(eval-when-compile
+ (require 'rx))
(require 'sx)
(require 'sx-question)
@@ -179,11 +181,14 @@ Second \"%s\" is replaced with the comment."
:type 'string
:group 'sx-question-mode)
+(defcustom sx-question-mode-pretty-links t
+ "If non-nil, markdown links are displayed in a compact form."
+ :type 'boolean
+ :group 'sx-question-mode)
+
;;; Printing a question's content
;;;; Functions
-;; This is where most of the work is still left to be done! Need to
-;; insert more data from QUESTION.
(defun sx-question-mode--print-question (question)
"Print a buffer describing QUESTION.
QUESTION must be a data structure returned by `json-read'."
@@ -262,63 +267,7 @@ DATA can represent a question or an answer."
'(face sx-question-mode-content-face)
(mapc #'sx-question-mode--print-comment .comments))))))
-(defvar sx-question-mode-bullet-appearance
- (propertize (if (char-displayable-p ?•) " •" " *")
- 'face 'markdown-list-face)
- "String to be displayed as the bullet of markdown list items.")
-
-;; (rx "[" (group-n 1 (1+ (not (any "]")))) "]"
-;; (or (group-n 2 (and "(" (1+ (not (any ")"))) ")"))
-;; (group-n 3 (and "[" (1+ (not (any "]"))) "]"))))
-
-(defvar sx-question-mode--removed-keywords
- '("<\\(\\(\\sw\\|\\s_\\|\\s.\\)+@\\(\\sw\\|\\s_\\|\\s.\\)+\\)>"
- "\\(?:acap\\|cid\\|da\\(?:ta\\|v\\)\\|f\\(?:ax\\|ile\\|tp\\)\\|gopher\\|https?\\|imap\\|ldap\\|m\\(?:ailto\\|id\\|odem\\)\\|n\\(?:ews\\|fs\\|ntp\\)\\|p\\(?:op\\|rospero\\)\\|rtsp\\|s\\(?:ervice\\|ip\\)\\|t\\(?:el\\(?:net\\)?\\|ip\\)\\|urn\\|vemmi\\|wais\\):[^] \n <>,;() ]+"
- "\\(<\\)\\(\\(?:acap\\|cid\\|da\\(?:ta\\|v\\)\\|f\\(?:ax\\|ile\\|tp\\)\\|gopher\\|https?\\|imap\\|ldap\\|m\\(?:ailto\\|id\\|odem\\)\\|n\\(?:ews\\|fs\\|ntp\\)\\|p\\(?:op\\|rospero\\)\\|rtsp\\|s\\(?:ervice\\|ip\\)\\|t\\(?:el\\(?:net\\)?\\|ip\\)\\|urn\\|vemmi\\|wais\\):[^] \n <>,;()]+\\)\\(>\\)"
- "\\(!\\)?\\(\\[\\([^]^][^]]*\\|\\)\\]\\)\\((\\([^)]*?\\)\\(?:\\s-+\\(\"[^\"]*\"\\)\\)?)\\)"
- "\\(!\\)?\\(\\[\\([^]^][^]]*\\|\\)\\]\\)[ ]?\\(\\[\\([^]]*?\\)\\]\\)"
- "^ \\{0,3\\}\\(\\[[^\n]+?\\]\\):\\s *\\(.*?\\)\\s *\\( \"[^\"]*\"$\\|$\\)")
- "Elements whose font-lock-keywords are blocked from the question buffer.")
-
-(defun sx-question-mode--fill-and-fontify (text)
- "Fill TEXT according to `markdown-mode' and return it."
- (with-temp-buffer
- (erase-buffer)
- (insert text)
- (markdown-mode)
- (font-lock-mode -1)
- (setq markdown-mode-font-lock-keywords
- (cl-remove-if (lambda (x) (member (car-safe x) sx-question-mode--removed-keywords))
- (copy-sequence markdown-mode-font-lock-keywords)))
- (font-lock-add-keywords
- nil
- `(("^ *\\(\\*\\|\\+\\|-\\|\\) "
- 1 '(face nil display ,sx-question-mode-bullet-appearance) prepend)
- ("\\[\\(?1:[^]]+\\)]\\(?:\\(?2:([^)]+)\\)\\|\\(?3:\\[[^]]+]\\)\\)"
- 1 '(face link)
- 2 '(face nil display "")
- 3 '(face nil display ""))
- ;; Highlight usernames.
- ("\\(?: \\|^\\)\\(@\\(?:\\sw\\|\\s_\\)+\\)\\_>"
- 1 font-lock-builtin-face)))
- ;; Do something here
- (font-lock-fontify-region (point-min) (point-max))
- (goto-char (point-min))
- (while (null (eobp))
- ;; Don't fill pre blocks.
- (unless (sx-question-mode--move-over-pre)
- (fill-paragraph)
- (forward-paragraph)))
- (buffer-string)))
-
-(defun sx-question-mode--move-over-pre ()
- "Non-nil if paragraph at point can be filled."
- (markdown-match-pre-blocks
- (save-excursion
- (skip-chars-forward "\r\n[:blank:]")
- (point))))
-
-(defun sx-question-mode--propertized-display-name (author)
+(defun sx-question-mode--propertize-display-name (author)
"Return display_name of AUTHOR with `sx-question-mode-author' face."
(sx-assoc-let author
(propertize .display_name
@@ -365,6 +314,109 @@ HEADER is given `sx-question-mode-header' face, and value is given FACE.
(propertize (pop args) 'face (pop args)))))
+;;;;; Font-locking the content
+(defvar sx-question-mode-bullet-appearance
+ (propertize (if (char-displayable-p ?•) " •" " *")
+ 'face 'markdown-list-face)
+ "String to be displayed as the bullet of markdown list items.")
+
+(defun sx-question-mode--fill-and-fontify (text)
+ "Fill TEXT according to `markdown-mode' and return it."
+ (with-temp-buffer
+ (erase-buffer)
+ (insert text)
+ (markdown-mode)
+ (font-lock-mode -1)
+ (when sx-question-mode-bullet-appearance
+ (font-lock-add-keywords ;; Bullet items.
+ nil
+ `(("^ *\\(\\*\\|\\+\\|-\\|\\) "
+ 1 '(face nil display ,sx-question-mode-bullet-appearance) prepend))))
+ (font-lock-add-keywords ;; Highlight usernames.
+ nil
+ `(("\\(?: \\|^\\)\\(@\\(?:\\sw\\|\\s_\\)+\\)\\_>"
+ 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--move-over-pre)
+ (skip-chars-forward "\r\n[:blank:]")
+ (fill-paragraph)
+ (forward-paragraph)))
+ (buffer-string)))
+
+(defvar sx-question-mode--link-regexp
+ ;; Done at compile time.
+ (rx "[" (group-n 1 (1+ (not (any "]")))) "]"
+ (or (and "(" (group-n 2 (1+ (not (any ")")))) ")")
+ (and "[" (group-n 3 (1+ (not (any "]")))) "]")))
+ "Regexp matching markdown links.")
+
+(defun sx-question-mode--process-links-in-buffer ()
+ "Turn all markdown links in this buffer into compact format."
+ (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)
+ (sx-question-mode-find-reference
+ (match-string-no-properties 3)
+ text))))
+ (replace-match
+ (sx-question-mode--propertize-link
+ (if sx-question-mode-pretty-links
+ text
+ (match-string-no-properties 0))
+ url)
+ :fixedcase :literal nil 0)))))
+
+(defun sx-question-mode--propertize-link (text url)
+ "Return a link propertized version of string TEXT.
+URL is used as help-echo and as "
+ (propertize
+ text
+ 'face 'link
+ 'help-echo url
+ 'url url
+ 'follow-link t
+ 'mouse-face 'highlight
+ 'action #'sx-question-mode-follow-link
+ 'point-entered
+ (lambda (&rest _)
+ (message "%s%s" (propertize "URL: " 'face 'minibuffer-prompt) url))))
+
+(defun sx-question-mode-follow-link (&optional pos)
+ "Follow link at POS or point"
+ (interactive)
+ (browse-url
+ (or (get-text-property (or pos (point)) 'url)
+ (error "No url under point: %s" (or pos (point))))))
+
+(defun sx-question-mode-find-reference (id &optional id2)
+ "Find url identified by reference ID in current buffer.
+If ID is nil, use ID2 instead."
+ (save-excursion
+ (save-match-data
+ (goto-char (point-min))
+ (when (search-forward-regexp
+ (format "^\\s-*\\[\\(%s\\)]:\\s-+\\(?2:[^ ]+\\)"
+ (or id id2))
+ nil t)
+ (match-string-no-properties 2)))))
+
+(defun sx-question-mode--move-over-pre ()
+ "Non-nil if paragraph at point can be filled."
+ (markdown-match-pre-blocks
+ (save-excursion
+ (skip-chars-forward "\r\n[:blank:]")
+ (point))))
+
+
;;; Movement commands
;; Sections are headers placed above a question's content or an
;; answer's content, or above the list of comments. They are