Dotfiles/sublime-text/Default/swap_line.py

111 lines
3.7 KiB
Python
Raw Normal View History

2014-05-12 18:57:15 +02:00
import sublime, sublime_plugin
def expand_to_line(view, region):
"""
As view.full_line, but doesn't expand to the next line if a full line is
already selected
"""
if not (region.a == region.b) and view.substr(region.end() - 1) == '\n':
return sublime.Region(view.line(region).begin(), region.end())
else:
return view.full_line(region)
def extract_line_blocks(view):
blocks = [expand_to_line(view, s) for s in view.sel()]
if len(blocks) == 0:
return blocks
# merge any adjacent blocks
merged_blocks = [blocks[0]]
for block in blocks[1:]:
last_block = merged_blocks[-1]
if block.begin() <= last_block.end():
merged_blocks[-1] = sublime.Region(last_block.begin(), block.end())
else:
merged_blocks.append(block)
return merged_blocks
class SwapLineUpCommand(sublime_plugin.TextCommand):
def run(self, edit):
blocks = extract_line_blocks(self.view)
# No selection
if len(blocks) == 0:
return
# Already at BOF
if blocks[0].begin() == 0:
return
# Add a trailing newline if required, the logic is simpler if every line
# ends with a newline
add_trailing_newline = (self.view.substr(self.view.size() - 1) != '\n') and blocks[-1].b == self.view.size()
if add_trailing_newline:
# The insert can cause the selection to move. This isn't wanted, so
# reset the selection if it has moved to EOF
sel = [r for r in self.view.sel()]
self.view.insert(edit, self.view.size(), '\n')
if self.view.sel()[-1].end() == self.view.size():
# Selection has moved, restore the previous selection
self.view.sel().clear()
for r in sel:
self.view.sel().add(r)
# Fix up any block that should now include this newline
blocks[-1] = sublime.Region(blocks[-1].a, blocks[-1].b + 1)
# Process in reverse order
blocks.reverse()
for b in blocks:
prev_line = self.view.full_line(b.begin() - 1)
self.view.insert(edit, b.end(), self.view.substr(prev_line))
self.view.erase(edit, prev_line)
if add_trailing_newline:
# Remove the added newline
self.view.erase(edit, sublime.Region(self.view.size() - 1, self.view.size()))
# Ensure the selection is visible
self.view.show(self.view.sel(), False)
class SwapLineDownCommand(sublime_plugin.TextCommand):
def run(self, edit):
blocks = extract_line_blocks(self.view)
# No selection
if len(blocks) == 0:
return
# Already at EOF
if blocks[-1].end() == self.view.size():
return
# Add a trailing newline if required, the logic is simpler if every line
# ends with a newline
add_trailing_newline = (self.view.substr(self.view.size() - 1) != '\n')
if add_trailing_newline:
# No block can be at EOF (checked above), so no need to fix up the
# blocks
self.view.insert(edit, self.view.size(), '\n')
# Process in reverse order
blocks.reverse()
for b in blocks:
next_line = self.view.full_line(b.end())
contents = self.view.substr(next_line)
self.view.erase(edit, next_line)
self.view.insert(edit, b.begin(), contents)
if add_trailing_newline:
# Remove the added newline
self.view.erase(edit, sublime.Region(self.view.size() - 1, self.view.size()))
# Ensure the selection is visible
self.view.show(self.view.sel(), False)