├── .ert-runner ├── test ├── assets │ ├── lambda.png │ ├── neckbeard.png │ └── trollface.png ├── launch.el ├── test-helper.el └── emojify-test.el ├── .gitignore ├── screenshots ├── emojify-in-action.gif └── emojify-in-action.png ├── Cask ├── .dir-locals.el ├── .travis.yml ├── .github └── workflows │ └── ci.yml ├── data └── emoji-sets.json ├── CHANGELOG.org ├── README.org ├── LICENSE └── emojify.el /.ert-runner: -------------------------------------------------------------------------------- 1 | --reporter ert 2 | -------------------------------------------------------------------------------- /test/assets/lambda.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iqbalansari/emacs-emojify/HEAD/test/assets/lambda.png -------------------------------------------------------------------------------- /test/assets/neckbeard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iqbalansari/emacs-emojify/HEAD/test/assets/neckbeard.png -------------------------------------------------------------------------------- /test/assets/trollface.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iqbalansari/emacs-emojify/HEAD/test/assets/trollface.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # -*- mode: gitignore; -*- 2 | # Compiled files 3 | *.elc 4 | 5 | # cask packages 6 | .cask/ 7 | /dist/ 8 | -------------------------------------------------------------------------------- /screenshots/emojify-in-action.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iqbalansari/emacs-emojify/HEAD/screenshots/emojify-in-action.gif -------------------------------------------------------------------------------- /screenshots/emojify-in-action.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iqbalansari/emacs-emojify/HEAD/screenshots/emojify-in-action.png -------------------------------------------------------------------------------- /Cask: -------------------------------------------------------------------------------- 1 | (source gnu) 2 | (source melpa) 3 | 4 | (package-file "emojify.el") 5 | (files "*.el" ("data" "data/emoji.json" "data/emoji-sets.json")) 6 | 7 | (development 8 | (depends-on "f") 9 | (depends-on "ecukes") 10 | (depends-on "ert-runner") 11 | (depends-on "el-mock") 12 | (depends-on "undercover") 13 | (depends-on "noflet")) 14 | -------------------------------------------------------------------------------- /.dir-locals.el: -------------------------------------------------------------------------------- 1 | ((emacs-lisp-mode 2 | (compile-command . "cask exec ert-runner --reporter ert") 3 | (eval ignore-errors 4 | (push (quote ("Tests" "(\\(\\\\s *\\(\\(?:\\sw\\|\\s_\\)+\\)?" 2)) imenu-generic-expression) 5 | (when (string-match-p "test" (buffer-file-name)) 6 | (setq-local emojify-inhibit-emojify-in-current-buffer-p t))))) 7 | -------------------------------------------------------------------------------- /test/launch.el: -------------------------------------------------------------------------------- 1 | ;; A simple configuration file to launch a standalone instance of Emacs 2 | ;; with just emojify and it's dependencies installed 3 | ;; 4 | ;; Do something like the following 5 | ;; 6 | ;; cask exec emacs -Q -l test/launch.el 7 | ;; 8 | (let ((project-dir (locate-dominating-file (or (buffer-file-name) load-file-name) 9 | ".cask"))) 10 | (if (not project-dir) 11 | (user-error "Could not locate project root") 12 | (let ((default-directory (expand-file-name (format ".cask/%d.%d" 13 | emacs-major-version 14 | emacs-minor-version) 15 | project-dir))) 16 | (normal-top-level-add-subdirs-to-load-path)) 17 | (add-to-list 'load-path project-dir))) 18 | 19 | (require 'emojify) 20 | 21 | (setq emojify-emojis-dir (make-temp-file "emojify" t)) 22 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: generic 2 | sudo: required 3 | env: 4 | - EVM_EMACS=emacs-26.2 5 | - EVM_EMACS=emacs-26.1 6 | - EVM_EMACS=emacs-25.3 7 | - EVM_EMACS=emacs-25.2 8 | 9 | cache: 10 | apt: true 11 | directories: 12 | - $HOME/emacsen 13 | - $HOME/.emacs.d/emojis 14 | 15 | addons: 16 | apt: 17 | packages: 18 | - python 19 | - curl 20 | # Dependencies for Emacs 21 | - libxaw7-dev 22 | - libjpeg-dev 23 | - libtiff5-dev 24 | - libpng-dev 25 | - texinfo 26 | - libgnutls28-dev 27 | 28 | before_install: 29 | - curl -fsSkL https://raw.github.com/rejeep/evm/master/go | bash 30 | - export PATH="$HOME/.evm/bin:$PATH" 31 | - mkdir -p $HOME/emacsen 32 | - evm config path $HOME/emacsen 33 | 34 | install: 35 | - evm install $EVM_EMACS --skip 36 | - evm use $EVM_EMACS 37 | - curl -fsSL https://raw.githubusercontent.com/cask/cask/master/go | python 38 | - export PATH="/home/travis/.cask/bin:$PATH" 39 | - cask install 40 | 41 | script: 42 | - find . -name '*.elc' -delete && cask exec ert-runner 43 | 44 | notifications: 45 | email: 46 | recipients: 47 | - iqbalansari02@yahoo.com 48 | on_success: never 49 | on_failure: always 50 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [pull_request, push] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | strategy: 9 | matrix: 10 | emacs-version: 11 | - emacs-26.2 12 | - emacs-26.1 13 | - emacs-25.3 14 | - emacs-25.2 15 | steps: 16 | - name: Checkout 17 | uses: actions/checkout@v2 18 | 19 | - name: Cache 20 | uses: actions/cache@v2 21 | with: 22 | path: | 23 | ~/emacsen 24 | ~/.emacs.d/emojis 25 | key: ${{ runner.os }}-${{ matrix.emacs-version }}-${{ hashFiles('**/Cask') }} 26 | 27 | - name: Install dependencies 28 | run: sudo apt-get install libxaw7-dev libgnutls28-dev 29 | 30 | - name: Install EVM 31 | run: | 32 | curl -fsSkL https://raw.github.com/rejeep/evm/master/go | bash 33 | echo "$HOME/.evm/bin" >> $GITHUB_PATH 34 | 35 | - name: SetUp EVM 36 | run: | 37 | mkdir -p $HOME/emacsen 38 | evm config path $HOME/emacsen 39 | evm install ${{ matrix.emacs-version }} --skip 40 | evm use ${{ matrix.emacs-version }} 41 | 42 | - name: Install Cask 43 | run: | 44 | curl -fsSL https://raw.githubusercontent.com/cask/cask/master/go | python3 45 | echo "$HOME/.cask/bin:$PATH" >> $GITHUB_PATH 46 | 47 | - name: Install 48 | run: cask install 49 | 50 | - name: Run tests 51 | run: find . -name '*.elc' -delete && cask exec ert-runner 52 | -------------------------------------------------------------------------------- /data/emoji-sets.json: -------------------------------------------------------------------------------- 1 | { 2 | "emojione-v2-22" : { 3 | "description" : "Emojis provided by Emoji One (version 2), resized to 22px", 4 | "website" : "http://emojione.com", 5 | "url" : "https://raw.githubusercontent.com/iqbalansari/emacs-emojify/9e36d0e8c2a9c373a39728f837a507adfbb7b931/emojione-fixed-v2-22.tar", 6 | "sha256" : "8b0ccea3610dc98c74d04de8d8c6340b4aaa8e950cad8fb62961e45792625dd7" 7 | }, 8 | "emojione-v2" : { 9 | "description" : "Emojis provided by Emoji One (version 2)", 10 | "website" : "http://emojione.com", 11 | "url" : "https://raw.githubusercontent.com/iqbalansari/emacs-emojify/9e36d0e8c2a9c373a39728f837a507adfbb7b931/emojione-fixed-v2.tar", 12 | "sha256" : "828765e8f89dc6aa4cfb06c271e9de122aa51383ed85e1169ac774fdf1c739fb" 13 | }, 14 | "emojione-v2.2.6-22" : { 15 | "description" : "Emojis provided by Emoji One (version 2.2.6), resized to 22px", 16 | "website" : "http://emojione.com", 17 | "url" : "https://raw.githubusercontent.com/iqbalansari/emacs-emojify/9e36d0e8c2a9c373a39728f837a507adfbb7b931/emojione-fixed-v2.2.6-22.tar", 18 | "sha256" : "f4428b0875cd0a418168139418bb03aaae0655254e4a683ec101f901d2c6ce59" 19 | }, 20 | "emojione-v2.2.6" : { 21 | "description" : "Emojis provided by Emoji One (version 2.2.6)", 22 | "website" : "http://emojione.com", 23 | "url" : "https://raw.githubusercontent.com/iqbalansari/emacs-emojify/9e36d0e8c2a9c373a39728f837a507adfbb7b931/emojione-fixed-v2.2.6.tar", 24 | "sha256" : "eb0ff5637924a2a04d3ab649b66d816a69c5d71eab2bf5274d292115e8178244" 25 | }, 26 | "twemoji-v2": { 27 | "description" : "Emojis provided by Twitter (version 2)", 28 | "website" : "https://twemoji.twitter.com/", 29 | "url": "https://raw.githubusercontent.com/iqbalansari/emacs-emojify/9e36d0e8c2a9c373a39728f837a507adfbb7b931/twemoji-fixed-v2.tar", 30 | "sha256" : "0991b1032a04d948835fba4249f43993b4ac88a66d2ae7f278f03be31884851d" 31 | }, 32 | "twemoji-v2-22": { 33 | "description" : "Emojis provided by Twitter (version 2), resized to 22px", 34 | "website" : "https://twemoji.twitter.com/", 35 | "url": "https://raw.githubusercontent.com/iqbalansari/emacs-emojify/9e36d0e8c2a9c373a39728f837a507adfbb7b931/twemoji-fixed-v2-22.tar", 36 | "sha256" : "e3ae26d7ac111fe0be7b90f29afdab89676610a865353dfb672673efb5af044a" 37 | }, 38 | "openmoji-v13-0": { 39 | "description" : "Emojis provided by HfG Schwäbisch Gmünd (version 13.0) at 72px", 40 | "website" : "https://openmoji.org", 41 | "url": "https://raw.githubusercontent.com/iqbalansari/emacs-emojify/9161fd27f399944516e86d36c90c3b55f31427dd/openmoji-v13.0.tar", 42 | "sha256" : "ccb0bce387e216a0c5f4ff54d53607dbfa1c62e7de9be95ee56af0cd39e42807" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /CHANGELOG.org: -------------------------------------------------------------------------------- 1 | * Change Log 2 | All notable changes to this project will be documented in this file. 3 | The format is based on [[http://keepachangelog.com/][Keep a Changelog]]. 4 | 5 | ** Unreleased 6 | See [[https://github.com/iqbalansari/emacs-emojify/compare/v1.1...HEAD][Changes]] 7 | 8 | ** v1.2 - 2019-05-21 9 | See [[https://github.com/iqbalansari/emacs-emojify/compare/v1.0...v1.1][Changes]] 10 | 11 | *** Fixed 12 | - Fix rendering of ascii emojis mode-line to take into account the context 13 | - Fix performance regression introduced due to use of ~while-no-input~ 14 | - Fix performance issues caused by repeated checks on ~imagemagick-types~ 15 | - Improve error messaging when image download fails 16 | 17 | *** Added 18 | - Support for displaying emojis in ~org-mode~ source blocks 19 | - Twemoji image set 20 | 21 | ** v1.0 - 2018-03-21 22 | See [[https://github.com/iqbalansari/emacs-emojify/compare/v0.4...v1.0][Changes]] 23 | 24 | *** Fixed 25 | - Do not try emojifying areas larger than 5000 points 26 | - Fix display of emojis containing the plus sign (eg. ~:+1:~) 27 | - Fix backspace to delete complete emojis in ~org-mode~ 28 | *** Added 29 | - Support for displaying emojis in the company mode's popups, this can be disabled by customizing the variable ~emojify-company-tooltips-p~ 30 | - Experimental support for displaying emojis in the mode-line, see ~emojify-mode-line-mode~ and ~global-emojify-mode-line-mode~ 31 | - New command ~emojify-describe-emoji-at-point~ can be used to view help about emoji displayed at point. 32 | - New command ~emojify-describe-emoji~ can be used to view help about a emoji. 33 | - New command ~emojify-list-emojis~ for listing all the emojis 34 | - Support for displaying emojis in ~company-mode~ buffers 35 | - New function ~emojify-completing-read~ to read an emoji from the user 36 | - New function ~emojify-string~ can be used to add relevant text-properties to strings so that emojis in them are displayed 37 | - Support for new text property ~emojify-inhibit~ it can be set by third party packages to inhibit the display of certain parts of buffer as emoji 38 | - Support for new text property ~emojify-force-display~ it can be set by third party packages to force the display of certain parts of buffer as emoji 39 | 40 | ** v0.4 - 2016-09-28 41 | See [[https://github.com/iqbalansari/emacs-emojify/compare/v0.3...v0.4][Changes]] 42 | 43 | *** Removed 44 | - The customizable variable ~emojify-image-dir~ has been removed since it did not interact well with emojify-emojis-dir. 45 | *** Fixed 46 | - Emojify mode is disabled in ~magit-diff-mode~, ~ibuffer-mode~ and ~debugger-mode~. 47 | - Emojify now does not rely on deprecated ~point-entered~ and ~point-left~ text properties for implementing uncovering logic, this makes emojify compatible with Emacs 25.1 48 | - Emojify mode is disabled when buffer switches to unibyte encoding 49 | *** Changed 50 | - Emoji images are now downloaded lazily instead of being packaged with code, this also makes it possible to use emoji sets other than EmojiOne. 51 | - The behaviour when point enters an emoji in ~isearch-mode~ is now customizable, earlier the emoji was revealed by default. See ~emojify-reveal-on-isearch~. 52 | - Images from EmojiOne v2.2.6 are used by default 53 | *** Added 54 | - ~unicode~ emojis appearing in source code are rendered by default. 55 | - New command ~emojify-apropos-emoji~ can be used to search emojis matching given regex. 56 | - New command ~emojify-insert-emoji~ can be used to insert emojis with completion. 57 | - Users can now define custom emojis using ~emojify-user-emojis~ 58 | - Emojify can now display composed text as emojis, this enables rendering of symbols used by ~prettify-symbols-mode~ or ~org-bullets-mode~ as emojis. See ~emojify-composed-text-p~ 59 | 60 | ** v0.3 - 2015-12-21 61 | See [[https://github.com/iqbalansari/emacs-emojify/compare/v0.2...v0.3][Changes]] 62 | 63 | *** Fixed 64 | - Multiple non-ascii emojis in sequence are displayed properly. 65 | - Treat tuareg-mode as programming mode. 66 | *** Added 67 | - Deletion commands remove complete emoji instead of removing a single character. 68 | - Emoji backgrounds are calculated based on the face at point. 69 | 70 | ** v0.2 - 2015-09-15 71 | See [[https://github.com/iqbalansari/emacs-emojify/compare/v0.1...v0.2][Changes]] 72 | 73 | *** Removed 74 | - The variable ~emojify-emoji-style~ has been replaced with ~emojify-emoji-styles~ 75 | 76 | *** Added 77 | - The package can now display unicode emojis as well 78 | 79 | ** v0.1 - 2015-09-14 80 | Initial public release 81 | -------------------------------------------------------------------------------- /test/test-helper.el: -------------------------------------------------------------------------------- 1 | ;;; test-helper.el --- Tests for emojify -*- lexical-binding: t; -*- 2 | 3 | ;;; Commentary: 4 | ;;; Helpers to write tests for emojify 5 | 6 | ;;; Code: 7 | 8 | ;; Setup load-path, some of this is redundant when tests are run from the 9 | ;; command line 10 | (let ((project-dir (locate-dominating-file (or (buffer-file-name) load-file-name) 11 | ".cask"))) 12 | (if (not project-dir) 13 | (user-error "Could not locate project root") 14 | (let ((default-directory (expand-file-name (format ".cask/%d.%d" 15 | emacs-major-version 16 | emacs-minor-version) 17 | project-dir))) 18 | (normal-top-level-add-subdirs-to-load-path)) 19 | (add-to-list 'load-path project-dir))) 20 | 21 | ;; Libs required for tests 22 | (require 'ert) 23 | (require 'el-mock) 24 | (eval-when-compile 25 | (require 'cl)) 26 | (require 'cl-lib) 27 | (require 'noflet) 28 | 29 | (when (require 'undercover nil t) 30 | (undercover "*.el")) 31 | 32 | ;; Load emojify 33 | (require 'emojify) 34 | 35 | ;; Define custom emoji config 36 | (defvar emojify-test-custom-emojis) 37 | (let* ((project-dir (locate-dominating-file (or (buffer-file-name) load-file-name) 38 | ".cask")) 39 | (custom-emoji-dir (expand-file-name "test/assets/" project-dir))) 40 | (setq emojify-test-custom-emojis 41 | `((":troll:" . (("name" . "Troll") ("image" . ,(expand-file-name "trollface.png" custom-emoji-dir)) ("style" . "github"))) 42 | (":neckbeard:" . (("name" . "Neckbeard") ("image" . ,(expand-file-name "neckbeard.png" custom-emoji-dir)) ("style" . "github"))) 43 | ("λ" . (("name" . "Lambda") ("image" . ,(expand-file-name "lambda.png" custom-emoji-dir)) ("style" . "unicode")))))) 44 | 45 | ;; Helper macros for tests 46 | (defmacro emojify-tests-with-saved-customizations (&rest forms) 47 | "Run FORMS saving current customizations and restoring them on completion. 48 | 49 | Helps isolate tests from each other's customizations." 50 | (declare (indent 0)) 51 | `(let ((emojify-saved-emoji-json emojify-emoji-json) 52 | (emojify-saved-user-emojis emojify-user-emojis) 53 | (emojify-saved-user-emojis-parsed emojify--user-emojis) 54 | (emojify-saved-emojify-regexps emojify-regexps) 55 | (emojify-saved-display-style emojify-display-style) 56 | (emojify-saved-inhibit-major-modes emojify-inhibit-major-modes) 57 | (emojify-saved-inhibit-in-buffer-functions emojify-inhibit-in-buffer-functions) 58 | (emojify-saved-emoji-style emojify-emoji-styles) 59 | (emojify-saved-program-contexts emojify-program-contexts) 60 | (emojify-saved-inhibit-functions emojify-inhibit-functions) 61 | (emojify-saved-point-entered-behaviour emojify-point-entered-behaviour) 62 | (emojify-saved-show-help emojify-show-help) 63 | (emojify-saved-reveal-on-isearch emojify-reveal-on-isearch) 64 | (emojify-saved-composed-text-p emojify-composed-text-p)) 65 | (unwind-protect 66 | (progn 67 | (unless (file-exists-p (emojify-image-dir)) 68 | (emojify-download-emoji emojify-emoji-set)) 69 | (emojify-debug-mode +1) 70 | (setq emojify-composed-text-p nil) 71 | ,@forms) 72 | (setq emojify-emoji-json emojify-saved-emoji-json 73 | emojify-display-style emojify-saved-display-style 74 | emojify-inhibit-major-modes emojify-saved-inhibit-major-modes 75 | emojify-user-emojis emojify-saved-user-emojis 76 | emojify--user-emojis emojify-saved-user-emojis-parsed 77 | emojify-regexps emojify-saved-emojify-regexps 78 | emojify-inhibit-in-buffer-functions emojify-saved-inhibit-in-buffer-functions 79 | emojify-program-contexts emojify-saved-program-contexts 80 | emojify-inhibit-functions emojify-saved-inhibit-functions 81 | emojify-point-entered-behaviour emojify-saved-point-entered-behaviour 82 | emojify-show-help emojify-saved-show-help 83 | emojify-reveal-on-isearch emojify-saved-reveal-on-isearch 84 | emojify-composed-text-p emojify-saved-composed-text-p) 85 | (emojify-set-emoji-styles emojify-saved-emoji-style)))) 86 | 87 | (defmacro emojify-tests-with-emojified-buffer (str &rest forms) 88 | "Create a buffer with STR and execute FORMS. 89 | 90 | The FORMS are executed with emojify enabled." 91 | (declare (indent 1)) 92 | ;; Run tests in a new buffer 93 | `(let ((test-buffer (get-buffer-create " *emojify-test-buffer*"))) 94 | (noflet ((emojify-buffer-p (buffer) 95 | (or (string-match-p "^ \\*emojify-test-buffer\\*" (buffer-name buffer)) 96 | (funcall this-fn buffer)))) 97 | (unwind-protect 98 | (save-window-excursion 99 | (switch-to-buffer test-buffer) 100 | ;; Rename it uniquely so that subsequent buffers do not conflict with it 101 | (rename-uniquely) 102 | ;; Save all possible customizations 103 | (emojify-tests-with-saved-customizations 104 | (setq emojify-point-entered-behaviour nil) 105 | (insert ,str) 106 | (emojify-mode +1) 107 | ;; Force refontification since JIT does it lazily 108 | (emojify-display-emojis-in-region (point-min) (point-max)) 109 | (goto-char (point-min)) 110 | ,@forms)) 111 | ;; Keep the buffer around for interactive tests, helps debugging failing 112 | ;; tests 113 | (when noninteractive 114 | (kill-buffer test-buffer)))))) 115 | 116 | (defmacro emojify-tests-with-emojified-static-buffer (str &rest forms) 117 | "Create a buffer with STR and execute FORMS. 118 | 119 | All kinds of dynamic behaviour on buffer are disabled. See 120 | `emojify-with-saved-buffer-state'" 121 | (declare (indent 1)) 122 | `(emojify-tests-with-emojified-buffer ,str 123 | (emojify-with-saved-buffer-state 124 | ,@forms))) 125 | 126 | (defun emojify-tests-should-be-emojified (point) 127 | "Assert there is an emoji at POINT." 128 | (should (get-text-property point 'emojified)) 129 | (should (get-text-property point 'emojify-display)) 130 | (should (get-text-property point 'emojify-buffer)) 131 | (should (get-text-property point 'emojify-beginning)) 132 | (should (get-text-property point 'emojify-end)) 133 | (should (get-text-property point 'emojify-text)) 134 | (should (get-text-property point 'display))) 135 | 136 | (defun emojify-tests-should-not-be-emojified (point) 137 | "Assert there is not emoji at POINT." 138 | (should-not (get-text-property point 'emojified)) 139 | (should-not (get-text-property point 'emojify-display)) 140 | (should-not (get-text-property point 'emojify-buffer)) 141 | (should-not (get-text-property point 'emojify-beginning)) 142 | (should-not (get-text-property point 'emojify-end)) 143 | (should-not (get-text-property point 'emojify-text)) 144 | (should-not (get-text-property point 'display))) 145 | 146 | (defun emojify-tests-should-be-uncovered (point) 147 | "Assert the emoji at POINT is uncovered." 148 | (should (get-text-property point 'emojified)) 149 | (should (get-text-property point 'emojify-buffer)) 150 | (should (get-text-property point 'emojify-beginning)) 151 | (should (get-text-property point 'emojify-end)) 152 | (should (get-text-property point 'emojify-text)) 153 | (should-not (get-text-property point 'display))) 154 | 155 | ;;; test-helper.el ends here 156 | -------------------------------------------------------------------------------- /README.org: -------------------------------------------------------------------------------- 1 | * Emojify 2 | 3 | [[./CHANGELOG.org][file:https://img.shields.io/badge/version-v0.4-blue.svg]] [[https://melpa.org/#/emojify][file:https://melpa.org/packages/emojify-badge.svg]] [[http://stable.melpa.org/#/emojify][file:http://stable.melpa.org/packages/emojify-badge.svg]] [[https://travis-ci.org/iqbalansari/emacs-emojify][https://travis-ci.org/iqbalansari/emacs-emojify.svg?branch=master]] [[https://coveralls.io/github/iqbalansari/emacs-emojify?branch=master][https://coveralls.io/repos/github/iqbalansari/emacs-emojify/badge.svg?branch=master]] [[http://www.gnu.org/licenses/gpl-3.0.html][http://img.shields.io/:license-gpl3-blue.svg]] [[http://makeapullrequest.com][file:https://img.shields.io/badge/PRs-welcome-brightgreen.svg]] 4 | 5 | ** Table of contents :TOC_3_gh: 6 | - [[#emojify][Emojify]] 7 | - [[#what-is-this][What is this?]] 8 | - [[#what-does-it-look-like][What does it look like?]] 9 | - [[#requirements][Requirements]] 10 | - [[#installation][Installation]] 11 | - [[#elpa][ELPA]] 12 | - [[#usage][Usage]] 13 | - [[#displaying-emojis][Displaying emojis]] 14 | - [[#searching-emojis][Searching emojis]] 15 | - [[#inserting-emojis][Inserting emojis]] 16 | - [[#describing-emojis][Describing emojis]] 17 | - [[#listing-all-emojis][Listing all emojis]] 18 | - [[#customizations][Customizations]] 19 | - [[#displaying-composed-text-as-emojis][Displaying composed text as emojis]] 20 | - [[#displaying-emojis-in-company-mode-tooltips][Displaying emojis in company mode tooltips]] 21 | - [[#configuring-the-types-of-emojis-displayed][Configuring the types of emojis displayed]] 22 | - [[#configuring-how-emojis-are-displayed][Configuring how emojis are displayed]] 23 | - [[#configuring-the-buffers-where-emojify-mode-is-enabled][Configuring the buffers where emojify mode is enabled]] 24 | - [[#configuring-the-texts-that-are-displayed-as-emojis][Configuring the texts that are displayed as emojis]] 25 | - [[#customizing-the-behaviour-when-point-enters-an-emoji][Customizing the behaviour when point enters an emoji]] 26 | - [[#customizing-the-behaviour-during-isearch-mode][Customizing the behaviour during isearch-mode]] 27 | - [[#customizing-the-behaviour-when-mouse-hovers-over-an-emoji][Customizing the behaviour when mouse hovers over an emoji]] 28 | - [[#custom-emojis][Custom emojis]] 29 | - [[#known-issues][Known issues]] 30 | - [[#contributing][Contributing]] 31 | - [[#thanks][Thanks]] 32 | - [[#credits][Credits]] 33 | - [[#licence][Licence]] 34 | - [[#emojione-images][EmojiOne images]] 35 | - [[#twemoji-images][Twemoji images]] 36 | - [[#source-code][Source code]] 37 | 38 | ** What is this? 39 | Emojify is an Emacs extension to display emojis. It can display github style 40 | emojis like ~:smile:~ or plain ascii ones like ~:)~. It tries to be as 41 | efficient as possible, while also providing a lot of [[#customizations][flexibility]] 42 | 43 | ** What does it look like? 44 | Here is how the commit log of this project looks with emojify mode enabled 45 | 46 | [[https://raw.githubusercontent.com/iqbalansari/emacs-emojify/master/screenshots/emojify-in-action.png][screenshots/emojify-in-action.png?raw=true]] 47 | 48 | If you prefer a GIF you can view one [[https://raw.githubusercontent.com/iqbalansari/emacs-emojify/master/screenshots/emojify-in-action.gif][here]]. 49 | 50 | ** Requirements 51 | This package requires Emacs *v24.3* and above. Emacs should be compiled with 52 | support for *PNG* images to display emojis as images. It is *recommended* 53 | that Emacs is compiled with *ImageMagick* support. ~emojify~ will use it to 54 | resize emojis if needed, additionally imagemagick is used to set background 55 | color for emojis to workaround the bug described in [[https://github.com/iqbalansari/emacs-emojify/issues/7][issue 7]]. However these 56 | are *completely optional*. 57 | 58 | PNG support might require some additional steps to on Windows, you might find 59 | [[http://stackoverflow.com/questions/2650041/emacs-under-windows-and-png-files][this]] stackoverflow answer helpful. 60 | 61 | ** Installation 62 | *** ELPA 63 | ~emojify~ is available on [[http://melpa.org/#/emojify][MELPA]] and [[http://stable.melpa.org/#/emojify][MELPA Stable]]. Please follow the instructions on MELPA 64 | [[http://melpa.org/#/getting-started][website]] to enable it, if you haven't already. 65 | 66 | You can then install ~emojify~ from the [[https://www.gnu.org/software/emacs/manual/html_node/emacs/Package-Menu.html][package menu]]. Alternatively install it by doing the following 67 | 68 | Refresh the package index 69 | #+BEGIN_QUOTE 70 | M-x package-refresh-contents RET 71 | #+END_QUOTE 72 | 73 | And then install it by doing 74 | #+BEGIN_QUOTE 75 | M-x package-install RET emojify 76 | #+END_QUOTE 77 | **** With use-package 78 | #+begin_src elisp 79 | (use-package emojify 80 | :hook (after-init . global-emojify-mode)) 81 | #+end_src 82 | ** Usage 83 | *** Displaying emojis 84 | ~emojify-mode~ can be enabled/disabled separately for a buffer by using the 85 | command ~emojify-mode~, to enable/disable it globally use the command 86 | ~global-emojify-mode~. 87 | 88 | To enable ~emojify-mode~ globally at startup add something like the 89 | following to your ~init~ file 90 | 91 | #+BEGIN_SRC emacs-lisp 92 | (add-hook 'after-init-hook #'global-emojify-mode) 93 | #+END_SRC 94 | 95 | Emojify integrates with packages like ~prettify-symbol-mode~ and 96 | ~org-bullets-mode~ which display text differently, if the alternate text 97 | displayed by these packages match an emoji, ~emojify~ will display them too. 98 | See [[#displaying-composed-text-as-emojis][Displaying composed text as emojis]] for more details. Emojify can also 99 | display emojis in ~company-mode~ tooltips, see [[#displaying-emojis-in-company-mode-tooltips][Displaying emojis in company mode tooltips]] 100 | for more details. 101 | 102 | *[EXPERIMENTAL]* Emojify can be used for displaying emojis in the mode-line, 103 | to enable/disable it for individual buffers use the command 104 | ~emojify-mode-line-mode~, to enable/disable it globally use the command 105 | ~global-emojify-mode-line-mode~. 106 | 107 | *** Searching emojis 108 | The command ~emojify-apropos-emoji~ can be used to display emojis that match 109 | given regexp/apropos pattern. The results are displayed in a specialized 110 | buffer, where ~w~ or ~c~ can be used to copy emojis to the kill ring. 111 | 112 | *** Inserting emojis 113 | The command ~emojify-insert-emoji~ can be used to insert emojis 114 | interactively. While the command works with vanilla Emacs completion system, 115 | the experience would be better with something like [[https://github.com/emacs-helm/helm][Helm]], [[https://github.com/abo-abo/swiper][Ivy]], [[https://www.emacswiki.org/emacs/Icicles][Icicles]] or Ido 116 | depending on you preference. 117 | 118 | *** Describing emojis 119 | The command ~emojify-describe-emoji-at-point~ can be used to view 120 | explanation about the command displayed at point. Additionally the command 121 | ~emojify-describe-emoji~ can be used to display description for an arbitrary 122 | emoji. 123 | 124 | *** Listing all emojis 125 | The command ~emojify-list-emojis~ can be used to view all the available emojis 126 | in a list form. 127 | 128 | ** Customizations 129 | *** Displaying composed text as emojis 130 | Emacs provides a way to modify how some parts of buffer are displayed using 131 | the ~composition~ text property. ~prettify-symbol-mode~ and 132 | ~org-bullets-mode~ are some popular packages that use this feature to 133 | display certain text in the buffer differently. 134 | 135 | If the alternate display matches an emoji then ~emojify~ will display those 136 | as emojis too. This is default behaviour. You can disable this behaviour by 137 | setting ~emojify-composed-text-p~ to ~nil~. 138 | 139 | *** Displaying emojis in company mode tooltips 140 | Emojify can also display emojis that are part of company-mode's completion 141 | tooltip (see [[https://github.com/dunn/company-emoji][company-emoji]] for an example of such tooltips). However this 142 | feature is turned off by default. To turn it on set 143 | ~emojify-company-tooltips-p~ to ~t~. 144 | 145 | *** Configuring the types of emojis displayed 146 | Emojify by default displays plain text emojis (~:)~), unicode emojis (~😄~) 147 | and github style emojis ~:smile:~. However this is customizable. You can do 148 | so by changing the value of ~emojify-emoji-styles~ using the customize 149 | interface. To change the value of the variable from Lisp using the function 150 | ~emojify-set-emoji-styles~ (or set it before loading ~emojify~), call it with 151 | one parameter the list of styles that you want to be displayed. The possible 152 | styles are 153 | 154 | - ascii - Display only plain ascii emojis 155 | - unicode - Display only unicode emojis 156 | - github - Display only github style emojis 157 | 158 | *** Configuring how emojis are displayed 159 | By default emojis are displayed using images. However you can instruct emojify 160 | to display it using unicode characters or ascii characters. To do so customize 161 | the variable ~emojify-display-style~. 162 | 163 | You can set it one of the following values 164 | - image - Display emojis using images, obviously this requires the Emacs 165 | instance to support image 166 | - unicode - Display emojis using unicode characters, this might be a good 167 | option on platforms with good emoji fonts 168 | - ascii - This is simplest and does not require any external dependencies 169 | In this case emojify will display ascii equivalents of github 170 | style emojis. 171 | 172 | *** Configuring the buffers where emojify mode is enabled 173 | You can control the buffers where emojify is enabled using 174 | ~emojify-inhibit-major-modes~ and ~emojify-inhibit-in-buffer-functions~. 175 | 176 | **** Major modes where emojify-mode should be not be enabled 177 | As the names suggests ~emojify-inhibit-major-modes~ is a list of major-modes 178 | where ~emojify~ should not be enabled. 179 | 180 | **** Inhibiting emojify-mode using custom functions 181 | ~emojify-inhibit-in-buffer-functions~ is a list of functions that ~emojify~ 182 | calls before enabling ~emojify-mode~ in a buffer. If any of the functions 183 | return a non-nil value ~emojify-mode~ is not enabled in the buffer. Users 184 | can add custom functions to this list if they wish to inhibit ~emojify~ in 185 | certain buffers. The functions are called with one argument the buffer 186 | where ~emojify-mode~ is about to be enabled. 187 | 188 | *** Configuring the texts that are displayed as emojis 189 | ~emojify~ offers two variables to inhibit the display of certain emojis. 190 | 191 | **** Controlling the display of emojis in programming modes 192 | If enabled in programming modes ~emojify~ by default will display only emojis 193 | in string and comments. This behaviour can be customized using the variable 194 | ~emojify-prog-contexts~. The variable can be set to one of the following values 195 | 196 | - comments - Display emojis only in comments 197 | - string - Display emojis only in string 198 | - both - Display emojis in comments as well as string 199 | - none - Do not display emojis in programming modes 200 | 201 | **** Inhibiting display of emojis using custom functions 202 | ~emojify-inhibit-functions~ is a list of function ~emojify~ calls before 203 | displaying certain text as emoji, if any of the functions return a non-nil 204 | value the corresponding text is not displayed as emoji. Users can add 205 | custom functions to this list, if they inhibit display of emojis in under 206 | certain conditions. The functions are called with three arguments 207 | 208 | - text - The text that is about to be displayed as an emoji 209 | - beg - The beginning point of text in the buffer 210 | - end - The ending point of text in the buffer 211 | 212 | These functions are called with the buffer where emoji is being displayed 213 | selected. 214 | 215 | *** Customizing the behaviour when point enters an emoji 216 | The behaviour when point enters an emoji can be customized using the 217 | variable ~emojify-point-entered-behaviour~. It can have one of the following 218 | values 219 | 220 | - echo - Display the emojified text in the minibuffer 221 | - uncover - Temporarily display the underlying text while point is in the emojified text 222 | 223 | Additionally it can be set to a custom function, the function is called with 224 | two parameters (the buffer where the emoji appears is selected while running 225 | the function) 226 | 227 | - beg - The beginning position of the text displayed as emoji 228 | - end - The ending position of the text displayed as emoji 229 | 230 | The return value of the function is ignored. 231 | 232 | *Note:* The custom function will be called for once for each character in an 233 | emoji as point moves through them. Avoid manipulating the buffer in these 234 | functions 235 | 236 | *** Customizing the behaviour during isearch-mode 237 | By default in isearch-mode the underlying emoji is displayed temporarily 238 | when point enters the emoji while searching (similar to ~uncover~ behaviour 239 | mentioned [[#controlling-behaviour-when-point-enters-an-emoji][above]]). This can be disabled by setting 240 | ~emojify-reveal-on-isearch~ to ~nil~. 241 | 242 | *** Customizing the behaviour when mouse hovers over an emoji 243 | When mouse hovers over a emoji, the underlying text is displayed in a help 244 | popup. This behaviour can be disabled by setting ~emojify-show-help~ to nil. 245 | 246 | *** Custom emojis 247 | You can specify custom emojis using the ~emojify-user-emojis~ variable. You 248 | need to set it to an alist where first element of cons is the text to be 249 | displayed as emoji, while the second element of the cons is an alist 250 | containing data about the emoji. 251 | 252 | The inner alist should have atleast 253 | 254 | 1) "name" - The name of the emoji 255 | 2) "style" - This should be one of "github", "ascii" or "github" 256 | 257 | Additionally the alist should contain one of (see [[#configuring-the-types-of-emojis-displayed][emojify-display-style]]) 258 | 1) "unicode" - The replacement for the provided emoji for "unicode" display style 259 | 2) "image" - The replacement for the provided emoji for "image" display style. 260 | This should be the *absolute* path to the image 261 | 3) "ascii" - The replacement for the provided emoji for "ascii" display style 262 | 263 | It is best to set this variable before you load ~emojify~, in case you set 264 | this variable after loading ~emojify~ run the function 265 | ~emojify-set-emoji-data~ to recalculate emoji data. 266 | 267 | User emojis take precedence over default emojis so the above mechanism can 268 | also be used to override the default emojis 269 | 270 | **** Example 271 | Below is an example of setting up custom emojis. Assuming that the custom 272 | images are at ~\~/.emacs.d/emojis/trollface.png~ and 273 | ~\~/.emacs.d/emojis/neckbeard.png~, you instruct ~emojify~ to display ~:trollface:~ 274 | and ~:neckbeard:~ as :trollface: and :neckbeard: 275 | 276 | #+BEGIN_SRC emacs-lisp 277 | (setq emojify-user-emojis '((":trollface:" . (("name" . "Troll Face") 278 | ("image" . "~/.emacs.d/emojis/trollface.png") 279 | ("style" . "github"))) 280 | (":neckbeard:" . (("name" . "Neckbeard") 281 | ("image" . "~/.emacs.d/emojis/neckbeard.png") 282 | ("style" . "github"))))) 283 | 284 | ;; If emojify is already loaded refresh emoji data 285 | (when (featurep 'emojify) 286 | (emojify-set-emoji-data)) 287 | #+END_SRC 288 | 289 | ** Known issues 290 | - Emojis are not properly updated after customizing ~emojify-display-style~ or 291 | ~emojify-program-contexts~. For time being you will be fine as long as you 292 | set these variables before ~emojify~ has loaded. 293 | - Some of the emojis prompted in the commands like ~emojify-insert-emoji~ might 294 | not be displayed by ~emojify~, this might happen if you have newer emoji 295 | data but old set of images. Download the latest emoji image using ~emojify-download-emoji~ 296 | and set ~emojify-emoji-set~ to the downloaded set. 297 | 298 | ** Contributing 299 | Code as well as documentation contributions are welcome. 300 | 301 | [[https://github.com/cask/cask][Cask]] is used to manage project dependencies so make sure you have it 302 | installed. To run the tests you need to install the dependencies by running 303 | the following 304 | 305 | #+BEGIN_SRC sh 306 | cask install 307 | #+END_SRC 308 | 309 | After the installation completes you can run the tests by running the 310 | following command 311 | 312 | #+BEGIN_SRC sh 313 | cask exec ert-runner 314 | #+END_SRC 315 | 316 | ** Thanks 317 | Special thanks to @ryanprior for bug reports and valuable feedback on the 318 | issue tracker. 319 | 320 | ** Credits 321 | Emoji set designed and offered free by [[http://emojione.com][Emoji One]]. 322 | 323 | ** Licence 324 | *** EmojiOne images 325 | The emoji images are distributed under [[http://creativecommons.org/licenses/by-sa/4.0/][Creative Commons License]] (CC-BY-SA). 326 | 327 | *** OpenMoji images 328 | All emojis designed by [[https://openmoji.org/][OpenMoji]] – the open-source emoji and icon project. License: [[https://creativecommons.org/licenses/by-sa/4.0/#][CC BY-SA 4.0]] 329 | 330 | *** Twemoji images 331 | Copyright 2018 Twitter, Inc and other contributors 332 | Code licensed under the MIT License: http://opensource.org/licenses/MIT 333 | Graphics licensed under CC-BY 4.0: https://creativecommons.org/licenses/by/4.0/ 334 | 335 | *** Source code 336 | The source code is distributed under [[http://www.gnu.org/licenses/quick-guide-gplv3.html][GNU General Public License v3]]. See [[LICENSE][LICENSE]]. 337 | -------------------------------------------------------------------------------- /test/emojify-test.el: -------------------------------------------------------------------------------- 1 | ;;; emojify-test.el --- Tests for emojify -*- lexical-binding: t; -*- 2 | 3 | ;;; Commentary: 4 | ;;; Tests for emojify, major use case is to run from the command-line 5 | ;;; but can use can be interactively as well. Do M-x `eval-buffer' RET 6 | 7 | ;;; Code: 8 | 9 | ;; For interactive testing 10 | (unless noninteractive 11 | (load (expand-file-name "test-helper.el") t)) 12 | 13 | ;; Used for testing integration with programming modes 14 | (require 'org) 15 | (require 'org-agenda) 16 | (require 'cc-mode) 17 | (require 'bytecomp) 18 | 19 | (ert-deftest emojify-tests-simple-ascii-emoji-test () 20 | :tags '(ascii simple) 21 | (emojify-tests-with-emojified-static-buffer ":)" 22 | (emojify-tests-should-be-emojified (point-min)) 23 | (should (equal (get-text-property (point-min) 'emojify-buffer) (current-buffer))) 24 | (should (equal (get-text-property (point-min) 'emojify-beginning) (point-min-marker))) 25 | (should (equal (get-text-property (point-min) 'emojify-end) (point-max-marker))) 26 | (should (equal (get-text-property (point-min) 'emojify-text) ":)")))) 27 | 28 | (ert-deftest emojify-tests-simple-github-emoji-test () 29 | :tags '(github simple) 30 | (emojify-tests-with-emojified-static-buffer ":smile:" 31 | (emojify-tests-should-be-emojified (point-min)) 32 | (should (equal (get-text-property (point) 'emojify-buffer) (current-buffer))) 33 | (should (equal (get-text-property (point-min) 'emojify-beginning) (point-min-marker))) 34 | (should (equal (get-text-property (point) 'emojify-end) (point-max-marker))) 35 | (should (equal (get-text-property (point) 'emojify-text) ":smile:")))) 36 | 37 | (ert-deftest emojify-tests-simple-unicode-emoji-test () 38 | :tags '(unicode simple) 39 | (emojify-tests-with-emojified-static-buffer "😉" 40 | (emojify-tests-should-be-emojified (point-min)) 41 | (should (equal (get-text-property (point) 'emojify-buffer) (current-buffer))) 42 | (should (equal (get-text-property (point-min) 'emojify-beginning) (point-min-marker))) 43 | (should (equal (get-text-property (point) 'emojify-end) (point-max-marker))) 44 | (should (equal (get-text-property (point) 'emojify-text) "😉"))) 45 | 46 | (emojify-tests-with-emojified-static-buffer "😉" 47 | ;; Emojis should be displayed by default in programming mode 48 | (emacs-lisp-mode) 49 | (emojify-tests-should-be-emojified (point-min)) 50 | (should (equal (get-text-property (point) 'emojify-buffer) (current-buffer))) 51 | (should (equal (get-text-property (point-min) 'emojify-beginning) (point-min-marker))) 52 | (should (equal (get-text-property (point) 'emojify-end) (point-max-marker))) 53 | (should (equal (get-text-property (point) 'emojify-text) "😉")) 54 | 55 | ;; Emojis should be displayed by default in non-programming mode 56 | (fundamental-mode) 57 | (emojify-redisplay-emojis-in-region) 58 | (emojify-tests-should-be-emojified (point-min)) 59 | 60 | ;; Emojis should not be displayed if code is not an element in emojify-program-contexts 61 | (setq emojify-program-contexts '()) 62 | (emacs-lisp-mode) 63 | (emojify-redisplay-emojis-in-region) 64 | (emojify-tests-should-not-be-emojified (point-min)))) 65 | 66 | (ert-deftest emojify-test-custom-emojis () 67 | :tags '(core custom-images) 68 | (let ((emojify-user-emojis emojify-test-custom-emojis)) 69 | (emojify-set-emoji-data) 70 | (emojify-tests-with-emojified-static-buffer ":neckbeard: 71 | :troll:" 72 | (emojify-tests-should-be-emojified (point-min)) 73 | (should (equal (get-text-property (point) 'emojify-buffer) (current-buffer))) 74 | (should (= (get-text-property (point-min) 'emojify-beginning) (point-min-marker))) 75 | (should (= (get-text-property (point) 'emojify-end) (line-end-position 1))) 76 | (should (equal (get-text-property (point) 'emojify-text) ":neckbeard:")) 77 | 78 | (emojify-tests-should-be-emojified (line-beginning-position 2)) 79 | (should (equal (get-text-property (line-beginning-position 2) 'emojify-buffer) (current-buffer))) 80 | (should (= (get-text-property (line-beginning-position 2) 'emojify-beginning) (line-beginning-position 2))) 81 | (should (= (get-text-property (line-beginning-position 2) 'emojify-end) (point-max-marker))) 82 | (should (equal (get-text-property (line-beginning-position 2) 'emojify-text) ":troll:"))))) 83 | 84 | (ert-deftest emojify-tests-mixed-emoji-test () 85 | :tags '(core mixed) 86 | (let ((emojify-user-emojis emojify-test-custom-emojis)) 87 | (emojify-set-emoji-data) 88 | (emojify-tests-with-emojified-static-buffer "😉\n:D\nD:\n:smile:\n:neckbeard:" 89 | (emojify-tests-should-be-emojified (point-min)) 90 | (emojify-tests-should-be-emojified (line-beginning-position 2)) 91 | (emojify-tests-should-be-emojified (line-beginning-position 3)) 92 | (emojify-tests-should-be-emojified (line-beginning-position 4)) 93 | (emojify-tests-should-be-emojified (line-beginning-position 5))))) 94 | 95 | (ert-deftest emojify-tests-emoji-uncovering () 96 | :tags '(behaviour point-motion) 97 | (emojify-tests-with-emojified-buffer " :)" 98 | (setq emojify-point-entered-behaviour 'uncover) 99 | (execute-kbd-macro (kbd "C-f") 2) 100 | (emojify-tests-should-be-uncovered (point)))) 101 | 102 | (ert-deftest emojify-tests-emoji-echoing () 103 | :tags '(behaviour point-motion) 104 | (emojify-tests-with-emojified-buffer " :)" 105 | (with-mock 106 | ;; Since emojify checks that there is no message being displayed 107 | ;; before echoing the emoji, we need to stub out current-message 108 | ;; too otherwise emojify does not echo the message since messages 109 | ;; from other tests are being displayed 110 | (stub current-message => nil) 111 | (mock (message ":)")) 112 | (setq emojify-point-entered-behaviour 'echo) 113 | (execute-kbd-macro (kbd "C-f")) 114 | (emojify-tests-should-be-emojified (point))))) 115 | 116 | (ert-deftest emojify-tests-custom-point-entered-function () 117 | :tags '(behaviour point-motion) 118 | (emojify-tests-with-emojified-buffer " :)" 119 | (setq emojify-point-entered-behaviour (lambda (buffer emoji-text emoji-start emoji-end) 120 | (should (equal buffer (current-buffer))) 121 | (should (equal emoji-text ":)")) 122 | (should (equal emoji-start (1+ (point-min)))) 123 | (should (equal emoji-start (point-max))))) 124 | (goto-char (1+ (point-min))) 125 | (emojify-tests-should-be-emojified (point)))) 126 | 127 | (ert-deftest emojify-tests-emojify-setting-styles () 128 | :tags '(styles github ascii) 129 | (emojify-tests-with-emojified-static-buffer ":) 😄 :smile: return" 130 | (let ((ascii-emoji-pos (point-min)) 131 | (unicode-emoji-pos (+ (point-min) (length ":) "))) 132 | (github-emoji-pos (+ (point-min) (length ":) 😄 "))) 133 | (prettify-emoji-pos (+ (point-min) (length ":) 😄 :smile: ")))) 134 | 135 | (setq emojify-composed-text-p t) 136 | (setq prettify-symbols-alist 137 | '(("return" . ?↪))) 138 | 139 | (setq emojify-composed-text-p t) 140 | 141 | (when (fboundp 'prettify-symbols-mode) 142 | (prettify-symbols-mode +1) 143 | ;; On Emacs 25.1 fontification does not happen automatically 144 | (when (fboundp 'font-lock-ensure) (font-lock-ensure))) 145 | 146 | (emojify-set-emoji-styles '(ascii)) 147 | (emojify-tests-should-be-emojified ascii-emoji-pos) 148 | (emojify-tests-should-not-be-emojified unicode-emoji-pos) 149 | (emojify-tests-should-not-be-emojified prettify-emoji-pos) 150 | (emojify-tests-should-not-be-emojified github-emoji-pos) 151 | 152 | (emojify-set-emoji-styles '(unicode)) 153 | (emojify-tests-should-not-be-emojified ascii-emoji-pos) 154 | (emojify-tests-should-be-emojified unicode-emoji-pos) 155 | (when (fboundp 'prettify-symbols-mode) 156 | (emojify-tests-should-be-emojified prettify-emoji-pos)) 157 | (emojify-tests-should-not-be-emojified github-emoji-pos) 158 | 159 | (emojify-set-emoji-styles '(github)) 160 | (emojify-tests-should-not-be-emojified ascii-emoji-pos) 161 | (emojify-tests-should-not-be-emojified unicode-emoji-pos) 162 | (emojify-tests-should-not-be-emojified prettify-emoji-pos) 163 | (emojify-tests-should-be-emojified github-emoji-pos) 164 | 165 | (emojify-set-emoji-styles '(ascii unicode github)) 166 | (emojify-tests-should-be-emojified ascii-emoji-pos) 167 | (emojify-tests-should-be-emojified unicode-emoji-pos) 168 | (when (fboundp 'prettify-symbols-mode) 169 | (emojify-tests-should-be-emojified prettify-emoji-pos)) 170 | (emojify-tests-should-be-emojified github-emoji-pos) 171 | 172 | (emojify-set-emoji-styles nil) 173 | (emojify-tests-should-not-be-emojified ascii-emoji-pos) 174 | (emojify-tests-should-not-be-emojified unicode-emoji-pos) 175 | (emojify-tests-should-not-be-emojified prettify-emoji-pos) 176 | (emojify-tests-should-not-be-emojified github-emoji-pos)))) 177 | 178 | (ert-deftest emojify-tests-program-contexts () 179 | :tags '(core prog contextual) 180 | (emojify-tests-with-emojified-static-buffer ";; :) :smile:\n\":smile:\"\n8) 💜 :smile:" 181 | (let* ((comment-ascii-emoji-pos (+ 3 (point-min))) 182 | (comment-github-emoji-pos (+ comment-ascii-emoji-pos (length ":) "))) 183 | (string-github-emoji-pos (1+ (line-beginning-position 2))) 184 | (prog-ascii-emoji-pos (line-beginning-position 3)) 185 | (prog-unicode-emoji-pos (+ 3 prog-ascii-emoji-pos)) 186 | (prog-github-emoji-pos (+ 2 prog-unicode-emoji-pos))) 187 | (emacs-lisp-mode) 188 | (setq emojify-program-contexts '(comments string code)) 189 | (emojify-redisplay-emojis-in-region) 190 | (emojify-tests-should-be-emojified comment-ascii-emoji-pos) 191 | (emojify-tests-should-be-emojified comment-github-emoji-pos) 192 | (emojify-tests-should-be-emojified string-github-emoji-pos) 193 | (emojify-tests-should-not-be-emojified prog-ascii-emoji-pos) 194 | (emojify-tests-should-be-emojified prog-unicode-emoji-pos) 195 | (emojify-tests-should-not-be-emojified prog-github-emoji-pos) 196 | 197 | (setq emojify-program-contexts '(comments)) 198 | (emojify-redisplay-emojis-in-region) 199 | (emojify-tests-should-be-emojified comment-ascii-emoji-pos) 200 | (emojify-tests-should-be-emojified comment-github-emoji-pos) 201 | (emojify-tests-should-not-be-emojified string-github-emoji-pos) 202 | (emojify-tests-should-not-be-emojified prog-ascii-emoji-pos) 203 | (emojify-tests-should-not-be-emojified prog-unicode-emoji-pos) 204 | (emojify-tests-should-not-be-emojified prog-github-emoji-pos) 205 | 206 | (setq emojify-program-contexts '(string)) 207 | (emojify-redisplay-emojis-in-region) 208 | (emojify-tests-should-not-be-emojified comment-ascii-emoji-pos) 209 | (emojify-tests-should-not-be-emojified comment-github-emoji-pos) 210 | (emojify-tests-should-be-emojified string-github-emoji-pos) 211 | (emojify-tests-should-not-be-emojified prog-ascii-emoji-pos) 212 | (emojify-tests-should-not-be-emojified prog-unicode-emoji-pos) 213 | (emojify-tests-should-not-be-emojified prog-github-emoji-pos) 214 | 215 | (setq emojify-program-contexts '(code)) 216 | (emojify-redisplay-emojis-in-region) 217 | (emojify-tests-should-not-be-emojified comment-ascii-emoji-pos) 218 | (emojify-tests-should-not-be-emojified comment-github-emoji-pos) 219 | (emojify-tests-should-not-be-emojified string-github-emoji-pos) 220 | (emojify-tests-should-not-be-emojified prog-ascii-emoji-pos) 221 | (emojify-tests-should-be-emojified prog-unicode-emoji-pos) 222 | (emojify-tests-should-not-be-emojified prog-github-emoji-pos) 223 | 224 | (setq emojify-program-contexts '()) 225 | (emojify-redisplay-emojis-in-region) 226 | (emojify-tests-should-not-be-emojified comment-ascii-emoji-pos) 227 | (emojify-tests-should-not-be-emojified comment-github-emoji-pos) 228 | (emojify-tests-should-not-be-emojified string-github-emoji-pos) 229 | (emojify-tests-should-not-be-emojified prog-ascii-emoji-pos) 230 | (emojify-tests-should-not-be-emojified prog-unicode-emoji-pos) 231 | (emojify-tests-should-not-be-emojified prog-github-emoji-pos)))) 232 | 233 | (ert-deftest emojify-tests-ascii-emoji-contexts () 234 | :tags '(core text contextual) 235 | ;; At start of comment 236 | (emojify-tests-with-emojified-static-buffer ";:)" 237 | (emacs-lisp-mode) 238 | (emojify-redisplay-emojis-in-region) 239 | (emojify-tests-should-be-emojified (1+ (point-min)))) 240 | 241 | ;; In comment after space 242 | (emojify-tests-with-emojified-static-buffer "; :)" 243 | (emacs-lisp-mode) 244 | (emojify-redisplay-emojis-in-region) 245 | (emojify-tests-should-be-emojified (+ 2 (point-min)))) 246 | 247 | ;; Inside a comment 248 | (emojify-tests-with-emojified-static-buffer "/**\n:)" 249 | (c-mode) 250 | (emojify-redisplay-emojis-in-region) 251 | (emojify-tests-should-be-emojified (line-beginning-position 2))) 252 | 253 | ;; Immediately after a word 254 | (emojify-tests-with-emojified-static-buffer "A:)" 255 | (emojify-tests-should-not-be-emojified (1+ (point-min)))) 256 | 257 | ;; Immediately before a word 258 | (emojify-tests-with-emojified-static-buffer ":)A" 259 | (emojify-tests-should-not-be-emojified (1+ (point-min)))) 260 | 261 | ;; Immediately before a closing bracket 262 | (emojify-tests-with-emojified-static-buffer ":))" 263 | (emojify-tests-should-be-emojified (1+ (point-min)))) 264 | 265 | ;; Immediately after a punctuation character 266 | (emojify-tests-with-emojified-static-buffer "!:)" 267 | (emojify-tests-should-not-be-emojified (1+ (point-min)))) 268 | 269 | ;; Following a punctuation and a space 270 | (emojify-tests-with-emojified-static-buffer "! :)" 271 | (emojify-tests-should-be-emojified (+ 2 (point-min)))) 272 | 273 | ;; Outside a comment 274 | (emojify-tests-with-emojified-static-buffer "/**/:)" 275 | (c-mode) 276 | (emojify-redisplay-emojis-in-region) 277 | (emojify-tests-should-not-be-emojified (+ 4 (point-min)))) 278 | 279 | ;; Surrounded by comments 280 | (emojify-tests-with-emojified-static-buffer "/*:)*/" 281 | (c-mode) 282 | (emojify-redisplay-emojis-in-region) 283 | (emojify-tests-should-not-be-emojified (+ 2 (point-min))))) 284 | 285 | (ert-deftest emojify-tests-multiple-emojis-in-sequence () 286 | "See Github issue #6" 287 | :tags '(core contextual) 288 | (emojify-tests-with-emojified-static-buffer ":100::smile: 289 | :100:a:smile: 290 | 🎆😃 291 | 🎆a😃 292 | 😉:wink: 293 | :100:😃 294 | :100:a😃" 295 | ;; Github emojis 296 | (emojify-tests-should-be-emojified (point-min)) 297 | (emojify-tests-should-be-emojified (+ (point-min) 5)) 298 | (emojify-tests-should-be-emojified (line-beginning-position 2)) 299 | (emojify-tests-should-be-emojified (+ (line-beginning-position 2) 6)) 300 | 301 | ;; Unicode emojis 302 | (emojify-tests-should-be-emojified (line-beginning-position 3)) 303 | (emojify-tests-should-be-emojified (+ (line-beginning-position 3) 1)) 304 | (emojify-tests-should-be-emojified (line-beginning-position 4)) 305 | (emojify-tests-should-be-emojified (+ (line-beginning-position 4) 2)) 306 | 307 | ;; Mixed emojis 308 | (emojify-tests-should-be-emojified (line-beginning-position 5)) 309 | (emojify-tests-should-be-emojified (+ (line-beginning-position 5) 1)) 310 | (emojify-tests-should-be-emojified (line-beginning-position 6)) 311 | (emojify-tests-should-be-emojified (+ (line-beginning-position 6) 5)) 312 | (emojify-tests-should-be-emojified (line-beginning-position 7)) 313 | (emojify-tests-should-be-emojified (+ (line-beginning-position 7) 6)))) 314 | 315 | (ert-deftest emojify-tests-emojifying-lists () 316 | :tags '(core contextual) 317 | (emojify-tests-with-emojified-static-buffer ":]" 318 | (emojify-tests-should-be-emojified (point-min))) 319 | 320 | (emojify-tests-with-emojified-static-buffer "[ :]" 321 | (emojify-tests-should-not-be-emojified (+ 3 (point-min)))) 322 | 323 | (emojify-tests-with-emojified-static-buffer ";; 8)" 324 | (emojify-tests-should-be-emojified (+ 3 (point-min)))) 325 | 326 | (emojify-tests-with-emojified-static-buffer ":( 327 | :)" 328 | (fundamental-mode) 329 | (emojify-redisplay-emojis-in-region) 330 | (emojify-tests-should-be-emojified (point-min)) 331 | (emojify-tests-should-be-emojified (line-beginning-position 2))) 332 | 333 | (emojify-tests-with-emojified-static-buffer "( 334 | :)" 335 | (fundamental-mode) 336 | (emojify-redisplay-emojis-in-region) 337 | (emojify-tests-should-not-be-emojified (point-min)) 338 | (emojify-tests-should-not-be-emojified (line-beginning-position 2))) 339 | 340 | (emojify-tests-with-emojified-static-buffer ";; (lambda () 8)" 341 | (emacs-lisp-mode) 342 | (emojify-redisplay-emojis-in-region) 343 | (emojify-tests-should-not-be-emojified (+ 14 (point-min))))) 344 | 345 | (ert-deftest emojify-tests-overlapping-emojis () 346 | :tags '(core) 347 | (emojify-tests-with-emojified-static-buffer "👲🏽" 348 | (fundamental-mode) 349 | (let ((count 0)) 350 | (emojify-do-for-emojis-in-region (point-min) (point-max) 351 | (cl-incf count)) 352 | ;; Only one emoji should be displayed 353 | (should (= count 1)) 354 | ;; The larger emoji should be preferred 355 | (should (string= (get-text-property (point-min) 'emojify-text) 356 | "👲🏽"))))) 357 | 358 | (ert-deftest emojify-tests-emojifying-org-mode-buffers () 359 | :tags '(org-mode contextual) 360 | (emojify-tests-with-emojified-static-buffer "* Test :books:\n:books:\n* Test :book:tag:\n" 361 | (org-mode) 362 | (emojify-redisplay-emojis-in-region) 363 | (emojify-tests-should-not-be-emojified (1- (line-end-position))) 364 | (emojify-tests-should-be-emojified (line-beginning-position 2)) 365 | (emojify-tests-should-not-be-emojified (- (line-end-position 3) 5))) 366 | 367 | (emojify-tests-with-emojified-static-buffer "8)" 368 | ;; org-mode in Emacs v24.3 failed in read only buffers 369 | ;; if first item was not a headline 370 | (with-mock (stub org-set-startup-visibility => nil) 371 | (org-mode)) 372 | (emojify-redisplay-emojis-in-region) 373 | (emojify-tests-should-not-be-emojified (point-min))) 374 | 375 | (emojify-tests-with-emojified-static-buffer "* 8)" 376 | (org-mode) 377 | (emojify-redisplay-emojis-in-region) 378 | (emojify-tests-should-be-emojified (1- (point-max)))) 379 | 380 | (emojify-tests-with-emojified-static-buffer "#+BEGIN_SRC emacs-lisp\n:)\n#+END_SRC" 381 | (org-mode) 382 | (emojify-redisplay-emojis-in-region) 383 | (emojify-tests-should-not-be-emojified (line-beginning-position 2))) 384 | 385 | ;; TODO: This does not work yet 386 | ;; (emojify-tests-with-emojified-static-buffer "8) 8)" 387 | ;; (org-mode) 388 | ;; (emojify-redisplay-emojis-in-region) 389 | ;; (emojify-tests-should-be-emojified (1- (point-max))) 390 | ;; (emojify-tests-should-not-be-emojified (1+ (point-min)))) 391 | 392 | ;; 8) should not emojified if it is a list item 393 | (emojify-tests-with-emojified-static-buffer "7) *) 394 | 8) 8) 395 | 9) :/" 396 | (org-mode) 397 | (emojify-redisplay-emojis-in-region) 398 | (emojify-tests-should-not-be-emojified (line-beginning-position 2)) 399 | (emojify-tests-should-be-emojified (1- (line-end-position 2)))) 400 | 401 | ;; Emojis that are part of org-mode tags should not be emojified 402 | (emojify-tests-with-emojified-static-buffer "* Test :p\n* Test2 :p:" 403 | (org-mode) 404 | (emojify-redisplay-emojis-in-region) 405 | (emojify-tests-should-be-emojified (1- (line-end-position))) 406 | (emojify-tests-should-not-be-emojified (- (line-end-position 2) 3))) 407 | 408 | (emojify-tests-with-emojified-static-buffer "* Test :books:\n:books:" 409 | (org-agenda-mode) 410 | (emojify-redisplay-emojis-in-region) 411 | (emojify-tests-should-not-be-emojified (1- (line-end-position))) 412 | (emojify-tests-should-be-emojified (line-beginning-position 2)))) 413 | 414 | (ert-deftest emojify-tests-uncover-on-isearch () 415 | :tags '(isearch) 416 | (emojify-tests-with-emojified-buffer "Testing isearch\n:books:" 417 | (with-mock 418 | (setq emojify-reveal-on-isearch t) 419 | ;; We do not want to be bothered with isearch messages 420 | (stub message => nil) 421 | (emojify-tests-should-be-emojified (line-beginning-position 2)) 422 | (isearch-mode +1) 423 | (execute-kbd-macro "boo") 424 | ;; Emoji should be uncovered when point enters it in isearch-mode 425 | (emojify-tests-should-be-uncovered (line-beginning-position)) 426 | ;; Emoji should be restored on leaving the underlying text 427 | (execute-kbd-macro "") 428 | (emojify-tests-should-be-emojified (line-beginning-position 2)) 429 | 430 | ;; Turn off revealing on isearch 431 | (setq emojify-reveal-on-isearch nil) 432 | ;; We do not want to be bothered with isearch messages 433 | (stub message => nil) 434 | (emojify-tests-should-be-emojified (line-beginning-position 2)) 435 | (isearch-mode +1) 436 | (execute-kbd-macro "boo") 437 | ;; Emoji should be uncovered when point enters it in isearch-mode 438 | (emojify-tests-should-be-emojified (line-beginning-position))))) 439 | 440 | (ert-deftest emojify-tests-electric-delete () 441 | :tags '(electric-delete) 442 | (emojify-tests-with-emojified-buffer "Unicode emoji 😉\nGithub emoji :wink:\nAscii emoji ;)" 443 | (goto-char (line-end-position)) 444 | (let ((final-line-end (get-text-property (1- (point)) 'emojify-beginning))) 445 | (execute-kbd-macro [backspace]) 446 | (emojify-tests-should-not-be-emojified (line-end-position)) 447 | (should (equal (copy-marker (line-end-position)) final-line-end)))) 448 | 449 | (emojify-tests-with-emojified-buffer "Unicode emoji 😉\nGithub emoji :wink:\nAscii emoji ;)" 450 | (goto-char (line-end-position 2)) 451 | (let ((final-line-end (get-text-property (1- (point)) 'emojify-beginning))) 452 | (execute-kbd-macro [backspace]) 453 | (emojify-tests-should-not-be-emojified (line-end-position)) 454 | (should (equal (copy-marker (line-end-position)) final-line-end)))) 455 | 456 | (emojify-tests-with-emojified-buffer "Unicode emoji 😉\nGithub emoji :wink:\nAscii emoji ;)" 457 | (goto-char (line-end-position 3)) 458 | (let ((final-line-end (get-text-property (1- (point)) 'emojify-beginning))) 459 | (execute-kbd-macro [backspace]) 460 | (emojify-tests-should-not-be-emojified (line-end-position)) 461 | (should (equal (copy-marker (line-end-position)) final-line-end)))) 462 | 463 | (emojify-tests-with-emojified-buffer ";) 😉:wink:" 464 | (dotimes (n 4) 465 | (execute-kbd-macro (kbd "C-d")) 466 | (emojify-redisplay-emojis-in-region)) 467 | (should (equal (point-min) (point-max)))) 468 | 469 | (emojify-tests-with-emojified-buffer "😉:wink: ;)" 470 | (goto-char (point-max)) 471 | (dotimes (_ 4) 472 | (execute-kbd-macro [backspace])) 473 | (should (equal (point-min) (point-max)))) 474 | 475 | (emojify-tests-with-emojified-buffer "😉 :smile:" 476 | (goto-char (1+ (point-min))) 477 | (dotimes (_ 3) 478 | (execute-kbd-macro (kbd "C-d")) 479 | (emojify-redisplay-emojis-in-region)) 480 | (should (equal (1+ (point-min)) (point-max)))) 481 | 482 | (emojify-tests-with-emojified-buffer "😉:wink: ;)" 483 | "Integration with delsel mode" 484 | (with-mock 485 | (stub message => nil) 486 | (delete-selection-mode +1) 487 | (set-mark-command nil) 488 | (activate-mark) 489 | (goto-char (point-max)) 490 | (exchange-point-and-mark) 491 | (let ((this-command 'emojify-delete-emoji-forward)) 492 | (delete-selection-pre-hook)) 493 | (should (equal (point-min) (point-max)))))) 494 | 495 | (ert-deftest emojify-test-composition () 496 | :tags '(composition) 497 | (emojify-tests-with-emojified-static-buffer "a == b" 498 | (setq emojify-composed-text-p t) 499 | (compose-region 1 2 (concat " " (list #Xe154))) 500 | (compose-region 3 5 (concat " " (list #Xe10f))) 501 | (compose-region 6 7 (concat " " (list #Xe155))) 502 | (emojify-string (buffer-string)))) 503 | 504 | (ert-deftest emojify-tests-prettify-symbols () 505 | :tags '(prettify-symbols) 506 | (when (fboundp 'prettify-symbols-mode) 507 | (emojify-tests-with-emojified-static-buffer "try: 508 | x = 1 509 | except: 510 | raise(Exception) 511 | lambdalambda 512 | \"lambda\" 513 | yield 3 514 | return 4 515 | " 516 | (setq emojify-composed-text-p t) 517 | (emojify-set-emoji-styles '(ascii unicode github)) 518 | (python-mode) 519 | (setq-local prettify-symbols-alist 520 | '(("return" . ?↪) 521 | ("try" . ?😱) 522 | ("except" . ?⛐) 523 | ("raise" . ?💥))) 524 | (emojify-tests-should-not-be-emojified (point-min)) 525 | (emojify-tests-should-not-be-emojified (line-beginning-position 3)) 526 | (emojify-tests-should-not-be-emojified (+ (line-beginning-position 4) 5)) 527 | (emojify-tests-should-not-be-emojified (line-beginning-position 5)) 528 | (emojify-tests-should-not-be-emojified (line-beginning-position 6)) 529 | (emojify-tests-should-not-be-emojified (line-beginning-position 7)) 530 | (emojify-tests-should-not-be-emojified (line-beginning-position 8)) 531 | (prettify-symbols-mode +1) 532 | ;; On Emacs 25.1 fontification does not happen automatically 533 | (when (fboundp 'font-lock-ensure) 534 | (font-lock-ensure) 535 | (emojify-redisplay-emojis-in-region)) 536 | (emojify-tests-should-be-emojified (point-min)) 537 | (should (equal (get-text-property (point-min) 'emojify-text) "😱")) 538 | (emojify-tests-should-not-be-emojified (line-beginning-position 3)) 539 | (emojify-tests-should-be-emojified (+ (line-beginning-position 4) 5)) 540 | (should (equal (get-text-property (+ (line-beginning-position 4) 5) 'emojify-text) "💥")) 541 | (emojify-tests-should-not-be-emojified (line-beginning-position 5)) 542 | (emojify-tests-should-not-be-emojified (line-beginning-position 6)) 543 | (emojify-tests-should-not-be-emojified (line-beginning-position 7)) 544 | (emojify-tests-should-be-emojified (line-beginning-position 8)) 545 | (should (equal (get-text-property (line-beginning-position 8) 'emojify-text) "↪")) 546 | (prettify-symbols-mode -1) 547 | ;; On Emacs 25.1 fontification does not happen automatically 548 | (when (fboundp 'font-lock-ensure) 549 | (font-lock-ensure) 550 | (emojify-redisplay-emojis-in-region)) 551 | (emojify-tests-should-not-be-emojified (point-min)) 552 | (emojify-tests-should-not-be-emojified (line-beginning-position 3)) 553 | (emojify-tests-should-not-be-emojified (+ (line-beginning-position 4) 5)) 554 | (emojify-tests-should-not-be-emojified (line-beginning-position 6)) 555 | (emojify-tests-should-not-be-emojified (line-beginning-position 5)) 556 | (emojify-tests-should-not-be-emojified (line-beginning-position 6)) 557 | (emojify-tests-should-not-be-emojified (line-beginning-position 7))))) 558 | 559 | 560 | (unless (version< emacs-version "26.1") 561 | (ert-deftest emojify-tests-org-source-blocks () 562 | :tags '(org-mode contextual) 563 | (emojify-tests-with-emojified-static-buffer "#+begin_src markdown 564 | :smile: 565 | :) 566 | :slight_smile: 567 | #+end_src 568 | 569 | #+begin_src emacs-lisp 570 | \":smile:\" 571 | ;; :smile: 572 | :smile: 573 | 574 | \":)\" 575 | ;; :) 576 | :) 577 | 578 | \"🙂\" 579 | ;; 🙂 580 | 🙂 581 | #+end_src 582 | " 583 | (emojify-set-emoji-styles '(ascii unicode github)) 584 | (setq emojify-program-contexts '(comments string code)) 585 | 586 | (org-mode) 587 | (when (fboundp 'font-lock-ensure) 588 | (font-lock-ensure) 589 | (emojify-redisplay-emojis-in-region)) 590 | 591 | ;; The emojis should be always displayed for org source block for 592 | ;; non-programming languages 593 | (emojify-tests-should-be-emojified (line-beginning-position 2)) 594 | (emojify-tests-should-be-emojified (line-beginning-position 3)) 595 | (emojify-tests-should-be-emojified (line-beginning-position 4)) 596 | 597 | ;; In org source block for programming languages emojis should 598 | ;; be displayed according to emojify-program-contexts 599 | (emojify-tests-should-be-emojified (1+ (line-beginning-position 8))) 600 | (emojify-tests-should-be-emojified (+ 3 (line-beginning-position 9))) 601 | (emojify-tests-should-not-be-emojified (line-beginning-position 10)) 602 | 603 | (emojify-tests-should-be-emojified (1+ (line-beginning-position 12))) 604 | (emojify-tests-should-be-emojified (+ 3 (line-beginning-position 13))) 605 | (emojify-tests-should-not-be-emojified (line-beginning-position 14)) 606 | 607 | (emojify-tests-should-be-emojified (1+ (line-beginning-position 16))) 608 | (emojify-tests-should-be-emojified (+ 3 (line-beginning-position 17))) 609 | (emojify-tests-should-be-emojified (line-beginning-position 18))))) 610 | 611 | (ert-deftest emojify-tests-prettify-symbols-with-custom-images () 612 | :tags '(prettify-symbols) 613 | (when (fboundp 'prettify-symbols-mode) 614 | (let ((emojify-user-emojis emojify-test-custom-emojis)) 615 | (emojify-set-emoji-data) 616 | (emojify-tests-with-emojified-static-buffer "try: 617 | lambda x: x 618 | except: 619 | raise(Exception) 620 | 621 | yield 3 622 | return 4 623 | " 624 | (setq emojify-composed-text-p t) 625 | (emojify-set-emoji-styles '(ascii unicode github)) 626 | (python-mode) 627 | (setq-local prettify-symbols-alist 628 | '(("return" . ?↪) 629 | ("try" . ?😱) 630 | ("except" . ?⛐) 631 | ("lambda" . ?λ) 632 | ("raise" . ?💥))) 633 | (emojify-tests-should-not-be-emojified (+ (line-beginning-position 2) 5)) 634 | (prettify-symbols-mode +1) 635 | ;; On Emacs 25.1 fontification does not happen automatically 636 | (when (fboundp 'font-lock-ensure) 637 | (font-lock-ensure) 638 | (emojify-redisplay-emojis-in-region)) 639 | (emojify-tests-should-be-emojified (point-min)) 640 | (emojify-tests-should-be-emojified (+ (line-beginning-position 2) 5)) 641 | (emojify-tests-should-not-be-emojified (line-beginning-position 3)) 642 | (emojify-tests-should-be-emojified (+ (line-beginning-position 4) 5)) 643 | (emojify-tests-should-not-be-emojified (line-beginning-position 6)) 644 | (emojify-tests-should-be-emojified (line-beginning-position 7)))))) 645 | 646 | (ert-deftest emojify-tests-apropos () 647 | :tags '(apropos) 648 | (emojify-apropos-emoji "squi") 649 | ;; Window with results should be visible 650 | (with-mock 651 | (stub message => nil) 652 | (should (get-buffer-window emojify-apropos-buffer-name)) 653 | (let ((matches 0)) 654 | 655 | (with-current-buffer emojify-apropos-buffer-name 656 | ;; Force a display of emojis 657 | (emojify-redisplay-emojis-in-region (point-min) (point-max)) 658 | (emojify-do-for-emojis-in-region (point-min) (point-max) 659 | (goto-char emoji-start) 660 | (call-interactively #'emojify-apropos-copy-emoji) 661 | (should (string= (car kill-ring) (get-text-property (point) 'emojify-text))) 662 | (cl-incf matches))) 663 | 664 | (should (= matches 2))) 665 | 666 | ;; Test with custom emoji 667 | (let ((emojify-user-emojis emojify-test-custom-emojis) 668 | (matches 0)) 669 | 670 | (emojify-set-emoji-data) 671 | (emojify-apropos-emoji "lambda") 672 | (should (get-buffer-window emojify-apropos-buffer-name)) 673 | 674 | (with-current-buffer emojify-apropos-buffer-name 675 | (emojify-redisplay-emojis-in-region (point-min) (point-max)) 676 | (emojify-do-for-emojis-in-region (point-min) (point-max) 677 | (goto-char emoji-start) 678 | (call-interactively #'emojify-apropos-copy-emoji) 679 | (should (string= (car kill-ring) (get-text-property (point) 'emojify-text))) 680 | (cl-incf matches))) 681 | 682 | (should (= matches 1))))) 683 | 684 | (ert-deftest emojify-tests-download-confirmation () 685 | :tags '(download) 686 | ;; Tests in interactive mode 687 | (let ((noninteractive nil)) 688 | ;; Prompt user for confirmation if `emojify-download-emojis-p' is `ask' 689 | (let ((emojify-download-emojis-p 'ask)) 690 | (with-mock 691 | (mock (yes-or-no-p *) => t) 692 | (should (emojify--confirm-emoji-download))) 693 | (with-mock 694 | (mock (yes-or-no-p *) => nil) 695 | (should-not (emojify--confirm-emoji-download)))) 696 | 697 | ;; Do not prompt user for confirmation if `emojify-download-emojis-p' is `t' 698 | (let ((emojify-download-emojis-p t)) 699 | (with-mock 700 | (stub yes-or-no-p => (error "`emojify--confirm-emoji-download' should prompt user, if `emojify-download-emojis-p' is t")) 701 | (should (emojify--confirm-emoji-download)))) 702 | 703 | ;; Do not prompt user for confirmation if `emojify-download-emojis-p' is `nil' 704 | (let ((emojify-download-emojis-p nil)) 705 | (with-mock 706 | (stub yes-or-no-p => (error "`emojify--confirm-emoji-download' should prompt user, if `emojify-download-emojis-p' is nil")) 707 | (should-not (emojify--confirm-emoji-download))))) 708 | 709 | ;; Do not prompt user for confirmation in noninteractive mode 710 | (let ((emojify-download-emojis-p 'ask) 711 | (noninteractive t)) 712 | (with-mock 713 | (stub yes-or-no-p => (error "`emojify--confirm-emoji-download' should prompt user, in non-interactive mode")) 714 | (should-not (emojify--confirm-emoji-download))))) 715 | 716 | (ert-deftest emojify-tests-no-byte-compilation-warnings () 717 | :tags '(byte-compilation) 718 | (with-mock 719 | (stub message => nil) 720 | (stub byte-compile-dest-file => "/tmp/emojify.elc") 721 | (should (byte-compile-file (locate-library "emojify.el"))))) 722 | 723 | ;; So that tests can be run simply by doing `eval-buffer' 724 | (unless noninteractive 725 | (ert "^emojify-")) 726 | 727 | (provide 'emojify-test) 728 | ;;; emojify-test.el ends here 729 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | -------------------------------------------------------------------------------- /emojify.el: -------------------------------------------------------------------------------- 1 | ;;; emojify.el --- Display emojis in Emacs -*- lexical-binding: t; -*- 2 | 3 | ;; Copyright (C) 2015-2018 Iqbal Ansari 4 | 5 | ;; Author: Iqbal Ansari 6 | ;; Keywords: multimedia, convenience 7 | ;; URL: https://github.com/iqbalansari/emacs-emojify 8 | ;; Version: 1.2.1 9 | ;; Package-Requires: ((seq "1.11") (ht "2.0") (emacs "24.3")) 10 | 11 | ;; This program is free software; you can redistribute it and/or modify 12 | ;; it under the terms of the GNU General Public License as published by 13 | ;; the Free Software Foundation, either version 3 of the License, or 14 | ;; (at your option) any later version. 15 | 16 | ;; This program is distributed in the hope that it will be useful, 17 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | ;; GNU General Public License for more details. 20 | 21 | ;; You should have received a copy of the GNU General Public License 22 | ;; along with this program. If not, see . 23 | 24 | ;;; Commentary: 25 | 26 | ;; This package displays emojis in Emacs similar to how Github, Slack etc do. It 27 | ;; can display plain ascii like ':)' as well as Github style emojis like ':smile:' 28 | ;; 29 | ;; It provides a minor mode `emojify-mode' to enable display of emojis in a buffer. 30 | ;; To enable emojify mode globally use `global-emojify-mode' 31 | ;; 32 | ;; For detailed documentation see the projects README file at 33 | ;; https://github.com/iqbalansari/emacs-emojify 34 | 35 | 36 | 37 | ;;; Code: 38 | 39 | (require 'seq) 40 | (require 'ht) 41 | 42 | (require 'subr-x nil :no-error) 43 | (require 'cl-lib) 44 | (require 'json) 45 | (require 'regexp-opt) 46 | (require 'jit-lock) 47 | (require 'pcase) 48 | (require 'tar-mode) 49 | (require 'apropos) 50 | 51 | 52 | 53 | ;; Satisfying the byte-compiler 54 | ;; We do not "require" these functions but if `org-mode' is active we use them 55 | 56 | ;; Required to determine point is in an org-list 57 | (declare-function org-list-get-item-begin "org-list") 58 | (declare-function org-at-heading-p "org") 59 | 60 | ;; Required to determine the context is in an org-src block 61 | (declare-function org-element-type "org-element") 62 | (declare-function org-element-property "org-element") 63 | (declare-function org-element-at-point "org-element") 64 | (declare-function org-src-get-lang-mode "org-src") 65 | (declare-function org-src--get-lang-mode "org-src") 66 | 67 | ;; Required for integration with company-mode 68 | (declare-function company-pseudo-tooltip-unhide "company") 69 | 70 | ;; Shouldn't require 'jit-lock be enough :/ 71 | (defvar jit-lock-start) 72 | (defvar jit-lock-end) 73 | 74 | ;; Used while inserting emojis using helm 75 | (defvar helm-buffer) 76 | (defvar helm-after-initialize-hook) 77 | 78 | 79 | 80 | ;; Compatibility functions 81 | 82 | (defun emojify-user-error (format &rest args) 83 | "Signal a pilot error, making a message by passing FORMAT and ARGS to ‘format-message’." 84 | (if (fboundp 'user-error) 85 | (apply #'user-error format args) 86 | (apply #'error format args))) 87 | 88 | (defun emojify-face-height (face) 89 | "Get font height for the FACE." 90 | (let ((face-font (face-font face))) 91 | (cond 92 | ((and (display-multi-font-p) 93 | ;; Avoid calling font-info if the frame's default font was 94 | ;; not changed since the frame was created. That's because 95 | ;; font-info is expensive for some fonts, see bug #14838. 96 | (not (string= (frame-parameter nil 'font) face-font))) 97 | (aref (font-info face-font) 3)) 98 | (t (frame-char-height))))) 99 | 100 | (defun emojify-default-font-height () 101 | "Return the height in pixels of the current buffer's default face font. 102 | 103 | `default-font-height' seems to be available only on Emacs versions after 24.3. 104 | This provides a compatibility version for previous versions." 105 | (if (fboundp 'default-font-height) 106 | (default-font-height) 107 | (emojify-face-height 'default))) 108 | 109 | (defun emojify-overlays-at (pos &optional sorted) 110 | "Return a list of the overlays that contain the character at POS. 111 | If SORTED is non-nil, then sort them by decreasing priority. 112 | 113 | The SORTED argument was introduced in Emacs 24.4, along with the incompatible 114 | change that overlay priorities can be any Lisp object (earlier they were 115 | restricted to integer and nil). This version uses the SORTED argument of 116 | `overlays-at' on Emacs version 24.4 onwards and manually sorts the overlays by 117 | priority on lower versions." 118 | (if (version< emacs-version "24.4") 119 | (let ((overlays-at-pos (overlays-at pos))) 120 | (if sorted 121 | (seq-sort (lambda (overlay1 overlay2) 122 | (if (and (overlay-get overlay2 'priority) 123 | (overlay-get overlay1 'priority)) 124 | ;; If both overlays have priorities compare them 125 | (< (overlay-get overlay1 'priority) 126 | (overlay-get overlay2 'priority)) 127 | ;; Otherwise overlay with nil priority is sorted below 128 | ;; the one with integer value otherwise preserve order 129 | (not (overlay-get overlay1 'priority)))) 130 | overlays-at-pos) 131 | overlays-at-pos)) 132 | (overlays-at pos sorted))) 133 | 134 | (defun emojify-string-join (strings &optional separator) 135 | "Join all STRINGS using SEPARATOR. 136 | 137 | This function is available on Emacs v24.4 and higher, it has been 138 | backported here for compatibility with older Emacsen." 139 | (if (fboundp 'string-join) 140 | (apply #'string-join (list strings separator)) 141 | (mapconcat 'identity strings separator))) 142 | 143 | (defun emojify-provided-mode-derived-p (mode &rest modes) 144 | "Non-nil if MODE is derived from one of MODES. 145 | Uses the `derived-mode-parent' property of the symbol to trace backwards. 146 | If you just want to check `major-mode', use `derived-mode-p'." 147 | (if (fboundp 'provided-mode-derived-p) 148 | (apply #'provided-mode-derived-p mode modes) 149 | (while (and (not (memq mode modes)) 150 | (setq mode (get mode 'derived-mode-parent)))) 151 | mode)) 152 | 153 | (defun emojify-org-src-get-lang-mode (lang) 154 | "Return major mode that should be used for LANG. 155 | LANG is a string, and the returned major mode is a symbol." 156 | (if (fboundp 'org-src-get-lang-mode) 157 | (org-src-get-lang-mode lang) 158 | (org-src--get-lang-mode lang))) 159 | 160 | 161 | 162 | ;; Debugging helpers 163 | 164 | (define-minor-mode emojify-debug-mode 165 | "Enable debugging for emojify-mode. 166 | 167 | By default emojify silences any errors during emoji redisplay. This is done 168 | since emojis are redisplayed using jit-lock (the same mechanism used for 169 | font-lock) as such any bugs in the code can cause other important things to 170 | fail. This also turns on jit-debug-mode so that (e)debugging emojify's redisplay 171 | functions work." 172 | :init-value nil 173 | (if emojify-debug-mode 174 | (when (fboundp 'jit-lock-debug-mode) 175 | (jit-lock-debug-mode +1)) 176 | (when (fboundp 'jit-lock-debug-mode) 177 | (jit-lock-debug-mode -1)))) 178 | 179 | (defmacro emojify-execute-ignoring-errors-unless-debug (&rest forms) 180 | "Execute FORMS ignoring errors unless variable `emojify-debug-mode' is non-nil." 181 | (declare (debug t) (indent 0)) 182 | `(if emojify-debug-mode 183 | (progn 184 | ,@forms) 185 | (ignore-errors 186 | ,@forms))) 187 | 188 | 189 | 190 | ;; Utility functions 191 | 192 | ;; These should be bound dynamically by functions calling 193 | ;; `emojify--inside-rectangle-selection-p' and 194 | ;; `emojify--inside-non-rectangle-selection-p' to region-beginning and 195 | ;; region-end respectively. This is needed mark the original region which is 196 | ;; impossible to get after point moves during processing. 197 | (defvar emojify-region-beg nil) 198 | (defvar emojify-region-end nil) 199 | 200 | ;; This should be bound dynamically to the location of point before emojify's 201 | ;; display loop, this since getting the point after point moves during 202 | ;; processing is impossible 203 | (defvar emojify-current-point nil) 204 | 205 | (defmacro emojify-with-saved-buffer-state (&rest forms) 206 | "Execute FORMS saving current buffer state. 207 | 208 | This saves point and mark, `match-data' and buffer modification state it also 209 | inhibits buffer change, point motion hooks." 210 | (declare (debug t) (indent 0)) 211 | `(let ((inhibit-point-motion-hooks t) 212 | (emojify-current-point (point)) 213 | (emojify-region-beg (when (region-active-p) (region-beginning))) 214 | (emojify-region-end (when (region-active-p) (region-end)))) 215 | (with-silent-modifications 216 | (save-match-data 217 | (save-excursion 218 | (save-restriction 219 | (widen) 220 | ,@forms)))))) 221 | 222 | (defmacro emojify-do-for-emojis-in-region (beg end &rest forms) 223 | "For all emojis between BEG and END, execute the given FORMS. 224 | 225 | During the execution `emoji-start' and `emoji-end' are bound to the start 226 | and end of the emoji for which the form is being executed." 227 | (declare (debug t) (indent 2)) 228 | `(let ((--emojify-loop-current-pos ,beg) 229 | (--emojify-loop-end ,end) 230 | (--emoji-positions nil) 231 | --emoji-start) 232 | (while (and (> --emojify-loop-end --emojify-loop-current-pos) 233 | (setq --emoji-start (text-property-any --emojify-loop-current-pos --emojify-loop-end 'emojified t))) 234 | (let ((--emoji-end (+ --emoji-start 235 | (length (get-text-property --emoji-start 'emojify-text))))) 236 | (push (cons --emoji-start --emoji-end) --emoji-positions) 237 | (setq --emojify-loop-current-pos --emoji-end))) 238 | (dolist (--position --emoji-positions) 239 | (let ((emoji-start (car --position)) 240 | (emoji-end (cdr --position))) 241 | ,@forms)))) 242 | 243 | (defun emojify-message (format-string &rest args) 244 | "Log debugging messages to buffer named 'emojify-log'. 245 | 246 | This is a substitute to `message' since using it during redisplay causes errors. 247 | FORMAT-STRING and ARGS are same as the arguments to `message'." 248 | (when emojify-debug-mode 249 | (emojify-with-saved-buffer-state 250 | (with-current-buffer (get-buffer-create "emojify-log") 251 | (goto-char (point-max)) 252 | (insert (apply #'format format-string args)) 253 | (insert "\n"))))) 254 | 255 | (defun emojify--get-relevant-region () 256 | "Try getting region in buffer that completely covers the current window. 257 | 258 | This is used instead of directly using `window-start' and `window-end', since 259 | they return the values corresponding buffer in currently selected window, which 260 | is incorrect if the buffer where there are called is not actually the buffer 261 | visible in the selected window." 262 | (let* ((window-size (- (window-end) (window-start))) 263 | (start (max (- (point) window-size) (point-min))) 264 | (end (min (+ (point) window-size) (point-max)))) 265 | (cons start end))) 266 | 267 | (defun emojify-quit-buffer () 268 | "Hide the current buffer. 269 | There are windows other than the one the current buffer is displayed in quit the 270 | current window too." 271 | (interactive) 272 | (if (= (length (window-list)) 1) 273 | (bury-buffer) 274 | (quit-window))) 275 | 276 | (defvar emojify-common-mode-map 277 | (let ((map (make-sparse-keymap))) 278 | (define-key map "q" #'emojify-quit-buffer) 279 | (define-key map "n" #'next-line) 280 | (define-key map "p" #'previous-line) 281 | (define-key map "r" #'isearch-backward) 282 | (define-key map "s" #'isearch-forward) 283 | (define-key map ">" #'end-of-buffer) 284 | (define-key map "<" #'beginning-of-buffer) 285 | 286 | (dolist (key '("?" "h" "H")) 287 | (define-key map key #'describe-mode)) 288 | 289 | (dolist (number (number-sequence 0 9)) 290 | (define-key map (number-to-string number) #'digit-argument)) 291 | 292 | map) 293 | "Common keybindings available in all special emojify buffers.") 294 | 295 | 296 | 297 | ;; Customizations for control how emojis are displayed 298 | 299 | (defgroup emojify nil 300 | "Customization options for emojify" 301 | :group 'display 302 | :prefix "emojify-") 303 | 304 | (defcustom emojify-emoji-json 305 | (expand-file-name "data/emoji.json" 306 | (cond (load-file-name (file-name-directory load-file-name)) 307 | ((locate-library "emojify") (file-name-directory (locate-library "emojify"))) 308 | (t default-directory))) 309 | "The path to JSON file containing the configuration for displaying emojis." 310 | :type 'file 311 | :group 'emojify) 312 | 313 | (defvar emojify-emoji-set-json 314 | (let ((json-array-type 'list) 315 | (json-object-type 'hash-table)) 316 | (json-read-file (expand-file-name "data/emoji-sets.json" 317 | (cond (load-file-name (file-name-directory load-file-name)) 318 | ((locate-library "emojify") (file-name-directory (locate-library "emojify"))) 319 | (t default-directory)))))) 320 | 321 | (defcustom emojify-emoji-set "emojione-v2.2.6-22" 322 | "The emoji set used to display emojis." 323 | :type (append '(radio :tag "Emoji set") 324 | (mapcar (lambda (set) (list 'const set)) 325 | (ht-keys emojify-emoji-set-json))) 326 | :group 'emojify) 327 | 328 | (defcustom emojify-emojis-dir 329 | (locate-user-emacs-file "emojis") 330 | "Path to the directory containing the emoji images." 331 | :type 'directory 332 | :group 'emojify) 333 | 334 | (defcustom emojify-display-style 335 | 'image 336 | "How the emoji's be displayed. 337 | 338 | Possible values are 339 | `image' - Display emojis using images, this requires images are supported by 340 | user's Emacs installation 341 | `unicode' - Display emojis using unicode characters, this works well on 342 | platforms with good emoji fonts. In this case the emoji text 343 | ':wink:' will be substituted with 😉. 344 | `ascii' - Display emojis as ascii characters, this is simplest and does not 345 | require any external dependencies. In this cases emoji text like 346 | ':wink:' are substituted with ascii equivalents like ';)'" 347 | :type '(radio :tag "Emoji display style" 348 | (const :tag "Display emojis as images" image) 349 | (const :tag "Display emojis as unicode characters" unicode) 350 | (const :tag "Display emojis as ascii string" ascii)) 351 | :group 'emojify) 352 | 353 | 354 | 355 | ;; Customizations to control the enabling of emojify-mode 356 | 357 | (defcustom emojify-inhibit-major-modes 358 | '(dired-mode 359 | doc-view-mode 360 | debugger-mode 361 | pdf-view-mode 362 | image-mode 363 | help-mode 364 | ibuffer-mode 365 | magit-popup-mode 366 | magit-diff-mode 367 | ert-results-mode 368 | compilation-mode 369 | proced-mode 370 | mu4e-headers-mode 371 | deft-mode) 372 | "Major modes where emojify mode should not be enabled." 373 | :type '(repeat symbol) 374 | :group 'emojify) 375 | 376 | (defcustom emojify-inhibit-in-buffer-functions 377 | '(emojify-minibuffer-p emojify-helm-buffer-p) 378 | "Functions used inhibit emojify-mode in a buffer. 379 | 380 | These functions are called with one argument, the buffer where command 381 | ‘emojify-mode’ is about to be enabled, emojify is not enabled if any of the 382 | functions return a non-nil value." 383 | :type 'hook 384 | :group 'emojify) 385 | 386 | (defvar emojify-inhibit-emojify-in-current-buffer-p nil 387 | "Should emojify be inhibited in current buffer. 388 | 389 | This is a buffer local variable that can be set to inhibit enabling of 390 | emojify in a buffer.") 391 | (make-variable-buffer-local 'emojify-inhibit-emojify-in-current-buffer-p) 392 | 393 | (defvar emojify-minibuffer-reading-emojis-p nil 394 | "Are we currently reading emojis using minibuffer?") 395 | 396 | (defun emojify-ephemeral-buffer-p (buffer) 397 | "Determine if BUFFER is an ephemeral/temporary buffer." 398 | (and (not (minibufferp)) 399 | (string-match-p "^ " (buffer-name buffer)))) 400 | 401 | (defun emojify-inhibit-major-mode-p (buffer) 402 | "Determine if user has disabled the `major-mode' enabled for the BUFFER. 403 | 404 | Returns non-nil if the buffer's major mode is part of `emojify-inhibit-major-modes'" 405 | (with-current-buffer buffer 406 | (apply #'derived-mode-p emojify-inhibit-major-modes))) 407 | 408 | (defun emojify-helm-buffer-p (buffer) 409 | "Determine if the current BUFFER is a helm buffer." 410 | (unless emojify-minibuffer-reading-emojis-p 411 | (string-match-p "\\*helm" (buffer-name buffer)))) 412 | 413 | (defun emojify-minibuffer-p (buffer) 414 | "Determine if the current BUFFER is a minibuffer." 415 | (unless emojify-minibuffer-reading-emojis-p 416 | (minibufferp buffer))) 417 | 418 | (defun emojify-buffer-p (buffer) 419 | "Determine if `emojify-mode' should be enabled for given BUFFER. 420 | 421 | `emojify-mode' mode is not enabled in temporary buffers. Additionally user 422 | can customize `emojify-inhibit-major-modes' and 423 | `emojify-inhibit-in-buffer-functions' to disabled emojify in additional buffers." 424 | (not (or emojify-inhibit-emojify-in-current-buffer-p 425 | (emojify-ephemeral-buffer-p (current-buffer)) 426 | (emojify-inhibit-major-mode-p (current-buffer)) 427 | (buffer-base-buffer buffer) 428 | (run-hook-with-args-until-success 'emojify-inhibit-in-buffer-functions buffer)))) 429 | 430 | 431 | 432 | ;; Obsolete vars 433 | 434 | (define-obsolete-variable-alias 'emojify-emoji-style 'emojify-emoji-styles "0.2") 435 | (define-obsolete-function-alias 'emojify-set-emoji-style 'emojify-set-emoji-styles "0.2") 436 | 437 | 438 | 439 | ;; Customizations to control display of emojis 440 | 441 | (defvar emojify-emoji-style-change-hook nil 442 | "Hooks run when emoji style changes.") 443 | 444 | ;;;###autoload 445 | (defun emojify-set-emoji-styles (styles) 446 | "Set the type of emojis that should be displayed. 447 | 448 | STYLES is the styles emoji styles that should be used, see `emojify-emoji-styles'" 449 | (when (not (listp styles)) 450 | (setq styles (list styles)) 451 | (warn "`emojify-emoji-style' has been deprecated use `emojify-emoji-styles' instead!")) 452 | 453 | (setq-default emojify-emoji-styles styles) 454 | 455 | (run-hooks 'emojify-emoji-style-change-hook)) 456 | 457 | (defcustom emojify-emoji-styles 458 | '(ascii unicode github) 459 | "The type of emojis that should be displayed. 460 | 461 | These can have one of the following values 462 | 463 | `ascii' - Display only ascii emojis for example ';)' 464 | `unicode' - Display only unicode emojis for example '😉' 465 | `github' - Display only github style emojis for example ':wink:'" 466 | :type '(set 467 | (const :tag "Display only ascii emojis" ascii) 468 | (const :tag "Display only github emojis" github) 469 | (const :tag "Display only unicode codepoints" unicode)) 470 | :set (lambda (_ value) (emojify-set-emoji-styles value)) 471 | :group 'emojify) 472 | 473 | (defcustom emojify-program-contexts 474 | '(comments string code) 475 | "Contexts where emojis can be displayed in programming modes. 476 | 477 | Possible values are 478 | `comments' - Display emojis in comments 479 | `string' - Display emojis in strings 480 | `code' - Display emojis in code (this is applicable only for unicode emojis)" 481 | :type '(set :tag "Contexts where emojis should be displayed in programming modes" 482 | (const :tag "Display emojis in comments" comments) 483 | (const :tag "Display emojis in string" string) 484 | (const :tag "Display emojis in code" code)) 485 | :group 'emojify) 486 | 487 | (defcustom emojify-inhibit-functions 488 | '(emojify-in-org-tags-p emojify-in-org-list-p) 489 | "Functions used to determine given emoji should displayed at current point. 490 | 491 | These functions are called with 3 arguments, the text to be emojified, the start 492 | of emoji text and the end of emoji text. These functions are called with the 493 | buffer where emojis are going to be displayed selected." 494 | :type 'hook 495 | :group 'emojify) 496 | 497 | (defcustom emojify-completing-read-function #'completing-read 498 | "Require same argument with `completing-read'." 499 | :type 'function 500 | :group 'emojify) 501 | 502 | (defcustom emojify-composed-text-p t 503 | "Should composed text be emojified." 504 | :type 'boolean 505 | :group 'emojify) 506 | 507 | (defcustom emojify-company-tooltips-p t 508 | "Should company mode tooltips be emojified." 509 | :type 'boolean 510 | :group 'emojify) 511 | 512 | (defun emojify-in-org-tags-p (match beg _end) 513 | "Determine whether the point is on `org-mode' tag. 514 | 515 | MATCH, BEG and _END are the text currently matched emoji and the start position 516 | and end position of emoji text respectively. 517 | 518 | Easiest would have to inspect face at point but unfortunately, there is no 519 | way to guarantee that we run after font-lock" 520 | (and (memq major-mode '(org-mode org-agenda-mode)) 521 | (string-match-p ":[^:]+[:]?" match) 522 | (org-at-heading-p) 523 | (save-excursion 524 | (save-match-data 525 | (goto-char beg) 526 | ;; Regex for tag picked from https://code.orgmode.org/bzg/org-mode/src/master/lisp/org.el#L589-L590 527 | (looking-at ":[[:alnum:]_@#%:]+:[\s-]*$"))))) 528 | 529 | (defun emojify-in-org-list-p (text beg &rest ignored) 530 | "Determine whether the point is in `org-mode' list. 531 | 532 | TEXT is the text which is supposed to rendered a an emoji. BEG is the beginning 533 | of the emoji text in the buffer. The arguments IGNORED are ignored." 534 | (and (eq major-mode 'org-mode) 535 | (equal text "8)") 536 | (equal (org-list-get-item-begin) beg))) 537 | 538 | (defun emojify-program-context-at-point-per-syntax-table (beg end) 539 | "Determine the progamming context between BEG and END using the the syntax table." 540 | (let ((syntax-beg (syntax-ppss beg)) 541 | (syntax-end (syntax-ppss end))) 542 | (cond ((and (nth 3 syntax-beg) (nth 3 syntax-end)) 'string) 543 | ((and (nth 4 syntax-beg) (nth 4 syntax-end)) 'comments) 544 | (t 'code)))) 545 | 546 | (defun emojify-program-context-at-point-per-face (beg _end) 547 | "Determine the progamming context between BEG and END using the the face. 548 | 549 | Used when the major mode for which we need to check the program context is not 550 | the same as the current buffer's major mode, currently only used when displaying 551 | emojis in org source blocks." 552 | (let* ((face-at-point (get-text-property beg 'face)) 553 | (faces-at-point (if (listp face-at-point) 554 | face-at-point 555 | (list face-at-point)))) 556 | (cond ((memql 'font-lock-doc-face faces-at-point) 'string) 557 | ((memql 'font-lock-string-face faces-at-point) 'string) 558 | ((memql 'font-lock-comment-face faces-at-point) 'comments) 559 | (t 'code)))) 560 | 561 | 562 | (defun emojify-valid-program-context-p (emoji beg end &optional use-faces) 563 | "Determine if EMOJI should be displayed for text between BEG and END. 564 | 565 | If the optional USE-FACES is true, the programming context is determined using 566 | faces. This returns non-nil if the region is valid according to 567 | `emojify-program-contexts'" 568 | (when emojify-program-contexts 569 | (let ((context (if use-faces 570 | (emojify-program-context-at-point-per-face beg end) 571 | (emojify-program-context-at-point-per-syntax-table beg end)))) 572 | (and (memql context emojify-program-contexts) 573 | (if (equal context 'code) 574 | (and (string= (ht-get emoji "style") "unicode") 575 | (memql 'unicode emojify-emoji-styles)) 576 | t))))) 577 | 578 | (defun emojify-org-src-lang-at-point (point) 579 | "Return the `major-mode' for the org source block at POINT. 580 | 581 | Returns nil if the point is not at an org source block" 582 | (when (eq major-mode 'org-mode) 583 | (save-excursion 584 | (goto-char point) 585 | (let ((element (org-element-at-point))) 586 | (when (eq (org-element-type element) 'src-block) 587 | (emojify-org-src-get-lang-mode (org-element-property :language element))))))) 588 | 589 | (defun emojify-looking-at-end-of-list-maybe (point) 590 | "Determine if POINT is end of a list. 591 | 592 | This is not accurate since it restricts the region to scan to 593 | the visible area." 594 | (let* ((area (emojify--get-relevant-region)) 595 | (beg (car area)) 596 | (end (cdr area))) 597 | (save-restriction 598 | (narrow-to-region beg end) 599 | (let ((list-start (ignore-errors (scan-sexps point -1)))) 600 | (when (and list-start 601 | ;; Ignore the starting brace if it is an emoji 602 | (not (get-text-property list-start 'emojified))) 603 | ;; If we got a list start make sure both start and end 604 | ;; belong to same string/comment 605 | (let ((syntax-beg (syntax-ppss list-start)) 606 | (syntax-end (syntax-ppss point))) 607 | (and list-start 608 | (eq (nth 8 syntax-beg) 609 | (nth 8 syntax-end))))))))) 610 | 611 | (defun emojify-valid-ascii-emoji-context-p (beg end) 612 | "Determine if the okay to display ascii emoji between BEG and END." 613 | ;; The text is at the start of the buffer 614 | (and (or (not (char-before beg)) 615 | ;; 32 space since ? (? followed by a space) is not readable 616 | ;; 34 is " since?" confuses font-lock 617 | ;; 41 is ) since?) (extra paren) confuses most packages 618 | (memq (char-syntax (char-before beg)) 619 | ;; space 620 | '(32 621 | ;; start/end of string 622 | 34 623 | ;; whitespace syntax 624 | ?- 625 | ;; comment start 626 | ?< 627 | ;; comment end, this handles text at start of line immediately 628 | ;; after comment line in a multiline comment 629 | ?>))) 630 | ;; The text is at the end of the buffer 631 | (or (not (char-after end)) 632 | (memq (char-syntax (char-after end)) 633 | ;; space 634 | '(32 635 | ;; start/end of string 636 | 34 637 | ;; whitespace syntax 638 | ?- 639 | ;; punctuation 640 | ?. 641 | ;; closing braces 642 | 41 643 | ;; comment end 644 | ?>))))) 645 | 646 | 647 | 648 | ;; Customizations to control the behaviour when point enters emojified text 649 | 650 | (defcustom emojify-point-entered-behaviour 'echo 651 | "The behaviour when point enters, an emojified text. 652 | 653 | It can be one of the following 654 | `echo' - Echo the underlying text in the minibuffer 655 | `uncover' - Display the underlying text while point is on it 656 | function - It is called with 2 arguments (the buffer where emoji appears is 657 | current during execution) 658 | 1) starting position of emoji text 659 | 2) ending position of emoji text 660 | 661 | Does nothing if the value is anything else." 662 | ;; TODO: Mention custom function 663 | :type '(radio :tag "Behaviour when point enters an emoji" 664 | (const :tag "Echo the underlying emoji text in the minibuffer" echo) 665 | (const :tag "Uncover (undisplay) the underlying emoji text" uncover)) 666 | :group 'emojify) 667 | 668 | (defcustom emojify-reveal-on-isearch t 669 | "Should underlying emoji be displayed when point enters emoji while in isearch mode." 670 | :type 'boolean 671 | :group 'emojify) 672 | 673 | (defcustom emojify-show-help t 674 | "If non-nil the underlying text is displayed in a popup when mouse moves over it." 675 | :type 'boolean 676 | :group 'emojify) 677 | 678 | (defun emojify-on-emoji-enter (beginning end) 679 | "Executed when point enters emojified text between BEGINNING and END." 680 | (cond ((and (eq emojify-point-entered-behaviour 'echo) 681 | ;; Do not echo in isearch-mode 682 | (not isearch-mode) 683 | (not (active-minibuffer-window)) 684 | (not (current-message))) 685 | (message (substring-no-properties (get-text-property beginning 'emojify-text)))) 686 | ((eq emojify-point-entered-behaviour 'uncover) 687 | (put-text-property beginning end 'display nil)) 688 | ((functionp 'emojify-point-entered-behaviour) 689 | (funcall emojify-point-entered-behaviour beginning end))) 690 | 691 | (when (and isearch-mode emojify-reveal-on-isearch) 692 | (put-text-property beginning end 'display nil))) 693 | 694 | (defun emojify-on-emoji-exit (beginning end) 695 | "Executed when point exits emojified text between BEGINNING and END." 696 | (put-text-property beginning 697 | end 698 | 'display 699 | (get-text-property beginning 'emojify-display))) 700 | 701 | (defvar-local emojify--last-emoji-pos nil) 702 | 703 | (defun emojify-detect-emoji-entry/exit () 704 | "Detect emoji entry and exit and run appropriate handlers. 705 | 706 | This is inspired by `prettify-symbol-mode's logic for 707 | `prettify-symbols-unprettify-at-point'." 708 | (emojify-with-saved-buffer-state 709 | (when emojify--last-emoji-pos 710 | (emojify-on-emoji-exit (car emojify--last-emoji-pos) (cdr emojify--last-emoji-pos))) 711 | 712 | (when (get-text-property (point) 'emojified) 713 | (let* ((text-props (text-properties-at (point))) 714 | (buffer (plist-get text-props 'emojify-buffer)) 715 | (match-beginning (plist-get text-props 'emojify-beginning)) 716 | (match-end (plist-get text-props 'emojify-end))) 717 | (when (eq buffer (current-buffer)) 718 | (emojify-on-emoji-enter match-beginning match-end) 719 | (setq emojify--last-emoji-pos (cons match-beginning match-end))))))) 720 | 721 | (defun emojify-help-function (_window _string pos) 722 | "Function to get help string to be echoed when point/mouse into the point. 723 | 724 | To understand WINDOW, STRING and POS see the function documentation for 725 | `help-echo' text-property." 726 | (when (and emojify-show-help 727 | (not isearch-mode) 728 | (not (active-minibuffer-window)) 729 | (not (current-message))) 730 | (plist-get (text-properties-at pos) 'emojify-text))) 731 | 732 | 733 | 734 | ;; Core functions and macros 735 | 736 | ;; Variables related to user emojis 737 | 738 | (defcustom emojify-user-emojis nil 739 | "User specified custom emojis. 740 | 741 | This is an alist where first element of cons is the text to be displayed as 742 | emoji, while the second element of the cons is an alist containing data about 743 | the emoji. 744 | 745 | The inner alist should have atleast (not all keys are strings) 746 | 747 | `name' - The name of the emoji 748 | `style' - This should be one of \"github\", \"ascii\" or \"github\" 749 | (see `emojify-emoji-styles') 750 | 751 | The alist should contain one of (see `emojify-display-style') 752 | `unicode' - The replacement for the provided emoji for \"unicode\" display style 753 | `image' - The replacement for the provided emoji for \"image\" display style. 754 | This should be the absolute path to the image 755 | `ascii' - The replacement for the provided emoji for \"ascii\" display style 756 | 757 | Example - 758 | The following assumes that custom images are at ~/.emacs.d/emojis/trollface.png and 759 | ~/.emacs.d/emojis/neckbeard.png 760 | 761 | '((\":troll:\" . ((\"name\" . \"Troll\") 762 | (\"image\" . \"~/.emacs.d/emojis/trollface.png\") 763 | (\"style\" . \"github\"))) 764 | (\":neckbeard:\" . ((\"name\" . \"Neckbeard\") 765 | (\"image\" . \"~/.emacs.d/emojis/neckbeard.png\") 766 | (\"style\" . \"github\"))))" 767 | :type '(alist :key-type string 768 | :value-type (alist :key-type string 769 | :value-type string)) 770 | :group 'emojify) 771 | 772 | (defvar emojify--user-emojis nil 773 | "User specified custom emojis.") 774 | 775 | (defvar emojify--user-emojis-regexp nil 776 | "Regexp to match user specified custom emojis.") 777 | 778 | ;; Variables related to default emojis 779 | (defvar emojify-emojis nil 780 | "Data about the emojis, this contains only the emojis that come with emojify.") 781 | 782 | (defvar emojify-regexps nil 783 | "List of regexps to match text to be emojified.") 784 | 785 | (defvar emojify--completing-candidates-cache nil 786 | "Cached values for completing read candidates calculated for `emojify-completing-read'.") 787 | 788 | ;; Cache for emoji completing read candidates 789 | (defun emojify--get-completing-read-candidates () 790 | "Get the candidates to be used for `emojify-completing-read'. 791 | 792 | The candidates are calculated according to currently active 793 | `emojify-emoji-styles' and cached" 794 | (let ((styles (mapcar #'symbol-name emojify-emoji-styles))) 795 | (unless (and emojify--completing-candidates-cache 796 | (equal styles (car emojify--completing-candidates-cache))) 797 | (setq emojify--completing-candidates-cache 798 | (cons styles 799 | (let ((emojis '())) 800 | (emojify-emojis-each (lambda (key value) 801 | (when (seq-position styles (ht-get value "style")) 802 | (push (format "%s - %s (%s)" 803 | key 804 | (ht-get value "name") 805 | (ht-get value "style")) 806 | emojis)))) 807 | emojis)))) 808 | (cdr emojify--completing-candidates-cache))) 809 | 810 | (defun emojify-create-emojify-emojis (&optional force) 811 | "Create `emojify-emojis' if needed. 812 | 813 | The function avoids reading emoji data if it has already been read unless FORCE 814 | in which case emoji data is re-read." 815 | (when (or force (not emojify-emojis)) 816 | (emojify-set-emoji-data))) 817 | 818 | (defun emojify-get-emoji (emoji) 819 | "Get data for given EMOJI. 820 | 821 | This first looks for the emoji in `emojify--user-emojis', 822 | and then in `emojify-emojis'." 823 | (or (when emojify--user-emojis 824 | (ht-get emojify--user-emojis emoji)) 825 | (ht-get emojify-emojis emoji))) 826 | 827 | (defun emojify-emojis-each (function) 828 | "Execute FUNCTION for each emoji. 829 | 830 | This first runs function for `emojify--user-emojis', 831 | and then `emojify-emojis'." 832 | (when emojify--user-emojis 833 | (ht-each function emojify--user-emojis)) 834 | (ht-each function emojify-emojis)) 835 | 836 | (defun emojify--verify-user-emojis (emojis) 837 | "Verify the EMOJIS in correct user format." 838 | (seq-every-p (lambda (emoji) 839 | (and (assoc "name" (cdr emoji)) 840 | ;; Make sure style is present is only one of 841 | ;; "unicode", "ascii" and "github". 842 | (assoc "style" (cdr emoji)) 843 | (seq-position '("unicode" "ascii" "github") 844 | (cdr (assoc "style" (cdr emoji)))) 845 | (or (assoc "unicode" (cdr emoji)) 846 | (assoc "image" (cdr emoji)) 847 | (assoc "ascii" (cdr emoji))))) 848 | emojis)) 849 | 850 | (defun emojify-set-emoji-data () 851 | "Read the emoji data for STYLES and set the regexp required to search them." 852 | (setq-default emojify-emojis (let ((json-array-type 'list) 853 | (json-object-type 'hash-table)) 854 | (json-read-file emojify-emoji-json))) 855 | 856 | (let (unicode-emojis ascii-emojis) 857 | (ht-each (lambda (emoji data) 858 | (when (string= (ht-get data "style") "unicode") 859 | (push emoji unicode-emojis)) 860 | 861 | (when (string= (ht-get data "style") "ascii") 862 | (push emoji ascii-emojis))) 863 | emojify-emojis) 864 | 865 | ;; Construct emojify-regexps such that github style are searched first 866 | ;; followed by unicode and then ascii emojis. 867 | (setq emojify-regexps (list ":[[:alnum:]+_-]+:" 868 | (regexp-opt unicode-emojis) 869 | (regexp-opt ascii-emojis)))) 870 | 871 | (when emojify-user-emojis 872 | (if (emojify--verify-user-emojis emojify-user-emojis) 873 | ;; Create entries for user emojis 874 | (let ((emoji-pairs (mapcar (lambda (user-emoji) 875 | (cons (car user-emoji) 876 | (ht-from-alist (cdr user-emoji)))) 877 | emojify-user-emojis))) 878 | (setq emojify--user-emojis (ht-from-alist emoji-pairs)) 879 | (setq emojify--user-emojis-regexp (regexp-opt (mapcar #'car emoji-pairs)))) 880 | (message "[emojify] User emojis are not in correct format ignoring them."))) 881 | 882 | (emojify-emojis-each (lambda (emoji data) 883 | ;; Add the emoji text to data, this makes the values 884 | ;; of the `emojify-emojis' standalone containing all 885 | ;; data about the emoji 886 | (ht-set! data "emoji" emoji) 887 | (ht-set! data "custom" (and emojify--user-emojis 888 | (ht-get emojify--user-emojis emoji))))) 889 | 890 | ;; Clear completion candidates cache 891 | (setq emojify--completing-candidates-cache nil)) 892 | 893 | (defvar emojify-emoji-keymap 894 | (let ((map (make-sparse-keymap))) 895 | (define-key map [remap delete-char] #'emojify-delete-emoji-forward) 896 | (define-key map [remap delete-forward-char] #'emojify-delete-emoji-forward) 897 | (define-key map [remap backward-delete-char] #'emojify-delete-emoji-backward) 898 | (define-key map [remap org-delete-backward-char] #'emojify-delete-emoji-backward) 899 | (define-key map [remap delete-backward-char] #'emojify-delete-emoji-backward) 900 | (define-key map [remap backward-delete-char-untabify] #'emojify-delete-emoji-backward) 901 | map)) 902 | 903 | (defun emojify-image-dir () 904 | "Get the path to directory containing images for currently selected emoji set." 905 | (expand-file-name emojify-emoji-set 906 | emojify-emojis-dir)) 907 | 908 | (defun emojify--get-point-col-and-line (point) 909 | "Return a cons of containing the column number and line at POINT." 910 | (save-excursion 911 | (goto-char point) 912 | (cons (current-column) (line-number-at-pos)))) 913 | 914 | (defun emojify--get-characters-for-composition (composition) 915 | "Extract the characters from COMPOSITION." 916 | (if (nth 3 composition) 917 | (nth 2 composition) 918 | (let ((index -1)) 919 | (seq-filter #'identity 920 | (seq-map (lambda (elt) 921 | (cl-incf index) 922 | (when (cl-evenp index) elt)) 923 | (nth 2 composition)))))) 924 | 925 | (defun emojify--get-composed-text (point) 926 | "Get the text used as composition property at POINT. 927 | 928 | This does not check if there is composition property at point the callers should 929 | make sure the point has a composition property otherwise this function will 930 | fail." 931 | (let* ((composition (find-composition point nil nil t)) 932 | (characters (emojify--get-characters-for-composition composition))) 933 | (emojify-string-join (seq-map #'char-to-string characters)))) 934 | 935 | (defun emojify--inside-rectangle-selection-p (beg end) 936 | "Check if region marked by BEG and END is inside a rectangular selection. 937 | 938 | In addition to explicit the parameters BEG and END, calling functions should 939 | also dynamically bind `emojify-region-beg' and `emojify-region-end' to beginning 940 | and end of region respectively." 941 | (when (and emojify-region-beg 942 | (bound-and-true-p rectangle-mark-mode)) 943 | (let ((rect-beg (emojify--get-point-col-and-line emojify-region-beg)) 944 | (rect-end (emojify--get-point-col-and-line emojify-region-end)) 945 | (emoji-start-pos (emojify--get-point-col-and-line beg)) 946 | (emoji-end-pos (emojify--get-point-col-and-line end))) 947 | (or (and (<= (car rect-beg) (car emoji-start-pos)) 948 | (<= (car emoji-start-pos) (car rect-end)) 949 | (<= (cdr rect-beg) (cdr emoji-start-pos)) 950 | (<= (cdr emoji-start-pos) (cdr rect-end))) 951 | (and (<= (car rect-beg) (car emoji-end-pos)) 952 | (<= (car emoji-end-pos) (car rect-end)) 953 | (<= (cdr rect-beg) (cdr emoji-end-pos)) 954 | (<= (cdr emoji-end-pos) (cdr rect-end))))))) 955 | 956 | (defun emojify--inside-non-rectangle-selection-p (beg end) 957 | "Check if region marked by BEG and END is inside a non-regular selection. 958 | 959 | In addition to the explicit parameters BEG and END, calling functions should 960 | also dynamically bind `emojify-region-beg' and `emojify-region-end' to beginning 961 | and end of region respectively." 962 | (when (and emojify-region-beg 963 | (region-active-p) 964 | (not (bound-and-true-p rectangle-mark-mode))) 965 | (or (and (< emojify-region-beg beg) 966 | (<= beg emojify-region-end)) 967 | (and (< emojify-region-beg end) 968 | (<= end emojify-region-end))))) 969 | 970 | (defun emojify--region-background-maybe (beg end) 971 | "If the BEG and END falls inside an active region return the region face. 972 | 973 | This returns nil if the emojis between BEG and END do not fall in region." 974 | ;; `redisplay-highlight-region-function' was not defined in Emacs 24.3 975 | (when (and (or (not (boundp 'redisplay-highlight-region-function)) 976 | (equal (default-value 'redisplay-highlight-region-function) redisplay-highlight-region-function)) 977 | (or (emojify--inside-non-rectangle-selection-p beg end) 978 | (emojify--inside-rectangle-selection-p beg end))) 979 | (face-background 'region))) 980 | 981 | (defun emojify--get-image-background (beg end) 982 | "Get the color to be used as background for emoji between BEG and END." 983 | ;; We do a separate check for region since `background-color-at-point' 984 | ;; does not always detect background color inside regions properly 985 | (or (emojify--region-background-maybe beg end) 986 | (save-excursion 987 | (goto-char beg) 988 | (condition-case nil 989 | (background-color-at-point) 990 | (wrong-type-argument nil))))) 991 | 992 | (defvar emojify--imagemagick-support-cache (ht-create)) 993 | 994 | (defun emojify--imagemagick-supports-p (format) 995 | "Check if imagemagick support given FORMAT. 996 | 997 | This function caches the result of the check since the naive check 998 | 999 | (memq format (imagemagick-types)) 1000 | 1001 | can be expensive if `imagemagick-types' returns a large list, this is 1002 | especially problematic since this check is potentially called during 1003 | very redisplay. See https://github.com/iqbalansari/emacs-emojify/issues/41" 1004 | (when (fboundp 'imagemagick-types) 1005 | (when (equal (ht-get emojify--imagemagick-support-cache format 'unset) 'unset) 1006 | (ht-set emojify--imagemagick-support-cache format (memq format (imagemagick-types)))) 1007 | (ht-get emojify--imagemagick-support-cache format))) 1008 | 1009 | (defun emojify--get-image-display (data buffer beg end &optional target) 1010 | "Get the display text property to display the emoji as an image. 1011 | 1012 | DATA holds the emoji data, _BUFFER is the target buffer where the emoji is to be 1013 | displayed, BEG and END delimit the region where emoji will be displayed. For 1014 | explanation of TARGET see the documentation of `emojify--get-text-display-props'. 1015 | 1016 | TODO: Perhaps TARGET should be generalized to work with overlays, buffers and 1017 | other different display constructs, for now this works." 1018 | (when (ht-get data "image") 1019 | (let* ((image-file (expand-file-name (ht-get data "image") 1020 | (emojify-image-dir))) 1021 | (image-type (intern (upcase (file-name-extension image-file))))) 1022 | (when (file-exists-p image-file) 1023 | (create-image image-file 1024 | ;; use imagemagick if available and supports PNG images 1025 | ;; (allows resizing images) 1026 | (when (emojify--imagemagick-supports-p image-type) 1027 | 'imagemagick) 1028 | nil 1029 | :ascent 'center 1030 | :heuristic-mask t 1031 | :background (cond ((equal target 'mode-line) 1032 | (face-background 'mode-line nil 'default)) 1033 | (t (emojify--get-image-background beg end))) 1034 | ;; no-op if imagemagick is not available 1035 | :scale 1 1036 | :height (cond ((bufferp target) 1037 | (with-current-buffer target 1038 | (emojify-default-font-height))) 1039 | ((equal target 'mode-line) 1040 | (emojify-face-height 'mode-line)) 1041 | (t (with-current-buffer buffer 1042 | (emojify-default-font-height))))))))) 1043 | 1044 | (defun emojify--get-unicode-display (data &rest ignored) 1045 | "Get the display text property to display the emoji as an unicode character. 1046 | 1047 | DATA holds the emoji data, rest of the arguments IGNORED are ignored" 1048 | (let* ((unicode (ht-get data "unicode")) 1049 | (characters (when unicode 1050 | (string-to-vector unicode)))) 1051 | (when (seq-every-p #'char-displayable-p characters) 1052 | unicode))) 1053 | 1054 | (defun emojify--get-ascii-display (data &rest ignored) 1055 | "Get the display text property to display the emoji as an ascii characters. 1056 | 1057 | DATA holds the emoji data, rest of the arguments IGNORED are ignored." 1058 | (ht-get data "ascii")) 1059 | 1060 | (defun emojify--get-text-display-props (emoji buffer beg end &optional target) 1061 | "Get the display property for an EMOJI. 1062 | 1063 | BUFFER is the buffer currently holding the EMOJI, BEG and END delimit the region 1064 | containing the emoji. TARGET can either be a buffer object or a special value 1065 | mode-line. It is used to indicate where EMOJI would be displayed, properties 1066 | like font-height are inherited from TARGET if provided." 1067 | (funcall (pcase emojify-display-style 1068 | (`image #'emojify--get-image-display) 1069 | (`unicode #'emojify--get-unicode-display) 1070 | (`ascii #'emojify--get-ascii-display)) 1071 | emoji 1072 | buffer 1073 | beg 1074 | end 1075 | target)) 1076 | 1077 | (defun emojify--propertize-text-for-emoji (emoji text buffer start end &optional target) 1078 | "Display EMOJI for TEXT in BUFFER between START and END. 1079 | 1080 | For explanation of TARGET see the documentation of 1081 | `emojify--get-text-display-props'." 1082 | (let ((display-prop 1083 | (emojify--get-text-display-props emoji buffer start end target)) 1084 | (buffer-props (unless target 1085 | (list 'emojify-buffer buffer 1086 | 'emojify-beginning (copy-marker start) 1087 | 'emojify-end (copy-marker end) 1088 | 'yank-handler (list nil text) 1089 | 'keymap emojify-emoji-keymap 1090 | 'help-echo #'emojify-help-function)))) 1091 | (when display-prop 1092 | (add-text-properties start 1093 | end 1094 | (append (list 'emojified t 1095 | 'emojify-display display-prop 1096 | 'display display-prop 1097 | 'emojify-text text) 1098 | buffer-props))))) 1099 | 1100 | (defun emojify-display-emojis-in-region (beg end &optional target) 1101 | "Display emojis in region. 1102 | 1103 | BEG and END are the beginning and end of the region respectively. TARGET 1104 | is used to determine the background color and size of emojis, by default 1105 | the current buffer is used to determine these, see 1106 | `emojify--get-text-display-props' for more details. 1107 | 1108 | Displaying happens in two phases, first search based phase displays actual text 1109 | appearing in buffer as emojis. In the next phase composed text is searched for 1110 | emojis and displayed. 1111 | 1112 | A minor problem here is that if the text is composed after this display loop it 1113 | would not be displayed as emoji, although in practice the two packages that use 1114 | the composition property `prettify-symbol-mode' and `org-bullets' use the 1115 | font-lock machinery which runs before emojify's display loop, so hopefully this 1116 | should not be a problem 🤞." 1117 | (emojify-with-saved-buffer-state 1118 | ;; Make sure we halt if displaying emojis takes more than a second (this 1119 | ;; might be too large duration) 1120 | (with-timeout (1 (emojify-message "Failed to display emojis under 1 second")) 1121 | (seq-doseq (regexp (apply #'append 1122 | (when emojify--user-emojis-regexp 1123 | (list emojify--user-emojis-regexp)) 1124 | (list emojify-regexps))) 1125 | (let (case-fold-search) 1126 | (goto-char beg) 1127 | (while (and (> end (point)) 1128 | (search-forward-regexp regexp end t)) 1129 | (let* ((match-beginning (match-beginning 0)) 1130 | (match-end (match-end 0)) 1131 | (match (match-string-no-properties 0)) 1132 | (buffer (current-buffer)) 1133 | (emoji (emojify-get-emoji match)) 1134 | (force-display (get-text-property match-beginning 'emojify-force-display))) 1135 | (when (and emoji 1136 | (not (or (get-text-property match-beginning 'emojify-inhibit) 1137 | (get-text-property match-end 'emojify-inhibit))) 1138 | (memql (intern (ht-get emoji "style")) emojify-emoji-styles) 1139 | ;; Skip displaying this emoji if the its bounds are 1140 | ;; already part of an existing emoji. Since the emojis 1141 | ;; are searched in descending order of length (see 1142 | ;; construction of emojify-regexp in `emojify-set-emoji-data'), 1143 | ;; this means larger emojis get precedence over smaller 1144 | ;; ones 1145 | (not (or (get-text-property match-beginning 'emojified) 1146 | (get-text-property (1- match-end) 'emojified))) 1147 | 1148 | ;; Validate the context in a programming major-mode, if 1149 | ;; the buffer is in org-mode we determine the major 1150 | ;; mode is picked from the language/babel block if any 1151 | ;; at point 1152 | (let ((major-mode-at-point (if (eq major-mode 'org-mode) 1153 | (or (emojify-org-src-lang-at-point match-beginning) 'org-mode) 1154 | major-mode))) 1155 | ;; Display unconditionally in non-prog mode 1156 | (or (not (emojify-provided-mode-derived-p major-mode-at-point 1157 | 'prog-mode 'tuareg--prog-mode 'comint-mode 'smalltalk-mode)) 1158 | ;; In prog mode enable respecting `emojify-program-contexts' 1159 | (emojify-valid-program-context-p emoji 1160 | match-beginning 1161 | match-end 1162 | (not (eq major-mode-at-point major-mode))))) 1163 | 1164 | ;; Display ascii emojis conservatively, since they have potential 1165 | ;; to be annoying consider d: in head:, except while executing apropos 1166 | ;; emoji 1167 | (or (not (string= (ht-get emoji "style") "ascii")) 1168 | force-display 1169 | (emojify-valid-ascii-emoji-context-p match-beginning match-end)) 1170 | 1171 | ;; Inhibit possibly inside a list 1172 | ;; 41 is ?) but packages get confused by the extra closing paren :) 1173 | ;; TODO Report bugs to such packages 1174 | (or force-display 1175 | (not (and (eq (char-syntax (char-before match-end)) 41) 1176 | (emojify-looking-at-end-of-list-maybe match-end)))) 1177 | 1178 | (not (run-hook-with-args-until-success 'emojify-inhibit-functions match match-beginning match-end))) 1179 | (emojify--propertize-text-for-emoji emoji match buffer match-beginning match-end target))) 1180 | ;; Stop a bit to let `with-timeout' kick in 1181 | (sit-for 0 t)))) 1182 | 1183 | ;; Loop to emojify composed text 1184 | (when (and emojify-composed-text-p 1185 | ;; Skip this if user has disabled unicode style emojis, since 1186 | ;; we display only composed text that are unicode emojis 1187 | (memql 'unicode emojify-emoji-styles)) 1188 | (goto-char beg) 1189 | (let ((compose-start (if (get-text-property beg 'composition) 1190 | ;; Check `beg' first for composition property 1191 | ;; since `next-single-property-change' will 1192 | ;; search for region after `beg' for property 1193 | ;; change thus skipping any composed text at 1194 | ;; `beg' 1195 | beg 1196 | (next-single-property-change beg 1197 | 'composition 1198 | nil 1199 | end)))) 1200 | (while (and (> end (point)) 1201 | ;; `end' would be equal to `compose-start' if there was no 1202 | ;; text with composition found within `end', this happens 1203 | ;; because `next-single-property-change' returns the limit 1204 | ;; (and we use `end' as the limit) if no match is found 1205 | (> end compose-start) 1206 | compose-start) 1207 | (let* ((match (emojify--get-composed-text compose-start)) 1208 | (emoji (emojify-get-emoji match)) 1209 | (compose-end (next-single-property-change compose-start 'composition nil end))) 1210 | ;; Display only composed text that is unicode char 1211 | (when (and emoji 1212 | (string= (ht-get emoji "style") "unicode")) 1213 | (emojify--propertize-text-for-emoji emoji match (current-buffer) compose-start compose-end target)) 1214 | ;; Setup the next loop 1215 | (setq compose-start (and compose-end (next-single-property-change compose-end 1216 | 'composition 1217 | nil 1218 | end))) 1219 | (goto-char compose-end)) 1220 | ;; Stop a bit to let `with-timeout' kick in 1221 | (sit-for 0 t))))))) 1222 | 1223 | (defun emojify-undisplay-emojis-in-region (beg end) 1224 | "Undisplay the emojis in region. 1225 | 1226 | BEG and END are the beginning and end of the region respectively" 1227 | (emojify-with-saved-buffer-state 1228 | (while (< beg end) 1229 | ;; Get the start of emojified region in the region, the region is marked 1230 | ;; with text-property `emojified' whose value is `t'. The region is marked 1231 | ;; so that we do not inadvertently remove display or other properties 1232 | ;; inserted by other packages. This might fail too if a package adds any 1233 | ;; of these properties between an emojified text, but that situation is 1234 | ;; hopefully very rare and this is better than blindly removing all text 1235 | ;; properties 1236 | (let* ((emoji-start (text-property-any beg end 'emojified t)) 1237 | ;; Get the end emojified text, if we could not find the start set 1238 | ;; emoji-end to region `end', this merely to make looping easier. 1239 | (emoji-end (or (and emoji-start 1240 | (text-property-not-all emoji-start end 'emojified t)) 1241 | ;; If the emojified text is at the end of the region 1242 | ;; assume that end is the emojified text. 1243 | end))) 1244 | ;; Proceed only if we got start of emojified text 1245 | (when emoji-start 1246 | ;; Remove the properties 1247 | (remove-text-properties emoji-start emoji-end (append (list 'emojified t 1248 | 'display t 1249 | 'emojify-display t 1250 | 'emojify-buffer t 1251 | 'emojify-text t 1252 | 'emojify-beginning t 1253 | 'emojify-end t 1254 | 'yank-handler t 1255 | 'keymap t 1256 | 'help-echo t 1257 | 'rear-nonsticky t)))) 1258 | ;; Setup the next iteration 1259 | (setq beg emoji-end))))) 1260 | 1261 | (defun emojify-redisplay-emojis-in-region (&optional beg end) 1262 | "Redisplay emojis in region between BEG and END. 1263 | 1264 | Redisplay emojis in the visible region if BEG and END are not specified" 1265 | (let* ((area (emojify--get-relevant-region)) 1266 | (beg (save-excursion 1267 | (goto-char (or beg (car area))) 1268 | (line-beginning-position))) 1269 | (end (save-excursion 1270 | (goto-char (or end (cdr area))) 1271 | (line-end-position)))) 1272 | (unless (> (- end beg) 5000) 1273 | (emojify-execute-ignoring-errors-unless-debug 1274 | (emojify-undisplay-emojis-in-region beg end) 1275 | (emojify-display-emojis-in-region beg end))))) 1276 | 1277 | (defun emojify-after-change-extend-region-function (beg end _len) 1278 | "Extend the region to be emojified. 1279 | 1280 | This simply extends the region to be fontified to the start of line at BEG and 1281 | end of line at END. _LEN is ignored. 1282 | 1283 | The idea is since an emoji cannot span multiple lines, redisplaying complete 1284 | lines ensures that all the possibly affected emojis are redisplayed." 1285 | (let ((emojify-jit-lock-start (save-excursion 1286 | (goto-char beg) 1287 | (line-beginning-position))) 1288 | (emojify-jit-lock-end (save-excursion 1289 | (goto-char end) 1290 | (line-end-position)))) 1291 | (setq jit-lock-start (if jit-lock-start 1292 | (min jit-lock-start emojify-jit-lock-start) 1293 | emojify-jit-lock-start)) 1294 | (setq jit-lock-end (if jit-lock-end 1295 | (max jit-lock-end emojify-jit-lock-end) 1296 | emojify-jit-lock-end)))) 1297 | 1298 | 1299 | 1300 | ;; Emojify standalone strings 1301 | 1302 | (defun emojify-string (string &optional styles target) 1303 | "Create a propertized version of STRING, to display emojis belonging STYLES. 1304 | 1305 | TARGET can either be a buffer object or a special value mode-line. It is used 1306 | to indicate where EMOJI would be displayed, properties like font-height are 1307 | inherited from TARGET if provided. See also `emojify--get-text-display-props'." 1308 | (emojify-create-emojify-emojis) 1309 | (let ((emojify-emoji-styles (or styles emojify-emoji-styles)) 1310 | ;; Temporarily disable all `buffer-list-update-hook's, with-temp-buffer 1311 | ;; internally calls `get-buffer-create' (and `kill-buffer'), which cause 1312 | ;; this hook to be run. This is problematic because EXWM uses 1313 | ;; `buffer-list-update-hook' and this temporary buffer confuses EXWM's 1314 | ;; tracking code leading to 1315 | ;; https://github.com/iqbalansari/emacs-emojify/issues/64 1316 | buffer-list-update-hook) 1317 | (with-temp-buffer 1318 | (insert string) 1319 | (emojify-display-emojis-in-region (point-min) (point-max) target) 1320 | (buffer-string)))) 1321 | 1322 | 1323 | 1324 | ;; Electric delete functionality 1325 | 1326 | (defun emojify--find-key-binding-ignoring-emojify-keymap (key) 1327 | "Find the binding for given KEY ignoring the text properties at point. 1328 | 1329 | This is needed since `key-binding' looks up in keymap text property as well 1330 | which is not what we want when falling back in `emojify-delete-emoji'" 1331 | (let* ((key-binding (or (minor-mode-key-binding key) 1332 | (local-key-binding key) 1333 | (global-key-binding key)))) 1334 | (when key-binding 1335 | (or (command-remapping key-binding 1336 | nil 1337 | (seq-filter (lambda (keymap) 1338 | (not (equal keymap emojify-emoji-keymap))) 1339 | (current-active-maps))) 1340 | key-binding)))) 1341 | 1342 | (defun emojify-delete-emoji (point) 1343 | "Delete emoji at POINT." 1344 | (if (get-text-property point 'emojified) 1345 | (delete-region (get-text-property point 'emojify-beginning) 1346 | (get-text-property point 'emojify-end)) 1347 | (call-interactively (emojify--find-key-binding-ignoring-emojify-keymap (this-command-keys))))) 1348 | 1349 | (defun emojify-delete-emoji-forward () 1350 | "Delete emoji after point." 1351 | (interactive) 1352 | (emojify-delete-emoji (point))) 1353 | 1354 | (defun emojify-delete-emoji-backward () 1355 | "Delete emoji before point." 1356 | (interactive) 1357 | (emojify-delete-emoji (1- (point)))) 1358 | 1359 | ;; Integrate with delete-selection-mode 1360 | ;; Basically instruct delete-selection mode to override our commands 1361 | ;; if the region is active. 1362 | (put 'emojify-delete-emoji-forward 'delete-selection 'supersede) 1363 | (put 'emojify-delete-emoji-backward 'delete-selection 'supersede) 1364 | 1365 | 1366 | 1367 | ;; Updating background color on selection 1368 | 1369 | (defun emojify--update-emojis-background-in-region (&optional beg end) 1370 | "Update the background color for emojis between BEG and END." 1371 | (when (equal emojify-display-style 'image) 1372 | (emojify-with-saved-buffer-state 1373 | (emojify-do-for-emojis-in-region beg end 1374 | (plist-put (cdr (get-text-property emoji-start 'display)) 1375 | :background 1376 | (emojify--get-image-background emoji-start 1377 | emoji-end)))))) 1378 | 1379 | (defun emojify--update-emojis-background-in-region-starting-at (point) 1380 | "Update background color for emojis in buffer starting at POINT. 1381 | 1382 | This updates the emojis in the region starting from POINT, the end of region is 1383 | determined by product of `frame-height' and `frame-width' which roughly 1384 | corresponds to the visible area. POINT usually corresponds to the starting 1385 | position of the window, see 1386 | `emojify-update-visible-emojis-background-after-command' and 1387 | `emojify-update-visible-emojis-background-after-window-scroll' 1388 | 1389 | NOTE: `window-text-height' and `window-text-width' would have been more 1390 | appropriate here however they were not defined in Emacs v24.3 and below." 1391 | (let* ((region-beginning point) 1392 | (region-end (min (+ region-beginning (* (frame-height) 1393 | (frame-width))) 1394 | (point-max)))) 1395 | (emojify--update-emojis-background-in-region region-beginning 1396 | region-end))) 1397 | 1398 | (defun emojify-update-visible-emojis-background-after-command () 1399 | "Function added to `post-command-hook' when region is active. 1400 | 1401 | This function updates the backgrounds of the emojis in the region changed after 1402 | the command. 1403 | 1404 | Ideally this would have been good enough to update emoji backgounds after region 1405 | changes, unfortunately this does not work well with commands that scroll the 1406 | window specifically `window-start' and `window-end' (sometimes only `window-end') 1407 | report incorrect values. 1408 | 1409 | To work around this 1410 | `emojify-update-visible-emojis-background-after-window-scroll' is added to 1411 | `window-scroll-functions' to update emojis on window scroll." 1412 | (emojify--update-emojis-background-in-region-starting-at (window-start))) 1413 | 1414 | (defun emojify-update-visible-emojis-background-after-window-scroll (_window display-start) 1415 | "Function added to `window-scroll-functions' when region is active. 1416 | 1417 | This function updates the backgrounds of the emojis in the newly displayed area 1418 | of the window. DISPLAY-START corresponds to the new start of the window." 1419 | (emojify--update-emojis-background-in-region-starting-at display-start)) 1420 | 1421 | 1422 | 1423 | ;; Lazy image downloading 1424 | 1425 | (defcustom emojify-download-emojis-p 'ask 1426 | "Should emojify download images, if the selected emoji sets are not available. 1427 | 1428 | Emojify can automatically download the images required to display the selected 1429 | emoji set. By default the user will be asked for confirmation before downloading 1430 | the image. Set this variable to t to download the images without asking for 1431 | confirmation. Setting it to nil will disable automatic download of the images. 1432 | 1433 | Please note that emojify will not download the images if Emacs is running in 1434 | non-interactive mode and `emojify-download-emojis-p' is set to `ask'." 1435 | :type '(radio :tag "Automatically download required images" 1436 | (const :tag "Ask before downloading" ask) 1437 | (const :tag "Download without asking" t) 1438 | (const :tag "Disable automatic downloads" nil)) 1439 | :group 'emojify) 1440 | 1441 | (defvar emojify--refused-image-download-p nil 1442 | "Used to remember that user has refused to download images in this session.") 1443 | (defvar emojify--download-in-progress-p nil 1444 | "Is emoji download in progress used to avoid multiple emoji download prompts.") 1445 | 1446 | (defun emojify--emoji-download-emoji-set (data) 1447 | "Download the emoji images according to DATA." 1448 | (let ((destination (expand-file-name (make-temp-name "emojify") 1449 | temporary-file-directory))) 1450 | (url-copy-file (ht-get data "url") 1451 | destination) 1452 | (let ((downloaded-sha (with-temp-buffer 1453 | (insert-file-contents-literally destination) 1454 | (secure-hash 'sha256 (current-buffer))))) 1455 | (if (string= downloaded-sha (ht-get data "sha256")) 1456 | destination 1457 | (error "Failed to download emojis from \"%s\", hash does not match %s (expected %s)" 1458 | (ht-get data "url") downloaded-sha (ht-get data "sha256")))))) 1459 | 1460 | (defun emojify--extract-emojis (file) 1461 | "Extract the tar FILE in emoji directory." 1462 | (let* ((default-directory emojify-emojis-dir)) 1463 | (with-temp-buffer 1464 | (insert-file-contents-literally file) 1465 | (let ((emojify-inhibit-emojify-in-current-buffer-p t)) 1466 | (tar-mode)) 1467 | (tar-untar-buffer)))) 1468 | 1469 | (defun emojify-download-emoji (emoji-set) 1470 | "Download the provided EMOJI-SET." 1471 | (interactive (list (completing-read "Select the emoji set you want to download: " 1472 | (ht-keys emojify-emoji-set-json)))) 1473 | (let ((emoji-data (ht-get emojify-emoji-set-json emoji-set))) 1474 | (cond ((not emoji-data) 1475 | (error "No emoji set named %s found" emoji-set)) 1476 | ((and (file-exists-p (expand-file-name emoji-set emojify-emojis-dir)) 1477 | (called-interactively-p 'any)) 1478 | (message "%s emoji-set already downloaded, not downloading again!" emoji-set)) 1479 | (t 1480 | (emojify--extract-emojis (emojify--emoji-download-emoji-set (ht-get emojify-emoji-set-json emoji-set))))))) 1481 | 1482 | (defun emojify--confirm-emoji-download () 1483 | "Confirm download of emojis. 1484 | 1485 | This takes care of respecting the `emojify-download-emojis-p' and making sure we 1486 | do not prompt the user to download emojis multiple times." 1487 | (if (not (equal emojify-download-emojis-p 'ask)) 1488 | emojify-download-emojis-p 1489 | ;; Skip the prompt if we are in noninteractive mode or the user has already 1490 | ;; denied us permission to download once 1491 | (unless (or noninteractive emojify--refused-image-download-p) 1492 | (let ((download-confirmed-p (yes-or-no-p "[emojify] Emoji images not available should I download them now? "))) 1493 | (setq emojify--refused-image-download-p (not download-confirmed-p)) 1494 | download-confirmed-p)))) 1495 | 1496 | (defun emojify-download-emoji-maybe () 1497 | "Download emoji images if needed." 1498 | (when (and (equal emojify-display-style 'image) 1499 | (not (file-exists-p (emojify-image-dir))) 1500 | (not emojify--refused-image-download-p)) 1501 | (unwind-protect 1502 | ;; Do not prompt for download if download is in progress 1503 | (unless emojify--download-in-progress-p 1504 | (setq emojify--download-in-progress-p t) 1505 | (if (emojify--confirm-emoji-download) 1506 | (emojify-download-emoji emojify-emoji-set) 1507 | (warn "[emojify] Not downloading emoji images for now. Emojis would 1508 | not be displayed since images are not available. If you wish to download emojis, 1509 | run the command `emojify-download-emoji'"))) 1510 | (setq emojify--download-in-progress-p nil)))) 1511 | 1512 | (defun emojify-ensure-images () 1513 | "Ensure that emoji images are downloaded." 1514 | (if after-init-time 1515 | (emojify-download-emoji-maybe) 1516 | (add-hook 'after-init-hook #'emojify-download-emoji-maybe t))) 1517 | 1518 | 1519 | 1520 | ;; Minor mode definitions 1521 | 1522 | (defun emojify-turn-on-emojify-mode () 1523 | "Turn on `emojify-mode' in current buffer." 1524 | 1525 | ;; Calculate emoji data if needed 1526 | (emojify-create-emojify-emojis) 1527 | 1528 | (when (emojify-buffer-p (current-buffer)) 1529 | ;; Download images if not available 1530 | (emojify-ensure-images) 1531 | 1532 | ;; Install our jit-lock function 1533 | (jit-lock-register #'emojify-redisplay-emojis-in-region) 1534 | (add-hook 'jit-lock-after-change-extend-region-functions #'emojify-after-change-extend-region-function t t) 1535 | 1536 | ;; Handle point entered behaviour 1537 | (add-hook 'post-command-hook #'emojify-detect-emoji-entry/exit t t) 1538 | 1539 | ;; Update emoji backgrounds after each command 1540 | (add-hook 'post-command-hook #'emojify-update-visible-emojis-background-after-command t t) 1541 | 1542 | ;; Update emoji backgrounds after mark is deactivated, this is needed since 1543 | ;; deactivation can happen outside the command loop 1544 | (add-hook 'deactivate-mark-hook #'emojify-update-visible-emojis-background-after-command t t) 1545 | 1546 | ;; Update emoji backgrounds after when window scrolls 1547 | (add-hook 'window-scroll-functions #'emojify-update-visible-emojis-background-after-window-scroll t t) 1548 | 1549 | ;; Redisplay emojis after enabling `prettify-symbol-mode' 1550 | (add-hook 'prettify-symbols-mode-hook #'emojify-redisplay-emojis-in-region) 1551 | 1552 | ;; Redisplay visible emojis when emoji style changes 1553 | (add-hook 'emojify-emoji-style-change-hook #'emojify-redisplay-emojis-in-region))) 1554 | 1555 | (defun emojify-turn-off-emojify-mode () 1556 | "Turn off `emojify-mode' in current buffer." 1557 | ;; Remove currently displayed emojis 1558 | (save-restriction 1559 | (widen) 1560 | (emojify-undisplay-emojis-in-region (point-min) (point-max))) 1561 | 1562 | ;; Uninstall our jit-lock function 1563 | (jit-lock-unregister #'emojify-redisplay-emojis-in-region) 1564 | (remove-hook 'jit-lock-after-change-extend-region-functions #'emojify-after-change-extend-region-function t) 1565 | 1566 | (remove-hook 'post-command-hook #'emojify-detect-emoji-entry/exit t) 1567 | 1568 | ;; Disable hooks to update emoji backgrounds 1569 | (remove-hook 'post-command-hook #'emojify-update-visible-emojis-background-after-command t) 1570 | (remove-hook 'deactivate-mark-hook #'emojify-update-visible-emojis-background-after-command t) 1571 | (remove-hook 'window-scroll-functions #'emojify-update-visible-emojis-background-after-window-scroll t) 1572 | 1573 | ;; Remove hook to redisplay emojis after enabling `prettify-symbol-mode' 1574 | (remove-hook 'prettify-symbols-mode-hook #'emojify-redisplay-emojis-in-region) 1575 | 1576 | ;; Remove style change hooks 1577 | (remove-hook 'emojify-emoji-style-change-hook #'emojify-redisplay-emojis-in-region)) 1578 | 1579 | ;; define a emojify-mode-map to enable defining keys specifically for emojify-mode 1580 | (defvar emojify-mode-map (make-sparse-keymap) 1581 | "Keymap for `emojify-mode'.") 1582 | 1583 | ;;;###autoload 1584 | (define-minor-mode emojify-mode 1585 | "Emojify mode" 1586 | :keymap emojify-mode-map 1587 | :init-value nil 1588 | (if emojify-mode 1589 | ;; Turn on 1590 | (emojify-turn-on-emojify-mode) 1591 | ;; Turn off 1592 | (emojify-turn-off-emojify-mode))) 1593 | 1594 | ;;;###autoload 1595 | (define-globalized-minor-mode global-emojify-mode 1596 | emojify-mode emojify-mode 1597 | :init-value nil) 1598 | 1599 | (defadvice set-buffer-multibyte (after emojify-disable-for-unibyte-buffers (&rest ignored)) 1600 | "Disable emojify when unibyte encoding is enabled for a buffer. 1601 | Re-enable it when buffer changes back to multibyte encoding." 1602 | (ignore-errors 1603 | (if enable-multibyte-characters 1604 | (when global-emojify-mode 1605 | (emojify-mode +1)) 1606 | (emojify-mode -1)))) 1607 | 1608 | (ad-activate #'set-buffer-multibyte) 1609 | 1610 | 1611 | 1612 | ;; Displaying emojis in mode-line 1613 | 1614 | (defun emojify--emojified-mode-line (format) 1615 | "Return an emojified version of mode-line FORMAT. 1616 | 1617 | The format is converted to the actual string to be displayed using 1618 | `format-mode-line' and the unicode characters are replaced by images." 1619 | (replace-regexp-in-string "%" 1620 | "%%" 1621 | (if emojify-mode 1622 | ;; Remove "%e" from format since we keep it as first part of the 1623 | ;; emojified mode-line, see `emojify-emojify-mode-line' 1624 | (emojify-string (format-mode-line (delq "%e" format)) nil 'mode-line) 1625 | (format-mode-line format)))) 1626 | 1627 | (defun emojify-mode-line-emojified-p () 1628 | "Check if the mode-line is already emojified. 1629 | 1630 | If the `mode-line-format' is of following format 1631 | 1632 | \(\"%e\" (:eval (emojify-emojified-mode-line ... ))) 1633 | 1634 | We can assume the mode-line is already emojified." 1635 | (and (consp mode-line-format) 1636 | (equal (ignore-errors (cl-caadr mode-line-format)) 1637 | :eval) 1638 | (equal (ignore-errors (car (cl-cadadr mode-line-format))) 1639 | 'emojify--emojified-mode-line))) 1640 | 1641 | (defun emojify-emojify-mode-line () 1642 | "Emojify unicode characters in the mode-line. 1643 | 1644 | This updates `mode-line-format' to a modified version which emojifies the 1645 | mode-line before it is displayed." 1646 | (unless (emojify-mode-line-emojified-p) 1647 | (setq mode-line-format `("%e" (:eval 1648 | (emojify--emojified-mode-line ',mode-line-format)))))) 1649 | 1650 | (defun emojify-unemojify-mode-line () 1651 | "Restore `mode-line-format' to unemojified version. 1652 | 1653 | This basically reverses the effect of `emojify-emojify-mode-line'." 1654 | (when (emojify-mode-line-emojified-p) 1655 | (setq mode-line-format (cl-cadadr (cl-cadadr mode-line-format))))) 1656 | 1657 | ;;;###autoload 1658 | (define-minor-mode emojify-mode-line-mode 1659 | "Emojify mode line" 1660 | :init-value nil 1661 | (if emojify-mode-line-mode 1662 | ;; Turn on 1663 | (emojify-emojify-mode-line) 1664 | ;; Turn off 1665 | (emojify-unemojify-mode-line))) 1666 | 1667 | ;;;###autoload 1668 | (define-globalized-minor-mode global-emojify-mode-line-mode 1669 | emojify-mode-line-mode emojify-mode-line-mode 1670 | :init-value nil) 1671 | 1672 | 1673 | 1674 | ;; Searching emojis 1675 | 1676 | (defvar emojify-apropos-buffer-name "*Apropos Emojis*") 1677 | 1678 | (defun emojify-apropos-quit () 1679 | "Delete the window displaying Emoji search results." 1680 | (interactive) 1681 | (if (= (length (window-list)) 1) 1682 | (bury-buffer) 1683 | (quit-window))) 1684 | 1685 | (defun emojify-apropos-copy-emoji () 1686 | "Copy the emoji being displayed at current line in apropos results." 1687 | (interactive) 1688 | (save-excursion 1689 | (goto-char (line-beginning-position)) 1690 | (if (not (get-text-property (point) 'emojified)) 1691 | (emojify-user-error "No emoji at point") 1692 | (kill-new (get-text-property (point) 'emojify-text)) 1693 | (message "Copied emoji (%s) to kill ring!" 1694 | (get-text-property (point) 'emojify-text))))) 1695 | 1696 | (defun emojify-apropos-describe-emoji () 1697 | "Copy the emoji being displayed at current line in apropos results." 1698 | (interactive) 1699 | (save-excursion 1700 | (goto-char (line-beginning-position)) 1701 | (if (not (get-text-property (point) 'emojified)) 1702 | (emojify-user-error "No emoji at point") 1703 | (emojify-describe-emoji (get-text-property (point) 'emojify-text))))) 1704 | 1705 | (defvar emojify-apropos-mode-map 1706 | (let ((map (make-sparse-keymap))) 1707 | (set-keymap-parent map emojify-common-mode-map) 1708 | (define-key map "c" #'emojify-apropos-copy-emoji) 1709 | (define-key map "w" #'emojify-apropos-copy-emoji) 1710 | (define-key map "d" #'emojify-apropos-describe-emoji) 1711 | (define-key map (kbd "RET") #'emojify-apropos-describe-emoji) 1712 | (define-key map "g" #'emojify-apropos-emoji) 1713 | map) 1714 | "Keymap used in `emojify-apropos-mode'.") 1715 | 1716 | (define-derived-mode emojify-apropos-mode fundamental-mode "Apropos Emojis" 1717 | "Mode used to display results of `emojify-apropos-emoji' 1718 | 1719 | \\{emojify-apropos-mode-map}" 1720 | (emojify-mode +1) 1721 | ;; view mode being a minor mode eats up our bindings avoid it 1722 | (let (view-read-only) 1723 | (read-only-mode +1))) 1724 | 1725 | (put 'emojify-apropos-mode 'mode-class 'special) 1726 | 1727 | (defvar emojify--apropos-last-query nil) 1728 | (make-variable-buffer-local 'emojify--apropos-last-query) 1729 | 1730 | (defun emojify-apropos-read-pattern () 1731 | "Read apropos pattern with INITIAL-INPUT as the initial input. 1732 | 1733 | Borrowed from apropos.el" 1734 | (let ((pattern (read-string (concat "Search for emoji (word list or regexp): ") 1735 | emojify--apropos-last-query))) 1736 | (if (string-equal (regexp-quote pattern) pattern) 1737 | (or (split-string pattern "[ \t]+" t) 1738 | (emojify-user-error "No word list given")) 1739 | pattern))) 1740 | 1741 | ;;;###autoload 1742 | (defun emojify-apropos-emoji (pattern) 1743 | "Show Emojis that match PATTERN." 1744 | (interactive (list (emojify-apropos-read-pattern))) 1745 | 1746 | (emojify-create-emojify-emojis) 1747 | 1748 | (let ((in-apropos-buffer-p (equal major-mode 'emojify-apropos-mode)) 1749 | matching-emojis 1750 | sorted-emojis) 1751 | 1752 | (unless (listp pattern) 1753 | (setq pattern (list pattern))) 1754 | 1755 | ;; Convert the user entered text to a regex to match the emoji name or 1756 | ;; description 1757 | (apropos-parse-pattern pattern) 1758 | 1759 | ;; Collect matching emojis in a list of (list score emoji emoji-data) 1760 | ;; elements, where score is the proximity of the emoji to given pattern 1761 | ;; calculated using `apropos-score-str' 1762 | (emojify-emojis-each (lambda (key value) 1763 | (when (or (string-match apropos-regexp key) 1764 | (string-match apropos-regexp (ht-get value "name"))) 1765 | (push (list (max (apropos-score-str key) 1766 | (apropos-score-str (ht-get value "name"))) 1767 | key 1768 | value) 1769 | matching-emojis)))) 1770 | 1771 | ;; Sort the emojis by the proximity score 1772 | (setq sorted-emojis (mapcar #'cdr 1773 | (sort matching-emojis 1774 | (lambda (emoji1 emoji2) 1775 | (> (car emoji1) (car emoji2)))))) 1776 | 1777 | ;; Insert result in apropos buffer and display it 1778 | (with-current-buffer (get-buffer-create emojify-apropos-buffer-name) 1779 | (let ((inhibit-read-only t) 1780 | (query (mapconcat 'identity pattern " "))) 1781 | (erase-buffer) 1782 | (insert (propertize "Emojis matching" 'face 'apropos-symbol)) 1783 | (insert (format " - \"%s\"" query)) 1784 | (insert "\n\nUse `c' or `w' to copy emoji on current line\nUse `g' to rerun apropos\n\n") 1785 | (dolist (emoji sorted-emojis) 1786 | (insert (format "%s - %s (%s)" 1787 | (car emoji) 1788 | (ht-get (cadr emoji) "name") 1789 | (ht-get (cadr emoji) "style"))) 1790 | (insert "\n")) 1791 | (goto-char (point-min)) 1792 | (forward-line (1- 6)) 1793 | (emojify-apropos-mode) 1794 | (setq emojify--apropos-last-query (concat query " ")) 1795 | (setq-local line-spacing 7))) 1796 | 1797 | (pop-to-buffer (get-buffer emojify-apropos-buffer-name) 1798 | (when in-apropos-buffer-p 1799 | (cons #'display-buffer-same-window nil))))) 1800 | 1801 | 1802 | 1803 | ;; Inserting emojis 1804 | 1805 | (defun emojify--completing-read-minibuffer-setup-hook () 1806 | "Enables `emojify-mode' in minbuffer while inserting emojis. 1807 | 1808 | This ensures `emojify' is enabled even when `global-emojify-mode' is not on." 1809 | (emojify-mode +1)) 1810 | 1811 | (defun emojify--completing-read-helm-hook () 1812 | "Enables `emojify-mode' in helm buffer. 1813 | 1814 | This ensures `emojify' is enabled in helm buffer displaying completion even when 1815 | `global-emojify-mode' is not on." 1816 | (with-current-buffer helm-buffer 1817 | (emojify-mode +1))) 1818 | 1819 | (defun emojify-completing-read (prompt &optional predicate require-match initial-input hist def inherit-input-method) 1820 | "Read emoji from the user and return the selected emoji. 1821 | 1822 | PROMPT is a string to prompt with, PREDICATE, REQUIRE-MATCH, INITIAL-INPUT, 1823 | HIST, DEF, INHERIT-INPUT-METHOD correspond to the arguments for 1824 | `emojify-completing-read-function' and are passed to 1825 | ‘emojify-completing-read-function’ without any interpretation. 1826 | 1827 | For each possible emoji PREDICATE is called with a string of the form 1828 | ' - (