diff --git a/ChangeLog b/ChangeLog index 8ef87d3c..aabf6e9c 100644 --- a/ChangeLog +++ b/ChangeLog @@ -52,6 +52,15 @@ (twittering-make-clickable-status-datum): do not redefine `source'. + * twittering-mode.el: Add text properties to tweets on rendering. + (twittering-http-get-default-sentinel): do not call + `twittering-make-clickable-status-datum'. + (twittering-make-clickable-status-datum): removed. + (twittering-make-string-with-user-name-property): new function. + (twittering-make-string-with-source-property): new function. + (twittering-make-fontified-tweet-text): new function. + (twittering-generate-format-table): add text properties. + 2011-01-23 Tadashi MATSUO * test/test-twittering-mode.el: Add some tests for the format diff --git a/twittering-mode.el b/twittering-mode.el index c3ba6dc4..352e1b70 100644 --- a/twittering-mode.el +++ b/twittering-mode.el @@ -1981,14 +1981,13 @@ the server when the HTTP status code equals to 400 or 403." (statuses (let ((xmltree (twittering-xml-parse-region (point-min) (point-max)))) - (mapcar 'twittering-make-clickable-status-datum - (cond - ((null xmltree) - nil) - ((eq 'search (car spec)) - (twittering-atom-xmltree-to-status xmltree)) - (t - (twittering-xmltree-to-status xmltree))))))) + (cond + ((null xmltree) + nil) + ((eq 'search (car spec)) + (twittering-atom-xmltree-to-status xmltree)) + (t + (twittering-xmltree-to-status xmltree)))))) (when statuses (let ((new-statuses (twittering-add-statuses-to-timeline-data statuses spec)) @@ -4274,134 +4273,6 @@ If `twittering-password' is nil, read it from the minibuffer." (user-location location t) (user-description description t)))))))))) -(defun twittering-make-clickable-status-datum (status) - (flet ((assq-get (item seq) - (cdr (assq item seq)))) - (let ((user-name (assq-get 'user-name status)) - (id (assq-get 'id status)) - (text (assq-get 'text status)) - (source (assq-get 'source status)) - (source-uri (assq-get 'source-uri status)) - (created-at (assq-get 'created-at status)) - (truncated (assq-get 'truncated status)) - (in-reply-to-status-id (assq-get 'in-reply-to-status-id status)) - (in-reply-to-screen-name (assq-get 'in-reply-to-screen-name status)) - (user-id (assq-get 'user-id status)) - (user-name (assq-get 'user-name status)) - (user-screen-name (assq-get 'user-screen-name status)) - (user-location (assq-get 'user-location status)) - (user-description (assq-get 'user-description status)) - (user-profile-image-url (assq-get 'user-profile-image-url status)) - (user-url (assq-get 'user-url status)) - (user-protected (assq-get 'user-protected status))) - - ;; make user-name clickable - (add-text-properties - 0 (length user-name) - `(mouse-face highlight - keymap ,twittering-mode-on-uri-map - uri ,(twittering-get-status-url user-screen-name) - screen-name-in-text ,user-screen-name - goto-spec ,(twittering-string-to-timeline-spec - user-screen-name) - face twittering-username-face) - user-name) - - ;; make user-screen-name clickable - (add-text-properties - 0 (length user-screen-name) - `(mouse-face highlight - keymap ,twittering-mode-on-uri-map - uri ,(twittering-get-status-url user-screen-name) - screen-name-in-text ,user-screen-name - goto-spec ,(twittering-string-to-timeline-spec - user-screen-name) - face twittering-username-face) - user-screen-name) - - ;; make hashtag, listname, screenname, and URI in text clickable - (let ((pos 0) - (regexp-str - (concat twittering-regexp-hash - "\\([a-zA-Z0-9_-]+\\)\\|" - twittering-regexp-atmark - "\\([a-zA-Z0-9_-]+/[a-zA-Z0-9_-]+\\)\\|" - twittering-regexp-atmark - "\\([a-zA-Z0-9_-]+\\)\\|" - "\\(https?://[-_.!~*'()a-zA-Z0-9;/?:@&=+$,%#]+\\)"))) - (while - (and (string-match regexp-str text pos) - (let ((next-pos (match-end 0)) - (hashtag (match-string-no-properties 1 text)) - (listname (match-string-no-properties 2 text)) - (screenname (match-string-no-properties 3 text)) - (uri (match-string-no-properties 4 text)) - beg end prop) - (if (eq next-pos pos) - nil - (cond - (hashtag - (setq beg (match-beginning 0) ;; XXX: not 1. - end (match-end 1)) - (let ((spec (twittering-string-to-timeline-spec - (concat "#" hashtag))) - (url (twittering-get-search-url - (concat "#" hashtag)))) - (setq prop - `(mouse-face - highlight - keymap ,twittering-mode-on-uri-map - uri ,url goto-spec ,spec - face twittering-username-face)))) - (listname - (setq beg (match-beginning 2) - end (match-end 2) - prop `(mouse-face - highlight - keymap ,twittering-mode-on-uri-map - uri ,(twittering-get-status-url listname) - goto-spec - ,(twittering-string-to-timeline-spec - listname) - face twittering-username-face))) - (screenname - (setq beg (match-beginning 3) - end (match-end 3) - prop `(mouse-face - highlight - keymap ,twittering-mode-on-uri-map - uri ,(twittering-get-status-url - screenname) - screen-name-in-text ,screenname - goto-spec - ,(twittering-string-to-timeline-spec - screenname) - face twittering-uri-face))) - (uri - (setq beg (match-beginning 4) - end (match-end 4) - prop `(mouse-face - highlight - keymap ,twittering-mode-on-uri-map - uri ,uri - face twittering-uri-face))) - (t - (setq prop nil))) - (when prop - (add-text-properties beg end prop text)) - (setq pos next-pos)))))) - - ;; make source pretty and clickable - (add-text-properties - 0 (length source) - `(mouse-face highlight - keymap ,twittering-mode-on-uri-map - uri ,source-uri - face twittering-uri-face - source ,source) - source) - status))) - (defun twittering-xmltree-to-status (xmltree) (setq xmltree (cond @@ -5247,6 +5118,110 @@ following symbols; (concat result skipped-string)) )) +(defun twittering-make-string-with-user-name-property (str status) + (if str + (let* ((user-screen-name (cdr (assq 'user-screen-name status))) + (uri (twittering-get-status-url user-screen-name)) + (spec (twittering-string-to-timeline-spec user-screen-name))) + (propertize str + 'mouse-face 'highlight + 'keymap twittering-mode-on-uri-map + 'uri uri + 'screen-name-in-text user-screen-name + 'goto-spec spec + 'face 'twittering-username-face)) + "")) + +(defun twittering-make-string-with-source-property (str status) + (if str + (let ((uri (cdr (assq 'source-uri status)))) + (propertize str + 'mouse-face 'highlight + 'keymap twittering-mode-on-uri-map + 'uri uri + 'face 'twittering-uri-face + 'source str)) + "")) + +(defun twittering-make-fontified-tweet-text (str) + (let* ((regexp-list + `(;; Hashtag + (hashtag . ,(concat twittering-regexp-hash "\\([a-zA-Z0-9_-]+\\)")) + ;; @USER/LIST + (list-name . ,(concat twittering-regexp-atmark + "\\([a-zA-Z0-9_-]+/[a-zA-Z0-9_-]+\\)")) + ;; @USER + (screen-name + . ,(concat twittering-regexp-atmark "\\([a-zA-Z0-9_-]+\\)")) + ;; URI + (uri . "\\(https?://[-_.!~*'()a-zA-Z0-9;/?:@&=+$,%#]+\\)"))) + (regexp-str (mapconcat 'cdr regexp-list "\\|")) + (pos 0) + (str (copy-sequence str))) + (while (string-match regexp-str str pos) + (let* ((entry + ;; Find matched entries. + (let ((rest regexp-list) + (counter 1)) + (while (and rest (not (match-string counter str))) + (setq rest (cdr rest)) + (setq counter (1+ counter))) + (when rest + (list (caar rest) + (match-beginning counter) + (match-string counter str))))) + (sym (elt entry 0)) + (matched-beg (elt entry 1)) + (matched-str (elt entry 2)) + (beg (if (memq sym '(list-name screen-name)) + ;; Properties are added to the matched part only. + ;; The prefixes `twittering-regexp-atmark' will not + ;; be highlighted. + matched-beg + (match-beginning 0))) + (end (match-end 0)) + (properties + (cond + ((eq sym 'hashtag) + (let* ((hashtag matched-str) + (spec + (twittering-string-to-timeline-spec + (concat "#" hashtag))) + (url (twittering-get-search-url (concat "#" hashtag)))) + (list + 'mouse-face 'highlight + 'keymap twittering-mode-on-uri-map + 'uri url + 'goto-spec spec + 'face 'twittering-username-face))) + ((eq sym 'list-name) + (let ((list-name matched-str)) + (list + 'mouse-face 'highlight + 'keymap twittering-mode-on-uri-map + 'uri (twittering-get-status-url list-name) + 'goto-spec (twittering-string-to-timeline-spec list-name) + 'face 'twittering-username-face))) + ((eq sym 'screen-name) + (let ((screen-name matched-str)) + (list + 'mouse-face 'highlight + 'keymap twittering-mode-on-uri-map + 'uri (twittering-get-status-url screen-name) + 'screen-name-in-text screen-name + 'goto-spec (twittering-string-to-timeline-spec screen-name) + 'face 'twittering-uri-face))) + ((eq sym 'uri) + (let ((uri matched-str)) + (list + 'mouse-face 'highlight + 'keymap twittering-mode-on-uri-map + 'uri uri + 'face 'twittering-uri-face)))))) + (add-text-properties beg end properties str) + (setq pos end))) + str)) + (defun twittering-generate-format-table (status-sym prefix-sym) `(("%" . "%") ("}" . "}") @@ -5255,7 +5230,9 @@ following symbols; "...")) ("c" . (cdr (assq 'created-at ,status-sym))) ("d" . (cdr (assq 'user-description ,status-sym))) - ("f" . (cdr (assq 'source ,status-sym))) + ("f" . + (twittering-make-string-with-source-property + (cdr (assq 'source ,status-sym)) ,status-sym)) ("i" . (when (and twittering-icon-mode window-system) (let ((url @@ -5312,10 +5289,16 @@ following symbols; (or (cdr (assq 'original-user-screen-name ,status-sym)) ""))) (unless (string= "" retweeted-by) (concat " (retweeted by " retweeted-by ")")))) - ("S" . (cdr (assq 'user-name ,status-sym))) - ("s" . (cdr (assq 'user-screen-name ,status-sym))) - ("T" . (cdr (assq 'text ,status-sym))) - ("t" . (cdr (assq 'text ,status-sym))) + ("S" . + (twittering-make-string-with-user-name-property + (cdr (assq 'user-name ,status-sym)) ,status-sym)) + ("s" . + (twittering-make-string-with-user-name-property + (cdr (assq 'user-screen-name ,status-sym)) ,status-sym)) + ("T" . + (twittering-make-fontified-tweet-text (cdr (assq 'text ,status-sym)))) + ("t" . + (twittering-make-fontified-tweet-text (cdr (assq 'text ,status-sym)))) ("u" . (cdr (assq 'user-url ,status-sym))))) (defun twittering-generate-formater-for-first-spec (format-str status-sym prefix-sym)