Skip to content

Instantly share code, notes, and snippets.

@mattst
Created June 22, 2018 15:29
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mattst/950c810c34fc2f5d11c505266f865972 to your computer and use it in GitHub Desktop.
Save mattst/950c810c34fc2f5d11c505266f865972 to your computer and use it in GitHub Desktop.
ExpandSelectionByExpression.py
#
# 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