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 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()