Skip to content

keycut ¤

keycut package.

A command line tool that helps you remembering ALL the numerous keyboard shortcuts of ALL your favorite programs.

Classes:

  • FileWatcher –

    Watch a command file and display shortcuts for new commands.

  • WindowFocusWatcher –

    Watch the focused process for application shortcuts.

  • XdotoolWatcher –

    Watch the focused window name for application shortcuts.

Functions:

  • as_colored_text –

    Render shortcut entries with terminal colors for fields and matches.

  • as_text –

    Render shortcut entries as plain text.

  • as_yaml –

    Serialize shortcut entries as YAML.

  • check –

    Get the path to a shortcut file and check whether it exists.

  • from_yaml –

    Load an application's shortcuts from YAML.

  • get_parser –

    Return the CLI argument parser.

  • grep –

    Find a shortcut file whose suffix appears in a command line.

  • in_action –

    Return shortcuts whose action matches a case-insensitive regex.

  • in_category –

    Return shortcuts whose category matches a case-insensitive regex.

  • in_keys –

    Return shortcuts whose keys field matches a case-insensitive regex.

  • main –

    Run the main program.

  • print_err –

    Write a message to standard error.

  • reload –

    Print shortcut entries with terminal colors.

  • search –

    Search shortcut actions, categories, and keys with a case-insensitive regex.

  • word_in_action –

    Return shortcuts with a whole-word match in their action.

  • word_in_category –

    Return shortcuts with a whole-word match in their category.

  • word_in_keys –

    Return shortcuts with a whole-word match in their keys field.

  • word_search –

    Search all shortcut fields for whole-word matches.

Attributes:

  • ACTION_COLOR –

    Default color for action names; None leaves them uncolored.

  • CATEGORY_COLOR –

    Default color for category names.

  • DIRECTORY –

    Directory containing application shortcut files.

  • Document –

    Shortcut entries with fields such as category, action, and keys.

  • KEY_COLOR –

    Default color for shortcut keys.

  • MATCH_COLOR –

    Color used to highlight matching text.

  • UI_COMMANDS –

    Command names mapped to their shortcut search functions.

ACTION_COLOR module-attribute ¤

ACTION_COLOR = None

Default color for action names; None leaves them uncolored.

CATEGORY_COLOR module-attribute ¤

CATEGORY_COLOR = 'blue'

Default color for category names.

DIRECTORY module-attribute ¤

DIRECTORY = Path(
    os.getenv("KEYCUT_DATA", "keycut-data/default")
)

Directory containing application shortcut files.

KEY_COLOR module-attribute ¤

KEY_COLOR = 'white'

Default color for shortcut keys.

MATCH_COLOR module-attribute ¤

MATCH_COLOR = 'yellow'

Color used to highlight matching text.

UI_COMMANDS module-attribute ¤

UI_COMMANDS = {
    "s": search,
    "a": in_action,
    "c": in_category,
    "k": in_keys,
    "ws": word_search,
    "wa": word_in_action,
    "wc": word_in_category,
    "wk": word_in_keys,
}

Command names mapped to their shortcut search functions.

FileWatcher ¤

FileWatcher(file: str)

Bases: Thread

Watch a command file and display shortcuts for new commands.

Creating the watcher clears the command file.

Methods:

  • read –

    Return the first line of the command file without trailing whitespace.

  • run –

    Poll the command file and display shortcuts for new commands.

  • write –

    Write a command and newline to the command file.

Attributes:

  • current –

    Last command used to load a shortcut document.

  • file –

    Path to the command file.

  • sleep –

    Seconds between reads of the command file.

Source code in src/keycut/_internal/watch.py
114
115
116
117
118
119
120
121
122
def __init__(self, file: str) -> None:
    Thread.__init__(self)
    self.file = file
    """Path to the command file."""
    self.current = ""
    """Last command used to load a shortcut document."""
    self.write("")
    self.sleep = 0.2
    """Seconds between reads of the command file."""

