Search code examples
pythonsublimetext3sublimetextsublime-text-plugin

How can I accurately set the new cursor positions after text replacements have been made


I am trying to adapt a plugin for automated text replacement in a Sublime Text 3 Plugin. What I want it to do is paste in text from the clipboard and make some automatic text substitutions

import sublime
import sublime_plugin
import re

class PasteAndEscapeCommand(sublime_plugin.TextCommand):
    def run(self, edit):
        # Position of cursor for all selections
        before_selections = [sel for sel in self.view.sel()]
        # Paste from clipboard
        self.view.run_command('paste')
        # Postion of cursor for all selections after paste
        after_selections = [sel for sel in self.view.sel()]
        # Define a new region based on pre and post paste cursor positions
        new_selections = list()
        delta = 0
        for before, after in zip(before_selections, after_selections):
            new = sublime.Region(before.begin() + delta, after.end()) 
            delta = after.end() - before.end()
            new_selections.append(new)
        # Clear any existing selections
        self.view.sel().clear()
        # Select the saved region
        self.view.sel().add_all(new_selections)
        # Replace text accordingly
        for region in self.view.sel():
            # Get the text from the selected region
            text = self.view.substr(region)
            # Make the required edits on the text
            text = text.replace("\\","\\\\")
            text = text.replace("_","\\_")
            text = text.replace("*","\\*")
            # Paste the text back to the saved region
            self.view.replace(edit, region, text)
        # Clear selections and set cursor position
        self.view.sel().clear()
        self.view.sel().add_all(after_selections)

This works for the most part except I need to get the new region for the edited text. The cursor will be placed to the location of the end of the pasted text. However since I am making replacements which always make the text larger the final position will be inaccurate.

I know very little about Python for Sublime and like most others this is my first plugin.

How do I set the cursor position to account for the size changes in the text. I know I need to do something with the after_selections list as I am not sure how to create new regions as they were created from selections which are cleared in an earlier step.

I feel that I am getting close with

# Add the updated region to the selection
self.view.sel().subtract(region)
self.view.sel().add(sublime.Region(region.begin()+len(text)))

This, for some yet unknown to me reason, places the cursor at the beginning and end of the replaced text. A guess would be that I am removing the regions one by one but forgetting some "initial" region that also exists.

Note

I am pretty sure the double loop in the code in the question here is redundant. but that is outside the scope of the question.


Solution

  • I think your own answer to your question is a good one and probably the way I would go if I was to do something like this in this manner.

    In particular, since the plugin is modifying the text on the fly and making it longer, the first way that immediately presents itself as a solution other than what your own answer is doing would be to track the length change of the text after the replacements so you can adjust the selections accordingly.

    Since I can't really provide a better answer to your question than the one you already came up with, here's an alternative solution to this instead:

    import sublime
    import sublime_plugin
    
    class PasteAndEscapeCommand(sublime_plugin.TextCommand):
        def run(self, edit):
            org_text = sublime.get_clipboard()
            text = org_text.replace("\\","\\\\")
            text = text.replace("_","\\_")
            text = text.replace("*","\\*")
    
            sublime.set_clipboard(text)
            self.view.run_command("paste")
    
            sublime.set_clipboard(org_text)
    

    This modifies the text on the clipboard to be quoted the way you want it to be quoted so that it can just use the built in paste command to perform the paste.

    The last part puts the original clipboard text back on the clipboard, which for your purposes may or may not be needed.