aboutsummaryrefslogtreecommitdiff
path: root/sx.el
diff options
context:
space:
mode:
Diffstat (limited to 'sx.el')
-rw-r--r--sx.el213
1 files changed, 142 insertions, 71 deletions
diff --git a/sx.el b/sx.el
index 955dc68..e0609a7 100644
--- a/sx.el
+++ b/sx.el
@@ -92,46 +92,70 @@ with a `link' property)."
"Convert string LINK into data that can be displayed."
(let ((result (list (cons 'site_par (sx--site link)))))
;; Try to strip a question or answer ID
- (when (or
+ (when (cond ;; Comment
+ ((or ;; If there's a #commentNUMBER_NUMBER at the end, we
+ ;; know it's a comment with that ID.
+ (string-match (rx "#comment" (group-n 1 (+ digit))
+ "_" (+ digit) string-end)
+ link)
+ ;; From inbox items
+ (string-match (rx "/posts/comments/"
+ ;; Comment ID
+ (group-n 1 (+ digit))
+ ;; Optional stuff at the end
+ (or (and (any "?#") (* any)) "")
+ string-end)
+ link))
+ (push '(type . comment) result))
;; Answer
- (and (or (string-match
- ;; From 'Share' button
- (rx "/a/"
- ;; Question ID
- (group (+ digit))
- ;; User ID
- "/" (+ digit)
- ;; Answer ID
- (group (or (sequence "#" (* any)) ""))
- string-end) link)
- (string-match
- ;; From URL
- (rx "/questions/" (+ digit) "/"
- (+ (not (any "/"))) "/"
- ;; User ID
- (optional (group (+ digit)))
- (optional "/")
- (group (or (sequence "#" (* any)) ""))
- string-end) link))
- (push '(type . answer) result))
+ ((or ;; If there's a #NUMBER at the end, we know it's an
+ ;; answer with that ID.
+ (string-match (rx "#" (group-n 1 (+ digit)) string-end) link)
+ ;; From 'Share' button
+ (string-match (rx "/a/"
+ ;; Answer ID
+ (group-n 1 (+ digit)) "/"
+ ;; User ID
+ (+ digit)
+ ;; Garbage at the end
+ (optional (and (any "?#") (* any)))
+ string-end)
+ link)
+ ;; From URL
+ (string-match (rx "/questions/" (+ digit) "/"
+ ;; Question title
+ (+ (not (any "/"))) "/"
+ ;; Answer ID. If this is absent, we match on
+ ;; Question clause below.
+ (group-n 1 (+ digit))
+ (opt "/")
+ ;; Garbage at the end
+ (optional (and (any "?#") (* any)))
+ string-end)
+ link))
+ (push '(type . answer) result))
;; Question
- (and (or (string-match
- ;; From 'Share' button
- (rx "/q/"
- ;; Question ID
- (group (+ digit))
- ;; User ID
- (optional "/" (+ digit))
- ;; Answer or Comment ID
- (group (or (sequence "#" (* any)) ""))
- string-end) link)
- (string-match
- ;; From URL
- (rx "/questions/"
- ;; Question ID
- (group (+ digit))
- "/") link))
- (push '(type . question) result)))
+ ((or ;; From 'Share' button
+ (string-match (rx "/q/"
+ ;; Question ID
+ (group-n 1 (+ digit))
+ ;; User ID
+ (optional "/" (+ digit))
+ ;; Garbage at the end
+ (optional (and (any "?#") (* any)))
+ string-end)
+ link)
+ ;; From URL
+ (string-match (rx "/questions/"
+ ;; Question ID
+ (group-n 1 (+ digit)) "/"
+ ;; Optional question title
+ (optional (+ (not (any "/"))) "/")
+ ;; Garbage at the end
+ (optional (and (any "?#") (* any)))
+ string-end)
+ link))
+ (push '(type . question) result)))
(push (cons 'id (string-to-number (match-string-no-properties 1 link)))
result))
result))
@@ -259,6 +283,48 @@ whenever BODY evaluates to nil."
:filter (lambda (&optional _)
(when (progn ,@body) ,def)))))
+(defun sx--goto-property-change (prop &optional direction)
+ "Move forward to the next change of text-property PROP.
+Return the new value of PROP at point.
+
+If DIRECTION is negative, move backwards instead."
+ (let ((func (if (and (numberp direction)
+ (< direction 0))
+ #'previous-single-property-change
+ #'next-single-property-change))
+ (limit (if (and (numberp direction)
+ (< direction 0))
+ (point-min) (point-max))))
+ (goto-char (funcall func (point) prop nil limit))
+ (get-text-property (point) prop)))
+
+(defun sx--find-in-buffer (type id)
+ "Move point to an object of TYPE and ID.
+That is, move forward from beginning of buffer until
+`sx--data-here' is an object of type TYPE with the respective id
+ID. If point is left at the of a line, move over the line break.
+
+TYPE is either question, answer, or comment.
+ID is an integer."
+ (let* ((id-symbol (cl-case type
+ (answer 'answer_id)
+ (comment 'comment_id)
+ (question 'question_id)))
+ (pos
+ (save-excursion
+ (goto-char (point-min))
+ (while (not (or (eobp)
+ (let ((data (sx--data-here type t)))
+ (and data
+ (= id (or (cdr (assq id-symbol data))))))))
+ (forward-char 1))
+ (point))))
+ (if (equal pos (point-max))
+ (sx-message "Can't find the specified %s" type)
+ (goto-char pos)
+ (when (looking-at-p "$")
+ (forward-char 1)))))
+
(defmacro sx--create-comparator (name doc compare-func get-func)
"Define a new comparator called NAME with documentation DOC.
COMPARE-FUNC is a function that takes the return value of
@@ -305,39 +371,6 @@ Return the result of BODY."
(push ov sx--overlays))
result))
-(defconst sx--ascii-replacement-list
- '(("[:space:]" . "")
- ("àåáâäãåą" . "a")
- ("èéêëę" . "e")
- ("ìíîïı" . "i")
- ("òóôõöøőð" . "o")
- ("ùúûüŭů" . "u")
- ("çćčĉ" . "c")
- ("żźž" . "z")
- ("śşšŝ" . "s")
- ("ñń" . "n")
- ("ýÿ" . "y")
- ("ğĝ" . "g")
- ("ř" . "r")
- ("ł" . "l")
- ("đ" . "d")
- ("ß" . "ss")
- ("Þ" . "th")
- ("ĥ" . "h")
- ("ĵ" . "j")
- ("^[:ascii:]" . ""))
- "List of replacements to use for non-ascii characters.
-Used to convert user names into @mentions.")
-
-(defun sx--user-@name (user)
- "Get the `display_name' of USER prepended with @.
-In order to correctly @mention the user, all whitespace is
-removed from the display name before it is returned."
- (sx-assoc-let user
- (when (stringp .display_name)
- (concat "@" (sx--recursive-replace
- sx--ascii-replacement-list .display_name)))))
-
(defun sx--recursive-replace (alist string)
"Replace each car of ALIST with its cdr in STRING."
(if alist
@@ -348,6 +381,44 @@ removed from the display name before it is returned."
(format "[%s]" (car kar)) (cdr kar) string)))
string))
+(defun sx-format-replacements (format alist &optional property-alist)
+ "Use FORMAT-STRING to format the values in ALIST.
+ALIST is a list with elements of the form (CHAR . STRING).
+The value is a copy of FORMAT-STRING, but with certain constructs
+replaced by text as given by ALIST.
+
+The construct is a `%' character followed by any other character.
+The replacement is the STRING corresponding to CHAR in ALIST. In
+addition, if CHAR is also the car of an element in
+PROPERTY-ALIST, the cdr of that element should be a list of text
+properties which will be applied on the replacement.
+
+The %% construct is special, it is replaced with a single %, even
+if ALIST contains a different string at the ?% entry."
+ (let ((alist (cons '(?% . "%") alist)))
+ (with-temp-buffer
+ (insert format)
+ (goto-char (point-min))
+ (while (search-forward-regexp
+ (rx "%" (group-n 1 (* (any "-+ #0-9.")))) nil 'noerror)
+ (let* ((char (char-after))
+ ;; Understand flags
+ (flag (match-string 1))
+ (val (cdr-safe (assq char alist))))
+ (unless val
+ (error "Invalid format character: `%%%c'" char))
+ ;; Insert first, to preserve text properties.
+ (insert-and-inherit (format (concat "%" flag "s") val))
+ (when property-alist
+ (add-text-properties (match-end 0) (point)
+ (cdr-safe (assq char property-alist))))
+ ;; Delete the specifier body.
+ (delete-region (match-beginning 0)
+ (match-end 0))
+ ;; Delete `char-after'.
+ (delete-char 1)))
+ (buffer-string))))
+
(defcustom sx-init-hook nil
"Hook run when SX initializes.