init commit

This commit is contained in:
unknown
2025-08-19 08:06:37 -04:00
commit 2957b5515a
743 changed files with 45495 additions and 0 deletions
+27
View File
@@ -0,0 +1,27 @@
from talon import Module, actions
mod = Module()
mod.list("delimiter_pair", "List of matching pair delimiters")
@mod.capture(rule="{user.delimiter_pair}")
def delimiter_pair(m) -> list[str]:
pair = m.delimiter_pair.split()
assert len(pair) == 2
# "space" requires a special written form because Talon lists are whitespace insensitive
open = pair[0] if pair[0] != "space" else " "
close = pair[1] if pair[1] != "space" else " "
return [open, close]
@mod.action_class
class Actions:
def delimiter_pair_insert(pair: list[str]):
"""Insert a delimiter pair <pair> leaving the cursor in the middle"""
actions.user.insert_between(pair[0], pair[1])
def delimiter_pair_wrap_selection(pair: list[str]):
"""Wrap selection with delimiter pair <pair>"""
selected = actions.edit.selected_text()
actions.insert(f"{pair[0]}{selected}{pair[1]}")
@@ -0,0 +1,25 @@
list: user.delimiter_pair
-
# Format:
# SPOKEN_FORM: LEFT_DELIMITER RIGHT_DELIMITER
# Use the literal symbols for delimiters (except for whitespace, which is "space")
#
# Examples:
# round: ( )
# pad: space space
round: ( )
box: [ ]
diamond: < >
curly: { }
twin: "' '"
quad: '" "'
skis: ` `
percentages: % %
pad: space space
escaped quad: '\\" \\"'
escaped twin: "\\' \\'"
escaped round: \( \)
escaped box: \[ \]
+153
View File
@@ -0,0 +1,153 @@
from talon import Context, Module, actions, clip, settings
ctx = Context()
mod = Module()
mod.setting(
"selected_text_timeout",
type=float,
default=0.25,
desc="Time in seconds to wait for the clipboard to change when trying to get selected text",
)
END_OF_WORD_SYMBOLS = ".!?;:—_/\\|@#$%^&*()[]{}<>=+-~`"
@ctx.action_class("edit")
class EditActions:
def selected_text() -> str:
timeout = settings.get("user.selected_text_timeout")
with clip.capture(timeout) as s:
actions.edit.copy()
try:
return s.text()
except clip.NoChange:
return ""
def line_insert_down():
actions.edit.line_end()
actions.key("enter")
def selection_clone():
actions.edit.copy()
actions.edit.select_none()
actions.edit.paste()
def line_clone():
# This may not work if editor auto-indents. Is there a better way?
actions.edit.line_start()
actions.edit.extend_line_end()
actions.edit.copy()
actions.edit.right()
actions.key("enter")
actions.edit.paste()
# # This simpler implementation of select_word mostly works, but in some apps it doesn't.
# # See https://github.com/talonhub/community/issues/1084.
# def select_word():
# actions.edit.right()
# actions.edit.word_left()
# actions.edit.extend_word_right()
def select_word():
actions.edit.extend_right()
character_to_right_of_initial_caret_position = actions.edit.selected_text()
# Occasionally apps won't let you edit.extend_right()
# and therefore won't select text if your caret is on the rightmost character
# such as in the Chrome URL bar
did_select_text = character_to_right_of_initial_caret_position != ""
if did_select_text:
# .strip() turns newline & space characters into empty string; the empty
# string is in any other string, so this works.
if (
character_to_right_of_initial_caret_position.strip()
in END_OF_WORD_SYMBOLS
):
# Come out of the highlight in the initial position.
actions.edit.left()
else:
# Come out of the highlight one character
# to the right of the initial position.
actions.edit.right()
actions.edit.word_left()
actions.edit.extend_word_right()
@mod.action_class
class Actions:
def paste(text: str):
"""Pastes text and preserves clipboard"""
with clip.revert():
clip.set_text(text)
actions.edit.paste()
# sleep here so that clip.revert doesn't revert the clipboard too soon
actions.sleep("150ms")
def delete_right():
"""Delete character to the right"""
actions.key("delete")
def delete_all():
"""Delete all text in the current document"""
actions.edit.select_all()
actions.edit.delete()
def words_left(n: int):
"""Moves left by n words."""
for _ in range(n):
actions.edit.word_left()
def words_right(n: int):
"""Moves right by n words."""
for _ in range(n):
actions.edit.word_right()
def cut_word_left():
"""Cuts the word to the left."""
actions.edit.extend_word_left()
actions.edit.cut()
def cut_word_right():
"""Cuts the word to the right."""
actions.edit.extend_word_right()
actions.edit.cut()
def copy_word_left():
"""Copies the word to the left."""
actions.edit.extend_word_left()
actions.edit.copy()
def copy_word_right():
"""Copies the word to the right."""
actions.edit.extend_word_right()
actions.edit.copy()
# ----- Start / End of line -----
def select_line_start():
"""Select to start of current line"""
if actions.edit.selected_text():
actions.edit.left()
actions.edit.extend_line_start()
def select_line_end():
"""Select to end of current line"""
if actions.edit.selected_text():
actions.edit.right()
actions.edit.extend_line_end()
def line_middle():
"""Go to the middle of the line"""
actions.edit.select_line()
half_line_length = int(len(actions.edit.selected_text()) / 2)
actions.edit.left()
for i in range(0, half_line_length):
actions.edit.right()
def cut_line():
"""Cut current line"""
actions.edit.select_line()
actions.edit.cut()
+85
View File
@@ -0,0 +1,85 @@
# Compound of action(select, clear, copy, cut, paste, etc.) and modifier(word,
# line, etc.) commands for editing text.
# eg: "select line", "clear all"
# For overriding or creating aliases for specific actions, this function will
# also accept strings, e.g. `user.edit_command("delete", "wordLeft")`.
# See edit_command_modifiers.py to discover the correct string for the modify argument,
# and `edit_command_actions.py` `simple_action_callbacks` to find strings for the action argument.
<user.edit_action> <user.edit_modifier>: user.edit_command(edit_action, edit_modifier)
# Zoom
zoom in: edit.zoom_in()
zoom out: edit.zoom_out()
zoom reset: edit.zoom_reset()
# Searching
find it: edit.find()
next one: edit.find_next()
# Navigation
# The reason for these spoken forms is that "page up" and "page down" are globally defined as keys.
scroll up: edit.page_up()
scroll down: edit.page_down()
# go left, go left left down, go 5 left 2 down
# go word left, go 2 words right
go <user.navigation_step>+: user.perform_navigation_steps(navigation_step_list)
go line start | head: edit.line_start()
go line end | tail: edit.line_end()
go way left:
edit.line_start()
edit.line_start()
go way right: edit.line_end()
go way up: edit.file_start()
go way down: edit.file_end()
go top: edit.file_start()
go bottom: edit.file_end()
go page up: edit.page_up()
go page down: edit.page_down()
# Indentation
indent [more]: edit.indent_more()
(indent less | out dent): edit.indent_less()
# Copy
copy that: edit.copy()
# Cut
cut that: edit.cut()
# Paste
(pace | paste) (that | it): edit.paste()
(pace | paste) enter:
edit.paste()
key(enter)
paste match: edit.paste_match_style()
# Duplication
clone that: edit.selection_clone()
clone line: edit.line_clone()
# Insert new line
new line above: edit.line_insert_up()
new line below | slap: edit.line_insert_down()
# Insert padding with optional symbols
padding: user.insert_between(" ", " ")
(pad | padding) <user.symbol_key>+:
insert(" ")
user.insert_many(symbol_key_list)
insert(" ")
# Undo/redo
undo that: edit.undo()
redo that: edit.redo()
# Save
file save: edit.save()
file save all: edit.save_all()
[go] line mid: user.line_middle()
+191
View File
@@ -0,0 +1,191 @@
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)
+113
View File
@@ -0,0 +1,113 @@
from dataclasses import dataclass
from typing import Callable, Union
from talon import Module, actions
@dataclass
class EditSimpleAction:
""" "Simple" actions are actions that don't require any arguments, only a type (select, copy, delete, etc.)"""
type: str
def __str__(self):
return self.type
@dataclass
class EditInsertAction:
type = "insert"
text: str
def __str__(self):
return self.type
@dataclass
class EditWrapAction:
type = "wrapWithDelimiterPair"
pair: list[str]
def __str__(self):
return self.type
@dataclass
class EditFormatAction:
type = "applyFormatter"
formatters: str
def __str__(self):
return self.type
EditAction = Union[
EditSimpleAction,
EditInsertAction,
EditWrapAction,
EditFormatAction,
]
mod = Module()
mod.list("edit_action", desc="Actions for the edit command")
@mod.capture(rule="{user.edit_action}")
def edit_simple_action(m) -> EditSimpleAction:
return EditSimpleAction(m.edit_action)
@mod.capture(rule="<user.delimiter_pair> wrap")
def edit_wrap_action(m) -> EditWrapAction:
return EditWrapAction(m.delimiter_pair)
@mod.capture(rule="<user.formatters> format")
def edit_format_action(m) -> EditFormatAction:
return EditFormatAction(m.formatters)
@mod.capture(
rule="<user.edit_simple_action> | <user.edit_wrap_action> | <user.edit_format_action>"
)
def edit_action(m) -> EditAction:
return m[0]
simple_action_callbacks: dict[str, Callable] = {
"select": actions.skip,
"goBefore": actions.edit.left,
"goAfter": actions.edit.right,
"copyToClipboard": actions.edit.copy,
"cutToClipboard": actions.edit.cut,
"pasteFromClipboard": actions.edit.paste,
"insertLineAbove": actions.edit.line_insert_up,
"insertLineBelow": actions.edit.line_insert_down,
"insertCopyAfter": actions.edit.selection_clone,
"delete": actions.edit.delete,
}
def run_action_callback(action: EditAction):
action_type = action.type
if action_type in simple_action_callbacks:
callback = simple_action_callbacks[action_type]
callback()
return
match action_type:
case "insert":
assert isinstance(action, EditInsertAction)
actions.insert(action.text)
case "wrapWithDelimiterPair":
assert isinstance(action, EditWrapAction)
return lambda: actions.user.delimiter_pair_wrap_selection(action.pair)
case "applyFormatter":
assert isinstance(action, EditFormatAction)
actions.user.formatters_reformat_selection(action.formatters)
case _:
raise ValueError(f"Unknown edit action: {action_type}")
@@ -0,0 +1,13 @@
list: user.edit_action
-
select: select
go before: goBefore
go after: goAfter
copy: copyToClipboard
cut: cutToClipboard
paste: pasteFromClipboard
paste to: pasteFromClipboard
clear: delete
@@ -0,0 +1,75 @@
from contextlib import suppress
from dataclasses import dataclass
from typing import Callable
from talon import Module, actions
mod = Module()
mod.list("edit_modifier", desc="Modifiers for the edit command")
mod.list(
"edit_modifier_repeatable",
desc="Modifiers for the edit command that are repeatable",
)
@dataclass
class EditModifier:
type: str
count: int = 1
@dataclass
class EditModifierCallback:
modifier: str
callback: Callable
@mod.capture(
rule="({user.edit_modifier}) | ([<number_small>] {user.edit_modifier_repeatable})"
)
def edit_modifier(m) -> EditModifier:
count = 1
with suppress(AttributeError):
count = m.number_small
with suppress(AttributeError):
type = m.edit_modifier
with suppress(AttributeError):
type = m.edit_modifier_repeatable
return EditModifier(type, count=count)
modifiers = [
EditModifierCallback("document", actions.edit.select_all),
EditModifierCallback("paragraph", actions.edit.select_paragraph),
EditModifierCallback("word", actions.edit.extend_word_right),
EditModifierCallback("wordLeft", actions.edit.extend_word_left),
EditModifierCallback("wordRight", actions.edit.extend_word_right),
EditModifierCallback("left", actions.edit.extend_left),
EditModifierCallback("right", actions.edit.extend_right),
EditModifierCallback("lineUp", actions.edit.extend_line_up),
EditModifierCallback("lineDown", actions.edit.extend_line_down),
EditModifierCallback("line", actions.edit.select_line),
EditModifierCallback("lineEnd", actions.edit.extend_line_end),
EditModifierCallback("lineStart", actions.edit.extend_line_start),
EditModifierCallback("fileStart", actions.edit.extend_file_start),
EditModifierCallback("fileEnd", actions.edit.extend_file_end),
EditModifierCallback("selection", actions.skip),
]
modifier_dictionary: dict[str, EditModifierCallback] = {
item.modifier: item for item in modifiers
}
def run_modifier_callback(modifier: EditModifier):
modifier_type = modifier.type
if modifier_type not in modifier_dictionary:
raise ValueError(f"Unknown edit modifier: {modifier_type}")
count = modifier.count
modifier = modifier_dictionary[modifier_type]
for i in range(1, count + 1):
modifier.callback()
@@ -0,0 +1,13 @@
list: user.edit_modifier
-
all: document
paragraph: paragraph
line: line
line start: lineStart
way left: lineStart
line end: lineEnd
way right: lineEnd
file start: fileStart
way up: fileStart
file end: fileEnd
way down: fileEnd
@@ -0,0 +1,9 @@
list: user.edit_modifier_repeatable
-
word: word
word left: wordLeft
word right: wordRight
up: lineUp
down: lineDown
left: left
right: right
+194
View File
@@ -0,0 +1,194 @@
# defines the default edit actions for linux
from talon import Context, actions
ctx = Context()
ctx.matches = r"""
os: linux
"""
@ctx.action_class("edit")
class EditActions:
def copy():
actions.key("ctrl-c")
def cut():
actions.key("ctrl-x")
def delete():
actions.key("backspace")
def delete_line():
actions.edit.select_line()
actions.edit.delete()
# action(edit.delete_paragraph):
# action(edit.delete_sentence):
def delete_word():
actions.edit.select_word()
actions.edit.delete()
def down():
actions.key("down")
# action(edit.extend_again):
# action(edit.extend_column):
def extend_down():
actions.key("shift-down")
def extend_file_end():
actions.key("shift-ctrl-end")
def extend_file_start():
actions.key("shift-ctrl-home")
def extend_left():
actions.key("shift-left")
# action(edit.extend_line):
def extend_line_down():
actions.key("shift-down")
def extend_line_end():
actions.key("shift-end")
def extend_line_start():
actions.key("shift-home")
def extend_line_up():
actions.key("shift-up")
def extend_page_down():
actions.key("shift-pagedown")
def extend_page_up():
actions.key("shift-pageup")
# action(edit.extend_paragraph_end):
# action(edit.extend_paragraph_next()):
# action(edit.extend_paragraph_previous()):
# action(edit.extend_paragraph_start()):
def extend_right():
actions.key("shift-right")
# action(edit.extend_sentence_end):
# action(edit.extend_sentence_next):
# action(edit.extend_sentence_previous):
# action(edit.extend_sentence_start):
def extend_up():
actions.key("shift-up")
def extend_word_left():
actions.key("ctrl-shift-left")
def extend_word_right():
actions.key("ctrl-shift-right")
def file_end():
actions.key("ctrl-end")
def file_start():
actions.key("ctrl-home")
def find(text: str = None):
actions.key("ctrl-f")
if text:
actions.insert(text)
def find_previous():
actions.key("shift-f3")
def find_next():
actions.key("f3")
def indent_less():
actions.key("home delete")
def indent_more():
actions.key("home tab")
# action(edit.jump_column(n: int)
# action(edit.jump_line(n: int)
def left():
actions.key("left")
def line_down():
actions.key("down home")
def line_end():
actions.key("end")
def line_insert_up():
actions.key("home enter up")
def line_start():
actions.key("home")
def line_up():
actions.key("up home")
# action(edit.move_again):
def page_down():
actions.key("pagedown")
def page_up():
actions.key("pageup")
# action(edit.paragraph_end):
# action(edit.paragraph_next):
# action(edit.paragraph_previous):
# action(edit.paragraph_start):
def paste():
actions.key("ctrl-v")
# action(paste_match_style):
def print():
actions.key("ctrl-p")
def redo():
actions.key("ctrl-y")
def right():
actions.key("right")
def save():
actions.key("ctrl-s")
def save_all():
actions.key("ctrl-shift-s")
def select_all():
actions.key("ctrl-a")
def select_line(n: int = None):
if n is not None:
actions.edit.jump_line(n)
actions.key("end shift-home")
# action(edit.select_lines(a: int, b: int)):
def select_none():
actions.key("right")
# action(edit.select_paragraph):
# action(edit.select_sentence):
def undo():
actions.key("ctrl-z")
def up():
actions.key("up")
def word_left():
actions.key("ctrl-left")
def word_right():
actions.key("ctrl-right")
def zoom_in():
actions.key("ctrl-+")
def zoom_out():
actions.key("ctrl--")
def zoom_reset():
actions.key("ctrl-0")
+194
View File
@@ -0,0 +1,194 @@
from talon import Context, actions, clip
ctx = Context()
ctx.matches = r"""
os: mac
"""
@ctx.action_class("edit")
class EditActions:
def copy():
actions.key("cmd-c")
def cut():
actions.key("cmd-x")
def delete():
actions.key("backspace")
def delete_line():
actions.edit.select_line()
actions.edit.delete()
# action(edit.delete_paragraph):
# action(edit.delete_sentence):
def delete_word():
actions.edit.select_word()
actions.edit.delete()
def down():
actions.key("down")
# action(edit.extend_again):
# action(edit.extend_column):
def extend_down():
actions.key("shift-down")
def extend_file_end():
actions.key("cmd-shift-down")
def extend_file_start():
actions.key("cmd-shift-up")
def extend_left():
actions.key("shift-left")
# action(edit.extend_line):
def extend_line_down():
actions.key("shift-down")
def extend_line_end():
actions.key("cmd-shift-right")
def extend_line_start():
actions.key("cmd-shift-left")
def extend_line_up():
actions.key("shift-up")
def extend_page_down():
actions.key("cmd-shift-pagedown")
def extend_page_up():
actions.key("cmd-shift-pageup")
# action(edit.extend_paragraph_end):
# action(edit.extend_paragraph_next()):
# action(edit.extend_paragraph_previous()):
# action(edit.extend_paragraph_start()):
def extend_right():
actions.key("shift-right")
# action(edit.extend_sentence_end):
# action(edit.extend_sentence_next):
# action(edit.extend_sentence_previous):
# action(edit.extend_sentence_start):
def extend_up():
actions.key("shift-up")
def extend_word_left():
actions.key("shift-alt-left")
def extend_word_right():
actions.key("shift-alt-right")
def file_end():
actions.key("cmd-down")
def file_start():
actions.key("cmd-up")
def find(text: str = None):
if text is not None:
clip.set_text(text, mode="find")
actions.key("cmd-f")
def find_next():
actions.key("cmd-g")
def find_previous():
actions.key("cmd-shift-g")
def indent_less():
actions.key("cmd-left delete")
def indent_more():
actions.key("cmd-left tab")
# action(edit.jump_column(n: int)
# action(edit.jump_line(n: int)
def left():
actions.key("left")
def line_down():
actions.key("down home")
def line_end():
actions.key("cmd-right")
def line_insert_up():
actions.key("cmd-left enter up")
def line_start():
actions.key("cmd-left")
def line_up():
actions.key("up cmd-left")
# action(edit.move_again):
def page_down():
actions.key("pagedown")
def page_up():
actions.key("pageup")
# action(edit.paragraph_end):
# action(edit.paragraph_next):
# action(edit.paragraph_previous):
# action(edit.paragraph_start):
def paste():
actions.key("cmd-v")
def paste_match_style():
actions.key("cmd-alt-shift-v")
def print():
actions.key("cmd-p")
def redo():
actions.key("cmd-shift-z")
def right():
actions.key("right")
def save():
actions.key("cmd-s")
def save_all():
actions.key("cmd-alt-s")
def select_all():
actions.key("cmd-a")
def select_line(n: int = None):
if n is not None:
actions.edit.jump_line(n)
actions.key("cmd-right cmd-shift-left")
# action(edit.select_lines(a: int, b: int)):
def select_none():
actions.key("right")
# action(edit.select_paragraph):
# action(edit.select_sentence):
def undo():
actions.key("cmd-z")
def up():
actions.key("up")
def word_left():
actions.key("alt-left")
def word_right():
actions.key("alt-right")
def zoom_in():
actions.key("cmd-=")
def zoom_out():
actions.key("cmd--")
def zoom_reset():
actions.key("cmd-0")
@@ -0,0 +1,65 @@
from contextlib import suppress
from dataclasses import dataclass
from typing import Callable, Literal
from talon import Module, actions, settings
@dataclass
class NavigationStep:
modifier: Literal[
"wordLeft", "wordRight", "word", "left", "right", "lineUp", "lineDown"
]
count: int
mod = Module()
@mod.capture(rule="[<number_small>] {user.edit_modifier_repeatable}")
def navigation_step(m) -> NavigationStep:
count = 1
modifier = m.edit_modifier_repeatable
with suppress(AttributeError):
count = m.number_small
return NavigationStep(
modifier=modifier,
count=count,
)
@mod.action_class
class Actions:
def perform_navigation_steps(steps: list[NavigationStep]):
"""Navigate by a series of steps"""
for step in steps:
match step.modifier:
case "wordLeft":
repeat_action(actions.edit.word_left, step.count, True)
case "wordRight":
repeat_action(actions.edit.word_right, step.count, True)
case "word":
repeat_action(actions.edit.word_right, step.count, True)
case "left":
repeat_action(actions.edit.left, step.count)
case "right":
repeat_action(actions.edit.right, step.count)
case "lineUp":
repeat_action(actions.edit.up, step.count)
case "lineDown":
repeat_action(actions.edit.down, step.count)
def repeat_action(action: Callable, count: int, delay: bool = False):
delay_string = None
if delay:
delay_string = f"{settings.get('user.edit_command_word_selection_delay')}ms"
for _ in range(count):
action()
if delay_string:
actions.sleep(delay_string)
+115
View File
@@ -0,0 +1,115 @@
from talon import Context, Module, actions
ctx = Context()
mod = Module()
@ctx.action_class("edit")
class EditActions:
def paragraph_start():
if extend_paragraph_start_with_success():
actions.edit.left()
def paragraph_end():
if extend_paragraph_end_with_success():
actions.edit.right()
def select_paragraph():
if is_line_empty():
return
# Search for start of paragraph
actions.edit.extend_paragraph_start()
actions.edit.left()
# Extend to end of paragraph
actions.edit.extend_paragraph_end()
def extend_paragraph_start():
# The reason for the wrapper function is a difference in function signature.
# The Talon action has no return value and the below function returns a boolean with success state.
extend_paragraph_start_with_success()
def extend_paragraph_end():
extend_paragraph_end_with_success()
def delete_paragraph():
actions.edit.select_paragraph()
# Remove selection
actions.edit.delete()
# Remove the empty line containing the cursor
actions.edit.delete()
# Remove leading or trailing empty line
actions.edit.delete_line()
@mod.action_class
class Actions:
def cut_paragraph():
"""Cut paragraph under the cursor"""
actions.edit.select_paragraph()
actions.edit.cut()
def copy_paragraph():
"""Copy paragraph under the cursor"""
actions.edit.select_paragraph()
actions.edit.copy()
def paste_paragraph():
"""Paste to paragraph under the cursor"""
actions.edit.select_paragraph()
actions.edit.paste()
def is_line_empty() -> bool:
"""Check if the current line is empty. Return True if empty."""
actions.edit.extend_line_start()
text = actions.edit.selected_text().strip()
if text:
actions.edit.right()
return False
actions.edit.extend_line_end()
text = actions.edit.selected_text().strip()
if text:
actions.edit.left()
return False
return True
def extend_paragraph_start_with_success() -> bool:
"""Extend selection to the start of the paragraph. Return True if successful."""
actions.edit.extend_line_start()
text = actions.edit.selected_text()
length = len(text)
while True:
actions.edit.extend_up()
actions.edit.extend_line_start()
text = actions.edit.selected_text()
new_length = len(text)
if new_length == length:
break
line = text[: new_length - length].strip()
if not line:
actions.edit.extend_down()
break
length = new_length
return text.strip() != ""
def extend_paragraph_end_with_success() -> bool:
"""Extend selection to the end of the paragraph. Return True if successful."""
actions.edit.extend_line_end()
text = actions.edit.selected_text()
length = len(text)
while True:
actions.edit.extend_down()
actions.edit.extend_line_end()
text = actions.edit.selected_text()
new_length = len(text)
if new_length == length:
break
line = text[length:].strip()
if not line:
actions.edit.extend_line_start()
actions.edit.extend_left()
break
length = new_length
return text.strip() != ""
+194
View File
@@ -0,0 +1,194 @@
# defines the default edit actions for windows
from talon import Context, actions
ctx = Context()
ctx.matches = r"""
os: windows
"""
@ctx.action_class("edit")
class EditActions:
def copy():
actions.key("ctrl-c")
def cut():
actions.key("ctrl-x")
def delete():
actions.key("backspace")
def delete_line():
actions.edit.select_line()
actions.edit.delete()
# action(edit.delete_paragraph):
# action(edit.delete_sentence):
def delete_word():
actions.edit.select_word()
actions.edit.delete()
def down():
actions.key("down")
# action(edit.extend_again):
# action(edit.extend_column):
def extend_down():
actions.key("shift-down")
def extend_file_end():
actions.key("shift-ctrl-end")
def extend_file_start():
actions.key("shift-ctrl-home")
def extend_left():
actions.key("shift-left")
# action(edit.extend_line):
def extend_line_down():
actions.key("shift-down")
def extend_line_end():
actions.key("shift-end")
def extend_line_start():
actions.key("shift-home")
def extend_line_up():
actions.key("shift-up")
def extend_page_down():
actions.key("shift-pagedown")
def extend_page_up():
actions.key("shift-pageup")
# action(edit.extend_paragraph_end):
# action(edit.extend_paragraph_next()):
# action(edit.extend_paragraph_previous()):
# action(edit.extend_paragraph_start()):
def extend_right():
actions.key("shift-right")
# action(edit.extend_sentence_end):
# action(edit.extend_sentence_next):
# action(edit.extend_sentence_previous):
# action(edit.extend_sentence_start):
def extend_up():
actions.key("shift-up")
def extend_word_left():
actions.key("ctrl-shift-left")
def extend_word_right():
actions.key("ctrl-shift-right")
def file_end():
actions.key("ctrl-end")
def file_start():
actions.key("ctrl-home")
def find(text: str = None):
actions.key("ctrl-f")
if text:
actions.insert(text)
def find_previous():
actions.key("shift-f3")
def find_next():
actions.key("f3")
def indent_less():
actions.key("home delete")
def indent_more():
actions.key("home tab")
# action(edit.jump_column(n: int)
# action(edit.jump_line(n: int)
def left():
actions.key("left")
def line_down():
actions.key("down home")
def line_end():
actions.key("end")
def line_insert_up():
actions.key("home enter up")
def line_start():
actions.key("home")
def line_up():
actions.key("up home")
# action(edit.move_again):
def page_down():
actions.key("pagedown")
def page_up():
actions.key("pageup")
# action(edit.paragraph_end):
# action(edit.paragraph_next):
# action(edit.paragraph_previous):
# action(edit.paragraph_start):
def paste():
actions.key("ctrl-v")
# action(paste_match_style):
def print():
actions.key("ctrl-p")
def redo():
actions.key("ctrl-y")
def right():
actions.key("right")
def save():
actions.key("ctrl-s")
def save_all():
actions.key("ctrl-shift-s")
def select_all():
actions.key("ctrl-a")
def select_line(n: int = None):
if n is not None:
actions.edit.jump_line(n)
actions.key("end shift-home")
# action(edit.select_lines(a: int, b: int)):
def select_none():
actions.key("right")
# action(edit.select_paragraph):
# action(edit.select_sentence):
def undo():
actions.key("ctrl-z")
def up():
actions.key("up")
def word_left():
actions.key("ctrl-left")
def word_right():
actions.key("ctrl-right")
def zoom_in():
actions.key("ctrl-+")
def zoom_out():
actions.key("ctrl--")
def zoom_reset():
actions.key("ctrl-0")
+12
View File
@@ -0,0 +1,12 @@
from talon import Module, actions
mod = Module()
@mod.action_class
class module_actions:
def insert_between(before: str, after: str):
"""Insert `before + after`, leaving cursor between `before` and `after`. Not entirely reliable if `after` contains newlines."""
actions.insert(f"{before}{after}")
for _ in after:
actions.edit.left()