emacs-emojify

fork of https://github.com/iqbalansari/emacs-emojify
Log | Files | Refs | LICENSE

commit eacd56bd6a275f7188faf29f1de8ec4c509204bd
parent f791ad4c741218668dad4ba8db65161da9a5b010
Author: Iqbal Ansari <iqbalansari02@yahoo.com>
Date:   Wed,  7 Sep 2016 23:58:01 +0530

Merge branch 'develop'

Diffstat:
MREADME.org | 86+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------
Memojify.el | 365++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------
Atest/assets/lambda.png | 0
Atest/assets/neckbeard.png | 0
Atest/assets/trollface.png | 0
Mtest/emojify-test.el | 241+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------
Mtest/test-helper.el | 40++++++++++++++++++++++++++++++++++++++--
7 files changed, 578 insertions(+), 154 deletions(-)

diff --git a/README.org b/README.org @@ -20,6 +20,8 @@ - [[#configuring-the-texts-that-are-displayed-as-emojis][Configuring the texts that are displayed as emojis]] - [[#controlling-behaviour-when-point-enters-an-emoji][Controlling behaviour when point enters an emoji]] - [[#controlling-behaviour-when-mouse-hovers-over-an-emoji][Controlling behaviour when mouse hovers over an emoji]] + - [[#custom-emojis][Custom emojis]] + - [[#integration-with-prettify-symbol-mode][Integration with prettify-symbol-mode]] - [[#known-issues][Known issues]] - [[#contributing][Contributing]] - [[#thanks][Thanks]] @@ -78,16 +80,22 @@ (add-hook 'after-init-hook #'global-emojify-mode) #+END_SRC - In programming modes only emojis in string and comments are displayed. + Additionally ~emojify~ can integrate with ~prettify-symbols-mode~ displaying + the symbols it recognizes as emojis. This is disabled by default, see + [[#integration-with-prettify-symbol-mode][Integration with prettify-symbol-mode]]. + + In programming modes ~github~ and ~ascii~ style emojis are displayed only in + string and comments, ~unicode~ emojis are displayed in all contexts. The + emojis of type ~prettify-symbol~ are displayed only in code context. *** Searching emojis The command ~emojify-apropos-emoji~ can be used to display emojis that match given regexp/apropos pattern. The results are displayed in a specialized buffer, where ~w~ or ~c~ can be used to copy emojis to the kill ring. - *NOTE* - You might notice that some of the emojis returned by this search - are not displayed by ~emojify~, this might happen if you newer emoji data - but old set of image. Download the latest emoji image using + *NOTE* - You might notice that some of the emojis prompted in the above + command are not displayed by ~emojify~, this might happen if you have newer + emoji data but old set of images. Download the latest emoji image using ~emojify-download-emoji~ and set ~emojify-emoji-set~ to the downloaded set. *** Inserting emojis @@ -97,8 +105,8 @@ depending on you preference. *NOTE* - You might notice that some of the emojis prompted in the above - command are not displayed by ~emojify~, this might happen if you newer emoji - data but old set of image. Download the latest emoji image using + command are not displayed by ~emojify~, this might happen if you have newer + emoji data but old set of images. Download the latest emoji image using ~emojify-download-emoji~ and set ~emojify-emoji-set~ to the downloaded set. ** Customizations @@ -111,9 +119,11 @@ one parameter the list of styles that you want to be displayed. The possible styles are - - ascii - Display only plain ascii emojis - - unicode - Display only unicode emojis - - github - Display only github style emojis + - ascii - Display only plain ascii emojis + - unicode - Display only unicode emojis + - github - Display only github style emojis + - prettify-symbol - Display symbols from ~prettify-symbols-alist~ + (which emojify recognizes) as emojis *** Configuring how emojis are displayed By default emojis are displayed using images. However you can instruct emojify @@ -198,12 +208,62 @@ When mouse hovers over a emoji, the underlying text is displayed in a help popup. This behaviour can be disabled by setting ~emojify-show-help~ to nil. +*** Custom emojis + You can specify custom emojis using the ~emojify-user-emojis~ variable. You + need to set it to an alist where first element of cons is the text to be + displayed as emoji, while the second element of the cons is an alist + containing data about the emoji. + + The inner alist should have atleast + + 1) "name" - The name of the emoji + 2) "style" - This should be one of "github", "ascii" or "github" + Note: "prettify-symbol" is not a valid style for custom emojis + + Additionally the alist should contain one of (see [[#configuring-the-types-of-emojis-displayed][emojify-display-style]]) + 1) "unicode" - The replacement for the provided emoji for "unicode" display style + 2) "image" - The replacement for the provided emoji for "image" display style. + This should be the *absolute* path to the image + 3) "ascii" - The replacement for the provided emoji for "ascii" display style + + It is best to set this variable before you load ~emojify~, in case you set + this variable after loading ~emojify~ run the function + ~emojify-set-emoji-data~ to recalculate emoji data. + + User emojis take precedence over default emojis so the above mechanism can + also be used to override the default emojis + +**** Example + Below is an example of setting up custom emojis. Assuming that the custom + images are at ~\~/.emacs.d/emojis/trollface.png~ and + ~\~/.emacs.d/emojis/neckbeard.png~, you instruct ~emojify~ to display ~:trollface:~ + and ~:neckbeard:~ as :trollface: and :neckbeard: + + #+BEGIN_SRC emacs-lisp + (setq emojify-user-emojis '((":trollface:" . (("name" . "Troll Face") + ("image" . "~/.emacs.d/emojis/trollface.png") + ("style" . "github"))) + (":neckbeard:" . (("name" . "Neckbeard") + ("image" . "~/.emacs.d/emojis/neckbeard.png") + ("style" . "github"))))) + + ;; If emojify is already loaded refresh emoji data + (when (featurep 'emojify) + (emojify-set-emoji-data)) + #+END_SRC + +** Integration with prettify-symbol-mode + Emojify can be configured to display symbols from ~prettify-symbols-alist~ as + emojis. To do so add ~prettify-symbol~ to [[#configuring-the-types-of-emojis-displayed][emojify-emoji-styles]]. You might + need re-enable ~prettify-symbols-mode~ if you are already using it, ~emojify~ + will automatically display any symbols from ~prettify-symbols-alist~ that it + recognizes as emojis. You can use [[#custom-emojis][emojify-user-emojis]] to teach ~emojify~ + about it any symbol it does not recognize yet. + ** Known issues - Emojis are not properly updated after customizing ~emojify-display-style~ or - ~emojify-prog-contexts~. This would be fixed in future. For time being you - will be fine as long as you set these variables before ~emojify~ has - loaded. - - There is currently no support for custom emojis/images. I plan to add this in future. + ~emojify-program-contexts~. For time being you will be fine as long as you + set these variables before ~emojify~ has loaded. ** Contributing Code as well as documentation contributions are welcome. Development on diff --git a/emojify.el b/emojify.el @@ -331,30 +331,9 @@ can customize `emojify-inhibit-major-modes' and ;; Customizations to control display of emojis -(defvar emojify-emojis nil - "Data about the emojis, this contains only the emojis that come with emojify.") - -(defvar emojify-regexps nil - "Regexp to match text to emojified.") - -(defvar emojify-emoji-style-change-hooks nil +(defvar emojify-emoji-style-change-hook nil "Hooks run when emoji style changes.") -(defun emojify-set-emoji-data () - "Read the emoji data for STYLES and set the regexp required to search them." - (setq emojify-emojis (let ((json-array-type 'list) - (json-object-type 'hash-table)) - (json-read-file emojify-emoji-json))) - - ;; Construct emojify-regexps in descending order of length, this is important - ;; so that larger emojis are searched first and get precedence over smaller - ;; ones (see also `emojify-display-emojis-in-region') - (setq emojify-regexps (seq-map #'regexp-opt - (seq-partition (sort (ht-keys emojify-emojis) - (lambda (string1 string2) (> (length string1) - (length string2)))) - 1000)))) - ;;;###autoload (defun emojify-set-emoji-styles (styles) "Set the type of emojis that should be displayed. @@ -366,7 +345,7 @@ STYLES is the styles emoji styles that should be used, see `emojify-emoji-styles (setq-default emojify-emoji-styles styles) - (run-hooks 'emojify-emoji-style-change-hooks)) + (run-hooks 'emojify-emoji-style-change-hook)) (defcustom emojify-emoji-styles '(ascii unicode github) @@ -374,13 +353,15 @@ STYLES is the styles emoji styles that should be used, see `emojify-emoji-styles These can have one of the following values -`ascii' - Display only ascii emojis for example ';)' -`unicode' - Display only unicode emojis for example '😉' -`github' - Display only github style emojis for example ':wink:'" +`ascii' - Display only ascii emojis for example ';)' +`unicode' - Display only unicode emojis for example '😉' +`github' - Display only github style emojis for example ':wink:' +`prettify-symbol' - Display only emojis extracted from `prettify-symbols-alist'" :type '(set (const :tag "Display only ascii emojis" ascii) (const :tag "Display only github emojis" github) - (const :tag "Display only unicode codepoints" unicode)) + (const :tag "Display only unicode codepoints" unicode) + (const :tag "Display only emojis extracted from `prettify-symbols-alist'" prettify-symbol)) :set (lambda (_ value) (emojify-set-emoji-styles value)) :group 'emojify) @@ -444,11 +425,15 @@ This returns non-nil if the region is valid according to `emojify-program-contex (t 'code)))) (and (memql context emojify-program-contexts) (if (equal context 'code) - ;; If context if code display only unicode emojis - (and (string= (ht-get emoji "style") "unicode") - (memql 'unicode emojify-emoji-styles)) - ;; No need to check for non-code context - t))))) + ;; If context is code display only unicode emojis + (or (and (string= (ht-get emoji "style") "unicode") + (memql 'unicode emojify-emoji-styles)) + (and (string= (ht-get emoji "style") "prettify-symbol") + (bound-and-true-p prettify-symbols-mode) + (memql 'prettify-symbol emojify-emoji-styles))) + ;; Display any other style in all contexts except for + ;; prettify-symbol emoji + (not (string= (ht-get emoji "style") "prettify-symbol"))))))) (defun emojify-inside-org-src-p (point) "Return non-nil if POINT is inside `org-mode' src block. @@ -604,6 +589,119 @@ To understand WINDOW, STRING and POS see the function documentation for ;; Core functions and macros +(defcustom emojify-user-emojis nil + "User specified custom emojis. + +This is an alist where first element of cons is the text to be displayed as +emoji, while the second element of the cons is an alist containing data about +the emoji. + +The inner alist should have atleast (not all keys are strings) + +`name' - The name of the emoji +`style' - This should be one of \"github\", \"ascii\" or \"github\" + (see `emojify-emoji-styles') + Note: \"prettify-symbol\" is not a valid style for custom emojis + +The alist should contain one of (see `emojify-display-style') +`unicode' - The replacement for the provided emoji for \"unicode\" display style +`image' - The replacement for the provided emoji for \"image\" display style. + This should be the absolute path to the image +`ascii' - The replacement for the provided emoji for \"ascii\" display style + +Example - +The following assumes that custom images are at ~/.emacs.d/emojis/trollface.png and +~/.emacs.d/emojis/neckbeard.png + +'((\":troll:\" . ((\"name\" . \"Troll\") + (\"image\" . \"~/.emacs.d/emojis/trollface.png\") + (\"style\" . \"github\"))) + (\":neckbeard:\" . ((\"name\" . \"Neckbeard\") + (\"image\" . \"~/.emacs.d/emojis/neckbeard.png\") + (\"style\" . \"github\"))))") + +(defvar emojify-emojis nil + "Data about the emojis, this contains only the emojis that come with emojify.") + +(defvar emojify-pretty-symbol-emojis nil + "Emojis extracted from `prettify-symbols-alist'.") + +(defvar emojify--user-emojis nil + "User specified custom emojis.") + +(defvar emojify-regexps nil + "Regexp to match text to emojified.") + +(make-variable-buffer-local 'emojify-regexps) +(make-variable-buffer-local 'emojify-pretty-symbol-emojis) + +(defun emojify-create-emojify-emojis () + "Create `emojify-emojis' if needed." + (unless emojify-emojis + (emojify-set-emoji-data))) + +(defun emojify-get-emoji (emoji) + "Get data for given EMOJI. + +This first looks for the emoji in `emojify--user-emojis', +`emojify-pretty-symbol-emojis' and finally in `emojify-emojis'." + (or (when emojify--user-emojis + (ht-get emojify--user-emojis emoji)) + (when emojify-pretty-symbol-emojis + (ht-get emojify-pretty-symbol-emojis emoji)) + (ht-get emojify-emojis emoji))) + +(defun emojify-emojis-each (function) + "Execute FUNCTION for each emoji. + +This first runs function for `emojify--user-emojis', +`emojify-pretty-symbol-emojis' and then `emojify-emojis'." + (when emojify--user-emojis + (ht-each function emojify--user-emojis)) + (when emojify-pretty-symbol-emojis + (ht-each function emojify-pretty-symbol-emojis)) + (ht-each function emojify-emojis)) + +(defun emojify--verify-user-emojis (emojis) + "Verify the EMOJIS in correct user format." + (seq-every-p (lambda (emoji) + (and (assoc "name" (cdr emoji)) + ;; Make sure style is present is only one of + ;; "unicode", "ascii" and "github". + (assoc "style" (cdr emoji)) + (seq-position '("unicode" "ascii" "github") + (cdr (assoc "style" (cdr emoji)))) + (or (assoc "unicode" (cdr emoji)) + (assoc "image" (cdr emoji)) + (assoc "ascii" (cdr emoji))))) + emojis)) + +(defun emojify-set-emoji-data () + "Read the emoji data for STYLES and set the regexp required to search them." + (setq-default emojify-emojis (let ((json-array-type 'list) + (json-object-type 'hash-table)) + (json-read-file emojify-emoji-json))) + + ;; Construct emojify-regexps in descending order of length, this is important + ;; so that larger emojis are searched first and get precedence over smaller + ;; ones (see also `emojify-display-emojis-in-region') + (setq-default emojify-regexps (seq-map #'regexp-opt + (seq-partition (sort (ht-keys emojify-emojis) + (lambda (string1 string2) (> (length string1) + (length string2)))) + 1000))) + (when emojify-user-emojis + (if (emojify--verify-user-emojis emojify-user-emojis) + ;; Create entries for user emojis + (let ((emoji-pairs (mapcar (lambda (user-emoji) + (cons (car user-emoji) + (ht-from-alist (cdr user-emoji)))) + emojify-user-emojis))) + (setq-default emojify--user-emojis (ht-from-alist emoji-pairs)) + (setq-default emojify-regexps (cons (regexp-opt (mapcar #'car emoji-pairs)) + emojify-regexps))) + (message "[emojify] User emojis are not in correct format ignoring them.")))) + (defvar emojify-emoji-keymap (let ((map (make-sparse-keymap))) (define-key map [remap delete-char] #'emojify-delete-emoji-forward) @@ -744,22 +842,23 @@ selection, but for some reason it does not work well." DATA holds the emoji data, BEG and END delimit the region where emoji will be displayed." - (let* ((image-file (expand-file-name (ht-get data "image") - (emojify-image-dir))) - (image-type (intern (upcase (file-name-extension image-file))))) - (when (file-exists-p image-file) - (create-image image-file - ;; use imagemagick if available and supports PNG images - ;; (allows resizing images) - (when (and (fboundp 'imagemagick-types) - (memq image-type (imagemagick-types))) - 'imagemagick) - nil - :ascent 'center - :heuristic-mask t - :background (emojify--get-image-background beg end) - ;; no-op if imagemagick is not available - :height (emojify-default-font-height))))) + (when (ht-get data "image") + (let* ((image-file (expand-file-name (ht-get data "image") + (emojify-image-dir))) + (image-type (intern (upcase (file-name-extension image-file))))) + (when (file-exists-p image-file) + (create-image image-file + ;; use imagemagick if available and supports PNG images + ;; (allows resizing images) + (when (and (fboundp 'imagemagick-types) + (memq image-type (imagemagick-types))) + 'imagemagick) + nil + :ascent 'center + :heuristic-mask t + :background (emojify--get-image-background beg end) + ;; no-op if imagemagick is not available + :height (emojify-default-font-height)))))) (defun emojify--get-unicode-display (data _beg _end) "Get the display text property to display the emoji as an unicode character. @@ -807,7 +906,7 @@ TODO: Skip emojifying if region is already emojified." (match-end (match-end 0)) (match (match-string-no-properties 0)) (buffer (current-buffer)) - (emoji (ht-get emojify-emojis match))) + (emoji (emojify-get-emoji match))) (when (and (memql (intern (ht-get emoji "style")) emojify-emoji-styles) ;; Skip displaying this emoji if the its bounds are @@ -901,10 +1000,12 @@ BEG and END are the beginning and end of the region respectively" Redisplay emojis in the visible region if BEG and END are not specified" (let* ((area (emojify--get-relevant-region)) (beg (or beg (car area))) - (end (or end (cdr area)))) - (emojify-execute-ignoring-errors-unless-debug - (emojify-undisplay-emojis-in-region beg end) - (emojify-display-emojis-in-region beg end)))) + (end (or end (cdr area))) + (region-too-big (> end (+ beg (* 10 (- (window-end) (window-start))))))) + (unless region-too-big + (emojify-execute-ignoring-errors-unless-debug + (emojify-undisplay-emojis-in-region beg end) + (emojify-display-emojis-in-region beg end))))) (defun emojify-after-change-extend-region-function (beg end _len) "Extend the region to be emojified. @@ -1030,6 +1131,41 @@ of the window. DISPLAY-START corresponds to the new start of the window." +;; Integration with prettify-symbols-mode + +(defun emojify-populate-emojis-from-prettify-symbol-mode () + "Populate additional text to display from `prettify-symbols-alist'." + (when (and (seq-position emojify-emoji-styles 'prettify-symbol) + (bound-and-true-p prettify-symbols-alist)) + + (let (new-regexps emojis) + (dolist (pretty-symbol prettify-symbols-alist) + (let* ((symbol-text (make-string 1 (cdr pretty-symbol))) + (emojify-symbol-data (emojify-get-emoji symbol-text))) + (when emojify-symbol-data + (push (cons (car pretty-symbol) + (ht-from-alist (list (cons "style" "prettify-symbol") + (cons "image" (gethash "image" emojify-symbol-data)) + (cons "unicode" symbol-text) + (cons "name" (format "Pretty represenation for '%s'" (car pretty-symbol)))))) + emojis) + (push (car pretty-symbol) new-regexps)))) + + (when emojis + (setq emojify-pretty-symbol-emojis (ht-from-alist emojis))) + + (when new-regexps + (let ((re (regexp-opt new-regexps 'symbols))) + (setq emojify-regexps (cons re (delete re emojify-regexps)))))))) + +(defun emojify-handle-prettify-symbol-mode () + "Redisplay emojis after `prettify-symbol-mode' is enabled/disabled." + (when (bound-and-true-p prettify-symbols-mode) + (emojify-populate-emojis-from-prettify-symbol-mode)) + (emojify-redisplay-emojis-in-region)) + + + ;; Lazy image downloading (defvar emojify--refused-image-download-p nil @@ -1101,8 +1237,7 @@ run the command `emojify-download-emoji'"))) "Turn on `emojify-mode' in current buffer." ;; Calculate emoji data if needed - (unless emojify-emojis - (emojify-set-emoji-data)) + (emojify-create-emojify-emojis) (when (emojify-buffer-p (current-buffer)) ;; Download images if not available @@ -1123,7 +1258,14 @@ run the command `emojify-download-emoji'"))) (add-hook 'window-scroll-functions #'emojify-update-visible-emojis-background-after-window-scroll t t) ;; Redisplay visible emojis when emoji style changes - (add-hook 'emojify-emoji-style-change-hooks #'emojify-redisplay-emojis-in-region))) + (add-hook 'emojify-emoji-style-change-hook #'emojify-redisplay-emojis-in-region) + + ;; Add symbols from prettify symbol mode, to displayed emojis and redisplay + ;; emojis when prettify-symbols-mode is activated + (add-hook 'prettify-symbols-mode-hook #'emojify-handle-prettify-symbol-mode) + + ;; Repopulate emojis from prettify-symbols-alist when style changes + (add-hook 'emojify-emoji-style-change-hook #'emojify-handle-prettify-symbol-mode))) (defun emojify-turn-off-emojify-mode () "Turn off `emojify-mode' in current buffer." @@ -1141,8 +1283,12 @@ run the command `emojify-download-emoji'"))) (remove-hook 'deactivate-mark-hook #'emojify-update-visible-emojis-background-after-command t) (remove-hook 'window-scroll-functions #'emojify-update-visible-emojis-background-after-window-scroll t) + ;; Disable display of symbols + (remove-hook 'prettify-symbols-mode-hook #'emojify-handle-prettify-symbol-mode) + ;; Remove style change hooks - (remove-hook 'emojify-emoji-style-change-hooks #'emojify-redisplay-emojis-in-region)) + (remove-hook 'emojify-emoji-style-change-hook #'emojify-redisplay-emojis-in-region) + (remove-hook 'emojify-emoji-style-change-hook #'emojify-handle-prettify-symbol-mode)) ;;;###autoload (define-minor-mode emojify-mode @@ -1163,6 +1309,8 @@ run the command `emojify-download-emoji'"))) ;; Searching and inserting emojis +(defvar emojify-apropos-buffer-name "*Apropos Emojis*") + (defun emojify-apropos-quit () "Delete the window displaying Emoji search results." (interactive) @@ -1190,6 +1338,7 @@ run the command `emojify-download-emoji'"))) (define-key map "p" #'previous-line) (define-key map "r" #'isearch-backward) (define-key map "s" #'isearch-forward) + (define-key map "g" #'emojify-apropos-emoji) (define-key map ">" 'end-of-buffer) (define-key map "<" 'beginning-of-buffer) @@ -1211,12 +1360,32 @@ run the command `emojify-download-emoji'"))) (put 'emojify-apropos-mode 'mode-class 'special) +(defvar emojify--apropos-last-query nil) +(make-variable-buffer-local 'emojify--apropos-last-query) + +(defun emojify-apropos-read-pattern () + "Read apropos pattern with INITIAL-INPUT as the initial input. + +Borrowed from apropos.el" + (let ((pattern (read-string (concat "Search for emoji (word list or regexp): ") + emojify--apropos-last-query))) + (if (string-equal (regexp-quote pattern) pattern) + (or (split-string pattern "[ \t]+" t) + (if (fboundp 'user-error) + (apply #'user-error "No word list given") + (apply #'error "No word list given"))) + pattern))) + ;;;###autoload (defun emojify-apropos-emoji (pattern) "Show Emojis that match PATTERN." - (interactive (list (apropos-read-pattern "emoji"))) + (interactive (list (emojify-apropos-read-pattern))) + + (emojify-create-emojify-emojis) - (let (matching-emojis sorted-emojis) + (let ((in-apropos-buffer-p (equal major-mode 'emojify-apropos-mode)) + matching-emojis + sorted-emojis) (unless (listp pattern) (setq pattern (list pattern))) @@ -1225,18 +1394,17 @@ run the command `emojify-download-emoji'"))) ;; description (apropos-parse-pattern pattern) - ;; Collection matching emojis in a list (list score emoji emoji-data) + ;; Collect matching emojis in a list of (list score emoji emoji-data) ;; elements, where score is the proximity of the emoji to given pattern ;; calculated using `apropos-score-str' - (maphash (lambda (key value) - (when (or (string-match apropos-regexp key) - (string-match apropos-regexp (gethash "name" value))) - (push (list (max (apropos-score-str key) - (apropos-score-str (gethash "name" value))) - key - value) - matching-emojis))) - emojify-emojis) + (emojify-emojis-each (lambda (key value) + (when (or (string-match apropos-regexp key) + (string-match apropos-regexp (ht-get value "name"))) + (push (list (max (apropos-score-str key) + (apropos-score-str (ht-get value "name"))) + key + value) + matching-emojis)))) ;; Sort the emojis by the proximity score (setq sorted-emojis (mapcar #'cdr @@ -1244,26 +1412,29 @@ run the command `emojify-download-emoji'"))) (lambda (emoji1 emoji2) (> (car emoji1) (car emoji2)))))) - (when (get-buffer "*Apropos Emojis*") - (kill-buffer "*Apropos Emojis*")) - ;; Insert result in apropos buffer and display it - (with-current-buffer (get-buffer-create "*Apropos Emojis*") - (erase-buffer) - (insert (propertize "Emojis matching" 'face 'apropos-symbol)) - (insert (format " - \"%s\"" (mapconcat 'identity pattern " "))) - (insert "\n\nUse `c' or `w' to copy emoji on current line\n\n") - (dolist (emoji sorted-emojis) - (insert (format "%s - %s (%s)" - (car emoji) - (gethash "name" (cadr emoji)) - (gethash "style" (cadr emoji)))) - (insert "\n")) - (goto-char (point-min)) - (emojify-apropos-mode) - (setq-local line-spacing 7)) - - (display-buffer (get-buffer "*Apropos Emojis*")))) + (with-current-buffer (get-buffer-create emojify-apropos-buffer-name) + (let ((inhibit-read-only t) + (query (mapconcat 'identity pattern " "))) + (erase-buffer) + (insert (propertize "Emojis matching" 'face 'apropos-symbol)) + (insert (format " - \"%s\"" query)) + (insert "\n\nUse `c' or `w' to copy emoji on current line\nUse `g' to rerun apropos\n\n") + (dolist (emoji sorted-emojis) + (insert (format "%s - %s (%s)" + (car emoji) + (ht-get (cadr emoji) "name") + (ht-get (cadr emoji) "style"))) + (insert "\n")) + (goto-char (point-min)) + (forward-line (1- 6)) + (emojify-apropos-mode) + (setq emojify--apropos-last-query (concat query " ")) + (setq-local line-spacing 7))) + + (select-window (display-buffer (get-buffer emojify-apropos-buffer-name) + (when in-apropos-buffer-p + (cons #'display-buffer-same-window nil)))))) ;;;###autoload (defun emojify-insert-emoji () @@ -1271,21 +1442,21 @@ run the command `emojify-download-emoji'"))) This respects the `emojify-emoji-styles' variable." (interactive) + (emojify-create-emojify-emojis) (let* ((emojify-in-insertion-command-p t) (styles (mapcar #'symbol-name emojify-emoji-styles)) (line-spacing 7) (completion-ignore-case t) (candidates (let (emojis) - (maphash (lambda (key value) - (when (member (gethash "style" value) styles) - (push (format "%s - %s (%s)" - key - (gethash "name" value) - (gethash "style" value)) - emojis))) - emojify-emojis) + (emojify-emojis-each (lambda (key value) + (when (seq-position styles (ht-get value "style")) + (push (format "%s - %s (%s)" + key + (ht-get value "name") + (ht-get value "style")) + emojis)))) emojis))) - (insert (car (split-string (completing-read "Apropos Emoji: " candidates) + (insert (car (split-string (completing-read "Insert Emoji: " candidates) " "))))) diff --git a/test/assets/lambda.png b/test/assets/lambda.png Binary files differ. diff --git a/test/assets/neckbeard.png b/test/assets/neckbeard.png Binary files differ. diff --git a/test/assets/trollface.png b/test/assets/trollface.png Binary files differ. diff --git a/test/emojify-test.el b/test/emojify-test.el @@ -63,45 +63,69 @@ (emojify-redisplay-emojis-in-region) (emojify-tests-should-not-be-emojified (point-min)))) +(ert-deftest emojify-test-custom-emojis () + :tags '(core custom-images) + (let ((emojify-user-emojis emojify-test-custom-emojis)) + (emojify-set-emoji-data) + (emojify-tests-with-emojified-static-buffer ":neckbeard: +:troll:" + (emojify-tests-should-be-emojified (point-min)) + (should (equal (get-text-property (point) 'emojify-buffer) (current-buffer))) + (should (= (get-text-property (point-min) 'emojify-beginning) (point-min-marker))) + (should (= (get-text-property (point) 'emojify-end) (line-end-position 1))) + (should (equal (get-text-property (point) 'emojify-text) ":neckbeard:")) + + (emojify-tests-should-be-emojified (line-beginning-position 2)) + (should (equal (get-text-property (line-beginning-position 2) 'emojify-buffer) (current-buffer))) + (should (= (get-text-property (line-beginning-position 2) 'emojify-beginning) (line-beginning-position 2))) + (should (= (get-text-property (line-beginning-position 2) 'emojify-end) (point-max-marker))) + (should (equal (get-text-property (line-beginning-position 2) 'emojify-text) ":troll:"))))) + (ert-deftest emojify-tests-mixed-emoji-test () :tags '(core mixed) - (emojify-tests-with-emojified-static-buffer "😉\n:D\nD:\n:smile:" - (emojify-tests-should-be-emojified (point-min)) - (emojify-tests-should-be-emojified (line-beginning-position 2)) - (emojify-tests-should-be-emojified (line-beginning-position 3)) - (emojify-tests-should-be-emojified (line-beginning-position 4)))) - -;; The after-change tests stopped working after moving to JIT lock :unamused: -;; (ert-deftest emojify-tests-emojifying-on-comment-uncomment () -;; :tags '(core after-change) -;; (emojify-tests-with-emojified-buffer ":smile:\n:)" -;; (emacs-lisp-mode) -;; (emojify-redisplay-emojis-in-region) -;; (emojify-mode +1) -;; (emojify-tests-should-not-be-emojified (line-beginning-position)) -;; (emojify-tests-should-not-be-emojified (line-beginning-position 2)) - -;; (comment-region (point-min) (point-max)) -;; (emojify-tests-should-be-emojified (+ 3 (line-beginning-position))) -;; (emojify-tests-should-be-emojified (+ 3 (line-beginning-position 2))) - -;; (uncomment-region (point-min) (point-max)) -;; (emojify-tests-should-not-be-emojified (line-beginning-position)) -;; (emojify-tests-should-not-be-emojified (line-beginning-position 2)))) - -;; (ert-deftest emojify-tests-emojifying-on-typing () -;; :tags '(core after-change) -;; (emojify-tests-with-emojified-buffer "" -;; (emacs-lisp-mode) -;; (emojify-redisplay-emojis-in-region) -;; (emojify-mode +1) -;; (emojify-insert-string "; :)") -;; (emojify-tests-should-be-emojified 4) -;; (newline) -;; (emojify-insert-string "; :smile") -;; (emojify-tests-should-not-be-emojified (+ 4 (line-beginning-position))) -;; (emojify-insert-string ":") -;; (emojify-tests-should-be-emojified (+ 4 (line-beginning-position))))) + (let ((emojify-user-emojis emojify-test-custom-emojis)) + (emojify-set-emoji-data) + (emojify-tests-with-emojified-static-buffer "😉\n:D\nD:\n:smile:\n:neckbeard:" + (emojify-tests-should-be-emojified (point-min)) + (emojify-tests-should-be-emojified (line-beginning-position 2)) + (emojify-tests-should-be-emojified (line-beginning-position 3)) + (emojify-tests-should-be-emojified (line-beginning-position 4)) + (emojify-tests-should-be-emojified (line-beginning-position 5))))) + +(ert-deftest emojify-tests-emojifying-on-comment-uncomment () + :tags '(core after-change) + (emojify-tests-with-emojified-buffer ":smile:\n:)" + (emacs-lisp-mode) + (emojify-redisplay-emojis-in-region) + (emojify-mode +1) + (emojify-tests-should-not-be-emojified (line-beginning-position)) + (emojify-tests-should-not-be-emojified (line-beginning-position 2)) + + (comment-region (point-min) (point-max)) + (emojify-redisplay) + (emojify-tests-should-be-emojified (+ 3 (line-beginning-position))) + (emojify-tests-should-be-emojified (+ 3 (line-beginning-position 2))) + + (uncomment-region (point-min) (point-max)) + (emojify-redisplay) + (emojify-tests-should-not-be-emojified (line-beginning-position)) + (emojify-tests-should-not-be-emojified (line-beginning-position 2)))) + +(ert-deftest emojify-tests-emojifying-on-typing () + :tags '(core after-change) + (emojify-tests-with-emojified-buffer "" + (emacs-lisp-mode) + (emojify-redisplay-emojis-in-region) + (emojify-mode +1) + (emojify-insert-string "; :)") + (emojify-redisplay) + (emojify-tests-should-be-emojified 4) + (newline) + (emojify-insert-string "; :smile") + (emojify-tests-should-not-be-emojified (+ 4 (line-beginning-position))) + (emojify-insert-string ":") + (emojify-redisplay) + (emojify-tests-should-be-emojified (+ 4 (line-beginning-position))))) (ert-deftest emojify-tests-emoji-uncovering () :tags '(behaviour point-motion) @@ -114,6 +138,12 @@ :tags '(behaviour point-motion) (emojify-tests-with-emojified-buffer " :)" (with-mock + ;; Since emojify checks that there is no message being displayed + ;; before echoing the emoji, we need to stub out current-message + ;; too otherwise emojify does not echo the message since messages + ;; from other tests are being displayed + (unless noninteractive + (stub current-message => nil)) (mock (message ":)")) (setq emojify-point-entered-behaviour 'echo) (goto-char (1+ (point-min))) @@ -132,35 +162,55 @@ (ert-deftest emojify-tests-emojify-setting-styles () :tags '(styles github ascii) - (emojify-tests-with-emojified-static-buffer ":) 😄 :smile:" + (emojify-tests-with-emojified-static-buffer ":) 😄 :smile: return" (let ((ascii-emoji-pos (point-min)) (unicode-emoji-pos (+ (point-min) (length ":) "))) - (github-emoji-pos (+ (point-min) (length ":) 😄 ")))) + (github-emoji-pos (+ (point-min) (length ":) 😄 "))) + (prettify-emoji-pos (+ (point-min) (length ":) 😄 :smile: ")))) + + (setq prettify-symbols-alist + '(("return" . ?↪))) + + (when (fboundp 'prettify-symbols-mode) + (prettify-symbols-mode +1)) (emojify-set-emoji-styles '(ascii)) (emojify-tests-should-be-emojified ascii-emoji-pos) (emojify-tests-should-not-be-emojified unicode-emoji-pos) (emojify-tests-should-not-be-emojified github-emoji-pos) + (emojify-tests-should-not-be-emojified prettify-emoji-pos) (emojify-set-emoji-styles '(unicode)) (emojify-tests-should-not-be-emojified ascii-emoji-pos) (emojify-tests-should-be-emojified unicode-emoji-pos) (emojify-tests-should-not-be-emojified github-emoji-pos) + (emojify-tests-should-not-be-emojified prettify-emoji-pos) (emojify-set-emoji-styles '(github)) (emojify-tests-should-not-be-emojified ascii-emoji-pos) (emojify-tests-should-not-be-emojified unicode-emoji-pos) (emojify-tests-should-be-emojified github-emoji-pos) + (emojify-tests-should-not-be-emojified prettify-emoji-pos) - (emojify-set-emoji-styles '(ascii unicode github)) + (emojify-set-emoji-styles '(prettify-symbol)) + (emojify-tests-should-not-be-emojified ascii-emoji-pos) + (emojify-tests-should-not-be-emojified unicode-emoji-pos) + (emojify-tests-should-not-be-emojified github-emoji-pos) + (when (fboundp 'prettify-symbols-mode) + (emojify-tests-should-be-emojified prettify-emoji-pos)) + + (emojify-set-emoji-styles '(ascii unicode github prettify-symbol)) (emojify-tests-should-be-emojified ascii-emoji-pos) (emojify-tests-should-be-emojified unicode-emoji-pos) (emojify-tests-should-be-emojified github-emoji-pos) + (when (fboundp 'prettify-symbols-mode) + (emojify-tests-should-be-emojified prettify-emoji-pos)) (emojify-set-emoji-styles nil) (emojify-tests-should-not-be-emojified ascii-emoji-pos) (emojify-tests-should-not-be-emojified unicode-emoji-pos) - (emojify-tests-should-not-be-emojified github-emoji-pos)))) + (emojify-tests-should-not-be-emojified github-emoji-pos) + (emojify-tests-should-not-be-emojified prettify-emoji-pos)))) (ert-deftest emojify-tests-program-contexts () :tags '(core prog contextual) @@ -335,7 +385,7 @@ (fundamental-mode) (let ((count 0)) (emojify-do-for-emojis-in-region (point-min) (point-max) - (incf count)) + (cl-incf count)) ;; Only one emoji should be displayed (should (= count 1)) ;; The larger emoji should be preferred @@ -484,6 +534,113 @@ (delete-selection-pre-hook)) (should (equal (point-min) (point-max)))))) +(ert-deftest emojify-tests-prettify-symbols () + :tags '(prettify-symbols) + (when (fboundp 'prettify-symbols-mode) + (emojify-tests-with-emojified-static-buffer "try: + x = 1 +except: + raise(Exception) + +yield 3 +return 4 +" + (emojify-set-emoji-styles '(ascii unicode github prettify-symbol)) + (python-mode) + (setq prettify-symbols-alist + '(("return" . ?↪) + ("try" . ?😱) + ("except" . ?⛐) + ("raise" . ?💥))) + (emojify-tests-should-not-be-emojified (point-min)) + (emojify-tests-should-not-be-emojified (line-beginning-position 3)) + (emojify-tests-should-not-be-emojified (+ (line-beginning-position 4) 5)) + (emojify-tests-should-not-be-emojified (line-beginning-position 6)) + (emojify-tests-should-not-be-emojified (line-beginning-position 7)) + (prettify-symbols-mode +1) + (emojify-tests-should-be-emojified (point-min)) + (should (equal (get-text-property (point-min) 'emojify-text) "try")) + (emojify-tests-should-not-be-emojified (line-beginning-position 3)) + (emojify-tests-should-be-emojified (+ (line-beginning-position 4) 5)) + (should (equal (get-text-property (+ (line-beginning-position 4) 5) 'emojify-text) "raise")) + (emojify-tests-should-not-be-emojified (line-beginning-position 6)) + (emojify-tests-should-be-emojified (line-beginning-position 7)) + (should (equal (get-text-property (line-beginning-position 7) 'emojify-text) "return")) + (prettify-symbols-mode -1) + (emojify-tests-should-not-be-emojified (point-min)) + (emojify-tests-should-not-be-emojified (line-beginning-position 3)) + (emojify-tests-should-not-be-emojified (+ (line-beginning-position 4) 5)) + (emojify-tests-should-not-be-emojified (line-beginning-position 6)) + (emojify-tests-should-not-be-emojified (line-beginning-position 7))))) + +(ert-deftest emojify-tests-prettify-symbols-with-custom-images () + :tags '(prettify-symbols) + (when (fboundp 'prettify-symbols-mode) + (let ((emojify-user-emojis emojify-test-custom-emojis)) + (emojify-set-emoji-data) + (emojify-tests-with-emojified-static-buffer "try: + lambda x: x +except: + raise(Exception) + +yield 3 +return 4 +" + (emojify-set-emoji-styles '(ascii unicode github prettify-symbol)) + (python-mode) + (setq prettify-symbols-alist + '(("return" . ?↪) + ("try" . ?😱) + ("except" . ?⛐) + ("lambda" . ?λ) + ("raise" . ?💥))) + (emojify-tests-should-not-be-emojified (+ (line-beginning-position 2) 5)) + (prettify-symbols-mode +1) + (emojify-tests-should-be-emojified (point-min)) + (emojify-tests-should-be-emojified (+ (line-beginning-position 2) 5)) + (emojify-tests-should-not-be-emojified (line-beginning-position 3)) + (emojify-tests-should-be-emojified (+ (line-beginning-position 4) 5)) + (emojify-tests-should-not-be-emojified (line-beginning-position 6)) + (emojify-tests-should-be-emojified (line-beginning-position 7)))))) + +(ert-deftest emojify-tests-apropos () + :tags '(apropos) + (emojify-apropos-emoji "squi") + ;; Window with results should be visible + (with-mock + (stub message => nil) + (should (get-buffer-window emojify-apropos-buffer-name)) + (let ((matches 0)) + + (with-current-buffer emojify-apropos-buffer-name + ;; Force a display of emojis + (emojify-redisplay-emojis-in-region (point-min) (point-max)) + (emojify-do-for-emojis-in-region (point-min) (point-max) + (goto-char emoji-start) + (call-interactively #'emojify-apropos-copy-emoji) + (should (string= (car kill-ring) (get-text-property (point) 'emojify-text))) + (cl-incf matches))) + + (should (= matches 2))) + + ;; Test with custom emoji + (let ((emojify-user-emojis emojify-test-custom-emojis) + (matches 0)) + + (emojify-set-emoji-data) + (emojify-apropos-emoji "lambda") + (should (get-buffer-window emojify-apropos-buffer-name)) + + (with-current-buffer emojify-apropos-buffer-name + (emojify-redisplay-emojis-in-region (point-min) (point-max)) + (emojify-do-for-emojis-in-region (point-min) (point-max) + (goto-char emoji-start) + (call-interactively #'emojify-apropos-copy-emoji) + (should (string= (car kill-ring) (get-text-property (point) 'emojify-text))) + (cl-incf matches))) + + (should (= matches 1))))) + (ert-deftest emojify-tests-no-byte-compilation-warnings () :tags '(byte-compilation) (with-mock diff --git a/test/test-helper.el b/test/test-helper.el @@ -22,19 +22,32 @@ ;; Libs required for tests (require 'ert) (require 'el-mock) -(require 'cl) +(require 'cl-lib) (require 'noflet) ;; Load emojify (require 'emojify) +;; Define custom emoji config +(defvar emojify-test-custom-emojis) +(let* ((project-dir (locate-dominating-file (or (buffer-file-name) load-file-name) + ".cask")) + (custom-emoji-dir (expand-file-name "test/assets/" project-dir))) + (setq emojify-test-custom-emojis + `((":troll:" . (("name" . "Troll") ("image" . ,(expand-file-name "trollface.png" custom-emoji-dir)) ("style" . "github"))) + (":neckbeard:" . (("name" . "Neckbeard") ("image" . ,(expand-file-name "neckbeard.png" custom-emoji-dir)) ("style" . "github"))) + ("λ" . (("name" . "Lambda") ("image" . ,(expand-file-name "lambda.png" custom-emoji-dir)) ("style" . "unicode")))))) + ;; Helper macros for tests (defmacro emojify-tests-with-saved-customizations (&rest forms) - "Run forms saving current customizations and restoring them on completion. + "Run FORMS saving current customizations and restoring them on completion. Helps isolate tests from each other's customizations." (declare (indent 0)) `(let ((emojify-saved-emoji-json emojify-emoji-json) + (emojify-saved-user-emojis emojify-user-emojis) + (emojify-saved-user-emojis-parsed emojify--user-emojis) + (emojify-saved-emojify-regexps emojify-regexps) (emojify-saved-display-style emojify-display-style) (emojify-saved-inhibit-major-modes emojify-inhibit-major-modes) (emojify-saved-inhibit-in-buffer-functions emojify-inhibit-in-buffer-functions) @@ -53,6 +66,9 @@ Helps isolate tests from each other's customizations." (setq emojify-emoji-json emojify-saved-emoji-json emojify-display-style emojify-saved-display-style emojify-inhibit-major-modes emojify-saved-inhibit-major-modes + emojify-user-emojis emojify-saved-user-emojis + emojify--user-emojis emojify-saved-user-emojis-parsed + emojify-regexps emojify-saved-emojify-regexps emojify-inhibit-in-buffer-functions emojify-saved-inhibit-in-buffer-functions emojify-program-contexts emojify-saved-program-contexts emojify-inhibit-functions emojify-saved-inhibit-functions @@ -64,6 +80,9 @@ Helps isolate tests from each other's customizations." (emojify-set-emoji-styles emojify-saved-emoji-style)))) (defmacro emojify-tests-with-emojified-buffer (str &rest forms) + "Create a buffer with STR and execute FORMS. + +The FORMS are executed with emojify enabled." (declare (indent 1)) ;; Run tests in a new buffer `(let ((test-buffer (get-buffer-create " *emojify-test-buffer*"))) @@ -90,12 +109,17 @@ Helps isolate tests from each other's customizations." (kill-buffer test-buffer)))))) (defmacro emojify-tests-with-emojified-static-buffer (str &rest forms) + "Create a buffer with STR and execute FORMS. + +All kinds of dynamic behaviour on buffer are disabled. See +`emojify-with-saved-buffer-state'" (declare (indent 1)) `(emojify-tests-with-emojified-buffer ,str (emojify-with-saved-buffer-state ,@forms))) (defmacro emojify-tests-should-be-emojified (point) + "Assert there is an emoji at POINT." `(progn (should-not (get-text-property ,point 'point-left)) (should (get-text-property ,point 'emojified)) @@ -108,6 +132,7 @@ Helps isolate tests from each other's customizations." (should (get-text-property ,point 'point-entered)))) (defmacro emojify-tests-should-not-be-emojified (point) + "Assert there is not emoji at POINT." `(progn (should-not (get-text-property ,point 'point-left)) (should-not (get-text-property ,point 'emojified)) @@ -120,6 +145,7 @@ Helps isolate tests from each other's customizations." (should-not (get-text-property ,point 'point-entered)))) (defmacro emojify-tests-should-be-uncovered (point) + "Assert the emoji at POINT is uncovered." `(progn (should (get-text-property ,point 'point-left)) (should (get-text-property ,point 'emojified)) @@ -131,9 +157,19 @@ Helps isolate tests from each other's customizations." (should-not (get-text-property ,point 'display)))) (defun emojify-insert-string (string) + "Insert the STRING." (mapc (lambda (character) (insert character)) (string-to-vector string))) +(defun emojify-redisplay () + "Trigger a redisplay." + (if noninteractive + ;; In noninteractive mode JIT is not called + ;; call it + (jit-lock-fontify-now) + ;; In interactive mode just force redisplay + (redisplay t))) + (provide 'test-helper) ;;; test-helper.el ends here