diff --git a/Default (Linux).sublime-keymap b/Default (Linux).sublime-keymap index d46d7f1..220e342 100644 --- a/Default (Linux).sublime-keymap +++ b/Default (Linux).sublime-keymap @@ -1,8 +1,3 @@ [ - { "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" } + { "keys": ["ctrl+shift+v"], "command": "paste_from_history_edit" } ] diff --git a/Default (OSX).sublime-keymap b/Default (OSX).sublime-keymap index acab2ff..1cae268 100644 --- a/Default (OSX).sublime-keymap +++ b/Default (OSX).sublime-keymap @@ -1,5 +1,3 @@ [ - { "keys": ["super+x"], "command": "cut_edit" }, - { "keys": ["super+c"], "command": "copy_edit" }, - { "keys": ["super+v"], "command": "paste_edit" } + { "keys": ["super+shift+v"], "command": "paste_from_history_edit" } ] diff --git a/Default (Windows).sublime-keymap b/Default (Windows).sublime-keymap index d46d7f1..220e342 100644 --- a/Default (Windows).sublime-keymap +++ b/Default (Windows).sublime-keymap @@ -1,8 +1,3 @@ [ - { "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" } + { "keys": ["ctrl+shift+v"], "command": "paste_from_history_edit" } ] diff --git a/copy_edit.py b/copy_edit.py index 1ee6e39..e38d704 100644 --- a/copy_edit.py +++ b/copy_edit.py @@ -1,4 +1,4 @@ -import sublime, sublime_plugin +import sublime, sublime_plugin, collections selection_strings = [] @@ -16,13 +16,14 @@ 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) 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") @@ -30,37 +31,44 @@ 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.line(s)) + "\n", 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]] + if len(new_sel_strings) > 0: 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)) - return True + 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 - 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 in reversed(self.view.sel()): - self.view.erase(edit, 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): + global selection_strings #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(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) @@ -68,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") # ‘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 + if numstrings <= numsels and numsels % numstrings == 0: strs_per_sel = 1 elif numsels < numstrings and numstrings % numsels == 0: @@ -75,20 +88,64 @@ 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, insertion_point, string) - insertion_point += len(string) - region = sublime.Region(insertion_point) - new_sels.append(region) - str_index = (str_index + strs_per_sel) % numstrings + 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(): + 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: + 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) + 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) + +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", "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])) diff --git a/cut_copy_paste_tests.py b/cut_copy_paste_tests.py new file mode 100644 index 0000000..2366fbc --- /dev/null +++ b/cut_copy_paste_tests.py @@ -0,0 +1,576 @@ +# 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<- + + +(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 +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 -><-. + + +(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): + 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"(\(\w+\))\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 + # \\ 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: + 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"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() + 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")) + if not compared_result_type: + return # to skip buffer.close() call + + 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 ?>‘’‘’‘’‘’‘’< + +TN 5 // In accordance with Eyenseo's fix e07420b70dcc46c031e86f5f87b702430bd19c9a (incorrect result observed at revision 276f8a7790939301fe181bd66288d24febd56923) +DA‘aaaa +bb>‘’‘’‘’‘’‘’‘1’< +>‘2’<’ +CO copy +DA‘’ +CO paste +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() + 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() + if new_sel: + buffer.sel().add_all(new_sel) + else: + buffer.sel().add(sublime.Region(0)) + 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)