current instance-attribute ¤

current = ''

Last command used to load a shortcut document.

file instance-attribute ¤

file = file

Path to the command file.

sleep instance-attribute ¤

sleep = 0.2

Seconds between reads of the command file.

read ¤

read() -> str

Return the first line of the command file without trailing whitespace.

Source code in src/keycut/_internal/watch.py
146
147
148
149
def read(self) -> str:
    """Return the first line of the command file without trailing whitespace."""
    with Path(self.file).open() as f:
        return f.readline().rstrip()

run ¤

run() -> None

Poll the command file and display shortcuts for new commands.

Source code in src/keycut/_internal/watch.py
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
def run(self) -> None:
    """Poll the command file and display shortcuts for new commands."""
    while True:
        line = self.read()
        if line:
            words = line.split(" ")
            command = words[0]
            if len(words) > 1:
                pattern = words[1]
                current = f"{command} {pattern}"
            else:
                current = command
                pattern = False
            if current != self.current:
                document = load.from_yaml(command)
                if document:
                    if pattern:
                        document = search(document, pattern)
                    self.current = current
                    ui.reload(document)
        time.sleep(self.sleep)

write ¤

write(command: str) -> None

Write a command and newline to the command file.

Parameters:

  • command ¤

    (str) –

    Command to store, replacing the file's contents.

Source code in src/keycut/_internal/watch.py
151
152
153
154
155
156
157
158
def write(self, command: str) -> None:
    """Write a command and newline to the command file.

    Args:
        command: Command to store, replacing the file's contents.
    """
    with Path(self.file).open("w") as f:
        f.write(f"{command}\n")

WindowFocusWatcher ¤

WindowFocusWatcher()

Bases: Thread

Watch the focused process for application shortcuts.

Methods:

  • run –

    Poll the focused process and display shortcuts when it changes.

Attributes:

  • cmdline –

    Last process command line with a loaded shortcut document.

  • name –

    Last process name with a loaded shortcut document.

  • sleep –

    Seconds between checks of the focused process.

Source code in src/keycut/_internal/watch.py
68
69
70
71
72
73
74
75
def __init__(self) -> None:
    Thread.__init__(self)
    self.name = ""
    """Last process name with a loaded shortcut document."""
    self.cmdline = ""
    """Last process command line with a loaded shortcut document."""
    self.sleep = 0.2
    """Seconds between checks of the focused process."""

cmdline instance-attribute ¤

cmdline = ''

Last process command line with a loaded shortcut document.

name instance-attribute ¤

name = ''

Last process name with a loaded shortcut document.

sleep instance-attribute ¤

sleep = 0.2

Seconds between checks of the focused process.

run ¤

run() -> None

Poll the focused process and display shortcuts when it changes.

Source code in src/keycut/_internal/watch.py
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
def run(self) -> None:
    """Poll the focused process and display shortcuts when it changes."""
    wid_command = "xprop -root | grep -F '_NET_ACTIVE_WINDOW(WINDOW)' | grep -o '0x.*'"
    pid_command = 'xprop -id %s | grep _NET_WM_PID | grep -o "[0-9]*"'
    name_command = "cat /proc/%s/comm"
    cmdline_command = "cat /proc/%s/cmdline"

    while True:
        wid = self._run_command(wid_command)
        pid = self._run_command(pid_command % wid)
        name = self._run_command(name_command % pid)
        cmdline = self._run_command(cmdline_command % pid)

        if name != self.name and cmdline != self.cmdline:
            document = load.from_yaml(name, cmdline)
            if document:
                self.name = name
                self.cmdline = cmdline
                ui.reload(document)
            else:
                _logger.error("Not found: {wid} {pid} {name} {cmdline}")
        time.sleep(self.sleep)

XdotoolWatcher ¤

XdotoolWatcher()

Bases: Thread

