From 810baea2eae5340f4eab56a1a46e13e1d8eeebe7 Mon Sep 17 00:00:00 2001 From: Gerry Agbobada Date: Thu, 9 Jan 2020 10:24:41 +0100 Subject: [PATCH 1/7] Use composite to call harfbuzz for ligatures This is based on code from microsoft -> cascadia-code repository (issue #153), which sets a proper composition-function-table according to specific ligature regexp. Using variables also allows to disable the ligatures for org-mode, where org-bullets might be incompatible It has a fallback to old pretty-code behaviour. --- modules/ui/pretty-code/config.el | 85 ++++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) diff --git a/modules/ui/pretty-code/config.el b/modules/ui/pretty-code/config.el index 6622c8fef..9c9d46300 100644 --- a/modules/ui/pretty-code/config.el +++ b/modules/ui/pretty-code/config.el @@ -98,10 +98,95 @@ Otherwise it builds `prettify-code-symbols-alist' according to (add-hook 'after-change-major-mode-hook #'+pretty-code-init-pretty-symbols-h) +(defvar +prog-ligatures-alist + '((?! . ".\\(?:\\(==\\|[!=]\\)[!=]?\\)") + (?# . ".\\(?:\\(###?\\|_(\\|[(:=?[_{]\\)[#(:=?[_{]?\\)") + (?$ . ".\\(?:\\(>\\)>?\\)") + (?% . ".\\(?:\\(%\\)%?\\)") + (?& . ".\\(?:\\(&\\)&?\\)") + (?* . ".\\(?:\\(\\*\\*\\|[*>]\\)[*>]?\\)") + ;; (?* . ".\\(?:\\(\\*\\*\\|[*/>]\\).?\\)") + (?+ . ".\\(?:\\([>]\\)>?\\)") + ;; (?+ . ".\\(?:\\(\\+\\+\\|[+>]\\).?\\)") + (?- . ".\\(?:\\(-[->]\\|<<\\|>>\\|[-<>|~]\\)[-<>|~]?\\)") + ;; (?. . ".\\(?:\\(\\.[.<]\\|[-.=]\\)[-.<=]?\\)") + (?. . ".\\(?:\\(\\.<\\|[-=]\\)[-<=]?\\)") + (?/ . ".\\(?:\\(//\\|==\\|[=>]\\)[/=>]?\\)") + ;; (?/ . ".\\(?:\\(//\\|==\\|[*/=>]\\).?\\)") + (?0 . ".\\(?:\\(x[a-fA-F0-9]\\).?\\)") + (?: . ".\\(?:\\(::\\|[:<=>]\\)[:<=>]?\\)") + (59 . ".\\(?:\\(;\\);?\\)") ;; 59 is ; + (?< . ".\\(?:\\(!--\\|\\$>\\|\\*>\\|\\+>\\|-[-<>|]\\|/>\\|<[-<=]\\|=[<>|]\\|==>?\\||>\\||||?\\|~[>~]\\|[$*+/:<=>|~-]\\)[$*+/:<=>|~-]?\\)") + (?= . ".\\(?:\\(!=\\|/=\\|:=\\|<<\\|=[=>]\\|>>\\|[=>]\\)[=<>]?\\)") + (?> . ".\\(?:\\(->\\|=>\\|>[-=>]\\|[-:=>]\\)[-:=>]?\\)") + (?? . ".\\(?:\\([.:=?]\\)[.:=?]?\\)") + (91 . ".\\(?:\\(|\\)[]|]?\\)") ;; 91 is [ + ;; (?\ . ".\\(?:\\([\\n]\\)[\\]?\\)") + (?^ . ".\\(?:\\(=\\)=?\\)") + (?_ . ".\\(?:\\(|_\\|[_]\\)_?\\)") + (?w . ".\\(?:\\(ww\\)w?\\)") + (?{ . ".\\(?:\\(|\\)[|}]?\\)") + (?| . ".\\(?:\\(->\\|=>\\||[-=>]\\||||*>\\|[]=>|}-]\\).?\\)") + (?~ . ".\\(?:\\(~>\\|[-=>@~]\\)[-=>@~]?\\)")) + "An alist containing all the ligatures used when in a `+prog-ligatures-modes' mode. + +The car is the character ASCII number, cdr is a regex which will call `font-shape-gstring' +when matched. + +Because of the underlying code in :ui pretty-code module, the regex should match a string +starting with the character contained in car." + ) + +;; Defaults to not org-mode because org-bullets might be incompatible +;; with the ?*-based replacements in the default value of +prg-ligatures-alist +(defvar +prog-ligatures-modes '(not org-mode) + "List of major modes in which ligatures should be enabled. + +If t, enable it everywhere. + +If the first element is 'not, enable it in any mode besides what is listed. + +If nil, fallback to the prettify-symbols based replacement (add +font features to pretty-code)." + ) + +(defun +pretty-code-init-ligatures-h () + "Enable ligatures. + +If in fundamental-mode, or a mode derived from special, comint, eshell or term +modes, this function does nothing. + +Otherwise it sets the buffer-local composition table to a composition table enhanced with +`+prog-ligatures-alist' ligatures regexes." + (unless (or (eq major-mode 'fundamental-mode) + (eq (get major-mode 'mode-class) 'special) + (derived-mode-p 'comint-mode 'eshell-mode 'term-mode)) + (when (or (eq +prog-ligatures-modes t) + (if (eq (car +prog-ligatures-modes) 'not) + (not (memq major-mode (cdr +prog-ligatures-modes))) + (memq major-mode +prog-ligatures-modes))) + (setq-local composition-function-table composition-ligature-table)))) + +(add-hook 'after-change-major-mode-hook #'+pretty-code-init-ligatures-h) + +(use-package! composite + :when (and (version<= "27.0" emacs-version) (string-match-p "HARFBUZZ" system-configuration-features)) + :init + (defvar composition-ligature-table (make-char-table nil)) + :config + (dolist (char-regexp +prog-ligatures-alist) + (set-char-table-range composition-ligature-table (car char-regexp) + `([,(cdr char-regexp) 0 font-shape-gstring]))) + (set-char-table-parent composition-ligature-table composition-function-table)) + ;; The emacs-mac build of Emacs appear to have built-in support for ligatures, ;; so use that instead if this module is enabled. (cond ((and IS-MAC (fboundp 'mac-auto-operator-composition-mode)) (mac-auto-operator-composition-mode)) + ;; Harfbuzz builds do not need font-specific ligature support + ((and (version<= "27.0" emacs-version) + (string-match-p "HARFBUZZ" system-configuration-features) + (not (null +prog-ligatures-modes))) + nil) ;; Font-specific ligature support ((featurep! +fira) (load! "+fira")) From 03748d48cd54a69dff5a57eba367d1043050abbf Mon Sep 17 00:00:00 2001 From: Gerry Agbobada Date: Tue, 14 Apr 2020 20:09:33 +0200 Subject: [PATCH 2/7] Add the explicit character at the beginning of the regexp --- modules/ui/pretty-code/config.el | 58 ++++++++++++++++---------------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/modules/ui/pretty-code/config.el b/modules/ui/pretty-code/config.el index 9c9d46300..8dd1463cb 100644 --- a/modules/ui/pretty-code/config.el +++ b/modules/ui/pretty-code/config.el @@ -99,35 +99,35 @@ Otherwise it builds `prettify-code-symbols-alist' according to (add-hook 'after-change-major-mode-hook #'+pretty-code-init-pretty-symbols-h) (defvar +prog-ligatures-alist - '((?! . ".\\(?:\\(==\\|[!=]\\)[!=]?\\)") - (?# . ".\\(?:\\(###?\\|_(\\|[(:=?[_{]\\)[#(:=?[_{]?\\)") - (?$ . ".\\(?:\\(>\\)>?\\)") - (?% . ".\\(?:\\(%\\)%?\\)") - (?& . ".\\(?:\\(&\\)&?\\)") - (?* . ".\\(?:\\(\\*\\*\\|[*>]\\)[*>]?\\)") - ;; (?* . ".\\(?:\\(\\*\\*\\|[*/>]\\).?\\)") - (?+ . ".\\(?:\\([>]\\)>?\\)") - ;; (?+ . ".\\(?:\\(\\+\\+\\|[+>]\\).?\\)") - (?- . ".\\(?:\\(-[->]\\|<<\\|>>\\|[-<>|~]\\)[-<>|~]?\\)") - ;; (?. . ".\\(?:\\(\\.[.<]\\|[-.=]\\)[-.<=]?\\)") - (?. . ".\\(?:\\(\\.<\\|[-=]\\)[-<=]?\\)") - (?/ . ".\\(?:\\(//\\|==\\|[=>]\\)[/=>]?\\)") - ;; (?/ . ".\\(?:\\(//\\|==\\|[*/=>]\\).?\\)") - (?0 . ".\\(?:\\(x[a-fA-F0-9]\\).?\\)") - (?: . ".\\(?:\\(::\\|[:<=>]\\)[:<=>]?\\)") - (59 . ".\\(?:\\(;\\);?\\)") ;; 59 is ; - (?< . ".\\(?:\\(!--\\|\\$>\\|\\*>\\|\\+>\\|-[-<>|]\\|/>\\|<[-<=]\\|=[<>|]\\|==>?\\||>\\||||?\\|~[>~]\\|[$*+/:<=>|~-]\\)[$*+/:<=>|~-]?\\)") - (?= . ".\\(?:\\(!=\\|/=\\|:=\\|<<\\|=[=>]\\|>>\\|[=>]\\)[=<>]?\\)") - (?> . ".\\(?:\\(->\\|=>\\|>[-=>]\\|[-:=>]\\)[-:=>]?\\)") - (?? . ".\\(?:\\([.:=?]\\)[.:=?]?\\)") - (91 . ".\\(?:\\(|\\)[]|]?\\)") ;; 91 is [ - ;; (?\ . ".\\(?:\\([\\n]\\)[\\]?\\)") - (?^ . ".\\(?:\\(=\\)=?\\)") - (?_ . ".\\(?:\\(|_\\|[_]\\)_?\\)") - (?w . ".\\(?:\\(ww\\)w?\\)") - (?{ . ".\\(?:\\(|\\)[|}]?\\)") - (?| . ".\\(?:\\(->\\|=>\\||[-=>]\\||||*>\\|[]=>|}-]\\).?\\)") - (?~ . ".\\(?:\\(~>\\|[-=>@~]\\)[-=>@~]?\\)")) + '((?! . "!\\(?:\\(==\\|[!=]\\)[!=]?\\)") + (?# . "#\\(?:\\(###?\\|_(\\|[(:=?[_{]\\)[#(:=?[_{]?\\)") + (?$ . "$\\(?:\\(>\\)>?\\)") + (?% . "%\\(?:\\(%\\)%?\\)") + (?& . "&\\(?:\\(&\\)&?\\)") + (?* . "*\\(?:\\(\\*\\*\\|[*>]\\)[*>]?\\)") + ;; (?* . "*\\(?:\\(\\*\\*\\|[*/>]\\).?\\)") + (?+ . "+\\(?:\\([>]\\)>?\\)") + ;; (?+ . "+\\(?:\\(\\+\\+\\|[+>]\\).?\\)") + (?- . "-\\(?:\\(-[->]\\|<<\\|>>\\|[-<>|~]\\)[-<>|~]?\\)") + ;; (?. . "\\.\\(?:\\(\\.[.<]\\|[-.=]\\)[-.<=]?\\)") + (?. . "\\.\\(?:\\(\\.<\\|[-=]\\)[-<=]?\\)") + (?/ . "/\\(?:\\(//\\|==\\|[=>]\\)[/=>]?\\)") + ;; (?/ . "/\\(?:\\(//\\|==\\|[*/=>]\\).?\\)") + (?0 . "0\\(?:\\(x[a-fA-F0-9]\\).?\\)") + (?: . ":\\(?:\\(::\\|[:<=>]\\)[:<=>]?\\)") + (59 . ";\\(?:\\(;\\);?\\)") ;; 59 is ; + (?< . "<\\(?:\\(!--\\|\\$>\\|\\*>\\|\\+>\\|-[-<>|]\\|/>\\|<[-<=]\\|=[<>|]\\|==>?\\||>\\||||?\\|~[>~]\\|[$*+/:<=>|~-]\\)[$*+/:<=>|~-]?\\)") + (?= . "=\\(?:\\(!=\\|/=\\|:=\\|<<\\|=[=>]\\|>>\\|[=>]\\)[=<>]?\\)") + (?> . ">\\(?:\\(->\\|=>\\|>[-=>]\\|[-:=>]\\)[-:=>]?\\)") + (?? . "?\\(?:\\([.:=?]\\)[.:=?]?\\)") + (91 . "\\[\\(?:\\(|\\)[]|]?\\)") ;; 91 is [ + ;; (?\ . "\\\\\\(?:\\([\\n]\\)[\\]?\\)") + (?^ . "^\\(?:\\(=\\)=?\\)") + (?_ . "_\\(?:\\(|_\\|[_]\\)_?\\)") + (?w . "w\\(?:\\(ww\\)w?\\)") + (?{ . "{\\(?:\\(|\\)[|}]?\\)") + (?| . "|\\(?:\\(->\\|=>\\||[-=>]\\||||*>\\|[]=>|}-]\\).?\\)") + (?~ . "~\\(?:\\(~>\\|[-=>@~]\\)[-=>@~]?\\)")) "An alist containing all the ligatures used when in a `+prog-ligatures-modes' mode. The car is the character ASCII number, cdr is a regex which will call `font-shape-gstring' From 9a79069ab5f6d4c2e5781c48ccbbd0f69bb56b0a Mon Sep 17 00:00:00 2001 From: Gerry Agbobada Date: Sat, 25 Apr 2020 16:58:27 +0200 Subject: [PATCH 3/7] Version-gate the feature to emacs 28 --- modules/ui/pretty-code/config.el | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/modules/ui/pretty-code/config.el b/modules/ui/pretty-code/config.el index 8dd1463cb..cb879d602 100644 --- a/modules/ui/pretty-code/config.el +++ b/modules/ui/pretty-code/config.el @@ -169,7 +169,8 @@ Otherwise it sets the buffer-local composition table to a composition table enha (add-hook 'after-change-major-mode-hook #'+pretty-code-init-ligatures-h) (use-package! composite - :when (and (version<= "27.0" emacs-version) (string-match-p "HARFBUZZ" system-configuration-features)) + ;; Starting from emacs "28" because this code breaks without fe903c5 + :when (and (version<= "28.0" emacs-version) (string-match-p "HARFBUZZ" system-configuration-features)) :init (defvar composition-ligature-table (make-char-table nil)) :config @@ -183,7 +184,8 @@ Otherwise it sets the buffer-local composition table to a composition table enha (cond ((and IS-MAC (fboundp 'mac-auto-operator-composition-mode)) (mac-auto-operator-composition-mode)) ;; Harfbuzz builds do not need font-specific ligature support - ((and (version<= "27.0" emacs-version) + ;; if they brought in the fe903c5 commit + ((and (version<= "28.0" emacs-version) (string-match-p "HARFBUZZ" system-configuration-features) (not (null +prog-ligatures-modes))) nil) From 8a2f8bf2603a4deab06af2921cc583a8f6bb44a8 Mon Sep 17 00:00:00 2001 From: Gerry Agbobada Date: Sat, 25 Apr 2020 17:54:51 +0200 Subject: [PATCH 4/7] Lint code --- modules/ui/pretty-code/config.el | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/modules/ui/pretty-code/config.el b/modules/ui/pretty-code/config.el index cb879d602..cf02e7ed1 100644 --- a/modules/ui/pretty-code/config.el +++ b/modules/ui/pretty-code/config.el @@ -98,6 +98,7 @@ Otherwise it builds `prettify-code-symbols-alist' according to (add-hook 'after-change-major-mode-hook #'+pretty-code-init-pretty-symbols-h) +;;; Automatic font-specific ligatures (defvar +prog-ligatures-alist '((?! . "!\\(?:\\(==\\|[!=]\\)[!=]?\\)") (?# . "#\\(?:\\(###?\\|_(\\|[(:=?[_{]\\)[#(:=?[_{]?\\)") @@ -134,20 +135,19 @@ The car is the character ASCII number, cdr is a regex which will call `font-shap when matched. Because of the underlying code in :ui pretty-code module, the regex should match a string -starting with the character contained in car." - ) +starting with the character contained in car. + +This variable is used only if you built Emacs with Harfbuzz on a version >= 28") -;; Defaults to not org-mode because org-bullets might be incompatible -;; with the ?*-based replacements in the default value of +prg-ligatures-alist (defvar +prog-ligatures-modes '(not org-mode) "List of major modes in which ligatures should be enabled. -If t, enable it everywhere. +If t, enable it everywhere. Fundamental mode, and modes derived from special-mode, +comint-mode, eshell-mode and term-mode are *still* excluded. If the first element is 'not, enable it in any mode besides what is listed. -If nil, fallback to the prettify-symbols based replacement (add +font features to pretty-code)." - ) +If nil, fallback to the prettify-symbols based replacement (add +font features to pretty-code).") (defun +pretty-code-init-ligatures-h () "Enable ligatures. @@ -184,7 +184,7 @@ Otherwise it sets the buffer-local composition table to a composition table enha (cond ((and IS-MAC (fboundp 'mac-auto-operator-composition-mode)) (mac-auto-operator-composition-mode)) ;; Harfbuzz builds do not need font-specific ligature support - ;; if they brought in the fe903c5 commit + ;; if they are above emacs-27 ((and (version<= "28.0" emacs-version) (string-match-p "HARFBUZZ" system-configuration-features) (not (null +prog-ligatures-modes))) From a897492b99d45b75c8e1711c286ef403e59ed4f5 Mon Sep 17 00:00:00 2001 From: Gerry Agbobada Date: Sun, 26 Apr 2020 12:17:43 +0200 Subject: [PATCH 5/7] Use regexp-opt instead of manual regexes This helps maintainability tremendously. Thanks wasamasa for the snippet --- modules/ui/pretty-code/config.el | 55 +++++++++++++++----------------- 1 file changed, 26 insertions(+), 29 deletions(-) diff --git a/modules/ui/pretty-code/config.el b/modules/ui/pretty-code/config.el index cf02e7ed1..00a68e211 100644 --- a/modules/ui/pretty-code/config.el +++ b/modules/ui/pretty-code/config.el @@ -100,35 +100,32 @@ Otherwise it builds `prettify-code-symbols-alist' according to ;;; Automatic font-specific ligatures (defvar +prog-ligatures-alist - '((?! . "!\\(?:\\(==\\|[!=]\\)[!=]?\\)") - (?# . "#\\(?:\\(###?\\|_(\\|[(:=?[_{]\\)[#(:=?[_{]?\\)") - (?$ . "$\\(?:\\(>\\)>?\\)") - (?% . "%\\(?:\\(%\\)%?\\)") - (?& . "&\\(?:\\(&\\)&?\\)") - (?* . "*\\(?:\\(\\*\\*\\|[*>]\\)[*>]?\\)") - ;; (?* . "*\\(?:\\(\\*\\*\\|[*/>]\\).?\\)") - (?+ . "+\\(?:\\([>]\\)>?\\)") - ;; (?+ . "+\\(?:\\(\\+\\+\\|[+>]\\).?\\)") - (?- . "-\\(?:\\(-[->]\\|<<\\|>>\\|[-<>|~]\\)[-<>|~]?\\)") - ;; (?. . "\\.\\(?:\\(\\.[.<]\\|[-.=]\\)[-.<=]?\\)") - (?. . "\\.\\(?:\\(\\.<\\|[-=]\\)[-<=]?\\)") - (?/ . "/\\(?:\\(//\\|==\\|[=>]\\)[/=>]?\\)") - ;; (?/ . "/\\(?:\\(//\\|==\\|[*/=>]\\).?\\)") - (?0 . "0\\(?:\\(x[a-fA-F0-9]\\).?\\)") - (?: . ":\\(?:\\(::\\|[:<=>]\\)[:<=>]?\\)") - (59 . ";\\(?:\\(;\\);?\\)") ;; 59 is ; - (?< . "<\\(?:\\(!--\\|\\$>\\|\\*>\\|\\+>\\|-[-<>|]\\|/>\\|<[-<=]\\|=[<>|]\\|==>?\\||>\\||||?\\|~[>~]\\|[$*+/:<=>|~-]\\)[$*+/:<=>|~-]?\\)") - (?= . "=\\(?:\\(!=\\|/=\\|:=\\|<<\\|=[=>]\\|>>\\|[=>]\\)[=<>]?\\)") - (?> . ">\\(?:\\(->\\|=>\\|>[-=>]\\|[-:=>]\\)[-:=>]?\\)") - (?? . "?\\(?:\\([.:=?]\\)[.:=?]?\\)") - (91 . "\\[\\(?:\\(|\\)[]|]?\\)") ;; 91 is [ - ;; (?\ . "\\\\\\(?:\\([\\n]\\)[\\]?\\)") - (?^ . "^\\(?:\\(=\\)=?\\)") - (?_ . "_\\(?:\\(|_\\|[_]\\)_?\\)") - (?w . "w\\(?:\\(ww\\)w?\\)") - (?{ . "{\\(?:\\(|\\)[|}]?\\)") - (?| . "|\\(?:\\(->\\|=>\\||[-=>]\\||||*>\\|[]=>|}-]\\).?\\)") - (?~ . "~\\(?:\\(~>\\|[-=>@~]\\)[-=>@~]?\\)")) + `((?! . ,(regexp-opt '("!!" "!=" "!=="))) + (?# . ,(regexp-opt '("##" "###" "####" "#(" "#:" "#=" "#?" "#[" "#_" "#_(" "#{"))) + (?$ . ,(regexp-opt '("$>" "$>>"))) + (?% . ,(regexp-opt '("%%" "%%%"))) + (?& . ,(regexp-opt '("&&" "&&&"))) + (?* . ,(regexp-opt '("*" "**" "***" "**/" "*/" "*>"))) + (?+ . ,(regexp-opt '("+" "++" "+++" "+>"))) + (?- . ,(regexp-opt '("--" "---" "-->" "-<" "-<<" "->" "->>" "-}" "-~"))) + (?. . ,(regexp-opt '(".-" ".." "..." "..<" ".="))) + (?/ . ,(regexp-opt '("/*" "/**" "//" "///" "/=" "/==" "/>"))) + (?: . ,(regexp-opt '(":" "::" ":::" ":=" ":<" ":=" ":>"))) + (?0 . "0\\(?:\\(x[a-fA-F0-9]\\).?\\)") ; Tries to match the x in 0xDEADBEEF + ;; (?x . ,(regexp-opt '("x"))) ; Also tries to match the x in 0xDEADBEEF + (?\; . ,(regexp-opt '(";;"))) + (?< . ,(regexp-opt '("