added key "?" for help
This commit is contained in:
@@ -113,6 +113,9 @@ class View:
|
||||
self.name_ = ""
|
||||
self.sel_ = []
|
||||
self.status = {}
|
||||
self.commands = []
|
||||
self.popup = None
|
||||
self.popup_navigate = None
|
||||
|
||||
def id(self):
|
||||
return 1
|
||||
@@ -129,6 +132,8 @@ class View:
|
||||
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
|
||||
@@ -185,6 +190,18 @@ class View:
|
||||
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 style_for_scope(self, scope):
|
||||
return {"foreground": "#ffffff", "background": "#333333"}
|
||||
|
||||
|
||||
@@ -124,3 +124,14 @@ def test_run_async_reports_git_error(monkeypatch):
|
||||
|
||||
commands.run_async(boom, lambda r: messages.append("done"))
|
||||
assert messages == ["vDiff: fatal: bad revision 'nope'"]
|
||||
|
||||
|
||||
def test_run_async_reports_generic_error(monkeypatch):
|
||||
messages = []
|
||||
monkeypatch.setattr(commands.sublime, "status_message", lambda m: messages.append(m))
|
||||
|
||||
def boom():
|
||||
raise ValueError("boom")
|
||||
|
||||
commands.run_async(boom, lambda r: messages.append("done"))
|
||||
assert messages == ["vDiff: boom"]
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
|
||||
import sublime # the stub, via conftest
|
||||
|
||||
from vdiff import commands, git, listeners, parse, render, state # noqa: F401
|
||||
@@ -110,3 +114,93 @@ def test_binary_and_caret_index():
|
||||
view.sel().clear()
|
||||
view.sel().add(sublime.Region(0))
|
||||
assert render.caret_index(view) == -1
|
||||
|
||||
|
||||
# --- key help popup ---------------------------------------------------------
|
||||
|
||||
|
||||
def help_popup(st):
|
||||
"""Open the popup through the action registry and return (view, html)"""
|
||||
view = sublime.View()
|
||||
render.init_view(view)
|
||||
commands.ACTIONS["help"](view, None, st)
|
||||
return view, view.popup
|
||||
|
||||
|
||||
def test_help_popup_lists_every_key():
|
||||
st = make_state()
|
||||
view, html = help_popup(st)
|
||||
|
||||
assert view.is_popup_visible()
|
||||
for group in ("Navigate", "Panels", "This file", "View"):
|
||||
assert ">{}<".format(group) in html
|
||||
for keys in ("] [", "n p", "o ⏎", "f", "c", "s", "u", "d", "r", "?", "esc"):
|
||||
assert ">{}<".format(keys) in html
|
||||
assert "next / previous change" in html and "open the real file at this line" in html
|
||||
# working-tree mode: nothing dimmed, every single-action row is a link
|
||||
assert 'class="dim"' not in html and "uncommitted only" not in html
|
||||
assert 'href="stage"' in html and 'href="files"' in html and 'href="close"' in html
|
||||
# rows covering two actions stay plain text
|
||||
assert 'href="next_change"' not in html
|
||||
# the status bar advertises it too
|
||||
assert "? keys" in view.status[render.STATUS_KEY]
|
||||
|
||||
|
||||
def test_help_popup_dims_file_actions_in_commit_mode():
|
||||
st = make_state()
|
||||
st.enter("abc", git.CommitInfo("abc", "abc1234", "Ann", "2026-09-23 10:00", "Fix things", ""))
|
||||
_, html = help_popup(st)
|
||||
|
||||
assert "uncommitted only" in html
|
||||
assert html.count('class="dim"') == 3
|
||||
assert 'href="stage"' not in html and 'href="unstage"' not in html and 'href="discard"' not in html
|
||||
# everything that still applies stays clickable
|
||||
assert 'href="files"' in html and 'href="open"' in html and 'href="refresh"' in html
|
||||
|
||||
|
||||
def test_help_popup_toggles_and_navigates():
|
||||
st = make_state()
|
||||
view, _ = help_popup(st)
|
||||
|
||||
commands.ACTIONS["help"](view, None, st)
|
||||
assert not view.is_popup_visible()
|
||||
|
||||
commands.ACTIONS["help"](view, None, st)
|
||||
assert view.is_popup_visible()
|
||||
view.popup_navigate("files")
|
||||
assert not view.is_popup_visible()
|
||||
assert view.commands[-1] == ("vdiff_action", {"action": "files"})
|
||||
|
||||
|
||||
# keymap spellings that the popup shows as a symbol
|
||||
DISPLAY_KEYS = {"enter": "⏎", "escape": "esc"}
|
||||
|
||||
|
||||
def test_keymap_matches_help_table():
|
||||
"""The keymap, ACTIONS and the popup agree on both the actions and the keys that reach them"""
|
||||
path = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "Default.sublime-keymap")
|
||||
with open(path, encoding="utf-8") as f:
|
||||
bindings = json.load(f)
|
||||
|
||||
assert all(b["command"] == "vdiff_action" for b in bindings)
|
||||
bound = {b["args"]["action"] for b in bindings}
|
||||
listed = {action for k in render.KEYS for action in k.actions}
|
||||
assert bound == set(commands.ACTIONS) == listed
|
||||
|
||||
# rebinding a key without updating the table would leave the popup and status bar lying
|
||||
bound_keys = {DISPLAY_KEYS.get(key, key) for b in bindings for key in b["keys"]}
|
||||
assert bound_keys == {token for k in render.KEYS for token in k.keys.split()}
|
||||
|
||||
|
||||
def test_minihtml_stylesheets_reject_unsupported_selectors():
|
||||
"""minihtml drops the rest of a stylesheet at a selector it cannot parse; these are the forms
|
||||
that silently broke HELP_CSS, so no stylesheet constant may use them"""
|
||||
# descendant/child/sibling combinators, pseudo-classes, attribute selectors, compound classes
|
||||
unsupported = re.compile(r"[\s:>~+\[]|[.#][^.#]*[.#]")
|
||||
sheets = [(name, css) for name, css in vars(render).items() if name.endswith("_CSS") and isinstance(css, str)]
|
||||
assert len(sheets) >= 2, sheets
|
||||
for name, css in sheets:
|
||||
for rule in css.split("}"):
|
||||
head = rule.split("{")[0].strip()
|
||||
for selector in filter(None, (s.strip() for s in head.split(","))):
|
||||
assert not unsupported.search(selector), "{}: {}".format(name, selector)
|
||||
|
||||
Reference in New Issue
Block a user