Search code examples
sublimetext3sublimetext2sublimetextsublime-text-plugin

Preserve selection on reverse "Split into Lines" command


If you select few lines of text and press Ctrl-Shilf-L (or Menu > Selection > Split into Lines) you will see that there are multicursors on the end of each line.

Here is plugin which is trying to accomplish the similar task, but now the cursors should be located at the start of lines.

This plugin works, but I want slightly improve it - it should preserve selection, just like default Ctrl-Shift-L behaviour.

import sublime_plugin

class SplitIntoLinesReverseCommand(sublime_plugin.TextCommand):
    def run(self, edit):
        self.view.run_command("split_selection_into_lines")
        # Seems to be redundant:
        # self.view.run_command("expand_selection", {"to": "line"})
        self.view.run_command("move", {"by": "characters", "forward": False})
        # Already tried this:
        # self.view.run_command("move", {"by": "characters", "forward": False, "extend": True})

Text for test:

foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo
bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar
baz baz baz baz baz baz baz baz baz baz baz baz baz baz baz baz

Solution

  • It sounds like you want something like this:

    import sublime_plugin
    
    class SplitIntoLinesReverseCommand(sublime_plugin.TextCommand):
        def run(self, edit):
            self.view.run_command("split_selection_into_lines")
            self.view.run_command("move", {"by": "characters", "forward": True})
            self.view.run_command("move_to", {"to": "bol", "extend": True})
    

    The steps here are:

    1. Use the existing command to split the selection into lines
      • This leaves every line selected with the cursor at the end of the line
    2. Move the cursor forward one character
      • When there is selected text, moving the cursor forward or backward by characters shifts the caret to that end of the selection and removes the selection; this this leaves the carets at the ends of the lines and removes the selection.
    3. Move the cursor to the beginning of the line, extending the selection
      • This puts the cursor at the position you want it to be in and puts the selection back.

    There are other ways to do the same thing by manipulating the selections directly, but it's generally easier and faster to build on existing commands as is being done here.


    The above solution will not work as expected if you have word wrapping enabled and any lines in the selection that are wrapping. This is because when a line is wrapped, the movement commands for jumping to the beginning and end of the line will jump to the logical (visual) end of the line the caret is on and not to the physical start and end of the line.

    In order to work in a word wrapping situation you need to take that into account. Again, this would be possible by manually fiddling with the selections, but the easier way to is temporarily turn off word wrapping for the duration of the command so that the movements work as you expect:

    import sublime_plugin
    
    class SplitIntoLinesReverseCommand(sublime_plugin.TextCommand):
        def run(self, edit):
            # Save the state of word wrap and ensure that it's turned off
            word_wrap = self.view.settings().get("word_wrap", None)
            self.view.settings().set("word_wrap", False)
    
            self.view.run_command("split_selection_into_lines")
            self.view.run_command("move", {"by": "characters", "forward": True})
            self.view.run_command("move_to", {"to": "bol", "extend": True})
    
            # Replace the setting as long as it existed.
            if word_wrap is not None:
                self.view.settings().set("word_wrap", word_wrap)