|
| 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