168 lines
4.6 KiB
Python
168 lines
4.6 KiB
Python
import re
|
|
import os
|
|
import textwrap
|
|
import sublime
|
|
import sublime_plugin
|
|
|
|
def get_tab_size(view):
|
|
return int(view.settings().get('tab_size', 8))
|
|
|
|
def normed_indentation_pt(view, sel, non_space=False):
|
|
"""
|
|
Calculates tab normed `visual` position of sel.begin() relative "
|
|
to start of line
|
|
|
|
\n\t\t\t => normed_indentation_pt => 12
|
|
\n \t\t\t => normed_indentation_pt => 12
|
|
|
|
Different amount of characters, same visual indentation.
|
|
"""
|
|
|
|
tab_size = get_tab_size(view)
|
|
pos = 0
|
|
ln = view.line(sel)
|
|
|
|
for pt in xrange(ln.begin(), ln.end() if non_space else sel.begin()):
|
|
ch = view.substr(pt)
|
|
|
|
if ch == '\t':
|
|
pos += tab_size - (pos % tab_size)
|
|
|
|
elif ch.isspace():
|
|
pos += 1
|
|
|
|
elif non_space:
|
|
break
|
|
else:
|
|
pos+=1
|
|
|
|
return pos
|
|
|
|
def compress_column(column):
|
|
# "SS\T"
|
|
if all(c.isspace() for c in column):
|
|
column = '\t'
|
|
|
|
# "CCSS"
|
|
elif column[-1] == ' ':
|
|
while column and column[-1] == ' ':
|
|
column.pop()
|
|
column.append('\t')
|
|
|
|
# "CC\T"
|
|
return column
|
|
|
|
def line_and_normed_pt(view, pt):
|
|
return ( view.rowcol(pt)[0],
|
|
normed_indentation_pt(view, sublime.Region(pt)) )
|
|
|
|
def pt_from_line_and_normed_pt(view, (ln, pt)):
|
|
i = start_pt = view.text_point(ln, 0)
|
|
tab_size = get_tab_size(view)
|
|
|
|
pos = 0
|
|
|
|
for i in xrange(start_pt, start_pt + pt):
|
|
ch = view.substr(i)
|
|
|
|
if ch == '\t':
|
|
pos += tab_size - (pos % tab_size)
|
|
else:
|
|
pos += 1
|
|
|
|
i += 1
|
|
if pos == pt: break
|
|
|
|
return i
|
|
|
|
def save_selections(view, selections=None):
|
|
return [ [line_and_normed_pt(view, p) for p in (sel.a, sel.b)]
|
|
for sel in selections or view.sel() ]
|
|
|
|
def region_from_stored_selection(view, stored):
|
|
return sublime.Region(*[pt_from_line_and_normed_pt(view, p) for p in stored])
|
|
|
|
def restore_selections(view, lines_and_pts):
|
|
view.sel().clear()
|
|
|
|
for stored in lines_and_pts:
|
|
view.sel().add(region_from_stored_selection(view, stored))
|
|
|
|
def unexpand(the_string, tab_size, first_line_offset = 0, only_leading=True):
|
|
lines = the_string.split('\n')
|
|
compressed = []
|
|
|
|
for li, line in enumerate(lines):
|
|
pos = 0
|
|
|
|
if not li: pos += first_line_offset
|
|
|
|
rebuilt_line = []
|
|
column = []
|
|
|
|
for i, char in enumerate(line):
|
|
if only_leading and not char.isspace():
|
|
column.extend(list(line[i:]))
|
|
break
|
|
|
|
column.append(char)
|
|
pos += 1
|
|
|
|
if char == '\t':
|
|
pos += tab_size - (pos % tab_size)
|
|
|
|
if pos % tab_size == 0:
|
|
rebuilt_line.extend(compress_column(column))
|
|
column = []
|
|
|
|
rebuilt_line.extend(column)
|
|
compressed.append(''.join(rebuilt_line))
|
|
|
|
return '\n'.join(compressed)
|
|
|
|
class TabCommand(sublime_plugin.TextCommand):
|
|
translate = False
|
|
|
|
def run(self, edit, set_translate_tabs=False, whole_buffer=True, **kw):
|
|
view = self.view
|
|
|
|
if set_translate_tabs or not self.translate:
|
|
view.settings().set('translate_tabs_to_spaces', self.translate)
|
|
|
|
if whole_buffer or not view.has_non_empty_selection_region():
|
|
self.operation_regions = [sublime.Region(0, view.size())]
|
|
else:
|
|
self.operation_regions = view.sel()
|
|
|
|
sels = save_selections(view)
|
|
visible, = save_selections(view, [view.visible_region()])
|
|
self.do(edit, **kw)
|
|
restore_selections(view, sels)
|
|
visible = region_from_stored_selection(view, visible)
|
|
view.show(visible, False)
|
|
view.run_command("scroll_lines", {"amount": 1.0 })
|
|
|
|
class ExpandTabs(TabCommand):
|
|
translate = True
|
|
|
|
def do(self, edit, **kw):
|
|
view = self.view
|
|
tab_size = get_tab_size(view)
|
|
|
|
for sel in self.operation_regions:
|
|
sel = view.line(sel) # TODO: expand tabs with non regular offsets
|
|
view.replace(edit, sel, view.substr(sel).expandtabs(tab_size))
|
|
|
|
class UnexpandTabs(TabCommand):
|
|
def do(self, edit, only_leading = True, **kw):
|
|
view = self.view
|
|
tab_size = get_tab_size(view)
|
|
|
|
for sel in self.operation_regions:
|
|
the_string = view.substr(sel)
|
|
first_line_off_set = normed_indentation_pt( view, sel ) % tab_size
|
|
|
|
compressed = unexpand( the_string, tab_size, first_line_off_set,
|
|
only_leading = only_leading )
|
|
|
|
view.replace(edit, sel, compressed)
|