Sublime Forum

Multiple Selections with Expand Selection to Line

#1

Would someone be able to clarify if this is normal behavior for multiple selections? I’m running Sublime Text on a Mac.

  • Add multiple cursors
  • Command+L to Expand Selection to Line
  • Add same amount of cursors
  • Command+L to Expand Selection to Line
  • Paste to replace the data

My results duplicate the data instead of replacing the data. My GIF shows one example of replacing the lines then an example of using Command+L to Expand Selection to Line.

Multiple%20Selections%20with%20Command%20L

0 Likes

#2

That behaviour is normal, though perhaps unexpected; the ultimate reason it’s doing what it’s doing is that there is a mismatch between the number of lines in the text when it’s copied versus the number of carets when the paste happens, even though it doesn’t seem like it at first blush.

When you have multiple selections and copy text, the clipboard ends up with a single string of text on it that combines all of the selections concatenated together with newline characters; thus you can paste the data into Sublime or other software and end up with all of the data you copied, which allows Sublime to “share” the clipboard with other applications transparently.

When you do a paste when there’s multiple lines of text on the clipboard, one of two things happens; if there are as many carets as there are lines of text, then each caret gets one line – if they don’t match, every caret gets all of the text.

The potentially confusing thing is that when you use Expand Selection to Line, you can see that the cursor visibly ends up on the line following the line where the cursor started, which in your example is lines 2 and 4. So what’s actually being copied is literally the entirety of lines 1 and 3, which includes the newlines on the end of them.

You can see this by using Expand Selection to Line as in your gif, copying the text, and then opening up the Sublime console with Ctrl+` (or View > Show Console) and using sublime.get_clipboard() to see what’s on the clipboard:

>>> sublime.get_clipboard()
'Replace 1\n\nReplace 2\n'

As seen here, you have the whole of line 1 and the whole of line 3, and they’re joined together with a \n in the middle, which is 3 lines of text total (with one of them being blank). So when you have only two cursors in the file and you do a paste, each cursor gets all of the text and it appears to duplicate.

On the other hand, if you copy the text by having multiple cursors and jumping from the start of the line to the end of the line as in your other example, the results on the clipboard are more like what you expect:

>>> sublime.get_clipboard()
'Replace 1\nReplace 2'

Since the cursor is left sitting at the end of the line without including it, the copied text doesn’t contain the \n that terminates each line, making the clipboard contain only two lines; pasting with two cursors now does what you want.

5 Likes

#3

I appreciate your time for this thorough explanation. You’re awesome! It makes sense. I was mistaken thinking multiple cursors would capture the new lines.

I was trying to fold certain headings, replace the text under those headings, then put them back. You don’t have to give me a detailed explanation, but do you know the correct workflow to achieve this? Would it require RegEx, scripting, or is there a hidden Sublime Text feature I’m missing?

0 Likes

#4

If you want to do more than one of them at a time as here, I think you need a plugin or package of some sort for that. To do it with regular copy/paste each section would need to be a single line, so it would need to have the heading and the body all concatenated together by you on copy, which you’d have to fix after you paste.

There may be a package on PackageControl that can help with this, but something like this plugin might help you or be a starting point for your own modifications (see this video if you’re not sure how to install plugins):

import sublime
import sublime_plugin


class MarkdownCopyCommand(sublime_plugin.ApplicationCommand):
    """
    Special overridden copy command for use in markdown files where there is
    more than one caret and all selections are non-empty.
    """
    selections = []

    def run(self):
        # Get the active view and do a normal copy just so the clipboard has
        # what it normally would have.
        view = sublime.active_window().active_view()
        view.run_command("copy")

        # Store the text at each of the selections in the active view in our
        # separate list.
        MarkdownCopyCommand.selections = [view.substr(s) for s in view.sel()]


class MarkdownPasteCommand(sublime_plugin.TextCommand):
    """
    Special overridden paste command for use in markdown files. Can restore
    the selected data from a preview markdown_copy invocation, if the number
    of selections is the same as when the copy happened.
    """
    def run(self, edit):
        # Get the last copied data (if any)
        data = MarkdownCopyCommand.selections

        # If there is a selection count mismatch, then just do a normal paste
        # and leave.
        if len(self.view.sel()) != len(data):
            return self.view.run_command("paste")

        # Iterate over all of the selections in reverse and put the text
        # back where it came from
        for sel, text in reversed(list(zip(self.view.sel(), data))):
            self.view.replace(edit, sel, text)

The markdown_copy command does a normal copy and then also stores the content of each of the selections in it’s own buffer, to keep them distinct. The markdown_paste command will do a normal paste if the number of carets is not the same as the last markdown_copy saved, but if the selection counts match then each selection gets the copied text in one chunk.

A version for cutting the text is not here but could be added fairly easily. The copy command also requires the selections to be non-empty (i.e. it doesn’t handle the copy_with_empty_selection preference, though it could with a bit more tweaking).

I tested it with key bindings something like the following, so that the keys only trigger in the correct circumstances; the contexts include triggering only in markdown files, but there’s nothing inherently markdown-y about them, so they could be used everywhere I would think.

Caveat: I didn’t do any super-exhaustive testing, but it seems to do what one would expect.

{ "keys": ["super+c"], "command": "markdown_copy",
	"context": [
	 	{ "key": "selector", "operator": "equal", "operand": "text.html.markdown" },
	 	{ "key": "num_selections", "operator": "not_equal", "operand": 1 },
	 	{ "key": "selection_empty", "operator": "equal", "operand": false },
	 ]
},
{ "keys": ["super+v"], "command": "markdown_paste",
	"context": [
	 	{ "key": "selector", "operator": "equal", "operand": "text.html.markdown" },
	 ]
}
0 Likes

#5

I’m sorry I didn’t reply earlier, and I appreciate your time with this thorough explanation. I’m still learning, so I will go through, apply, and test everything you wrote. Thank you so much!

1 Like