Skip to content
7 changes: 3 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,13 @@ Fill a void in your Sublime Text multiple selection capabilities! This plugin al

## Usage

- <kbd>ctrl/alt</kbd> + <kbd>shift</kbd> + <kbd>s</kbd>: pulls up an input field, where you can type:
- <kbd>ctrl/alt</kbd> + <kbd>shift</kbd> + <kbd>s</kbd> once: pulls up an input field, where you can type:

- `search term` or `[search term]`: for each selection, select up to and including the first occurrence of the search term.
- `/regex search/`: select through the first occurrence of the regex.
- `{character count}`: select forward the given number of characters.
- `-[search term]`: select backwards up to and including the search term.
- `-/regex/`: backwards regex.
- `-{character count}`: select backwards a certain number of characters (`{-count}` works too).

- <kbd>ctrl/alt</kbd> + <kbd>shift</kbd> + <kbd>s</kbd> a second time: reverse the search direction.

- <kbd>ctrl/alt</kbd> + <kbd>shift</kbd> + <kbd>r</kbd>: reverse all selections (so if the insertion point is at the end of the selection, it is moved to the beginning, and vice versa).

Expand Down
93 changes: 62 additions & 31 deletions select-until.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,52 +10,55 @@ def safe_end(region):
return -1
return region.end()

def clean_up(view):
view.erase_regions("select-until-extended")
view.erase_regions("select-until")
view.erase_regions("select-until-originals")
SelectUntilCommand.running = False

def on_done(view, extend):
if extend:
newSels = view.get_regions("select-until-extended")
else:
newSels = view.get_regions("select-until")
view.erase_regions("select-until-extended")
view.erase_regions("select-until")

sels = view.sel()
sels.clear()
for sel in newSels:
sels.add(sel)

SelectUntilCommand.prevSelector = SelectUntilCommand.temp or SelectUntilCommand.prevSelector
clean_up(view)

rSelector = re.compile("^(-?)(?:\{(-?\d+)\}|\[(.+)\]|/(.+)/)$")
rSelector = re.compile("^\{(-?\d+)\}|\[(.+)\]|/(.+)/|(.*)$")
def find_matching_point(view, sel, selector):
if selector == "": return -1

result = rSelector.search(selector)

if result is None: return safe_end(view.find(selector, sel.end(), sublime.LITERAL))

groups = result.groups()
isReverse = (groups[0] == "-")
num = int(groups[1]) if groups[1] is not None else None
chars = groups[2]
regex = groups[3]

if num is not None:
if isReverse: return sel.begin() - num
else: return sel.end() + num

if not isReverse:
if regex is not None: return safe_end(view.find(regex, sel.end()))
num = int(groups[0]) if groups[0] is not None else None
chars = groups[1] or groups[3]
regex = groups[2]
searchForward = SelectUntilCommand.searchForward

if searchForward:
if num is not None: return sel.end() + num
elif regex is not None: return safe_end(view.find(regex, sel.end()))
else: return safe_end(view.find(chars, sel.end(), sublime.LITERAL))

if regex is not None: regions = view.find_all(regex)
else: regions = view.find_all(chars, sublime.LITERAL)
else:
if num is not None: return sel.begin() - num
elif regex is not None: regions = view.find_all(regex)
else: regions = view.find_all(chars, sublime.LITERAL)

for region in reversed(regions):
if region.end() <= sel.begin():
return region.begin()

for region in reversed(regions):
if region.end() <= sel.begin():
return region.begin()
return -1

def on_change(view, oriSels, selector):
def on_change(view, oriSels, selector, extend):
SelectUntilCommand.temp = selector
extendedSels = []
newSels = []
Expand All @@ -71,31 +74,59 @@ def on_change(view, oriSels, selector):

newSels.append(region)

view.add_regions("select-until-extended", extendedSels, "comment", "", sublime.DRAW_OUTLINED)
view.add_regions("select-until", newSels, "comment", "", sublime.DRAW_OUTLINED)
view.add_regions("select-until-originals", oriSels, "comment", "", sublime.DRAW_EMPTY)
if extend:
view.erase_regions("select-until")
view.add_regions("select-until-extended", extendedSels, "entity", "", sublime.DRAW_OUTLINED)
else:
view.erase_regions("select-until-extended")
view.add_regions("select-until", newSels, "entity", "", sublime.DRAW_EMPTY)

def on_cancel(view, oriSels):
view.erase_regions("select-until-extended")
view.erase_regions("select-until")

sels = view.sel()
sels.clear()
for sel in oriSels:
sels.add(sel)

clean_up(view)

class SelectUntilCommand(sublime_plugin.TextCommand):
temp = ""
prevSelector = ""

#If we get called again while the quick panel's up, on_cancel gets called.
#There's no way in the API to distinguish this from the user pressing esc, so
#we have to do it ourselves.
running = False
searchForward = True

def run(self, edit, extend):
view = self.view
#Make sure the view never refers to the quick panel - if we hit the shortcut
#while the panel is up, the wrong view is targetted.
view = self.view.window().active_view_in_group(self.view.window().active_group())

if SelectUntilCommand.running:
#Don't switch direction if the panel is open but unfocussed
if view != self.view and SelectUntilCommand.extend == extend:
SelectUntilCommand.searchForward = not SelectUntilCommand.searchForward
SelectUntilCommand.prevSelector = SelectUntilCommand.temp
else:
SelectUntilCommand.searchForward = True
SelectUntilCommand.running = True
SelectUntilCommand.extend = extend

#We have to use set_timeout here; otherwise the quick panel doesn't actually
#update correctly if we open it a second time. Seems to be a bug in Sublime.
sublime.set_timeout(lambda : self.show_panel(view, extend), 0)

def show_panel(self, view, extend):
oriSels = [ sel for sel in view.sel() ]

direction = "Next" if SelectUntilCommand.searchForward else "Previous"
v = view.window().show_input_panel(
"Select Until Next -- chars or [chars] or {count} or /regex/. Use minus (-) to reverse search:",
"Select Until {} -- chars or [chars] or {{count}} or /regex/. Press shortcut again to reverse search:".format(direction),
SelectUntilCommand.prevSelector,
lambda selector: on_done(view, extend),
lambda selector: on_change(view, oriSels, selector),
lambda selector: on_change(view, oriSels, selector, extend),
lambda : on_cancel(view, oriSels)
)
v.sel().clear()
Expand Down