2025-08-19 08:06:37 -04:00

192 lines
6.5 KiB
Python

from talon import Module, actions, settings
from .edit_command_actions import EditAction, EditSimpleAction, run_action_callback
from .edit_command_modifiers import EditModifier, run_modifier_callback
mod = Module()
# providing some settings for customizing the word and line selection delay
# talon can execute selections must faster than a human
# resulting in unexpected or inconsistent results in applications such as visual studio code
mod.setting(
"edit_command_word_selection_delay",
type=int,
default=75,
desc="Sleep required between word selections",
)
mod.setting(
"edit_command_line_selection_delay",
type=int,
default=75,
desc="Sleep required between line selections",
)
def before_line_up():
actions.edit.up()
actions.edit.line_start()
def after_line_up():
actions.edit.up()
actions.edit.line_end()
def before_line_down():
actions.edit.down()
actions.edit.line_start()
def after_line_down():
actions.edit.down()
actions.edit.line_end()
def select_lines(action, direction, count):
if direction == "lineUp":
selection_callback = actions.edit.extend_line_up
extend_line_callback = actions.edit.extend_line_start
else:
selection_callback = actions.edit.extend_line_down
extend_line_callback = actions.edit.extend_line_end
selection_delay = f"{settings.get('user.edit_command_line_selection_delay')}ms"
for i in range(1, count + 1):
selection_callback()
actions.sleep(selection_delay)
# ensure we take the start/end of the line too!
extend_line_callback()
actions.sleep(selection_delay)
run_action_callback(action)
def select_words(action, direction, count):
if direction == "wordLeft":
selection_callback = actions.edit.extend_word_left
else:
selection_callback = actions.edit.extend_word_right
selection_delay = f"{settings.get('user.edit_command_word_selection_delay')}ms"
for i in range(1, count + 1):
selection_callback()
actions.sleep(selection_delay)
run_action_callback(action)
def word_movement_handler(action, direction, count):
if direction == "wordLeft":
movement_callback = actions.edit.word_left
else:
movement_callback = actions.edit.word_right
selection_delay = f"{settings.get('user.edit_command_word_selection_delay')}ms"
for i in range(1, count + 1):
movement_callback()
actions.sleep(selection_delay)
# in some cases, it is necessary to have some custom handling for timing reasons
custom_callbacks = {
("goAfter", "wordLeft"): word_movement_handler,
("goAfter", "wordRight"): word_movement_handler,
("goBefore", "wordLeft"): word_movement_handler,
("goBefore", "wordRight"): word_movement_handler,
# delete
("delete", "word"): select_words,
("delete", "wordLeft"): select_words,
("delete", "wordRight"): select_words,
("delete", "lineUp"): select_lines,
("delete", "lineDown"): select_lines,
# cut
("cutToClipboard", "word"): select_words,
("cutToClipboard", "wordLeft"): select_words,
("cutToClipboard", "wordRight"): select_words,
("cutToClipboard", "lineUp"): select_lines,
("cutToClipboard", "lineDown"): select_lines,
# copy
("copyToClipboard", "word"): select_words,
("copyToClipboard", "wordLeft"): select_words,
("copyToClipboard", "wordRight"): select_words,
("copyToClipboard", "lineUp"): select_lines,
("copyToClipboard", "lineDown"): select_lines,
# select
("select", "lineUp"): select_lines,
("select", "lineDown"): select_lines,
}
# In other cases there already is a "compound" talon action for a given action and modifier
compound_actions = {
# select
("select", "wordLeft"): actions.edit.extend_word_left,
("select", "wordRight"): actions.edit.extend_word_right,
("select", "left"): actions.edit.extend_left,
("select", "right"): actions.edit.extend_right,
("select", "word"): actions.edit.extend_word_right,
# Go before
("goBefore", "line"): actions.edit.line_start,
("goBefore", "lineUp"): before_line_up,
("goBefore", "lineDown"): before_line_down,
("goBefore", "paragraph"): actions.edit.paragraph_start,
("goBefore", "document"): actions.edit.file_start,
("goBefore", "fileStart"): actions.edit.file_start,
("goBefore", "selection"): actions.edit.left,
("goBefore", "wordLeft"): actions.edit.word_left,
("goBefore", "word"): actions.edit.word_left,
# Go after
("goAfter", "line"): actions.edit.line_end,
("goAfter", "lineUp"): after_line_up,
("goAfter", "lineDown"): after_line_down,
("goAfter", "paragraph"): actions.edit.paragraph_end,
("goAfter", "document"): actions.edit.file_end,
("goAfter", "fileEnd"): actions.edit.file_end,
("goAfter", "selection"): actions.edit.right,
("goAfter", "wordRight"): actions.edit.word_right,
("goAfter", "wordLeft"): actions.edit.word_left,
("goAfter", "word"): actions.edit.word_right,
# Delete
("delete", "left"): actions.edit.delete,
("delete", "right"): actions.user.delete_right,
("delete", "line"): actions.edit.delete_line,
("delete", "paragraph"): actions.edit.delete_paragraph,
# ("delete", "document"): actions.edit.delete_all, # Beta only
("delete", "document"): actions.user.delete_all,
("delete", "selection"): actions.edit.delete,
# Cut to clipboard
("cutToClipboard", "line"): actions.user.cut_line,
("cutToClipboard", "selection"): actions.edit.cut,
# copy
("copyToClipboard", "selection"): actions.edit.copy,
}
@mod.action_class
class Actions:
def edit_command(action: EditAction | str, modifier: EditModifier | str):
"""Perform edit command with associated modifier.
Action and modifier can be dataclasses (formed from utterances via
capture) or str, for use in scripts. Strings should match the action or
modifier types declared here or in edit_command_modifiers.py or
edit_command_actions.py"""
if isinstance(modifier, str):
modifier = EditModifier(modifier)
if isinstance(action, str):
action = EditSimpleAction(action)
key = (action.type, modifier.type)
count = modifier.count
if key in custom_callbacks:
custom_callbacks[key](action, modifier.type, count)
return
elif key in compound_actions:
for i in range(1, count + 1):
compound_actions[key]()
return
run_modifier_callback(modifier)
run_action_callback(action)