Watch the focused window name for application shortcuts.

Methods:

  • run –

    Poll the focused window and display shortcuts when its name changes.

Attributes:

  • name –

    Last window name with a loaded shortcut document.

  • sleep –

    Seconds between checks of the focused window.

Source code in src/keycut/_internal/watch.py
34
35
36
37
38
39
def __init__(self) -> None:
    Thread.__init__(self)
    self.name = ""
    """Last window name with a loaded shortcut document."""
    self.sleep = 0.2
    """Seconds between checks of the focused window."""

name instance-attribute ¤

name = ''

Last window name with a loaded shortcut document.

sleep instance-attribute ¤

sleep = 0.2

Seconds between checks of the focused window.

run ¤

run() -> None

Poll the focused window and display shortcuts when its name changes.

Source code in src/keycut/_internal/watch.py
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
def run(self) -> None:
    """Poll the focused window and display shortcuts when its name changes."""
    name_command = "xdotool getwindowfocus getwindowname"

    while True:
        name = self._run_command(name_command)

        if name != self.name:
            document = load.from_yaml(name, command_line=name)
            if document:
                self.name = name
                ui.reload(document)
            else:
                _logger.error(f"Not found: {name}")
        time.sleep(self.sleep)

as_colored_text ¤

as_colored_text(document: Document) -> str

Render shortcut entries with terminal colors for fields and matches.

Parameters:

  • document ¤

    (Document) –

    Shortcut entries to render. Position fields, when present, mark the text to highlight.

Returns:

  • str –

    Formatted text, or an empty string for an empty document.

Source code in src/keycut/_internal/render.py
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
def as_colored_text(document: Document) -> str:
    """Render shortcut entries with terminal colors for fields and matches.

    Args:
        document: Shortcut entries to render. Position fields, when present,
            mark the text to highlight.

    Returns:
        Formatted text, or an empty string for an empty document.
    """
    str_list = []
    for item in document:
        s = []
        category = item.get("category", None)
        if category:
            s.append("Category: ")
            category_pos = item.get("category_pos", None)
            if category_pos:
                s.extend(_color_match(category, category_pos, CATEGORY_COLOR))
                s.append("\n")
            else:
                s.append(f"{_color(category, CATEGORY_COLOR)}\n")
        action = item["action"].rstrip("\n")
        action_pos = item.get("action_pos", None)
        s.append("  Action: ")
        if action_pos:
            s.extend(_color_match(action, action_pos, ACTION_COLOR))
            s.append("\n")
        else:
            s.append(f"{_color(action, ACTION_COLOR)}\n")
        s.append("    Keys: ")
        s_key = []
        keys = item["keys"]
        for key in keys:
            key_pos = item.get("keys_pos", {}).get(key)
            if key_pos:
                s_key.append("".join(_color_match(key, key_pos, KEY_COLOR)))
            else:
                s_key.append(f"{_color(key, KEY_COLOR)}")
        s.append(", ".join(s_key))
        s.append("\n")
        str_list.append("".join(s))
    return "\n".join(str_list) if str_list else ""

as_text ¤

as_text(document: Document) -> str

Render shortcut entries as plain text.

Parameters:

  • document ¤

    (Document) –

    Shortcut entries to render.

Returns:

  • str –

    Formatted text, or an empty string for an empty document.

Source code in src/keycut/_internal/render.py
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
def as_text(document: Document) -> str:
    """Render shortcut entries as plain text.

    Args:
        document: Shortcut entries to render.

    Returns:
        Formatted text, or an empty string for an empty document.
    """
    str_list = []
    for item in document:
        category = item.get("category", None)
        if category:
            str_list.append(f"Category: {category}\nAction: {item['action'].rstrip()}\nKeys: {item['keys']}\n")
        else:
            str_list.append(f"Action: {item['action'].rstrip()}\nKeys: {item['keys']}\n")
    return "\n".join(str_list) if str_list else ""

