S or /

Defining and overriding behavior

Talon Abstractions Overview

You can define and override behavior in Python files. You use Module objects to define things like actions and settings, and you use Context objects to override stuff builtin to Talon or defined by a Module in specific contexts, such as overriding an action when a specific application is focused or on a specific operating system.

Talon provides abstractions for defining flexible command spoken forms. A list maps spoken forms to corresponding values. A capture can use combinations of lists, other captures, and explicit spoken forms to map what the user says to values. As an example, a list can map the names of numbers to their numeric values to allow defining voice commands that take a number as an argument, such as allowing saying "delete twenty five" to press the delete key 25 times or "delete thirty" to press the delete key 30 times. Captures allow for complex combinations of spoken forms to allow for flexible commands.

Talon provides constructs for understanding context. A Tag is either active or not. Tags can be activated and deactivated through commands or as a consequence of other context information. A Scope can have arbitrary values. This can allow matching on properties of a scope or specific scope values. A Mode defines a set of commands users can chain together. Users typically define a mode if they only want commands from that mode active in specific contexts, such as an exam mode limiting the available commands to a small, approved subset.

Context is important. A lot of functionality needs to be overridden based on the nature of the current operating system or application to work correctly.

Declarations

How to create a Module:

from talon import Module

mod = Module()

This defines a Module named mod that you can use to define things. The following code examples will assume that a Module named mod is defined.

There are 2 ways to declare an action on a Module. The first is writing @mod.action and then a function definition.

@mod.action
def action_name():
    "Description of the action"

The second is writing @mod.action_class and then a class providing the action declarations in its methods. This is just a more convenient way to declare multiple actions.

@mod.action_class
class Actions:
    def action_name():
        "Description of the action"

    def second_action(arg1: int, arg2: str='') -> str:
        "Action with arguments, return type, and body"
        return 'test'

You can declare a capture on a Module by writing @mod.capture(rule=rule), where rule is the capture rule, and then a function for the capture that has a documentation string. If you want to provide an implementation for a capture, provide a function definition deciding what to convert the spoken input to. The spoken input can be parsed as a list of words, but you can also use the names of lists and captures used as attributes on the input. You define the rule using the same syntax that you use for command spoken forms in a .talon file.

from contextlib import suppress

# <> denotes a capture
# [] is for an optional part
# | means or
@mod.capture(rule="[minus] <number_small> [(point | dot) <number_small>]")
def user_file_set_small_float(m) -> float:
    """A floating point number"""
    is_negative = m[0] == "minus"
    number = m.number_small_1
    with suppress(AttributeError):
        decimal_part = m.number_small_2
        if decimal_part > 9:
            decimal_part /= 100
        else:
            decimal_part /= 10
        number += decimal_part
    if is_negative:
        number *= -1
    return number

@mod.capture(rule="<user.user_file_set_small_float>")
def user_file_set_small_float_string(m) -> str:
    """A string representation of a floating point number"""
    return str(m.user_file_set_small_float)

A scope is a highly flexible abstraction for context matching. You declare a scope on a Module by writing @mod.scope followed by a function that returns a dictionary mapping the name of the scope to its value. You then call .update() on that function to update the value of the scope.

from talon import Module, ui

from pathlib import Path

mod = Module()
previous_app_name: str = ""

@mod.scope
def scope_updater():
    return {"user_file_set_previous_app": previous_app_name}

# when a new window is focused, update the previous app
def update_previous_app(*args):
    global previous_app_name
    app = ui.active_app()
    app_name = Path(app.path).stem
    if app_name == previous_app_name:
        return
    scope_updater.update()
    previous_app_name = app_name

ui.register("win_focus", update_previous_app)

This scope can then be used from a .talon file like this:

user.user_file_set_previous_app: Microsoft Word
mode: sleep
-
# when Microsoft Word was the previous app and Talon is not listening for regular commands,
# pause the current video and switch back to Word so I can take notes
pause:
    key('space')
    apps.focus("Microsoft Word")

You declare a setting on a module by calling mod.setting(). You can access a setting value in Python code by calling settings.get() with the setting name. Example:

from talon import actions, settings

mod.setting(
    "user_file_set_spaces_per_tab",
    type=int,
    default=4,
    desc="The number of spaces to use per tab"
)

@mod.action_class
class Actions:
    def user_file_set_insert_tab_spaces(number_of_tabs: int=1):
        """Insert the specified number of tabs as spaces"""
        spaces_per_tab = settings.get("user.user_file_set_spaces_per_tab")
        actions.insert(spaces_per_tab*number_of_tabs*" ")

