From 623fe56116069aacfec4f0e87145a36b2a317057 Mon Sep 17 00:00:00 2001 From: chaosparrot Date: Sun, 1 Dec 2024 16:44:19 +0100 Subject: [PATCH 01/24] Started working on terminal 'support' by creating a couple of settings These settings still need to be properly connected however --- README.md | 3 +++ context.py | 16 ++++++++++++---- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index ac8e8ed..0689a08 100644 --- a/README.md +++ b/README.md @@ -91,6 +91,9 @@ We could make it easier to loop through sentences since we already have the buff [] - Implement flow for digits Right now, you still need to say `numb zero` every time between commands. We can detect if we should allow digits, periods and other kinds of formatters as single words if we can be very certain that the next character will be +[] - Word wrap detection +We need to find a way to deal with word wrap, meaning things being on a single line, but visually ( and most importantly, keyboard relatively ) they are on multiple lines. Our current Up and Down arrow key pressing does not deal with that. + #### Programs [] - Improved MacOS support diff --git a/context.py b/context.py index f4ec05c..63a1c78 100644 --- a/context.py +++ b/context.py @@ -3,20 +3,28 @@ mod = Module() ctx = Context() mod.list("marithime_terminator_word", desc="A list of all the end-of-command terminator words used within dictation and other commands") - mod.tag("marithime_available", desc="Check that the Marithime package is available for a user") mod.tag("marithime_dictation", desc="Overrides the dictation insert with the marithime one") -mod.tag("marithime_context_disable_shift_selection", desc="Disables shift selection for the current context") -mod.tag("marithime_context_disable_word_wrap", desc="Disables word wrap detection for the current context") +mod.tag("marithime_input_field_text", desc="Set when a single line text input field is focused") mod.setting("marithime_auto_fixing_enabled", type=int, default=0, desc="Whether to allow auto-fixing ( auto correct ) based on earlier corrections") + +# Settings that handle multi-line +mod.setting("marithime_context_shift_selection", type=int, default=1, desc="Enables or disables the use of shift press selection for the current context") +mod.setting("marithime_context_multiline_supported", type=int, default=1, desc="Enables or disables the use of multiple lines through the enter key") +mod.setting("marithime_context_word_wrap_width", type=int, default=-1, desc="Sets the width of the current input element for word wrapping, negative for disabled") + +# Key tracking +mod.setting("marithime_context_clear_key", type=str, default="", desc="When this key is pressed, the context is cleared - For example, enter presses clearing the terminal") mod.setting("marithime_context_remove_undo", type=str, default="ctrl-z", desc="The key combination to undo a paste action") mod.setting("marithime_context_remove_word", type=str, default="ctrl-backspace", desc="The key combination to clear a word to the left of the caret") mod.setting("marithime_context_remove_letter", type=str, default="backspace", desc="The key combination to clear a single letter to the left of the caret") mod.setting("marithime_context_remove_forward_word", type=str, default="ctrl-delete", desc="The key combination to clear a word to the right of the caret") mod.setting("marithime_context_remove_forward_letter", type=str, default="delete", desc="The key combination to clear a single letter to the right of the caret") -# This is default turned to aggressive ( to re-index after every action ) until we fix most of the edgecases where de-syncs happen +# Options - "" (default) - Whenever the confidence that it has lost the location in the file, re-index +# - aggressive - After every marithime command that requires context, we re-index +# - disabled - Disable indexing altogether, so no shift-select, clipboard, file or accessibility indexing mod.setting("marithime_indexing_strategy", type=str, default="", desc="Determine what strategy we should use to begin reindexing documents") ctx.tags = ["user.marithime_available"] From 1eb4c6a8f6fff92b196d72e24eaa9f9fb9aa5ab8 Mon Sep 17 00:00:00 2001 From: chaosparrot Date: Sun, 8 Dec 2024 14:13:58 +0100 Subject: [PATCH 02/24] Started adding terminal and input field settings --- programs/input_field.talon | 4 ++++ programs/terminal.talon | 7 +++++++ 2 files changed, 11 insertions(+) create mode 100644 programs/input_field.talon create mode 100644 programs/terminal.talon diff --git a/programs/input_field.talon b/programs/input_field.talon new file mode 100644 index 0000000..6ce920e --- /dev/null +++ b/programs/input_field.talon @@ -0,0 +1,4 @@ +tag: user.marithime_input_field_text +- +settings(): + user.marithime_context_multiline_supported = 0 \ No newline at end of file diff --git a/programs/terminal.talon b/programs/terminal.talon new file mode 100644 index 0000000..4be5ea5 --- /dev/null +++ b/programs/terminal.talon @@ -0,0 +1,7 @@ +tag: terminal +- +settings(): + user.marithime_indexing_strategy = "disabled" + user.marithime_context_multiline_supported = 0 + user.marithime_context_shift_selection = 0 + user.marithime_context_clear_key = "enter" \ No newline at end of file From 43924eb9d035f3ea5a9db9471c16eb3e7b482534 Mon Sep 17 00:00:00 2001 From: chaosparrot Date: Sun, 8 Dec 2024 15:31:04 +0100 Subject: [PATCH 03/24] Integrating multiline, clear key and shift selection support throughout the virtual buffer --- virtual_buffer/buffer.py | 11 +++++++++++ virtual_buffer/caret_tracker.py | 18 +++++++++++++---- virtual_buffer/input_context.py | 9 ++++++++- virtual_buffer/input_context_manager.py | 25 ++++++++++++++++++++++-- virtual_buffer/manager.py | 26 +++++++++++++++++++++++++ 5 files changed, 82 insertions(+), 7 deletions(-) diff --git a/virtual_buffer/buffer.py b/virtual_buffer/buffer.py index 0c5f559..13864df 100644 --- a/virtual_buffer/buffer.py +++ b/virtual_buffer/buffer.py @@ -22,6 +22,7 @@ class VirtualBuffer: caret_tracker: CaretTracker = None last_action_type = "insert" + shift_selection = True def __init__(self): self.caret_tracker = CaretTracker() @@ -678,3 +679,13 @@ def get_next_text(self) -> str: next_text += self.tokens[index].text[right_token_index[1]:] return next_text + + def set_shift_selection(self, shift_selection: bool): + self.caret_tracker.shift_selection = shift_selection + self.shift_selection = shift_selection + + def set_multiline_supported(self, multiline_supported: bool): + self.caret_tracker.multiline_supported = multiline_supported + + def set_clear_key(self, clear_key: str): + self.caret_tracker.clear_key = clear_key \ No newline at end of file diff --git a/virtual_buffer/caret_tracker.py b/virtual_buffer/caret_tracker.py index 6bb6a24..0532717 100644 --- a/virtual_buffer/caret_tracker.py +++ b/virtual_buffer/caret_tracker.py @@ -59,6 +59,10 @@ class CaretTracker: selection_caret_marker = (-1, -1) last_caret_movement: str = "" + shift_selection = True + multiline_supported = True + clear_key:str = "" + def __init__(self, system = platform.system()): self.system = system self.is_macos = system == "Darwin" @@ -86,9 +90,15 @@ def apply_key(self, key: str) -> bool: self.shift_down = False continue + # Clear the full context after the clear key was pressed + if key_modifier[0] in self.clear_key: + self.clear() + key_used = True + return + if self.is_macos: if "alt" in key: - self.selecting_text = "shift" in key or self.shift_down + self.selecting_text = self.shift_selection and "shift" in key or self.shift_down key_combinations = key_modifier[0].lower().split("-") if "left" in key: left_movements = 1 @@ -113,7 +123,7 @@ def apply_key(self, key: str) -> bool: # Control keys are slightly inconsistent across programs, but generally they skip a word elif "ctrl" in key: - self.selecting_text = "shift" in key or self.shift_down + self.selecting_text = self.shift_selection and "shift" in key or self.shift_down key_combinations = key_modifier[0].lower().split("-") if "left" in key: left_movements = 1 @@ -148,7 +158,7 @@ def apply_key(self, key: str) -> bool: key_used = True elif "left" in key and not ("cmd" in key or "super" in key): - selecting_text = "shift" in key or self.shift_down + selecting_text = self.shift_selection and "shift" in key or self.shift_down left_movements = 1 if len(key_modifier) >= 1 and key_modifier[-1].isnumeric(): left_movements = int(key_modifier[-1]) @@ -161,7 +171,7 @@ def apply_key(self, key: str) -> bool: key_used = True self.last_caret_movement = "left" elif "right" in key and not ("cmd" in key or "super" in key): - selecting_text = "shift" in key or self.shift_down + selecting_text = self.shift_selection and "shift" in key or self.shift_down right_movements = 1 if len(key_modifier) >= 1 and key_modifier[-1].isnumeric(): right_movements = int(key_modifier[-1]) diff --git a/virtual_buffer/input_context.py b/virtual_buffer/input_context.py index 57795ac..868c600 100644 --- a/virtual_buffer/input_context.py +++ b/virtual_buffer/input_context.py @@ -59,4 +59,11 @@ def destroy(self): self.modified_at = 0 self.buffer = None - \ No newline at end of file + def set_shift_selection(self, shift_selection: bool): + self.buffer.set_shift_selection(shift_selection) + + def set_multiline_supported(self, multiline_supported: bool): + self.buffer.set_multiline_supported(multiline_supported) + + def set_clear_key(self, clear_key: str): + self.buffer.set_clear_key(clear_key) \ No newline at end of file diff --git a/virtual_buffer/input_context_manager.py b/virtual_buffer/input_context_manager.py index 39fca13..66e7e1f 100644 --- a/virtual_buffer/input_context_manager.py +++ b/virtual_buffer/input_context_manager.py @@ -100,9 +100,13 @@ def switch_context(self, window) -> bool: def ensure_viable_context(self): # Reset the confidence for every pol - if (settings.get("user.marithime_indexing_strategy") == "aggressive"): + indexing_strategy = settings.get("user.marithime_indexing_strategy") + if indexing_strategy == "aggressive": self.visual_state['content_confidence'] = 0 self.visual_state['caret_confidence'] = 1 + # Don't do indexing at all + elif indexing_strategy == "disabled": + return update_caret = self.visual_state['caret_confidence'] != 2 update_content = self.visual_state['caret_confidence'] == 1 and self.visual_state['content_confidence'] < 1 @@ -292,6 +296,9 @@ def should_use_last_formatter(self, use_last_formatter: bool): self.use_last_set_formatter = use_last_formatter def index_accessible_content(self): + if settings.get("user.marithime_indexing_strategy") == "disabled": + return None + accessible_text = actions.user.marithime_get_element_text() if self.current_context: self.current_context.set_accessible_api_available("text", True) @@ -317,6 +324,9 @@ def index_file(self, file_path: str): self.index_content(file_contents) def index_textarea(self, total_value: str = "", forced = True): + if settings.get("user.marithime_indexing_strategy") == "disabled": + return + self.update_visual_state(scanning=True) accessible_text = self.index_accessible_content() if total_value == "" and accessible_text: @@ -354,6 +364,8 @@ def zero_width_space_insertion_index(self) -> (int, int): return self.indexer.determine_caret_position(zwsp, total_value) def find_caret_position(self, total_value: str, visibility_level = 0, accessible_text = None) -> (str, (int, int), (int, int)): + if settings.get("user.marithime_indexing_strategy") == "disabled": + return ("", (-1, -1), (-1, -1)) self.update_visual_state(scanning=True) undefined_positions = (total_value, (-1, -1), (-1, -1)) before_text = "" @@ -532,4 +544,13 @@ def update_visual_state(self, level: str = None, caret_confidence: int = -1, con if is_updated and self.state_callback: self.state_callback(self.visual_state['scanning'], self.visual_state['level'], self.visual_state['caret_confidence'], self.visual_state['content_confidence']) except NotImplementedError: - pass \ No newline at end of file + pass + + def set_shift_selection(self, shift_selection: bool): + self.get_current_context().set_shift_selection(shift_selection) + + def set_multiline_supported(self, multiline_supported: bool): + self.get_current_context().set_multiline_supported(multiline_supported) + + def set_clear_key(self, clear_key: str): + self.get_current_context().set_clear_key(clear_key) diff --git a/virtual_buffer/manager.py b/virtual_buffer/manager.py index ca7668a..c2b9c8e 100644 --- a/virtual_buffer/manager.py +++ b/virtual_buffer/manager.py @@ -287,7 +287,17 @@ def focus_changed(self, event): def window_closed(self, event): self.context.close_context(event) + def set_shift_selection(self, shift_selection: bool): + self.context.set_shift_selection(shift_selection) + + def set_multiline_supported(self, multiline_supported: bool): + self.context.set_multiline_supported(multiline_supported) + + def set_clear_key(self, clear_key: str): + self.context.set_clear_key(clear_key) + def update_language(language: str): + global mutator if not language: language = settings.get("speech.language", "en") if language is None: @@ -301,14 +311,30 @@ def update_language(language: str): pass mutator.fixer.load_fixes(language, engine_description) +def update_shift_selection(shift_selection: bool): + global mutator + mutator.set_shift_selection(shift_selection) + +def update_multiline_supported(multiline_support: bool): + global mutator + mutator.set_multiline_supported(multiline_support) + +def update_clear_key(clear_key: str): + global mutator + mutator.set_clear_key(clear_key) + mutator = None def init_mutator(): global mutator mutator = VirtualBufferManager() ui.register("win_focus", mutator.focus_changed) ui.register("win_close", mutator.window_closed) + settings.register("speech.language", lambda language: update_language(language)) settings.register("speech.engine", lambda _: update_language("")) + settings.register("user.marithime_context_shift_selection", lambda shift_enabled: update_shift_selection(shift_enabled > 0)) + settings.register("user.marithime_context_clear_key", lambda clear_key: update_clear_key(clear_key)) + settings.register("user.marithime_context_multiline_supported", lambda supported: update_multiline_supported(supported > 0)) update_language("") app.register("ready", init_mutator) From 16d142df8a12d15ad0ccc1dcb0dcd41d7b06305e Mon Sep 17 00:00:00 2001 From: chaosparrot Date: Sun, 8 Dec 2024 20:32:56 +0100 Subject: [PATCH 04/24] Started writing testcases for virtually selecting and virtually removing text ( when shift selection is not supported ) --- .../removing_virtual_selection.py | 107 ++++++++++++++++++ .../virtual_buffer/virtual_selection_range.py | 45 ++++++++ virtual_buffer/buffer.py | 64 ++++++++--- virtual_buffer/manager.py | 5 + 4 files changed, 208 insertions(+), 13 deletions(-) create mode 100644 tests/virtual_buffer/removing_virtual_selection.py create mode 100644 tests/virtual_buffer/virtual_selection_range.py diff --git a/tests/virtual_buffer/removing_virtual_selection.py b/tests/virtual_buffer/removing_virtual_selection.py new file mode 100644 index 0000000..03c22d4 --- /dev/null +++ b/tests/virtual_buffer/removing_virtual_selection.py @@ -0,0 +1,107 @@ +from ...virtual_buffer.caret_tracker import _CARET_MARKER +from ...virtual_buffer.buffer import VirtualBuffer +from ...virtual_buffer.indexer import text_to_virtual_buffer_tokens +from ..test import create_test_suite + +def get_filled_vb(): + vb = VirtualBuffer() + vb.shift_selection = False + vb.insert_tokens(text_to_virtual_buffer_tokens("Insert ", "insert")) + vb.insert_tokens(text_to_virtual_buffer_tokens("two ", "two")) + vb.insert_tokens(text_to_virtual_buffer_tokens("words ", "words")) + vb.insert_tokens(text_to_virtual_buffer_tokens("into ", "into")) + vb.insert_tokens(text_to_virtual_buffer_tokens("a ", "a")) + vb.insert_tokens(text_to_virtual_buffer_tokens("previous ", "previous")) + vb.insert_tokens(text_to_virtual_buffer_tokens("sentence, ", "sentence")) + vb.insert_tokens(text_to_virtual_buffer_tokens("so ", "so")) + vb.insert_tokens(text_to_virtual_buffer_tokens("that ", "that")) + vb.insert_tokens(text_to_virtual_buffer_tokens("the ", "the")) + vb.insert_tokens(text_to_virtual_buffer_tokens("previous ", "previous")) + vb.insert_tokens(text_to_virtual_buffer_tokens("sentence ", "sentence")) + vb.insert_tokens(text_to_virtual_buffer_tokens("will ", "will")) + vb.insert_tokens(text_to_virtual_buffer_tokens("become ", "become")) + vb.insert_tokens(text_to_virtual_buffer_tokens("longer ", "longer")) + vb.insert_tokens(text_to_virtual_buffer_tokens("too! ", "too")) + vb.insert_tokens(text_to_virtual_buffer_tokens("Words ", "words")) + vb.insert_tokens(text_to_virtual_buffer_tokens("can ", "can")) + vb.insert_tokens(text_to_virtual_buffer_tokens("be ", "be")) + vb.insert_tokens(text_to_virtual_buffer_tokens("anything.", "anything")) + + return vb + + +def test_remove_selecting_single_tokens(assertion): + vb = get_filled_vb() + + assertion( " Virtually selecting a single token to the left and remove it...") + vb.select_phrases(["anything"]) + assertion(vb.virtual_selection, False) + keys = vb.remove_virtual_selection("backspace") + for key in keys: + vb.apply_key(key) + caret_index = vb.caret_tracker.get_caret_index() + assertion( " Expect caret line index to be 1", caret_index[0] == 1) + assertion( " Expect caret character index to be the same as before (0)", caret_index[1] == 0) + assertion( " Expect 'backspace' to have been pressed 9 times to remove 'anything.'", keys[0] == "backspace:9") + assertion( " Expect no selection detected", vb.is_selecting() == False) + assertion( " Expect final token to have changed", vb.tokens[-1].text == "be") + assertion( "'" + vb.tokens[-1].text + "'", False) + + #assertion( " Selecting three characters to the right and remove them...") + #vb.apply_key("shift:down right:3 shift:up delete") + #assertion( " Expect token length to stay the same (3)", len(vb.tokens) == 3) + #caret_index = vb.caret_tracker.get_caret_index() + #assertion( " Expect caret line index to be 1", caret_index[0] == 1) + #assertion( " Expect caret character index to be the three less than before (7)", caret_index[1] == 7) + #assertion( " Expect no selection detected", vb.is_selecting() == False) + #assertion( " Expect text to be merged", vb.tokens[1].text == "Insert a secondtence. \n") + #assertion( " Expect phrase to be merged", vb.tokens[1].phrase == "insert a secondtence") + #assertion( " Selecting right beyond the line break and remove the selection...") + #vb.apply_key("shift:down right:10 shift:up backspace") + #assertion( " Expect token length to be one less (2)", len(vb.tokens) == 2) + #caret_index = vb.caret_tracker.get_caret_index() + #assertion( " Expect caret line index to be 1", caret_index[0] == 1) + #assertion( " Expect caret character index to be the start of the next sentence (22)", caret_index[1] == 22) + #assertion( " Expect no selection detected", vb.is_selecting() == False) + #assertion( " Expect text to be merged", vb.tokens[1].text == "Insert a secondsert a third sentence.") + #assertion( " Expect phrase to be merged", vb.tokens[1].phrase == "insert a secondsert a third sentence") + #assertion( " Selecting left beyond the line break and remove the selection...") + #vb.apply_key("shift:down left:18 shift:up backspace") + #assertion( " Expect token length to be one less (1)", len(vb.tokens) == 1) + #caret_index = vb.caret_tracker.get_caret_index() + #assertion( " Expect caret line index to be 0", caret_index[0] == 0) + #assertion( " Expect caret character index to be the end of the sentence (22)", caret_index[1] == 22) + #assertion( " Expect no selection detected", vb.is_selecting() == False) + #assertion( " Expect text to be merged", vb.tokens[0].text == "Insert a new sentencesert a third sentence.") + #assertion( " Expect phrase to be merged", vb.tokens[0].phrase == "insert a new sentencesert a third sentence") + +def test_remove_selecting_multiple_tokens_left(assertion): + vb = VirtualBuffer() + vb.insert_tokens(text_to_virtual_buffer_tokens("Suggest", "suggest")) + vb.insert_tokens(text_to_virtual_buffer_tokens(" create", "create")) + vb.insert_tokens(text_to_virtual_buffer_tokens(" delete", "delete")) + vb.insert_tokens(text_to_virtual_buffer_tokens(" insertion", "insertion")) + vb.caret_tracker.text_buffer = "Suggest create delete insert" + _CARET_MARKER + "ion" + + assertion( " Selecting characters until the left side of the token is reached and removing it...") + vb.apply_key("shift:down left:7 shift:up backspace") + assertion( " Expect token length to be one less (3)", len(vb.tokens) == 3) + caret_index = vb.caret_tracker.get_caret_index() + assertion( " Expect caret line index to be 0", caret_index[0] == 0) + assertion( " Expect caret character index to be the same as before (3)", caret_index[1] == 3) + assertion( " Expect no selection detected", vb.is_selecting() == False) + assertion( " Expect text to be merged", vb.tokens[-1].text == " deleteion") + assertion( " Expect phrase to be merged", vb.tokens[-1].phrase == "deleteion") + assertion( " Selecting characters until multiple tokens have been skipped over and removing it...") + vb.apply_key("shift:down left:14 shift:up backspace") + assertion( " Expect token length to be two less (1)", len(vb.tokens) == 1) + caret_index = vb.caret_tracker.get_caret_index() + assertion( " Expect caret line index to be 0", caret_index[0] == 0) + assertion( " Expect caret character index to be the same as before (3)", caret_index[1] == 3) + assertion( " Expect no selection detected", vb.is_selecting() == False) + assertion( " Expect text to be merged", vb.tokens[-1].text == "Suggestion") + assertion( " Expect phrase to be merged", vb.tokens[-1].phrase == "suggestion") + +suite = create_test_suite("Removing virtually selected text") +suite.add_test(test_remove_selecting_single_tokens) +suite.run() \ No newline at end of file diff --git a/tests/virtual_buffer/virtual_selection_range.py b/tests/virtual_buffer/virtual_selection_range.py new file mode 100644 index 0000000..33ca552 --- /dev/null +++ b/tests/virtual_buffer/virtual_selection_range.py @@ -0,0 +1,45 @@ +from ...virtual_buffer.buffer import VirtualBuffer +from ...virtual_buffer.indexer import text_to_virtual_buffer_tokens +from ..test import create_test_suite + +def test_virtual_select_single_word_and_extending(assertion): + vb = VirtualBuffer() + vb.shift_selection = False + vb.insert_tokens(text_to_virtual_buffer_tokens("Insert ", "insert")) + vb.insert_tokens(text_to_virtual_buffer_tokens("a ", "a")) + vb.insert_tokens(text_to_virtual_buffer_tokens("new ", "new")) + vb.insert_tokens(text_to_virtual_buffer_tokens("sentence.", "sentence")) + + assertion( " Selecting a single character to the left and then selecting 'Insert a'...") + keys = vb.select_phrases(["insert", "a"]) + assertion( " Should not have the text 'Insert a ' selected", vb.caret_tracker.get_selection_text() != 'Insert a ') + assertion( " Should start the virtual selecting with 'Insert '", vb.virtual_selection[0].text == 'Insert ') + assertion( " Should not deselect the previous selection", keys[0] not in ["left", "right"]) + assertion( " Should go right 13 times to connect the end of 'a '", keys[0] == "left:13") + assertion( " Virtually selecting 'Insert ' until 'new ' after that...") + vb.select_phrases(["insert"]) + keys = vb.select_phrases(["new"], extend_selection=True) + assertion( " Should not have the text 'Insert a new ' selected", vb.caret_tracker.get_selection_text() != 'Insert a new ') + assertion( " Should end the virtual selecting with 'new '", vb.virtual_selection[-1].text == 'new ') + assertion( " Should not deselect the previous selection", keys[0] not in ["left", "right"]) + assertion( " Should go right 6 times to connect the end of 'Insert ' with 'new '", keys[0] == "right:6") + assertion( " Virtually selecting 'Insert ' and extend it until the end of the buffer...") + keys = vb.select_until_end("insert") + assertion( " Should not have the text 'Insert a new sentence.' selected", vb.caret_tracker.get_selection_text() != 'Insert a new sentence.') + assertion( " Should not deselect the previous selection", keys[0] not in ["left", "right"]) + assertion( " Should end the virtual selecting with 'sentence.'", vb.virtual_selection[-1].text == 'sentence.') + assertion( " Should go right 10 times to go from the end of 'Insert a new ' to the end of 'sentence.'", keys[0] == "right:9") + assertion( " Virtually selecting 'new ' and extend it until the end of the buffer...") + vb.select_phrases(["new"]) + keys = vb.select_until_end() + assertion( " Should not have the text 'new sentence.' selected", vb.caret_tracker.get_selection_text() != 'new sentence.') + assertion( " Should end the virtual selecting with 'sentence.'", vb.virtual_selection[-1].text == 'sentence.') + assertion( " Should not deselect the previous selection", keys[0] not in ["left", "right"]) + assertion( " Should go right 9 times to connect the end of 'new ' with 'sentence.'", keys[0] == "right:9") + assertion( " Virtually selecting 'Insert ' using 'inssert' and extend it until the end of the buffer...") + keys = vb.select_until_end("inssert") + assertion( " Should not have the text 'Insert a new sentence.' selected", vb.caret_tracker.get_selection_text() != 'Insert a new sentence.') + assertion( " Should not move at all since we have the same end selection", len(keys) == 0) + +suite = create_test_suite("Virtual selection range after words") +suite.add_test(test_virtual_select_single_word_and_extending) \ No newline at end of file diff --git a/virtual_buffer/buffer.py b/virtual_buffer/buffer.py index 13864df..4d291ad 100644 --- a/virtual_buffer/buffer.py +++ b/virtual_buffer/buffer.py @@ -20,7 +20,7 @@ class VirtualBuffer: tokens: List[VirtualBufferToken] = None caret_tracker: CaretTracker = None - + virtual_selection = None last_action_type = "insert" shift_selection = True @@ -39,12 +39,13 @@ def clear_tokens(self): self.set_tokens() def set_tokens(self, tokens: List[VirtualBufferToken] = None, move_cursor_to_end: bool = False): + self.virtual_selection = [] if tokens is None: self.caret_tracker.clear() self.tokens = [] else: self.tokens = tokens - + if move_cursor_to_end: self.reformat_tokens() self.caret_tracker.clear() @@ -507,16 +508,22 @@ def go_phrase(self, phrase: str, position: str = 'end', keep_selection: bool = F return self.navigate_to_token(token, -1 if position == 'end' else 0, keep_selection) else: return None - + def select_until_end(self, phrase: str = "") -> List[str]: keys = [] if len(self.tokens) > 0: if phrase != "": start_token = self.find_token_by_phrase(phrase, 0, True, False) if start_token: - keys.extend(self.navigate_to_token(start_token, 0, False)) - - keys.extend(self.select_token(self.tokens[-1], True)) + if self.shift_selection: + keys.extend(self.navigate_to_token(start_token, 0, False)) + keys.extend(self.select_token(self.tokens[-1], True)) + else: + keys.extend(self.navigate_to_token(self.tokens[-1])) + self.virtual_selection = [start_token, self.tokens[-1]] + elif not self.shift_selection and len(self.virtual_selection) > 0: + self.virtual_selection = [self.virtual_selection[0], self.tokens[-1]] + keys.extend(self.navigate_to_token(self.tokens[-1])) return keys @@ -529,7 +536,7 @@ def select_phrases(self, phrases: List[str], match_threshold: float = SELECTION_ if not should_go_to_next_occurrence: break - best_match_tokens, best_match = self.matcher.find_best_match_by_phrases(self, phrases, match_threshold, should_go_to_next_occurrence, selecting=True, for_correction=for_correction, verbose=verbose) + best_match_tokens, _ = self.matcher.find_best_match_by_phrases(self, phrases, match_threshold, should_go_to_next_occurrence, selecting=True, for_correction=for_correction, verbose=verbose) if best_match_tokens is not None and len(best_match_tokens) > 0: if verbose: print("!!! SELECTING !!!", best_match_tokens) @@ -542,22 +549,37 @@ def select_token_range(self, start_token: VirtualBufferToken, end_token: Virtual # When our end token isn't past the rightmost cursor # We do not want to extend to the end token # Because it would reset the existing selection to just our current query - if extend_selection and self.is_selecting(): + if extend_selection and (self.is_selecting() or len(self.virtual_selection) > 0): right_index = self.caret_tracker.get_rightmost_caret_index() if right_index[0] < end_token.line_index or \ ( right_index[0] == end_token.line_index and right_index[1] < end_token.index_from_line_end ): should_extend_right = False - if not extend_selection: - keys = self.navigate_to_token(start_token, 0) + self.virtual_selection = [] + keys = [] + if self.shift_selection: + if not extend_selection: + keys = self.navigate_to_token(start_token, 0) + else: + keys = self.select_token(start_token, extend_selection) + + if should_extend_right: + keys.extend( self.select_token(end_token, True)) else: - keys = self.select_token(start_token, extend_selection) + if len(self.virtual_selection) > 0: + if should_extend_right: + self.virtual_selection = [self.virtual_selection[0], end_token] + keys = self.navigate_to_token(end_token) + else: + self.virtual_selection = [start_token, self.virtual_selection[-1]] + else: + keys = self.navigate_to_token(end_token) + self.virtual_selection = [start_token, end_token] - if should_extend_right: - keys.extend( self.select_token(end_token, True)) return keys def select_token(self, token: VirtualBufferToken, extend_selection: bool = False) -> List[str]: + self.virtual_selection = [] if token: self.use_last_set_formatter = False keys = [] @@ -679,6 +701,22 @@ def get_next_text(self) -> str: next_text += self.tokens[index].text[right_token_index[1]:] return next_text + + def remove_virtual_selection(self, remove_key) -> List[str]: + keys = [] + if len(self.virtual_selection) > 0: + total_amount = 0 + if self.virtual_selection[0].line_index == self.virtual_selection[-1].line_index: + total_amount = self.virtual_selection[0].index_from_line_end - self.virtual_selection[-1].index_from_line_end + total_amount += len(self.virtual_selection[0].text) + else: + total_amount = self.virtual_selection[0].index_from_line_end + len(self.virtual_selection[0].text) + # TODO CALCULATE MULTILINE STUFF + + if total_amount: + keys = [remove_key + ":" + str(total_amount)] + + return keys def set_shift_selection(self, shift_selection: bool): self.caret_tracker.shift_selection = shift_selection diff --git a/virtual_buffer/manager.py b/virtual_buffer/manager.py index c2b9c8e..c721fc1 100644 --- a/virtual_buffer/manager.py +++ b/virtual_buffer/manager.py @@ -71,6 +71,9 @@ def track_insert(self, insert: str, phrase: str = None): def is_selecting(self) -> bool: return self.context.get_current_context().buffer.is_selecting() + + def is_virtual_selecting(self) -> bool: + return len(self.context.get_current_context().buffer.virtual_selection) > 0 def has_phrase(self, phrase: str) -> bool: return self.context.get_current_context().buffer.has_matching_phrase(phrase) @@ -238,6 +241,8 @@ def clear_keys(self, backwards = True) -> List[str]: if self.is_selecting(): return [settings.get("user.marithime_context_remove_letter")] + elif self.is_virtual_selecting(): + return vbm.clear_virtual_selection(settings.get("user.marithime_context_remove_letter")) if context.current is not None: if context.character_index == 0 and backwards and context.previous is not None: From aa13d9a32a8c8168579040a6a9b01253dc8371fd Mon Sep 17 00:00:00 2001 From: chaosparrot Date: Wed, 11 Dec 2024 20:23:58 +0100 Subject: [PATCH 05/24] Started improving the test cases for virtual selection removal --- phonetics/phonetics.py | 2 +- .../removing_virtual_selection.py | 98 ++++++++----------- virtual_buffer/buffer.py | 15 +-- virtual_buffer/caret_tracker.py | 2 +- 4 files changed, 51 insertions(+), 66 deletions(-) diff --git a/phonetics/phonetics.py b/phonetics/phonetics.py index 36b58ed..5c69834 100644 --- a/phonetics/phonetics.py +++ b/phonetics/phonetics.py @@ -1,7 +1,7 @@ from typing import List, Callable from .detection import detect_phonetic_fix_type, phonetic_normalize, levenshtein, syllable_count, EXACT_MATCH, HOMOPHONE_MATCH, PHONETIC_MATCH -class PhoneticSearch: +class PhoneticSearch: # The file content and callbacks, - Separated from talon bindings for easier testing homophone_content = "" diff --git a/tests/virtual_buffer/removing_virtual_selection.py b/tests/virtual_buffer/removing_virtual_selection.py index 03c22d4..934ee0b 100644 --- a/tests/virtual_buffer/removing_virtual_selection.py +++ b/tests/virtual_buffer/removing_virtual_selection.py @@ -2,9 +2,16 @@ from ...virtual_buffer.buffer import VirtualBuffer from ...virtual_buffer.indexer import text_to_virtual_buffer_tokens from ..test import create_test_suite +from ...phonetics.phonetics import PhoneticSearch +from ...virtual_buffer.matcher import VirtualBufferMatcher + +def get_filled_vb(with_multiline = False): + search = PhoneticSearch() + search.set_homophones("") + search.set_phonetic_similiarities("") -def get_filled_vb(): vb = VirtualBuffer() + vb.matcher = VirtualBufferMatcher(search) vb.shift_selection = False vb.insert_tokens(text_to_virtual_buffer_tokens("Insert ", "insert")) vb.insert_tokens(text_to_virtual_buffer_tokens("two ", "two")) @@ -16,7 +23,7 @@ def get_filled_vb(): vb.insert_tokens(text_to_virtual_buffer_tokens("so ", "so")) vb.insert_tokens(text_to_virtual_buffer_tokens("that ", "that")) vb.insert_tokens(text_to_virtual_buffer_tokens("the ", "the")) - vb.insert_tokens(text_to_virtual_buffer_tokens("previous ", "previous")) + vb.insert_tokens(text_to_virtual_buffer_tokens("previous" + ("\n" if with_multiline else " "), "previous")) vb.insert_tokens(text_to_virtual_buffer_tokens("sentence ", "sentence")) vb.insert_tokens(text_to_virtual_buffer_tokens("will ", "will")) vb.insert_tokens(text_to_virtual_buffer_tokens("become ", "become")) @@ -29,79 +36,54 @@ def get_filled_vb(): return vb - def test_remove_selecting_single_tokens(assertion): vb = get_filled_vb() assertion( " Virtually selecting a single token to the left and remove it...") vb.select_phrases(["anything"]) - assertion(vb.virtual_selection, False) - keys = vb.remove_virtual_selection("backspace") + assertion( vb.tokens, False ) + keys = vb.remove_virtual_selection() for key in keys: vb.apply_key(key) + assertion( vb.tokens, False ) + caret_index = vb.caret_tracker.get_caret_index() - assertion( " Expect caret line index to be 1", caret_index[0] == 1) + assertion( vb.tokens, False ) + assertion( " Expect caret line index to be 0", caret_index[0] == 0) assertion( " Expect caret character index to be the same as before (0)", caret_index[1] == 0) - assertion( " Expect 'backspace' to have been pressed 9 times to remove 'anything.'", keys[0] == "backspace:9") + assertion( " Expect 'backspace' to have been pressed 9 times to remove 'anything.'", keys[0] == "backspace:9") assertion( " Expect no selection detected", vb.is_selecting() == False) - assertion( " Expect final token to have changed", vb.tokens[-1].text == "be") - assertion( "'" + vb.tokens[-1].text + "'", False) + #assertion( " Expect final token to have changed", vb.tokens[-2].text == "be ") - #assertion( " Selecting three characters to the right and remove them...") - #vb.apply_key("shift:down right:3 shift:up delete") - #assertion( " Expect token length to stay the same (3)", len(vb.tokens) == 3) - #caret_index = vb.caret_tracker.get_caret_index() - #assertion( " Expect caret line index to be 1", caret_index[0] == 1) - #assertion( " Expect caret character index to be the three less than before (7)", caret_index[1] == 7) - #assertion( " Expect no selection detected", vb.is_selecting() == False) - #assertion( " Expect text to be merged", vb.tokens[1].text == "Insert a secondtence. \n") - #assertion( " Expect phrase to be merged", vb.tokens[1].phrase == "insert a secondtence") - #assertion( " Selecting right beyond the line break and remove the selection...") - #vb.apply_key("shift:down right:10 shift:up backspace") - #assertion( " Expect token length to be one less (2)", len(vb.tokens) == 2) - #caret_index = vb.caret_tracker.get_caret_index() - #assertion( " Expect caret line index to be 1", caret_index[0] == 1) - #assertion( " Expect caret character index to be the start of the next sentence (22)", caret_index[1] == 22) - #assertion( " Expect no selection detected", vb.is_selecting() == False) - #assertion( " Expect text to be merged", vb.tokens[1].text == "Insert a secondsert a third sentence.") - #assertion( " Expect phrase to be merged", vb.tokens[1].phrase == "insert a secondsert a third sentence") - #assertion( " Selecting left beyond the line break and remove the selection...") - #vb.apply_key("shift:down left:18 shift:up backspace") - #assertion( " Expect token length to be one less (1)", len(vb.tokens) == 1) - #caret_index = vb.caret_tracker.get_caret_index() - #assertion( " Expect caret line index to be 0", caret_index[0] == 0) - #assertion( " Expect caret character index to be the end of the sentence (22)", caret_index[1] == 22) + assertion( " Virtually selecting a single token to the left in the middle of the text and remove it...") + vb.select_phrases(["will"]) + keys = vb.remove_virtual_selection() + for key in keys: + vb.apply_key(key) + caret_index = vb.caret_tracker.get_caret_index() + assertion( caret_index, False ) + assertion( " Expect caret line index to be 0", caret_index[0] == 0) + assertion( " Expect caret character index to be different than before behind 'will'", caret_index[1] == 32) + #assertion( " Expect 'backspace' to have been pressed 5 times to remove 'will '", keys[0] == "backspace:5") #assertion( " Expect no selection detected", vb.is_selecting() == False) - #assertion( " Expect text to be merged", vb.tokens[0].text == "Insert a new sentencesert a third sentence.") - #assertion( " Expect phrase to be merged", vb.tokens[0].phrase == "insert a new sentencesert a third sentence") + #assertion( " Expect final token to not have changed", vb.tokens[-2].text == "be ") def test_remove_selecting_multiple_tokens_left(assertion): - vb = VirtualBuffer() - vb.insert_tokens(text_to_virtual_buffer_tokens("Suggest", "suggest")) - vb.insert_tokens(text_to_virtual_buffer_tokens(" create", "create")) - vb.insert_tokens(text_to_virtual_buffer_tokens(" delete", "delete")) - vb.insert_tokens(text_to_virtual_buffer_tokens(" insertion", "insertion")) - vb.caret_tracker.text_buffer = "Suggest create delete insert" + _CARET_MARKER + "ion" - - assertion( " Selecting characters until the left side of the token is reached and removing it...") - vb.apply_key("shift:down left:7 shift:up backspace") - assertion( " Expect token length to be one less (3)", len(vb.tokens) == 3) - caret_index = vb.caret_tracker.get_caret_index() - assertion( " Expect caret line index to be 0", caret_index[0] == 0) - assertion( " Expect caret character index to be the same as before (3)", caret_index[1] == 3) - assertion( " Expect no selection detected", vb.is_selecting() == False) - assertion( " Expect text to be merged", vb.tokens[-1].text == " deleteion") - assertion( " Expect phrase to be merged", vb.tokens[-1].phrase == "deleteion") - assertion( " Selecting characters until multiple tokens have been skipped over and removing it...") - vb.apply_key("shift:down left:14 shift:up backspace") - assertion( " Expect token length to be two less (1)", len(vb.tokens) == 1) + vb = get_filled_vb() + vb.select_phrases(["can", "be", "anything"]) + assertion( " Virtually selecting multiple tokens to the left and remove it...") + keys = vb.remove_virtual_selection() + for key in keys: + vb.apply_key(key) caret_index = vb.caret_tracker.get_caret_index() - assertion( " Expect caret line index to be 0", caret_index[0] == 0) - assertion( " Expect caret character index to be the same as before (3)", caret_index[1] == 3) + assertion( " Expect caret line index to be 0", caret_index[0] == 1) + assertion( " Expect caret character index to be the same as before (0)", caret_index[1] == 0) + assertion( " Expect 'backspace' to have been pressed 16 times to remove 'can be anything.'", keys[0] == "backspace:16") assertion( " Expect no selection detected", vb.is_selecting() == False) - assertion( " Expect text to be merged", vb.tokens[-1].text == "Suggestion") - assertion( " Expect phrase to be merged", vb.tokens[-1].phrase == "suggestion") + assertion( " Expect final token to have changed", vb.tokens[-1].text == "Words ") suite = create_test_suite("Removing virtually selected text") suite.add_test(test_remove_selecting_single_tokens) +#suite.add_test(test_remove_selecting_multiple_tokens_left) +# suite.add_test(test_remove_multiline_multiple_tokens) suite.run() \ No newline at end of file diff --git a/virtual_buffer/buffer.py b/virtual_buffer/buffer.py index 4d291ad..2d66a73 100644 --- a/virtual_buffer/buffer.py +++ b/virtual_buffer/buffer.py @@ -93,8 +93,11 @@ def insert_tokens(self, tokens: List[VirtualBufferToken]): self.insert_token(token, reformat_after_each_token or index == len(tokens) - 1) def insert_token(self, token_to_insert: VirtualBufferToken, reformat = True): - if self.is_selecting() and token_to_insert.text != "": - self.remove_selection() + if token_to_insert != "": + if self.is_selecting(): + self.remove_selection() + elif len(self.virtual_selection) > 0: + self.remove_virtual_selection(self.caret_tracker.clear_key) line_index, character_index = self.caret_tracker.get_caret_index() if line_index > -1 and character_index > -1: @@ -466,7 +469,7 @@ def apply_backspace(self, backspace_count = 0): self.tokens[previous_token_index].phrase = text_to_phrase(text) del self.tokens[previous_token_index + 1] - self.reformat_tokens() + self.reformat_tokens() self.caret_tracker.remove_before_caret(backspace_count) self.last_action_type = "remove" @@ -701,8 +704,8 @@ def get_next_text(self) -> str: next_text += self.tokens[index].text[right_token_index[1]:] return next_text - - def remove_virtual_selection(self, remove_key) -> List[str]: + + def remove_virtual_selection(self) -> List[str]: keys = [] if len(self.virtual_selection) > 0: total_amount = 0 @@ -714,7 +717,7 @@ def remove_virtual_selection(self, remove_key) -> List[str]: # TODO CALCULATE MULTILINE STUFF if total_amount: - keys = [remove_key + ":" + str(total_amount)] + keys = [self.caret_tracker.clear_key + ":" + str(total_amount)] return keys diff --git a/virtual_buffer/caret_tracker.py b/virtual_buffer/caret_tracker.py index 0532717..b4f4d5c 100644 --- a/virtual_buffer/caret_tracker.py +++ b/virtual_buffer/caret_tracker.py @@ -61,7 +61,7 @@ class CaretTracker: shift_selection = True multiline_supported = True - clear_key:str = "" + clear_key:str = "backspace" def __init__(self, system = platform.system()): self.system = system From 08be687abf32c6181772ac30ec8f4812e407f110 Mon Sep 17 00:00:00 2001 From: Kevin te Raa Date: Sat, 14 Dec 2024 11:11:28 +0100 Subject: [PATCH 06/24] Added more keybinding support for terminals Added task for text editors --- README.md | 13 ++++++++----- context.py | 7 ++++++- programs/terminal.talon | 6 ++++++ virtual_buffer/buffer.py | 11 ++++++++++- virtual_buffer/caret_tracker.py | 10 ++++++++-- virtual_buffer/input_context.py | 11 ++++++++++- virtual_buffer/input_context_manager.py | 9 +++++++++ virtual_buffer/macos_context.talon | 5 ++++- virtual_buffer/manager.py | 25 +++++++++++++++++++++++++ 9 files changed, 86 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 0689a08..1f393e6 100644 --- a/README.md +++ b/README.md @@ -49,10 +49,10 @@ If you want to highlight a specific set of tests, go inside of the specific test #### Documentation -[] - Create a usage and installation video +[] - Create a usage and installation video Videos seem to speak to people more than written text does, so accompany this with a video as well -[] - Extension possibilities for other packages +[] - Extension possibilities for other packages There's a ton of ways other packages can make use of our captures, settings and detections, but we will need to document them so they are easier to reuse as well. #### Dictation @@ -61,7 +61,7 @@ There's a ton of ways other packages can make use of our captures, settings and This boils down to matching `an` and `the` to be similar despite them being phonetically different. We can add something configurable so its easy for users to extend. -[] - Terminator words +[~] - Terminator words Right now the word `quill` is used, instead of the word `over`, to terminate a command. We probably want to extend this a bit, though we need to take into account that they need to not only be used in commands, but filtered out in other ways. [] - Making automatic fixing work @@ -99,12 +99,15 @@ We need to find a way to deal with word wrap, meaning things being on a single l [] - Improved MacOS support While there's programs where it nails the accessibility API pretty well, others just don't connect properly with finding the right focused element. We'll need to address these one by one unfortunately, because accessibility APIs are all over the place from program to program. -[] - Terminal support +[~] - Terminal support Right now terminals have a ton of issues because they do not allow for text selection, have painful accessibility support, and use a ton of custom key binds that don't correlate with other document builders. -[] - Single line detection / support +[~] - Single line detection / support Some fields, like name fields, do not have the possibility to add multiple lines. In that case, we probably want to either clear the buffer or simply not allow the enter to change the field. We should probably do a refresh if we are in an accessible field, and a clear in a terminal. +[] - Text editor support +This means we should be able to support vim, nano and other keybindings. This runs into the same issues as using a terminal does however, namely poor accessibility support and hard to detect when something is inside of a text editor in the first place. + [] - Accessiblity input tags We can detect a field type, like email, phone number etc from the accessibility APIs. That means we could expose that information for other packages to use as well, so you can say `Homer` to input `homer@odyssey.com` for example. diff --git a/context.py b/context.py index 63a1c78..1a09811 100644 --- a/context.py +++ b/context.py @@ -21,6 +21,7 @@ mod.setting("marithime_context_remove_letter", type=str, default="backspace", desc="The key combination to clear a single letter to the left of the caret") mod.setting("marithime_context_remove_forward_word", type=str, default="ctrl-delete", desc="The key combination to clear a word to the right of the caret") mod.setting("marithime_context_remove_forward_letter", type=str, default="delete", desc="The key combination to clear a single letter to the right of the caret") +mod.setting("marithime_context_remove_line", type=str, default="", desc="The key to remove an entire line from the current text") # Options - "" (default) - Whenever the confidence that it has lost the location in the file, re-index # - aggressive - After every marithime command that requires context, we re-index @@ -28,4 +29,8 @@ mod.setting("marithime_indexing_strategy", type=str, default="", desc="Determine what strategy we should use to begin reindexing documents") ctx.tags = ["user.marithime_available"] -ctx.lists["user.marithime_terminator_word"] = ["quill", "quilt"] \ No newline at end of file +ctx.lists["user.marithime_terminator_word"] = [ +# "over", + "quill", + "quilt" +] \ No newline at end of file diff --git a/programs/terminal.talon b/programs/terminal.talon index 4be5ea5..28f6459 100644 --- a/programs/terminal.talon +++ b/programs/terminal.talon @@ -1,7 +1,13 @@ +os: macos +os: windows +os: linux tag: terminal - settings(): user.marithime_indexing_strategy = "disabled" user.marithime_context_multiline_supported = 0 user.marithime_context_shift_selection = 0 + user.marithime_context_end_line_key = "ctrl-e" + user.marithime_context_start_line_key = "ctrl-a" + user.marithime_context_remove_line = "ctrl-u" user.marithime_context_clear_key = "enter" \ No newline at end of file diff --git a/virtual_buffer/buffer.py b/virtual_buffer/buffer.py index 2d66a73..63b050b 100644 --- a/virtual_buffer/buffer.py +++ b/virtual_buffer/buffer.py @@ -729,4 +729,13 @@ def set_multiline_supported(self, multiline_supported: bool): self.caret_tracker.multiline_supported = multiline_supported def set_clear_key(self, clear_key: str): - self.caret_tracker.clear_key = clear_key \ No newline at end of file + self.caret_tracker.clear_key = clear_key + + def set_end_of_line_key(self, end_of_line_key: str): + self.caret_tracker.end_of_line_key = end_of_line_key + + def set_start_of_line_key(self, start_of_line_key: str): + self.caret_tracker.start_of_line_key = start_of_line_key + + def set_clear_line_key(self, clear_line_key: str): + self.caret_tracker.clear_line_key = clear_line_key \ No newline at end of file diff --git a/virtual_buffer/caret_tracker.py b/virtual_buffer/caret_tracker.py index b4f4d5c..480e734 100644 --- a/virtual_buffer/caret_tracker.py +++ b/virtual_buffer/caret_tracker.py @@ -49,6 +49,9 @@ # 28 - From a coarse position, we can always move back to the end of the current line to have a consistent position # 29 - By default, when a selection is made, going to the left places the caret on the left end of the selection, and going to the right places it on the right # 30 - Certain programs do not allow selection, like terminals +# 31 - There are some standard Terminal key bindings - Like Ctrl+E ( end line ), Ctrl+A ( start line ) and Ctrl+U ( clear line ) that we can use for removal and navigation. +# 32 - There are multiple ways to get to the end of the line or the start of the line on windows terminals ( Ctrl+A and Home, Ctrl+E and End ) + class CaretTracker: system: str = "" is_macos: bool = False @@ -62,6 +65,9 @@ class CaretTracker: shift_selection = True multiline_supported = True clear_key:str = "backspace" + clear_line_key:str = "" + end_of_line_key:str = "end" + start_of_line_key:str = "home" def __init__(self, system = platform.system()): self.system = system @@ -687,13 +693,13 @@ def navigate_to_position(self, line_index: int, character_from_end: int, deselec # Move to line end to have a consistent line ending, as that seems to be consistent if current[1] == -1: - keys.append("end" if not self.is_macos else "cmd-right") + keys.append(self.end_of_line_key) current = (current[0], 0) # Move to the right character position if not character_from_end == current[1] and current[1] != -1: char_diff = current[1] - character_from_end - renewed_selection = selecting and not deselection and not self.shift_down + renewed_selection = selecting and not deselection and not self.shift_down and self.shift_selection keys.append( ("shift-" if renewed_selection else "" ) + ("left:" if char_diff < 0 else "right:") + str(abs(char_diff))) if current[0] == -1 or current[1] == -1: diff --git a/virtual_buffer/input_context.py b/virtual_buffer/input_context.py index 868c600..31bc827 100644 --- a/virtual_buffer/input_context.py +++ b/virtual_buffer/input_context.py @@ -66,4 +66,13 @@ def set_multiline_supported(self, multiline_supported: bool): self.buffer.set_multiline_supported(multiline_supported) def set_clear_key(self, clear_key: str): - self.buffer.set_clear_key(clear_key) \ No newline at end of file + self.buffer.set_clear_key(clear_key) + + def set_end_of_line_key(self, end_of_line_key: str): + self.get_current_context().set_end_of_line_key(end_of_line_key) + + def set_start_of_line_key(self, start_of_line_key: str): + self.get_current_context().set_start_of_line_key(start_of_line_key) + + def set_clear_line_key(self, clear_line_key: str): + self.get_current_context().set_clear_line_key(clear_line_key) \ No newline at end of file diff --git a/virtual_buffer/input_context_manager.py b/virtual_buffer/input_context_manager.py index 66e7e1f..5b37918 100644 --- a/virtual_buffer/input_context_manager.py +++ b/virtual_buffer/input_context_manager.py @@ -554,3 +554,12 @@ def set_multiline_supported(self, multiline_supported: bool): def set_clear_key(self, clear_key: str): self.get_current_context().set_clear_key(clear_key) + + def set_end_of_line_key(self, end_of_line_key: str): + self.get_current_context().set_end_of_line_key(end_of_line_key) + + def set_start_of_line_key(self, start_of_line_key: str): + self.get_current_context().set_start_of_line_key(start_of_line_key) + + def set_clear_line_key(self, clear_line_key: str): + self.get_current_context().set_clear_line_key(clear_line_key) \ No newline at end of file diff --git a/virtual_buffer/macos_context.talon b/virtual_buffer/macos_context.talon index 701fc79..ecec54a 100644 --- a/virtual_buffer/macos_context.talon +++ b/virtual_buffer/macos_context.talon @@ -5,4 +5,7 @@ settings(): user.marithime_context_remove_word = "cmd-backspace" user.marithime_context_remove_letter = "backspace" user.marithime_context_remove_forward_word = "cmd-delete" - user.marithime_context_remove_forward_letter = "delete" \ No newline at end of file + user.marithime_context_remove_forward_letter = "delete" + user.marithime_context_remove_line = "" + user.marithime_context_end_line_key = "cmd-right" + user.marithime_context_start_line_key = "cmd-left" \ No newline at end of file diff --git a/virtual_buffer/manager.py b/virtual_buffer/manager.py index c721fc1..05678ec 100644 --- a/virtual_buffer/manager.py +++ b/virtual_buffer/manager.py @@ -301,6 +301,15 @@ def set_multiline_supported(self, multiline_supported: bool): def set_clear_key(self, clear_key: str): self.context.set_clear_key(clear_key) + def set_clear_line_key(self, clear_line_key: str): + self.context.set_clear_line_key(clear_line_key) + + def set_start_of_line_key(self, start_of_line_key: str): + self.context.set_start_of_line_key(start_of_line_key) + + def set_end_of_line_key(self, end_of_line_key: str): + self.context.set_end_of_line_key(end_of_line_key) + def update_language(language: str): global mutator if not language: @@ -328,6 +337,18 @@ def update_clear_key(clear_key: str): global mutator mutator.set_clear_key(clear_key) +def update_remove_line_key(remove_line_key: str): + global mutator + mutator.set_remove_line_key(remove_line_key) + +def update_start_of_line_key(start_of_line_key: str): + global mutator + mutator.set_start_of_line_key(start_of_line_key) + +def update_end_of_line_key(end_of_line_key: str): + global mutator + mutator.set_end_of_line_key(end_of_line_key) + mutator = None def init_mutator(): global mutator @@ -340,6 +361,10 @@ def init_mutator(): settings.register("user.marithime_context_shift_selection", lambda shift_enabled: update_shift_selection(shift_enabled > 0)) settings.register("user.marithime_context_clear_key", lambda clear_key: update_clear_key(clear_key)) settings.register("user.marithime_context_multiline_supported", lambda supported: update_multiline_supported(supported > 0)) + settings.register("user.marithime_context_remove_line_key", lambda remove_line: update_remove_line_key(remove_line)) + settings.register("user.marithime_context_start_of_line_key", lambda start_of_line: update_start_of_line_key(start_of_line)) + settings.register("user.marithime_context_end_of_line_key", lambda end_of_line: update_end_of_line_key(end_of_line)) + update_language("") app.register("ready", init_mutator) From becea7f72663ce098384daeda9c9022f33c8bfaf Mon Sep 17 00:00:00 2001 From: Kevin te Raa Date: Sat, 14 Dec 2024 11:45:51 +0100 Subject: [PATCH 07/24] Fixed terminal clearing after pressing enter --- tests/virtual_buffer/terminal_enter_clear.py | 108 ++++++++++++++++++ ...ection.py => terminal_remove_selection.py} | 0 ...n_range.py => terminal_selection_range.py} | 0 virtual_buffer/buffer.py | 11 +- virtual_buffer/caret_tracker.py | 2 +- virtual_buffer/input_context.py | 6 +- 6 files changed, 121 insertions(+), 6 deletions(-) create mode 100644 tests/virtual_buffer/terminal_enter_clear.py rename tests/virtual_buffer/{removing_virtual_selection.py => terminal_remove_selection.py} (100%) rename tests/virtual_buffer/{virtual_selection_range.py => terminal_selection_range.py} (100%) diff --git a/tests/virtual_buffer/terminal_enter_clear.py b/tests/virtual_buffer/terminal_enter_clear.py new file mode 100644 index 0000000..46819bd --- /dev/null +++ b/tests/virtual_buffer/terminal_enter_clear.py @@ -0,0 +1,108 @@ +from ...virtual_buffer.caret_tracker import _CARET_MARKER +from ...virtual_buffer.buffer import VirtualBuffer +from ...virtual_buffer.indexer import text_to_virtual_buffer_tokens +from ..test import create_test_suite +from ...phonetics.phonetics import PhoneticSearch +from ...virtual_buffer.matcher import VirtualBufferMatcher + +def get_filled_vb(with_multiline = False): + search = PhoneticSearch() + search.set_homophones("") + search.set_phonetic_similiarities("") + + vb = VirtualBuffer() + vb.matcher = VirtualBufferMatcher(search) + vb.shift_selection = False + vb.set_clear_key("enter") + vb.insert_tokens(text_to_virtual_buffer_tokens("Insert ", "insert")) + vb.insert_tokens(text_to_virtual_buffer_tokens("two ", "two")) + vb.insert_tokens(text_to_virtual_buffer_tokens("words ", "words")) + vb.insert_tokens(text_to_virtual_buffer_tokens("into ", "into")) + vb.insert_tokens(text_to_virtual_buffer_tokens("a ", "a")) + vb.insert_tokens(text_to_virtual_buffer_tokens("previous ", "previous")) + vb.insert_tokens(text_to_virtual_buffer_tokens("sentence, ", "sentence")) + vb.insert_tokens(text_to_virtual_buffer_tokens("so ", "so")) + vb.insert_tokens(text_to_virtual_buffer_tokens("that ", "that")) + vb.insert_tokens(text_to_virtual_buffer_tokens("the ", "the")) + vb.insert_tokens(text_to_virtual_buffer_tokens("previous" + ("\n" if with_multiline else " "), "previous")) + vb.insert_tokens(text_to_virtual_buffer_tokens("sentence ", "sentence")) + vb.insert_tokens(text_to_virtual_buffer_tokens("will ", "will")) + vb.insert_tokens(text_to_virtual_buffer_tokens("become ", "become")) + vb.insert_tokens(text_to_virtual_buffer_tokens("longer ", "longer")) + vb.insert_tokens(text_to_virtual_buffer_tokens("too! ", "too")) + vb.insert_tokens(text_to_virtual_buffer_tokens("Words ", "words")) + vb.insert_tokens(text_to_virtual_buffer_tokens("can ", "can")) + vb.insert_tokens(text_to_virtual_buffer_tokens("be ", "be")) + vb.insert_tokens(text_to_virtual_buffer_tokens("anything.", "anything")) + + return vb + +def test_clear_single_line(assertion): + vb = get_filled_vb() + assertion( " Pressing 'Enter' in a terminal with a single line") + vb.apply_key("enter") + caret_index = vb.caret_tracker.get_caret_index() + assertion( " Expect caret line index to be unknown", caret_index[0] == -1) + assertion( " Expect no selection detected", vb.is_selecting() == False) + assertion( " Expect the tokens to be cleared", len(vb.tokens) == 0) + + assertion( " Inserting a new line after adding text in a terminal with a single line") + vb = get_filled_vb() + vb.insert_tokens(text_to_virtual_buffer_tokens("A ", "a")) + vb.insert_tokens(text_to_virtual_buffer_tokens("new ", "new")) + vb.insert_tokens(text_to_virtual_buffer_tokens("type ", "type")) + vb.insert_tokens(text_to_virtual_buffer_tokens("of ", "of")) + vb.insert_tokens(text_to_virtual_buffer_tokens("text\n", "text")) + caret_index = vb.caret_tracker.get_caret_index() + assertion( " Expect caret line index to be unknown", caret_index[0] == -1) + assertion( " Expect no selection detected", vb.is_selecting() == False) + assertion( " Expect the tokens to be cleared", len(vb.tokens) == 1) + + assertion( " Inserting a new line in the middle of adding text in a terminal with a single line") + vb = get_filled_vb() + vb.insert_tokens(text_to_virtual_buffer_tokens("A ", "a")) + vb.insert_tokens(text_to_virtual_buffer_tokens("new ", "new")) + vb.insert_tokens(text_to_virtual_buffer_tokens("type\n", "type")) + vb.insert_tokens(text_to_virtual_buffer_tokens("of ", "of")) + vb.insert_tokens(text_to_virtual_buffer_tokens("text.", "text")) + caret_index = vb.caret_tracker.get_caret_index() + assertion( " Expect caret line index to be 0", caret_index[0] == 0) + assertion( " Expect no selection detected", vb.is_selecting() == False) + assertion( " Expect two tokens to exist", len(vb.tokens) == 2) + +def test_clear_multi_line(assertion): + vb = get_filled_vb(True) + assertion( " Pressing 'Enter' in a terminal with multiple lines") + vb.apply_key("enter") + caret_index = vb.caret_tracker.get_caret_index() + assertion( " Expect caret line index to be unknown", caret_index[0] == -1) + assertion( " Expect no selection detected", vb.is_selecting() == False) + assertion( " Expect the tokens to be cleared", len(vb.tokens) == 0) + + assertion( " Inserting a new line after adding text in a terminal with multiple lines") + vb = get_filled_vb(True) + vb.insert_tokens(text_to_virtual_buffer_tokens("A ", "a")) + vb.insert_tokens(text_to_virtual_buffer_tokens("new ", "new")) + vb.insert_tokens(text_to_virtual_buffer_tokens("type ", "type")) + vb.insert_tokens(text_to_virtual_buffer_tokens("of ", "of")) + vb.insert_tokens(text_to_virtual_buffer_tokens("text\n", "text")) + caret_index = vb.caret_tracker.get_caret_index() + assertion( " Expect caret line index to be unknown", caret_index[0] == -1) + assertion( " Expect no selection detected", vb.is_selecting() == False) + assertion( " Expect the tokens to be cleared", len(vb.tokens) == 1) + + assertion( " Inserting a new line in the middle of adding text in a terminal with multiple lines") + vb = get_filled_vb(True) + vb.insert_tokens(text_to_virtual_buffer_tokens("A ", "a")) + vb.insert_tokens(text_to_virtual_buffer_tokens("new ", "new")) + vb.insert_tokens(text_to_virtual_buffer_tokens("type\n", "type")) + vb.insert_tokens(text_to_virtual_buffer_tokens("of ", "of")) + vb.insert_tokens(text_to_virtual_buffer_tokens("text.", "text")) + caret_index = vb.caret_tracker.get_caret_index() + assertion( " Expect caret line index to be 0", caret_index[0] == 0) + assertion( " Expect no selection detected", vb.is_selecting() == False) + assertion( " Expect two tokens to exist", len(vb.tokens) == 2) + +suite = create_test_suite("Removing the entire terminal context on 'Enter'") +suite.add_test(test_clear_single_line) +suite.add_test(test_clear_multi_line) \ No newline at end of file diff --git a/tests/virtual_buffer/removing_virtual_selection.py b/tests/virtual_buffer/terminal_remove_selection.py similarity index 100% rename from tests/virtual_buffer/removing_virtual_selection.py rename to tests/virtual_buffer/terminal_remove_selection.py diff --git a/tests/virtual_buffer/virtual_selection_range.py b/tests/virtual_buffer/terminal_selection_range.py similarity index 100% rename from tests/virtual_buffer/virtual_selection_range.py rename to tests/virtual_buffer/terminal_selection_range.py diff --git a/virtual_buffer/buffer.py b/virtual_buffer/buffer.py index 63b050b..33b07b6 100644 --- a/virtual_buffer/buffer.py +++ b/virtual_buffer/buffer.py @@ -97,7 +97,13 @@ def insert_token(self, token_to_insert: VirtualBufferToken, reformat = True): if self.is_selecting(): self.remove_selection() elif len(self.virtual_selection) > 0: - self.remove_virtual_selection(self.caret_tracker.clear_key) + self.remove_virtual_selection() + + # Edge case - If we clear the input on Enter press, + # We also need to clear if a newline is added through insertion + if self.caret_tracker.clear_key.lower() == "enter" and token_to_insert.text.endswith("\n"): + self.clear_tokens() + return line_index, character_index = self.caret_tracker.get_caret_index() if line_index > -1 and character_index > -1: @@ -717,7 +723,8 @@ def remove_virtual_selection(self) -> List[str]: # TODO CALCULATE MULTILINE STUFF if total_amount: - keys = [self.caret_tracker.clear_key + ":" + str(total_amount)] + # TODO - Are there any other single character removal methods? + keys = ["backspace:" + str(total_amount)] return keys diff --git a/virtual_buffer/caret_tracker.py b/virtual_buffer/caret_tracker.py index 480e734..166c017 100644 --- a/virtual_buffer/caret_tracker.py +++ b/virtual_buffer/caret_tracker.py @@ -64,7 +64,7 @@ class CaretTracker: shift_selection = True multiline_supported = True - clear_key:str = "backspace" + clear_key:str = "" clear_line_key:str = "" end_of_line_key:str = "end" start_of_line_key:str = "home" diff --git a/virtual_buffer/input_context.py b/virtual_buffer/input_context.py index 31bc827..6391206 100644 --- a/virtual_buffer/input_context.py +++ b/virtual_buffer/input_context.py @@ -69,10 +69,10 @@ def set_clear_key(self, clear_key: str): self.buffer.set_clear_key(clear_key) def set_end_of_line_key(self, end_of_line_key: str): - self.get_current_context().set_end_of_line_key(end_of_line_key) + self.buffer.set_end_of_line_key(end_of_line_key) def set_start_of_line_key(self, start_of_line_key: str): - self.get_current_context().set_start_of_line_key(start_of_line_key) + self.buffer.set_start_of_line_key(start_of_line_key) def set_clear_line_key(self, clear_line_key: str): - self.get_current_context().set_clear_line_key(clear_line_key) \ No newline at end of file + self.buffer.set_clear_line_key(clear_line_key) \ No newline at end of file From e344db9d62a4bd0a9b9bc20d5085bb2e91c1f838 Mon Sep 17 00:00:00 2001 From: Kevin te Raa Date: Sat, 14 Dec 2024 12:10:41 +0100 Subject: [PATCH 08/24] Added testcases for single line support without clear keys --- tests/virtual_buffer/single_line_support.py | 114 ++++++++++++++++++++ virtual_buffer/buffer.py | 7 ++ 2 files changed, 121 insertions(+) create mode 100644 tests/virtual_buffer/single_line_support.py diff --git a/tests/virtual_buffer/single_line_support.py b/tests/virtual_buffer/single_line_support.py new file mode 100644 index 0000000..d223be8 --- /dev/null +++ b/tests/virtual_buffer/single_line_support.py @@ -0,0 +1,114 @@ +from ...virtual_buffer.caret_tracker import _CARET_MARKER +from ...virtual_buffer.buffer import VirtualBuffer +from ...virtual_buffer.indexer import text_to_virtual_buffer_tokens +from ..test import create_test_suite +from ...phonetics.phonetics import PhoneticSearch +from ...virtual_buffer.matcher import VirtualBufferMatcher + +def get_filled_vb(): + search = PhoneticSearch() + search.set_homophones("") + search.set_phonetic_similiarities("") + + vb = VirtualBuffer() + vb.matcher = VirtualBufferMatcher(search) + vb.shift_selection = True + vb.caret_tracker.multiline_supported = False + vb.caret_tracker.clear_key = "" + vb.insert_tokens(text_to_virtual_buffer_tokens("Insert ", "insert")) + vb.insert_tokens(text_to_virtual_buffer_tokens("two ", "two")) + vb.insert_tokens(text_to_virtual_buffer_tokens("words ", "words")) + vb.insert_tokens(text_to_virtual_buffer_tokens("into ", "into")) + vb.insert_tokens(text_to_virtual_buffer_tokens("a ", "a")) + vb.insert_tokens(text_to_virtual_buffer_tokens("previous ", "previous")) + vb.insert_tokens(text_to_virtual_buffer_tokens("sentence, ", "sentence")) + vb.insert_tokens(text_to_virtual_buffer_tokens("so ", "so")) + vb.insert_tokens(text_to_virtual_buffer_tokens("that ", "that")) + vb.insert_tokens(text_to_virtual_buffer_tokens("the ", "the")) + vb.insert_tokens(text_to_virtual_buffer_tokens("previous ", "previous")) + vb.insert_tokens(text_to_virtual_buffer_tokens("sentence ", "sentence")) + vb.insert_tokens(text_to_virtual_buffer_tokens("will ", "will")) + vb.insert_tokens(text_to_virtual_buffer_tokens("become ", "become")) + vb.insert_tokens(text_to_virtual_buffer_tokens("longer ", "longer")) + vb.insert_tokens(text_to_virtual_buffer_tokens("too! ", "too")) + vb.insert_tokens(text_to_virtual_buffer_tokens("Words ", "words")) + vb.insert_tokens(text_to_virtual_buffer_tokens("can ", "can")) + vb.insert_tokens(text_to_virtual_buffer_tokens("be ", "be")) + vb.insert_tokens(text_to_virtual_buffer_tokens("anything.", "anything")) + + return vb + +def test_press_enter_key(assertion): + vb = get_filled_vb() + assertion( " Pressing 'Enter' in a single line field") + vb.apply_key("enter") + caret_index = vb.caret_tracker.get_caret_index() + assertion( " Expect caret line index to stay the same", caret_index[0] == 0) + assertion( " Expect no selection detected", vb.is_selecting() == False) + assertion( " Expect token length to be the same", len(vb.tokens) == 20) + + assertion( " Inserting tokens after pressing 'Enter' in a single line field") + vb.insert_tokens(text_to_virtual_buffer_tokens(" A ", "a")) + vb.insert_tokens(text_to_virtual_buffer_tokens("new ", "new")) + vb.insert_tokens(text_to_virtual_buffer_tokens("type ", "type")) + vb.insert_tokens(text_to_virtual_buffer_tokens("of ", "of")) + vb.insert_tokens(text_to_virtual_buffer_tokens("text", "text")) + caret_index = vb.caret_tracker.get_caret_index() + assertion( " Expect caret line index to stay the same", caret_index[0] == 0) + assertion( " Expect no selection detected", vb.is_selecting() == False) + assertion( " Expect token length to increase by 5", len(vb.tokens) == 25) + +def test_press_enter_on_select_key(assertion): + # Inserting newlines is different from enter presses in single line fields + # As it clears the input field and makes the final line the one used + # Rather than ignoring the new line + vb = get_filled_vb() + assertion( " Pressing 'Enter' in a single line field with text selection") + vb.select_phrases(["can", "be", "anything"]) + vb.apply_key("enter") + caret_index = vb.caret_tracker.get_caret_index() + assertion( " Expect caret line index to stay the same", caret_index[0] == 0) + assertion( " Expect selection detected", vb.is_selecting() == True) + assertion( " Expect token length to be the same", len(vb.tokens) == 20) + + assertion( " Inserting tokens with a selection after pressing 'Enter' in a single line field") + vb.insert_tokens(text_to_virtual_buffer_tokens(" A ", "a")) + vb.insert_tokens(text_to_virtual_buffer_tokens("new ", "new")) + vb.insert_tokens(text_to_virtual_buffer_tokens("type ", "type")) + vb.insert_tokens(text_to_virtual_buffer_tokens("of ", "of")) + vb.insert_tokens(text_to_virtual_buffer_tokens("text", "text")) + caret_index = vb.caret_tracker.get_caret_index() + assertion( " Expect caret line index to stay the same", caret_index[0] == 0) + assertion( " Expect no selection detected after insertions", vb.is_selecting() == False) + assertion( " Expect token length to increase by 2", len(vb.tokens) == 22) + +def test_newline_insert(assertion): + assertion( " Inserting a new line after adding text in a single line field") + vb = get_filled_vb() + vb.insert_tokens(text_to_virtual_buffer_tokens("A ", "a")) + vb.insert_tokens(text_to_virtual_buffer_tokens("new ", "new")) + vb.insert_tokens(text_to_virtual_buffer_tokens("type ", "type")) + vb.insert_tokens(text_to_virtual_buffer_tokens("of ", "of")) + vb.insert_tokens(text_to_virtual_buffer_tokens("text\n", "text")) + caret_index = vb.caret_tracker.get_caret_index() + assertion( " Expect caret line index to be unknown", caret_index[0] == -1) + assertion( " Expect no selection detected", vb.is_selecting() == False) + assertion( " Expect only one token available", len(vb.tokens) == 1) + + assertion( " Inserting a new line in the middle of adding text in a single line field") + vb = get_filled_vb() + vb.insert_tokens(text_to_virtual_buffer_tokens("A ", "a")) + vb.insert_tokens(text_to_virtual_buffer_tokens("new ", "new")) + vb.insert_tokens(text_to_virtual_buffer_tokens("type", "type")) + vb.insert_tokens(text_to_virtual_buffer_tokens("\n", "")) + vb.insert_tokens(text_to_virtual_buffer_tokens("of ", "of")) + vb.insert_tokens(text_to_virtual_buffer_tokens("text.", "text")) + caret_index = vb.caret_tracker.get_caret_index() + assertion( " Expect caret line index to stay the same", caret_index[0] == 0) + assertion( " Expect no selection detected", vb.is_selecting() == False) + assertion( " Expect only two tokens available", len(vb.tokens) == 2) + +suite = create_test_suite("Handling 'Enter' presses within fields that do not have multiline support") +suite.add_test(test_press_enter_key) +suite.add_test(test_press_enter_on_select_key) +suite.add_test(test_newline_insert) \ No newline at end of file diff --git a/virtual_buffer/buffer.py b/virtual_buffer/buffer.py index 33b07b6..350d4aa 100644 --- a/virtual_buffer/buffer.py +++ b/virtual_buffer/buffer.py @@ -105,6 +105,13 @@ def insert_token(self, token_to_insert: VirtualBufferToken, reformat = True): self.clear_tokens() return + # Inserting newlines is different from enter presses in single line fields + # As it clears the input field and makes the final line the one used + # Rather than ignoring the new line + if self.caret_tracker.multiline_supported == False and token_to_insert.text.endswith("\n"): + self.clear_tokens() + return + line_index, character_index = self.caret_tracker.get_caret_index() if line_index > -1 and character_index > -1: token_index, token_character_index = self.determine_token_index() From 9c5b4e73d7224d2c73c6539b9d3e2aa9ad44e15a Mon Sep 17 00:00:00 2001 From: Kevin te Raa Date: Sat, 14 Dec 2024 12:17:40 +0100 Subject: [PATCH 09/24] Fixed remove single line virtual selection after backspace press test cases --- .../terminal_remove_selection.py | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/tests/virtual_buffer/terminal_remove_selection.py b/tests/virtual_buffer/terminal_remove_selection.py index 934ee0b..7ebc034 100644 --- a/tests/virtual_buffer/terminal_remove_selection.py +++ b/tests/virtual_buffer/terminal_remove_selection.py @@ -32,7 +32,7 @@ def get_filled_vb(with_multiline = False): vb.insert_tokens(text_to_virtual_buffer_tokens("Words ", "words")) vb.insert_tokens(text_to_virtual_buffer_tokens("can ", "can")) vb.insert_tokens(text_to_virtual_buffer_tokens("be ", "be")) - vb.insert_tokens(text_to_virtual_buffer_tokens("anything.", "anything")) + vb.insert_tokens(text_to_virtual_buffer_tokens("anything.", "anything"), ) return vb @@ -41,19 +41,16 @@ def test_remove_selecting_single_tokens(assertion): assertion( " Virtually selecting a single token to the left and remove it...") vb.select_phrases(["anything"]) - assertion( vb.tokens, False ) keys = vb.remove_virtual_selection() for key in keys: vb.apply_key(key) - assertion( vb.tokens, False ) caret_index = vb.caret_tracker.get_caret_index() - assertion( vb.tokens, False ) assertion( " Expect caret line index to be 0", caret_index[0] == 0) assertion( " Expect caret character index to be the same as before (0)", caret_index[1] == 0) assertion( " Expect 'backspace' to have been pressed 9 times to remove 'anything.'", keys[0] == "backspace:9") assertion( " Expect no selection detected", vb.is_selecting() == False) - #assertion( " Expect final token to have changed", vb.tokens[-2].text == "be ") + assertion( " Expect final token to have changed", vb.tokens[-2].text == "be ") assertion( " Virtually selecting a single token to the left in the middle of the text and remove it...") vb.select_phrases(["will"]) @@ -61,12 +58,11 @@ def test_remove_selecting_single_tokens(assertion): for key in keys: vb.apply_key(key) caret_index = vb.caret_tracker.get_caret_index() - assertion( caret_index, False ) assertion( " Expect caret line index to be 0", caret_index[0] == 0) - assertion( " Expect caret character index to be different than before behind 'will'", caret_index[1] == 32) - #assertion( " Expect 'backspace' to have been pressed 5 times to remove 'will '", keys[0] == "backspace:5") - #assertion( " Expect no selection detected", vb.is_selecting() == False) - #assertion( " Expect final token to not have changed", vb.tokens[-2].text == "be ") + assertion( " Expect caret character index to be different than before behind 'will'", caret_index[1] == 41) + assertion( " Expect 'backspace' to have been pressed 5 times to remove 'will '", keys[0] == "backspace:5") + assertion( " Expect no selection detected", vb.is_selecting() == False) + assertion( " Expect final token to not have changed", vb.tokens[-2].text == "be ") def test_remove_selecting_multiple_tokens_left(assertion): vb = get_filled_vb() @@ -84,6 +80,6 @@ def test_remove_selecting_multiple_tokens_left(assertion): suite = create_test_suite("Removing virtually selected text") suite.add_test(test_remove_selecting_single_tokens) -#suite.add_test(test_remove_selecting_multiple_tokens_left) +# suite.add_test(test_remove_selecting_multiple_tokens_left) # suite.add_test(test_remove_multiline_multiple_tokens) suite.run() \ No newline at end of file From 0b132e01b1133d53f816bd0dea5bce4c6f03ad00 Mon Sep 17 00:00:00 2001 From: Kevin te Raa Date: Sat, 14 Dec 2024 13:45:05 +0100 Subject: [PATCH 10/24] Started adding home and end support for single line fields Broke some tests with coarse checking - needs fixing --- README.md | 6 +- tests/test.py | 6 +- tests/virtual_buffer/coarse_caret_tracking.py | 23 ++++-- .../virtual_buffer/exact_caret_navigation.py | 8 ++- tests/virtual_buffer/exact_caret_tracking.py | 9 ++- tests/virtual_buffer/single_line_support.py | 47 +++++++++++- virtual_buffer/caret_tracker.py | 72 ++++++++++++++++--- 7 files changed, 147 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index 1f393e6..fc8c6c8 100644 --- a/README.md +++ b/README.md @@ -101,9 +101,13 @@ While there's programs where it nails the accessibility API pretty well, others [~] - Terminal support Right now terminals have a ton of issues because they do not allow for text selection, have painful accessibility support, and use a ton of custom key binds that don't correlate with other document builders. +# TODO - SELECT +# TODO - CORRECT +# TODO - MULTILINE +# TODO - FIX COARSE TRACKING SINGLE AND MULTI LINE [~] - Single line detection / support -Some fields, like name fields, do not have the possibility to add multiple lines. In that case, we probably want to either clear the buffer or simply not allow the enter to change the field. We should probably do a refresh if we are in an accessible field, and a clear in a terminal. +Some fields, like name fields, do not have the possibility to add multiple lines. In that case, we probably want to either clear the buffer or simply not allow the enter to change the field. We should probably do a refresh if we are in an accessible field, and a clear in a terminal. [] - Text editor support This means we should be able to support vim, nano and other keybindings. This runs into the same issues as using a terminal does however, namely poor accessibility support and hard to detect when something is inside of a text editor in the first place. diff --git a/tests/test.py b/tests/test.py index 33d3b5c..6a374b7 100644 --- a/tests/test.py +++ b/tests/test.py @@ -89,8 +89,8 @@ def add_test_suite(self, test_suite: TestSuite): def run(self, verbosity = 0): # Only run tests if marithime testing is turned on - if settings.get("user.marithime_testing") == 0: - return + #if settings.get("user.marithime_testing") == 0: + # return start_time = time.perf_counter() total_results = [] suite_results = [] @@ -130,5 +130,5 @@ def run_tests(): def run_tests_ready(): test_suite_collection.ready = True test_suite_collection.run() - + app.register("ready", run_tests_ready) \ No newline at end of file diff --git a/tests/virtual_buffer/coarse_caret_tracking.py b/tests/virtual_buffer/coarse_caret_tracking.py index 576656c..923b8c3 100644 --- a/tests/virtual_buffer/coarse_caret_tracking.py +++ b/tests/virtual_buffer/coarse_caret_tracking.py @@ -10,6 +10,8 @@ def coarse_caret_tracker_splitting(assertion): vb.insert_tokens(text_to_virtual_buffer_tokens("Insert a third sentence.", "insert a third sentence")) vb.caret_tracker.system = "Windows" vb.caret_tracker.is_macos = False + vb.caret_tracker.end_of_line_key = "end" + vb.caret_tracker.start_of_line_key = "home" vb.caret_tracker.text_buffer = """Insert a new sentence. Insert a second """ + _CARET_MARKER + """sentence. Insert a third sentence.""" @@ -35,7 +37,9 @@ def coarse_caret_tracker_splitting(assertion): def coarse_caret_tracking_single_line(assertion): vb = VirtualBuffer() vb.caret_tracker.system = "Windows" - vb.caret_tracker.is_macos = False + vb.caret_tracker.is_macos = False + vb.caret_tracker.end_of_line_key = "end" + vb.caret_tracker.start_of_line_key = "home" vb.insert_tokens(text_to_virtual_buffer_tokens("Insert a new sentence. \n", "insert a new sentence")) vb.insert_tokens(text_to_virtual_buffer_tokens("Insert a second sentence. \n", "insert a second sentence")) vb.insert_tokens(text_to_virtual_buffer_tokens("Insert a third sentence.", "insert a third sentence")) @@ -86,7 +90,10 @@ def coarse_caret_tracking_single_line(assertion): def coarse_caret_tracking_multi_line(assertion): vb = VirtualBuffer() vb.caret_tracker.system = "Windows" - vb.caret_tracker.is_macos = False + vb.caret_tracker.is_macos = False + vb.caret_tracker.end_of_line_key = "end" + vb.caret_tracker.start_of_line_key = "home" + vb.insert_tokens(text_to_virtual_buffer_tokens("Insert a new sentence. \n", "insert a new sentence")) vb.insert_tokens(text_to_virtual_buffer_tokens("Insert a second sentence. \n", "insert a second sentence")) vb.insert_tokens(text_to_virtual_buffer_tokens("Insert a third sentence.", "insert a third sentence")) @@ -110,7 +117,9 @@ def coarse_caret_tracking_multi_line(assertion): def coarse_caret_tracking_single_line_macos(assertion): vb = VirtualBuffer() vb.caret_tracker.system = "Darwin" - vb.caret_tracker.is_macos = True + vb.caret_tracker.is_macos = True + vb.caret_tracker.end_of_line_key = "cmd-right" + vb.caret_tracker.start_of_line_key = "cmd-left" vb.insert_tokens(text_to_virtual_buffer_tokens("Insert a new sentence. \n", "insert a new sentence")) vb.insert_tokens(text_to_virtual_buffer_tokens("Insert a second sentence. \n", "insert a second sentence")) vb.insert_tokens(text_to_virtual_buffer_tokens("Insert a third sentence.", "insert a third sentence")) @@ -161,7 +170,9 @@ def coarse_caret_tracking_single_line_macos(assertion): def coarse_caret_tracking_multi_line_macos(assertion): vb = VirtualBuffer() vb.caret_tracker.system = "Darwin" - vb.caret_tracker.is_macos = True + vb.caret_tracker.is_macos = True + vb.caret_tracker.end_of_line_key = "cmd-right" + vb.caret_tracker.start_of_line_key = "cmd-left" vb.insert_tokens(text_to_virtual_buffer_tokens("Insert a new sentence. \n", "insert a new sentence")) vb.insert_tokens(text_to_virtual_buffer_tokens("Insert a second sentence. \n", "insert a second sentence")) vb.insert_tokens(text_to_virtual_buffer_tokens("Insert a third sentence.", "insert a third sentence")) @@ -176,9 +187,11 @@ def coarse_caret_tracking_multi_line_macos(assertion): vb.apply_key("down:2") caret_index = vb.caret_tracker.get_caret_index(True) assertion( " Expect caret line index to be 2", caret_index[0] == 2) - assertion( " Pressing CMD+LEFT to go to the start of the third sentence...") + assertion( " Pressing cmd-LEFT to go to the start of the third sentence...") vb.apply_key("cmd-left") caret_index = vb.caret_tracker.get_caret_index(True) + assertion( caret_index ) + assertion( vb.caret_tracker.text_buffer ) assertion( " Expect caret line index to be 2", caret_index[0] == 2) assertion( " Expect coarse character index to be before the word insert (24)", caret_index[1] == 24) diff --git a/tests/virtual_buffer/exact_caret_navigation.py b/tests/virtual_buffer/exact_caret_navigation.py index 7283344..92d698c 100644 --- a/tests/virtual_buffer/exact_caret_navigation.py +++ b/tests/virtual_buffer/exact_caret_navigation.py @@ -5,7 +5,9 @@ def test_single_line_exact_caret_navigation(assertion): vb = VirtualBuffer() vb.caret_tracker.system = "Windows" - vb.caret_tracker.is_macos = False + vb.caret_tracker.is_macos = False + vb.caret_tracker.end_of_line_key = "end" + vb.caret_tracker.start_of_line_key = "home" vb.insert_tokens(text_to_virtual_buffer_tokens("Insert ", "insert")) vb.insert_tokens(text_to_virtual_buffer_tokens("a ", "a")) vb.insert_tokens(text_to_virtual_buffer_tokens("new ", "new")) @@ -46,6 +48,8 @@ def test_multi_line_exact_caret_navigation(assertion): vb = VirtualBuffer() vb.caret_tracker.system = "Windows" vb.caret_tracker.is_macos = False + vb.caret_tracker.end_of_line_key = "end" + vb.caret_tracker.start_of_line_key = "home" vb.insert_tokens(text_to_virtual_buffer_tokens("Insert ", "insert")) vb.insert_tokens(text_to_virtual_buffer_tokens("a ", "a")) vb.insert_tokens(text_to_virtual_buffer_tokens("new ", "new")) @@ -105,6 +109,8 @@ def test_multi_line_exact_caret_navigation_macos(assertion): vb = VirtualBuffer() vb.caret_tracker.system = "Darwin" vb.caret_tracker.is_macos = True + vb.caret_tracker.end_of_line_key = "cmd-right" + vb.caret_tracker.start_of_line_key = "cmd-left" vb.insert_tokens(text_to_virtual_buffer_tokens("Insert ", "insert")) vb.insert_tokens(text_to_virtual_buffer_tokens("a ", "a")) vb.insert_tokens(text_to_virtual_buffer_tokens("new ", "new")) diff --git a/tests/virtual_buffer/exact_caret_tracking.py b/tests/virtual_buffer/exact_caret_tracking.py index 6d8262e..272377d 100644 --- a/tests/virtual_buffer/exact_caret_tracking.py +++ b/tests/virtual_buffer/exact_caret_tracking.py @@ -8,6 +8,8 @@ def exact_caret_tracking(assertion): vb = VirtualBuffer() vb.caret_tracker.is_macos = False vb.caret_tracker.system = "Windows" + vb.caret_tracker.end_of_line_key = "end" + vb.caret_tracker.start_of_line_key = "home" vb.insert_tokens(text_to_virtual_buffer_tokens("Insert a new sentence. \n", "insert a new sentence")) vb.insert_tokens(text_to_virtual_buffer_tokens("Insert a second sentence. \n", "insert a second sentence")) vb.insert_tokens(text_to_virtual_buffer_tokens("Insert a third sentence.", "insert a third sentence")) @@ -69,12 +71,15 @@ def exact_caret_tracking(assertion): vb.apply_key("left:10") assertion( " Moving one character to the left and pressing CMD-right ( on MacOS ) instead...") vb.caret_tracker.is_macos = True + vb.caret_tracker.end_of_line_key = "cmd-right" + vb.caret_tracker.start_of_line_key = "cmd-left" vb.caret_tracker.system = "Darwin" - vb.apply_key("super-right") + vb.apply_key("cmd-right") caret_index = vb.caret_tracker.get_caret_index() assertion( " Expect caret line index to be 1", caret_index[0] == 1) assertion( " Expect caret character index to be at the end of the second sentence", caret_index[1] == 0) suite = create_test_suite("Exact caret tracking with a filled virtual buffer ( Windows + MacOS )") -suite.add_test(exact_caret_tracking) \ No newline at end of file +suite.add_test(exact_caret_tracking) +suite.run() \ No newline at end of file diff --git a/tests/virtual_buffer/single_line_support.py b/tests/virtual_buffer/single_line_support.py index d223be8..ad85339 100644 --- a/tests/virtual_buffer/single_line_support.py +++ b/tests/virtual_buffer/single_line_support.py @@ -15,6 +15,8 @@ def get_filled_vb(): vb.shift_selection = True vb.caret_tracker.multiline_supported = False vb.caret_tracker.clear_key = "" + vb.caret_tracker.start_of_line_key = "home" + vb.caret_tracker.end_of_line_key = "end" vb.insert_tokens(text_to_virtual_buffer_tokens("Insert ", "insert")) vb.insert_tokens(text_to_virtual_buffer_tokens("two ", "two")) vb.insert_tokens(text_to_virtual_buffer_tokens("words ", "words")) @@ -108,7 +110,50 @@ def test_newline_insert(assertion): assertion( " Expect no selection detected", vb.is_selecting() == False) assertion( " Expect only two tokens available", len(vb.tokens) == 2) +def test_arrow_key_press(assertion): + vb = get_filled_vb() + assertion( " Pressing 'Down' in a single line field") + caret_index = vb.caret_tracker.get_caret_index() + + vb.apply_key("down") + caret_index = vb.caret_tracker.get_caret_index() + assertion( " Expect caret line index to be the same", caret_index[0] == 0) + assertion( " Expect caret character index to be coarse", caret_index[1] == -1) + assertion( " Expect no selection detected", vb.is_selecting() == False) + assertion( " Expect token length to be the same", len(vb.tokens) == 20) + + assertion( " Pressing 'Up' in a single line field") + vb.apply_key("up") + caret_index = vb.caret_tracker.get_caret_index() + assertion( " Expect caret line index to be the same", caret_index[0] == 0) + assertion( " Expect caret character index to be coarse", caret_index[1] == -1) + assertion( " Expect no selection detected", vb.is_selecting() == False) + assertion( " Expect token length to be the same", len(vb.tokens) == 20) + +def test_line_navigation_key_press(assertion): + vb = get_filled_vb() + assertion( " Pressing 'Home' in a single line field") + + vb.apply_key("home") + caret_index = vb.caret_tracker.get_caret_index() + assertion( caret_index, False ) + assertion( " Expect caret line index to be the same", caret_index[0] == 0) + assertion( " Expect caret character index to be at the start", caret_index[1] == 119) + assertion( " Expect no selection detected", vb.is_selecting() == False) + assertion( " Expect token length to be the same", len(vb.tokens) == 20) + + assertion( " Pressing 'End' in a single line field") + vb.apply_key("end") + caret_index = vb.caret_tracker.get_caret_index() + assertion( " Expect caret line index to be the same", caret_index[0] == 0) + assertion( " Expect caret character index to be at the end", caret_index[1] == 0) + assertion( " Expect no selection detected", vb.is_selecting() == False) + assertion( " Expect token length to be the same", len(vb.tokens) == 20) + suite = create_test_suite("Handling 'Enter' presses within fields that do not have multiline support") suite.add_test(test_press_enter_key) suite.add_test(test_press_enter_on_select_key) -suite.add_test(test_newline_insert) \ No newline at end of file +suite.add_test(test_newline_insert) +suite.add_test(test_arrow_key_press) +suite.add_test(test_line_navigation_key_press) +suite.run() \ No newline at end of file diff --git a/virtual_buffer/caret_tracker.py b/virtual_buffer/caret_tracker.py index 166c017..77c9a44 100644 --- a/virtual_buffer/caret_tracker.py +++ b/virtual_buffer/caret_tracker.py @@ -51,6 +51,7 @@ # 30 - Certain programs do not allow selection, like terminals # 31 - There are some standard Terminal key bindings - Like Ctrl+E ( end line ), Ctrl+A ( start line ) and Ctrl+U ( clear line ) that we can use for removal and navigation. # 32 - There are multiple ways to get to the end of the line or the start of the line on windows terminals ( Ctrl+A and Home, Ctrl+E and End ) +# 33 - UP and DOWN in terminals might change the context completely, so we need to remove the context we have in that case class CaretTracker: system: str = "" @@ -189,27 +190,55 @@ def apply_key(self, key: str) -> bool: self.track_caret_right(right_movements) key_used = True self.last_caret_movement = "right" - elif ( not self.is_macos and "end" in key ) or ( self.is_macos and ("cmd" in key or "super" in key) and "right" in key ): + + # TODO PROPER SPLIT UP WITH OTHER MODIFIERS + elif self.end_of_line_key in key: self.mark_caret_to_end_of_line() key_used = True self.last_caret_movement = "right" - elif ( not self.is_macos and "home" in key ) or ( self.is_macos and ("cmd" in key or "super" in key) and "left" in key ): - self.mark_line_as_coarse() + + # TODO PROPER SPLIT UP WITH OTHER MODIFIERS + elif self.start_of_line_key in key: + + # In VSCODE - Moving to the start of the line depends on the whitespace + if self.multiline_supported: + self.mark_line_as_coarse() + else: + self.mark_caret_to_start_of_line() + key_used = True self.last_caret_movement = "left" elif "up" in key and ( not self.is_macos or ("cmd" not in key and "super" not in key)): up_movements = 1 if len(key_modifier) >= 1 and key_modifier[-1].isnumeric(): up_movements = int(key_modifier[-1]) - for _ in range(up_movements): - self.mark_above_line_as_coarse() + + # For single line input fields, going up has the behavior of either + # 1 - Moving to the start of the line + # 2 - Ignoring the character altogether + # 3 - Changing the line completely ( history of terminals ) + # So at least mark the line as coarse in that case + if self.multiline_supported == False: + self.mark_line_as_coarse() + else: + for _ in range(up_movements): + self.mark_above_line_as_coarse() key_used = True elif "down" in key and ( not self.is_macos or ("cmd" not in key and "super" not in key)): down_movements = 1 if len(key_modifier) >= 1 and key_modifier[-1].isnumeric(): down_movements = int(key_modifier[-1]) - for _ in range(down_movements): - self.mark_below_line_as_coarse() + + # For single line input fields, going down has the behavior of either + # 1 - Moving to the end of the line + # 2 - Ignoring the character altogether + # 3 - Changing the line completely ( history of terminals ) + # So at least mark the line as coarse in that case + if self.multiline_supported == False: + self.mark_line_as_coarse() + else: + for _ in range(down_movements): + self.mark_below_line_as_coarse() key_used = True return key_used @@ -230,7 +259,28 @@ def mark_caret_to_end_of_line(self): before_caret_text = "\n".join(before_caret) after_caret_text = "\n".join(after_caret) - if len(after_caret) > 0: + if len(after_caret) > 0 and len(lines) > 1: + after_caret_text = "\n" + after_caret_text + self.set_buffer(before_caret_text, after_caret_text) + + def mark_caret_to_start_of_line(self): + lines = self.text_buffer.splitlines() + before_caret = [] + after_caret = [] + before_caret_marker = True + for line in lines: + if _CARET_MARKER in line or _COARSE_MARKER in line: + after_caret.append(line.replace(_CARET_MARKER, "").replace(_COARSE_MARKER, "")) + before_caret_marker = False + else: + if before_caret_marker: + before_caret.append(line) + else: + after_caret.append(line) + + before_caret_text = "\n".join(before_caret) + after_caret_text = "\n".join(after_caret) + if len(after_caret) > 0 and len(lines) > 1: after_caret_text = "\n" + after_caret_text self.set_buffer(before_caret_text, after_caret_text) @@ -251,7 +301,7 @@ def mark_line_as_coarse(self, difference_from_line: int = 0): # If the line falls outside of the known line count, we have lost the caret position entirely # And must clear the input entirely - line_out_of_known_bounds = line_with_caret + difference_from_line < 0 or line_with_caret + difference_from_line > len(lines) + line_out_of_known_bounds = self.multiline_supported and ( line_with_caret + difference_from_line < 0 or line_with_caret + difference_from_line > len(lines) ) if line_out_of_known_bounds: before_caret = [] after_caret = [] @@ -275,7 +325,7 @@ def mark_line_as_coarse(self, difference_from_line: int = 0): before_caret_text = "\n".join(before_caret) after_caret_text = "\n".join(after_caret) - if len(after_caret) > 0: + if len(after_caret) > 0 and len(lines) > 0: if difference_from_line == 0: before_caret_text += "\n" elif char_index == 0: @@ -585,7 +635,7 @@ def get_caret_index(self, check_coarse = False) -> (int, int): for index, line in enumerate(lines): if _COARSE_MARKER in line: line_index = index - character_index = len(line.split(_COARSE_MARKER)[1]) if check_coarse else -1 + character_index = len(line.split(_COARSE_MARKER)[0]) if check_coarse else -1 break return line_index, character_index From 081742b0e05f9abef54df050cdb60cab9ad7ba6d Mon Sep 17 00:00:00 2001 From: Kevin te Raa Date: Sun, 15 Dec 2024 17:02:12 +0100 Subject: [PATCH 11/24] Made progress on multiline support for virtual selection Temporarily putting commits in a separate branch to check where faulty tests come from --- README.md | 1 + tests/virtual_buffer/selection_range.py | 3 +- .../terminal_remove_selection.py | 66 +++++++++++++++++-- virtual_buffer/buffer.py | 11 +++- virtual_buffer/caret_tracker.py | 39 +++++++++++ virtual_buffer/manager.py | 1 + 6 files changed, 113 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index fc8c6c8..87e34e0 100644 --- a/README.md +++ b/README.md @@ -105,6 +105,7 @@ Right now terminals have a ton of issues because they do not allow for text sele # TODO - CORRECT # TODO - MULTILINE # TODO - FIX COARSE TRACKING SINGLE AND MULTI LINE +# TODO - NO BACKSPACE WRAP MODE [~] - Single line detection / support Some fields, like name fields, do not have the possibility to add multiple lines. In that case, we probably want to either clear the buffer or simply not allow the enter to change the field. We should probably do a refresh if we are in an accessible field, and a clear in a terminal. diff --git a/tests/virtual_buffer/selection_range.py b/tests/virtual_buffer/selection_range.py index a6c50ed..2d1a249 100644 --- a/tests/virtual_buffer/selection_range.py +++ b/tests/virtual_buffer/selection_range.py @@ -116,4 +116,5 @@ def test_select_phrase_inside_selection_with_duplicates(assertion): suite = create_test_suite("Selection range after words") suite.add_test(test_select_single_word_and_extending) suite.add_test(test_selecting_multiple_phrases_with_duplicates) -suite.add_test(test_select_phrase_inside_selection_with_duplicates) \ No newline at end of file +suite.add_test(test_select_phrase_inside_selection_with_duplicates) +suite.run() \ No newline at end of file diff --git a/tests/virtual_buffer/terminal_remove_selection.py b/tests/virtual_buffer/terminal_remove_selection.py index 7ebc034..d284aee 100644 --- a/tests/virtual_buffer/terminal_remove_selection.py +++ b/tests/virtual_buffer/terminal_remove_selection.py @@ -72,14 +72,72 @@ def test_remove_selecting_multiple_tokens_left(assertion): for key in keys: vb.apply_key(key) caret_index = vb.caret_tracker.get_caret_index() - assertion( " Expect caret line index to be 0", caret_index[0] == 1) + assertion( " Expect caret line index to be 0", caret_index[0] == 0) assertion( " Expect caret character index to be the same as before (0)", caret_index[1] == 0) assertion( " Expect 'backspace' to have been pressed 16 times to remove 'can be anything.'", keys[0] == "backspace:16") assertion( " Expect no selection detected", vb.is_selecting() == False) - assertion( " Expect final token to have changed", vb.tokens[-1].text == "Words ") + assertion( " Expect final token to have changed", vb.tokens[-2].text == "Words ") + +def test_remove_multiline_multiple_tokens(assertion): + vb = get_filled_vb(True) + + assertion( " Virtually selecting a single token to the left and remove it...") + vb.select_phrases(["anything"]) + keys = vb.remove_virtual_selection() + for key in keys: + vb.apply_key(key) + + caret_index = vb.caret_tracker.get_caret_index() + assertion( " Expect caret line index to be 1", caret_index[0] == 1) + assertion( " Expect caret character index to be the same as before (0)", caret_index[1] == 0) + assertion( " Expect 'backspace' to have been pressed 9 times to remove 'anything.'", keys[0] == "backspace:9") + assertion( " Expect no selection detected", vb.is_selecting() == False) + assertion( " Expect final token to have changed", vb.tokens[-2].text == "be ") + + assertion( " Virtually selecting a single token to the left in the middle of the text and remove it...") + vb.select_phrases(["become"]) + keys = vb.remove_virtual_selection() + for key in keys: + vb.apply_key(key) + caret_index = vb.caret_tracker.get_caret_index() + assertion( " Expect caret line index to be 1", caret_index[0] == 1) + assertion( " Expect caret character index to be different than before behind 'become'", caret_index[1] == 34) + assertion( " Expect 'backspace' to have been pressed 7 times to remove 'become '", keys[0] == "backspace:7") + assertion( " Expect no selection detected", vb.is_selecting() == False) + assertion( " Expect final token to not have changed", vb.tokens[-2].text == "be ") + + assertion( " Virtually selecting a single token on the first line to the left in the middle of the text and remove it...") + vb.select_phrases(["into"]) + keys = vb.remove_virtual_selection() + for key in keys: + vb.apply_key(key) + caret_index = vb.caret_tracker.get_caret_index() + assertion( " Expect caret line index to be 0", caret_index[0] == 0) + assertion( " Expect caret character index to be different than before behind 'into'", caret_index[1] == 41) + assertion( " Expect 'backspace' to have been pressed 5 times to remove 'into '", keys[0] == "backspace:5") + assertion( " Expect no selection detected", vb.is_selecting() == False) + assertion( " Expect final token to not have changed", vb.tokens[-2].text == "be ") + +def test_remove_linebreak_multiline_tokens(assertion): + vb = get_filled_vb(True) + + assertion( " Virtually selecting multiple tokens across the line boundary in the middle of the text and remove it...") + vb.select_phrases(["the", "previous", "sentence"]) + keys = vb.remove_virtual_selection() + for key in keys: + vb.apply_key(key) + caret_index = vb.caret_tracker.get_caret_index() + assertion( keys[0], False ) + assertion( " Expect caret line index to be 1", caret_index[0] == 0) + assertion( " Expect caret character index to be different than before behind 'previous sentence'", caret_index[1] == 46) + assertion( " Expect 'backspace' to have been pressed 18 times to remove 'previous sentence '", keys[0] == "backspace:18") + assertion( " Expect no selection detected", vb.is_selecting() == False) + assertion( " Expect final token to not have changed", vb.tokens[-2].text == "be ") + assertion( " But the line index of the final token to have changed", vb.tokens[-2].line_index == 0) suite = create_test_suite("Removing virtually selected text") suite.add_test(test_remove_selecting_single_tokens) -# suite.add_test(test_remove_selecting_multiple_tokens_left) -# suite.add_test(test_remove_multiline_multiple_tokens) +suite.add_test(test_remove_selecting_multiple_tokens_left) +suite.add_test(test_remove_multiline_multiple_tokens) +suite.add_test(test_remove_linebreak_multiline_tokens) suite.run() \ No newline at end of file diff --git a/virtual_buffer/buffer.py b/virtual_buffer/buffer.py index 350d4aa..bfc4691 100644 --- a/virtual_buffer/buffer.py +++ b/virtual_buffer/buffer.py @@ -726,11 +726,16 @@ def remove_virtual_selection(self) -> List[str]: total_amount = self.virtual_selection[0].index_from_line_end - self.virtual_selection[-1].index_from_line_end total_amount += len(self.virtual_selection[0].text) else: - total_amount = self.virtual_selection[0].index_from_line_end + len(self.virtual_selection[0].text) - # TODO CALCULATE MULTILINE STUFF + text_buffer = self.caret_tracker.get_text_between_tokens( + (self.virtual_selection[0].line_index, self.virtual_selection[0].index_from_line_end - len(self.virtual_selection[0].text)), + (self.virtual_selection[1].line_index, self.virtual_selection[1].index_from_line_end), + False + ) + print( "BUFFER!", text_buffer ) + + total_amount = len(text_buffer) if total_amount: - # TODO - Are there any other single character removal methods? keys = ["backspace:" + str(total_amount)] return keys diff --git a/virtual_buffer/caret_tracker.py b/virtual_buffer/caret_tracker.py index 77c9a44..a03624d 100644 --- a/virtual_buffer/caret_tracker.py +++ b/virtual_buffer/caret_tracker.py @@ -52,6 +52,7 @@ # 31 - There are some standard Terminal key bindings - Like Ctrl+E ( end line ), Ctrl+A ( start line ) and Ctrl+U ( clear line ) that we can use for removal and navigation. # 32 - There are multiple ways to get to the end of the line or the start of the line on windows terminals ( Ctrl+A and Home, Ctrl+E and End ) # 33 - UP and DOWN in terminals might change the context completely, so we need to remove the context we have in that case +# 34 - Text editors do not have wrapping ( pressing backspace does not remove newlines and move to the next line ) class CaretTracker: system: str = "" @@ -678,6 +679,44 @@ def get_selection_text(self) -> str: selection_lines.append( replaced_line[:len(replaced_line) - right[1]] ) return "\n".join(selection_lines) + def get_text_between_tokens(self, left_index = (-1, -1), right_index = (-1, -1), from_end = False) -> str: + cursor_lines = [] + if left_index != (-1, -1) and right_index != (-1, -1): + lines = self.text_buffer.splitlines() + left_index_line = left_index[0] + left_index_character_index = left_index[1] + right_index_line = right_index[0] + right_index_character_index = right_index[1] + + # Normalize the ends to be the same as the character index on the line + if from_end: + if left_index_line < len(lines): + replaced_line = lines[left_index_line].replace(_CARET_MARKER, '').replace(_COARSE_MARKER, '') + left_index_character_index = len(replaced_line) - left_index_character_index + else: + return "" + + if right_index_line < len(lines): + replaced_line = lines[right_index_line].replace(_CARET_MARKER, '').replace(_COARSE_MARKER, '') + right_index_character_index = len(replaced_line) - right_index_character_index + else: + return "" + + left = (left_index_line, left_index_character_index) + right = (right_index_line, right_index_character_index) + + for line_index, line in enumerate(lines): + replaced_line = line.replace(_CARET_MARKER, '').replace(_COARSE_MARKER, '') + if line_index == left[0] and line_index == right[0]: + cursor_lines.append(replaced_line[len(replaced_line) - left[1]:len(replaced_line) - right[1]]) + elif line_index == left[0] and line_index < right[0]: + cursor_lines.append( replaced_line[len(replaced_line) - left[1]] ) + elif line_index > left[0] and line_index < right[0]: + cursor_lines.append( replaced_line ) + elif line_index > left[0] and line_index == right[0]: + cursor_lines.append( replaced_line[:len(replaced_line) - right[1]] ) + return "\n".join(cursor_lines) + def navigate_to_position(self, line_index: int, character_from_end: int, deselection: bool = True, selecting: bool = None) -> List[str]: current = self.get_caret_index() diff --git a/virtual_buffer/manager.py b/virtual_buffer/manager.py index 05678ec..421c359 100644 --- a/virtual_buffer/manager.py +++ b/virtual_buffer/manager.py @@ -129,6 +129,7 @@ def transform_insert(self, insert: str, enable_self_repair: bool = False) -> (st repair_keys = [] # Allow the user to do self repair in speech + # TODO - TRACK VIRTUAL SELECTION CORRECTION correction_insertion = self.is_selecting() previous_selection = "" if not correction_insertion else vbm.caret_tracker.get_selection_text() current_insertion = "" From 6d45c531e861202b776e7380875cdfed03ba8e2f Mon Sep 17 00:00:00 2001 From: Kevin te Raa Date: Sun, 15 Dec 2024 17:38:38 +0100 Subject: [PATCH 12/24] Fixed multiline virtual selection removal Still need to fix coarse tracking again --- README.md | 1 - tests/virtual_buffer/selection_range.py | 8 ++-- tests/virtual_buffer/single_line_support.py | 2 +- .../terminal_remove_selection.py | 6 +-- virtual_buffer/buffer.py | 12 +++--- virtual_buffer/caret_tracker.py | 41 +++---------------- 6 files changed, 21 insertions(+), 49 deletions(-) diff --git a/README.md b/README.md index 87e34e0..a55c538 100644 --- a/README.md +++ b/README.md @@ -103,7 +103,6 @@ While there's programs where it nails the accessibility API pretty well, others Right now terminals have a ton of issues because they do not allow for text selection, have painful accessibility support, and use a ton of custom key binds that don't correlate with other document builders. # TODO - SELECT # TODO - CORRECT -# TODO - MULTILINE # TODO - FIX COARSE TRACKING SINGLE AND MULTI LINE # TODO - NO BACKSPACE WRAP MODE diff --git a/tests/virtual_buffer/selection_range.py b/tests/virtual_buffer/selection_range.py index 2d1a249..e215e64 100644 --- a/tests/virtual_buffer/selection_range.py +++ b/tests/virtual_buffer/selection_range.py @@ -4,6 +4,7 @@ def test_select_single_word_and_extending(assertion): vb = VirtualBuffer() + vb.shift_selection = True vb.insert_tokens(text_to_virtual_buffer_tokens("Insert ", "insert")) vb.insert_tokens(text_to_virtual_buffer_tokens("a ", "a")) vb.insert_tokens(text_to_virtual_buffer_tokens("new ", "new")) @@ -18,7 +19,7 @@ def test_select_single_word_and_extending(assertion): assertion( " Should go right 2 times to connect the end of 'Insert ' with 'a '", keys[0] == "shift-right:2") assertion( " Deselecting, and then selecting 'Insert ' until 'new ' after that...") vb.select_phrases(["insert"]) - keys = vb.select_phrases(["new"], extend_selection=True) + keys = vb.select_phrases(["new"], extend_selection=True) assertion( " Should have the text 'Insert a new ' selected", vb.caret_tracker.get_selection_text() == 'Insert a new ') assertion( " Should not deselect the previous selection", keys[0] not in ["left", "right"]) assertion( " Should go right 6 times to connect the end of 'Insert ' with 'new '", keys[0] == "shift-right:6") @@ -41,6 +42,7 @@ def test_select_single_word_and_extending(assertion): def test_selecting_multiple_phrases_with_duplicates(assertion): vb = VirtualBuffer() + vb.shift_selection = True vb.insert_tokens(text_to_virtual_buffer_tokens("Insert ", "insert")) vb.insert_tokens(text_to_virtual_buffer_tokens("a ", "a")) vb.insert_tokens(text_to_virtual_buffer_tokens("new ", "new")) @@ -93,6 +95,7 @@ def test_selecting_multiple_phrases_with_duplicates(assertion): def test_select_phrase_inside_selection_with_duplicates(assertion): vb = VirtualBuffer() + vb.shift_selection = True vb.insert_tokens(text_to_virtual_buffer_tokens("Insert ", "insert")) vb.insert_tokens(text_to_virtual_buffer_tokens("a ", "a")) vb.insert_tokens(text_to_virtual_buffer_tokens("new ", "new")) @@ -116,5 +119,4 @@ def test_select_phrase_inside_selection_with_duplicates(assertion): suite = create_test_suite("Selection range after words") suite.add_test(test_select_single_word_and_extending) suite.add_test(test_selecting_multiple_phrases_with_duplicates) -suite.add_test(test_select_phrase_inside_selection_with_duplicates) -suite.run() \ No newline at end of file +suite.add_test(test_select_phrase_inside_selection_with_duplicates) \ No newline at end of file diff --git a/tests/virtual_buffer/single_line_support.py b/tests/virtual_buffer/single_line_support.py index ad85339..5c8977c 100644 --- a/tests/virtual_buffer/single_line_support.py +++ b/tests/virtual_buffer/single_line_support.py @@ -156,4 +156,4 @@ def test_line_navigation_key_press(assertion): suite.add_test(test_newline_insert) suite.add_test(test_arrow_key_press) suite.add_test(test_line_navigation_key_press) -suite.run() \ No newline at end of file +suite.run() \ No newline at end of file diff --git a/tests/virtual_buffer/terminal_remove_selection.py b/tests/virtual_buffer/terminal_remove_selection.py index d284aee..27591de 100644 --- a/tests/virtual_buffer/terminal_remove_selection.py +++ b/tests/virtual_buffer/terminal_remove_selection.py @@ -127,10 +127,9 @@ def test_remove_linebreak_multiline_tokens(assertion): for key in keys: vb.apply_key(key) caret_index = vb.caret_tracker.get_caret_index() - assertion( keys[0], False ) assertion( " Expect caret line index to be 1", caret_index[0] == 0) assertion( " Expect caret character index to be different than before behind 'previous sentence'", caret_index[1] == 46) - assertion( " Expect 'backspace' to have been pressed 18 times to remove 'previous sentence '", keys[0] == "backspace:18") + assertion( " Expect 'backspace' to have been pressed 18 times to remove 'previous sentence '", keys[0] == "backspace:23") assertion( " Expect no selection detected", vb.is_selecting() == False) assertion( " Expect final token to not have changed", vb.tokens[-2].text == "be ") assertion( " But the line index of the final token to have changed", vb.tokens[-2].line_index == 0) @@ -139,5 +138,4 @@ def test_remove_linebreak_multiline_tokens(assertion): suite.add_test(test_remove_selecting_single_tokens) suite.add_test(test_remove_selecting_multiple_tokens_left) suite.add_test(test_remove_multiline_multiple_tokens) -suite.add_test(test_remove_linebreak_multiline_tokens) -suite.run() \ No newline at end of file +suite.add_test(test_remove_linebreak_multiline_tokens) \ No newline at end of file diff --git a/virtual_buffer/buffer.py b/virtual_buffer/buffer.py index bfc4691..7eecf01 100644 --- a/virtual_buffer/buffer.py +++ b/virtual_buffer/buffer.py @@ -541,6 +541,9 @@ def select_until_end(self, phrase: str = "") -> List[str]: self.virtual_selection = [self.virtual_selection[0], self.tokens[-1]] keys.extend(self.navigate_to_token(self.tokens[-1])) + if self.shift_selection: + keys.extend(self.select_token(self.tokens[-1], True)) + return keys def select_phrases(self, phrases: List[str], match_threshold: float = SELECTION_THRESHOLD, extend_selection: bool = False, for_correction: bool = False, verbose = False) -> List[str]: @@ -727,14 +730,13 @@ def remove_virtual_selection(self) -> List[str]: total_amount += len(self.virtual_selection[0].text) else: text_buffer = self.caret_tracker.get_text_between_tokens( - (self.virtual_selection[0].line_index, self.virtual_selection[0].index_from_line_end - len(self.virtual_selection[0].text)), - (self.virtual_selection[1].line_index, self.virtual_selection[1].index_from_line_end), - False + (self.virtual_selection[0].line_index, self.virtual_selection[0].index_from_line_end + len(self.virtual_selection[0].text)), + (self.virtual_selection[1].line_index, self.virtual_selection[1].index_from_line_end) ) - print( "BUFFER!", text_buffer ) - total_amount = len(text_buffer) + # TODO - SUPPORT NO BACKSPACE WRAPPING + if total_amount: keys = ["backspace:" + str(total_amount)] diff --git a/virtual_buffer/caret_tracker.py b/virtual_buffer/caret_tracker.py index a03624d..397515d 100644 --- a/virtual_buffer/caret_tracker.py +++ b/virtual_buffer/caret_tracker.py @@ -661,56 +661,27 @@ def split_string_with_punctuation(self, text: str) -> List[str]: return re.sub(r"[" + re.escape("!\"#$%&'()*+, -./:;<=>?@[\\]^`{|}~") + "]+", " ", text).split() def get_selection_text(self) -> str: - selection_lines = [] if self.is_selecting(): left = self.get_leftmost_caret_index(True) right = self.get_rightmost_caret_index(True) - lines = self.text_buffer.splitlines() - for line_index, line in enumerate(lines): - replaced_line = line.replace(_CARET_MARKER, '').replace(_COARSE_MARKER, '') - if line_index == left[0] and line_index == right[0]: - selection_lines.append(replaced_line[len(replaced_line) - left[1]:len(replaced_line) - right[1]]) - elif line_index == left[0] and line_index < right[0]: - selection_lines.append( replaced_line[len(replaced_line) - left[1]] ) - elif line_index > left[0] and line_index < right[0]: - selection_lines.append( replaced_line ) - elif line_index > left[0] and line_index == right[0]: - selection_lines.append( replaced_line[:len(replaced_line) - right[1]] ) - return "\n".join(selection_lines) + return self.get_text_between_tokens(left, right) + return "" - def get_text_between_tokens(self, left_index = (-1, -1), right_index = (-1, -1), from_end = False) -> str: + def get_text_between_tokens(self, left_index = (-1, -1), right_index = (-1, -1)) -> str: cursor_lines = [] if left_index != (-1, -1) and right_index != (-1, -1): lines = self.text_buffer.splitlines() - left_index_line = left_index[0] - left_index_character_index = left_index[1] - right_index_line = right_index[0] - right_index_character_index = right_index[1] - - # Normalize the ends to be the same as the character index on the line - if from_end: - if left_index_line < len(lines): - replaced_line = lines[left_index_line].replace(_CARET_MARKER, '').replace(_COARSE_MARKER, '') - left_index_character_index = len(replaced_line) - left_index_character_index - else: - return "" - - if right_index_line < len(lines): - replaced_line = lines[right_index_line].replace(_CARET_MARKER, '').replace(_COARSE_MARKER, '') - right_index_character_index = len(replaced_line) - right_index_character_index - else: - return "" - left = (left_index_line, left_index_character_index) - right = (right_index_line, right_index_character_index) + left = (left_index[0], left_index[1]) + right = (right_index[0], right_index[1]) for line_index, line in enumerate(lines): replaced_line = line.replace(_CARET_MARKER, '').replace(_COARSE_MARKER, '') if line_index == left[0] and line_index == right[0]: cursor_lines.append(replaced_line[len(replaced_line) - left[1]:len(replaced_line) - right[1]]) elif line_index == left[0] and line_index < right[0]: - cursor_lines.append( replaced_line[len(replaced_line) - left[1]] ) + cursor_lines.append( replaced_line[-left[1]:] ) elif line_index > left[0] and line_index < right[0]: cursor_lines.append( replaced_line ) elif line_index > left[0] and line_index == right[0]: From 580b4da80ae42219f067eaee6926d5670f2fe459 Mon Sep 17 00:00:00 2001 From: Kevin te Raa Date: Sun, 15 Dec 2024 18:37:13 +0100 Subject: [PATCH 13/24] Added removal keys for virtual selection when transforming the inserts during marithime inserts or corrections --- README.md | 6 +----- tests/virtual_buffer/coarse_caret_tracking.py | 8 +++----- tests/virtual_buffer/single_line_support.py | 5 +++-- virtual_buffer/caret_tracker.py | 4 ++-- virtual_buffer/manager.py | 10 +++++++++- 5 files changed, 18 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index a55c538..3ca2fa2 100644 --- a/README.md +++ b/README.md @@ -101,16 +101,12 @@ While there's programs where it nails the accessibility API pretty well, others [~] - Terminal support Right now terminals have a ton of issues because they do not allow for text selection, have painful accessibility support, and use a ton of custom key binds that don't correlate with other document builders. -# TODO - SELECT -# TODO - CORRECT -# TODO - FIX COARSE TRACKING SINGLE AND MULTI LINE -# TODO - NO BACKSPACE WRAP MODE [~] - Single line detection / support Some fields, like name fields, do not have the possibility to add multiple lines. In that case, we probably want to either clear the buffer or simply not allow the enter to change the field. We should probably do a refresh if we are in an accessible field, and a clear in a terminal. [] - Text editor support -This means we should be able to support vim, nano and other keybindings. This runs into the same issues as using a terminal does however, namely poor accessibility support and hard to detect when something is inside of a text editor in the first place. +This means we should be able to support vim, nano and other keybindings. This runs into the same issues as using a terminal does however, namely poor accessibility support and hard to detect when something is inside of a text editor in the first place. Another is no line wrapping when reaching the start or end of the line and key-pressing beyond that boundary. [] - Accessiblity input tags We can detect a field type, like email, phone number etc from the accessibility APIs. That means we could expose that information for other packages to use as well, so you can say `Homer` to input `homer@odyssey.com` for example. diff --git a/tests/virtual_buffer/coarse_caret_tracking.py b/tests/virtual_buffer/coarse_caret_tracking.py index 923b8c3..d24e86c 100644 --- a/tests/virtual_buffer/coarse_caret_tracking.py +++ b/tests/virtual_buffer/coarse_caret_tracking.py @@ -189,16 +189,14 @@ def coarse_caret_tracking_multi_line_macos(assertion): assertion( " Expect caret line index to be 2", caret_index[0] == 2) assertion( " Pressing cmd-LEFT to go to the start of the third sentence...") vb.apply_key("cmd-left") - caret_index = vb.caret_tracker.get_caret_index(True) - assertion( caret_index ) - assertion( vb.caret_tracker.text_buffer ) + + caret_index = vb.caret_tracker.get_caret_index(True) assertion( " Expect caret line index to be 2", caret_index[0] == 2) assertion( " Expect coarse character index to be before the word insert (24)", caret_index[1] == 24) - suite = create_test_suite("Coarse caret tracking") suite.add_test(coarse_caret_tracker_splitting) suite.add_test(coarse_caret_tracking_single_line) suite.add_test(coarse_caret_tracking_multi_line) -suite.add_test(coarse_caret_tracking_single_line_macos) +suite.add_test(coarse_caret_tracking_single_line_macos) suite.add_test(coarse_caret_tracking_multi_line_macos) \ No newline at end of file diff --git a/tests/virtual_buffer/single_line_support.py b/tests/virtual_buffer/single_line_support.py index 5c8977c..72b9e3d 100644 --- a/tests/virtual_buffer/single_line_support.py +++ b/tests/virtual_buffer/single_line_support.py @@ -117,6 +117,8 @@ def test_arrow_key_press(assertion): vb.apply_key("down") caret_index = vb.caret_tracker.get_caret_index() + assertion( caret_index ) + assertion( vb.caret_tracker.text_buffer ) assertion( " Expect caret line index to be the same", caret_index[0] == 0) assertion( " Expect caret character index to be coarse", caret_index[1] == -1) assertion( " Expect no selection detected", vb.is_selecting() == False) @@ -136,7 +138,6 @@ def test_line_navigation_key_press(assertion): vb.apply_key("home") caret_index = vb.caret_tracker.get_caret_index() - assertion( caret_index, False ) assertion( " Expect caret line index to be the same", caret_index[0] == 0) assertion( " Expect caret character index to be at the start", caret_index[1] == 119) assertion( " Expect no selection detected", vb.is_selecting() == False) @@ -156,4 +157,4 @@ def test_line_navigation_key_press(assertion): suite.add_test(test_newline_insert) suite.add_test(test_arrow_key_press) suite.add_test(test_line_navigation_key_press) -suite.run() \ No newline at end of file +suite.run() \ No newline at end of file diff --git a/virtual_buffer/caret_tracker.py b/virtual_buffer/caret_tracker.py index 397515d..2178628 100644 --- a/virtual_buffer/caret_tracker.py +++ b/virtual_buffer/caret_tracker.py @@ -326,7 +326,7 @@ def mark_line_as_coarse(self, difference_from_line: int = 0): before_caret_text = "\n".join(before_caret) after_caret_text = "\n".join(after_caret) - if len(after_caret) > 0 and len(lines) > 0: + if len(after_caret) > 0 and len(before_caret) > 0 and len(lines) > 0: if difference_from_line == 0: before_caret_text += "\n" elif char_index == 0: @@ -636,7 +636,7 @@ def get_caret_index(self, check_coarse = False) -> (int, int): for index, line in enumerate(lines): if _COARSE_MARKER in line: line_index = index - character_index = len(line.split(_COARSE_MARKER)[0]) if check_coarse else -1 + character_index = len(line.split(_COARSE_MARKER)[1]) if check_coarse else -1 break return line_index, character_index diff --git a/virtual_buffer/manager.py b/virtual_buffer/manager.py index 421c359..cd99325 100644 --- a/virtual_buffer/manager.py +++ b/virtual_buffer/manager.py @@ -129,10 +129,18 @@ def transform_insert(self, insert: str, enable_self_repair: bool = False) -> (st repair_keys = [] # Allow the user to do self repair in speech - # TODO - TRACK VIRTUAL SELECTION CORRECTION correction_insertion = self.is_selecting() previous_selection = "" if not correction_insertion else vbm.caret_tracker.get_selection_text() current_insertion = "" + + # Make sure we remove the virtual selection if we apply a new insert + if len(vbm.virtual_selection) > 0: + previous_selection = "" if not correction_insertion else vbm.caret_tracker.get_text_between_tokens( + vbm.virtual_selection[0], + vbm.virtual_selection[-1] + ) + repair_keys.extend(vbm.remove_virtual_selection()) + if enable_self_repair: # Remove stutters / repeats in the same phrase From 106dc8591b882cd6adc41502bbc213f5bbe60e6a Mon Sep 17 00:00:00 2001 From: Kevin te Raa Date: Sat, 21 Dec 2024 18:48:26 +0100 Subject: [PATCH 14/24] Removed task from todo list --- README.md | 9 +++------ tests/test.py | 6 +++--- tests/virtual_buffer/selection_tests.py | 9 ++++----- tests/virtual_buffer/single_line_support.py | 3 +-- 4 files changed, 11 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 3ca2fa2..d4e7f92 100644 --- a/README.md +++ b/README.md @@ -99,15 +99,12 @@ We need to find a way to deal with word wrap, meaning things being on a single l [] - Improved MacOS support While there's programs where it nails the accessibility API pretty well, others just don't connect properly with finding the right focused element. We'll need to address these one by one unfortunately, because accessibility APIs are all over the place from program to program. -[~] - Terminal support -Right now terminals have a ton of issues because they do not allow for text selection, have painful accessibility support, and use a ton of custom key binds that don't correlate with other document builders. - -[~] - Single line detection / support -Some fields, like name fields, do not have the possibility to add multiple lines. In that case, we probably want to either clear the buffer or simply not allow the enter to change the field. We should probably do a refresh if we are in an accessible field, and a clear in a terminal. - [] - Text editor support This means we should be able to support vim, nano and other keybindings. This runs into the same issues as using a terminal does however, namely poor accessibility support and hard to detect when something is inside of a text editor in the first place. Another is no line wrapping when reaching the start or end of the line and key-pressing beyond that boundary. +[~] - Single line detection +Some fields, like name fields, do not have the possibility to add multiple lines. In that case, we probably want to either clear the buffer or simply not allow the enter to change the field. We should probably do a refresh if we are in an accessible field, and a clear in a terminal. + [] - Accessiblity input tags We can detect a field type, like email, phone number etc from the accessibility APIs. That means we could expose that information for other packages to use as well, so you can say `Homer` to input `homer@odyssey.com` for example. diff --git a/tests/test.py b/tests/test.py index 6a374b7..33d3b5c 100644 --- a/tests/test.py +++ b/tests/test.py @@ -89,8 +89,8 @@ def add_test_suite(self, test_suite: TestSuite): def run(self, verbosity = 0): # Only run tests if marithime testing is turned on - #if settings.get("user.marithime_testing") == 0: - # return + if settings.get("user.marithime_testing") == 0: + return start_time = time.perf_counter() total_results = [] suite_results = [] @@ -130,5 +130,5 @@ def run_tests(): def run_tests_ready(): test_suite_collection.ready = True test_suite_collection.run() - + app.register("ready", run_tests_ready) \ No newline at end of file diff --git a/tests/virtual_buffer/selection_tests.py b/tests/virtual_buffer/selection_tests.py index 9301dec..e29154d 100644 --- a/tests/virtual_buffer/selection_tests.py +++ b/tests/virtual_buffer/selection_tests.py @@ -727,8 +727,7 @@ def percentage_test_selfrepair(assertion): percentage_tests(assertion, False, False, True, 0.95) suite = create_test_suite("Selecting whole phrases inside of a selection") -#suite.add_test(percentage_test_selection) -#suite.add_test(percentage_test_correction) -#suite.add_test(percentage_test_selfrepair) -#suite.add_test(percentage_tests) -suite.run() \ No newline at end of file +suite.add_test(percentage_test_selection) +suite.add_test(percentage_test_correction) +suite.add_test(percentage_test_selfrepair) +#suite.add_test(percentage_tests) \ No newline at end of file diff --git a/tests/virtual_buffer/single_line_support.py b/tests/virtual_buffer/single_line_support.py index 72b9e3d..374faeb 100644 --- a/tests/virtual_buffer/single_line_support.py +++ b/tests/virtual_buffer/single_line_support.py @@ -156,5 +156,4 @@ def test_line_navigation_key_press(assertion): suite.add_test(test_press_enter_on_select_key) suite.add_test(test_newline_insert) suite.add_test(test_arrow_key_press) -suite.add_test(test_line_navigation_key_press) -suite.run() \ No newline at end of file +suite.add_test(test_line_navigation_key_press) \ No newline at end of file From 5b66f03663009fa709cdea6a7dcfe25849221ada Mon Sep 17 00:00:00 2001 From: Kevin te Raa Date: Sat, 21 Dec 2024 20:02:22 +0100 Subject: [PATCH 15/24] Due to inconsistencies, started adding a live tracing window to see what the virtual buffer thinks the state is --- talon_hud_integration/index_visualisation.py | 8 +++- virtual_buffer/input_context_manager.py | 21 ++++++++-- virtual_buffer/manager.py | 41 +++++++++++++------- 3 files changed, 52 insertions(+), 18 deletions(-) diff --git a/talon_hud_integration/index_visualisation.py b/talon_hud_integration/index_visualisation.py index 8f42ec0..df55a82 100644 --- a/talon_hud_integration/index_visualisation.py +++ b/talon_hud_integration/index_visualisation.py @@ -63,4 +63,10 @@ def marithime_update_sensory_state(scanning: bool, level: str, caret_confidence: absolute_status_bar_image = os.path.join(IMAGES_DIR, status_bar_image + ".png") status_bar_icon = actions.user.hud_create_status_icon("virtual_buffer", absolute_status_bar_image, "", "Virtual buffer unavailable", index_document) - actions.user.hud_publish_status_icon("virtual_buffer", status_bar_icon) \ No newline at end of file + actions.user.hud_publish_status_icon("virtual_buffer", status_bar_icon) + + def marithime_show_context() -> str: + """Show the current context in a Window if given the chance""" + content_to_render = actions.next() + actions.user.hud_publish_content(content_to_render, "documentation", "Virtual buffer context", True, [], []) + return content_to_render \ No newline at end of file diff --git a/virtual_buffer/input_context_manager.py b/virtual_buffer/input_context_manager.py index d2847e9..f7a830b 100644 --- a/virtual_buffer/input_context_manager.py +++ b/virtual_buffer/input_context_manager.py @@ -1,4 +1,4 @@ -from talon import ui, actions, clip, settings +from talon import ui, actions, clip, settings, cron from .input_context import InputContext import time from typing import List, Callable, Tuple @@ -23,6 +23,8 @@ class InputContextManager: active_formatters: List[TextFormatter] formatter_names: List[str] state_callback: Callable[[str, int, int, bool], None] = None + context_tracking = False + context_tracking_cron = None last_title: str = "" last_pid: int = -1 @@ -90,7 +92,7 @@ def switch_context(self, window) -> bool: if accessible_text and accessible_text.active_caret.line_index == caret_index[0] and accessible_text.active_caret.characters_from_end == caret_index[1]: caret_confidence = 2 - + self.update_visual_state("accessibility" if context_to_switch_to.accessible_api_available else "text", caret_confidence=caret_confidence, content_confidence=content_confidence) else: self.update_visual_state("text", 0, 0, False) @@ -258,7 +260,7 @@ def clear_stale_contexts(self): del contexts_to_clear[-1] def get_current_context(self) -> InputContext: - if self.current_context: + if self.current_context is not None: if self.current_context.pid == -1: self.switch_context(ui.active_window()) @@ -562,4 +564,15 @@ def set_start_of_line_key(self, start_of_line_key: str): self.get_current_context().set_start_of_line_key(start_of_line_key) def set_clear_line_key(self, clear_line_key: str): - self.get_current_context().set_clear_line_key(clear_line_key) \ No newline at end of file + self.get_current_context().set_clear_line_key(clear_line_key) + + def set_context_tracking(self, tracking = False): + self.context_tracking = tracking + cron.cancel(self.context_tracking_cron) + if tracking == False: + self.context_tracking_cron = None + else: + self.context_tracking_cron = cron.interval("1s", self.update_context_debug_state) + + def update_context_debug_state(self): + actions.user.marithime_show_context() diff --git a/virtual_buffer/manager.py b/virtual_buffer/manager.py index b20b9ca..c4e3a5d 100644 --- a/virtual_buffer/manager.py +++ b/virtual_buffer/manager.py @@ -335,28 +335,22 @@ def update_language(language: str): mutator.fixer.load_fixes(language, engine_description) def update_shift_selection(shift_selection: bool): - global mutator - mutator.set_shift_selection(shift_selection) + get_mutator().set_shift_selection(shift_selection) def update_multiline_supported(multiline_support: bool): - global mutator - mutator.set_multiline_supported(multiline_support) + get_mutator().set_multiline_supported(multiline_support) def update_clear_key(clear_key: str): - global mutator - mutator.set_clear_key(clear_key) + get_mutator().set_clear_key(clear_key) def update_remove_line_key(remove_line_key: str): - global mutator - mutator.set_remove_line_key(remove_line_key) + get_mutator().set_remove_line_key(remove_line_key) def update_start_of_line_key(start_of_line_key: str): - global mutator - mutator.set_start_of_line_key(start_of_line_key) + get_mutator().set_start_of_line_key(start_of_line_key) def update_end_of_line_key(end_of_line_key: str): - global mutator - mutator.set_end_of_line_key(end_of_line_key) + get_mutator().set_end_of_line_key(end_of_line_key) mutator = None def init_mutator(): @@ -570,4 +564,25 @@ def marithime_dump_context(): actions.insert(json.dumps(list(mutator.fixer.done_fixes.keys()))) actions.key("enter") - mutator.enable_tracking("DUMP") \ No newline at end of file + mutator.enable_tracking("DUMP") + + def marithime_enable_track_context(): + """Start tracking the marithime virtual buffer context as it changes""" + mutator = get_mutator() + mutator.context.set_context_tracking(True) + actions.user.marithime_show_context() + + def marithime_disable_track_context(): + """Stop tracking the marithime virtual buffer context as it changes""" + mutator = get_mutator() + mutator.context.set_context_tracking(False) + + def marithime_show_context() -> str: + """Show the current context in a Window if supported""" + mutator = get_mutator() + current_context = mutator.context.get_current_context() + lines = "Using " + current_context.title + " - PID " + str(current_context.pid ) + "\n" + lines += "------------------------------------------------\n" + lines += (current_context.buffer.caret_tracker.text_buffer).replace("$CARET", "|^|") + + return lines \ No newline at end of file From 2a6d760180cabfc97c1e402989a9c1530deca631 Mon Sep 17 00:00:00 2001 From: chaosparrot Date: Sat, 28 Dec 2024 15:47:09 +0100 Subject: [PATCH 16/24] Improved debugability by adding the document poller on the virtual buffer icon button Attempted to improve accessibility support through the iaccessible interface but ran into issues --- accessibility/windows.py | 15 ++++++++++++--- talon_hud_integration/index_visualisation.py | 1 + virtual_buffer/manager.py | 11 ++++++++++- 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/accessibility/windows.py b/accessibility/windows.py index 569fe55..f0c1b66 100644 --- a/accessibility/windows.py +++ b/accessibility/windows.py @@ -30,7 +30,7 @@ def index_element_text(self, element = None) -> AccessibilityText: if value is not None: accessibility_text = AccessibilityText(value) - if accessibility_text and "Text" in element.patterns: + if not accessibility_text and "Text" in element.patterns: try: value = element.text_pattern.document_range.text # Windows sometimes just throws operation successful errors... @@ -100,7 +100,7 @@ def determine_caret_positions(self, element = None) -> List[AccessibilityCaret]: # Code adapted from AndreasArvidsson's talon files # Currently only Text and Text2 are supported - has_text_pattern = False if "Text2" not in element.patterns and "Text" not in element.patterns else True + has_text_pattern = "Text2" in element.patterns or "Text" in element.patterns if has_text_pattern: text_pattern = element.text_pattern2 if "Text2" in element.patterns else element.text_pattern @@ -180,8 +180,17 @@ def determine_caret_positions(self, element = None) -> List[AccessibilityCaret]: return [end_caret, start_caret] if is_reversed else [start_caret, end_caret] else: return [] + # IAccessible proves to be harder to implement + # Further investigation can be done with the code seen in NVAccess + # https://github.com/nvaccess/nvda/blob/e80d7822160f7d2ff151140bc97ca84e5798c1fb/source/NVDAObjects/IAccessible/__init__.py#L465 + #elif "LegacyIAccessible" in element.patterns: + # print("ATTEMPTING!") + # pattern = element.legacyiaccessible_pattern + # selection = pattern.selection + # if len(selection) > 0: + # print( dir( selection ), selection ) + # print( dir(element), element.aria_role ) return [] - windows_api = WindowsAccessibilityApi() diff --git a/talon_hud_integration/index_visualisation.py b/talon_hud_integration/index_visualisation.py index df55a82..24048e0 100644 --- a/talon_hud_integration/index_visualisation.py +++ b/talon_hud_integration/index_visualisation.py @@ -5,6 +5,7 @@ def index_document(_ = None, _2 = None): actions.user.marithime_index_textarea() + actions.user.marithime_toggle_track_context() ctx_override = Context() ctx_override.matches = """ diff --git a/virtual_buffer/manager.py b/virtual_buffer/manager.py index c4e3a5d..2782ea3 100644 --- a/virtual_buffer/manager.py +++ b/virtual_buffer/manager.py @@ -277,7 +277,7 @@ def index(self): words_list = [] for token in vbm.tokens: words_list.append(token.phrase) - ctx.lists["user.marithime_indexed_words"] = words_list + #ctx.lists["user.marithime_indexed_words"] = words_list tags = [] token_index = vbm.determine_token_index() @@ -566,6 +566,14 @@ def marithime_dump_context(): mutator.enable_tracking("DUMP") + def marithime_toggle_track_context(): + """Toggle between tracking and not tracking the marithime virtual buffer context""" + mutator = get_mutator() + if mutator.context.context_tracking == True: + actions.user.marithime_disable_track_context() + else: + actions.user.marithime_enable_track_context() + def marithime_enable_track_context(): """Start tracking the marithime virtual buffer context as it changes""" mutator = get_mutator() @@ -584,5 +592,6 @@ def marithime_show_context() -> str: lines = "Using " + current_context.title + " - PID " + str(current_context.pid ) + "\n" lines += "------------------------------------------------\n" lines += (current_context.buffer.caret_tracker.text_buffer).replace("$CARET", "|^|") + # TODO only show relevant lines around cursor? return lines \ No newline at end of file From b6421a46fc65ffb6a1d4457cfbe2a375da08c353 Mon Sep 17 00:00:00 2001 From: chaosparrot Date: Sat, 28 Dec 2024 16:28:26 +0100 Subject: [PATCH 17/24] Moved shift selection setting over to focus change but there are still desyncs happening Will need to figure out a way to use the context system together with talon without getting desyncs but without polling talon for everything --- accessibility/windows.py | 7 ++--- context.py | 2 ++ programs/terminal.talon | 1 + virtual_buffer/buffer.py | 3 +++ virtual_buffer/input_context.py | 1 + virtual_buffer/input_context_manager.py | 3 ++- virtual_buffer/manager.py | 36 +++++++++---------------- 7 files changed, 25 insertions(+), 28 deletions(-) diff --git a/accessibility/windows.py b/accessibility/windows.py index f0c1b66..b798650 100644 --- a/accessibility/windows.py +++ b/accessibility/windows.py @@ -99,10 +99,11 @@ def determine_caret_positions(self, element = None) -> List[AccessibilityCaret]: return None # Code adapted from AndreasArvidsson's talon files - # Currently only Text and Text2 are supported - has_text_pattern = "Text2" in element.patterns or "Text" in element.patterns + # Currently only Text2 is supported + # Text(1) doesn't seem to have caret_range and other methods + has_text_pattern = "Text2" in element.patterns# or "Text" in element.patterns if has_text_pattern: - text_pattern = element.text_pattern2 if "Text2" in element.patterns else element.text_pattern + text_pattern = element.text_pattern2# if "Text2" in element.patterns else element.text_pattern # Make copy of the document range to avoid modifying the original range_before_selection = text_pattern.document_range.clone() diff --git a/context.py b/context.py index 1a09811..afbd252 100644 --- a/context.py +++ b/context.py @@ -22,6 +22,8 @@ mod.setting("marithime_context_remove_forward_word", type=str, default="ctrl-delete", desc="The key combination to clear a word to the right of the caret") mod.setting("marithime_context_remove_forward_letter", type=str, default="delete", desc="The key combination to clear a single letter to the right of the caret") mod.setting("marithime_context_remove_line", type=str, default="", desc="The key to remove an entire line from the current text") +mod.setting("marithime_context_start_line_key", type=str, default="home", desc="The key to move to the start of the line") +mod.setting("marithime_context_end_line_key", type=str, default="end", desc="The key to move to the end of the line") # Options - "" (default) - Whenever the confidence that it has lost the location in the file, re-index # - aggressive - After every marithime command that requires context, we re-index diff --git a/programs/terminal.talon b/programs/terminal.talon index 28f6459..9549862 100644 --- a/programs/terminal.talon +++ b/programs/terminal.talon @@ -2,6 +2,7 @@ os: macos os: windows os: linux tag: terminal +# TODO - Windows Powershell supports shift selection?! - settings(): user.marithime_indexing_strategy = "disabled" diff --git a/virtual_buffer/buffer.py b/virtual_buffer/buffer.py index 7eecf01..898b39f 100644 --- a/virtual_buffer/buffer.py +++ b/virtual_buffer/buffer.py @@ -28,6 +28,7 @@ def __init__(self): self.caret_tracker = CaretTracker() self.matcher = VirtualBufferMatcher(phonetic_search) self.set_tokens() + print("CREATING NEW BUFFER!!!") def is_selecting(self) -> bool: return self.caret_tracker.is_selecting() @@ -576,6 +577,7 @@ def select_token_range(self, start_token: VirtualBufferToken, end_token: Virtual self.virtual_selection = [] keys = [] + print("USES SHIFT SELECTION!?!?!?!", self.shift_selection) if self.shift_selection: if not extend_selection: keys = self.navigate_to_token(start_token, 0) @@ -745,6 +747,7 @@ def remove_virtual_selection(self) -> List[str]: def set_shift_selection(self, shift_selection: bool): self.caret_tracker.shift_selection = shift_selection self.shift_selection = shift_selection + print("SETTING SHIFT SELECTION ON BUFFER", shift_selection) def set_multiline_supported(self, multiline_supported: bool): self.caret_tracker.multiline_supported = multiline_supported diff --git a/virtual_buffer/input_context.py b/virtual_buffer/input_context.py index 6391206..c28b3d3 100644 --- a/virtual_buffer/input_context.py +++ b/virtual_buffer/input_context.py @@ -60,6 +60,7 @@ def destroy(self): self.buffer = None def set_shift_selection(self, shift_selection: bool): + print("SET SHIFT SELECTION IN CONTEXT", self.pid, self.app_name, shift_selection) self.buffer.set_shift_selection(shift_selection) def set_multiline_supported(self, multiline_supported: bool): diff --git a/virtual_buffer/input_context_manager.py b/virtual_buffer/input_context_manager.py index f7a830b..04a2339 100644 --- a/virtual_buffer/input_context_manager.py +++ b/virtual_buffer/input_context_manager.py @@ -261,11 +261,12 @@ def clear_stale_contexts(self): def get_current_context(self) -> InputContext: if self.current_context is not None: + print("USING CONTEXTS", self.current_context.pid) if self.current_context.pid == -1: self.switch_context(ui.active_window()) self.current_context.update_modified_at() - self.clear_stale_contexts() + #self.clear_stale_contexts() else: self.create_context() diff --git a/virtual_buffer/manager.py b/virtual_buffer/manager.py index 2782ea3..356f926 100644 --- a/virtual_buffer/manager.py +++ b/virtual_buffer/manager.py @@ -296,8 +296,18 @@ def index_textarea(self): def focus_changed(self, event): context_switched = self.context.switch_context(event) if context_switched: + # Update the context after the focus event to make sure we are updating the right context + self.set_shift_selection(settings.get("user.marithime_context_shift_selection")) + self.set_clear_key(settings.get("user.marithime_context_clear_key")) + self.set_multiline_supported(settings.get("user.marithime_context_multiline_supported")) + self.set_clear_line_key(settings.get("user.marithime_context_remove_line_key")) + self.set_start_of_line_key(settings.get("user.marithime_context_start_line_key")) + self.set_end_of_line_key(settings.get("user.marithime_context_end_line_key")) + + # Then index the values self.index() + def window_closed(self, event): self.context.close_context(event) @@ -334,24 +344,6 @@ def update_language(language: str): pass mutator.fixer.load_fixes(language, engine_description) -def update_shift_selection(shift_selection: bool): - get_mutator().set_shift_selection(shift_selection) - -def update_multiline_supported(multiline_support: bool): - get_mutator().set_multiline_supported(multiline_support) - -def update_clear_key(clear_key: str): - get_mutator().set_clear_key(clear_key) - -def update_remove_line_key(remove_line_key: str): - get_mutator().set_remove_line_key(remove_line_key) - -def update_start_of_line_key(start_of_line_key: str): - get_mutator().set_start_of_line_key(start_of_line_key) - -def update_end_of_line_key(end_of_line_key: str): - get_mutator().set_end_of_line_key(end_of_line_key) - mutator = None def init_mutator(): global mutator @@ -361,13 +353,9 @@ def init_mutator(): settings.register("speech.language", lambda language: update_language(language)) settings.register("speech.engine", lambda _: update_language("")) - settings.register("user.marithime_context_shift_selection", lambda shift_enabled: update_shift_selection(shift_enabled > 0)) - settings.register("user.marithime_context_clear_key", lambda clear_key: update_clear_key(clear_key)) - settings.register("user.marithime_context_multiline_supported", lambda supported: update_multiline_supported(supported > 0)) - settings.register("user.marithime_context_remove_line_key", lambda remove_line: update_remove_line_key(remove_line)) - settings.register("user.marithime_context_start_of_line_key", lambda start_of_line: update_start_of_line_key(start_of_line)) - settings.register("user.marithime_context_end_of_line_key", lambda end_of_line: update_end_of_line_key(end_of_line)) + # Enable this line to allow for quicker debugging + actions.menu.open_log() update_language("") return mutator From 749792f8a6e055956f93a102968833da2a4f9977 Mon Sep 17 00:00:00 2001 From: chaosparrot Date: Sat, 28 Dec 2024 16:32:51 +0100 Subject: [PATCH 18/24] Added new task --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index d4e7f92..4821401 100644 --- a/README.md +++ b/README.md @@ -102,6 +102,9 @@ While there's programs where it nails the accessibility API pretty well, others [] - Text editor support This means we should be able to support vim, nano and other keybindings. This runs into the same issues as using a terminal does however, namely poor accessibility support and hard to detect when something is inside of a text editor in the first place. Another is no line wrapping when reaching the start or end of the line and key-pressing beyond that boundary. +[~] - Fix desynchronization issues +There are a bunch of desynchronization issues with settings, context and process ids that cause all kinds of weird things like completely losing the context at random intervals, buffers being created without any need for it, and things like shift selection being turned on when it shouldn't. This will need to be fixed before we can actually support terminals. + [~] - Single line detection Some fields, like name fields, do not have the possibility to add multiple lines. In that case, we probably want to either clear the buffer or simply not allow the enter to change the field. We should probably do a refresh if we are in an accessible field, and a clear in a terminal. From 76a354386db3638abe74f1434473f12a9065e6b4 Mon Sep 17 00:00:00 2001 From: chaosparrot Date: Sun, 29 Dec 2024 15:00:24 +0100 Subject: [PATCH 19/24] Fixed desync issues when dealing with changing support for shift selection Still have some desync issues when the focus changes rapidly Added documentation about supported programs --- README.md | 7 ++ programs/SUPPORTED.md | 35 ++++++ programs/terminal.talon | 1 + programs/windows/powershell.talon | 6 ++ streamdeck.talon | 3 +- tests/virtual_buffer/appending.py | 9 +- tests/virtual_buffer/coarse_caret_tracking.py | 31 +++--- tests/virtual_buffer/dictation_self_repair.py | 10 +- .../virtual_buffer/exact_caret_navigation.py | 23 ++-- tests/virtual_buffer/exact_caret_tracking.py | 15 +-- tests/virtual_buffer/fuzzy_match.py | 8 +- .../merge_and_split_detection.py | 8 +- tests/virtual_buffer/merging_and_splitting.py | 7 +- tests/virtual_buffer/multiple_fuzzy_match.py | 8 +- .../multiple_match_consistent.py | 7 +- .../virtual_buffer/multiple_match_looping.py | 8 +- tests/virtual_buffer/removing.py | 9 +- tests/virtual_buffer/removing_selection.py | 11 +- tests/virtual_buffer/replacing_selection.py | 11 +- tests/virtual_buffer/selecting.py | 6 +- .../selection_caret_navigation.py | 7 +- .../selection_multiple_fuzzy_words.py | 7 +- .../selection_multiple_words.py | 7 +- ...n_multiple_words_and_matches_consistent.py | 7 +- tests/virtual_buffer/selection_performance.py | 25 +++-- tests/virtual_buffer/selection_range.py | 17 +-- tests/virtual_buffer/selection_tests.py | 8 +- tests/virtual_buffer/single_line_support.py | 17 +-- tests/virtual_buffer/terminal_enter_clear.py | 11 +- .../terminal_remove_selection.py | 9 +- .../terminal_selection_range.py | 9 +- virtual_buffer/buffer.py | 54 ++++------ virtual_buffer/caret_tracker.py | 40 ++++--- virtual_buffer/input_context.py | 1 - virtual_buffer/manager.py | 100 +++++------------- virtual_buffer/settings.py | 79 ++++++++++++++ 36 files changed, 411 insertions(+), 210 deletions(-) create mode 100644 programs/SUPPORTED.md create mode 100644 programs/windows/powershell.talon create mode 100644 virtual_buffer/settings.py diff --git a/README.md b/README.md index 4821401..7ead682 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,10 @@ If you do not want marithime dictation, but instead only want to use the selecti You can always say `marithime` followed by a phrase to use it if you do not want to override the regular dictation insert. +### List of supported programs + +Generally this package tries to support all kinds of programs through the accessibility APIs. Though in order to properly know whether they work, the programs are tested manually for support. [The list of supported programs is documented here](programs/SUPPORTED.md) + ### Privacy statement Because most software isn't accessible, this package relies on a couple of ways to understand what is inside a text field, and where the caret inside of it is. One of these methods is **locally tracking keystrokes that happen through Talon voice**. @@ -94,6 +98,9 @@ Right now, you still need to say `numb zero` every time between commands. We can [] - Word wrap detection We need to find a way to deal with word wrap, meaning things being on a single line, but visually ( and most importantly, keyboard relatively ) they are on multiple lines. Our current Up and Down arrow key pressing does not deal with that. +[] - Zero width space indexation selection fix +When a zero width space indexation is used, it is possible that a current selection is removed. We can fix that selection afterwards so we don't have issues where content is removed unnecessarily + #### Programs [] - Improved MacOS support diff --git a/programs/SUPPORTED.md b/programs/SUPPORTED.md new file mode 100644 index 0000000..986aa94 --- /dev/null +++ b/programs/SUPPORTED.md @@ -0,0 +1,35 @@ +# Supported programs + +This is a list of programs that are known to be supported. Others might have varying degrees of support depending on how well accessibility is handled. + +## Operating system support + +Both Windows and MacOS have supported accessibility APIs, which allow us to introspect the currently focused text area in great detail, but Linux does not. For that reason, Linux doesn't have as good of a user experience since we cannot poll the content of a text area directly. + +## Browser support + +TODO + +## Word processor support + +TODO + +## Code editor / IDE support + +| Program | OS | Cursor tracking | Content tracking | Selection | Notes | +|-----------------|---------|-----------------|------------------|-----------|-------| +| VSCode editor | Windows | Yes* | Yes* | Shift | This requires turning on accessiblity support | +| VSCode editor | MacOS | Yes* | Yes* | Shift | This requries turning on accessibility support `Shift+Option+F1`| + +## Terminal support + +| Program | OS | Cursor tracking | Content tracking | Selection | Notes | +|-----------------|---------|-----------------|------------------|-----------|-------| +| Terminal | Windows | Key tracking | Key tracking | Virtual | No accessibility APIs used | +| Git BASH | Windows | Key tracking | Key tracking | Virtual | No accessibility APIs used | +| CMD | Windows | Key tracking | Key tracking | Virtual | No accessibility APIs used | +| PowerShell | Windows | Key tracking | Key tracking | Shift | PowerShell supports shift selection! | + +Terminal programs generally aren't as well supported as other programs with are more rich set of accessibility APIs. Not to mention that text editors such as VIM, emacs and nano each have their own set of hotkeys to navigate the text displayed, so key tracking becomes increasingly hard to do and prone for desyncs. + +While it is possible to tackle this, it is also quite hard to do without major time investments and plugins designed for each text editor available. \ No newline at end of file diff --git a/programs/terminal.talon b/programs/terminal.talon index 9549862..96770dd 100644 --- a/programs/terminal.talon +++ b/programs/terminal.talon @@ -11,4 +11,5 @@ settings(): user.marithime_context_end_line_key = "ctrl-e" user.marithime_context_start_line_key = "ctrl-a" user.marithime_context_remove_line = "ctrl-u" + user.marithime_context_remove_word = "ctrl-w" user.marithime_context_clear_key = "enter" \ No newline at end of file diff --git a/programs/windows/powershell.talon b/programs/windows/powershell.talon new file mode 100644 index 0000000..d35eb0a --- /dev/null +++ b/programs/windows/powershell.talon @@ -0,0 +1,6 @@ +os: windows +tag: terminal +win.title: /PowerShell/ +- +settings(): + user.marithime_context_shift_selection = 1 \ No newline at end of file diff --git a/streamdeck.talon b/streamdeck.talon index 28f903b..9c7daca 100644 --- a/streamdeck.talon +++ b/streamdeck.talon @@ -1,5 +1,4 @@ tag: user.talon_hud_enabled tag: user.talon_hud_deck_enabled - -#deck(compass): user.marithime_index_textarea() -#deck(question-mark): user.marithime_dump_context() \ No newline at end of file +#deck(compass): user.marithime_index_textarea() \ No newline at end of file diff --git a/tests/virtual_buffer/appending.py b/tests/virtual_buffer/appending.py index ba85c5c..e2af2b4 100644 --- a/tests/virtual_buffer/appending.py +++ b/tests/virtual_buffer/appending.py @@ -2,6 +2,11 @@ from ...virtual_buffer.typing import VirtualBufferToken from ...virtual_buffer.indexer import text_to_virtual_buffer_tokens from ..test import create_test_suite +from ...virtual_buffer.settings import VirtualBufferSettings + +def get_virtual_buffer() -> VirtualBuffer: + settings = VirtualBufferSettings(live_checking=False) + return VirtualBuffer(settings) def text_to_virtual_buffer_token_replacement(assertion): assertion( " Expect token without phrase is expected phrase", text_to_virtual_buffer_tokens("Insert")[0].phrase == "insert") @@ -16,7 +21,7 @@ def text_to_virtual_buffer_token_replacement(assertion): def detect_insert_strategies(assertion): assertion( "Detecting insert strategies for inserting tokens" ) - empty_vb = VirtualBuffer() + empty_vb = get_virtual_buffer() assertion( " From an empty virtual buffer") empty_append = empty_vb.detect_merge_strategy(0, 0, VirtualBufferToken("This ", "this", "")) assertion( " Should append 'This ' if the virtual buffer is empty", empty_append == (-1, 0, -1)) @@ -34,7 +39,7 @@ def detect_insert_strategies(assertion): assertion( " Should merge a new line because new lines cannot exist on their own", final_append == (-1, 1, -1)) def appending_to_buffer(assertion): - vb = VirtualBuffer() + vb = get_virtual_buffer() assertion( "Appending to virtual buffer") assertion( " Inserting text into a clean virtual buffer...") vb.insert_tokens(text_to_virtual_buffer_tokens("Insert ", "insert")) diff --git a/tests/virtual_buffer/coarse_caret_tracking.py b/tests/virtual_buffer/coarse_caret_tracking.py index d24e86c..e95d4e5 100644 --- a/tests/virtual_buffer/coarse_caret_tracking.py +++ b/tests/virtual_buffer/coarse_caret_tracking.py @@ -2,9 +2,14 @@ from ...virtual_buffer.buffer import VirtualBuffer from ...virtual_buffer.indexer import text_to_virtual_buffer_tokens from ..test import create_test_suite +from ...virtual_buffer.settings import VirtualBufferSettings + +def get_virtual_buffer() -> VirtualBuffer: + settings = VirtualBufferSettings(live_checking=False) + return VirtualBuffer(settings) def coarse_caret_tracker_splitting(assertion): - vb = VirtualBuffer() + vb = get_virtual_buffer() vb.insert_tokens(text_to_virtual_buffer_tokens("Insert a new sentence. \n", "insert a new sentence")) vb.insert_tokens(text_to_virtual_buffer_tokens("Insert a second sentence. \n", "insert a second sentence")) vb.insert_tokens(text_to_virtual_buffer_tokens("Insert a third sentence.", "insert a third sentence")) @@ -35,11 +40,11 @@ def coarse_caret_tracker_splitting(assertion): assertion( " Expect a sentence with new lines to have a length of 5", len(items) == 5 ) def coarse_caret_tracking_single_line(assertion): - vb = VirtualBuffer() + vb = get_virtual_buffer() vb.caret_tracker.system = "Windows" vb.caret_tracker.is_macos = False - vb.caret_tracker.end_of_line_key = "end" - vb.caret_tracker.start_of_line_key = "home" + vb.settings.end_of_line_key = "end" + vb.settings.start_of_line_key = "home" vb.insert_tokens(text_to_virtual_buffer_tokens("Insert a new sentence. \n", "insert a new sentence")) vb.insert_tokens(text_to_virtual_buffer_tokens("Insert a second sentence. \n", "insert a second sentence")) vb.insert_tokens(text_to_virtual_buffer_tokens("Insert a third sentence.", "insert a third sentence")) @@ -88,11 +93,11 @@ def coarse_caret_tracking_single_line(assertion): assertion( " Expect caret line index to be lost", caret_index[0] == -1 ) def coarse_caret_tracking_multi_line(assertion): - vb = VirtualBuffer() + vb = get_virtual_buffer() vb.caret_tracker.system = "Windows" vb.caret_tracker.is_macos = False - vb.caret_tracker.end_of_line_key = "end" - vb.caret_tracker.start_of_line_key = "home" + vb.settings.end_of_line_key = "end" + vb.settings.start_of_line_key = "home" vb.insert_tokens(text_to_virtual_buffer_tokens("Insert a new sentence. \n", "insert a new sentence")) vb.insert_tokens(text_to_virtual_buffer_tokens("Insert a second sentence. \n", "insert a second sentence")) @@ -115,11 +120,11 @@ def coarse_caret_tracking_multi_line(assertion): assertion( " Expect coarse character index to be before the word insert (24)", caret_index[1] == 24) def coarse_caret_tracking_single_line_macos(assertion): - vb = VirtualBuffer() + vb = get_virtual_buffer() vb.caret_tracker.system = "Darwin" vb.caret_tracker.is_macos = True - vb.caret_tracker.end_of_line_key = "cmd-right" - vb.caret_tracker.start_of_line_key = "cmd-left" + vb.settings.end_of_line_key = "cmd-right" + vb.settings.start_of_line_key = "cmd-left" vb.insert_tokens(text_to_virtual_buffer_tokens("Insert a new sentence. \n", "insert a new sentence")) vb.insert_tokens(text_to_virtual_buffer_tokens("Insert a second sentence. \n", "insert a second sentence")) vb.insert_tokens(text_to_virtual_buffer_tokens("Insert a third sentence.", "insert a third sentence")) @@ -168,11 +173,11 @@ def coarse_caret_tracking_single_line_macos(assertion): assertion( " Expect caret line index to be lost", caret_index[0] == -1 ) def coarse_caret_tracking_multi_line_macos(assertion): - vb = VirtualBuffer() + vb = get_virtual_buffer() vb.caret_tracker.system = "Darwin" vb.caret_tracker.is_macos = True - vb.caret_tracker.end_of_line_key = "cmd-right" - vb.caret_tracker.start_of_line_key = "cmd-left" + vb.settings.end_of_line_key = "cmd-right" + vb.settings.start_of_line_key = "cmd-left" vb.insert_tokens(text_to_virtual_buffer_tokens("Insert a new sentence. \n", "insert a new sentence")) vb.insert_tokens(text_to_virtual_buffer_tokens("Insert a second sentence. \n", "insert a second sentence")) vb.insert_tokens(text_to_virtual_buffer_tokens("Insert a third sentence.", "insert a third sentence")) diff --git a/tests/virtual_buffer/dictation_self_repair.py b/tests/virtual_buffer/dictation_self_repair.py index 695ecda..fa74829 100644 --- a/tests/virtual_buffer/dictation_self_repair.py +++ b/tests/virtual_buffer/dictation_self_repair.py @@ -2,9 +2,15 @@ from ...virtual_buffer.typing import VirtualBufferTokenList from ...virtual_buffer.indexer import text_to_virtual_buffer_tokens from ..test import create_test_suite +from ...virtual_buffer.settings import VirtualBufferSettings + +def get_virtual_buffer() -> VirtualBuffer: + settings = VirtualBufferSettings(live_checking=False) + return VirtualBuffer(settings) + def get_filled_vb(): - vb = VirtualBuffer() + vb = get_virtual_buffer() vb.insert_tokens(text_to_virtual_buffer_tokens("Insert ", "insert")) vb.insert_tokens(text_to_virtual_buffer_tokens("a ", "a")) vb.insert_tokens(text_to_virtual_buffer_tokens("new ", "new")) @@ -17,7 +23,7 @@ def get_filled_vb(): return vb def get_filled_vb_with_examples(examples): - vb = VirtualBuffer() + vb = get_virtual_buffer() for index, example in enumerate(examples): vb.insert_tokens(text_to_virtual_buffer_tokens(example + " " if index < len(examples) - 1 else example, example)) return vb diff --git a/tests/virtual_buffer/exact_caret_navigation.py b/tests/virtual_buffer/exact_caret_navigation.py index 92d698c..6cbf8cc 100644 --- a/tests/virtual_buffer/exact_caret_navigation.py +++ b/tests/virtual_buffer/exact_caret_navigation.py @@ -1,13 +1,18 @@ from ...virtual_buffer.buffer import VirtualBuffer from ..test import create_test_suite from ...virtual_buffer.indexer import text_to_virtual_buffer_tokens +from ...virtual_buffer.settings import VirtualBufferSettings + +def get_virtual_buffer() -> VirtualBuffer: + settings = VirtualBufferSettings(live_checking=False) + return VirtualBuffer(settings) def test_single_line_exact_caret_navigation(assertion): - vb = VirtualBuffer() + vb = get_virtual_buffer() vb.caret_tracker.system = "Windows" vb.caret_tracker.is_macos = False - vb.caret_tracker.end_of_line_key = "end" - vb.caret_tracker.start_of_line_key = "home" + vb.settings.end_of_line_key = "end" + vb.settings.start_of_line_key = "home" vb.insert_tokens(text_to_virtual_buffer_tokens("Insert ", "insert")) vb.insert_tokens(text_to_virtual_buffer_tokens("a ", "a")) vb.insert_tokens(text_to_virtual_buffer_tokens("new ", "new")) @@ -45,11 +50,11 @@ def test_single_line_exact_caret_navigation(assertion): assertion( " Expect caret index to be the same", caret_index == (0, 0)) def test_multi_line_exact_caret_navigation(assertion): - vb = VirtualBuffer() + vb = get_virtual_buffer() vb.caret_tracker.system = "Windows" vb.caret_tracker.is_macos = False - vb.caret_tracker.end_of_line_key = "end" - vb.caret_tracker.start_of_line_key = "home" + vb.settings.end_of_line_key = "end" + vb.settings.start_of_line_key = "home" vb.insert_tokens(text_to_virtual_buffer_tokens("Insert ", "insert")) vb.insert_tokens(text_to_virtual_buffer_tokens("a ", "a")) vb.insert_tokens(text_to_virtual_buffer_tokens("new ", "new")) @@ -106,11 +111,11 @@ def test_multi_line_exact_caret_navigation(assertion): assertion( " Expect caret index to be the same", caret_index == (2, 5)) def test_multi_line_exact_caret_navigation_macos(assertion): - vb = VirtualBuffer() + vb = get_virtual_buffer() vb.caret_tracker.system = "Darwin" vb.caret_tracker.is_macos = True - vb.caret_tracker.end_of_line_key = "cmd-right" - vb.caret_tracker.start_of_line_key = "cmd-left" + vb.settings.end_of_line_key = "cmd-right" + vb.settings.start_of_line_key = "cmd-left" vb.insert_tokens(text_to_virtual_buffer_tokens("Insert ", "insert")) vb.insert_tokens(text_to_virtual_buffer_tokens("a ", "a")) vb.insert_tokens(text_to_virtual_buffer_tokens("new ", "new")) diff --git a/tests/virtual_buffer/exact_caret_tracking.py b/tests/virtual_buffer/exact_caret_tracking.py index 272377d..50f0806 100644 --- a/tests/virtual_buffer/exact_caret_tracking.py +++ b/tests/virtual_buffer/exact_caret_tracking.py @@ -2,14 +2,18 @@ from ...virtual_buffer.buffer import VirtualBuffer from ...virtual_buffer.indexer import text_to_virtual_buffer_tokens from ..test import create_test_suite +from ...virtual_buffer.settings import VirtualBufferSettings +def get_virtual_buffer() -> VirtualBuffer: + settings = VirtualBufferSettings(live_checking=False) + return VirtualBuffer(settings) def exact_caret_tracking(assertion): - vb = VirtualBuffer() + vb = get_virtual_buffer() vb.caret_tracker.is_macos = False vb.caret_tracker.system = "Windows" - vb.caret_tracker.end_of_line_key = "end" - vb.caret_tracker.start_of_line_key = "home" + vb.settings.end_of_line_key = "end" + vb.settings.start_of_line_key = "home" vb.insert_tokens(text_to_virtual_buffer_tokens("Insert a new sentence. \n", "insert a new sentence")) vb.insert_tokens(text_to_virtual_buffer_tokens("Insert a second sentence. \n", "insert a second sentence")) vb.insert_tokens(text_to_virtual_buffer_tokens("Insert a third sentence.", "insert a third sentence")) @@ -71,8 +75,8 @@ def exact_caret_tracking(assertion): vb.apply_key("left:10") assertion( " Moving one character to the left and pressing CMD-right ( on MacOS ) instead...") vb.caret_tracker.is_macos = True - vb.caret_tracker.end_of_line_key = "cmd-right" - vb.caret_tracker.start_of_line_key = "cmd-left" + vb.settings.end_of_line_key = "cmd-right" + vb.settings.start_of_line_key = "cmd-left" vb.caret_tracker.system = "Darwin" vb.apply_key("cmd-right") caret_index = vb.caret_tracker.get_caret_index() @@ -82,4 +86,3 @@ def exact_caret_tracking(assertion): suite = create_test_suite("Exact caret tracking with a filled virtual buffer ( Windows + MacOS )") suite.add_test(exact_caret_tracking) -suite.run() \ No newline at end of file diff --git a/tests/virtual_buffer/fuzzy_match.py b/tests/virtual_buffer/fuzzy_match.py index 737d163..b56232b 100644 --- a/tests/virtual_buffer/fuzzy_match.py +++ b/tests/virtual_buffer/fuzzy_match.py @@ -1,10 +1,16 @@ from ...virtual_buffer.buffer import VirtualBuffer from ...virtual_buffer.indexer import text_to_virtual_buffer_tokens from ..test import create_test_suite +from ...virtual_buffer.settings import VirtualBufferSettings + +def get_virtual_buffer() -> VirtualBuffer: + settings = VirtualBufferSettings(live_checking=False) + return VirtualBuffer(settings) + suite = create_test_suite("With a filled virtual buffer containing a full sentence") def test_fuzzy_matching(assertion): - vb = VirtualBuffer() + vb = get_virtual_buffer() vb.insert_tokens(text_to_virtual_buffer_tokens("Insert ", "insert")) vb.insert_tokens(text_to_virtual_buffer_tokens("an ", "an")) vb.insert_tokens(text_to_virtual_buffer_tokens("ad ", "ad")) diff --git a/tests/virtual_buffer/merge_and_split_detection.py b/tests/virtual_buffer/merge_and_split_detection.py index d3d4d71..541327c 100644 --- a/tests/virtual_buffer/merge_and_split_detection.py +++ b/tests/virtual_buffer/merge_and_split_detection.py @@ -2,9 +2,15 @@ from ...virtual_buffer.indexer import text_to_virtual_buffer_tokens from ...virtual_buffer.typing import VirtualBufferToken from ..test import create_test_suite +from ...virtual_buffer.settings import VirtualBufferSettings + +def get_virtual_buffer() -> VirtualBuffer: + settings = VirtualBufferSettings(live_checking=False) + return VirtualBuffer(settings) + def get_empty_vb() -> VirtualBuffer: - return VirtualBuffer() + return get_virtual_buffer() def get_filled_vb() -> VirtualBuffer: vb = get_empty_vb() diff --git a/tests/virtual_buffer/merging_and_splitting.py b/tests/virtual_buffer/merging_and_splitting.py index 599bb88..93912f6 100644 --- a/tests/virtual_buffer/merging_and_splitting.py +++ b/tests/virtual_buffer/merging_and_splitting.py @@ -2,9 +2,14 @@ from ...virtual_buffer.indexer import text_to_virtual_buffer_tokens from ...virtual_buffer.buffer import VirtualBuffer from ..test import create_test_suite +from ...virtual_buffer.settings import VirtualBufferSettings + +def get_virtual_buffer() -> VirtualBuffer: + settings = VirtualBufferSettings(live_checking=False) + return VirtualBuffer(settings) def test_merging_and_splitting(assertion): - vb = VirtualBuffer() + vb = get_virtual_buffer() vb.insert_tokens(text_to_virtual_buffer_tokens("Insert a new sentence. \n", "insert a new sentence")) vb.insert_tokens(text_to_virtual_buffer_tokens("Insert a second sentence. \n", "insert a second sentence")) vb.insert_tokens(text_to_virtual_buffer_tokens("Insert a third sentence.", "insert a third sentence")) diff --git a/tests/virtual_buffer/multiple_fuzzy_match.py b/tests/virtual_buffer/multiple_fuzzy_match.py index 26b2b4e..f35130e 100644 --- a/tests/virtual_buffer/multiple_fuzzy_match.py +++ b/tests/virtual_buffer/multiple_fuzzy_match.py @@ -1,9 +1,15 @@ from ...virtual_buffer.buffer import VirtualBuffer from ...virtual_buffer.indexer import text_to_virtual_buffer_tokens from ..test import create_test_suite +from ...virtual_buffer.settings import VirtualBufferSettings + +def get_virtual_buffer() -> VirtualBuffer: + settings = VirtualBufferSettings(live_checking=False) + return VirtualBuffer(settings) + def get_filled_vb(): - vb = VirtualBuffer() + vb = get_virtual_buffer() vb.insert_tokens(text_to_virtual_buffer_tokens("Insert ", "insert")) vb.insert_tokens(text_to_virtual_buffer_tokens("a ", "a")) vb.insert_tokens(text_to_virtual_buffer_tokens("new ", "new")) diff --git a/tests/virtual_buffer/multiple_match_consistent.py b/tests/virtual_buffer/multiple_match_consistent.py index bc60718..69d12bb 100644 --- a/tests/virtual_buffer/multiple_match_consistent.py +++ b/tests/virtual_buffer/multiple_match_consistent.py @@ -1,9 +1,14 @@ from ...virtual_buffer.buffer import VirtualBuffer from ...virtual_buffer.indexer import text_to_virtual_buffer_tokens from ..test import create_test_suite +from ...virtual_buffer.settings import VirtualBufferSettings + +def get_virtual_buffer() -> VirtualBuffer: + settings = VirtualBufferSettings(live_checking=False) + return VirtualBuffer(settings) def get_filled_vb(): - vb = VirtualBuffer() + vb = get_virtual_buffer() vb.caret_tracker.system = "Windows" vb.caret_tracker.is_macos = False vb.insert_tokens(text_to_virtual_buffer_tokens("Insert ", "insert")) diff --git a/tests/virtual_buffer/multiple_match_looping.py b/tests/virtual_buffer/multiple_match_looping.py index ea03ac9..5ac0c48 100644 --- a/tests/virtual_buffer/multiple_match_looping.py +++ b/tests/virtual_buffer/multiple_match_looping.py @@ -1,9 +1,15 @@ from ...virtual_buffer.buffer import VirtualBuffer from ...virtual_buffer.indexer import text_to_virtual_buffer_tokens from ..test import create_test_suite +from ...virtual_buffer.settings import VirtualBufferSettings + +def get_virtual_buffer() -> VirtualBuffer: + settings = VirtualBufferSettings(live_checking=False) + return VirtualBuffer(settings) + def get_filled_vb(): - vb = VirtualBuffer() + vb = get_virtual_buffer() vb.insert_tokens(text_to_virtual_buffer_tokens("Insert ", "insert")) vb.insert_tokens(text_to_virtual_buffer_tokens("a ", "a")) vb.insert_tokens(text_to_virtual_buffer_tokens("new ", "new")) diff --git a/tests/virtual_buffer/removing.py b/tests/virtual_buffer/removing.py index 4f211fe..d2c3fc1 100644 --- a/tests/virtual_buffer/removing.py +++ b/tests/virtual_buffer/removing.py @@ -2,9 +2,14 @@ from ...virtual_buffer.buffer import VirtualBuffer from ...virtual_buffer.indexer import text_to_virtual_buffer_tokens from ..test import create_test_suite +from ...virtual_buffer.settings import VirtualBufferSettings + +def get_virtual_buffer() -> VirtualBuffer: + settings = VirtualBufferSettings(live_checking=False) + return VirtualBuffer(settings) def test_removing_characters(assertion): - vb = VirtualBuffer() + vb = get_virtual_buffer() vb.insert_tokens(text_to_virtual_buffer_tokens("Insert a new sentence. \n", "insert a new sentence")) vb.insert_tokens(text_to_virtual_buffer_tokens("Insert a second sentence. \n", "insert a second sentence")) vb.insert_tokens(text_to_virtual_buffer_tokens("Insert a third sentence.", "insert a third sentence")) @@ -125,7 +130,7 @@ def test_removing_characters(assertion): assertion( " Expect caret character index to be at the start of the second line", caret_index[1] == 22) def test_remove_single_line_ending_remaining(assertion): - vb = VirtualBuffer() + vb = get_virtual_buffer() vb.insert_tokens(text_to_virtual_buffer_tokens("Insert a new sentence. \n", "insert a new sentence")) vb.insert_tokens(text_to_virtual_buffer_tokens("Insert a second sentence", "insert a second sentence")) vb.insert_tokens(text_to_virtual_buffer_tokens(". \n", "")) diff --git a/tests/virtual_buffer/removing_selection.py b/tests/virtual_buffer/removing_selection.py index 9264904..5df6b80 100644 --- a/tests/virtual_buffer/removing_selection.py +++ b/tests/virtual_buffer/removing_selection.py @@ -2,9 +2,14 @@ from ...virtual_buffer.buffer import VirtualBuffer from ...virtual_buffer.indexer import text_to_virtual_buffer_tokens from ..test import create_test_suite +from ...virtual_buffer.settings import VirtualBufferSettings + +def get_virtual_buffer() -> VirtualBuffer: + settings = VirtualBufferSettings(live_checking=False) + return VirtualBuffer(settings) def test_remove_selecting_single_tokens(assertion): - vb = VirtualBuffer() + vb = get_virtual_buffer() vb.insert_tokens(text_to_virtual_buffer_tokens("Insert a new sentence. \n", "insert a new sentence")) vb.insert_tokens(text_to_virtual_buffer_tokens("Insert a second sentence. \n", "insert a second sentence")) vb.insert_tokens(text_to_virtual_buffer_tokens("Insert a third sentence.", "insert a third sentence")) @@ -50,7 +55,7 @@ def test_remove_selecting_single_tokens(assertion): assertion( " Expect phrase to be merged", vb.tokens[0].phrase == "insert a new sentencesert a third sentence") def test_remove_selecting_multiple_tokens_left(assertion): - vb = VirtualBuffer() + vb = get_virtual_buffer() vb.insert_tokens(text_to_virtual_buffer_tokens("Suggest", "suggest")) vb.insert_tokens(text_to_virtual_buffer_tokens(" create", "create")) vb.insert_tokens(text_to_virtual_buffer_tokens(" delete", "delete")) @@ -77,7 +82,7 @@ def test_remove_selecting_multiple_tokens_left(assertion): assertion( " Expect phrase to be merged", vb.tokens[-1].phrase == "suggestion") def test_remove_selecting_multiple_tokens_right(assertion): - vb = VirtualBuffer() + vb = get_virtual_buffer() vb.insert_tokens(text_to_virtual_buffer_tokens("Suggest ", "suggest")) vb.insert_tokens(text_to_virtual_buffer_tokens("create ", "create")) vb.insert_tokens(text_to_virtual_buffer_tokens("delete ", "delete")) diff --git a/tests/virtual_buffer/replacing_selection.py b/tests/virtual_buffer/replacing_selection.py index 7be9e1c..ddeef31 100644 --- a/tests/virtual_buffer/replacing_selection.py +++ b/tests/virtual_buffer/replacing_selection.py @@ -2,9 +2,14 @@ from ...virtual_buffer.buffer import VirtualBuffer from ...virtual_buffer.indexer import text_to_virtual_buffer_tokens from ..test import create_test_suite +from ...virtual_buffer.settings import VirtualBufferSettings + +def get_virtual_buffer() -> VirtualBuffer: + settings = VirtualBufferSettings(live_checking=False) + return VirtualBuffer(settings) def test_single_token_replacement(assertion): - vb = VirtualBuffer() + vb = get_virtual_buffer() vb.insert_tokens(text_to_virtual_buffer_tokens("Insert a new sentence. \n", "insert a new sentence")) vb.insert_tokens(text_to_virtual_buffer_tokens("Insert a second sentence. \n", "insert a second sentence")) vb.insert_tokens(text_to_virtual_buffer_tokens("Insert a third sentence.", "insert a third sentence")) @@ -50,7 +55,7 @@ def test_single_token_replacement(assertion): assertion( " Expect no selection detected", vb.is_selecting() == False) def test_multiple_token_replacement_left(assertion): - vb = VirtualBuffer() + vb = get_virtual_buffer() vb.insert_tokens(text_to_virtual_buffer_tokens("Suggest", "suggest")) vb.insert_tokens(text_to_virtual_buffer_tokens(" create", "create")) vb.insert_tokens(text_to_virtual_buffer_tokens(" delete", "delete")) @@ -79,7 +84,7 @@ def test_multiple_token_replacement_left(assertion): assertion( " Expect phrase to be merged", vb.tokens[-1].phrase == "suggestilation") def test_multiple_token_replacement_right(assertion): - vb = VirtualBuffer() + vb = get_virtual_buffer() vb.insert_tokens(text_to_virtual_buffer_tokens("Suggest ", "suggest")) vb.insert_tokens(text_to_virtual_buffer_tokens("create ", "create")) vb.insert_tokens(text_to_virtual_buffer_tokens("delete ", "delete")) diff --git a/tests/virtual_buffer/selecting.py b/tests/virtual_buffer/selecting.py index ea39990..ffdc84c 100644 --- a/tests/virtual_buffer/selecting.py +++ b/tests/virtual_buffer/selecting.py @@ -2,10 +2,14 @@ from ...virtual_buffer.buffer import VirtualBuffer from ...virtual_buffer.indexer import text_to_virtual_buffer_tokens from ..test import create_test_suite +from ...virtual_buffer.settings import VirtualBufferSettings +def get_virtual_buffer() -> VirtualBuffer: + settings = VirtualBufferSettings(live_checking=False) + return VirtualBuffer(settings) def test_selection_tracking(assertion): - vb = VirtualBuffer() + vb = get_virtual_buffer() vb.insert_tokens(text_to_virtual_buffer_tokens("Insert a new sentence. \n", "insert a new sentence")) vb.insert_tokens(text_to_virtual_buffer_tokens("Insert a second sentence. \n", "insert a second sentence")) vb.insert_tokens(text_to_virtual_buffer_tokens("Insert a third sentence.", "insert a third sentence")) diff --git a/tests/virtual_buffer/selection_caret_navigation.py b/tests/virtual_buffer/selection_caret_navigation.py index 76ada29..709bae3 100644 --- a/tests/virtual_buffer/selection_caret_navigation.py +++ b/tests/virtual_buffer/selection_caret_navigation.py @@ -1,9 +1,14 @@ from ...virtual_buffer.buffer import VirtualBuffer from ...virtual_buffer.indexer import text_to_virtual_buffer_tokens from ..test import create_test_suite +from ...virtual_buffer.settings import VirtualBufferSettings + +def get_virtual_buffer() -> VirtualBuffer: + settings = VirtualBufferSettings(live_checking=False) + return VirtualBuffer(settings) def test_track_selection_caret(assertion): - vb = VirtualBuffer() + vb = get_virtual_buffer() vb.insert_tokens(text_to_virtual_buffer_tokens("Insert ", "insert")) vb.insert_tokens(text_to_virtual_buffer_tokens("a ", "a")) vb.insert_tokens(text_to_virtual_buffer_tokens("new ", "new")) diff --git a/tests/virtual_buffer/selection_multiple_fuzzy_words.py b/tests/virtual_buffer/selection_multiple_fuzzy_words.py index 5bbc798..1ad4331 100644 --- a/tests/virtual_buffer/selection_multiple_fuzzy_words.py +++ b/tests/virtual_buffer/selection_multiple_fuzzy_words.py @@ -1,9 +1,14 @@ from ...virtual_buffer.buffer import VirtualBuffer from ...virtual_buffer.indexer import text_to_virtual_buffer_tokens from ..test import create_test_suite +from ...virtual_buffer.settings import VirtualBufferSettings + +def get_virtual_buffer() -> VirtualBuffer: + settings = VirtualBufferSettings(live_checking=False) + return VirtualBuffer(settings) def test_selecting_multiple_fuzzy_words(assertion): - vb = VirtualBuffer() + vb = get_virtual_buffer() tokens = [] tokens.extend(text_to_virtual_buffer_tokens("Insert ", "insert")) tokens.extend(text_to_virtual_buffer_tokens("a ", "a")) diff --git a/tests/virtual_buffer/selection_multiple_words.py b/tests/virtual_buffer/selection_multiple_words.py index 9af3e30..9a1cd18 100644 --- a/tests/virtual_buffer/selection_multiple_words.py +++ b/tests/virtual_buffer/selection_multiple_words.py @@ -1,9 +1,14 @@ from ...virtual_buffer.buffer import VirtualBuffer from ...virtual_buffer.indexer import text_to_virtual_buffer_tokens from ..test import create_test_suite +from ...virtual_buffer.settings import VirtualBufferSettings + +def get_virtual_buffer() -> VirtualBuffer: + settings = VirtualBufferSettings(live_checking=False) + return VirtualBuffer(settings) def test_select_multiple_words(assertion): - vb = VirtualBuffer() + vb = get_virtual_buffer() tokens = [] tokens.extend(text_to_virtual_buffer_tokens("Insert ", "insert")) tokens.extend(text_to_virtual_buffer_tokens("a ", "a")) diff --git a/tests/virtual_buffer/selection_multiple_words_and_matches_consistent.py b/tests/virtual_buffer/selection_multiple_words_and_matches_consistent.py index f3e3a99..512657b 100644 --- a/tests/virtual_buffer/selection_multiple_words_and_matches_consistent.py +++ b/tests/virtual_buffer/selection_multiple_words_and_matches_consistent.py @@ -1,9 +1,14 @@ from ...virtual_buffer.buffer import VirtualBuffer from ...virtual_buffer.indexer import text_to_virtual_buffer_tokens from ..test import create_test_suite +from ...virtual_buffer.settings import VirtualBufferSettings + +def get_virtual_buffer() -> VirtualBuffer: + settings = VirtualBufferSettings(live_checking=False) + return VirtualBuffer(settings) def get_filled_vb(): - vb = VirtualBuffer() + vb = get_virtual_buffer() vb.insert_tokens(text_to_virtual_buffer_tokens("Insert ", "insert")) vb.insert_tokens(text_to_virtual_buffer_tokens("two ", "two")) vb.insert_tokens(text_to_virtual_buffer_tokens("words ", "words")) diff --git a/tests/virtual_buffer/selection_performance.py b/tests/virtual_buffer/selection_performance.py index 898070f..209629c 100644 --- a/tests/virtual_buffer/selection_performance.py +++ b/tests/virtual_buffer/selection_performance.py @@ -3,6 +3,11 @@ from ..test import create_test_suite import time import os +from ...virtual_buffer.settings import VirtualBufferSettings + +def get_virtual_buffer() -> VirtualBuffer: + settings = VirtualBufferSettings(live_checking=False) + return VirtualBuffer(settings) token_1 = text_to_virtual_buffer_tokens("Insert ", "insert") token_2 = text_to_virtual_buffer_tokens("a ", "a") @@ -34,7 +39,7 @@ def fill_tokens(): tokens = [] def test_selection_performance_no_match(assertion): - vb = VirtualBuffer() + vb = get_virtual_buffer() vb.set_tokens(fill_tokens(), True) vb.reformat_tokens() @@ -47,7 +52,7 @@ def test_selection_performance_no_match(assertion): assertion( " Actual milliseconds: " + str((end_time - start_time) * 1000), made_performance_check) def test_selection_performance_no_match_lorum(assertion): - vb = VirtualBuffer() + vb = get_virtual_buffer() vb.set_tokens(fill_lorum_tokens(), True) vb.reformat_tokens() @@ -60,7 +65,7 @@ def test_selection_performance_no_match_lorum(assertion): assertion( " Actual milliseconds: " + str((end_time - start_time) * 1000), made_performance_check) def test_selection_performance_multiple_no_matches_lorum(assertion): - vb = VirtualBuffer() + vb = get_virtual_buffer() vb.set_tokens(fill_lorum_tokens(), True) assertion( " Starting from the end of a large document and searching for text that isn't in the document'...") @@ -73,7 +78,7 @@ def test_selection_performance_multiple_no_matches_lorum(assertion): def test_selection_performance_single_match(assertion): - vb = VirtualBuffer() + vb = get_virtual_buffer() vb.set_tokens(fill_tokens(), True) assertion( " Starting from the end of a large document and searching for a word that is in the document'...") @@ -85,7 +90,7 @@ def test_selection_performance_single_match(assertion): assertion( " Actual milliseconds: " + str((end_time - start_time) * 1000), made_performance_check) def test_selection_performance_multiple_match(assertion): - vb = VirtualBuffer() + vb = get_virtual_buffer() vb.set_tokens(fill_tokens(), True) assertion( " Starting from the end of a large document and searching for two words that are exactly in the document'...") @@ -97,7 +102,7 @@ def test_selection_performance_multiple_match(assertion): assertion( " Actual milliseconds: " + str((end_time - start_time) * 1000), made_performance_check) def test_selection_performance_multiple_no_matches(assertion): - vb = VirtualBuffer() + vb = get_virtual_buffer() vb.set_tokens(fill_tokens(), True) assertion( " Starting from the end of a large document and searching for text that isn't in the document'...") @@ -109,7 +114,7 @@ def test_selection_performance_multiple_no_matches(assertion): assertion( " Actual milliseconds: " + str((end_time - start_time) * 1000), made_performance_check) def test_selection_performance_single_fuzzy_match(assertion): - vb = VirtualBuffer() + vb = get_virtual_buffer() vb.set_tokens(fill_tokens(), True) assertion( " Starting from the end of a large document and searching for one word that is fuzzy within the document'...") @@ -121,7 +126,7 @@ def test_selection_performance_single_fuzzy_match(assertion): assertion( " Actual milliseconds: " + str((end_time - start_time) * 1000), made_performance_check) def test_selection_performance_multiple_fuzzy_match(assertion): - vb = VirtualBuffer() + vb = get_virtual_buffer() vb.set_tokens(fill_tokens(), True) assertion( " Starting from the end of a large document and searching for two words that are fuzzy within the document'...") @@ -133,7 +138,7 @@ def test_selection_performance_multiple_fuzzy_match(assertion): assertion( " Actual milliseconds: " + str((end_time - start_time) * 1000), made_performance_check) def test_selection_performance_four_fuzzy_matches(assertion): - vb = VirtualBuffer() + vb = get_virtual_buffer() vb.set_tokens(fill_tokens(), True) assertion( " Starting from the end of a large document and searching for four words that are fuzzy within the document'...") @@ -145,7 +150,7 @@ def test_selection_performance_four_fuzzy_matches(assertion): assertion( " Actual milliseconds: " + str((end_time - start_time) * 1000), made_performance_check) def test_selection_performance_four_fuzzy_matches_lorum(assertion): - vb = VirtualBuffer() + vb = get_virtual_buffer() vb.set_tokens(fill_lorum_tokens(), True) assertion( " Starting from the end of a large document without a lot of duplicates and searching for four words that are fuzzy within the document'...") diff --git a/tests/virtual_buffer/selection_range.py b/tests/virtual_buffer/selection_range.py index e215e64..fe26b17 100644 --- a/tests/virtual_buffer/selection_range.py +++ b/tests/virtual_buffer/selection_range.py @@ -1,10 +1,15 @@ from ...virtual_buffer.buffer import VirtualBuffer from ...virtual_buffer.indexer import text_to_virtual_buffer_tokens from ..test import create_test_suite +from ...virtual_buffer.settings import VirtualBufferSettings + +def get_virtual_buffer() -> VirtualBuffer: + settings = VirtualBufferSettings(live_checking=False) + return VirtualBuffer(settings) def test_select_single_word_and_extending(assertion): - vb = VirtualBuffer() - vb.shift_selection = True + vb = get_virtual_buffer() + vb.settings.shift_selection = True vb.insert_tokens(text_to_virtual_buffer_tokens("Insert ", "insert")) vb.insert_tokens(text_to_virtual_buffer_tokens("a ", "a")) vb.insert_tokens(text_to_virtual_buffer_tokens("new ", "new")) @@ -41,8 +46,8 @@ def test_select_single_word_and_extending(assertion): assertion( " Should go right 22 times to connect the end of 'Insert ' with 'sentence.'", keys[2] == "shift-right:22") def test_selecting_multiple_phrases_with_duplicates(assertion): - vb = VirtualBuffer() - vb.shift_selection = True + vb = get_virtual_buffer() + vb.settings.shift_selection = True vb.insert_tokens(text_to_virtual_buffer_tokens("Insert ", "insert")) vb.insert_tokens(text_to_virtual_buffer_tokens("a ", "a")) vb.insert_tokens(text_to_virtual_buffer_tokens("new ", "new")) @@ -94,8 +99,8 @@ def test_selecting_multiple_phrases_with_duplicates(assertion): assertion( " And then should move from the start of the previous selection to the end of the new selection", keys[1] == "shift-right:10") def test_select_phrase_inside_selection_with_duplicates(assertion): - vb = VirtualBuffer() - vb.shift_selection = True + vb = get_virtual_buffer() + vb.settings.shift_selection = True vb.insert_tokens(text_to_virtual_buffer_tokens("Insert ", "insert")) vb.insert_tokens(text_to_virtual_buffer_tokens("a ", "a")) vb.insert_tokens(text_to_virtual_buffer_tokens("new ", "new")) diff --git a/tests/virtual_buffer/selection_tests.py b/tests/virtual_buffer/selection_tests.py index e29154d..c19e325 100644 --- a/tests/virtual_buffer/selection_tests.py +++ b/tests/virtual_buffer/selection_tests.py @@ -6,6 +6,12 @@ from ...virtual_buffer.typing import SELECTION_THRESHOLD, CORRECTION_THRESHOLD from talon import resource from ..test import create_test_suite +from ...virtual_buffer.settings import VirtualBufferSettings + +def get_virtual_buffer() -> VirtualBuffer: + settings = VirtualBufferSettings(live_checking=False) + return VirtualBuffer(settings) + test_path = os.path.dirname(os.path.realpath(__file__)) # Auto reload test cases if the csvs are changed @@ -14,7 +20,7 @@ #resource.open(os.path.join(test_path, "testcase_selection.csv")) def get_uncached_virtual_buffer(): - vb = VirtualBuffer() + vb = get_virtual_buffer() # Reset the phonetic search to make sure there is no influence from user settings vb.matcher.phonetic_search = PhoneticSearch() diff --git a/tests/virtual_buffer/single_line_support.py b/tests/virtual_buffer/single_line_support.py index 374faeb..22f4dba 100644 --- a/tests/virtual_buffer/single_line_support.py +++ b/tests/virtual_buffer/single_line_support.py @@ -4,19 +4,24 @@ from ..test import create_test_suite from ...phonetics.phonetics import PhoneticSearch from ...virtual_buffer.matcher import VirtualBufferMatcher +from ...virtual_buffer.settings import VirtualBufferSettings + +def get_virtual_buffer() -> VirtualBuffer: + settings = VirtualBufferSettings(live_checking=False) + return VirtualBuffer(settings) def get_filled_vb(): search = PhoneticSearch() search.set_homophones("") search.set_phonetic_similiarities("") - vb = VirtualBuffer() + vb = get_virtual_buffer() vb.matcher = VirtualBufferMatcher(search) - vb.shift_selection = True - vb.caret_tracker.multiline_supported = False - vb.caret_tracker.clear_key = "" - vb.caret_tracker.start_of_line_key = "home" - vb.caret_tracker.end_of_line_key = "end" + vb.settings.shift_selection = True + vb.settings.multiline_supported = False + vb.settings.clear_key = "" + vb.settings.start_of_line_key = "home" + vb.settings.end_of_line_key = "end" vb.insert_tokens(text_to_virtual_buffer_tokens("Insert ", "insert")) vb.insert_tokens(text_to_virtual_buffer_tokens("two ", "two")) vb.insert_tokens(text_to_virtual_buffer_tokens("words ", "words")) diff --git a/tests/virtual_buffer/terminal_enter_clear.py b/tests/virtual_buffer/terminal_enter_clear.py index 46819bd..58a241b 100644 --- a/tests/virtual_buffer/terminal_enter_clear.py +++ b/tests/virtual_buffer/terminal_enter_clear.py @@ -4,16 +4,21 @@ from ..test import create_test_suite from ...phonetics.phonetics import PhoneticSearch from ...virtual_buffer.matcher import VirtualBufferMatcher +from ...virtual_buffer.settings import VirtualBufferSettings + +def get_virtual_buffer() -> VirtualBuffer: + settings = VirtualBufferSettings(live_checking=False) + return VirtualBuffer(settings) def get_filled_vb(with_multiline = False): search = PhoneticSearch() search.set_homophones("") search.set_phonetic_similiarities("") - vb = VirtualBuffer() + vb = get_virtual_buffer() vb.matcher = VirtualBufferMatcher(search) - vb.shift_selection = False - vb.set_clear_key("enter") + vb.settings.shift_selection = False + vb.settings.clear_key = "enter" vb.insert_tokens(text_to_virtual_buffer_tokens("Insert ", "insert")) vb.insert_tokens(text_to_virtual_buffer_tokens("two ", "two")) vb.insert_tokens(text_to_virtual_buffer_tokens("words ", "words")) diff --git a/tests/virtual_buffer/terminal_remove_selection.py b/tests/virtual_buffer/terminal_remove_selection.py index 27591de..7381533 100644 --- a/tests/virtual_buffer/terminal_remove_selection.py +++ b/tests/virtual_buffer/terminal_remove_selection.py @@ -4,15 +4,20 @@ from ..test import create_test_suite from ...phonetics.phonetics import PhoneticSearch from ...virtual_buffer.matcher import VirtualBufferMatcher +from ...virtual_buffer.settings import VirtualBufferSettings + +def get_virtual_buffer() -> VirtualBuffer: + settings = VirtualBufferSettings(live_checking=False) + return VirtualBuffer(settings) def get_filled_vb(with_multiline = False): search = PhoneticSearch() search.set_homophones("") search.set_phonetic_similiarities("") - vb = VirtualBuffer() + vb = get_virtual_buffer() vb.matcher = VirtualBufferMatcher(search) - vb.shift_selection = False + vb.settings.shift_selection = False vb.insert_tokens(text_to_virtual_buffer_tokens("Insert ", "insert")) vb.insert_tokens(text_to_virtual_buffer_tokens("two ", "two")) vb.insert_tokens(text_to_virtual_buffer_tokens("words ", "words")) diff --git a/tests/virtual_buffer/terminal_selection_range.py b/tests/virtual_buffer/terminal_selection_range.py index 33ca552..1ed1fe6 100644 --- a/tests/virtual_buffer/terminal_selection_range.py +++ b/tests/virtual_buffer/terminal_selection_range.py @@ -1,10 +1,15 @@ from ...virtual_buffer.buffer import VirtualBuffer from ...virtual_buffer.indexer import text_to_virtual_buffer_tokens from ..test import create_test_suite +from ...virtual_buffer.settings import VirtualBufferSettings + +def get_virtual_buffer() -> VirtualBuffer: + settings = VirtualBufferSettings(live_checking=False) + return VirtualBuffer(settings) def test_virtual_select_single_word_and_extending(assertion): - vb = VirtualBuffer() - vb.shift_selection = False + vb = get_virtual_buffer() + vb.settings.shift_selection = False vb.insert_tokens(text_to_virtual_buffer_tokens("Insert ", "insert")) vb.insert_tokens(text_to_virtual_buffer_tokens("a ", "a")) vb.insert_tokens(text_to_virtual_buffer_tokens("new ", "new")) diff --git a/virtual_buffer/buffer.py b/virtual_buffer/buffer.py index 898b39f..17abba6 100644 --- a/virtual_buffer/buffer.py +++ b/virtual_buffer/buffer.py @@ -6,6 +6,7 @@ from ..phonetics.actions import phonetic_search from .indexer import text_to_phrase, normalize_text, reindex_tokens import re +from .settings import VirtualBufferSettings, virtual_buffer_settings mod = Module() ctx = Context() @@ -22,13 +23,14 @@ class VirtualBuffer: caret_tracker: CaretTracker = None virtual_selection = None last_action_type = "insert" - shift_selection = True + settings: VirtualBufferSettings = None - def __init__(self): - self.caret_tracker = CaretTracker() + def __init__(self, settings: VirtualBufferSettings = None): + global virtual_buffer_settings + self.settings = settings if settings is not None else virtual_buffer_settings + self.caret_tracker = CaretTracker(settings=self.settings) self.matcher = VirtualBufferMatcher(phonetic_search) self.set_tokens() - print("CREATING NEW BUFFER!!!") def is_selecting(self) -> bool: return self.caret_tracker.is_selecting() @@ -102,14 +104,14 @@ def insert_token(self, token_to_insert: VirtualBufferToken, reformat = True): # Edge case - If we clear the input on Enter press, # We also need to clear if a newline is added through insertion - if self.caret_tracker.clear_key.lower() == "enter" and token_to_insert.text.endswith("\n"): + if self.settings.get_clear_key().lower() == "enter" and token_to_insert.text.endswith("\n"): self.clear_tokens() return # Inserting newlines is different from enter presses in single line fields # As it clears the input field and makes the final line the one used # Rather than ignoring the new line - if self.caret_tracker.multiline_supported == False and token_to_insert.text.endswith("\n"): + if self.settings.has_multiline_support() == False and token_to_insert.text.endswith("\n"): self.clear_tokens() return @@ -532,17 +534,17 @@ def select_until_end(self, phrase: str = "") -> List[str]: if phrase != "": start_token = self.find_token_by_phrase(phrase, 0, True, False) if start_token: - if self.shift_selection: + if self.settings.has_shift_selection(): keys.extend(self.navigate_to_token(start_token, 0, False)) keys.extend(self.select_token(self.tokens[-1], True)) else: keys.extend(self.navigate_to_token(self.tokens[-1])) self.virtual_selection = [start_token, self.tokens[-1]] - elif not self.shift_selection and len(self.virtual_selection) > 0: + elif not self.settings.has_shift_selection() and len(self.virtual_selection) > 0: self.virtual_selection = [self.virtual_selection[0], self.tokens[-1]] keys.extend(self.navigate_to_token(self.tokens[-1])) - if self.shift_selection: + if self.settings.has_shift_selection(): keys.extend(self.select_token(self.tokens[-1], True)) return keys @@ -577,8 +579,7 @@ def select_token_range(self, start_token: VirtualBufferToken, end_token: Virtual self.virtual_selection = [] keys = [] - print("USES SHIFT SELECTION!?!?!?!", self.shift_selection) - if self.shift_selection: + if self.settings.has_shift_selection(): if not extend_selection: keys = self.navigate_to_token(start_token, 0) else: @@ -681,6 +682,11 @@ def navigate_to_token(self, token: VirtualBufferToken, char_position: int = -1, self.last_action_type = self.caret_tracker.last_caret_movement else: key_events = [] + + # Make sure we don't accidentally remove virtual selections + if keep_selection == False and len(self.virtual_selection): + self.virtual_selection = [] + return key_events def get_current_formatters(self) -> List[str]: @@ -725,7 +731,7 @@ def get_next_text(self) -> str: def remove_virtual_selection(self) -> List[str]: keys = [] - if len(self.virtual_selection) > 0: + if len(self.virtual_selection) > 0: total_amount = 0 if self.virtual_selection[0].line_index == self.virtual_selection[-1].line_index: total_amount = self.virtual_selection[0].index_from_line_end - self.virtual_selection[-1].index_from_line_end @@ -740,26 +746,6 @@ def remove_virtual_selection(self) -> List[str]: # TODO - SUPPORT NO BACKSPACE WRAPPING if total_amount: - keys = ["backspace:" + str(total_amount)] - - return keys - - def set_shift_selection(self, shift_selection: bool): - self.caret_tracker.shift_selection = shift_selection - self.shift_selection = shift_selection - print("SETTING SHIFT SELECTION ON BUFFER", shift_selection) - - def set_multiline_supported(self, multiline_supported: bool): - self.caret_tracker.multiline_supported = multiline_supported - - def set_clear_key(self, clear_key: str): - self.caret_tracker.clear_key = clear_key - - def set_end_of_line_key(self, end_of_line_key: str): - self.caret_tracker.end_of_line_key = end_of_line_key - - def set_start_of_line_key(self, start_of_line_key: str): - self.caret_tracker.start_of_line_key = start_of_line_key + keys = [self.settings.get_remove_character_left_key() + ":" + str(total_amount)] - def set_clear_line_key(self, clear_line_key: str): - self.caret_tracker.clear_line_key = clear_line_key \ No newline at end of file + return keys \ No newline at end of file diff --git a/virtual_buffer/caret_tracker.py b/virtual_buffer/caret_tracker.py index 2178628..3a1cb81 100644 --- a/virtual_buffer/caret_tracker.py +++ b/virtual_buffer/caret_tracker.py @@ -1,6 +1,7 @@ from typing import List import re import platform +from .settings import VirtualBufferSettings, virtual_buffer_settings # CARET MARKERS # TODO APPEND RANDOM NUMBER FOR LESS COLLISIONS? @@ -56,6 +57,8 @@ class CaretTracker: system: str = "" + + settings: VirtualBufferSettings = None is_macos: bool = False text_buffer: str = "" enable_caret_tracking: bool = True @@ -64,16 +67,11 @@ class CaretTracker: selection_caret_marker = (-1, -1) last_caret_movement: str = "" - shift_selection = True - multiline_supported = True - clear_key:str = "" - clear_line_key:str = "" - end_of_line_key:str = "end" - start_of_line_key:str = "home" - - def __init__(self, system = platform.system()): + def __init__(self, system = platform.system(), settings: VirtualBufferSettings = None): + global virtual_buffer_settings self.system = system self.is_macos = system == "Darwin" + self.settings = settings if settings is not None else virtual_buffer_settings self.clear() def clear(self): @@ -99,14 +97,14 @@ def apply_key(self, key: str) -> bool: continue # Clear the full context after the clear key was pressed - if key_modifier[0] in self.clear_key: + if key_modifier[0] in self.settings.get_clear_key(): self.clear() key_used = True return if self.is_macos: if "alt" in key: - self.selecting_text = self.shift_selection and "shift" in key or self.shift_down + self.selecting_text = self.settings.has_shift_selection() and "shift" in key or self.shift_down key_combinations = key_modifier[0].lower().split("-") if "left" in key: left_movements = 1 @@ -131,7 +129,7 @@ def apply_key(self, key: str) -> bool: # Control keys are slightly inconsistent across programs, but generally they skip a word elif "ctrl" in key: - self.selecting_text = self.shift_selection and "shift" in key or self.shift_down + self.selecting_text = self.settings.has_shift_selection() and "shift" in key or self.shift_down key_combinations = key_modifier[0].lower().split("-") if "left" in key: left_movements = 1 @@ -166,7 +164,7 @@ def apply_key(self, key: str) -> bool: key_used = True elif "left" in key and not ("cmd" in key or "super" in key): - selecting_text = self.shift_selection and "shift" in key or self.shift_down + selecting_text = self.settings.has_shift_selection() and "shift" in key or self.shift_down left_movements = 1 if len(key_modifier) >= 1 and key_modifier[-1].isnumeric(): left_movements = int(key_modifier[-1]) @@ -179,7 +177,7 @@ def apply_key(self, key: str) -> bool: key_used = True self.last_caret_movement = "left" elif "right" in key and not ("cmd" in key or "super" in key): - selecting_text = self.shift_selection and "shift" in key or self.shift_down + selecting_text = self.settings.has_shift_selection() and "shift" in key or self.shift_down right_movements = 1 if len(key_modifier) >= 1 and key_modifier[-1].isnumeric(): right_movements = int(key_modifier[-1]) @@ -193,16 +191,16 @@ def apply_key(self, key: str) -> bool: self.last_caret_movement = "right" # TODO PROPER SPLIT UP WITH OTHER MODIFIERS - elif self.end_of_line_key in key: + elif self.settings.get_end_of_line_key() in key: self.mark_caret_to_end_of_line() key_used = True self.last_caret_movement = "right" # TODO PROPER SPLIT UP WITH OTHER MODIFIERS - elif self.start_of_line_key in key: + elif self.settings.get_start_of_line_key() in key: # In VSCODE - Moving to the start of the line depends on the whitespace - if self.multiline_supported: + if self.settings.has_multiline_support(): self.mark_line_as_coarse() else: self.mark_caret_to_start_of_line() @@ -219,7 +217,7 @@ def apply_key(self, key: str) -> bool: # 2 - Ignoring the character altogether # 3 - Changing the line completely ( history of terminals ) # So at least mark the line as coarse in that case - if self.multiline_supported == False: + if self.settings.has_multiline_support() == False: self.mark_line_as_coarse() else: for _ in range(up_movements): @@ -235,7 +233,7 @@ def apply_key(self, key: str) -> bool: # 2 - Ignoring the character altogether # 3 - Changing the line completely ( history of terminals ) # So at least mark the line as coarse in that case - if self.multiline_supported == False: + if self.settings.multiline_supported() == False: self.mark_line_as_coarse() else: for _ in range(down_movements): @@ -302,7 +300,7 @@ def mark_line_as_coarse(self, difference_from_line: int = 0): # If the line falls outside of the known line count, we have lost the caret position entirely # And must clear the input entirely - line_out_of_known_bounds = self.multiline_supported and ( line_with_caret + difference_from_line < 0 or line_with_caret + difference_from_line > len(lines) ) + line_out_of_known_bounds = self.settings.has_multiline_support() and ( line_with_caret + difference_from_line < 0 or line_with_caret + difference_from_line > len(lines) ) if line_out_of_known_bounds: before_caret = [] after_caret = [] @@ -753,13 +751,13 @@ def navigate_to_position(self, line_index: int, character_from_end: int, deselec # Move to line end to have a consistent line ending, as that seems to be consistent if current[1] == -1: - keys.append(self.end_of_line_key) + keys.append(self.settings.get_end_of_line_key()) current = (current[0], 0) # Move to the right character position if not character_from_end == current[1] and current[1] != -1: char_diff = current[1] - character_from_end - renewed_selection = selecting and not deselection and not self.shift_down and self.shift_selection + renewed_selection = selecting and not deselection and not self.shift_down and self.settings.has_shift_selection() keys.append( ("shift-" if renewed_selection else "" ) + ("left:" if char_diff < 0 else "right:") + str(abs(char_diff))) if current[0] == -1 or current[1] == -1: diff --git a/virtual_buffer/input_context.py b/virtual_buffer/input_context.py index c28b3d3..6391206 100644 --- a/virtual_buffer/input_context.py +++ b/virtual_buffer/input_context.py @@ -60,7 +60,6 @@ def destroy(self): self.buffer = None def set_shift_selection(self, shift_selection: bool): - print("SET SHIFT SELECTION IN CONTEXT", self.pid, self.app_name, shift_selection) self.buffer.set_shift_selection(shift_selection) def set_multiline_supported(self, multiline_supported: bool): diff --git a/virtual_buffer/manager.py b/virtual_buffer/manager.py index 356f926..f4f3084 100644 --- a/virtual_buffer/manager.py +++ b/virtual_buffer/manager.py @@ -2,6 +2,7 @@ from .input_context_manager import InputContextManager from .input_fixer import InputFixer from .typing import CORRECTION_THRESHOLD, SELECTION_THRESHOLD +from .settings import VirtualBufferSettings, virtual_buffer_settings from ..phonetics.detection import EXACT_MATCH from typing import List, Union import json @@ -34,9 +35,11 @@ class VirtualBufferManager: tracking_lock: str = "" use_last_set_formatter = False - def __init__(self): + def __init__(self, settings: VirtualBufferSettings = None): + global virtual_buffer_settings self.context = InputContextManager(actions.user.marithime_update_sensory_state) self.fixer = InputFixer() + self.settings = settings if settings is not None else virtual_buffer_settings # self.fixer.verbose = True # TODO - Improve logging for fixes when the fixer is improved @@ -93,7 +96,7 @@ def move_caret_back(self, keep_selection: bool = False) -> List[str]: else: return vbm.navigate_to_token(last_token, -1, keep_selection) else: - return ["end"] + return [vbm.settings.get_end_of_line_key()] def select_phrases(self, phrases: List[str], until_end = False, for_correction=False) -> List[str]: self.disable_tracking() @@ -249,27 +252,27 @@ def clear_keys(self, backwards = True) -> List[str]: context = vbm.determine_context() if self.is_selecting(): - return [settings.get("user.marithime_context_remove_letter")] + return [self.settings.get_remove_character_left_key()] elif self.is_virtual_selecting(): - return vbm.clear_virtual_selection(settings.get("user.marithime_context_remove_letter")) + return vbm.remove_virtual_selection() if context.current is not None: if context.character_index == 0 and backwards and context.previous is not None: - return [settings.get("user.marithime_context_remove_letter") + ":" + str(len(context.previous.text))] + return [self.settings.get_remove_character_left_key() + ":" + str(len(context.previous.text))] elif context.character_index == len(context.current.text): if not backwards and context.next is not None: - return [settings.get("user.marithime_context_remove_forward_letter") + ":" + str(len(context.next.text))] + return [self.settings.get_remove_character_right_key() + ":" + str(len(context.next.text))] elif backwards: - return [settings.get("user.marithime_context_remove_letter") + ":" + str(len(context.current.text))] + return [self.settings.get_remove_character_left_key() + ":" + str(len(context.current.text))] if context.character_index > 0 and context.character_index < len(context.current.text) - 1: if backwards: - return [settings.get("user.marithime_context_remove_letter") + ":" + str(context.character_index)] + return [self.settings.get_remove_character_left_key() + ":" + str(context.character_index)] else: - return [settings.get("user.marithime_context_remove_forward_letter") + ":" + str(len(context.current.text) - context.character_index)] + return [self.settings.get_remove_character_right_key() + ":" + str(len(context.current.text) - context.character_index)] - return [settings.get("user.marithime_context_remove_word") if backwards else settings.get("user.marithime_context_remove_forward_word")] + return [self.settings.get_remove_word_left_key() if backwards else self.settings.get_remove_word_right_key()] def index(self): vbm = self.context.get_current_context().buffer @@ -277,7 +280,7 @@ def index(self): words_list = [] for token in vbm.tokens: words_list.append(token.phrase) - #ctx.lists["user.marithime_indexed_words"] = words_list + ctx.lists["user.marithime_indexed_words"] = words_list tags = [] token_index = vbm.determine_token_index() @@ -296,39 +299,11 @@ def index_textarea(self): def focus_changed(self, event): context_switched = self.context.switch_context(event) if context_switched: - # Update the context after the focus event to make sure we are updating the right context - self.set_shift_selection(settings.get("user.marithime_context_shift_selection")) - self.set_clear_key(settings.get("user.marithime_context_clear_key")) - self.set_multiline_supported(settings.get("user.marithime_context_multiline_supported")) - self.set_clear_line_key(settings.get("user.marithime_context_remove_line_key")) - self.set_start_of_line_key(settings.get("user.marithime_context_start_line_key")) - self.set_end_of_line_key(settings.get("user.marithime_context_end_line_key")) - - # Then index the values self.index() - def window_closed(self, event): self.context.close_context(event) - def set_shift_selection(self, shift_selection: bool): - self.context.set_shift_selection(shift_selection) - - def set_multiline_supported(self, multiline_supported: bool): - self.context.set_multiline_supported(multiline_supported) - - def set_clear_key(self, clear_key: str): - self.context.set_clear_key(clear_key) - - def set_clear_line_key(self, clear_line_key: str): - self.context.set_clear_line_key(clear_line_key) - - def set_start_of_line_key(self, start_of_line_key: str): - self.context.set_start_of_line_key(start_of_line_key) - - def set_end_of_line_key(self, end_of_line_key: str): - self.context.set_end_of_line_key(end_of_line_key) - def update_language(language: str): global mutator if not language: @@ -524,36 +499,6 @@ def marithime_update_sensory_state(scanning: bool, level: str, caret_confidence: """Visually or audibly update the state for the user""" pass - def marithime_dump_context(): - """Dump the current state of the virtual buffer for debugging purposes""" - mutator = get_mutator() - mutator.disable_tracking("DUMP") - actions.insert("Available contexts:" ) - for context in mutator.context.contexts: - actions.insert( " " + context.app_name + " - " + context.title + " - PID - " + str(context.pid)) - - actions.key("enter") - actions.insert("Using " + mutator.context.get_current_context().title + " - PID " + str(mutator.context.get_current_context().pid )) - actions.key("enter") - actions.insert(mutator.context.get_current_context().buffer.caret_tracker.text_buffer) - actions.key("enter:2") - tokens = [] - for token in mutator.context.get_current_context().buffer.tokens: - tokens.append({ - "index_from_line_end": token.index_from_line_end, - "line_index": token.line_index, - "phrase": token.phrase, - "text": token.text - }) - actions.insert(json.dumps(tokens)) - actions.key("enter") - actions.key("enter") - - actions.insert(json.dumps(list(mutator.fixer.done_fixes.keys()))) - actions.key("enter") - - mutator.enable_tracking("DUMP") - def marithime_toggle_track_context(): """Toggle between tracking and not tracking the marithime virtual buffer context""" mutator = get_mutator() @@ -579,7 +524,20 @@ def marithime_show_context() -> str: current_context = mutator.context.get_current_context() lines = "Using " + current_context.title + " - PID " + str(current_context.pid ) + "\n" lines += "------------------------------------------------\n" - lines += (current_context.buffer.caret_tracker.text_buffer).replace("$CARET", "|^|") - # TODO only show relevant lines around cursor? + text_lines = current_context.buffer.caret_tracker.text_buffer.splitlines() + found_index = -1 + for index, text_line in enumerate(text_lines): + if "$CARET" in text_line: + found_index = index + + # Only show the relevant lines around the cursor + if found_index == -1: + lines += (current_context.buffer.caret_tracker.text_buffer).replace("$CARET", "|^|") + else: + if found_index > 0: + lines += text_lines[found_index - 1] + "\n" + lines += text_lines[found_index].replace("$CARET", "|^|") + if found_index < len(text_lines) - 1: + lines += "\n" + text_lines[found_index + 1] return lines \ No newline at end of file diff --git a/virtual_buffer/settings.py b/virtual_buffer/settings.py new file mode 100644 index 0000000..270e10f --- /dev/null +++ b/virtual_buffer/settings.py @@ -0,0 +1,79 @@ +from talon import settings + +# Class that keeps the context for what kind of key behavior the current program and input field has +# Kept in a single class to make sure it doesn't get out of sync or new empty input contexts get created +class VirtualBufferSettings: + + # Whether to use live talon checking + # Disabled during testing to make sure we don't invoke the talon context system during start up + live_checking = True + + shift_selection = True + multiline_supported = True + + # Cursor navigation keys + end_of_line_key:str = "end" + start_of_line_key:str = "home" + + # Content clearing keys + clear_key:str = "" + clear_line_key:str = "" + remove_letter_key:str = "backspace" + remove_forward_letter_key:str = "delete" + remove_word_key:str = "ctrl-backspace" + remove_forward_word_key:str = "ctrl-delete" + + def __init__(self, live_checking=False): + self.live_checking = live_checking + + def has_shift_selection(self): + if self.live_checking: + self.shift_selection = settings.get("user.marithime_context_shift_selection") > 0 + return self.shift_selection + + def has_multiline_support(self): + if self.live_checking: + self.multiline_supported = settings.get("user.marithime_context_multiline_supported") > 0 + return self.multiline_supported + + def get_clear_key(self): + if self.live_checking: + self.clear_key = settings.get("user.marithime_context_clear_key") + return self.clear_key + + def get_end_of_line_key(self): + if self.live_checking: + self.end_of_line_key = settings.get("user.marithime_context_end_line_key") + return self.end_of_line_key + + def get_start_of_line_key(self): + if self.live_checking: + self.end_of_line_key = settings.get("user.marithime_context_start_line_key") + return self.end_of_line_key + + def get_remove_line_key(self): + if self.live_checking: + self.remove_line_key = settings.get("user.marithime_context_remove_line_key") + return self.remove_line_key + + def get_remove_character_left_key(self): + if self.live_checking: + self.remove_letter_key = settings.get("user.marithime_context_remove_letter") + return self.remove_letter_key + + def get_remove_character_right_key(self): + if self.live_checking: + self.remove_forward_letter_key = settings.get("user.marithime_context_remove_forward_letter") + return self.remove_forward_letter_key + + def get_remove_word_left_key(self): + if self.live_checking: + self.remove_word_key = settings.get("user.marithime_context_remove_word") + return self.remove_word_key + + def get_remove_word_right_key(self): + if self.live_checking: + self.remove_word_forward_key = settings.get("user.marithime_context_remove_forward_word") + return self.remove_word_forward_key + +virtual_buffer_settings = VirtualBufferSettings(True) \ No newline at end of file From a1ebeff8b9f2e60f78f304a38ccf7085bb645205 Mon Sep 17 00:00:00 2001 From: chaosparrot Date: Sun, 29 Dec 2024 16:09:08 +0100 Subject: [PATCH 20/24] Improved support for Cmder and tested support for visual studio terminal --- README.md | 3 +++ programs/SUPPORTED.md | 34 +++++++++++++++++-------- programs/terminal.talon | 1 - programs/windows/cmder.talon | 6 +++++ virtual_buffer/input_context_manager.py | 3 +-- 5 files changed, 34 insertions(+), 13 deletions(-) create mode 100644 programs/windows/cmder.talon diff --git a/README.md b/README.md index 7ead682..4201f82 100644 --- a/README.md +++ b/README.md @@ -101,6 +101,9 @@ We need to find a way to deal with word wrap, meaning things being on a single l [] - Zero width space indexation selection fix When a zero width space indexation is used, it is possible that a current selection is removed. We can fix that selection afterwards so we don't have issues where content is removed unnecessarily +[] - Add clipboard pasting insert support +Right now it isn't possible to use clipboard pasting as a way to insert things rather than typing out the characters one by one. This makes the insertion slower than it could be. This can be done with 'Ctrl+C' and 'Ctrl+V', or 'Ctrl+Shift+C' and 'Ctrl+Shift+V' in terminals. Though we probably want to use `action.edit.paste()` to make it compatible with other packages. We do need to be aware that in terminals there is a possibility that `Remove trailing white-space when pasting` is turned on, which might cause desyncs. + #### Programs [] - Improved MacOS support diff --git a/programs/SUPPORTED.md b/programs/SUPPORTED.md index 986aa94..1e5d1a6 100644 --- a/programs/SUPPORTED.md +++ b/programs/SUPPORTED.md @@ -1,6 +1,6 @@ # Supported programs -This is a list of programs that are known to be supported. Others might have varying degrees of support depending on how well accessibility is handled. +This is a list of programs that are known to be supported. Others might have varying degrees of support depending on how well accessibility is handled. Testing is generally done on the beta release of Talon Voice, for windows on Windows 11 ( 10 if specifically mentioned ). ## Operating system support @@ -16,20 +16,34 @@ TODO ## Code editor / IDE support -| Program | OS | Cursor tracking | Content tracking | Selection | Notes | -|-----------------|---------|-----------------|------------------|-----------|-------| -| VSCode editor | Windows | Yes* | Yes* | Shift | This requires turning on accessiblity support | -| VSCode editor | MacOS | Yes* | Yes* | Shift | This requries turning on accessibility support `Shift+Option+F1`| +| Program | OS | Cursor tracking | Content tracking | Notes | +|-----------------|---------|-----------------|------------------|-------| +| VSCode editor | Windows | Yes* | Yes* | This requires turning on accessiblity support | +| VSCode editor | MacOS | Yes* | Yes* | This requries turning on accessibility support `Shift+Option+F1`| ## Terminal support | Program | OS | Cursor tracking | Content tracking | Selection | Notes | |-----------------|---------|-----------------|------------------|-----------|-------| -| Terminal | Windows | Key tracking | Key tracking | Virtual | No accessibility APIs used | -| Git BASH | Windows | Key tracking | Key tracking | Virtual | No accessibility APIs used | -| CMD | Windows | Key tracking | Key tracking | Virtual | No accessibility APIs used | -| PowerShell | Windows | Key tracking | Key tracking | Shift | PowerShell supports shift selection! | +| Terminal | Windows | Key tracking | Key tracking | Virtual | | +| Git BASH | Windows | Key tracking | Key tracking | Virtual | | +| CMD | Windows | Key tracking | Key tracking | Virtual | | +| Cygwin | Windows | Key tracking | Key tracking | Virtual | | +| ConEmu | Windows | Key tracking | Key tracking | Virtual | | +| Cmder | Windows | Key tracking | Key tracking | Shift | | +| PowerShell | Windows | Key tracking | Key tracking | Shift | | +| VSCode terminal | Windows | Key tracking | Key tracking | Virtual | This requires [changing the windows title as described in talonhub community](https://github.com/talonhub/community/tree/main/apps/vscode#terminal) | Terminal programs generally aren't as well supported as other programs with are more rich set of accessibility APIs. Not to mention that text editors such as VIM, emacs and nano each have their own set of hotkeys to navigate the text displayed, so key tracking becomes increasingly hard to do and prone for desyncs. -While it is possible to tackle this, it is also quite hard to do without major time investments and plugins designed for each text editor available. \ No newline at end of file +It seems that the `TextPattern` is supported on Windows 11 for terminals, so it might be worth exploring this more in the future, though each terminal program has a different leading character set ( '$ ' for bash-likes, `λ ` for Cmder, '...>' for PowerShell et cetera ) and we can realistically only support single line programs for now. + +While it is possible to tackle this, it is also quite hard to do without major time investments and plugins designed for each text editor available. + +Terminals are detected by the `tag: terminal` which is generally retrieved from .talon files like the ones shown in the talon community repository. + +### Virtual selection + +Virtual selection is used when shift selection isn't supported. What this boils down to is that the text caret will be set after the selected text. Follow up commands, like inserting text, will be have as if the text was actually selected, meaning we would replace the selection in the case of replacing text. This allows you to continue using the same exact commands without worrying about the internals of the specific programs. + +While it would be possible to support text selection like the mode supported by holding down Shift in ConEmu, or using mark modes like the ones shown when pressing `Ctrl+Shift+M` in a windows terminal, doing so would further complicate selection, and might not work with Text editors either. \ No newline at end of file diff --git a/programs/terminal.talon b/programs/terminal.talon index 96770dd..a5cc514 100644 --- a/programs/terminal.talon +++ b/programs/terminal.talon @@ -2,7 +2,6 @@ os: macos os: windows os: linux tag: terminal -# TODO - Windows Powershell supports shift selection?! - settings(): user.marithime_indexing_strategy = "disabled" diff --git a/programs/windows/cmder.talon b/programs/windows/cmder.talon new file mode 100644 index 0000000..88d5cdf --- /dev/null +++ b/programs/windows/cmder.talon @@ -0,0 +1,6 @@ +os: windows +tag: terminal +win.title: /Cmder/ +- +settings(): + user.marithime_context_shift_selection = 1 \ No newline at end of file diff --git a/virtual_buffer/input_context_manager.py b/virtual_buffer/input_context_manager.py index 04a2339..f7a830b 100644 --- a/virtual_buffer/input_context_manager.py +++ b/virtual_buffer/input_context_manager.py @@ -261,12 +261,11 @@ def clear_stale_contexts(self): def get_current_context(self) -> InputContext: if self.current_context is not None: - print("USING CONTEXTS", self.current_context.pid) if self.current_context.pid == -1: self.switch_context(ui.active_window()) self.current_context.update_modified_at() - #self.clear_stale_contexts() + self.clear_stale_contexts() else: self.create_context() From aa5c7b0b15f5aadf471304ee8bd155d5349e9e7b Mon Sep 17 00:00:00 2001 From: chaosparrot Date: Sun, 29 Dec 2024 17:07:30 +0100 Subject: [PATCH 21/24] Fixed context desync issues when a program briefly gains visibility before switching back again --- README.md | 3 --- virtual_buffer/buffer.py | 1 + virtual_buffer/caret_tracker.py | 1 + virtual_buffer/input_context.py | 24 ++++-------------------- virtual_buffer/input_context_manager.py | 18 ++++++++++++++---- 5 files changed, 20 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index 4201f82..7b36a24 100644 --- a/README.md +++ b/README.md @@ -112,9 +112,6 @@ While there's programs where it nails the accessibility API pretty well, others [] - Text editor support This means we should be able to support vim, nano and other keybindings. This runs into the same issues as using a terminal does however, namely poor accessibility support and hard to detect when something is inside of a text editor in the first place. Another is no line wrapping when reaching the start or end of the line and key-pressing beyond that boundary. -[~] - Fix desynchronization issues -There are a bunch of desynchronization issues with settings, context and process ids that cause all kinds of weird things like completely losing the context at random intervals, buffers being created without any need for it, and things like shift selection being turned on when it shouldn't. This will need to be fixed before we can actually support terminals. - [~] - Single line detection Some fields, like name fields, do not have the possibility to add multiple lines. In that case, we probably want to either clear the buffer or simply not allow the enter to change the field. We should probably do a refresh if we are in an accessible field, and a clear in a terminal. diff --git a/virtual_buffer/buffer.py b/virtual_buffer/buffer.py index 17abba6..f497378 100644 --- a/virtual_buffer/buffer.py +++ b/virtual_buffer/buffer.py @@ -747,5 +747,6 @@ def remove_virtual_selection(self) -> List[str]: if total_amount: keys = [self.settings.get_remove_character_left_key() + ":" + str(total_amount)] + self.virtual_selection = [] return keys \ No newline at end of file diff --git a/virtual_buffer/caret_tracker.py b/virtual_buffer/caret_tracker.py index 3a1cb81..ba709c2 100644 --- a/virtual_buffer/caret_tracker.py +++ b/virtual_buffer/caret_tracker.py @@ -75,6 +75,7 @@ def __init__(self, system = platform.system(), settings: VirtualBufferSettings = self.clear() def clear(self): + print("CLEAR CARET TRACKER!") self.set_buffer("") def disable_caret_tracking(self): diff --git a/virtual_buffer/input_context.py b/virtual_buffer/input_context.py index 6391206..1810076 100644 --- a/virtual_buffer/input_context.py +++ b/virtual_buffer/input_context.py @@ -36,7 +36,7 @@ def update_pattern(self, app_name: str = "", title: str = "", pid: int = -1): self.pid = pid def match_pattern(self, app_name: str, title: str, pid: int) -> bool: - return self.coarse_match_pattern(app_name, title, pid) and title.lower() == self.title + return self.coarse_match_pattern(app_name, title, pid) and title.lower() == self.title.lower() def coarse_match_pattern(self, app_name: str, title: str, pid: int) -> bool: return self.app_name.lower() == app_name.lower() or self.pid == pid @@ -57,22 +57,6 @@ def destroy(self): self.pid = -1 self.key_matching = "" self.modified_at = 0 - self.buffer = None - - def set_shift_selection(self, shift_selection: bool): - self.buffer.set_shift_selection(shift_selection) - - def set_multiline_supported(self, multiline_supported: bool): - self.buffer.set_multiline_supported(multiline_supported) - - def set_clear_key(self, clear_key: str): - self.buffer.set_clear_key(clear_key) - - def set_end_of_line_key(self, end_of_line_key: str): - self.buffer.set_end_of_line_key(end_of_line_key) - - def set_start_of_line_key(self, start_of_line_key: str): - self.buffer.set_start_of_line_key(start_of_line_key) - - def set_clear_line_key(self, clear_line_key: str): - self.buffer.set_clear_line_key(clear_line_key) \ No newline at end of file + self.buffer.caret_tracker.settings = None + self.buffer.settings = None + self.buffer = None \ No newline at end of file diff --git a/virtual_buffer/input_context_manager.py b/virtual_buffer/input_context_manager.py index f7a830b..999ded7 100644 --- a/virtual_buffer/input_context_manager.py +++ b/virtual_buffer/input_context_manager.py @@ -239,9 +239,19 @@ def get_window_context(self, window) -> (str, str, int): self.last_title = title return (app_name, title, pid) - def create_context(self): - self.current_context = InputContext(self.last_app_name, self.last_title, self.last_pid) - self.contexts.append(self.current_context) + def create_context_if_not_exists(self): + existing_context = None + for context in self.contexts: + if context.match_pattern(self.last_app_name, self.last_title, self.last_pid): + existing_context = context + break + + # Make sure we don't accidentally create new contexts where we already have an exact match + if existing_context is None: + self.current_context = InputContext(self.last_app_name, self.last_title, self.last_pid) + self.contexts.append(self.current_context) + else: + self.current_context = existing_context def clear_stale_contexts(self): # Only check stale contexts every minute @@ -267,7 +277,7 @@ def get_current_context(self) -> InputContext: self.current_context.update_modified_at() self.clear_stale_contexts() else: - self.create_context() + self.create_context_if_not_exists() return self.current_context From 324e830652797152222028709c82124b13435545 Mon Sep 17 00:00:00 2001 From: chaosparrot Date: Sun, 29 Dec 2024 17:27:32 +0100 Subject: [PATCH 22/24] Fixed correction and self repair for virtual selection programs like terminals --- virtual_buffer/caret_tracker.py | 1 - virtual_buffer/manager.py | 23 +++++++++++++++++++---- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/virtual_buffer/caret_tracker.py b/virtual_buffer/caret_tracker.py index ba709c2..3a1cb81 100644 --- a/virtual_buffer/caret_tracker.py +++ b/virtual_buffer/caret_tracker.py @@ -75,7 +75,6 @@ def __init__(self, system = platform.system(), settings: VirtualBufferSettings = self.clear() def clear(self): - print("CLEAR CARET TRACKER!") self.set_buffer("") def disable_caret_tracking(self): diff --git a/virtual_buffer/manager.py b/virtual_buffer/manager.py index f4f3084..aa4f4c0 100644 --- a/virtual_buffer/manager.py +++ b/virtual_buffer/manager.py @@ -142,7 +142,12 @@ def transform_insert(self, insert: str, enable_self_repair: bool = False) -> (st vbm.virtual_selection[0], vbm.virtual_selection[-1] ) - repair_keys.extend(vbm.remove_virtual_selection()) + + # Apply virtual selection removal so the formatter has up to date information + keys_to_remove_virtual_selection = vbm.remove_virtual_selection() + repair_keys.extend(keys_to_remove_virtual_selection) + for key in keys_to_remove_virtual_selection: + self.context.apply_key(key) if enable_self_repair: @@ -197,8 +202,18 @@ def transform_insert(self, insert: str, enable_self_repair: bool = False) -> (st previous_selection = vbm.caret_tracker.get_selection_text() current_insertion = " ".join(insert.split()[:end_index - start_index]) - repair_keys.append("backspace") - self.context.apply_key("backspace") + # Regular shift selection + if len(vbm.virtual_selection) == 0: + remove_character_left_key = self.settings.get_remove_character_left_key() + repair_keys.append(remove_character_left_key) + self.context.apply_key(remove_character_left_key) + + # Let self repair work for virtual selection as well + else: + keys_to_remove_virtual_selection = vbm.remove_virtual_selection() + repair_keys.extend(keys_to_remove_virtual_selection) + for key in keys_to_remove_virtual_selection: + self.context.apply_key(key) correction_insertion = True # Determine formatter @@ -436,7 +451,7 @@ def marithime_correction(selection_and_correction: List[str]): """Select a fuzzy match of the words and apply the given words""" mutator = get_mutator() keys = mutator.select_phrases(selection_and_correction, for_correction=True) - if len(keys) > 0: + if len(keys) > 0 or mutator.is_virtual_selecting(): mutator.disable_tracking() if keys: for key in keys: From ee1165d1c5fbde01da14ea97bdeebff881138bbf Mon Sep 17 00:00:00 2001 From: chaosparrot Date: Mon, 30 Dec 2024 18:53:19 +0100 Subject: [PATCH 23/24] Added support for KiTTY and checked support for iTerm and iTerm2 --- programs/SUPPORTED.md | 6 +++++- programs/mac/kitty.talon | 6 ++++++ {virtual_buffer => programs}/macos_context.talon | 0 programs/terminal.talon | 2 +- 4 files changed, 12 insertions(+), 2 deletions(-) create mode 100644 programs/mac/kitty.talon rename {virtual_buffer => programs}/macos_context.talon (100%) diff --git a/programs/SUPPORTED.md b/programs/SUPPORTED.md index 1e5d1a6..001c41f 100644 --- a/programs/SUPPORTED.md +++ b/programs/SUPPORTED.md @@ -32,7 +32,11 @@ TODO | ConEmu | Windows | Key tracking | Key tracking | Virtual | | | Cmder | Windows | Key tracking | Key tracking | Shift | | | PowerShell | Windows | Key tracking | Key tracking | Shift | | -| VSCode terminal | Windows | Key tracking | Key tracking | Virtual | This requires [changing the windows title as described in talonhub community](https://github.com/talonhub/community/tree/main/apps/vscode#terminal) | +| iTerm | MacOS | Key tracking | Key tracking | Virtual | | +| iTerm2 | MacOS | Key tracking | Key tracking | Virtual | | +| KiTTY | MacOS | Key tracking | Key tracking | Virtual | | +| VSCode terminal | Windows / MacOS | Key tracking | Key tracking | Virtual | This requires [changing the windows title as described in talonhub community](https://github.com/talonhub/community/tree/main/apps/vscode#terminal) | + Terminal programs generally aren't as well supported as other programs with are more rich set of accessibility APIs. Not to mention that text editors such as VIM, emacs and nano each have their own set of hotkeys to navigate the text displayed, so key tracking becomes increasingly hard to do and prone for desyncs. diff --git a/programs/mac/kitty.talon b/programs/mac/kitty.talon new file mode 100644 index 0000000..cd6f6b0 --- /dev/null +++ b/programs/mac/kitty.talon @@ -0,0 +1,6 @@ +os: mac +and app.bundle: net.kovidgoyal.kitty +- +tag(): terminal +settings(): + user.marithime_context_shift_selection = 0 \ No newline at end of file diff --git a/virtual_buffer/macos_context.talon b/programs/macos_context.talon similarity index 100% rename from virtual_buffer/macos_context.talon rename to programs/macos_context.talon diff --git a/programs/terminal.talon b/programs/terminal.talon index a5cc514..037386b 100644 --- a/programs/terminal.talon +++ b/programs/terminal.talon @@ -1,4 +1,4 @@ -os: macos +os: mac os: windows os: linux tag: terminal From dd18dcdf8d28b50df1a4755c1a17bfd38bea09ad Mon Sep 17 00:00:00 2001 From: chaosparrot Date: Tue, 31 Dec 2024 06:58:25 +0100 Subject: [PATCH 24/24] Tested the terminal support on Manjaro as well --- programs/SUPPORTED.md | 12 ++++++++---- virtual_buffer/manager.py | 2 +- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/programs/SUPPORTED.md b/programs/SUPPORTED.md index 001c41f..6172863 100644 --- a/programs/SUPPORTED.md +++ b/programs/SUPPORTED.md @@ -6,11 +6,12 @@ This is a list of programs that are known to be supported. Others might have var Both Windows and MacOS have supported accessibility APIs, which allow us to introspect the currently focused text area in great detail, but Linux does not. For that reason, Linux doesn't have as good of a user experience since we cannot poll the content of a text area directly. -## Browser support +## Word processor support TODO -## Word processor support + +## Browser support TODO @@ -21,6 +22,8 @@ TODO | VSCode editor | Windows | Yes* | Yes* | This requires turning on accessiblity support | | VSCode editor | MacOS | Yes* | Yes* | This requries turning on accessibility support `Shift+Option+F1`| +VSCode has some issues when creating new files that do not have a sticky filename however. + ## Terminal support | Program | OS | Cursor tracking | Content tracking | Selection | Notes | @@ -35,8 +38,9 @@ TODO | iTerm | MacOS | Key tracking | Key tracking | Virtual | | | iTerm2 | MacOS | Key tracking | Key tracking | Virtual | | | KiTTY | MacOS | Key tracking | Key tracking | Virtual | | -| VSCode terminal | Windows / MacOS | Key tracking | Key tracking | Virtual | This requires [changing the windows title as described in talonhub community](https://github.com/talonhub/community/tree/main/apps/vscode#terminal) | - +| Gnome Terminal | Linux | Key tracking | Key tracking | Virtual | | +| Guake | Linux | Key tracking | Key tracking | Virtual | | +| VSCode terminal | Windows / MacOS / Linux | Key tracking | Key tracking | Virtual | This requires [changing the windows title as described in talonhub community](https://github.com/talonhub/community/tree/main/apps/vscode#terminal) | Terminal programs generally aren't as well supported as other programs with are more rich set of accessibility APIs. Not to mention that text editors such as VIM, emacs and nano each have their own set of hotkeys to navigate the text displayed, so key tracking becomes increasingly hard to do and prone for desyncs. diff --git a/virtual_buffer/manager.py b/virtual_buffer/manager.py index aa4f4c0..d73bb9d 100644 --- a/virtual_buffer/manager.py +++ b/virtual_buffer/manager.py @@ -345,7 +345,7 @@ def init_mutator(): settings.register("speech.engine", lambda _: update_language("")) # Enable this line to allow for quicker debugging - actions.menu.open_log() + # actions.menu.open_log() update_language("") return mutator