as_yaml ¤

as_yaml(document: Document) -> str

Serialize shortcut entries as YAML.

Parameters:

  • document ¤

    (Document) –

    Shortcut entries to serialize.

Returns:

  • str –

    YAML text.

Source code in src/keycut/_internal/render.py
120
121
122
123
124
125
126
127
128
129
def as_yaml(document: Document) -> str:
    """Serialize shortcut entries as YAML.

    Args:
        document: Shortcut entries to serialize.

    Returns:
        YAML text.
    """
    return yaml.dump(document)

check ¤

check(
    name: str, path: Path = DIRECTORY
) -> tuple[Path, bool]

Get the path to a shortcut file and check whether it exists.

Parameters:

  • name ¤

    (str) –

    Application name without the .yml suffix.

  • path ¤

    (Path, default: DIRECTORY ) –

    Directory containing the shortcut file.

Returns:

Source code in src/keycut/_internal/load.py
47
48
49
50
51
52
53
54
55
56
57
58
def check(name: str, path: Path = DIRECTORY) -> tuple[Path, bool]:
    """Get the path to a shortcut file and check whether it exists.

    Args:
        name: Application name without the `.yml` suffix.
        path: Directory containing the shortcut file.

    Returns:
        The file path and whether it is a file.
    """
    file = path.joinpath(name).with_suffix(".yml")
    return file, file.is_file()

from_yaml ¤

from_yaml(
    app: str, command_line: str | None = None
) -> Document | None

Load an application's shortcuts from YAML.

Parameters:

  • app ¤

    (str) –

    Application name used to locate the YAML file.

  • command_line ¤

    (str | None, default: None ) –

    Command line used to find a fallback file when the application's file is missing.

Returns:

  • Document | None –

    Shortcut entries, or None if no fallback file matches.

Source code in src/keycut/_internal/load.py
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
def from_yaml(app: str, command_line: str | None = None) -> Document | None:
    """Load an application's shortcuts from YAML.

    Args:
        app: Application name used to locate the YAML file.
        command_line: Command line used to find a fallback file when the
            application's file is missing.

    Returns:
        Shortcut entries, or `None` if no fallback file matches.
    """
    file, exist = check(app)
    if not exist and command_line is not None:
        file = grep(command_line)
        if not file:
            return None
    with Path(file).open() as f:
        doc = yaml.safe_load(f)
    if isinstance(doc, dict):
        document = [dict(category=key, **v) for key, value in doc.items() for v in value]
    else:
        document = [dict(category="", **value) for value in doc]
    return document

get_parser ¤

get_parser() -> ArgumentParser

Return the CLI argument parser.

Returns:

Source code in src/keycut/_internal/cli.py
48
49
50
51
52
53
54
55
56
57
def get_parser() -> argparse.ArgumentParser:
    """Return the CLI argument parser.

    Returns:
        An argparse parser.
    """
    parser = argparse.ArgumentParser(prog="keycut")
    parser.add_argument("-V", "--version", action="version", version=f"%(prog)s {debug._get_version()}")
    parser.add_argument("--debug-info", action=_DebugInfo, help="Print debug information.")
    return parser

grep ¤

grep(cmdline: str) -> Path | None

Find a shortcut file whose suffix appears in a command line.

Parameters:

  • cmdline ¤

    (str) –

    Command line to search, ignoring case.

Returns:

  • Path | None –

    The matching file path, or None if no file matches.

Source code in src/keycut/_internal/load.py
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
def grep(cmdline: str) -> Path | None:
    """Find a shortcut file whose suffix appears in a command line.

    Args:
        cmdline: Command line to search, ignoring case.

    Returns:
        The matching file path, or `None` if no file matches.
    """
    cmdline = cmdline.lower()
    for file in DIRECTORY.iterdir():
        app = file.suffix.lower()
        if app in cmdline:
            return DIRECTORY / file
    return None

