From 43b57baf5dfd65f3fdedfe5380523218da50bfc3 Mon Sep 17 00:00:00 2001 From: Alexander Tretyak Date: Thu, 10 Nov 2016 22:57:50 +1000 Subject: [PATCH 01/20] Add support of standard main menu commands (Edit:Cut/Copy/Paste) --- Default (Linux).sublime-keymap | 8 -------- Default (OSX).sublime-keymap | 5 ----- Default (Windows).sublime-keymap | 8 -------- copy_edit.py | 5 +++++ 4 files changed, 5 insertions(+), 21 deletions(-) delete mode 100644 Default (Linux).sublime-keymap delete mode 100644 Default (OSX).sublime-keymap delete mode 100644 Default (Windows).sublime-keymap diff --git a/Default (Linux).sublime-keymap b/Default (Linux).sublime-keymap deleted file mode 100644 index d46d7f1..0000000 --- a/Default (Linux).sublime-keymap +++ /dev/null @@ -1,8 +0,0 @@ -[ - { "keys": ["shift+delete"], "command": "cut_edit" }, - { "keys": ["ctrl+insert"], "command": "copy_edit" }, - { "keys": ["shift+insert"], "command": "paste_edit" }, - { "keys": ["ctrl+x"], "command": "cut_edit" }, - { "keys": ["ctrl+c"], "command": "copy_edit" }, - { "keys": ["ctrl+v"], "command": "paste_edit" } -] diff --git a/Default (OSX).sublime-keymap b/Default (OSX).sublime-keymap deleted file mode 100644 index acab2ff..0000000 --- a/Default (OSX).sublime-keymap +++ /dev/null @@ -1,5 +0,0 @@ -[ - { "keys": ["super+x"], "command": "cut_edit" }, - { "keys": ["super+c"], "command": "copy_edit" }, - { "keys": ["super+v"], "command": "paste_edit" } -] diff --git a/Default (Windows).sublime-keymap b/Default (Windows).sublime-keymap deleted file mode 100644 index d46d7f1..0000000 --- a/Default (Windows).sublime-keymap +++ /dev/null @@ -1,8 +0,0 @@ -[ - { "keys": ["shift+delete"], "command": "cut_edit" }, - { "keys": ["ctrl+insert"], "command": "copy_edit" }, - { "keys": ["shift+insert"], "command": "paste_edit" }, - { "keys": ["ctrl+x"], "command": "cut_edit" }, - { "keys": ["ctrl+c"], "command": "copy_edit" }, - { "keys": ["ctrl+v"], "command": "paste_edit" } -] diff --git a/copy_edit.py b/copy_edit.py index 1ee6e39..af34694 100644 --- a/copy_edit.py +++ b/copy_edit.py @@ -92,3 +92,8 @@ def run(self, edit): self.view.sel().clear() for s in new_sels: self.view.sel().add(s) + +class CopyEditListener(sublime_plugin.EventListener): # for support of standard main menu commands (Edit:Cut/Copy/Paste) + def on_text_command(self, view, command_name, args): + if command_name in ["cut", "copy", "paste"]: + return (command_name + "_edit", args) From 982e8c6caf902239e83d36912edfbeea47b2e8d6 Mon Sep 17 00:00:00 2001 From: Alexander Tretyak Date: Thu, 1 Dec 2016 15:47:48 +1000 Subject: [PATCH 02/20] Add cut_copy_paste_tests.py --- cut_copy_paste_tests.py | 318 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 318 insertions(+) create mode 100644 cut_copy_paste_tests.py diff --git a/cut_copy_paste_tests.py b/cut_copy_paste_tests.py new file mode 100644 index 0000000..6724f36 --- /dev/null +++ b/cut_copy_paste_tests.py @@ -0,0 +1,318 @@ +# To run this tests just put this script file under SublimeText Packages folder +# and in SublimeText Console enter this command: +# sublime.run_command("cut_copy_paste_tests") + +import sublime, sublime_plugin, re + +class cut_copy_paste_tests_command(sublime_plugin.ApplicationCommand): + def run(self): + # These tests are quite human-readable, but not so human-writable (: as it may seems :). But this is intentional, as in + # case of small typo exception is raised instead of "smart" guessing what you possibly wanted, and running without fail + # [and may be even improperly showing test as CORRECTly passed whilst it was INcorrectly parsed]. + tests = """\ +(1) +1. Select THIS +2. Additionally [multi-] select THIS2 [also (e.g. with Ctrl+mouse)]. +3. Copy [to clipboard (e.g. press Ctrl+C)]. +4. Set cursor to here -><-, + and [also (via Ctrl+click)] to here -><-. +5. Paste [from clipboard (e.g. press Ctrl+V)]. + +[This works correctly in SublimeText by default (and this plugin SHOULD NOT break this behaviour/functionality).] +This is CORRECT result: +4. Set cursor to here ->THIS<-, + and [also (via Ctrl+click)] to here ->THIS2<-. + + +(2) +1. Select THIS +2. Additionally select THIS2 +3. Cut [to clipboard (e.g. press Ctrl+X)]. +4. Paste [from clipboard (e.g. press Ctrl+V)]. +5. Paste [again]. + +[This test was added based on wbond's comment[https://github.com/SublimeTextIssues/Core/issues/1435#issuecomment-258159654 "When performing editing in multiple selections, if the user cuts and then pastes, the obvious functionality it to cut text from each line and then paste the same text back to where the cursor currently is. This allows you to batch edit lines."]. +This works correctly in SublimeText by default (and this plugin SHOULD NOT break this behaviour/functionality).] + +This is WRONG result: +1. Select THIS +THIS2THIS +THIS2 +2. Additionally select THIS +THIS2THIS +THIS2 + +This is CORRECT result: +1. Select THISTHIS +2. Additionally select THIS2THIS2 + + +(3) +1. Select T-H- +. . . . . .-I-S +2. Copy. +3. Set cursor to here -><-, + and to here -><-. +4. Paste. + +[This does not work correctly in SublimeText by default (issue[https://github.com/SublimeTextIssues/Core/issues/1435]).] + +This is WRONG result: +3. Set cursor to here ->T-H-<-, + and to here ->. . . . . .-I-S<-. + +[But SHOULD works correctly after installing this plugin.] + +This is CORRECT result: +3. Set cursor to here ->T-H- +. . . . . .-I-S<-, + and to here ->T-H- +. . . . . .-I-S<-. + + +(4) +1. Select THIS +2. Additionally select THIS +3. Copy +4. Set cursor to here -><- +5. Paste + +[This does not work correctly in SublimeText by default (issue[https://github.com/SublimeTextIssues/Core/issues/1461]).] + +This is WRONG result: +4. Set cursor to here ->THIS +THIS<- + +This is ALSO WRONG result [observed in CopyEdit up to version 9b68204818258c33889bc923a15f1d83cad8423e]: +4. Set cursor to here ->THISTHIS<- + +This is CORRECT result: +4. Set cursor to here ->THIS<- + + +(5) +1. Select THIS +2. Additionally select THIS +3. Copy +4. Set cursor to here -><-, + and to here -><-. +5. Paste + +[This works correctly in SublimeText by default (this test was added just to designate particularity of test (4) +and to check that this [(5) test] behaviour should remain working as well).] + +This is CORRECT result: +4. Set cursor to here ->THIS<-, + and to here ->THIS<-. + + +(6) +1. Set cursor + to here -><- +2. Copy +3. Set cursor + to here -><- +4. Paste + +This is CORRECT result: +3. Set cursor + to here -><- + to here -><- + +This is WRONG result: +3. Set cursor + to here -> to here -><- +<- + + +(7) +1. Set cursor + to here -><-, + and to here -><-. +2. Copy. +3. Set cursor + to here -><-, + and to here -><-. +4. Paste. + +[This test was added based on my comment[https://github.com/SublimeTextIssues/Core/issues/1461#issuecomment-258406270 "... multi-caret [multi-line] cut (Ctrl+X) in SublimeText without selection is even more broken $'`"\U0001f615"`' than pasting in multiple selections (#1435) ..."].] + +This is WRONG result: +3. Set cursor + to here -><-, + + and to here -><-. + to here -><-, + to here -><-, + + and to here -><-. + and to here -><-. + +This is ALSO WRONG result: +3. Set cursor + to here -> to here -><-, +<-, + and to here -> and to here -><-. +<-. + +This is CORRECT result: +3. Set cursor + to here -><-, + to here -><-, + and to here -><-. + and to here -><-. + + +[Nothing interesting here. This following checks are just for completeness.] +(8) +1. Set cursor + to here -><- +2. Cut +3. Set cursor + to here -><- +4. Paste + +This is CORRECT result: +1. Set cursor +3. Set cursor + to here -><- + to here -><- + + +(9) +1. Set cursor + to here -><-, + and to here -><-. +2. Cut. +3. Set cursor + to here -><-. +4. Paste. + +This is CORRECT result: +1. Set cursor +3. Set cursor + to here -><-, + and to here -><-. + to here -><-. +""" + pos = 0 + def read_re(rexp): + nonlocal pos + r = re.compile(rexp).match(tests, pos) # re.match(rexp, tests[pos:]) + if not r: + raise "?" + pos = r.end() # pos += r.end() + return r.groups() + + def read_list_of_commands(): + nonlocal pos + commands = [] + while True: + commands.append(read_re(R"(\d+)\. ([\s\S]+?)\n(?=\d+\.|\n(?! )|$)")) + if pos == len(tests): + break + if tests[pos] == "\n": + pos += 1 # skip \n + break + return commands + + def skip_comments(): + nonlocal pos + while tests[pos] == '[': + #read_re(R"\[[^\[\]]+(?:\[[^\]]+\])?[^\]]+\]\n\n?") + nesting_level = 0 + while True: + ch = tests[pos] + if ch == "[": + nesting_level += 1 + elif ch == "]": + nesting_level -= 1 + if nesting_level == 0: + pos += 1 + break + pos += 1 + if pos == len(tests): + raise 'Unpaired `[`' + assert(tests[pos] == "\n") + pos += 1 + if tests[pos] == "\n": + pos += 1 + + # Create scratch buffer just for testing purposes + buffer = sublime.active_window().new_file() + buffer.set_scratch(True) + + while pos < len(tests): + # Read test id [test number] + skip_comments() + test_id = read_re(R"(\(\d+\))\n")[0] + + # Read commands + commands = read_list_of_commands() + + # Prepare scratch buffer + buffer.run_command("select_all") + buffer.run_command("right_delete") + buffer.run_command("append", { "characters": "".join([c[0] + '. ' + c[1] + "\n" for c in commands]) } ) # "insert" is not working totally correctly here, so "append" is used instead + + # Process commands + for command in commands: + cmd = re.sub(R' \[[^]]+]', '', command[1]) # remove comments + cmd = cmd.rstrip('.')#rstrip('.', 1) # remove ending `.` if present + if cmd in ["Cut", "Copy", "Paste"]: + #buffer.run_command(cmd.lower()) # this does not work, so emulate correct behaviour manually: + overrided_command = sublime_plugin.on_text_command(buffer.id(), cmd.lower(), None) + buffer.run_command(*overrided_command if overrided_command[0] else (cmd.lower(),)) + continue + def where_command_starts(next = 0): # (using this function below is not totally fair, but much easier) + return buffer.find("^" + str(int(command[0])+next) + ". ", 0) + r = re.match(R"Select (T[-\.\s]*?H[-\.\s]*?I[-\.\s]*?S\d*)$", cmd) + if r: + buffer.sel().clear() + buffer.sel().add(buffer.find(r.group(1), where_command_starts().b, sublime.LITERAL)) + continue + r = re.match(R"Additionally select (THIS\d*)$", cmd) + if r: + buffer.sel().add(buffer.find(r.group(1), where_command_starts().b, sublime.LITERAL)) + continue + r = re.match(R"Set cursor\s+to here -><-(?:,\s+and to here -><-)?$", cmd) + if r: + buffer.sel().clear() + pos_ = where_command_starts().b + end_ = where_command_starts(1).a + while True: + pos_ = buffer.find("-><-", pos_, sublime.LITERAL).a + if pos_ == -1 or pos_ > end_: + break + pos_ += 2 + buffer.sel().add(sublime.Region(pos_, pos_)) + continue + raise "Unknown command" + obtained_result = buffer.substr(sublime.Region(0, buffer.size())) + + # Read predetermined results + compared_result_type = None + while True: + skip_comments() + type_of_result = read_re(R"This is (.+) result(?: \[[^\]]+])?:\n")[0] + rcommands = read_list_of_commands() + + # Compare this result with processed result + ccommands = list(commands) # create copy of commands + for c in rcommands: # write rcommands over ccommands + assert(ccommands[int(c[0])-1][0] == c[0] and ccommands[int(c[0])-1][1] != c[1]) + ccommands[int(c[0])-1] = c + if "".join([c[0] + '. ' + c[1] + "\n" for c in ccommands]) == obtained_result: + assert(compared_result_type == None) + compared_result_type = type_of_result + #break # break is commented out for more accurate correctness testing/checking + + # Check break conditions + if pos == len(tests): + break + if tests[pos] == "\n": + pos += 1 # skip \n + break + print(test_id + ' ' + (compared_result_type if compared_result_type else "INCORRECT")) + + buffer.close() From b9e74b82c4421829d3bb34e8c4ddd6fccea9507c Mon Sep 17 00:00:00 2001 From: Alexander Tretyak Date: Thu, 1 Dec 2016 20:25:42 +1000 Subject: [PATCH 03/20] Fix test #(4) and [this issue](https://github.com/SublimeTextIssues/Core/issues/1461) --- copy_edit.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/copy_edit.py b/copy_edit.py index af34694..e5bbca9 100644 --- a/copy_edit.py +++ b/copy_edit.py @@ -33,7 +33,10 @@ def copy(self, edit): new_sel_strings.append(self.view.substr(s)) elif copy_with_empty_sel: new_sel_strings.append(self.view.substr(self.view.full_line(s))) - + + if all(s == new_sel_strings[0] for s in new_sel_strings): + new_sel_strings = [new_sel_strings[0]] + if len(new_sel_strings) > 0: selection_strings[:] = [] #.clear() doesn't exist in 2.7 selection_strings.extend(new_sel_strings) From 98746fee3066adb0f7620a9d46f4340b47e5992e Mon Sep 17 00:00:00 2001 From: Alexander Tretyak Date: Thu, 1 Dec 2016 20:52:44 +1000 Subject: [PATCH 04/20] Fix tests #(6) and #(7) --- copy_edit.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/copy_edit.py b/copy_edit.py index e5bbca9..3b79875 100644 --- a/copy_edit.py +++ b/copy_edit.py @@ -16,7 +16,7 @@ def print_status_message(verb, numregions=None): numregions = numregions or len(selection_strings) - numchars = sum([len(s) for s in selection_strings]) + numchars = sum([len(s[0]) for s in selection_strings]) message = "{0} {1} character{2}".format(verb, numchars, 's' if numchars != 1 else '') if numregions > 1: message += " over {0} selection regions".format(numregions) @@ -30,9 +30,9 @@ def copy(self, edit): new_sel_strings = [] for s in self.view.sel(): if len(s): - new_sel_strings.append(self.view.substr(s)) + new_sel_strings.append((self.view.substr(s), False)) elif copy_with_empty_sel: - new_sel_strings.append(self.view.substr(self.view.full_line(s))) + new_sel_strings.append((self.view.substr(self.view.full_line(s)), True)) if all(s == new_sel_strings[0] for s in new_sel_strings): new_sel_strings = [new_sel_strings[0]] @@ -41,7 +41,7 @@ def copy(self, edit): selection_strings[:] = [] #.clear() doesn't exist in 2.7 selection_strings.extend(new_sel_strings) line_ending = line_endings[self.view.line_endings()] - sublime.set_clipboard(line_ending.join(selection_strings)) + sublime.set_clipboard(line_ending.join([s[0] for s in selection_strings])) return True return False @@ -61,9 +61,9 @@ def run(self, edit): #check if clipboard is more up to date pasteboard = sublime.get_clipboard() from_clipboard = False - if pasteboard != '\n'.join(selection_strings): + if pasteboard != '\n'.join([s[0] for s in selection_strings]): selection_strings[:] = [] #.clear() doesn't exist in 2.7 - selection_strings.append(pasteboard) + selection_strings.append((pasteboard, False)) from_clipboard = True #what should be done in this case? numstrings = len(selection_strings) @@ -84,8 +84,8 @@ def run(self, edit): self.view.erase(edit, sel) insertion_point = sel.begin() for string in selection_strings[str_index:str_index+strs_per_sel]: - self.view.insert(edit, insertion_point, string) - insertion_point += len(string) + self.view.insert(edit, self.view.line(insertion_point).begin() if string[1] else insertion_point, string[0]) + insertion_point += len(string[0]) region = sublime.Region(insertion_point) new_sels.append(region) str_index = (str_index + strs_per_sel) % numstrings From 7673f1e6544cf4b55c6ceb3f51ab7e43d3e9ce33 Mon Sep 17 00:00:00 2001 From: Alexander Tretyak Date: Thu, 1 Dec 2016 21:12:51 +1000 Subject: [PATCH 05/20] Fix tests #(8) and #(9) --- copy_edit.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/copy_edit.py b/copy_edit.py index 3b79875..82d0f72 100644 --- a/copy_edit.py +++ b/copy_edit.py @@ -52,8 +52,8 @@ def run(self, edit, verb="Copied"): class CutEditCommand(sublime_plugin.TextCommand): def run(self, edit): self.view.run_command("copy_edit", {"verb":"Cut"}) - for s in reversed(self.view.sel()): - self.view.erase(edit, s) + for s, ss in reversed(list(zip(self.view.sel(), selection_strings))): + self.view.erase(edit, self.view.full_line(s) if ss[1] else s) class PasteEditCommand(sublime_plugin.TextCommand): def run(self, edit): From 3cbe63482c0ee477bc87f7880e25e28611a58f13 Mon Sep 17 00:00:00 2001 From: Alexander Tretyak Date: Thu, 1 Dec 2016 22:09:54 +1000 Subject: [PATCH 06/20] Fix test #(4a) --- copy_edit.py | 18 +++++++++++------- cut_copy_paste_tests.py | 15 ++++++++++++++- 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/copy_edit.py b/copy_edit.py index 82d0f72..2145a4c 100644 --- a/copy_edit.py +++ b/copy_edit.py @@ -23,6 +23,7 @@ def print_status_message(verb, numregions=None): sublime.status_message(message) class CopyEditCommand(sublime_plugin.TextCommand): + @staticmethod def copy(self, edit): #See copy_with_empty_selection note above. copy_with_empty_sel = self.view.settings().get("copy_with_empty_selection") @@ -34,6 +35,7 @@ def copy(self, edit): elif copy_with_empty_sel: new_sel_strings.append((self.view.substr(self.view.full_line(s)), True)) + actual_selection_strings = new_sel_strings if all(s == new_sel_strings[0] for s in new_sel_strings): new_sel_strings = [new_sel_strings[0]] @@ -42,18 +44,20 @@ def copy(self, edit): selection_strings.extend(new_sel_strings) line_ending = line_endings[self.view.line_endings()] sublime.set_clipboard(line_ending.join([s[0] for s in selection_strings])) - return True + return actual_selection_strings return False - def run(self, edit, verb="Copied"): - if self.copy(edit): - print_status_message(verb) + def run(self, edit): + if self.copy(self, edit): + print_status_message("Copied") class CutEditCommand(sublime_plugin.TextCommand): def run(self, edit): - self.view.run_command("copy_edit", {"verb":"Cut"}) - for s, ss in reversed(list(zip(self.view.sel(), selection_strings))): - self.view.erase(edit, self.view.full_line(s) if ss[1] else s) + actual_selection_strings = CopyEditCommand.copy(self, edit) + if actual_selection_strings: + print_status_message("Cut") + for s, ss in reversed(list(zip(self.view.sel(), actual_selection_strings))): + self.view.erase(edit, self.view.full_line(s) if ss[1] else s) class PasteEditCommand(sublime_plugin.TextCommand): def run(self, edit): diff --git a/cut_copy_paste_tests.py b/cut_copy_paste_tests.py index 6724f36..6a42ed6 100644 --- a/cut_copy_paste_tests.py +++ b/cut_copy_paste_tests.py @@ -90,6 +90,19 @@ def run(self): 4. Set cursor to here ->THIS<- +(4a) +1. Select THIS +2. Additionally select THIS +3. Cut +4. Set cursor to here -><- +5. Paste + +This is CORRECT result: +1. Select +2. Additionally select +4. Set cursor to here ->THIS<- + + (5) 1. Select THIS 2. Additionally select THIS @@ -245,7 +258,7 @@ def skip_comments(): while pos < len(tests): # Read test id [test number] skip_comments() - test_id = read_re(R"(\(\d+\))\n")[0] + test_id = read_re(R"(\(\w+\))\n")[0] # Read commands commands = read_list_of_commands() From 7b5920d257c04290ada35c8a86d2f40141c7261c Mon Sep 17 00:00:00 2001 From: Alexander Tretyak Date: Sat, 3 Dec 2016 17:22:16 +1000 Subject: [PATCH 07/20] Add paste_from_history_edit command --- Default (Linux).sublime-keymap | 3 +++ Default (OSX).sublime-keymap | 3 +++ Default (Windows).sublime-keymap | 3 +++ copy_edit.py | 32 ++++++++++++++++++++++++++++---- 4 files changed, 37 insertions(+), 4 deletions(-) create mode 100644 Default (Linux).sublime-keymap create mode 100644 Default (OSX).sublime-keymap create mode 100644 Default (Windows).sublime-keymap diff --git a/Default (Linux).sublime-keymap b/Default (Linux).sublime-keymap new file mode 100644 index 0000000..220e342 --- /dev/null +++ b/Default (Linux).sublime-keymap @@ -0,0 +1,3 @@ +[ + { "keys": ["ctrl+shift+v"], "command": "paste_from_history_edit" } +] diff --git a/Default (OSX).sublime-keymap b/Default (OSX).sublime-keymap new file mode 100644 index 0000000..1cae268 --- /dev/null +++ b/Default (OSX).sublime-keymap @@ -0,0 +1,3 @@ +[ + { "keys": ["super+shift+v"], "command": "paste_from_history_edit" } +] diff --git a/Default (Windows).sublime-keymap b/Default (Windows).sublime-keymap new file mode 100644 index 0000000..220e342 --- /dev/null +++ b/Default (Windows).sublime-keymap @@ -0,0 +1,3 @@ +[ + { "keys": ["ctrl+shift+v"], "command": "paste_from_history_edit" } +] diff --git a/copy_edit.py b/copy_edit.py index 2145a4c..55c7057 100644 --- a/copy_edit.py +++ b/copy_edit.py @@ -1,4 +1,4 @@ -import sublime, sublime_plugin +import sublime, sublime_plugin, collections selection_strings = [] @@ -43,7 +43,7 @@ def copy(self, edit): selection_strings[:] = [] #.clear() doesn't exist in 2.7 selection_strings.extend(new_sel_strings) line_ending = line_endings[self.view.line_endings()] - sublime.set_clipboard(line_ending.join([s[0] for s in selection_strings])) + sublime.set_clipboard(add_string_to_paste_history(line_ending.join([s[0] for s in selection_strings]))) return actual_selection_strings return False @@ -63,7 +63,7 @@ class PasteEditCommand(sublime_plugin.TextCommand): def run(self, edit): #check if clipboard is more up to date - pasteboard = sublime.get_clipboard() + pasteboard = add_string_to_paste_history(sublime.get_clipboard()) # this is needed when string was copied to clipboard not within Sublime Text from_clipboard = False if pasteboard != '\n'.join([s[0] for s in selection_strings]): selection_strings[:] = [] #.clear() doesn't exist in 2.7 @@ -100,7 +100,31 @@ def run(self, edit): for s in new_sels: self.view.sel().add(s) +paste_history_deque = collections.deque(maxlen = 15) # 15 is the same as in SublimeText's "Paste from History" list + +def add_string_to_paste_history(string):#, do_not_reorder_entries_of_paste_history_deque = False): + if string == "": + return + if string in paste_history_deque: + paste_history_deque.remove(string) + paste_history_deque.appendleft(string) + return string + +class PasteFromHistoryIdxCommand(sublime_plugin.TextCommand): + def run(self, edit, idx): + if idx != -1: + sublime.set_clipboard(paste_history_deque[idx]) + self.view.run_command("paste_edit") + +class PasteFromHistoryEditCommand(sublime_plugin.TextCommand): + def run(self, edit): + add_string_to_paste_history(sublime.get_clipboard()) # this is needed when string was copied to clipboard not within Sublime Text + if len(paste_history_deque) > 0: + (self.view.window().show_quick_panel if self.view.settings().get("paste_from_history_quick_panel") else self.view.show_popup_menu)( + [(s if len(s) < 45 else s[:45] + '...').replace("\n", " ").replace("\t", " ") for s in paste_history_deque], + lambda idx: self.view.run_command("paste_from_history_idx", {"idx": idx})) + class CopyEditListener(sublime_plugin.EventListener): # for support of standard main menu commands (Edit:Cut/Copy/Paste) def on_text_command(self, view, command_name, args): - if command_name in ["cut", "copy", "paste"]: + if command_name in ["cut", "copy", "paste", "paste_from_history"]: # actually adding "paste_from_history" here does not make any sense because this command is disabled after startup of SublimeText return (command_name + "_edit", args) From 3283bcbd27ec65dc790a2bc02e97cc446bc1c969 Mon Sep 17 00:00:00 2001 From: Alexander Tretyak Date: Tue, 6 Dec 2016 22:01:11 +1000 Subject: [PATCH 08/20] Fix incorrect line endings when copying multi-line text in Sublime Text on Windows (bug was observed while pasting multi-line text [copied from Sublime Text] into Windows Notepad) --- copy_edit.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/copy_edit.py b/copy_edit.py index 55c7057..1ed54b2 100644 --- a/copy_edit.py +++ b/copy_edit.py @@ -43,7 +43,7 @@ def copy(self, edit): selection_strings[:] = [] #.clear() doesn't exist in 2.7 selection_strings.extend(new_sel_strings) line_ending = line_endings[self.view.line_endings()] - sublime.set_clipboard(add_string_to_paste_history(line_ending.join([s[0] for s in selection_strings]))) + sublime.set_clipboard(add_string_to_paste_history(line_ending.join([s[0].replace('\n', line_ending) for s in selection_strings]))) return actual_selection_strings return False From f70397b75fde9a1a6ff082d8acdd1a800bc613e5 Mon Sep 17 00:00:00 2001 From: Alexander Tretyak Date: Tue, 13 Dec 2016 21:28:36 +1000 Subject: [PATCH 09/20] Fix this error: Traceback (most recent call last): File "...\SublimeText\sublime_plugin.py", line 818, in run_ return self.run(edit) File "...\SublimeText\Data\Packages\CopyEdit\copy_edit.py", line 68, in run if pasteboard != '\n'.join([s[0] for s in selection_strings]): TypeError: sequence item 0: expected str instance, NoneType found --- copy_edit.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/copy_edit.py b/copy_edit.py index 1ed54b2..20b0c11 100644 --- a/copy_edit.py +++ b/copy_edit.py @@ -104,7 +104,7 @@ def run(self, edit): def add_string_to_paste_history(string):#, do_not_reorder_entries_of_paste_history_deque = False): if string == "": - return + return "" if string in paste_history_deque: paste_history_deque.remove(string) paste_history_deque.appendleft(string) From 55d7187e204f6159af33c257a4ebcc5bd4174cbb Mon Sep 17 00:00:00 2001 From: Alexander Tretyak Date: Wed, 22 Mar 2017 20:14:33 +1000 Subject: [PATCH 10/20] Fix test #(10) --- copy_edit.py | 32 ++++++++++++++++++++++---------- cut_copy_paste_tests.py | 20 ++++++++++++++++++++ 2 files changed, 42 insertions(+), 10 deletions(-) diff --git a/copy_edit.py b/copy_edit.py index 20b0c11..d536a3b 100644 --- a/copy_edit.py +++ b/copy_edit.py @@ -82,17 +82,29 @@ def run(self, edit): else: strs_per_sel = numstrings - str_index = 0 new_sels = [] - for sel in self.view.sel(): - self.view.erase(edit, sel) - insertion_point = sel.begin() - for string in selection_strings[str_index:str_index+strs_per_sel]: - self.view.insert(edit, self.view.line(insertion_point).begin() if string[1] else insertion_point, string[0]) - insertion_point += len(string[0]) - region = sublime.Region(insertion_point) - new_sels.append(region) - str_index = (str_index + strs_per_sel) % numstrings + if len(self.view.sel()) == numstrings or numstrings == 1: # fix for test #10 (character-by-character selection) + sel_strings = iter(selection_strings) + string = next(sel_strings) + for sel in self.view.sel(): + replace_region = sublime.Region(self.view.line(sel.begin()).begin()) if string[1] else sel + self.view.replace(edit, replace_region, string[0]) + new_sels.append(sublime.Region(replace_region.end() + len(string[0]))) + string = next(sel_strings, None) + if string == None: + sel_strings = iter(selection_strings) + string = next(sel_strings) + else: + str_index = 0 + for sel in self.view.sel(): + self.view.erase(edit, sel) + insertion_point = sel.begin() + for string in selection_strings[str_index:str_index+strs_per_sel]: + self.view.insert(edit, self.view.line(insertion_point).begin() if string[1] else insertion_point, string[0]) + insertion_point += len(string[0]) + region = sublime.Region(insertion_point) + new_sels.append(region) + str_index = (str_index + strs_per_sel) % numstrings print_status_message("Pasted", len(self.view.sel())) diff --git a/cut_copy_paste_tests.py b/cut_copy_paste_tests.py index 6a42ed6..03e7b60 100644 --- a/cut_copy_paste_tests.py +++ b/cut_copy_paste_tests.py @@ -207,6 +207,19 @@ def run(self): to here -><-, and to here -><-. to here -><-. + + +(10) +1. Select THIS. +2. Copy. +3. Character-by-character select THIS. +4. Paste. + +This is WRONG result: +3. Character-by-character select THISTHISITHIS. + +This is CORRECT result: +3. Character-by-character select THISTHISTHISTHIS. """ pos = 0 def read_re(rexp): @@ -288,6 +301,13 @@ def where_command_starts(next = 0): # (using this function below is not totally if r: buffer.sel().add(buffer.find(r.group(1), where_command_starts().b, sublime.LITERAL)) continue + r = re.match(R"Character-by-character select (THIS)$", cmd) + if r: + buffer.sel().clear() + start = buffer.find(r.group(1), where_command_starts().b, sublime.LITERAL).begin() + for x in range(len(r.group(1))): + buffer.sel().add(sublime.Region(start + x, start + x + 1)) + continue r = re.match(R"Set cursor\s+to here -><-(?:,\s+and to here -><-)?$", cmd) if r: buffer.sel().clear() From a7bc3d4d9b043a77a00f963d759c12d6f209d38c Mon Sep 17 00:00:00 2001 From: Alexander Tretyak Date: Thu, 23 Mar 2017 16:58:11 +1000 Subject: [PATCH 11/20] Make "backup" copy of selection in the paste-history-list after deleting selected text by pressing Delete or Backspace --- copy_edit.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/copy_edit.py b/copy_edit.py index d536a3b..3ad4b3a 100644 --- a/copy_edit.py +++ b/copy_edit.py @@ -140,3 +140,5 @@ class CopyEditListener(sublime_plugin.EventListener): # for support of standard def on_text_command(self, view, command_name, args): if command_name in ["cut", "copy", "paste", "paste_from_history"]: # actually adding "paste_from_history" here does not make any sense because this command is disabled after startup of SublimeText return (command_name + "_edit", args) + if command_name in ["left_delete", "right_delete"]: + add_string_to_paste_history(view.substr(view.sel()[0])) From 11a1fb6e00a1fdd76a96cb477ed605d616b6951a Mon Sep 17 00:00:00 2001 From: Alexander Tretyak Date: Thu, 30 Mar 2017 19:22:10 +1000 Subject: [PATCH 12/20] Add tests_neo for testing much more accurately --- cut_copy_paste_tests.py | 182 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 180 insertions(+), 2 deletions(-) diff --git a/cut_copy_paste_tests.py b/cut_copy_paste_tests.py index 03e7b60..45cedfb 100644 --- a/cut_copy_paste_tests.py +++ b/cut_copy_paste_tests.py @@ -244,7 +244,7 @@ def read_list_of_commands(): def skip_comments(): nonlocal pos - while tests[pos] == '[': + while tests[pos] == '[': # [ #read_re(R"\[[^\[\]]+(?:\[[^\]]+\])?[^\]]+\]\n\n?") nesting_level = 0 while True: @@ -326,7 +326,7 @@ def where_command_starts(next = 0): # (using this function below is not totally # Read predetermined results compared_result_type = None while True: - skip_comments() + skip_comments() # [ type_of_result = read_re(R"This is (.+) result(?: \[[^\]]+])?:\n")[0] rcommands = read_list_of_commands() @@ -349,3 +349,181 @@ def where_command_starts(next = 0): # (using this function below is not totally print(test_id + ' ' + (compared_result_type if compared_result_type else "INCORRECT")) buffer.close() + + # Those tests_neo (below) allow do testing much more accurately, and they can also check cursor/selection position after command was executed + tests_neo = """ +TN 0 // Test Number 0 — basic syntax [of this new language for tests] tests/checks +"""+0*""" +DA 1234 +CU 1>‘2’<34 //select/‘set CUrsor’ just one character ‘2’ +CR 1>‘2’<34 //paranoiac check result 1|2|34 +CU >>‘’. // just like pressing right arrow key → 12||34 +CR 12>‘’<34 +CU >‘’> // once more → 123||4 +CR 123>‘’<4 +CU .‘’> // like pressing Shift + → 123|4| +CR 123>‘4’< +CU <‘’. // like pressing Shift + ← 12|34| +CR 12>‘34’< +CU <‘’< // 1|23|4 +CR 1>‘23’<4 +CU .‘’< 1|2|34 +CR 1>‘2’<34 +CU <‘’. |12|34 +CR >‘12’<34 +CU .‘’<< ||1234 +CR >‘’<1234 +CU >‘’>> 1|234| +CR 1>‘234’< +CU <<‘’>> // select all |1234| +CR >‘1234’< // check/correct result +CU >>‘’>> // End 1234|| +CR 1234>‘’< +CU <<‘’<< // Home ||1234 +CR >‘’<1234 +"""+""" +TN 1 // Just copy of (1) test +DA‘1. Select >‘THIS’< +2. Additionally [multi-] select >‘THIS2’< [also (e.g. with Ctrl+mouse)].’ +CO copy +DA‘Set cursor to here ->>‘’<<-, + and [also (via Ctrl+click)] to here ->>‘’<<-.’ +CO paste +CR‘Set cursor to here ->THIS>‘’<<-, + and [also (via Ctrl+click)] to here ->THIS2>‘’<<-.’ + +TN 2 // Some newly discovered bug (for the sake of what all this new language (tests_neo) was created) +DA >‘Test’‘’< // incorrect result +CR Test>‘’‘THIS’<. +CO copy +DA 3. Character-by-character select >‘T’<>‘H’<>‘I’<>‘S’<. +CO paste +IR 3. Character-by-character select THIST>‘’‘’‘’‘’< +CR 3. Character-by-character select THIS>‘’‘’‘’‘’<. + +TN 4 // Something like test (10) (incorrect result observed at revision f70397b75fde9a1a6ff082d8acdd1a800bc613e5) +DA >‘?’< +CO copy +DA >‘╚════’< +CO split_selection_into_characters +CR >‘╚’<>‘═’<>‘═’<>‘═’<>‘═’< +CO paste +IR ?>‘’‘’<═?>‘’‘’< +CR ?>‘’‘’‘’‘’‘’< +""" + # Create scratch buffer just for testing purposes + buffer = sublime.active_window().new_file() + buffer.set_scratch(True) + + def switch_test(): + print("passed") + + pos = 0 + while True: + # Skip empty lines + while pos < len(tests_neo) and tests_neo[pos] == '\n': + pos += 1 + + if pos == len(tests_neo): + break + + # Read command + cmd = tests_neo[pos:pos+2] + pos += 2 + + # Read command data + if tests_neo[pos] == " ": + end_of_data = tests_neo.find("\n", pos) + data = tests_neo[pos+1: end_of_data] + comment_start = data.find("//") + if comment_start != -1: + data = data[:comment_start] + data = data.rstrip() + pos = end_of_data + elif tests_neo[pos] == "‘": # ’ + i = pos + nesting_level = 0 + while True: + ch = tests_neo[i] + if ch == "‘": + nesting_level += 1 + elif ch == "’": + nesting_level -= 1 + if nesting_level == 0: + break + i += 1 + if i == len(tests_neo): + raise 'Unpaired quote' + data = tests_neo[pos+1:i] + pos = i + 1 + else: + print(tests_neo[pos:pos+33]) + assert(False) + + if cmd == "TN": # Test Number + if data != "0": + switch_test() + print("Test " + data, end = " ") + elif cmd == "DA": # set DAta + # Find all selection/cursor marks in the data + new_sel = [] + i = 0 + while True: + sel_start = data.find(">‘", i) + if sel_start == -1: + break + sel_end = data.find("’<", sel_start + 2) + assert(sel_end != -1) + data = data[:sel_start] + data[sel_start+2:sel_end] + data[sel_end+2:] # remove service characters (i.e. cursor>‘’‘ion’< mark) + new_sel.append(sublime.Region(sel_start, sel_end-2)) + # Fill up the scratch buffer with new data + buffer.run_command("select_all") + buffer.run_command("right_delete") + buffer.run_command("append", { "characters": data } ) # "insert" is not working totally correctly here, so "append" is used instead + buffer.sel().clear() + buffer.sel().add_all(new_sel) + elif cmd == "CU": # CUrsor/selection manipulation + # [-not implemented yet-] + pass + elif cmd == "CO": + overrided_command = sublime_plugin.on_text_command(buffer.id(), data, None) + buffer.run_command(*overrided_command if overrided_command[0] else (data,)) + elif cmd == "IR" or cmd == "CR": + # Put all cursors/selections marks in buffer's text and compare it with data + buffer_data = buffer.substr(sublime.Region(0, buffer.size())) + new_data = "" + prev_pos = 0 + for sel in buffer.sel(): + new_data += buffer_data[prev_pos:sel.begin()] + ">‘" + buffer_data[sel.begin():sel.end()] + "’<" + prev_pos = sel.end() + new_data += buffer_data[prev_pos:] + if cmd == "IR": + if new_data == data: + print("incorrect result detected (command: IR‘"+data+"’") + return # to skip buffer.close() call + else: + assert(cmd == "CR") + if new_data != data: + print("check result failed (command: CR‘"+data+"’)") + return # to skip buffer.close() call + else: + raise 'Unknown command ' + cmd + + switch_test() + buffer.close() + + +class split_selection_into_characters(sublime_plugin.TextCommand): + def run(self, edit): + newsel = [] + for r in self.view.sel(): + for x in range(r.begin(), r.end()): + newsel += [sublime.Region(x, x+1)] + self.view.sel().clear() + self.view.sel().add_all(newsel) From 276f8a7790939301fe181bd66288d24febd56923 Mon Sep 17 00:00:00 2001 From: Alexander Tretyak Date: Thu, 30 Mar 2017 19:24:48 +1000 Subject: [PATCH 13/20] Fix test TN 2 --- copy_edit.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/copy_edit.py b/copy_edit.py index 3ad4b3a..d439210 100644 --- a/copy_edit.py +++ b/copy_edit.py @@ -89,7 +89,7 @@ def run(self, edit): for sel in self.view.sel(): replace_region = sublime.Region(self.view.line(sel.begin()).begin()) if string[1] else sel self.view.replace(edit, replace_region, string[0]) - new_sels.append(sublime.Region(replace_region.end() + len(string[0]))) + new_sels.append(sublime.Region(replace_region.begin() + len(string[0]))) string = next(sel_strings, None) if string == None: sel_strings = iter(selection_strings) From e07420b70dcc46c031e86f5f87b702430bd19c9a Mon Sep 17 00:00:00 2001 From: eyenseo Date: Wed, 19 Apr 2017 00:29:31 +0200 Subject: [PATCH 14/20] Fix caret position This restores the original sublime text behaviour of copy and pasting without selection. Example: Caret=| Copy Paste aaaa aaaa bb|bb bbbb cccc bb|bb cccc Without this patch the carets return on paste to the front of the line --- copy_edit.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/copy_edit.py b/copy_edit.py index d439210..0784f1f 100644 --- a/copy_edit.py +++ b/copy_edit.py @@ -82,14 +82,12 @@ def run(self, edit): else: strs_per_sel = numstrings - new_sels = [] if len(self.view.sel()) == numstrings or numstrings == 1: # fix for test #10 (character-by-character selection) sel_strings = iter(selection_strings) string = next(sel_strings) for sel in self.view.sel(): replace_region = sublime.Region(self.view.line(sel.begin()).begin()) if string[1] else sel self.view.replace(edit, replace_region, string[0]) - new_sels.append(sublime.Region(replace_region.begin() + len(string[0]))) string = next(sel_strings, None) if string == None: sel_strings = iter(selection_strings) @@ -103,11 +101,14 @@ def run(self, edit): self.view.insert(edit, self.view.line(insertion_point).begin() if string[1] else insertion_point, string[0]) insertion_point += len(string[0]) region = sublime.Region(insertion_point) - new_sels.append(region) str_index = (str_index + strs_per_sel) % numstrings print_status_message("Pasted", len(self.view.sel())) + new_sels=[] + for s in self.view.sel(): + caret=s.end() + new_sels.append(sublime.Region(caret,caret)) self.view.sel().clear() for s in new_sels: self.view.sel().add(s) From 9061b995ea3c6b4fc6c8f6a9cd6c22155392f63b Mon Sep 17 00:00:00 2001 From: Alexander Tretyak Date: Wed, 19 Apr 2017 21:37:50 +1000 Subject: [PATCH 15/20] Add TN 5 (in accordance with Eyenseo's fix) --- cut_copy_paste_tests.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/cut_copy_paste_tests.py b/cut_copy_paste_tests.py index 45cedfb..4dd2131 100644 --- a/cut_copy_paste_tests.py +++ b/cut_copy_paste_tests.py @@ -416,6 +416,21 @@ def where_command_starts(next = 0): # (using this function below is not totally CO paste IR ?>‘’‘’<═?>‘’‘’< CR ?>‘’‘’‘’‘’‘’< + +TN 5 // In accordance with Eyenseo's fix e07420b70dcc46c031e86f5f87b702430bd19c9a (incorrect result observed at revision 276f8a7790939301fe181bd66288d24febd56923) +DA‘aaaa +bb>‘’‘’‘’ Date: Fri, 16 Jun 2017 20:15:06 +1000 Subject: [PATCH 16/20] Fix single line case --- copy_edit.py | 2 +- cut_copy_paste_tests.py | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/copy_edit.py b/copy_edit.py index 0784f1f..a27270e 100644 --- a/copy_edit.py +++ b/copy_edit.py @@ -33,7 +33,7 @@ def copy(self, edit): if len(s): new_sel_strings.append((self.view.substr(s), False)) elif copy_with_empty_sel: - new_sel_strings.append((self.view.substr(self.view.full_line(s)), True)) + new_sel_strings.append((self.view.substr(self.view.line(s)) + "\n", True)) actual_selection_strings = new_sel_strings if all(s == new_sel_strings[0] for s in new_sel_strings): diff --git a/cut_copy_paste_tests.py b/cut_copy_paste_tests.py index 4dd2131..a9cf6d9 100644 --- a/cut_copy_paste_tests.py +++ b/cut_copy_paste_tests.py @@ -431,6 +431,13 @@ def where_command_starts(next = 0): # (using this function below is not totally bbbb bb>‘’‘’‘’ Date: Wed, 4 Oct 2017 14:01:36 +1000 Subject: [PATCH 17/20] Add&fix TN 7 --- copy_edit.py | 8 +++++++- cut_copy_paste_tests.py | 19 +++++++++++++++++-- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/copy_edit.py b/copy_edit.py index a27270e..b54673e 100644 --- a/copy_edit.py +++ b/copy_edit.py @@ -61,6 +61,7 @@ def run(self, edit): class PasteEditCommand(sublime_plugin.TextCommand): def run(self, edit): + global selection_strings #check if clipboard is more up to date pasteboard = add_string_to_paste_history(sublime.get_clipboard()) # this is needed when string was copied to clipboard not within Sublime Text @@ -75,6 +76,11 @@ def run(self, edit): if numsels == 0: return + if numsels == 1: # To fix TN 7 + selection_strings = [(("" if selection_strings[0][1] else "\n") # проверка нужна, так как если selection_strings[0][1] == True, то \n уже есть в конце строки + .join([s[0] for s in selection_strings]), selection_strings[0][1])] + numstrings = 1 + if numstrings <= numsels and numsels % numstrings == 0: strs_per_sel = 1 elif numsels < numstrings and numstrings % numsels == 0: @@ -82,7 +88,7 @@ def run(self, edit): else: strs_per_sel = numstrings - if len(self.view.sel()) == numstrings or numstrings == 1: # fix for test #10 (character-by-character selection) + if numsels == numstrings or numstrings == 1: # fix for test #10 (character-by-character selection) sel_strings = iter(selection_strings) string = next(sel_strings) for sel in self.view.sel(): diff --git a/cut_copy_paste_tests.py b/cut_copy_paste_tests.py index a9cf6d9..39ee1b3 100644 --- a/cut_copy_paste_tests.py +++ b/cut_copy_paste_tests.py @@ -347,6 +347,8 @@ def where_command_starts(next = 0): # (using this function below is not totally pos += 1 # skip \n break print(test_id + ' ' + (compared_result_type if compared_result_type else "INCORRECT")) + if not compared_result_type: + return # to skip buffer.close() call buffer.close() @@ -438,6 +440,16 @@ def where_command_starts(next = 0): # (using this function below is not totally CO paste CR‘line lin>‘’‘1’< +>‘2’<’ +CO copy +DA‘’ +CO paste +IR 12>‘’< +CR‘1 +2>‘’<’ """ # Create scratch buffer just for testing purposes buffer = sublime.active_window().new_file() @@ -509,7 +521,10 @@ def switch_test(): buffer.run_command("right_delete") buffer.run_command("append", { "characters": data } ) # "insert" is not working totally correctly here, so "append" is used instead buffer.sel().clear() - buffer.sel().add_all(new_sel) + if new_sel: # это баг Sublime Text что приходится делать такую проверку (курсор должен сбрасываться автоматически в 0 позицию в этом/данном случае) + buffer.sel().add_all(new_sel) + else: + buffer.sel().add(sublime.Region(0)) elif cmd == "CU": # CUrsor/selection manipulation # [-not implemented yet-] pass @@ -527,7 +542,7 @@ def switch_test(): new_data += buffer_data[prev_pos:] if cmd == "IR": if new_data == data: - print("incorrect result detected (command: IR‘"+data+"’") + print("incorrect result detected (command: IR‘"+data+"’)") return # to skip buffer.close() call else: assert(cmd == "CR") From 77019893d010f958c79dd8245eb49390a3ebb0be Mon Sep 17 00:00:00 2001 From: Alexander Tretyak Date: Sat, 3 Feb 2018 18:40:50 +1000 Subject: [PATCH 18/20] Comment extension for # "insert" is not working totally correctly here, so "append" is used instead --- cut_copy_paste_tests.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/cut_copy_paste_tests.py b/cut_copy_paste_tests.py index 39ee1b3..6e58bb1 100644 --- a/cut_copy_paste_tests.py +++ b/cut_copy_paste_tests.py @@ -279,11 +279,11 @@ def skip_comments(): # Prepare scratch buffer buffer.run_command("select_all") buffer.run_command("right_delete") - buffer.run_command("append", { "characters": "".join([c[0] + '. ' + c[1] + "\n" for c in commands]) } ) # "insert" is not working totally correctly here, so "append" is used instead - - # Process commands - for command in commands: - cmd = re.sub(R' \[[^]]+]', '', command[1]) # remove comments + buffer.run_command("append", { "characters": "".join([c[0] + '. ' + c[1] + "\n" for c in commands]) } ) # || "insert" is not working totally correctly here, so "append" is used instead + # \\ To see what is the difference try + # Process commands # \\ `view.run_command("append", { "characters": " a\nb" } )` + for command in commands: # \\ and + cmd = re.sub(R' \[[^]]+]', '', command[1]) # remove comments # \\ `view.run_command("insert", { "characters": " a\nb" } )` cmd = cmd.rstrip('.')#rstrip('.', 1) # remove ending `.` if present if cmd in ["Cut", "Copy", "Paste"]: #buffer.run_command(cmd.lower()) # this does not work, so emulate correct behaviour manually: From a2c5bae3565e18f4fc2c23af2dba7c7fd6983086 Mon Sep 17 00:00:00 2001 From: Alexander Tretyak Date: Tue, 14 Aug 2018 19:52:35 +1000 Subject: [PATCH 19/20] Add&fix TN 8 --- copy_edit.py | 2 +- cut_copy_paste_tests.py | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/copy_edit.py b/copy_edit.py index b54673e..27dfa61 100644 --- a/copy_edit.py +++ b/copy_edit.py @@ -92,7 +92,7 @@ def run(self, edit): sel_strings = iter(selection_strings) string = next(sel_strings) for sel in self.view.sel(): - replace_region = sublime.Region(self.view.line(sel.begin()).begin()) if string[1] else sel + replace_region = sublime.Region(self.view.line(sel.begin()).begin()) if string[1] and sel.size() == 0 else sel self.view.replace(edit, replace_region, string[0]) string = next(sel_strings, None) if string == None: diff --git a/cut_copy_paste_tests.py b/cut_copy_paste_tests.py index 6e58bb1..002f68e 100644 --- a/cut_copy_paste_tests.py +++ b/cut_copy_paste_tests.py @@ -450,6 +450,16 @@ def where_command_starts(next = 0): # (using this function below is not totally IR 12>‘’< CR‘1 2>‘’<’ + +TN 8 +DA‘1>‘’< +2’ +CO copy +DA‘>‘1 +’<2’ +CO paste +CR‘1 +>‘’<2’ """ # Create scratch buffer just for testing purposes buffer = sublime.active_window().new_file() From 87b0adc0c46ffa349ce879b42f3aacd8d575b71e Mon Sep 17 00:00:00 2001 From: Alexander Tretyak Date: Sun, 19 Aug 2018 10:23:42 +1000 Subject: [PATCH 20/20] =?UTF-8?q?Translate=20comment=20upon=20=E2=80=98req?= =?UTF-8?q?uest=20of=20adzenith=E2=80=99[https://github.com/adzenith/CopyE?= =?UTF-8?q?dit/pull/8#discussion=5Fr211051597],=20and=20remove=20one=20wro?= =?UTF-8?q?ng/strange[/unnecessary]=20comment?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- copy_edit.py | 2 +- cut_copy_paste_tests.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/copy_edit.py b/copy_edit.py index 27dfa61..e38d704 100644 --- a/copy_edit.py +++ b/copy_edit.py @@ -77,7 +77,7 @@ def run(self, edit): return if numsels == 1: # To fix TN 7 - selection_strings = [(("" if selection_strings[0][1] else "\n") # проверка нужна, так как если selection_strings[0][1] == True, то \n уже есть в конце строки + selection_strings = [(("" if selection_strings[0][1] else "\n") # ‘this check is needed because if selection_strings[0][1] == True, then \n is already present at the end of line’\‘проверка нужна, так как если selection_strings[0][1] == True, то \n уже есть в конце строки’ .join([s[0] for s in selection_strings]), selection_strings[0][1])] numstrings = 1 diff --git a/cut_copy_paste_tests.py b/cut_copy_paste_tests.py index 002f68e..2366fbc 100644 --- a/cut_copy_paste_tests.py +++ b/cut_copy_paste_tests.py @@ -531,7 +531,7 @@ def switch_test(): buffer.run_command("right_delete") buffer.run_command("append", { "characters": data } ) # "insert" is not working totally correctly here, so "append" is used instead buffer.sel().clear() - if new_sel: # это баг Sublime Text что приходится делать такую проверку (курсор должен сбрасываться автоматически в 0 позицию в этом/данном случае) + if new_sel: buffer.sel().add_all(new_sel) else: buffer.sel().add(sublime.Region(0))