Sublime Forum

How to push selection using middle-click (like in linux)

#1

On linux, it’s a default OS behavior, if you highlight text anywhere, and then press middle-click, it “pastes” the selection there (without even using clipboard)

On windows, there’s no such magic unfortunately, I’d like to know if we can at least have this behavior working inside sublime text only? I’d highlight text, then move my cursor and press middle-click, then that text would go duplicated at cursor position

It’s not { "button": "button3", "command": "paste" } because here that’s the clipboard, so that’s not ideal (I still need to press Ctrl+C before)

Thanks in advance guys

0 Likes

#2

You would need a plugin for that on systems that aren’t Linux; as far as I know there is no such package for Windows/MacOS that would provide this.

The following is an example plugin that would do this, though note that it is a fairly simple sample all things considered. It watches for ANY selection change (be it keyboard or mouse related) and captures the selected text, which the associated command will insert verbatim.

Note:

  1. The capture of the selected text will only happen if there is exactly 1 selection (cursor) and that selection is not empty
  2. The fake_clipboard_paste command disables itself and will do nothing if there is no text in the fake clipboard yet (generally only after a Sublime start) OR there is more than one selection (cursor) OR there is selected text (i.e. if you want to replace selected text with the new text, press backspace after you select the text).

The mousemap entry you would want for this would be:

    // Note: this overrides the default ability to column select
    // with the middle mouse button.
    { "button": "button3", "command": "fake_clipboard_paste" }

If you’re not sure how to install a plugin, see this video

import sublime
import sublime_plugin

import functools

# Storage for the "fake clipboard"; only tracks a single string at a time
_fake_clipboard = ''


class FakeClipboardEventListener(sublime_plugin.EventListener):
    """
    Simple listener that puts the currently selected text FROM THE FIRST
    SELECTION ONLY into a "fake clipboard" whenever the selection changes for
    any reason and the first selection is not empty.

    This means that selecting text with the mouse or keyboard, or any command
    that causes text to be selected will update the fake clipboard.
    """
    pending = 0

    def on_selection_modified_async(self, view):
        self.pending += 1
        sublime.set_timeout_async(functools.partial(self.update, view), 100)

    def update(self, view):
        self.pending -= 1
        if self.pending != 0:
            return

        global _fake_clipboard

        # If the view has at least one cursor in it, AND the area of  the first
        # selection is non-zero (i.e. it is not just a cursor), pull out the
        # text of the selection and put it into the fake clipboard.
        if view.sel() and len(view.sel()[0]):
            _fake_clipboard = text = view.substr(view.sel()[0])



class FakeClipboardPasteCommand(sublime_plugin.TextCommand):
    """
    When triggered, the text from the fake clipboard is inserted at the
    position of the first cursor.

    The command disables itself if there is more than one selection, any text
    is currently selected, or the fake clipboard is empty.
    """
    def run(self, edit):
        self.view.insert(edit, self.view.sel()[0].b, _fake_clipboard)


    def is_enabled(self):
        # Disable the command when the clipboard is empty, there is more than
        # one selection, or the first selection is not empty.
        return (
            _fake_clipboard != '' and
            len(self.view.sel()) == 1 and
            len(self.view.sel()[0]) == 0)
1 Like

#3

You can control-drag:

  • Select text
  • Hold down Ctrl key
  • Drag selection somewhere else, a + icon should be added to your mouse cursor

This is a default thing on windows, it should work (almost) anywhere

0 Likes

#4

Good to know! thanks @watch

Thanks @OdatNurd, working like a charm. Maybe a small difference is that we 1-Select text 2-Place cursor somewhere 3-Middle-click, but in Linux I think step 2 is not needed, it happens automatically on step 3
Not sure it’s possible here in windows+sublime (I can see the logic in is_enabled (last condition) but not sure view.sel can be updated during middle-click).
No big deal, it’s OK like this, thanks again

0 Likes

#5

It would indeed be possible to augment the paste command so that it tries to do the insert at the place where the mouse cursor is.

I don’t have time at the moment, but:

  • The command needs to define want_event() and return True (the default version always returns False)
  • The signature for run() should include event=None as an argument (because it will only be provided if you use the mouse and not when you use the keyboard
  • When event is not None, it has the mouse location; there is an API method to convert that to a position in the view.
  • The self.view.sel()[0].b is saying that the insertion should happen at the cursor location of the first selection; replace that with the value you get from the previous step and it will insert at the mouse location without adjusting the cursor position at all.
1 Like

#6

Thanks very much, got it working (thanks to this too: Is it possible to get the location of the right click in Context.sublime-menu)

import sublime
import sublime_plugin

import functools

# Storage for the "fake clipboard"; only tracks a single string at a time
_fake_clipboard = ''


class FakeClipboardEventListener(sublime_plugin.EventListener):
    """
    Simple listener that puts the currently selected text FROM THE FIRST
    SELECTION ONLY into a "fake clipboard" whenever the selection changes for
    any reason and the first selection is not empty.

    This means that selecting text with the mouse or keyboard, or any command
    that causes text to be selected will update the fake clipboard.
    """
    pending = 0

    def on_selection_modified_async(self, view):
        self.pending += 1
        sublime.set_timeout_async(functools.partial(self.update, view), 100)

    def update(self, view):
        self.pending -= 1
        if self.pending != 0:
            return

        global _fake_clipboard

        # If the view has at least one cursor in it, AND the area of  the first
        # selection is non-zero (i.e. it is not just a cursor), pull out the
        # text of the selection and put it into the fake clipboard.
        if view.sel() and len(view.sel()[0]):
            _fake_clipboard = text = view.substr(view.sel()[0])



class FakeClipboardPasteCommand(sublime_plugin.TextCommand):
    """
    When triggered, the text from the fake clipboard is inserted at the
    position of the first cursor.

    The command disables itself if there is more than one selection, any text
    is currently selected, or the fake clipboard is empty.
    """
    def run(self, edit, event=None):
        pt = self.view.sel()[0].b if not event else self.view.window_to_text((event["x"], event["y"]))
        self.view.insert(edit, pt, _fake_clipboard)

    def want_event(self):
        return True

    def is_enabled(self):
        # Disable the command when clipboard empty or more than 1 selection
        return (
            _fake_clipboard != '' and
            len(self.view.sel()) == 1)
0 Likes

#7

The want_event() as you’re using it here passes event to the command when it’s executed any time it’s triggered via the mouse, includes when you choose an item from the context menu.

In such a case, the event position is the point the mouse was at when the menu was opened.

0 Likes

#8

Ok, is there something I should change? Because it’s working fine for me, context menu as well works fine

0 Likes

#9

Oh, my bad; I read your original statement as a question as to how to do it, not a statement that said that you did it. :slight_smile:

0 Likes