in_action ¤

Return shortcuts whose action matches a case-insensitive regex.

Parameters:

  • document ¤

    (Document) –

    Shortcut entries to search.

  • pattern ¤

    (str) –

    Regular expression to match.

Returns:

  • Document –

    Matching shortcut entries.

Source code in src/keycut/_internal/search.py
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
def in_action(document: Document, pattern: str) -> Document:
    """Return shortcuts whose action matches a case-insensitive regex.

    Args:
        document: Shortcut entries to search.
        pattern: Regular expression to match.

    Returns:
        Matching shortcut entries.
    """
    return _search(document, pattern, key="action")

in_category ¤

in_category(document: Document, pattern: str) -> Document

Return shortcuts whose category matches a case-insensitive regex.

Parameters:

  • document ¤

    (Document) –

    Shortcut entries to search.

  • pattern ¤

    (str) –

    Regular expression to match.

Returns:

  • Document –

    Matching shortcut entries.

Source code in src/keycut/_internal/search.py
77
78
79
80
81
82
83
84
85
86
87
def in_category(document: Document, pattern: str) -> Document:
    """Return shortcuts whose category matches a case-insensitive regex.

    Args:
        document: Shortcut entries to search.
        pattern: Regular expression to match.

    Returns:
        Matching shortcut entries.
    """
    return _search(document, pattern, key="category")

in_keys ¤

Return shortcuts whose keys field matches a case-insensitive regex.

Parameters:

  • document ¤

    (Document) –

    Shortcut entries to search.

  • pattern ¤

    (str) –

    Regular expression to match.

Returns:

  • Document –

    Matching shortcut entries.

Source code in src/keycut/_internal/search.py
103
104
105
106
107
108
109
110
111
112
113
def in_keys(document: Document, pattern: str) -> Document:
    """Return shortcuts whose keys field matches a case-insensitive regex.

    Args:
        document: Shortcut entries to search.
        pattern: Regular expression to match.

    Returns:
        Matching shortcut entries.
    """
    return _search(document, pattern, key="keys")

main ¤

main(args: list[str] | None = None) -> int

Run the main program.

This function is executed when you type keycut or python -m keycut.

Parameters:

  • args ¤

    (list[str] | None, default: None ) –

    Arguments passed from the command line.

Returns:

  • int –

    An exit code.

Source code in src/keycut/_internal/cli.py
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
def main(args: list[str] | None = None) -> int:
    """Run the main program.

    This function is executed when you type `keycut` or `python -m keycut`.

    Parameters:
        args: Arguments passed from the command line.

    Returns:
        An exit code.
    """
    parser = get_parser()
    opts = parser.parse_args(args=args)
    print(opts)
    return 0

print_err ¤

print_err(message: str) -> None

Write a message to standard error.

Parameters:

  • message ¤

    (str) –

    Text to write without an added newline.

Source code in src/keycut/_internal/utils.py
22
23
24
25
26
27
28
def print_err(message: str) -> None:
    """Write a message to standard error.

    Args:
        message: Text to write without an added newline.
    """
    sys.stderr.write(message)

reload ¤

reload(document: Document) -> None

Print shortcut entries with terminal colors.

Parameters:

  • document ¤

    (Document) –

    Shortcut entries to print.

Source code in src/keycut/_internal/ui.py
45
46
47
48
49
50
51
52
def reload(document: Document) -> None:
    """Print shortcut entries with terminal colors.

    Args:
        document: Shortcut entries to print.
    """
    text = render.as_colored_text(document)
    print(text)  # noqa: T201

search ¤

Search shortcut actions, categories, and keys with a case-insensitive regex.

Matching entries gain position fields for highlighting. If nothing matches, return the original document.

Parameters:

  • document ¤

    (Document) –

    Shortcut entries to search.

  • pattern ¤

    (str) –

    Regular expression to match.

