Skip to content

Commit 03b4859

Browse files
committed
Add instant and prefix-based completion (#36)
1 parent 1895efd commit 03b4859

File tree

4 files changed

+189
-91
lines changed

4 files changed

+189
-91
lines changed

README.md

+24
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,18 @@ To explicitly convert (or convert back) use commands **UnicodeMath: Convert**, *
6262

6363
To select symbols from list, use command **UnicodeMath: Insert**
6464

65+
Instant conversion
66+
------------------
67+
68+
Instant conversion allows eager conversion of symbols in the following situations:
69+
70+
1. When `\name` is typed, there is a symbol called `name`, and no other symbol starts with `name`;
71+
2. When `\nameX` is typed (for `X` any character), there is a symbol called `name`, but none that starts with `nameX`.
72+
73+
The intent is to remove control keystrokes and get the same result as when typing LaTeX code; for instance typing `\delta \subseteq \pi(f)` with instant conversion enabled will input `δ ⊆ π(f)`.
74+
75+
When using instant conversion it is recommended to disable `accept_prefixes`. `convert_on_space` can also be disabled to make a space after a symbol name use case 2 above instead of activating the conversion command.
76+
6577
Settings
6678
--------
6779

@@ -115,6 +127,18 @@ Enable (default) or disable converting list of chars with prefix:
115127
"convert_list": true
116128
</pre>
117129

130+
Enable or disable (default) instant conversion:
131+
132+
<pre>
133+
"convert_instantly": true
134+
</pre>
135+
136+
Enable or disable (default) treating a non-ambiguous prefix of a symbol name as the full name:
137+
138+
<pre>
139+
"accept_prefixes": true
140+
</pre>
141+
118142
Font settings
119143
---
120144

UnicodeMath.sublime-settings

+5-1
Original file line numberDiff line numberDiff line change
@@ -22,5 +22,9 @@
2222
"convert_sub_super": true,
2323
// List-convert, \\prefix\abc → \prefixa\prefixb\prefixc, for example
2424
// \\Bbb\ABC → \BbbA\BbbB\BbbC → 𝔸𝔹ℂ
25-
"convert_list": true
25+
"convert_list": true,
26+
// Convert instantly when an escape is complete, without pressing space
27+
"convert_instantly": false,
28+
// Treat a non-ambiguous prefix as a full symbol name
29+
"accept_prefixes": false,
2630
}

mathsymbols.py

+63-31
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import sublime
2+
import bisect
23
import re
4+
from itertools import chain
35
from sys import version
46

57
PyV3 = version[0] == "3"
@@ -4869,10 +4871,21 @@ def make_synonyms():
48694871
}
48704872
return result
48714873

4872-
maths = {}
4873-
synonyms = {}
4874-
inverse_maths = {}
4875-
inverse_synonyms = {}
4874+
class Translation:
4875+
def __init__(self, initial_fun, inverse_fun):
4876+
self.initial_fun = initial_fun
4877+
self.inverse_fun = inverse_fun
4878+
4879+
self.direct = {}
4880+
self.direct_sorted = []
4881+
self.inverse = {}
4882+
4883+
def update(self, dict_mapping):
4884+
self.direct = self.initial_fun()
4885+
self.direct.update(dict((k, replace_codes(v)) for k, v in dict_mapping.items()))
4886+
self.direct_sorted = sorted(self.direct)
4887+
self.inverse = self.inverse_fun(self.direct)
4888+
48764889
CODE_RE = re.compile(r'\\u([\da-fA-F]{4})|\\U([\da-fA-F]{8})|\\U\+([\da-fA-F]{4,8})')
48774890

48784891

@@ -4933,43 +4946,39 @@ def get_settings():
49334946
return sublime.load_settings('UnicodeMath.sublime-settings')
49344947

49354948

4936-
def update_and_subscribe(d, initd, inverse_update, key):
4949+
def update_and_subscribe(tr, key):
49374950
def refill():
4938-
d.clear()
4939-
d.update(initd())
4940-
d.update(dict((k, replace_codes(v)) for k, v in get_settings().get(key, {}).items()))
4941-
inverse_update()
4951+
tr.update(get_settings().get(key, {}))
49424952
refill()
49434953
get_settings().add_on_change(key, refill)
49444954

49454955

4946-
def update_inverse_maths():
4947-
global inverse_maths
4948-
inverse_maths = dict((v, k) for k, v in maths.items())
4956+
def make_inverse_maths(direct):
4957+
return dict((v, k) for k, v in direct.items())
49494958

49504959

4951-
def update_inverse_synonyms():
4952-
global inverse_synonyms
4960+
def make_inverse_synonyms(direct):
49534961
inverse_synonyms = {}
4954-
for k, v in synonyms.items():
4955-
inverse_synonyms[v] = inverse_synonyms.get(v, [])
4956-
inverse_synonyms[v].append(k)
4962+
for k, v in direct.items():
4963+
inverse_synonyms.setdefault(v, []).append(k)
4964+
return inverse_synonyms
49574965

4966+
maths = Translation(make_maths, make_inverse_maths)
4967+
synonyms = Translation(make_synonyms, make_inverse_synonyms)
49584968

49594969
def names_by_symbol(symbol):
49604970
"""
49614971
Returns list of names by symbol specified, first element is name, others - synonyms
49624972
If no symbol found, returns empty list
49634973
"""
4964-
global inverse_maths
4965-
global inverse_synonyms
4974+
global maths, synonyms
49664975
res = []
4967-
name = inverse_maths.get(symbol, None)
4976+
name = maths.inverse.get(symbol, None)
49684977
if not name:
49694978
return res
49704979
res.append(name)
49714980
if name:
4972-
res.extend(inverse_synonyms.get(name, []))
4981+
res.extend(synonyms.inverse.get(name, []))
49734982
return res
49744983

49754984

@@ -4981,23 +4990,46 @@ def symbol_by_name(name, exclude=None):
49814990
"""
49824991
if exclude is None:
49834992
exclude = []
4984-
global maths
4985-
global synonyms
4993+
global maths, synonyms
49864994
if name in exclude:
49874995
return None
4988-
if name in maths:
4989-
return maths[name]
4990-
if name in synonyms:
4996+
if name in maths.direct:
4997+
return maths.direct[name]
4998+
if name in synonyms.direct:
49914999
exclude.append(name)
4992-
return symbol_by_name(synonyms[name], exclude) # synonym may ref on other synonym
5000+
return symbol_by_name(synonyms.direct[name], exclude) # synonym may ref on other synonym
49935001
return None
49945002

5003+
def extensions_of(sorted_strings, prefix):
5004+
"""
5005+
Yields all of the strings in sorted_strings that start with prefix.
5006+
"""
5007+
5008+
i = bisect.bisect_left(sorted_strings, prefix)
5009+
while i < len(sorted_strings) and sorted_strings[i].startswith(prefix):
5010+
yield sorted_strings[i]
5011+
i += 1
5012+
5013+
def symbol_by_prefix(prefix, *, unique=False):
5014+
"""
5015+
If the given string is a prefix of a one name or synonym, return the associated
5016+
symbol, otherwise None. If unique=True, return None when there is more than one match
5017+
"""
5018+
5019+
# determine whether there are 2+ options without generating everything
5020+
options = chain(
5021+
extensions_of(maths.direct_sorted, prefix),
5022+
extensions_of(synonyms.direct_sorted, prefix)
5023+
)
5024+
hit1 = next(options, None)
5025+
hit2 = next(options, None)
5026+
if hit1 is not None and (not unique or hit2 is None):
5027+
return symbol_by_name(hit1)
49955028

49965029
def plugin_loaded():
4997-
global maths
4998-
global synonyms
4999-
update_and_subscribe(maths, make_maths, update_inverse_maths, 'symbols')
5000-
update_and_subscribe(synonyms, make_synonyms, update_inverse_synonyms, 'synonyms')
5030+
global maths, synonyms
5031+
update_and_subscribe(maths, 'symbols')
5032+
update_and_subscribe(synonyms, 'synonyms')
50015033

50025034
if int(sublime.version()) < 3000:
50035035
plugin_loaded()

0 commit comments

Comments
 (0)