314 lines
9.1 KiB
Python
314 lines
9.1 KiB
Python
""" Minimal sublime API stub so the plugin modules import and header/regions logic runs offline """
|
|
import re
|
|
|
|
OP_EQUAL = 0
|
|
OP_NOT_EQUAL = 1
|
|
DRAW_NO_OUTLINE = 1
|
|
DRAW_NO_FILL = 2
|
|
LAYOUT_BLOCK = 2
|
|
ENCODED_POSITION = 1
|
|
KIND_ID_COLOR_REDISH = 1
|
|
KIND_ID_COLOR_ORANGISH = 2
|
|
KIND_ID_COLOR_YELLOWISH = 3
|
|
KIND_ID_COLOR_GREENISH = 4
|
|
KIND_ID_COLOR_CYANISH = 5
|
|
KIND_ID_COLOR_BLUISH = 6
|
|
KIND_ID_COLOR_PURPLISH = 7
|
|
KIND_ID_COLOR_PINKISH = 8
|
|
KIND_AMBIGUOUS = (0, "", "")
|
|
|
|
|
|
class Region:
|
|
def __init__(self, a, b=None):
|
|
self.a = a
|
|
self.b = a if b is None else b
|
|
|
|
def begin(self):
|
|
return min(self.a, self.b)
|
|
|
|
def end(self):
|
|
return max(self.a, self.b)
|
|
|
|
def __eq__(self, other):
|
|
return (self.a, self.b) == (other.a, other.b)
|
|
|
|
def __repr__(self):
|
|
return f"Region({self.a}, {self.b})"
|
|
|
|
|
|
class Edit:
|
|
"""Opaque edit token; only ever passed through, so the annotations have something to name"""
|
|
|
|
|
|
class Settings(dict):
|
|
def get(self, key, default=None):
|
|
return dict.get(self, key, default)
|
|
|
|
def set(self, key, value):
|
|
self[key] = value
|
|
|
|
def add_on_change(self, tag, callback):
|
|
pass
|
|
|
|
|
|
_settings = {}
|
|
|
|
|
|
def load_settings(name):
|
|
return _settings.setdefault(name, Settings())
|
|
|
|
|
|
def set_timeout(fn, delay=0):
|
|
fn()
|
|
|
|
|
|
def set_timeout_async(fn, delay=0):
|
|
fn()
|
|
|
|
|
|
def status_message(text):
|
|
pass
|
|
|
|
|
|
def packages_path():
|
|
return "/tmp/Packages"
|
|
|
|
|
|
def windows():
|
|
return []
|
|
|
|
|
|
class QuickPanelItem:
|
|
def __init__(self, trigger, details="", annotation="", kind=KIND_AMBIGUOUS):
|
|
self.trigger, self.details, self.annotation, self.kind = trigger, details, annotation, kind
|
|
|
|
|
|
class Phantom:
|
|
def __init__(self, region, content, layout, on_navigate=None):
|
|
self.region, self.content, self.layout, self.on_navigate = region, content, layout, on_navigate
|
|
|
|
|
|
class Window:
|
|
"""Captures the quick panel and the input panel so tests can drive their callbacks"""
|
|
|
|
def __init__(self):
|
|
self.panel = None
|
|
self.input = None
|
|
self.input_view = None
|
|
self.commands = []
|
|
|
|
def focus_view(self, view):
|
|
pass
|
|
|
|
def run_command(self, name, args=None):
|
|
self.commands.append((name, args))
|
|
|
|
def show_quick_panel(self, items, on_select, flags=0, selected=-1, on_highlight=None, placeholder=None):
|
|
self.panel = (items, on_select, selected, on_highlight, placeholder)
|
|
|
|
def show_input_panel(self, caption, initial_text, on_done, on_change, on_cancel):
|
|
self.input = (caption, initial_text, on_done, on_change, on_cancel)
|
|
self.input_view = View(initial_text)
|
|
# the real panel hands its prefill back selected, which is why callers clear the selection
|
|
self.input_view.sel().add(Region(0, len(initial_text)))
|
|
return self.input_view
|
|
|
|
|
|
# a bundled scheme's styles: diff.* and region.* defined, the plugin's vdiff.* ones not. Nothing
|
|
# simulates sublime merging an override, so a test wanting those to resolve adds them itself.
|
|
DEFAULT_FG = "#ffffff"
|
|
DEFAULT_STYLES = {
|
|
"diff.deleted": {"foreground": DEFAULT_FG, "background": "#4a2b2b"},
|
|
"diff.deleted.char": {"foreground": DEFAULT_FG, "background": "#6a3535"},
|
|
"diff.inserted": {"foreground": DEFAULT_FG, "background": "#2b4a3a"},
|
|
"diff.inserted.char": {"foreground": DEFAULT_FG, "background": "#356a4a"},
|
|
"region.yellowish": {"foreground": "#e6db74", "background": "#4a4a2b"},
|
|
"markup.deleted": {"foreground": "#d64646"},
|
|
"markup.inserted": {"foreground": "#46d646"},
|
|
# a few of palette.PALETTE_PROBES, so the palette probe finds a working set of colours
|
|
"source": {"foreground": DEFAULT_FG},
|
|
"comment": {"foreground": "#7c8595"},
|
|
"string": {"foreground": "#99c794"},
|
|
"keyword": {"foreground": "#c594c5"},
|
|
"constant.numeric": {"foreground": "#f99157"},
|
|
"entity.name.function": {"foreground": "#6699cc"},
|
|
"punctuation.definition": {"foreground": "#5fb3b3"},
|
|
}
|
|
|
|
|
|
class View:
|
|
"""Text buffer with just the calls render.py makes"""
|
|
|
|
def __init__(self, text=""):
|
|
self.text = text
|
|
self.phantoms = {}
|
|
self.settings_ = Settings()
|
|
self.regions = {}
|
|
self.syntax = None
|
|
self.read_only = True
|
|
self.name_ = ""
|
|
self.sel_ = []
|
|
self.status = {}
|
|
self.commands = []
|
|
self.popup = None
|
|
self.popup_navigate = None
|
|
# what style_for_scope answers; a test reshapes it to model another colour scheme
|
|
self.styles = {scope: dict(style) for scope, style in DEFAULT_STYLES.items()}
|
|
# whether sublime has merged the override vdiff writes; it only does so on a restart
|
|
self.override_loaded = False
|
|
|
|
def id(self):
|
|
return 1
|
|
|
|
def settings(self):
|
|
return self.settings_
|
|
|
|
def size(self):
|
|
return len(self.text)
|
|
|
|
def replace(self, edit, region, text):
|
|
self.text = self.text[: region.begin()] + text + self.text[region.end() :]
|
|
|
|
def run_command(self, name, args=None):
|
|
if name == "vdiff_replace":
|
|
self.replace(None, Region(0, self.size()), args["text"])
|
|
else:
|
|
self.commands.append((name, args))
|
|
|
|
def set_read_only(self, flag):
|
|
self.read_only = flag
|
|
|
|
def is_read_only(self):
|
|
return self.read_only
|
|
|
|
def assign_syntax(self, path):
|
|
self.syntax = path
|
|
|
|
def add_regions(self, key, regions, scope="", icon="", flags=0):
|
|
self.regions[key] = (list(regions), scope, icon, flags)
|
|
|
|
def erase_regions(self, key):
|
|
self.regions.pop(key, None)
|
|
|
|
def text_point(self, row, col):
|
|
lines = self.text.split("\n")
|
|
return sum(len(l) + 1 for l in lines[:row]) + col
|
|
|
|
def rowcol(self, point):
|
|
before = self.text[:point]
|
|
row = before.count("\n")
|
|
return row, len(before) - (before.rfind("\n") + 1)
|
|
|
|
def full_line(self, point):
|
|
start = self.text.rfind("\n", 0, point) + 1
|
|
end = self.text.find("\n", point)
|
|
end = len(self.text) if end == -1 else end + 1
|
|
return Region(start, end)
|
|
|
|
def erase_phantoms(self, key):
|
|
self.phantoms.pop(key, None)
|
|
|
|
def add_phantom(self, key, region, content, layout, on_navigate=None):
|
|
self.phantoms.setdefault(key, []).append(Phantom(region, content, layout, on_navigate))
|
|
return len(self.phantoms[key])
|
|
|
|
def set_name(self, name):
|
|
self.name_ = name
|
|
|
|
def name(self):
|
|
return self.name_
|
|
|
|
def set_scratch(self, flag):
|
|
pass
|
|
|
|
def set_status(self, key, value):
|
|
self.status[key] = value
|
|
|
|
def window(self):
|
|
# detached: the all-files view then skips tokenising in a hidden panel
|
|
return None
|
|
|
|
def viewport_position(self):
|
|
return (0, 0)
|
|
|
|
def set_viewport_position(self, xy, animate=True):
|
|
pass
|
|
|
|
def show(self, point):
|
|
pass
|
|
|
|
def show_at_center(self, point):
|
|
pass
|
|
|
|
def show_popup(self, content, flags=0, location=-1, max_width=320, max_height=240, on_navigate=None, on_hide=None):
|
|
self.popup, self.popup_navigate = content, on_navigate
|
|
|
|
def hide_popup(self):
|
|
self.popup = None
|
|
|
|
def is_popup_visible(self):
|
|
return self.popup is not None
|
|
|
|
def em_width(self):
|
|
return 8.0
|
|
|
|
def extract_tokens_with_scopes(self, region):
|
|
"""One token per run of word characters and one per run of anything else, scoped by its shape
|
|
|
|
Enough for the colouring tests: real sublime returns the syntax's tokens, but the painter only
|
|
needs adjacent tokens to differ and each to map to a coloured scope.
|
|
"""
|
|
tokens = []
|
|
text = self.text[region.a:region.b]
|
|
for match in re.finditer(r"\w+|\s+|[^\w\s]+", text):
|
|
scope = "keyword" if match.group().isidentifier() else "punctuation.definition"
|
|
tokens.append((Region(region.a + match.start(), region.a + match.end()), scope))
|
|
return tokens
|
|
|
|
def style_for_scope(self, scope):
|
|
# a fresh dict per call, and no background for an unknown scope, like the real API
|
|
if self.override_loaded and scope.startswith("vdiff.") and scope not in self.styles:
|
|
# sublime has merged the override, so every rule vdiff wrote in it resolves
|
|
return {"foreground": DEFAULT_FG, "background": "#333333"}
|
|
return dict(self.styles.get(scope, {"foreground": DEFAULT_FG}))
|
|
|
|
def is_valid(self):
|
|
return True
|
|
|
|
class _Sel(list):
|
|
def clear(self):
|
|
del self[:]
|
|
|
|
def add(self, region):
|
|
self.append(region)
|
|
|
|
def sel(self):
|
|
if not isinstance(self.sel_, View._Sel):
|
|
self.sel_ = View._Sel(self.sel_)
|
|
return self.sel_
|
|
|
|
|
|
def find_syntax_for_file(path, first_line=""):
|
|
class Syntax:
|
|
pass
|
|
|
|
syntax = Syntax()
|
|
syntax.path = "Packages/Python/Python.sublime-syntax" if path.endswith(".py") else "Packages/Text/Plain text.tmLanguage"
|
|
return syntax
|
|
|
|
|
|
def ui_info():
|
|
return {
|
|
"system": {"style": "dark"},
|
|
"color_scheme": {
|
|
"value": "Mariana.sublime-color-scheme",
|
|
"resolved_value": "Packages/Color Scheme - Default/Mariana.sublime-color-scheme",
|
|
},
|
|
}
|
|
|
|
|
|
def decode_value(text):
|
|
import json
|
|
|
|
return json.loads(text)
|