Sublime Forum

Start selection in one pane and finish in another

#1

Hi everyone,

is there a way to start a selection in one pane and finish it in another?

The use case for me has to do with large files. Really large. Sometimes more than 100MB text files.
I want to be able to select and remove or copy large selections of hundreds to thousands of lines.

The imagined workflow (or how it works in Nedit) is:

  • I go to the first line I want to select.
  • Create a second view into the same file (second pane).
  • In the second pane I go to (search for) the last line of the intended selection.
  • Click in the first pane to mark start of selection. Hold [shift] and click in the second pane to mark the end of the selection. The whole block from first to last line should be selected and can be copied, cut, deleted,…

Is there a way to do this in Sublime? Maybe a Package that enables this behavior?
That is really the only feature I am missing. It really the only thing I have to do every now and then that forces me to start the oldschool Nedit.
Hope I could get that done with Sublime and never have to start Nedit again :grin:

3 Likes

#2

I don’t think there’s a base behavior for this, but it would be possible to accomplish this with a special routine (plugin, package, whatever). I’m not super familiar with the windowing portions of the API, but I think it’d be possible to edit the file as long as both window view objects referred to the same file.

0 Likes

#3

There’s no default behaviour for something like this in Sublime, unfortunately. Although you can create multiple views into the same file (File > New View into File or similar in the Command Palette), such a clone view shares the same underlying text buffer but has it’s own unique view object wrapping it.

The buffer represents the file contents so that changes in any cloned view are reflected in all of the clones, but the state of the selection and the current cursor location (which is actually just a selection of length 0) are local to the view.

I attempted to create a plugin that would synchronize the state of the selection between a view and it’s clones, which would allow this to transparently work as in your current work flow, but unfortunately there’s a bug in Sublime that stops this from working as intended.

As a workaround, below is a simple plugin that will do something like this, albeit with a slightly different work flow. To use this, select Tools > Developer > New Plugin... from the menu and then replace the stub code with the code below, and save it as something like clone_select.py in the default location that Sublime will propose (your User package).

import sublime
import sublime_plugin


class ClonePaneSelectionCommand(sublime_plugin.TextCommand):
    """
    If the current view has exactly one clone, modify the selection in the
    current view so that it starts at the first selection in the other view
    and ends at the first selection in this view.
    """
    def run(self, edit, update_clone=False):
        clone = self.get_clone()

        span = sublime.Region(clone.sel()[0].begin(),
                              self.view.sel()[0].begin())

        self.view.sel().clear()
        self.view.sel().add(span)

        if update_clone:
            clone.sel().clear()
            clone.sel().add(span)

    def get_clone(self):
        for window in sublime.windows():
            for view in window.views():
                if view.buffer_id() == self.view.buffer_id():
                    if view.id() != self.view.id():
                        return view

    def is_enabled(self, update_clone=False):
        return self.view.settings().get("_clones", 0) == 1


class CloneViewListener(sublime_plugin.EventListener):
    """
    Maintain a view setting named "_clones" that indicates how many clones of
    this view exist. The setting is removed when there are no clones.
    """
    def clone_count(self, view, update):
        count = view.settings().get("_clones", 0) + update
        if update != 0:
            view.settings().set("_clones", count)
        return count

    def on_window_command(self, window, cmd, args):
        if cmd == "clone_file":
            self.clone_count(window.active_view(), +1)

    def on_close(self, view):
        if view.settings().has("_clones"):
            count = self.clone_count(view, -1)
            if count == 0:
                view.settings().erase("_clones")

This has two parts that tie together. The CloneViewListener class is responsible for adding, updating and deleting a custom setting named _clones that says how many clones of this view there currently are. The setting is added when the first clone is created and removed when the last clone is closed (leaving only a single view).

The command enables itself only when there is exactly one clone of the current view, and will update the selection in the current view so that it spans from the cursor location in the clone to the cursor position in the current view. Optionally it can also update the selection in the other buffer to match the current one.


Note: Arguably the part of this that tracks the number of clones is not really needed and the code could be adapted to not need it, but I’d already written that part before I discovered my initial approach would not work. :wink:


With this in place, you can bind the command clone_pane_selection to some key combination, then your workflow would be similar to the above:

  1. Go to the first line that you want to select and place the cursor where the selection should start
  2. Create a second view into the same file
  3. In the second pane search for the last line of the intended selection and place the cursor where the selection should end
  4. Press the key combination to update the selection

An example of such a key binding would be the following (update the key as appropriate for your needs):

    {
        "keys": ["ctrl+alt+s"],
        "command": "clone_pane_selection",
        "args": {
            "update_clone": true
        }
    },

This sample binding sets the update_clone argument to the command to true, which will make it update the other view (the one your cursor is not in) to have the same selection as what gets set in this view. Presumably that’s how it would work in Nedit. If you like you can change that to false instead, and it will leave the selection in the first view alone.

2 Likes

#4

Have you tried marks? You can:

  1. Put cursor in the beginning and set mark (Edit -> Mark -> Set Mark). You’ll see the dot in the gutter.
  2. Put cursor in the end and select to the set mark (Edit -> Mark -> Select to Mark).

With hotkeys this obviously will be faster.

5 Likes

#5

@OdatNurd: Thank you so much! This is an awesome solution!!

@lastsecondsave: Thank you too. That’s also a good tip. I prefer the other solution, because it is more visual and I can actually see and edit both ends of the selection.

Thanks again guys!

0 Likes