You declare a list on a Module by calling mod.list() with the name of the list and a description. Example:

mod.list("list_name", desc="list description")

You declare a mode on a Module by calling mod.mode() with the name of the mode and a description. Example:

mod.mode("user_file_set_video_mode", desc="Watching a video")

The following example .talon files are for activating the mode and then using it

video mode:
    # save the current modes
    mode.save()
    mode.disable("command")
    mode.disable("dictation")
    mode.enable("user.user_file_set_video_mode")
mode: user.user_file_set_video_mode
-
video (pause | play): key('space')
video backup: key('left')
mode restore:
    mode.disable("user.user_file_set_video_mode")
    # restore the saved modes
    mode.restore()

You can define a tag on a Module by calling mod.tag() with the name of the tag and a description. Example:

mod.tag("tag_name", desc="tag description")

Anything declared by a user script on a Module is in the user. namespace and must therefore be referenced using the user. prefix. For instance, the above action_name action example would be called by writing user.action_name() in a .talon file.

Implementations

How to create a Context object:

from talon import Context

ctx = Context()

This creates a context matching the default context. You can make it match a specific context by setting the .matches attribute to a string defining what to match using the same syntax as the context header of a .talon file.

Context matching example:

chrome_ctx = Context()
chrome_ctx.matches = "app.name: Google Chrome"

You can use a Context object to override something when the context it matches is active.

The following example shows how to override an action on a Context. To overwrite an action on a context object named ctx using an action class, write @ctx.action_class(prefix), where prefix is the prefix for the current namespace, such as “user” for the user. namespace.

#Define an action
@mod.action_class
class Actions:
    def user_file_set_insert_reversed(text: str):
        """Insert the text reversed"""
        reversed_list = [text[i] for i in range(len(text)-1, -1, -1)]
        reversed_text = "".join(reversed_list)
        actions.insert(reversed_text)

#Override the action when using Google Chrome
chrome_ctx = Context()
chrome_ctx.matches = "app.name: Google Chrome"
@chrome_ctx.action_class("user")
class ChromeActions:
    def user_file_set_insert_reversed(text: str):
        #print in the log
        print('text', text)
        #actions.next calls the overridden action, which is sometimes desired
        actions.next(text)

To override a capture on a Context named ctx, write @ctx.capture(capture_name, rule=rule) where capture_name is the name of the capture to override and rule is the new capture rule. Example:

# {} denotes a list
# + means that part of the rule can be repeated
@mod.capture(rule="{user.letter}+")
def user_file_set_letters(m) -> str:
    """A series of letters"""
    return "".join(m.letter_list)

# override the letter capture for Google Chrome
chrome_context = Context()
chrome_context.matches = "app.name: Google Chrome"

@chrome_context.capture("user.user_file_set_letters", rule="[capital] {user.letter}+")
def user_file_set_letters(m) -> str:
    should_capitalize = m[0] == "capital"
    text = "".join(m.letter_list)
    if should_capitalize:
        text = text.upper()
    return text

You can set a setting on a Context called ctx by writing ctx.settings[setting_name] = new_value, where setting_name is the name of the setting and new_value is the value to store in the setting.

mod.setting(
    "user_file_set_spaces_per_tab",
    type=int,
    default=4,
    desc="The number of spaces to use per tab"
)

@mod.action_class
class Actions:
    def user_file_set_insert_tab_spaces(number_of_tabs: int=1):
        """Insert the specified number of tabs as spaces"""
        spaces_per_tab = settings.get("user.user_file_set_spaces_per_tab")
        actions.insert(spaces_per_tab*number_of_tabs*" ")

# setting the setting in a context
text_edit_context = Context()
text_edit_context.matches = "app.name: TextEdit"

text_edit_context.settings["user.user_file_set_spaces_per_tab"] = 2

You can set a Talon list’s contents when a Context named ctx is active by assigning a dictionary mapping spoken forms to values to ctx.lists[list_name], where list_name is the name of the list. Example:

mod.list("code_data_type", desc="Refers to a data type for a programming language")

# defining the list for python
python_context = Context()
python_context.matches = "code.language: python"

python_context.lists["user.code_data_type"] = {
    "list": "list",
    "dictionary": "dict",
    "integer": "int",
    "int": "int",
    "float": "float",
    "string": "str",
    "tuple": "tuple",
    "none": "None"
}
Source

Lifecycle

Reference