Merge pull request #2345 from gagbo/feature/harfbuzzed-ligatures

Use composite to call harfbuzz for ligatures
This commit is contained in:
Henrik Lissner 2020-04-30 18:39:21 -04:00 committed by GitHub
commit b4f2b25d8c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 103 additions and 1 deletions

View file

@ -22,6 +22,8 @@ This module enables ligatures and/or arbitrary symbol substitutions with
This module has no dedicated maintainers.
** Module Flags
*** Font ligatures module flags
Those flags are ignored on emacs-mac and on other platforms if running emacs 28+ with harfbuzz feature. In those cases, ligatures are handled without needing the extra configuration provided by those flags.
+ =+fira= Enables =Fira Code= ligatures. This requires Fira Code Symbol and a
patched version of Fira Code (see below).
+ =+hasklig= Enable =Hasklig= ligatures. This requires a patched version of the
@ -35,8 +37,13 @@ This module has no dedicated maintainers.
This module installs no packages.
* Prerequisites
For ligatures to work, you must:
** Ligatures
For ligatures to work, you must either have a recent enough version of emacs which will compose ligatures automatically, or use the font specific configurations.
*** Emacs-mac port or Emacs 28+
Ligatures are handled without needing additional configuration. If this doesn't work, report a bug with your current version of emacs and the ligatures which aren't working.
*** Not Emacs-mac and Emacs <= 27
1. Enable one of the four ligature font flags: =+fira=, =+hasklig=, =+iosevka=
or =+pragmata-pro=.
2. Install the patched version of the associated font with ~M-x
@ -46,6 +53,15 @@ For ligatures to work, you must:
* TODO Features
# An in-depth list of features, how to use them, and their dependencies.
** TODO Mathematical symbols replacement
** Coding ligatures
This module includes configuration to compose combinations like =->= or =::= into prettier glyphs (called a ligature). Depending on the current version of emacs, this is implemented in two different ways :
- prettify-symbols-mode method :: this is the "legacy" method. It uses a font which haves the ligatures as separate unicode symbols, and using prettify-symbols-mode, =->=-like combinations are manually listed and replaced with the correct symbol. The mapping between =->=-like sequences and unicode values in the font are font-specific ; therefore =+fira=, =+iosevka=... files and specific fonts are necessary for it to work.
- composition-function-table method :: regexes are used to match all the usual sequences which are composed into ligatures. These regexes are passed to emacs directly, which asks Harfbuzz to shape it. Ligatures are obtained automatically depending on the capabilities of the font, and no font-specific configuration is necessary.
Emacs-mac port implements the /composition-function-table/ method in its code, nothing is necessary on Doom side ; otherwise, Doom implements the /composition-function-table/ for emacs 28+ built with Harfbuzz support, and the /prettify-symbols-mode/ method otherwise.
Even though harfbuzz has been included in emacs 27, there is currently a [[https://lists.gnu.org/archive/html/bug-gnu-emacs/2020-04/msg01121.html][bug (#40864)]] which prevents a safe usage of /composition-function-table/ method in emacs 27.
* TODO Configuration
# How to configure this module, including common problems and how to address them.

View file

@ -98,10 +98,96 @@ 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
`((?! . ,(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 '("<!--" "<$" "<$>" "<*" "<*>" "<+" "<+>" "<-" "<--" "<->" "</" "</>" "<<" "<<-" "<<<" "<<=" "<=" "<=" "<=<" "<==" "<=>" "<>" "<|" "<|>" "<~" "<~~")))
(?= . ,(regexp-opt '("=/=" "=:=" "=<<" "==" "===" "==>" "=>" "=>>")))
(?> . ,(regexp-opt '(">-" ">->" ">:" ">=" ">=>" ">>" ">>-" ">>=" ">>>")))
(?? . ,(regexp-opt '("??" "?." "?:" "?=")))
(?\[ . ,(regexp-opt '("[]" "[|]" "[|")))
(?\\ . ,(regexp-opt '("\\\\" "\\\\\\" "\\\\n")))
(?^ . ,(regexp-opt '("^=" "^==")))
(?w . ,(regexp-opt '("www" "wwww")))
(?{ . ,(regexp-opt '("{-" "{|" "{||" "{|}" "{||}")))
(?| . ,(regexp-opt '("|=" "|>" "||" "||=" "|->" "|=>" "|]" "|}")))
(?_ . ,(regexp-opt '("_|_" "__")))
(?~ . ,(regexp-opt '("~-" "~=" "~>" "~@" "~~" "~~>"))))
"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.
This variable is used only if you built Emacs with Harfbuzz on a version >= 28")
(defvar +prog-ligatures-modes '(not org-mode)
"List of major modes in which ligatures should be enabled.
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).")
(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
;; Starting from emacs "28" because this code breaks without fe903c5
:when (and EMACS28+ (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,
;; using the same composition-function-table method
;; https://bitbucket.org/mituharu/emacs-mac/src/26c8fd9920db9d34ae8f78bceaec714230824dac/lisp/term/mac-win.el?at=master#lines-345:805
;; 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
;; if they are above emacs-27
((and EMACS28+
(string-match-p "HARFBUZZ" system-configuration-features)
(not (null +prog-ligatures-modes)))
nil)
;; Font-specific ligature support
((featurep! +fira)
(load! "+fira"))