Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
keymap.py263 linesDownload Raw Back to console
1import logging2import os3from collections import defaultdict4from collections.abc import Sequence5from functools import cache6 7import ruamel.yaml.error8 9import mitmproxy.types10from mitmproxy import command11from mitmproxy import ctx12from mitmproxy import exceptions13from mitmproxy.tools.console import commandexecutor14from mitmproxy.tools.console import signals15 16 17class KeyBindingError(Exception):18    pass19 20 21Contexts = {22    "chooser",23    "commands",24    "commonkey",25    "dataviewer",26    "eventlog",27    "flowlist",28    "flowview",29    "global",30    "grideditor",31    "help",32    "keybindings",33    "options",34}35 36 37navkeys = [38    "m_start",39    "m_end",40    "m_next",41    "m_select",42    "up",43    "down",44    "page_up",45    "page_down",46    "left",47    "right",48]49 50 51class Binding:52    def __init__(self, key, command, contexts, help):53        self.key, self.command, self.contexts = key, command, sorted(contexts)54        self.help = help55 56    def keyspec(self):57        """58        Translate the key spec from a convenient user specification to one59        Urwid understands.60        """61        return self.key.replace("space", " ")62 63    def key_short(self) -> str:64        return (65            self.key.replace("enter", "⏎").replace("right", "→").replace("space", "␣")66        )67 68    def sortkey(self):69        return self.key + ",".join(self.contexts)70 71 72class Keymap:73    def __init__(self, master):74        self.executor = commandexecutor.CommandExecutor(master)75        self.keys: dict[str, dict[str, Binding]] = defaultdict(dict)76        self.bindings = []77 78    def _check_contexts(self, contexts):79        if not contexts:80            raise ValueError("Must specify at least one context.")81        for c in contexts:82            if c not in Contexts:83                raise ValueError("Unsupported context: %s" % c)84 85    def _on_change(self) -> None:86        signals.keybindings_change.send()87        self.binding_for_help.cache_clear()88 89    def add(self, key: str, command: str, contexts: Sequence[str], help="") -> None:90        """91        Add a key to the key map.92        """93        self._check_contexts(contexts)94 95        for b in self.bindings:96            if b.key == key and b.command.strip() == command.strip():97                b.contexts = sorted(list(set(b.contexts + contexts)))98                if help:99                    b.help = help100                self.bind(b)101                break102        else:103            self.remove(key, contexts)104            b = Binding(key=key, command=command, contexts=contexts, help=help)105            self.bindings.append(b)106            self.bind(b)107        self._on_change()108 109    def remove(self, key: str, contexts: Sequence[str]) -> None:110        """111        Remove a key from the key map.112        """113        self._check_contexts(contexts)114        for c in contexts:115            b = self.get(c, key)116            if b:117                self.unbind(b)118                b.contexts = [x for x in b.contexts if x != c]119                if b.contexts:120                    self.bindings.append(b)121                    self.bind(b)122        self._on_change()123 124    def bind(self, binding: Binding) -> None:125        for c in binding.contexts:126            self.keys[c][binding.keyspec()] = binding127 128    def unbind(self, binding: Binding) -> None:129        """130        Unbind also removes the binding from the list.131        """132        for c in binding.contexts:133            del self.keys[c][binding.keyspec()]134            self.bindings = [b for b in self.bindings if b != binding]135        self._on_change()136 137    def get(self, context: str, key: str) -> Binding | None:138        if context in self.keys:139            return self.keys[context].get(key, None)140        return None141 142    @cache143    def binding_for_help(self, help: str) -> Binding | None:144        for b in self.bindings:145            if b.help == help:146                return b147        return None148 149    def list(self, context: str) -> Sequence[Binding]:150        b = [x for x in self.bindings if context in x.contexts or context == "all"]151        single = [x for x in b if len(x.key.split()) == 1]152        multi = [x for x in b if len(x.key.split()) != 1]153        single.sort(key=lambda x: x.sortkey())154        multi.sort(key=lambda x: x.sortkey())155        return single + multi156 157    def handle(self, context: str, key: str) -> str | None:158        """159        Returns the key if it has not been handled, or None.160        """161        b = self.get(context, key) or self.get("global", key)162        if b:163            self.executor(b.command)164            return None165        return key166 167    def handle_only(self, context: str, key: str) -> str | None:168        """169        Like handle, but ignores global bindings. Returns the key if it has170        not been handled, or None.171        """172        b = self.get(context, key)173        if b:174            self.executor(b.command)175            return None176        return key177 178 179keyAttrs = {180    "key": lambda x: isinstance(x, str),181    "cmd": lambda x: isinstance(x, str),182    "ctx": lambda x: isinstance(x, list) and [isinstance(v, str) for v in x],183    "help": lambda x: isinstance(x, str),184}185requiredKeyAttrs = {"key", "cmd"}186 187 188class KeymapConfig:189    defaultFile = "keys.yaml"190 191    def __init__(self, master):192        self.master = master193 194    @command.command("console.keymap.load")195    def keymap_load_path(self, path: mitmproxy.types.Path) -> None:196        try:197            self.load_path(self.master.keymap, path)  # type: ignore198        except (OSError, KeyBindingError) as e:199            raise exceptions.CommandError("Could not load key bindings - %s" % e) from e200 201    def running(self):202        p = os.path.join(os.path.expanduser(ctx.options.confdir), self.defaultFile)203        if os.path.exists(p):204            try:205                self.load_path(self.master.keymap, p)206            except KeyBindingError as e:207                logging.error(e)208 209    def load_path(self, km, p):210        if os.path.exists(p) and os.path.isfile(p):211            with open(p, encoding="utf8") as f:212                try:213                    txt = f.read()214                except UnicodeDecodeError as e:215                    raise KeyBindingError(f"Encoding error - expected UTF8: {p}: {e}")216            try:217                vals = self.parse(txt)218            except KeyBindingError as e:219                raise KeyBindingError(f"Error reading {p}: {e}") from e220            for v in vals:221                user_ctxs = v.get("ctx", ["global"])222                try:223                    km._check_contexts(user_ctxs)224                    km.remove(v["key"], user_ctxs)225                    km.add(226                        key=v["key"],227                        command=v["cmd"],228                        contexts=user_ctxs,229                        help=v.get("help", None),230                    )231                except ValueError as e:232                    raise KeyBindingError(f"Error reading {p}: {e}") from e233 234    def parse(self, text):235        try:236            data = ruamel.yaml.YAML(typ="safe", pure=True).load(text)237        except ruamel.yaml.error.MarkedYAMLError as v:238            if hasattr(v, "problem_mark"):239                snip = v.problem_mark.get_snippet()240                raise KeyBindingError(241                    "Key binding config error at line %s:\n%s\n%s"242                    % (v.problem_mark.line + 1, snip, v.problem)243                )244            else:245                raise KeyBindingError("Could not parse key bindings.")246        if not data:247            return []248        if not isinstance(data, list):249            raise KeyBindingError("Invalid keybinding config - expected a list of keys")250 251        for k in data:252            unknown = k.keys() - keyAttrs.keys()253            if unknown:254                raise KeyBindingError("Unknown key attributes: %s" % unknown)255            missing = requiredKeyAttrs - k.keys()256            if missing:257                raise KeyBindingError("Missing required key attributes: %s" % unknown)258            for attr in k.keys():259                if not keyAttrs[attr](k[attr]):260                    raise KeyBindingError("Invalid type for %s" % attr)261 262        return data263 
codekingpro/portable-devtools · Team Ai