Created
June 22, 2018 15:29
-
-
Save mattst/950c810c34fc2f5d11c505266f865972 to your computer and use it in GitHub Desktop.
ExpandSelectionByExpression.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# | |
# Name: Expand Selection By Expression | |
# Author: mattst@i-dig.info | |
# Requires: Sublime Text v3 | |
# | |
# ST Command: expand_selection_by_expression | |
# Optional Arg: expression_type --> "regex" (default), "literal" | |
# Optional Arg: case_sensitive --> boolean (default is false) | |
# | |
# { "keys": ["ctrl+shift+t", "ctrl+shift+e"], | |
# "command": "expand_selection_by_expression", | |
# "args": {"expression_type": "literal", "case_sensitive": true} }, | |
import sublime | |
import sublime_plugin | |
REGEX = 101 | |
LITERAL = 102 | |
SENSITIVE = 103 | |
INSENSITIVE = 104 | |
REGIONS_KEY = "expand_selection_by_exp_regions_key" | |
ADD_SELECTIONS_CMD = "expand_selection_by_exp_add_selections" | |
# The ST3 API documentation for find() specifies that it returns | |
# None if the pattern is not found - that happens in ST2, but in | |
# ST3 find() returns the Region(-1, -1) if pattern is not found. | |
NOT_FOUND = sublime.Region(-1, -1) | |
class ExpandSelectionByExpressionCommand(sublime_plugin.TextCommand): | |
""" | |
The ExpandSelectionByExpressionCommand class is a Sublime Text plugin which | |
allows the current selection(s) to be expanded up to and including the text | |
matching a user supplied regular or literal expression. It can be run with | |
or without case sensitivity. | |
""" | |
def run(self, edit, **kwargs): | |
self.expression_type = self.get_expression_type(**kwargs) | |
self.case_sensitivity = self.get_case_sensitivity(**kwargs) | |
self.original_sels = list(self.view.sel()) | |
if len(self.original_sels) < 1: | |
return | |
# Clear and redraw the selections, nicely styled so they are | |
# shown clearly to the user while the search term is entered. | |
self.view.sel().clear() | |
self.view.add_regions(REGIONS_KEY, self.original_sels, "None", "dot", | |
sublime.DRAW_NO_FILL | sublime.DRAW_EMPTY_AS_OVERWRITE) | |
self.show_input_panel() | |
def get_expression_type(self, **kwargs): | |
""" Returns the expression type arg or the regex default. """ | |
expression_type = kwargs.get("expression_type") | |
if isinstance(expression_type, str): | |
if expression_type == "regex": | |
return REGEX | |
elif expression_type == "literal": | |
return LITERAL | |
return REGEX | |
def get_case_sensitivity(self, **kwargs): | |
""" Returns the case sensitivity arg or the insensitive default. """ | |
case_sensitive = kwargs.get("case_sensitive") | |
if isinstance(case_sensitive, bool): | |
if case_sensitive: | |
return SENSITIVE | |
else: | |
return INSENSITIVE | |
return INSENSITIVE | |
def show_input_panel(self): | |
""" Display the input panel for search term entry. """ | |
if self.expression_type == REGEX: | |
input_msg = "Enter Regular Expression" | |
else: | |
input_msg = "Enter Literal Expression" | |
if self.case_sensitivity == INSENSITIVE: | |
input_msg += " (Case Insensitive):" | |
else: | |
input_msg += " (Case Sensitive):" | |
self.view.window().show_input_panel(input_msg, "", self.on_done, | |
self.on_change, self.on_cancel) | |
def on_done(self, expression): | |
""" Called when the user accepts the text in the input panel. """ | |
if len(expression) < 1: | |
self.on_cancel() | |
return | |
# There is no need to call expand_selections() again; on_change() | |
# will have already run expand_selections() with the most recent | |
# expression and assigned the returned selections to REGIONS_KEY | |
# using add_regions(). Finalizing them is all that is needed. | |
# An edit object is needed to reliably add selections, | |
# this class's edit object expired when run() returned. | |
self.view.run_command(ADD_SELECTIONS_CMD) | |
def on_change(self, expression): | |
""" Called when the user changes the text in the input panel. """ | |
if len(expression) > 0: | |
new_sels = self.expand_selections(expression) | |
else: | |
new_sels = self.original_sels | |
self.view.add_regions(REGIONS_KEY, new_sels, "None", "dot", | |
sublime.DRAW_NO_FILL | sublime.DRAW_EMPTY_AS_OVERWRITE) | |
def on_cancel(self): | |
""" Called when the user cancels the input panel. """ | |
# Restore the original selections. | |
self.view.add_regions(REGIONS_KEY, self.original_sels, "None", "dot", | |
sublime.DRAW_NO_FILL | sublime.DRAW_EMPTY_AS_OVERWRITE) | |
# An edit object is needed to reliably add selections, | |
# this class's edit object expired when run() returned. | |
self.view.run_command(ADD_SELECTIONS_CMD) | |
def expand_selections(self, expression): | |
""" | |
Tries to expand the user's original selections up to the first match of | |
the entered expression and in so doing it creates a list of (possibly) | |
expanded selections which are returned. | |
""" | |
# find() defaults to a case sensitive regex. | |
flags = 0 | |
if self.expression_type == LITERAL: | |
flags = flags | sublime.LITERAL | |
if self.case_sensitivity == INSENSITIVE: | |
flags = flags | sublime.IGNORECASE | |
new_sels = [] | |
for sel in self.original_sels: | |
find_region = self.view.find(expression, sel.end(), flags) | |
if find_region == NOT_FOUND: | |
new_sels.append(sel) | |
else: | |
expanded = sublime.Region(sel.begin(), find_region.end()) | |
new_sels.append(expanded) | |
return new_sels | |
class ExpandSelectionByExpAddSelectionsCommand(sublime_plugin.TextCommand): | |
""" | |
Reliably adds the selections referenced by REGIONS_KEY, these will always | |
be either the new selections or the original ones. | |
The JumpToCharSeqCommand class's edit object expires when its run() method | |
returns. Since an edit object is required to reliably add selections, this | |
class provides that functionality. | |
""" | |
def run(self, edit): | |
sels = self.view.get_regions(REGIONS_KEY) | |
self.view.erase_regions(REGIONS_KEY) | |
self.view.sel().add_all(sels) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment