Sublime Forum

Can a plugin influence where the undo command leaves the caret?

#1

Say I have this file:

aaa bbb ccc
aaa bbb ccc
aaa $|xxx ccc

$| denotes the selection (1 selection), where | is the caret.

My plugin performs the following steps:

First, it changes the selection like this:

|aaa bbb ccc
aaa bbb ccc
aaa $xxx ccc

Then, it executes right_delete. These two operations are executed from a parent TextCommand that drives the whole process.

If I undo the deletion, I end up with this:

aaa bbb ccc
aaa bbb ccc
aaa $|xxx ccc

In other words, I’m exactly where I was at the beginning.

However, I want to end up in this state when I undo:

$|aaa bbb ccc
aaa bbb ccc
aaa xxx ccc

I have tried using mark_undo_groups_for_gluingglue_marked_undo_groups after my selection and before the delete operation. I’ve also tried making the selection changes in a separate TextCommand, but neither method will work.

While investigating, it seems that even soft_undo skips over my selection change.

Is there any way in which I can tell Sublime “ignore all previous selection changes in this undo group and use the current selection when undoing”? For example, is there a select command that contributes a separate undo step to the undo stack? (Just an example.)

I think I’ve ran out of ideas how to achieve this.

2 Likes

#2

you can do it like this:

In the user-facing command that is executed from a keybinding/menu/whatever:

  • make note of the selection position
  • move the selection to where you want it to be after the undo command
  • use set_timeout to execute another TextCommand (passing it the selection position noted):
    • which will move the selection and
    • perform the right_delete

Example:

import sublime
import sublime_plugin

class MoveCursorToUndoPosThenDoDeleteCommand(sublime_plugin.TextCommand): # user facing command
    def run(self, edit):
        sel_for_right_delete = sublime.Region(self.view.sel()[0].end(), 0)
        pos_after_undo = sublime.Region(0, 0)
        self.view.sel().clear()
        self.view.sel().add(pos_after_undo)
        
        sublime.set_timeout(lambda: self.view.run_command('do_delete', { 'a': sel_for_right_delete.a, 'b': sel_for_right_delete.b }), 0)

class DoDeleteCommand(sublime_plugin.TextCommand): # command that does the actual work
    def run(self, edit, **kwargs):
        self.view.sel().clear()
        self.view.sel().add(sublime.Region(kwargs['a'], kwargs['b']))
        self.view.run_command('right_delete')

to test this:

  • set a keybinding for the obtusely named move_cursor_to_undo_pos_then_do_delete command

  • create a view with the following contents / selection:

      aaa bbb ccc
      aaa bbb ccc
      aaa $|xxx ccc
    
  • press the keybinding

  • undo

  • note the position of the cursor

2 Likes

#3

I think this may work for my use case. Thanks for the idea!

0 Likes