Skip to content

Instantly share code, notes, and snippets.

@mattst
Last active August 11, 2019 04:03
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mattst/4fdc94f4e51affbab01cd9e5811aec0b to your computer and use it in GitHub Desktop.
Save mattst/4fdc94f4e51affbab01cd9e5811aec0b to your computer and use it in GitHub Desktop.
An ST plugin to clear multiple selections to the top or bottom selection.
#
# Name: MultipleSelectionClearerTopOrBottom
# Requirements: Plugin for Sublime Text v2 and v3
# Written by: mattst - https://github.com/mattst
# ST Command: multiple_selection_clearer_top_or_bottom
# Arg: pos: "top" or "bottom"
# Context Key: multiple_selection_clearer_top_or_bottom_key
#
# This plugin has been designed to be used with specific keys, those being the
# same ones as the Move Tab In Group plugin. The context key ensures that this
# plugin is only called when there are 0 or more than 1 selections. If there is
# 1 selection, or the view is a widget (console, panel, overlay, or sidebar),
# then the Move Tab In Group plugin will be activated.
#
# If this plugin is activated by the keys it will do the following:
#
# 0 Selections: A cursor is added on the middle visible line.
#
# >1 Selections: The selections are cleared in such a way that a single cursor
# is placed at the cursor position of the top or bottom selection but soft-undo
# will move the cursor to the non-cursor end, and soft-undo again will restore
# the selected text of the top or bottom selection.
#
# { "keys": ["ctrl+shift+pageup"],
# "command": "move_tab_in_group", "args": {"direction": "left"} },
#
# { "keys": ["ctrl+shift+pagedown"],
# "command": "move_tab_in_group", "args": {"direction": "right"} },
#
# { "keys": ["ctrl+shift+pageup"], "args": {"pos": "top"},
# "command": "multiple_selection_clearer_top_or_bottom",
# "context": [{"key": "multiple_selection_clearer_top_or_bottom_key"}] },
#
# { "keys": ["ctrl+shift+pagedown"], "args": {"pos": "bottom"},
# "command": "multiple_selection_clearer_top_or_bottom",
# "context": [{"key": "multiple_selection_clearer_top_or_bottom_key"}] },
import sublime
import sublime_plugin
class MultipleSelectionClearerTopOrBottomCommand(sublime_plugin.TextCommand):
"""
A Sublime Text plugin to clear all but the top or bottom multiple selection
or to add a cursor on the middle visible line if no selections at all.
"""
def run(self, edit, pos):
sels = self.view.sel()
sels_len = len(sels)
# The MultipleSelectionClearEventListener class (below) in
# conjunction with the keys assigned to this command should
# prevent this plugin from being called if sels_len == 1.
if sels_len == 1:
msg = "MultipleSelectionClearerTopOrBottom plugin error"
msg += " - the plugin should never be called when there "
msg += "is exactly one selection."
raise ValueError(msg)
elif sels_len == 0:
self.cursor_to_middle_visible_line()
elif sels_len > 1:
if isinstance(pos, str) and pos == "bottom":
self.clear_to_selection(sels[sels_len - 1])
else:
self.clear_to_selection(sels[0])
def clear_to_selection(self, sel):
"""
Clears all but the "sel" selection.
By using the MultipleSelectionClearerAddSelCommand helper class
to clear and add selections, in conjunction with set_timeout(),
all the added regions will get added to ST's soft-undo buffer.
After the plugin has been run the effect of this is:
1) The cursor is placed at the cursor end of sel
Press the soft-undo keys and...
2) The cursor is placed at the non-cursor end of sel
Press the soft-undo keys and...
3) The sel selected text is restored
Press the soft-undo keys and...
4) All selections restored to pre-plugin state
"""
# No selected text.
if sel.size() == 0:
self.view.sel().clear()
self.view.sel().add(sel)
return
add_cmd = "multiple_selection_clearer_add_sel"
sel_text_args = {"sel_a": sel.a, "sel_b": sel.b}
cursor_a_args = {"sel_a": sel.a, "sel_b": sel.a}
cursor_b_args = {"sel_a": sel.b, "sel_b": sel.b}
cmd_sel_text = lambda: self.view.run_command(add_cmd, sel_text_args)
cmd_cursor_a = lambda: self.view.run_command(add_cmd, cursor_a_args)
cmd_cursor_b = lambda: self.view.run_command(add_cmd, cursor_b_args)
# Items added with a zero timeout value will
# be called in the order they are received.
sublime.set_timeout(cmd_sel_text, 0)
sublime.set_timeout(cmd_cursor_a, 0)
sublime.set_timeout(cmd_cursor_b, 0)
def cursor_to_middle_visible_line(self):
"""
Adds a single cursor at the start of the middle of the visible lines,
the 'top' middle line gets used if there are an even number of lines.
"""
visible_region = self.view.visible_region()
visible_lines = self.view.lines(visible_region)
# Add 1 because of an ST bug; the bottom line of
# the region is not included by lines() if it is
# blank, which is almost always the case for me.
num_visible_lines = len(visible_lines) + 1
middle_line_index = int(num_visible_lines / 2)
# If even, use the top middle line.
if num_visible_lines % 2 == 0: middle_line_index -= 1
cursor_pos = visible_lines[middle_line_index].begin()
# There is no need to clear the selections because
# this method will only be called if there are none.
self.view.sel().add(sublime.Region(cursor_pos, cursor_pos))
class MultipleSelectionClearerAddSelCommand(sublime_plugin.TextCommand):
""" Helper TextCommand to clear all selections and add a selection. """
def run(self, edit, sel_a, sel_b):
self.view.sel().clear()
self.view.sel().add(sublime.Region(sel_a, sel_b))
class MultipleSelectionClearEventListener(sublime_plugin.EventListener):
""" A class to listen for events triggered by ST. """
def on_query_context(self, view, key, operator, operand, match_all):
"""
The on_query_context() event is triggered by ST to determine whether
key bindings, that contain a context key, should be activated or not.
Key bindings will be activated only if this method returns true.
"""
# Returns true if the key is present, if the view does not
# belong to a widget, i.e. console, panel, overlay, sidebar,
# and if the number of selections is not 1, otherwise false.
return (key == "multiple_selection_clearer_top_or_bottom_key"
and not view.settings().get("is_widget") and len(view.sel()) != 1)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment