From 13f6baf9831685f96e1d92b1104ca4d740ea1197 Mon Sep 17 00:00:00 2001 From: Richard Stein Date: Thu, 11 May 2017 18:05:08 +0200 Subject: [PATCH 1/5] Initial import of "insert math popup" --- Default.sublime-keymap | 75 +++++ latextools_utils/latexhtml.py | 209 ++++++++++++++ math_popup.py | 351 ++++++++++++++++++++++++ resource/insert_math_popup.css | 38 +++ resource/insert_math_popup_entries.json | 91 ++++++ resource/latex_symbols.yaml | 325 ++++++++++++++++++++++ 6 files changed, 1089 insertions(+) create mode 100644 Default.sublime-keymap create mode 100644 latextools_utils/latexhtml.py create mode 100644 math_popup.py create mode 100644 resource/insert_math_popup.css create mode 100644 resource/insert_math_popup_entries.json create mode 100644 resource/latex_symbols.yaml diff --git a/Default.sublime-keymap b/Default.sublime-keymap new file mode 100644 index 000000000..3cfb47e2f --- /dev/null +++ b/Default.sublime-keymap @@ -0,0 +1,75 @@ +[ + { + "keys": [""], + "command": "latextools_math_popup_insert_char", + "context": + [ + { "key": "latextools.math_popup.await_next_key" } + ] + }, + { + "keys": ["?"], + "command": "latextools_math_popup_toggle_mode", + "context": + [ + { "key": "latextools.math_popup.await_next_key" } + ] + }, + { + "keys": ["backspace"], + "command": "latextools_math_popup_delete_char", + "context": + [ + { "key": "latextools.math_popup.await_next_key" } + ] + }, + { + "keys": ["up"], + "command": "latextools_math_popup_move_line", + "args": { + "forward": false + }, + "context": + [ + { "key": "latextools.math_popup.await_next_key" } + ] + }, + { + "keys": ["down"], + "command": "latextools_math_popup_move_line", + "args": { + "forward": true + }, + "context": + [ + { "key": "latextools.math_popup.await_next_key" } + ] + }, + { + "keys": [" "], + "command": "latextools_math_popup_commit", + "args": { + "insert_characters": " " + }, + "context": + [ + { "key": "latextools.math_popup.await_next_key" } + ] + }, + { + "keys": ["tab"], + "command": "latextools_math_popup_commit", + "context": + [ + { "key": "latextools.math_popup.await_next_key" } + ] + }, + { + "keys": ["enter"], + "command": "latextools_math_popup_commit", + "context": + [ + { "key": "latextools.math_popup.await_next_key" } + ] + }, +] \ No newline at end of file diff --git a/latextools_utils/latexhtml.py b/latextools_utils/latexhtml.py new file mode 100644 index 000000000..2c0ae869c --- /dev/null +++ b/latextools_utils/latexhtml.py @@ -0,0 +1,209 @@ +import html +import re +import shlex +import string +import yaml + +import sublime + + +def _create_char_map(upper_start, lower_start=None): + # if lower start is None we assume the lower case chars + # to start after the uppercase char + if lower_start is None: + lower_start = upper_start + len(string.ascii_lowercase) + char_map = { + c: chr(lower_start + i) + for i, c in enumerate(string.ascii_lowercase) + } + char_map.update({ + c: chr(upper_start + i) + for i, c in enumerate(string.ascii_uppercase) + }) + return char_map + + +def get_math_font_maps(): + if hasattr(get_math_font_maps, "result"): + return get_math_font_maps.result + mbb_map = _create_char_map(120120) + # fix missing chars + mbb_map.update({ + "C": chr(8450), + "H": chr(8461), + "N": chr(8469), + "P": chr(8473), + "Q": chr(8474), + "R": chr(8477), + "Z": chr(8484), + }) + mcal_map = _create_char_map(119964) + mfrak_map = _create_char_map(120068) + + math_font_maps = { + "\\mathfrak": mfrak_map, + "\\mathbb": mbb_map, + "\\mathcal": mcal_map + } + get_math_font_maps.result = math_font_maps + return get_math_font_maps.result + + +def get_tag_map(): + tag_map = { + "\\textbf": "b", + "\\textit": "i", + "\\textsl": "i", + "\\emph": "em", + "\\underline": "u", + "\\texttt": "code", + "\\mathbf": "b", + "\\mathit": "i", + } + return tag_map + + +_RE_IS_COMMAND = re.compile(r"\\[{0}]+$".format(string.ascii_letters + "@")) + + +def is_command(token): + return bool(_RE_IS_COMMAND.match(token)) + + +def _escape_html(content): + return html.escape(content, quote=False) + + +def _highlight_missing(content): + return '{0}'.format(_escape_html(content)) + + +def parse_snippet(it): + c = next(it) + + if c.isnumeric(): + return '{0}'.format("$" + c) + if c == "0": + return "|" + return chr(8943) + return "----" + + +def tokenize(latex_string): + # lex the string and set some options + shl = shlex.shlex(latex_string) + shl.wordchars = string.ascii_letters + "\\" + # we want to preserve white spaces + shl.whitespace = "" + shl.commenters = "%" + + # split command, which are directly behind each other + # e.g. \alpha\beta -> \alpha,\beta + for token in shl: + if "\\" not in token[1:]: + yield token + continue + print("token:", token) + while True: + try: + index = token[1:].index("\\") + 1 + except ValueError: + break + yield token[0:index] + + token = token[index:] + + +def get_symbol_map(): + if hasattr(get_symbol_map, "result"): + return get_symbol_map.result + + try: + resource = sublime.load_resource( + "Packages/LaTeXtools/resource/latex_symbols.yaml") + get_symbol_map.result = yaml.load(resource) + except (OSError, ValueError): + return {} + + return get_symbol_map.result + + +def highlight_latex(latex_string, is_math=True, as_snippet=False, + skip_unknown=True): + symbol_map = get_symbol_map() + math_font_maps = get_math_font_maps() + tag_map = get_tag_map() + + def _missing(content): + return _highlight_missing(content) + + it = tokenize(latex_string) + html_lst = [] + tags = [] + do_tag = "" + escape_symbol = False # escape symbols, e.g. \& + for t in it: + # if we have an escaped char, we insert it (next char), e.g. \& + if escape_symbol: + html_lst.append(_escape_html(t)) + escape_symbol = False + elif t == "\\": + escape_symbol = True + # if it is a symbol we insert it, e.g. \alpha + elif t in symbol_map: + html_lst.append(symbol_map[t]) + # insert special symbols from the map, e.g. \mathbb{N} + elif t in math_font_maps: + char_map = math_font_maps.get(t, {}) + orig_t = t + # TODO handle wrong input + assert next(it) == "{" + t = next(it) + if t == "$": + c = "{0}{{{1}}}".format(orig_t, parse_snippet(it)) + # c += parse_snippet(it) + else: + c = char_map.get(t, _missing(t)) + html_lst.append(c) + assert next(it) == "}" + # if we have a markup command (text/textit) we prepare the + # corresponding tag + elif t in tag_map: + do_tag = tag_map[t] + # handle super and subscript + elif t == "^" or t == "_": + # subscript/superscript are not supported by minihtml, + # hence we just use small instead + do_tag = "small" + elif t == "{": + if do_tag: + html_lst.append("<{0}>".format(do_tag)) + tags.append("".format(do_tag)) + else: + html_lst.append(_missing("{")) + tags.append(_missing("}")) + + do_tag = "" + elif t == "}": + try: + html_lst.append(tags.pop()) + except IndexError: + html_lst.append(_missing("}")) + # test single char tags for ^ and _ + elif t.isalnum() and do_tag: + html_lst.append("<{0}>{1}{2}".format(do_tag, t[:1], t[1:])) + do_tag = "" + elif is_command(t): + if skip_unknown: + html_lst.append(" ") + else: + html_lst.append(_missing(t)) + elif t.isalnum() or t.isspace() or t in "+-*/'\"": + html_lst.append(t) + elif t == "$" and as_snippet: + html_lst.append(parse_snippet(it)) + else: + html_lst.append(_missing(t)) + + html_str = "".join(html_lst) + return html_str diff --git a/math_popup.py b/math_popup.py new file mode 100644 index 000000000..7cce7e454 --- /dev/null +++ b/math_popup.py @@ -0,0 +1,351 @@ +import html + +import sublime +import sublime_plugin + +import mdpopups + +from .latextools_utils import latexhtml +# TODO for development reload +import imp +imp.reload(latexhtml) + +_prefix_len = 10 +_symbol_len = 10 +_command_len = 10 + + +def _get_command_entries(): + if hasattr(_get_command_entries, "result"): + return _get_command_entries.result + try: + resource = sublime.load_resource( + "Packages/LaTeXTools/resource/insert_math_popup_entries.json") + command_mapping = sublime.decode_value(resource) + _get_command_entries.result = command_mapping + except (OSError, ValueError): + return [] + return _get_command_entries.result + pass + + +def _get_css(): + if hasattr(_get_css, "result"): + return _get_css.result + try: + _get_css.result = sublime.load_resource( + "Packages/LaTeXTools/resource/insert_math_popup.css") + except OSError: + return ".selected { background-color: #FFFF00; }" + return _get_css.result + + +_html_options = """ +
+(back) +
+""" + + +def show_html_popup(view, html_content): + + def cleanup(): + _cleanup(view) + + def _update_popup(content): + mdpopups.update_popup( + view, content, md=False, css=_get_css(), + wrapper_class="latextools-insert-math") + + def _show_popup(content, on_navigate, on_hide): + mdpopups.show_popup( + view, content, md=False, css=_get_css(), + wrapper_class="latextools-insert-math", on_navigate=on_navigate, + on_hide=on_hide) + + def navigate(href): + print("navigate!!!") + print("href:", href) + + if href == "toggle_search": + do_search = view.settings().get("lt_mp_search") + view.settings().set("lt_mp_search", not do_search) + view.settings().set("lt_mp_prefix", "") + _execute_command(view, "") + elif href == "options": + _update_popup(_html_options) + elif href == "option_back": + _update_popup(html_content) + else: + try: + mapping = _prefix_entries(view) + entry = mapping[int(href)] + except (ValueError, KeyError): + print("Unkown href: ", href) + return + cleanup() + mdpopups.hide_popup(view) + view.run_command("insert", {"characters": entry[1]}) + if mdpopups.is_popup_visible(view): + _update_popup(html_content) + else: + _show_popup(html_content, on_navigate=navigate, on_hide=cleanup) + + +def _escape_html(s): + return html.escape(s, quote=False) + + +def _get_html_size(s): + try: + special_size = _get_html_size.special_size + except AttributeError: + special_size = latexhtml.get_symbol_map().get(":special_size:", {}) + _get_html_size.special_size = special_size + + # TODO better size estimation + if s in special_size: + return special_size[s] + else: + return len(s) + return len(s) + + +def _imake_html(entries, do_search, prefix, invalid_char, index): + # create the header + yield '
' + if do_search: + yield "Search: \\" + else: + yield "Match: " + yield '' + yield _escape_html(prefix) + yield "" + yield " " + if invalid_char: + yield " " * 4 + yield '' + yield "({0})".format(_escape_html(invalid_char)) + yield "" + yield "
" + yield '' + yield "(Toggle Mode)" + yield "" + yield " " + yield '(Options)' + + def high_common_prefix(s): + pre = "\\" + prefix if do_search else prefix + # check there is really a match! + if not s.startswith(pre): + return _escape_html(s) + post = _escape_html(s[len(pre):]) + pre = _escape_html(pre) + return ( + '{0}' + '{1}' + .format(pre, post) + ) + for i, m in enumerate(entries): + trigger, value, *rest = m + hlatex = latexhtml.highlight_latex( + value, is_math=True, as_snippet=True) + + if i == index: + yield "
" + else: + yield "
" + yield ''.format(i) + + if not do_search and prefix: + yield high_common_prefix(trigger) + else: + yield _escape_html(trigger) + + yield (" " * (_prefix_len - len(trigger))) + yield "" + yield hlatex + yield "" + vsize = _get_html_size(hlatex) + yield (" " * (_symbol_len - vsize)) + yield "" + if do_search and prefix: + yield high_common_prefix(value) + else: + yield _escape_html(value) + yield "" + yield "" + yield "
" + + +def _make_html(entries, do_search, prefix, invalid_char, index): + return "".join(_imake_html(**locals())) + + +def _prefix_entries(view, prefix=None): + search_mode = view.settings().get("lt_mp_search") + if prefix is None: + prefix = view.settings().get("lt_mp_prefix", "") + cache = (_prefix_entries.cache if not search_mode + else _prefix_entries.search_cache) + try: + return cache[prefix] + except KeyError: + print("not cached" + prefix, search_mode) + sprefix = "\\" + prefix + if search_mode: + def is_prefixed(m): + return m[1].startswith(sprefix) + else: + def is_prefixed(m): + return m[0].startswith(prefix) + + cache[prefix] = [m for m in _get_command_entries() if is_prefixed(m)] + return cache[prefix] + + +_prefix_entries.cache = {} +_prefix_entries.search_cache = {} + + +def insert_entry(view, value, postfix=""): + content = value[1] + try: + info = value[2] + except IndexError: + info = {} + vtype = info.get("type", "insert") + if vtype == "insert": + view.run_command("insert", {"characters": content + postfix}) + elif vtype == "snippet": + view.run_command("insert_snippet", {"contents": content}) + + +def _cleanup(view): + view.settings().erase("lt_mp_sel") + view.settings().erase("lt_mp_prefix") + view.settings().erase("lt_mp_search") + view.settings().erase("lt_mp_index") + view.hide_popup() + + +def _execute_command(view, prefix, invalid_char="", index=-1): + entries = _prefix_entries(view, prefix) + do_search = view.settings().get("lt_mp_search") + if len(entries) == 1 and not do_search: + _cleanup(view) + insert_entry(view, entries[0], "") + return + if index == -1 and prefix: + index = 0 + view.settings().set("lt_mp_prefix", prefix) + if index != -1: + view.settings().set("lt_mp_index", index) + html_content = _make_html( + entries, do_search, prefix, invalid_char, index) + show_html_popup(view, html_content) + + +class LatextoolsMathPopupShowCommand(sublime_plugin.WindowCommand): + """Open and show the LaTeXTools "insert math popup".""" + + def run(self, do_search=False): + view = self.window.active_view() + # store the current selection to clear the command if the + # selection changes + v = str(list(view.sel())) + view.settings().set("lt_mp_sel", v) + view.settings().set("lt_mp_search", do_search) + + _execute_command(view, "") + + +class LatextoolsMathPopupToggleModeCommand(sublime_plugin.WindowCommand): + """ + Toggle the input mode of the LaTeXTools "insert math popup" between + matching a trigger sequence and searching the corresponding command. + """ + + def run(self): + view = self.window.active_view() + do_search = view.settings().get("lt_mp_search") + view.settings().set("lt_mp_search", not do_search) + # remove the prefix and end the command + _execute_command(view, "") + + +class LatextoolsMathPopupCommitCommand(sublime_plugin.WindowCommand): + """ + Commit the selected entry of the LaTeXTools "insert math popup". + This insert the corresponding text/snippet into the view. + """ + + def run(self, insert_characters="", reopen=False): + view = self.window.active_view() + entries = _prefix_entries(view) + index = view.settings().get("lt_mp_index", 0) + print("index:", index) + value = entries[index] + insert_entry(view, value, insert_characters) + _cleanup(view) + + +class LatextoolsMathPopupInsertCharCommand(sublime_plugin.WindowCommand): + """Insert a character into the LaTeXTools "insert math popup".""" + + def run(self, character): + view = self.window.active_view() + prefix = view.settings().get("lt_mp_prefix", "") + + if _prefix_entries(view, prefix + character): + _execute_command(view, prefix + character, invalid_char="") + else: + _execute_command(view, prefix, invalid_char=character) + + +class LatextoolsMathPopupDeleteCharCommand(sublime_plugin.WindowCommand): + """Delete a character from the LaTeXTools "insert math popup".""" + + def run(self): + view = self.window.active_view() + prefix = view.settings().get("lt_mp_prefix", "") + _execute_command(view, prefix[:-1]) + + +class LatextoolsMathPopupMoveLineCommand(sublime_plugin.WindowCommand): + """ + Select the next/previous entry in the LaTeXTools "insert math popup". + """ + + def run(self, forward): + view = self.window.active_view() + index = view.settings().get("lt_mp_index", 0) + + index += 1 if forward else -1 + + entries_length = len(_prefix_entries(view)) + if index >= entries_length: + index = 0 + elif index < 0: + index = entries_length - 1 + + print("index:", index) + # keep the old prefix + prefix = view.settings().get("lt_mp_prefix", "") + _execute_command(view, prefix, index=index) + + +class LatextoolsMathPopupContextListener(sublime_plugin.EventListener): + """Check whether the LaTeXTools "insert math popup" is open.""" + + def on_query_context(self, view, key, operator, operand, match_all): + # TODO handle operator/operand + if key != "latextools.math_popup.await_next_key": + return + sel = view.settings().get("lt_mp_sel") + if not sel: + return False + if str(list(view.sel())) == sel: + return True + _cleanup(view) + return False diff --git a/resource/insert_math_popup.css b/resource/insert_math_popup.css new file mode 100644 index 000000000..5865d8555 --- /dev/null +++ b/resource/insert_math_popup.css @@ -0,0 +1,38 @@ +html { + background-color: #333333; + color: #CCCCCC; +} +a { + color: #cc0000; +} +a.entry { + color: #CCCCCC; + text-decoration: none; +} +.prefix_line { + background-color: #333399; +} +.prefix { + font-weight: bold; +} +.invalid_char { + color: red; +} +.selected { + background-color: #993300; + color: #FFFF00; + font-weight: bold; +} +.key { + width: 150px; +} +.common_prefix { + font-style: italic; + # font-weight: bold; +} +.pkg_missing { + color: red; +} +.pkg_available { + color: green; +} \ No newline at end of file diff --git a/resource/insert_math_popup_entries.json b/resource/insert_math_popup_entries.json new file mode 100644 index 000000000..096a157e8 --- /dev/null +++ b/resource/insert_math_popup_entries.json @@ -0,0 +1,91 @@ +[ + // lower greek characters + ["a", "\\alpha"], + ["b", "\\beta"], + ["g", "\\gamma"], + ["d", "\\delta"], + ["e", "\\epsilon"], + ["z", "\\zeta"], + ["h", "\\eta"], + ["j", "\\theta"], + ["k", "\\kappa"], + ["l", "\\lambda"], + ["m", "\\mu"], + ["n", "\\nu"], + ["x", "\\xi"], + ["p", "\\pi"], + ["r", "\\rho"], + ["s", "\\sigma"], + ["t", "\\tau"], + ["u", "\\upsilon"], + ["f", "\\phi"], + ["v", "\\varphi"], + ["q", "\\chi"], + ["y", "\\psi"], + ["w", "\\omega"], + + // capital greek characters + ["D", "\\Delta"], + ["G", "\\Gamma"], + ["Q", "\\Theta"], + ["L", "\\Lambda"], + ["X", "\\Xi"], + ["P", "\\Pi"], + ["S", "\\Sigma"], + ["U", "\\Upsilon"], + ["F", "\\Phi"], + ["Y", "\\Psi"], + ["W", "\\Omega"], + + ["A", "\\forall"], + ["E", "\\exists"], + ["8", "\\infty"], + ["0", "\\emptyset"], + + ["i", "\\in"], + ["+", "\\cup"], + ["*", "\\cap"], + + ["!=", "\\neq"], + ["~", "\\sim"], + ["~~", "\\approx"], + ["!~~", "\\napprox"], + + ["cc", "\\mathcal{$1}$0", {"type": "snippet"}], + ["cb", "\\mathbb{$1}$0", {"type": "snippet"}], + ["cf", "\\mathfrak{$1}$0", {"type": "snippet"}], + + ["cbn", "\\mathbb{N}"], + ["cbz", "\\mathbb{Z}"], + ["cbq", "\\mathbb{Q}"], + ["cbr", "\\mathbb{R}"], + ["cbc", "\\mathbb{C}"], + + ["<", "\\leq"], + [">", "\\geq"], + ["<-", "\\leftarrow"], + ["<--", "\\longleftarrow"], + ["->", "\\rightarrow"], + ["-->", "\\longrightarrow"], + ["<=", "\\Leftarrow"], + ["<==", "\\Longleftarrow"], + ["=>", "\\Rightarrow"], + ["==>", "\\Longrightarrow"], + ["<->", "\\leftrightarrow"], + ["<=>", "\\Leftrightarrow"], + ["<-->", "\\longleftrightarrow"], + ["<==>", "\\Longleftrightarrow"], + + ["|", "\\mid"], + ["|-", "\\vdash"], + ["|=", "\\vDash"], + ["-|", "\\dashv"], + ["|->", "\\mapsto"], + + + [".", "\\cdot"], + ["**", "\\times"], + + [":=", "\\coloneqq", {"package": "mathtools"}], + [":-", "\\coloneq", {"package": "mathtools"}], +] \ No newline at end of file diff --git a/resource/latex_symbols.yaml b/resource/latex_symbols.yaml new file mode 100644 index 000000000..92b778a49 --- /dev/null +++ b/resource/latex_symbols.yaml @@ -0,0 +1,325 @@ +# lower greek +\alpha: α +\beta: β +\gamma: γ +\delta: δ +\epsilon: ε +\zeta: ζ +\eta: η +\theta: θ +\kappa: κ +\lambda: λ +\mu: μ +\nu: ν +\xi: ξ +\pi: π +\rho: ρ +\sigma: σ +\tau: τ +\upsilon: υ +\phi: ϕ +\chi: χ +\psi: ψ +\omega: ω +\varphi: φ + +# capital greek +\Delta: Δ +\Gamma: Γ +\Theta: Θ +\Lambda: Λ +\Xi: Ξ +\Pi: Π +\Sigma: Σ +\Upsilon: Υ +\Phi: Φ +\Psi: Ψ +\Omega: Ω + +# special symbols +\nabla: ∇ +\infty: ∞ +\forall: ∀ +\exists: ∃ +\sum: ∑ +\prod: Π +\bot: ⊥ +\top: ⊤ + +# simple arrows +\rightarrow: → +\leftarrow: ← +\uparrow: ↑ +\downarrow: ↓ +\leftrightarrow: ↔ +\updownarrow: ↕ + +\Rightarrow: ⇒ +\Leftarrow: ⇐ +\Uparrow: ⇑ +\Downarrow: ⇓ +\Leftrightarrow: ⇔ +\Updownarrow: ⇕ + +\longrightarrow: ⟶ +\longleftarrow: ⟵ +\longuparrow: ↑ +\longdownarrow: ↓ +\longleftrightarrow: ⟷ +\longupdownarrow: ↕ + +\Longrightarrow: ⟹ +\Longleftarrow: ⟸ +\Longuparrow: ⇑ +\Longdownarrow: ⇓ +\Longleftrightarrow: ⟺ +\Longupdownarrow: ⇕ + +# binary relations +\ne: ≠  +\neq: ≠  +\vdash: ⊢ +\dashv: ⊣ +\vDash: ⊨ +\models: ⊨ +\nvdash: ⊬ +\nvDash: ⊭ +\Vdash: ⊩ +\nVDash: ⊯ +\Vvdash: ⊪ +\multimap: ⊸ +\equiv: ≡ +\nequiv: ≢ +\coloneq: ':-' +\eqcolon: '-:' +\coloneqq: ≔ +\Coloneqq: ⩴ +\eqcolon: ≕ +\eqcirc: ≖ +\ni: ∋ +\in: ∈ +# \not\in: ∉  + +\doteqdot: ≑ +\risingdotseq: ≓ +\fallingdotseq: ≒ +\tilde: '~' +\sim: '~' +\eqsim: ≂ +\nsimeq: ≄ +\backsimeq: ⋍ +\ncong: ≇ +\nsim: ≁ +\approx: ≈ +\napprox: ≉ + +\bumpeq: ≏ +\Bumpeq: ≎ + +\subset: ⊂ +\supset: ⊃ +\subseteq: ⊆ +\supseteq: ⊇ + +\leq: ≤ +\geq: ≥ +\nless: ≮ +\ngtr: ≯ +\ll: ≪ +\gg: ≫ +\lll: ⋘ +\ggg: ⋙ +\nleqslant: ≰ +\leqslant: ≤ +\geqslant: ≥ +\ngeqslant: ≱ +\nlessgtr: ≸ +\ngtrless: ≹ +\lneqq: ≨ +\leqq: ≦ +\geqq: ≧ +\gneqq: ≩ +\lnsim: ⋦ +\lesssim: ≲ +\gtrsim: ≳ +\gnsim: ⋧ +\nprec: ⊀ +\nsucc: ⊁ + +# operators +\lnot: ¬ +\neg: ¬ +\not: / +\in: ∈ +\backepsilon: ∍ +\times: × +\cdot: ∙ +\setminus: ∖ +\cup: ∪ +\cap: ∩ +\vee: ∨ +\wedge: ∧ +\lor: ∨ +\land: ∧ +\oplus: ⊕ +\odot: ⊙ +\dotplus: ∔ +\dotdiv: ∸ +\pm: ± +\mp: ∓ +\mid: ∣ + +# named functions +\cos: cos +\det: det +\exp: exp +\hat: '^' +\inf: inf +\lg: lg +\lim: lim +\liminf: liminf +\limsup: limsup +\ln: ln +\log: log +\min: min +\max: max +\sin: sin +\sin: sin +\sup: sup +\tan: tan + + +# advanced arrows +\nleftarrow: ↚ +\nrightarrow: ↛ +\nleftrightarrow: ↮ +\lightning: ↯ +\nLeftrightarrow: ⇎ +\APLuparrowbox: ⍐ +\APLdownarrowbox: ⍗ +\APLleftarrowbox: ⍇ +\APLrightarrowbox: ⍈ +\pointer: ➪ +\nLeftarrow: ⇍ +\nRightarrow: ⇏ +\Lleftarrow: ⇚ +\Rrightarrow: ⇛ +\Lsh: ↰ +\Rsh: ↱ +\curvearrowleft: ↶ +\curvearrowright: ↷ +\leftrightarrows: ⇆ +\rightleftarrows: ⇄ +\upharpoonleft: ↿ +\downharpoonleft: ⇃ +\downharpoonright: ⇂ +\upharpoonright: ↾ +\leftarrowtobar: ⇤ +\rightarrowtobar: ⇥ +\leftrightharpoons: ⇋ +\leftleftharpoons: ⥢ +\rightrightharpoons: ⥤ +\Mapsfrom: ⇐| +\Mapsto: '|⇒' +\mapsto: ↦ +\mapsfrom: ↤ +\dashleftarrow: ⇠ +\upuparrows: ⇈ +\downdownarrows: ⇊ +\leftleftarrows: ⇇ +\rightrightarrows: ⇉ +\leftarrowtail: ↢ +\rightarrowtail: ↣ +\twoheadleftarrow: ↞ +\twoheadrightarrow: ↠ +\looparrowleft: ↫ +\looparrowright: ↬ +\nearrow: ↗ + +# advanced operators/relations +\triangleq: ≜ +\Corresponds: ≙ +\veebar: ⊻ +\barwedge: ⊼ +\doublebarwedge: ⌆ +\therefore: ∴ +\because: ∵ +\boxdot: ⊡ +\circledast: ⊛ +\circledcirc: ⊚ +\circleddash: ⊝ +\divideontimes: ⋇ +\ntriangleleft: ⋪ +\ntriangleright: ⋫ +\trianglelefteq: ⊴ +\trianglerighteq: ⊵ +\ntrianglelefteq: ⋬ +\ntrianglerighteq: ⋭ +\preccurlyeq: ≼ +\succcurlyeq: ≽ +\nsucccurlyeq: ⋡ +\precsim: ≾ +\succsim: ≿ +\succnsim: ⋩ +\lesseqgtr: ⋛ +\gtreqless: ⋚ +\gtreqqless: ⪌ +\lesseqqgtr: ⪋ +\gtrdot: ⋗ +\lessdot: ⋖ +\Subset: ⋐ +\Supset: ⋑ +\nsupset: ⊅ +\nsubseteq: ⊈ +\subseteqq: ⫅ +\supseteqq: ⫆ +\subsetneqq: ⫋ +\supsetneqq: ⫌ +\nsupseteq: ⊉ +\nsqsubset: ⊏̸ +\notni: ∌ +\pitchfork: ⋔ +\between: ≬ +\notslash: ⌿ +\notbackslash: ⍀ +\ltimes: ⋉ +\rtimes: ⋊ +\Cup: ⋓ +\Cap: ⋒ +\udot: ⊍ + +# other +\langle: ⟨ +\rangle: ⟩ +\emptyset: ∅ +\nabla: ∇ +\lozenge: ◊ +\male: ♂ +\mho: ℧ +\leftthreetimes: ⋋ +\rightthreetimes: ⋌ +\oiint: ∯ +\oiiint: ∰ +\ointclockwise: ∲ +\landupint: ∱ +\ointctrclockwise: ∳ +\iint: ∬ +\iiint: ∭ +\idotsint: ∫⋯∫ +\EUR: € +\P: ¶ +\maltese: ✠ +\mercury: ☿ +\libra: ♎ +\neptune: ♆ +\Yup: ⅄ + + +# meta data +':special_size:': + ⟶: 2 + ⟵: 2 + ⟷: 2 + ⟹: 2 + ⟸: 2 + ⟺: 2 From 96869589486086c57722ba70e545a352d1591b2b Mon Sep 17 00:00:00 2001 From: Richard Stein Date: Wed, 17 May 2017 12:43:49 +0200 Subject: [PATCH 2/5] Minor tag changes --- latextools_utils/latexhtml.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/latextools_utils/latexhtml.py b/latextools_utils/latexhtml.py index 2c0ae869c..f586541f7 100644 --- a/latextools_utils/latexhtml.py +++ b/latextools_utils/latexhtml.py @@ -54,11 +54,12 @@ def get_tag_map(): "\\textbf": "b", "\\textit": "i", "\\textsl": "i", + "\\texttt": "tt", "\\emph": "em", "\\underline": "u", - "\\texttt": "code", "\\mathbf": "b", "\\mathit": "i", + "\\mathtt": "tt", } return tag_map From 02c9b84e3bb3287f4d02b96616001626928b5a5f Mon Sep 17 00:00:00 2001 From: Richard Stein Date: Thu, 8 Jun 2017 12:13:08 +0200 Subject: [PATCH 3/5] Add a postfix insert command to the math popup This will just insert the sequence and omit the popup. --- math_popup.py | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/math_popup.py b/math_popup.py index 7cce7e454..68e7ad19b 100644 --- a/math_popup.py +++ b/math_popup.py @@ -26,7 +26,6 @@ def _get_command_entries(): except (OSError, ValueError): return [] return _get_command_entries.result - pass def _get_css(): @@ -349,3 +348,39 @@ def on_query_context(self, view, key, operator, operand, match_all): return True _cleanup(view) return False + + +class LatextoolsMathPopupPostfixInsertCommand(sublime_plugin.TextCommand): + """ + Insert the entry from the math popup based on the text before the + caret. + """ + + def run(self, edit): + view = self.view + command_entries = {k: v for k, v, *_ in _get_command_entries()} + for sel in view.sel(): + # get the word and the key from the view + word = sublime.Region(view.word(sel.b).a, sel.b) + insert_key = view.substr(word) + + # strip the key + # these characters are stripped and hence can't be used in + # the sequence + strip_chars = " \n{}_^" + insert_key = insert_key.lstrip(strip_chars) + len_strip_delta = len(word) - len(insert_key) + if len_strip_delta > 0: + word = sublime.Region(word.a + len_strip_delta, word.b) + + # retrieve and insert the value for the key + try: + insert_value = command_entries[insert_key] + except KeyError: + message = "No math insertion key for '{}'".format(insert_key) + print(message) + sublime.status_message(message) + return + + # insert the value and replace the prefix sequence + view.replace(edit, word, insert_value) From 5478525de8477802db97a65499d267f32700ba92 Mon Sep 17 00:00:00 2001 From: Richard Stein Date: Thu, 8 Jun 2017 13:08:36 +0200 Subject: [PATCH 4/5] Made the math popup css more variable. --- math_popup.py | 6 ++++-- resource/insert_math_popup.css | 33 ++++++++++++++++----------------- 2 files changed, 20 insertions(+), 19 deletions(-) diff --git a/math_popup.py b/math_popup.py index 68e7ad19b..04221edc1 100644 --- a/math_popup.py +++ b/math_popup.py @@ -127,11 +127,13 @@ def _imake_html(entries, do_search, prefix, invalid_char, index): yield "({0})".format(_escape_html(invalid_char)) yield "" yield "
" - yield '' + yield '
' + yield '' yield "(Toggle Mode)" yield "" yield " " - yield '(Options)' + yield '(Options)' + yield '
' def high_common_prefix(s): pre = "\\" + prefix if do_search else prefix diff --git a/resource/insert_math_popup.css b/resource/insert_math_popup.css index 5865d8555..3a10ee730 100644 --- a/resource/insert_math_popup.css +++ b/resource/insert_math_popup.css @@ -1,38 +1,37 @@ -html { - background-color: #333333; - color: #CCCCCC; -} a { - color: #cc0000; + color: var(--foreground); +} + +a.button { + color: var(--redish); } + a.entry { - color: #CCCCCC; text-decoration: none; } .prefix_line { - background-color: #333399; + background-color: var(--bluish); + color: yellow; } .prefix { font-weight: bold; } + .invalid_char { - color: red; + color: var(--redish); } + .selected { - background-color: #993300; - color: #FFFF00; + background-color: var(--pinkish); +} +.selected a.entry { + color: var(--yellowish); font-weight: bold; } .key { width: 150px; } + .common_prefix { font-style: italic; - # font-weight: bold; -} -.pkg_missing { - color: red; } -.pkg_available { - color: green; -} \ No newline at end of file From 79c798269909c67537c01e436cc826c15fc7831d Mon Sep 17 00:00:00 2001 From: Richard Stein Date: Thu, 8 Jun 2017 13:34:35 +0200 Subject: [PATCH 5/5] Added option do edit math popup css --- Main.sublime-menu | 10 +++++++++- math_popup.py | 37 ++++++++++++++++++++++++++++++++++--- 2 files changed, 43 insertions(+), 4 deletions(-) diff --git a/Main.sublime-menu b/Main.sublime-menu index f069cd744..bfbe5f49c 100644 --- a/Main.sublime-menu +++ b/Main.sublime-menu @@ -94,7 +94,15 @@ }, "caption": "Key Bindings – User" }, - { "caption": "-" } + { "caption": "-" }, + { + "command": "edit_settings", "args": + { + "base_file": "${packages}/LaTeXTools/resource/insert_math_popup.css", + "default": "\n" + }, + "caption": "Edit Math Popup CSS" + }, ] } ] diff --git a/math_popup.py b/math_popup.py index 04221edc1..2a6a435b0 100644 --- a/math_popup.py +++ b/math_popup.py @@ -1,4 +1,5 @@ import html +import os import sublime import sublime_plugin @@ -32,13 +33,43 @@ def _get_css(): if hasattr(_get_css, "result"): return _get_css.result try: - _get_css.result = sublime.load_resource( - "Packages/LaTeXTools/resource/insert_math_popup.css") + default_css = _get_css.default_css + except AttributeError: + try: + default_css = _get_css.default_css = sublime.load_resource( + "Packages/LaTeXTools/resource/insert_math_popup.css") + except OSError: + return ".selected { background-color: #FFFF00; }" + try: + user_css = sublime.load_resource("Packages/User/insert_math_popup.css") + result = default_css + "\n" + user_css except OSError: - return ".selected { background-color: #FFFF00; }" + result = default_css + + _get_css.result = result + return _get_css.result +class _CssInvalidateListener(sublime_plugin.EventListener): + """This listener invalidates the saves user css if you save the file""" + + def on_post_save_async(self, view): + if not view.score_selector(0, "source.css"): + return + try: + self.user_popup_path + except AttributeError: + self.user_popup_path = os.path.normpath(os.path.join( + sublime.packages_path(), "User/insert_math_popup.css")) + if view.file_name() == self.user_popup_path: + print("Invalidate LaTeXTools math popup user css.") + try: + del _get_css.result + except Exception: + pass + + _html_options = """
(back)