Last active
August 11, 2019 04:03
-
-
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.
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: 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