Returns:

  • Document –

    Matching shortcut entries, or the original document if none match.

Source code in src/keycut/_internal/search.py
61
62
63
64
65
66
67
68
69
70
71
72
73
74
def search(document: Document, pattern: str) -> Document:
    """Search shortcut actions, categories, and keys with a case-insensitive regex.

    Matching entries gain position fields for highlighting. If nothing matches,
    return the original document.

    Args:
        document: Shortcut entries to search.
        pattern: Regular expression to match.

    Returns:
        Matching shortcut entries, or the original document if none match.
    """
    return _search(document, pattern)

word_in_action ¤

word_in_action(
    document: Document, pattern: str
) -> Document

Return shortcuts with a whole-word match in their action.

Parameters:

  • document ¤

    (Document) –

    Shortcut entries to search.

  • pattern ¤

    (str) –

    Case-insensitive regular expression to match as a whole word.

Returns:

  • Document –

    Matching shortcut entries.

Source code in src/keycut/_internal/search.py
145
146
147
148
149
150
151
152
153
154
155
def word_in_action(document: Document, pattern: str) -> Document:
    """Return shortcuts with a whole-word match in their action.

    Args:
        document: Shortcut entries to search.
        pattern: Case-insensitive regular expression to match as a whole word.

    Returns:
        Matching shortcut entries.
    """
    return _search(document, pattern, key="action", word=True)

word_in_category ¤

word_in_category(
    document: Document, pattern: str
) -> Document

Return shortcuts with a whole-word match in their category.

Parameters:

  • document ¤

    (Document) –

    Shortcut entries to search.

  • pattern ¤

    (str) –

    Case-insensitive regular expression to match as a whole word.

Returns:

  • Document –

    Matching shortcut entries.

Source code in src/keycut/_internal/search.py
132
133
134
135
136
137
138
139
140
141
142
def word_in_category(document: Document, pattern: str) -> Document:
    """Return shortcuts with a whole-word match in their category.

    Args:
        document: Shortcut entries to search.
        pattern: Case-insensitive regular expression to match as a whole word.

    Returns:
        Matching shortcut entries.
    """
    return _search(document, pattern, key="category", word=True)

word_in_keys ¤

word_in_keys(document: Document, pattern: str) -> Document

Return shortcuts with a whole-word match in their keys field.

Parameters:

  • document ¤

    (Document) –

    Shortcut entries to search.

  • pattern ¤

    (str) –

    Case-insensitive regular expression to match as a whole word.

Returns:

  • Document –

    Matching shortcut entries.

Source code in src/keycut/_internal/search.py
158
159
160
161
162
163
164
165
166
167
168
def word_in_keys(document: Document, pattern: str) -> Document:
    """Return shortcuts with a whole-word match in their keys field.

    Args:
        document: Shortcut entries to search.
        pattern: Case-insensitive regular expression to match as a whole word.

    Returns:
        Matching shortcut entries.
    """
    return _search(document, pattern, key="keys", word=True)
word_search(document: Document, pattern: str) -> Document

Search all shortcut fields for whole-word matches.

Matching entries gain position fields for highlighting. If nothing matches, return the original document.

Parameters:

  • document ¤

    (Document) –

    Shortcut entries to search.

  • pattern ¤

    (str) –

    Case-insensitive regular expression to match as a whole word.

Returns:

  • Document –

    Matching shortcut entries, or the original document if none match.

Source code in src/keycut/_internal/search.py
116
117
118
119
120
121
122
123
124
125
126
127
128
129
def word_search(document: Document, pattern: str) -> Document:
    """Search all shortcut fields for whole-word matches.

    Matching entries gain position fields for highlighting. If nothing matches,
    return the original document.

    Args:
        document: Shortcut entries to search.
        pattern: Case-insensitive regular expression to match as a whole word.

    Returns:
        Matching shortcut entries, or the original document if none match.
    """
    return _search(document, pattern, word=True)