Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Swap directions by hitting the shortcut a second time #8

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ 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.
Expand All @@ -13,6 +13,8 @@ Fill a void in your Sublime Text multiple selection capabilities! This plugin al
- `-/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).

On Windows and Linux the default shortcuts use <kbd>alt</kbd>. On OSX <kbd>ctrl</kbd> is used.
Expand Down
86 changes: 59 additions & 27 deletions select-until.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,52 +10,56 @@ 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]
chars = groups[2] or groups[4]
regex = groups[3]
searchForward = isReverse ^ SelectUntilCommand.searchForward

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()))
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 +75,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/. Use minus (-) or 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