Revisions for ⁨text_manipulation_functions_modified⁩

View the changes made to this paste.

unlisted ⁨1⁩ ⁨file⁩ 2019-09-09 15:50:38 UTC

text_manipulation_functions_modified.py

@@ -0,0 +1,479 @@

+import pyperclip
+import re 
+import copy
+from dragonfly import Key, Pause, AppContext, Window
+from castervoice.lib import context
+from castervoice.lib.ccr.core.punctuation import text_punc_dict,  double_text_punc_dict
+from castervoice.lib.alphanumeric import caster_alphabet
+
+
+contexts = {
+    "texstudio": AppContext(executable="texstudio"),
+    "lyx": AppContext(executable="lyx"),
+    "winword": AppContext(executable="winword")
+}
+
+def get_application():
+    window = Window.get_foreground()
+    # Check all contexts. Return the name of the first one that matches or
+    # "standard" if none matched.
+    for name, context in contexts.items():
+        if context.matches(window.executable, window.title, window.handle):
+            return name
+    return "standard"
+            
+def get_start_end_position(text, phrase, direction, occurrence_number, dictation_versus_character):
+# def get_start_end_position(text, phrase, direction):
+    # print(dictation_versus_character)
+    # if dictation_versus_character == "character":
+    pattern = re.escape(phrase) 
+    # if dictation_versus_character == "dictation":
+        # avoid e.g. matching 'and' in 'land' but allow e.g. matching 'and' in 'hello.and'
+        # for matching purposes use lowercase
+        # PROBLEM: this will not match words in class names like "Class" in "ClassName"
+        # PROBLEM: it's not matching the right one when you have two occurrences of the same word in a row
+        # pattern = re.escape('(?:[^A-Za-z]|\A)({})(?:[^A-Za-z]|\Z)'.format(phrase.lower())) # must get group 1
+        # pattern = '(?:[^A-Za-z]|\A)({})(?:[^A-Za-z]|\Z)'.format(phrase.lower()) # must get group 1
+        
+    if not re.search(pattern, text.lower()):
+        # replaced phase not found
+        print("'{}' not found".format(phrase))
+        return
+    match_iter = re.finditer(pattern, text.lower())
+    # if dictation_versus_character == "character":
+    match_index_list = [(m.start(), m.end()) for m in match_iter] 
+    # if dictation_versus_character == "dictation":
+        # match_index_list = [(m.start(1), m.end(1)) for m in match_iter] # first group
+    
+    if direction == "left":
+        try:
+            match = match_index_list[-1*occurrence_number] # count from the right
+        except IndexError:
+            print("There aren't that many occurrences of '{}'".format(phrase))
+            return
+    if direction == "right":
+        try:
+            match = match_index_list[occurrence_number - 1] # count from the left
+        except IndexError:
+            print("There aren't that many occurrences of '{}'".format(phrase))
+            return 
+    left_index, right_index = match
+    return (left_index, right_index)
+    
+copy_pause_time_dict = {"standard": "10", "texstudio": "70", "lyx": "60", "winword": "90"}
+paste_pause_time_dict = {"standard": "0", "texstudio": "100", "lyx": "20", "winword": "20"} 
+# winword (a.k.a. Microsoft Word) pause times may need some tweaking, 
+# people are probably better off just using the native Dragon commands in winword.
+
+def text_manipulation_copy(application):
+    """ the wait time can also be modified up or down further by going into context.read_selected_without_altering_clipboard 
+    and changing the sleep time which is apparently slightly different than the pause time.
+    the sleep time is set to a positive number, so can be reduced
+    here I am using "wait time" to mean the sum of the sleep and pause time right after pressing control c """
+    # double hashmarks below indicate an alternative to using the functions in lib.context
+    ## previous_item_on_the_clipboard = pyperclip.paste()
+    ## Key("c-c").execute()
+    ## Pause(copy_pause_time_dict[application]).execute()
+    ## selected_text = pyperclip.paste()
+    
+    err, selected_text = context.read_selected_without_altering_clipboard(same_is_okay=True, pause_time=copy_pause_time_dict[application])
+    if err != 0:
+        # I'm not discriminating between err = 1 and err = 2
+        print("failed to copy text")
+        return
+    return selected_text
+    ## pyperclip.copy(previous_item_on_the_clipboard) 
+
+def text_manipulation_paste(text, application):
+    context.paste_string_without_altering_clipboard(text, pause_time=copy_pause_time_dict[application])
+
+def select_text_and_return_it(direction, number_of_lines_to_search, application):
+    if direction == "left":
+        if number_of_lines_to_search > 0:
+            Key("s-up:%d" %number_of_lines_to_search).execute()
+        Key("s-home").execute()
+    if direction == "right":    
+        if number_of_lines_to_search > 0:
+            Key("s-down:%d" %number_of_lines_to_search).execute()
+        Key("s-end").execute()
+        
+    selected_text = text_manipulation_copy(application)
+    if selected_text == None:
+        # failed to copy
+        return 
+    if selected_text == "":
+        print("no text to select")
+        return 
+    
+    return selected_text
+
+def deal_with_phrase_not_found(selected_text, application, direction):
+        # Approach 1: unselect text by pressing left and then right, works in Tex studio
+        if application == "texstudio":
+            Key("left, right").execute() # unselect text
+            if direction == "right":
+                Key("left:%d" %len(selected_text)).execute()
+        # Approach 2: unselect text by pressing opposite arrow key, does not work in Tex studio
+        else:
+            if direction == "left":
+                Key("right").execute()
+            if direction == "right":
+                Key("left").execute()
+
+def deal_with_up_down_directions(direction, number_of_lines_to_search):
+    # note that zero is the default number of lines to search, so if you change that you will may want to change this
+    if number_of_lines_to_search == 0 and (direction == "up" or direction == "down"):
+        # if the user says sauce (meeting up) or dunce (meaning down), set default number of lines to 3
+        number_of_lines_to_search = 3
+    if direction == "up":
+        direction = "left"
+    if direction == "down":    
+        direction = "right"
+    return (number_of_lines_to_search, direction)
+        
+def replace_phrase_with_phrase(text, replaced_phrase, replacement_phrase, direction, occurrence_number, dictation_versus_character):
+    match_index = get_start_end_position(text, replaced_phrase, direction, occurrence_number, dictation_versus_character)
+    if match_index:
+        left_index, right_index = match_index
+    else:
+        return
+    return text[: left_index] + replacement_phrase + text[right_index:] 
+    
+
+def copypaste_replace_phrase_with_phrase(replaced_phrase, replacement_phrase, direction, number_of_lines_to_search, occurrence_number, dictation_versus_character):
+    if direction == "up" or direction == "down":
+        # "up" and "down" get treated just as the "left" and "right" 
+        # except that the default number of lines to search get set to three instead of zero
+        number_of_lines_to_search, direction = deal_with_up_down_directions(direction, number_of_lines_to_search)
+    application = get_application()
+    selected_text = select_text_and_return_it(direction, number_of_lines_to_search, application)
+    if not selected_text:
+        return 
+    replaced_phrase = str(replaced_phrase)
+    replacement_phrase = str(replacement_phrase) 
+    new_text = replace_phrase_with_phrase(selected_text, replaced_phrase, replacement_phrase, direction, occurrence_number, dictation_versus_character)
+    if not new_text:
+        # replaced_phrase not found
+        deal_with_phrase_not_found(selected_text, application, direction)
+        return
+    
+    text_manipulation_paste(new_text, application)
+    if number_of_lines_to_search < 20: 
+        # only put the cursor back in the right spot if the number of lines to search is fairly small
+        if direction == "right":
+            offset = len(new_text)
+            Key("left:%d" %offset).execute()
+    
+def remove_phrase_from_text(text, phrase, direction, occurrence_number, dictation_versus_character):
+    match_index = get_start_end_position(text, phrase, direction, occurrence_number, dictation_versus_character)
+    if match_index:
+        left_index, right_index = match_index
+    else:
+        return
+        
+    # if the "phrase" is punctuation, just remove it, but otherwise remove an extra space adjacent to the phrase
+    if dictation_versus_character == "character":
+        return text[: left_index] + text[right_index:] 
+    else:
+        if left_index == 0:
+            # the phrase is at the beginning of the line
+            return text[right_index:]  # todo: consider removing extra space
+        elif text[left_index - 1] == " ":
+            return text[: left_index - 1] + text[right_index:]
+        else:
+            return text[: left_index] + text[right_index:]
+
+def copypaste_remove_phrase_from_text(phrase, direction, number_of_lines_to_search, occurrence_number, dictation_versus_character):
+    if direction == "up" or direction == "down":
+        number_of_lines_to_search, direction = deal_with_up_down_directions(direction, number_of_lines_to_search)
+    application = get_application()
+    selected_text = select_text_and_return_it(direction, number_of_lines_to_search, application)
+    if not selected_text:
+        return 
+    phrase = str(phrase)
+    new_text = remove_phrase_from_text(selected_text, phrase, direction, occurrence_number, dictation_versus_character)
+    if not new_text:
+        # phrase not found
+        deal_with_phrase_not_found(selected_text, application, direction)
+        return 
+    
+    text_manipulation_paste(new_text, application)
+
+    if direction == "right":
+        offset = len(new_text)
+        Key("left:%d" %offset).execute()
+
+    
+def delete_until_phrase(text, phrase, direction, before_after, occurrence_number, dictation_versus_character):
+    match_index = get_start_end_position(text, phrase, direction, occurrence_number, dictation_versus_character)
+    if match_index:
+        left_index, right_index = match_index
+    else:
+        return
+    # the spacing below may need to be tweaked
+    if direction == "left":
+        if before_after == "before":
+            # if text[-1] == " ":
+            #     return text[: left_index] + " "
+                return text[: left_index]
+
+        else: # todo: handle before-and-after defaults better
+            if text[-1] == " ":
+                return text[: right_index] + " "
+            else:
+                return text[: right_index]
+    if direction == "right":
+        if before_after == "after":
+            return text[right_index :]
+        else:
+            if text[0] == " ":
+                return " " + text[left_index :]
+            else:
+                return text[left_index :]
+
+def copypaste_delete_until_phrase(direction, phrase, number_of_lines_to_search, before_after, occurrence_number, dictation_versus_character):
+    if direction == "up" or direction == "down":
+        number_of_lines_to_search, direction = deal_with_up_down_directions(direction, number_of_lines_to_search)
+    application = get_application()  
+    if not before_after:
+        # default to delete all the way through the phrase not just up until it
+        if direction == "left":
+            before_after = "before"
+        if direction == "right":
+            before_after = "after"
+
+    selected_text = select_text_and_return_it(direction, number_of_lines_to_search, application)
+    if not selected_text:
+        return 
+    phrase = str(phrase)
+    new_text = delete_until_phrase(selected_text, phrase, direction, before_after, occurrence_number, dictation_versus_character)
+        
+    if new_text is None: 
+        # do NOT use `if not new_text` because that will pick up the case where new_text="" which
+        # occurs if the phrase is at the beginning of the line
+        # phrase not found
+        # deal_with_phrase_not_found(selected_text, temp_for_previous_clipboard_item, application, direction)
+        deal_with_phrase_not_found(selected_text, application, direction)
+        return
+
+    if new_text == "":
+        # phrase is at the beginning of the line
+        Key("del").execute()
+        return
+    else:
+        text_manipulation_paste(new_text, application)
+
+        if direction == "right":
+            offset = len(new_text)
+            Key("left:%d" %offset).execute()
+
+    
+
+def move_until_phrase(direction, before_after, phrase, number_of_lines_to_search, occurrence_number, dictation_versus_character):
+    if direction == "up" or direction == "down":
+        number_of_lines_to_search, direction = deal_with_up_down_directions(direction, number_of_lines_to_search)
+    application = get_application()
+    if not before_after:
+          # default to whatever is closest to the cursor
+        if direction  == "left":
+            before_after = "after"
+        if direction == "right":
+            before_after = "before"
+    
+    selected_text = select_text_and_return_it(direction, number_of_lines_to_search, application)
+    if not selected_text:
+        return
+    phrase = str(phrase)
+    match_index = get_start_end_position(selected_text, phrase, direction, occurrence_number, dictation_versus_character)
+    if match_index:
+        left_index, right_index = match_index
+    else:
+        # phrase not found
+        deal_with_phrase_not_found(selected_text, application, direction)
+        return
+              
+    if application == "texstudio":
+    # Approach 1: Unselect text by pressing left and then right. A little slower but works in Texstudio
+        Key("left, right").execute() # unselect text
+        if direction == "left":
+            # cursor is at the left side of the previously selected text
+            if before_after == "before":
+                selected_text_to_the_left_of_phrase = selected_text[:left_index]
+                multiline_offset_correction = selected_text_to_the_left_of_phrase.count("\r\n")
+                offset = left_index - multiline_offset_correction
+            if before_after == "after":
+                selected_text_to_the_left_of_phrase = selected_text[:right_index]
+                multiline_offset_correction = selected_text_to_the_left_of_phrase.count("\r\n")
+                offset = right_index - multiline_offset_correction
+            Key("right:%d" %offset).execute()
+
+        if direction == "right":
+            # cursor is at the left side of the previously selected text
+            if before_after == "before":
+                selected_text_to_the_right_of_phrase = selected_text[left_index :]    
+            if before_after == "after":
+                selected_text_to_the_right_of_phrase = selected_text[right_index :]
+            multiline_offset_correction = selected_text_to_the_right_of_phrase.count("\r\n")
+            if before_after  == "before":
+                offset = len(selected_text) - left_index - multiline_offset_correction
+            if before_after == "after":
+                offset = len(selected_text) - right_index - multiline_offset_correction
+            Key("left:%d" %offset).execute()
+    else:
+        # Approach 2: unselect using arrow keys rather than pasting over the existing text. (a little faster) does not work texstudio
+        if right_index < round(len(selected_text))/2:
+            # it's faster to approach phrase from the left
+            Key("left").execute() # unselect text and place cursor on the left side of selection 
+            if before_after  == "before":
+                offset_correction = selected_text[: left_index].count("\r\n")
+                offset = left_index - offset_correction
+            if before_after  == "after":
+                offset_correction = selected_text[: right_index].count("\r\n")
+                offset = right_index - offset_correction
+            Key("right:%d" %offset).execute()
+        else:
+            # it's faster to approach phrase from the right
+            Key("right").execute() # unselect text and place cursor on the right side of selection
+            if before_after  == "before":
+                offset_correction = selected_text[left_index :].count("\r\n")
+                offset = len(selected_text) - left_index - offset_correction
+            if before_after  == "after":
+                offset_correction = selected_text[right_index :].count("\r\n")
+                offset = len(selected_text) - right_index - offset_correction
+            Key("left:%d" %offset).execute()
+    
+            
+    
+def select_phrase(phrase, direction, number_of_lines_to_search, occurrence_number, dictation_versus_character):
+    if direction == "up" or direction == "down":
+        number_of_lines_to_search, direction = deal_with_up_down_directions(direction, number_of_lines_to_search)
+    application = get_application()
+    selected_text = select_text_and_return_it(direction, number_of_lines_to_search, application)
+    if not selected_text:
+        return 
+    phrase = str(phrase)
+    match_index = get_start_end_position(selected_text, phrase, direction, occurrence_number, dictation_versus_character)
+    if match_index:
+        left_index, right_index = match_index
+    else:
+        # phrase not found
+        deal_with_phrase_not_found(selected_text, application, direction)
+        return
+    
+        
+    # Approach 1: paste the selected text over itself rather than simply unselecting. A little slower but works Texstudio
+    # todo: change this so that it unselects by pressing left and then right rather than pasting over the top
+    if application == "texstudio":
+        text_manipulation_paste(selected_text, application) # yes, this is kind of redundant but it gets the proper pause time
+        multiline_movement_correction = selected_text[right_index :].count("\r\n")
+        movement_offset = len(selected_text) - right_index - multiline_movement_correction
+        Key("left:%d" %movement_offset).execute()
+        multiline_selection_correction = selected_text[left_index : right_index].count("\r\n")
+        selection_offset = len(selected_text[left_index : right_index]) - multiline_selection_correction
+        Key("s-left:%d" %selection_offset).execute()
+
+    # Approach 2: unselect using arrow keys rather than pasting over the existing text. (a little faster) does not work texstudio
+    else:
+        if right_index < round(len(selected_text))/2:
+            # it's faster to approach phrase from the left
+            Key("left").execute() # unselect text and place cursor on the left side of selection 
+            multiline_movement_offset_correction = selected_text[: left_index].count("\r\n")
+            movement_offset = left_index - multiline_movement_offset_correction
+            # move to the left side of the phrase
+            Key("right:%d" %movement_offset).execute()
+            # select phrase
+            multiline_selection_offset_correction = selected_text[left_index : right_index].count("\r\n")
+            selection_offset = len(phrase) - multiline_selection_offset_correction
+            Key("s-right:%d" %selection_offset).execute()
+        else:
+            # it's faster to approach phrase from the right
+            Key("right").execute() # unselect text and place cursor on the right side of selection
+            multiline_movement_offset_correction = selected_text[left_index :].count("\r\n")
+            movement_offset = len(selected_text) -  left_index - multiline_movement_offset_correction
+            # move to the left side of the phrase
+            Key("left:%d" %movement_offset).execute()
+            # select phrase
+            multiline_selection_offset_correction = selected_text[left_index : right_index].count("\r\n")
+            selection_offset = len(phrase) - multiline_selection_offset_correction
+            Key("s-right:%d" %selection_offset).execute()
+    
+
+
+def select_until_phrase(direction, phrase, before_after, number_of_lines_to_search, occurrence_number, dictation_versus_character):
+    if direction == "up" or direction == "down":
+        number_of_lines_to_search, direction = deal_with_up_down_directions(direction, number_of_lines_to_search)
+    application = get_application()  
+    if not before_after:
+    # default to select all the way through the phrase not just up until it
+        if direction == "left":
+            before_after = "before"
+        if direction == "right":
+            before_after = "after"
+    
+    selected_text = select_text_and_return_it(direction, number_of_lines_to_search, application)
+    if not selected_text:
+        return 
+    phrase = str(phrase)
+    match_index = get_start_end_position(selected_text, phrase, direction, occurrence_number, dictation_versus_character)
+    if match_index:
+        left_index, right_index = match_index
+    else:
+        # phrase not found
+        deal_with_phrase_not_found(selected_text, application, direction)
+        return
+    
+    # Approach 1: paste the selected text over itself rather than simply unselecting. A little slower but works Texstudio
+    # todo: change this so that it unselects by pressing left and then right rather than pasting over the top
+    if application == "texstudio":
+        text_manipulation_paste(selected_text, application) # yes, this is kind of redundant but it gets the proper pause time
+        if direction == "left":
+            if before_after == "before": 
+                selected_text_to_the_right_of_phrase = selected_text[left_index :]    
+                multiline_offset_correction = selected_text_to_the_right_of_phrase.count("\r\n")
+                offset = len(selected_text) - left_index - multiline_offset_correction
+                
+            if before_after == "after":
+                selected_text_to_the_right_of_phrase = selected_text[right_index :]
+                multiline_offset_correction = selected_text_to_the_right_of_phrase.count("\r\n")
+                offset = len(selected_text) - right_index - multiline_offset_correction
+            
+            Key("s-left:%d" %offset).execute()
+        if direction == "right":
+            multiline_movement_correction = selected_text.count("\r\n")
+            movement_offset = len(selected_text) - multiline_movement_correction
+            
+            if before_after == "before":
+                multiline_selection_correction = selected_text[: left_index].count("\r\n")
+                selection_offset = left_index - multiline_movement_correction
+            if before_after == "after":
+                multiline_selection_correction = selected_text[: right_index].count("\r\n")
+                selection_offset = right_index
+
+            # move cursor to original position
+            Key("left:%d" %movement_offset).execute()
+            # select text
+            Key("s-right:%d" %selection_offset).execute()
+    
+    # Approach 2: unselect using arrow keys rather than pasting over the existing text. (a little faster) does not work texstudio
+    else:
+        if direction == "left":
+            Key("right").execute() # unselect text and move to left side of selection
+            if before_after == "before":
+                multiline_correction = selected_text[left_index :].count("\r\n")
+                offset = len(selected_text) - left_index - multiline_correction
+            if before_after == "after":
+                multiline_correction = selected_text[right_index :].count("\r\n")
+                offset = len(selected_text) - right_index - multiline_correction
+            Key("s-left:%d" %offset).execute()
+        if direction == "right": 
+            Key("left").execute() # unselect text and move to the right side of selection
+            if before_after == "before":
+                multiline_correction = selected_text[: left_index].count("\r\n")
+                offset = left_index - multiline_correction
+            if before_after == "after":
+                multiline_correction = selected_text[: right_index].count("\r\n")
+                offset = right_index - multiline_correction
+            Key("s-right:%d" %offset).execute()
+
+
+