extension.py
Pinchord · pinchord.git · Tickets · Changelog
Branches > serial-mods > latest > files / plover_pinchord_31 / extension.py
## Makes the affix keys work on every entry, even user-defined ones
import re
import sys
from plover import system
from plover.steno import Stroke
PREFIXES = {(): "{}",
("#-",): "{}{-|}",
("+-",): "{^}",
("#-", "+-"): "{^}{-|}"}
SUFFIXES = {(): "",
("-e",): "{^~|^}e",
("-s",): "{^~|^}s",
("-e", "-s"): "{^~|^}es"}
# Macros don't work with translations like {} (only =undo works, not {}=undo)
MACRO = r"=\w+(:.*)?|\{\*[!?+]?\}"
# Wraps the given translation in its affix translations
# e.g. with_affixes(Stroke("#"), "test", Stroke("-s")) -> "{}{-|}test{^~|^}s"
def with_affixes(prefixes, translation, suffixes):
if translation is None:
return None
elif re.fullmatch(MACRO, translation):
return None if prefixes or suffixes else translation
else:
return PREFIXES[prefixes.keys()] + translation + SUFFIXES[suffixes.keys()]
# Looks up the stroke in the dictionary stack after splitting the affix keys,
# returns the translation joined with the affix key translations
def affixed_lookup(lookup, stroke):
prefixes = stroke & Stroke("#+")
suffixes = stroke & Stroke("-es")
stroke_without_affixes = stroke - prefixes - suffixes
stroke_entry = lookup((stroke.rtfcre,))
if stroke_entry is not None and stroke != stroke_without_affixes:
return stroke_entry
elif not stroke_without_affixes and suffixes:
return with_affixes(prefixes, "", suffixes)
else:
return with_affixes(prefixes, lookup((stroke_without_affixes.rtfcre,)), suffixes)
engine = None
class Extension:
def __init__(self, engine):
self.dictionaries = engine.dictionaries
self.original_lookup = self.dictionaries.lookup
self.engine = engine
def start(self):
self.dictionaries.lookup = self.lookup
global engine
engine = self.engine
def stop(self):
self.dictionaries.lookup = self.original_lookup
global engine
engine = None
def lookup(self, outline):
# inlines the system name, be aware!
if system.NAME == "Pinchord" and len(outline) == 1:
return affixed_lookup(self.original_lookup, Stroke.from_steno(outline[0]))
else:
return self.original_lookup(outline)
def thread_unsafe_press_or_release(mods, engine, pressed_boolean):
# Grabs the parse_key_combo function from the engine's keyboard emulation module
# Replaces it in this call with a version that strips the press or release part
# send_key_combo calls this function, and presses and releases everything it returns
# So the press or release part is stripped out
emulation = engine._keyboard_emulation
emulation_module = sys.modules[type(emulation).__module__]
original_parse_key_combo = emulation_module.parse_key_combo
emulation_module.parse_key_combo = lambda *args: [event for event in original_parse_key_combo(*args)
if event[1] == pressed_boolean]
try:
emulation.send_key_combo(" ".join(mods))
finally:
emulation_module.parse_key_combo = original_parse_key_combo
def do_press_or_release(mods, press):
global engine
if mods and engine:
engine._same_thread_hook(thread_unsafe_press_or_release, mods, engine, press)
def press(mods: tuple[str]):
"""Starts pressing down a tuple of mods (like "control", "shift") without releasing them."""
do_press_or_release(mods, True)
def release(mods):
"""Releases the tuple of mods (like "control", "shift") without pressing them first."""
do_press_or_release(mods, False)