Created
September 8, 2011 11:53
-
-
Save sublimator/1203231 to your computer and use it in GitHub Desktop.
Updated Indentation
This file contains hidden or 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
import sublime | |
import sublime_plugin | |
import textwrap | |
def get_tab_size(view): | |
return int(view.settings().get('tab_size', 8)) | |
def handle_tabs(view, string, offset=0): | |
if not view.settings().get('translateTabsToSpaces'): | |
tab_size = view.settings().get('tab_size', 8) | |
string = process_indentation(string, tab_size, offset) | |
return string | |
def insert_or_replace(edit, view, region, string): | |
if region: | |
a = view.replace(edit, region, string) | |
else: | |
a = view.insert(edit, region.begin(), string) | |
return a | |
def insert_or_replace_indented(edit, view, region, string): | |
string = string.splitlines(1) | |
cursor_at = normed_indentation_pt(view, region) | |
indent = cursor_at * ' ' | |
indented_string = ''.join([ | |
handle_tabs(view, l) for l in | |
([ string[0] ] + [ indent + l for l in string[1:] ]) ]) | |
return insert_or_replace(edit, view, region, indented_string) | |
def normed_indentation_pt(view, sel, non_space=False): | |
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 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 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 process_indentation( the_string, tab_size, first_line_offset = 0, | |
only_leading=True, process_column = compress_column): | |
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(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(process_column(column)) | |
column = [] | |
rebuilt_line.extend(column) | |
compressed.append(''.join(rebuilt_line)) | |
return '\n'.join(compressed) | |
class TabCommand(sublime_plugin.TextCommand): | |
def run(self, edit, whole_buffer = True, | |
only_leading = True, | |
translate = None, | |
# ignore set_translate_tabs that was always set `true` | |
**kw ): | |
view = self.view | |
view.settings().set('translate_tabs_to_spaces', ( | |
translate if translate is not None else 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(view, edit, only_leading) | |
restore_selections(view, sels) | |
visible = region_from_stored_selection(view, visible) | |
view.show(visible, False) | |
view.run_command("scroll_lines", {"amount": 1.0 }) | |
def do(self, view, edit, only_leading): | |
tab_size = self.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 = process_indentation ( | |
the_string, tab_size, first_line_off_set, | |
only_leading = only_leading, process_column=self.process_column) | |
view.replace(edit, sel, compressed) | |
class ExpandTabs(TabCommand): | |
translate = True | |
process_column = lambda s, l: ''.join(l).expandtabs(s.tab_size) | |
class UnexpandTabs(TabCommand): | |
translate = False | |
process_column = staticmethod(compress_column) | |
class RelativeIndent(sublime_plugin.TextCommand): | |
is_enabled = lambda s, **a: s.view.sel() | |
def run(self, ed, cmd='paste'): | |
view = self.view | |
sels = view.sel() | |
if cmd == 'paste': | |
clip = sublime.get_clipboard().expandtabs(get_tab_size(view)) | |
clip = textwrap.dedent(clip).rstrip().split(u'\n') | |
if len(sels) > 1: # Columnar Paste | |
for region in view.sel(): | |
insert_or_replace(ed, view, region, clip.pop(0)) | |
else: | |
clip = '\n'.join(clip) | |
insert_or_replace_indented(ed, view, view.sel()[0], clip) | |
else: | |
clip = [] | |
for sel in view.sel(): | |
indent = normed_indentation_pt(view, sel) * ' ' | |
lines = view.substr(sel).splitlines(1) | |
clip.extend([indent + lines[0]] + lines[1:]) | |
sublime.set_clipboard(''.join(clip)) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment