Skip to content

Commit f338ec3

Browse files
author
deathaxe
committed
Refactor command/environment completions
Refactor command and environment completions, both of which using latex_cwl_completions module as backend for completions from cwl files. - properly implements environment-name auto-completions via EnvFillAllHelper -> LatexCmdCompletions no longer has to deal with them - replaces `latex_own_command_completions` module, which was a mix of command and environment-name completions before. - add ST4 kind information for richer user experience - remove hacks to asynchronously load cwl files in a background threaded as whole completion query is already outsourced to ST's worker thread. - overall simplification of CWL file loading
1 parent 55196ac commit f338ec3

7 files changed

+487
-610
lines changed
+209
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
import os
2+
import re
3+
import sublime
4+
import sublime_plugin
5+
6+
from . import latex_input_completions
7+
8+
from .latex_cite_completions import NEW_STYLE_CITE_REGEX
9+
from .latex_cite_completions import OLD_STYLE_CITE_REGEX
10+
from .latex_cwl_completions import command_to_snippet
11+
from .latex_cwl_completions import get_cwl_command_completions
12+
from .latex_env_completions import BEGIN_END_BEFORE_REGEX
13+
from .latex_env_completions import get_own_environments
14+
from .latex_ref_completions import NEW_STYLE_REF_REGEX
15+
from .latex_ref_completions import OLD_STYLE_REF_REGEX
16+
17+
from .utils import analysis
18+
from .utils.decorators import async_completions
19+
from .utils.settings import get_setting
20+
from .utils.tex_directives import get_tex_root
21+
22+
__all__ = ["LatexCmdCompletion"]
23+
24+
# Do not do completions in these environments
25+
ENV_DONOT_AUTO_COM = [
26+
BEGIN_END_BEFORE_REGEX,
27+
OLD_STYLE_CITE_REGEX,
28+
NEW_STYLE_CITE_REGEX,
29+
OLD_STYLE_REF_REGEX,
30+
NEW_STYLE_REF_REGEX,
31+
]
32+
33+
# whether the leading backslash is escaped
34+
ESCAPE_REGEX = re.compile(r"\w*(\\\\)+([^\\]|$)")
35+
36+
37+
class NoArgs(Exception):
38+
pass
39+
40+
41+
def get_own_operators(ana):
42+
return ana.filter_commands(["DeclareMathOperator"])
43+
44+
45+
def get_own_operator_completions(tex_root):
46+
res = []
47+
48+
ana = analysis.get_analysis(tex_root)
49+
if ana:
50+
kind = (sublime.KindId.FUNCTION, "o", "Operator")
51+
for c in get_own_operators(ana):
52+
res.append(
53+
sublime.CompletionItem(
54+
trigger=c.args,
55+
annotation="local operator",
56+
details=f"from {os.path.basename(c.file_name)}",
57+
kind=kind,
58+
)
59+
)
60+
61+
return res
62+
63+
64+
def get_own_commands(ana):
65+
return ana.filter_commands(["newcommand", "renewcommand"])
66+
67+
68+
def get_own_command_completions(tex_root):
69+
res = []
70+
71+
ana = analysis.get_analysis(tex_root)
72+
if ana:
73+
kind = (sublime.KindId.FUNCTION, "f", "Command")
74+
75+
for c in get_own_commands(ana):
76+
try:
77+
if not c.optargs2:
78+
raise NoArgs()
79+
arg_count = int(c.optargs2)
80+
has_opt = bool(c.optargs2a)
81+
trigger = c.args
82+
if has_opt:
83+
trigger += f"[{c.optargs2a}]"
84+
arg_count -= 1
85+
elif arg_count == 0:
86+
raise NoArgs()
87+
trigger += "{arg}" * arg_count
88+
completion = command_to_snippet(trigger)
89+
if completion is None:
90+
raise NoArgs()
91+
completion = completion[1]
92+
except NoArgs:
93+
completion = trigger = c.args + "{}"
94+
95+
res.append(
96+
sublime.CompletionItem(
97+
trigger=trigger,
98+
completion=completion,
99+
completion_format=sublime.CompletionFormat.SNIPPET,
100+
annotation="local",
101+
details=f"from {os.path.basename(c.file_name)}",
102+
kind=kind,
103+
)
104+
)
105+
106+
for c in get_own_environments(ana):
107+
res.append(
108+
sublime.CompletionItem(
109+
trigger=f"\\begin{{{c.args}}}",
110+
completion=f"\\begin{{{c.args}}}\n\t$0\n\\end{{{c.args}}}",
111+
completion_format=sublime.CompletionFormat.SNIPPET,
112+
annotation="local",
113+
details=f"from {os.path.basename(c.file_name)}",
114+
kind=kind,
115+
)
116+
)
117+
res.append(
118+
sublime.CompletionItem(
119+
trigger=f"\\end{{{c.args}}}",
120+
annotation="local",
121+
details=f"from {os.path.basename(c.file_name)}",
122+
kind=kind,
123+
)
124+
)
125+
126+
return res
127+
128+
129+
class LatexCmdCompletion(sublime_plugin.EventListener):
130+
_cmd_cache = {}
131+
_op_cache = {}
132+
133+
@async_completions
134+
def on_query_completions(self, view, prefix, locations):
135+
pt = locations[0]
136+
if not view.match_selector(pt, "text.tex.latex"):
137+
return []
138+
139+
reg = view.line(pt)
140+
reg.b = pt
141+
142+
line = view.substr(reg)
143+
line = line[::-1]
144+
145+
is_prefixed = line[len(prefix) : len(prefix) + 1] == "\\"
146+
147+
# default completion level is "prefixed"
148+
level = get_setting("command_completion", "prefixed", view)
149+
if not (level == "always" or level == "prefixed" and is_prefixed):
150+
return []
151+
152+
# do not autocomplete if the leading backslash is escaped
153+
if ESCAPE_REGEX.match(line):
154+
# if there the autocompletion has been opened with the \ trigger
155+
# (no prefix) and the user has not enabled auto completion for the
156+
# scope, then hide the auto complete popup
157+
selector = view.settings().get("auto_complete_selector")
158+
if not prefix and not view.match_selector(pt, selector):
159+
view.run_command("hide_auto_complete")
160+
return []
161+
162+
# Do not do completions in actions
163+
if latex_input_completions.TEX_INPUT_FILE_REGEX not in ENV_DONOT_AUTO_COM:
164+
ENV_DONOT_AUTO_COM.append(latex_input_completions.TEX_INPUT_FILE_REGEX)
165+
166+
for rex in ENV_DONOT_AUTO_COM:
167+
if rex.match(line) is not None:
168+
return []
169+
170+
tex_root = get_tex_root(view)
171+
if not tex_root:
172+
return []
173+
174+
if tex_root not in self._cmd_cache:
175+
self._cmd_cache[tex_root] = get_own_command_completions(
176+
tex_root
177+
) + get_cwl_command_completions(tex_root)
178+
179+
if view.match_selector(pt, "text.tex meta.environment.math"):
180+
if tex_root not in self._op_cache:
181+
self._op_cache[tex_root] = get_own_operator_completions(tex_root)
182+
183+
return self._cmd_cache[tex_root] + self._op_cache[tex_root]
184+
185+
return self._cmd_cache[tex_root]
186+
187+
def on_close(self, view):
188+
tex_root = get_tex_root(view)
189+
if tex_root:
190+
self._cmd_cache.pop(tex_root, None)
191+
self._op_cache.pop(tex_root, None)
192+
193+
def on_post_save_async(self, view):
194+
tex_root = get_tex_root(view)
195+
if tex_root:
196+
self._cmd_cache.pop(tex_root, None)
197+
self._op_cache.pop(tex_root, None)
198+
199+
200+
def latextools_plugin_loaded():
201+
# add `\` as an autocomplete trigger
202+
prefs = sublime.load_settings("Preferences.sublime-settings")
203+
acts = prefs.get("auto_complete_triggers", [])
204+
205+
# Whether auto trigger is already set in
206+
# Preferences.sublime-settings
207+
if not any(i.get("selector") == "text.tex.latex" and i.get("characters") == "\\" for i in acts):
208+
acts.append({"characters": "\\", "selector": "text.tex.latex"})
209+
prefs.set("auto_complete_triggers", acts)

0 commit comments

Comments
 (0)