Merge pull request #134 from claymager/fira-logic

Port Fira Code's ligature logic
pull/141/head
Jany Belluz 2020-07-04 11:42:02 +01:00 committed by GitHub
commit 514454e9e7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 187 additions and 30 deletions

View File

@ -3,6 +3,7 @@
# Adapted from https://github.com/tonsky/FiraCode/blob/master/gen_calt.clj
from textwrap import dedent
from collections import defaultdict
import tempfile
@ -44,36 +45,192 @@ def rule(liga):
[LIG f i] LIG
[ f f i] LIG }
"""
if len(liga) == 2:
return dedent('''\
lookup {0}_{1} {{
ignore sub {0} {0}' {1};
ignore sub {0}' {1} {1};
sub {0}' {1} by LIG;
sub LIG {1}' by {0}_{1}.liga;
}} {0}_{1};
''').format(*liga)
elif len(liga) == 3:
return dedent('''\
lookup {0}_{1}_{2} {{
ignore sub {0} {0}' {1} {2};
ignore sub {0}' {1} {2} {2};
sub {0}' {1} {2} by LIG;
sub LIG {1}' {2} by LIG;
sub LIG LIG {2}' by {0}_{1}_{2}.liga;
}} {0}_{1}_{2};
''').format(*liga)
elif len(liga) == 4:
return dedent('''\
lookup {0}_{1}_{2}_{3} {{
ignore sub {0} {0}' {1} {2} {3};
ignore sub {0}' {1} {2} {3} {3};
sub {0}' {1} {2} {3} by LIG;
sub LIG {1}' {2} {3} by LIG;
sub LIG LIG {2}' {3} by LIG;
sub LIG LIG LIG {3}' by {0}_{1}_{2}_{3}.liga;
}} {0}_{1}_{2}_{3};
''').format(*liga)
rules = []
# standard ignores:
# ignore sub {0} {0}' {1};
# ignore sub {0}' {1} {1};
if tuple(liga) not in skip_ignores:
rules.extend(
[
ignore(prefix=liga[:1], head=liga[0], suffix=liga[1:]),
ignore(head=liga[0], suffix=(liga[1:] + [liga[-1]])),
]
)
# careful with repeats:
# #133 ->->->->, /**/**/**/, etc.
if len(liga) > 2 and liga[0] == liga[-1]:
rules.append(ignore([liga[-2]], liga[0], liga[1:]))
rules.append(ignore(head=liga[0], suffix=(liga[1:] + [liga[1]])))
# Don't cut into `prefix` to complete a ligature.
# i.e. regex `(?=`> is not `(?`=>.
rules.extend(
[
ignore(prefix[:-n], liga[0], liga[1:])
for prefix in ignore_prefixes
for n in range(1, len(liga))
if prefix[-n:] == liga[:n]
]
)
# hardcoded ignores, i.e. `<||>`
rules.extend(ignores[tuple(liga)])
name = "_".join(liga)
# substitution logic
# sub {0}' {1} by LIG;
# sub LIG {1}' by {0}_{1}.liga;
for i in range(len(liga)):
init = _join(["LIG" for lig in liga[:i]])
tail = _join(liga[i + 1 :])
replace = "LIG" if (i + 1 < len(liga)) else (name + ".liga")
rules.append("sub{0} {1}'{2} by {3};".format(init, liga[i], tail, replace))
# put it all together
lines = (
["lookup " + name + " {"] + [" " + r for r in rules] + ["}} {0};".format(name)]
)
return "\n".join(lines)
def _join(items):
return (" " + " ".join(items)) if items else ""
def ignore(prefix=None, head=None, suffix=None):
""" don't substitute `head` if it's surrounded by `prefix` and `suffix` """
assert head
pref = _join(prefix)
rest = _join(suffix)
return "ignore sub{0} {1}'{2};".format(pref, head, rest)
ignores = defaultdict(
list,
{
("slash", "asterisk"): [
"ignore sub slash' asterisk slash;",
"ignore sub asterisk slash' asterisk;",
],
("asterisk", "slash"): [
"ignore sub slash asterisk' slash;",
"ignore sub asterisk' slash asterisk;",
],
# ("asterisk", "asterisk"): [
# "ignore sub slash asterisk' asterisk;",
# "ignore sub asterisk' asterisk slash;",
# ],
# ("asterisk", "asterisk", "asterisk"): [
# "ignore sub slash asterisk' asterisk asterisk;",
# "ignore sub asterisk' asterisk asterisk slash;",
# ],
# <||>
("less", "bar", "bar"): ["ignore sub less' bar bar greater;"],
("bar", "bar", "greater"): ["ignore sub less bar' bar greater;"],
# # :>=
# ("colon", "greater"): ["ignore sub colon' greater equal;"],
# # {|}
# ("braceleft", "bar"): ["ignore sub braceleft' bar braceright;"],
# ("bar", "braceright"): ["ignore sub braceleft bar' braceright;"],
# # [|]
# ("bracketleft", "bar"): ["ignore sub bracketleft' bar bracketright;"],
# ("bar", "bracketright"): ["ignore sub bracketleft bar' bracketright;"],
# # <*>>> <+>>> <$>>>
# ("greater", "greater", "greater"): [
# "ignore sub [asterisk plus dollar] greater' greater greater;"
# ],
# # <<<*> <<<+> <<<$>
# ("less", "less", "less"): ["ignore sub less' less less [asterisk plus dollar];"],
# # =:=
# ("colon", "equal"): ["ignore sub equal colon' equal;"],
# =!=
("exclam", "equal"): ["ignore sub equal exclam' equal;"],
# =!==
("exclam", "equal", "equal"): ["ignore sub equal exclam' equal equal;"],
# =<= <=< <=> <=| <=: <=! <=/
("less", "equal"): [
"ignore sub equal less' equal;",
"ignore sub less' equal [less greater bar colon exclam slash];",
],
# >=<
# =>= >=> >=< >=| >=: >=! >=/
("greater", "equal"): [
"ignore sub equal greater' equal;",
"ignore sub greater' equal [less greater bar colon exclam slash];",
],
# <*>> <+>> <$>>
# >>->> >>=>>
("greater", "greater"): [
# "ignore sub [asterisk plus dollar] greater' greater;",
# "ignore sub [hyphen equal] greater' greater;",
# "ignore sub greater' greater [hyphen equal];",
],
# <<*> <<+> <<$>
# <<-<< <<=<<
("less", "less"): [
# "ignore sub less' less [asterisk plus dollar];",
# "ignore sub [hyphen equal] less' less;",
# "ignore sub less' less [hyphen equal];",
],
# ||-|| ||=||
("bar", "bar"): [
"ignore sub [hyphen equal] bar' bar;",
"ignore sub bar' bar [hyphen equal];",
],
# # <--> >--< |--|
# ("hyphen", "hyphen"): [
# "ignore sub [less greater bar] hyphen' hyphen;",
# "ignore sub hyphen' hyphen [less greater bar];",
# ],
# # <---> >---< |---|
# ("hyphen", "hyphen", "hyphen"):
# "ignore sub [less greater bar] hyphen' hyphen hyphen;",
# "ignore sub hyphen' hyphen hyphen [less greater bar];",
# ],
("equal", "equal"): [ # ==
# "ignore sub bracketleft equal' equal;", # [==
# "ignore sub equal' equal bracketright;",# ==]
"ignore sub equal [colon exclam] equal' equal;", # =:== =!==
# "ignore sub [less greater bar slash] equal' equal;", # <== >== |== /==
# "ignore sub equal' equal [less greater bar slash];", # ==< ==> ==| ==/
"ignore sub equal' equal [colon exclam] equal;", # ==:= ==!=
],
# [===[ ]===]
# [=== ===]
# <===> >===< |===| /===/ =:=== =!=== ===:= ===!=
("equal", "equal", "equal"): [
# "ignore sub bracketleft equal' equal equal;",
# "ignore sub equal' equal equal bracketright;",
"ignore sub equal [colon exclam] equal' equal equal;",
"ignore sub [less greater bar slash] equal' equal equal;",
# "ignore sub equal' equal equal [less greater bar slash];",
"ignore sub equal' equal equal [colon exclam] equal;",
],
# #118 https://
("slash", "slash"): ["ignore sub colon slash' slash;"],
},
)
ignore_prefixes = [
["parenleft", "question", "colon"],
# Regexp lookahead/lookbehind
["parenleft", "question", "equal"],
["parenleft", "question", "less", "equal"],
["parenleft", "question", "exclam"],
["parenleft", "question", "less", "exclam"],
# PHP <?=
["less", "question", "equal"],
]
# DO NOT generate ignores at all
skip_ignores = {
# # <<*>> <<+>> <<$>>
# ("less", "asterisk", "greater"),
# ("less", "plus", "greater"),
# ("less", "dollar", "greater"),
}
def indent(text, prefix):