Skip to content

Instantly share code, notes, and snippets.

@FichteFoll
Created May 12, 2012 02:09
  • Star 3 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save FichteFoll/2663710 to your computer and use it in GitHub Desktop.
Plugin for Sublime Text 2 that "prettifies" MultiMarkdown tables and aligns their vertical separators
from sublime import Region
import sublime_plugin
"""
This plugin for Sublime Text 2 searches for tables with a style like:
| I'm a header | I'm second|lolol|
|:-|-:|::|
| I'm a cell | yeah, you bet ||
in either the whole file or just your selections
and aligns the vertical separators accordingly.
///////////////////////////////////////
Usage:
{ "keys": ["alt+m", "alt+t"], "command": "prettify_markdown_table" }
Known issues:
- Due to str.splitlines() trimming blank lines at the end the resulting
string (file) will have no new line at the end.
///////////////////////////////////////
Original source:
http://www.leancrew.com/all-this/2008/08/tables-for-markdown-and-textmate/
Modified:
FichteFoll, for use with ST2 and multiple tables in one string
"""
def just(string, type, n):
"""Justify a string to length n according to type."""
if type == '::':
return string.center(n)
elif type == '-:':
return string.rjust(n)
elif type == ':-':
return string.ljust(n)
else:
return string
def norm_tables(text):
"""Finds and aligns the vertical bars in a text table."""
# Start by turning the text into a list of lines.
lines = text.splitlines()
rows = len(lines)
# iterrate over all lines
i = -1
while i < rows - 1:
i += 1
# First, find the separator line.
if not ( lines[i] and set(lines[i]).issubset('|:.-') ):
continue
# Determine how each column is to be justified.
fstrings = lines[i].strip('|').split('|')
justify = []
try:
for cell in fstrings:
ends = cell[0] + cell[-1]
if ends == '::':
justify.append('::')
elif ends == '-:':
justify.append('-:')
else:
justify.append(':-')
except IndexError:
# The cell is not correctly formatted (i.e. too short)
print("Parse error at line %i: Malformatted format line. (%r)" % (i, lines[i]))
continue
# Assume the number of columns in the separator line is the number
# for the entire table.
columns = len(justify)
if columns == 0:
continue
# print("justify:" + str(justify))
# Extract the content into a matrix (lists within a list).
content = []
# Begin parsing upwards.
j = i - 1
while j >= 0 and lines[j].startswith('|'):
cells = lines[j].strip('| ').split('|')
# Surround b one space as "bumpers."
content.append([ ' ' + x.strip() + ' ' for x in cells ])
j -= 1
# Reverse the list, append a dummy for the format line and continue parsing downwards.
content.reverse()
content.append([])
contentstart = j + 1
j = i + 1
while j < lines and lines[j].startswith('|'):
cells = lines[j].strip('| ').split('|')
# Surround b one space as "bumpers."
content.append([ ' ' + x.strip() + ' ' for x in cells ])
j += 1
# Append cells to rows that don't have enough.
subrows = len(content)
if subrows == 0:
# `subrows = 1` doesn't make much sense as well (since it's only the format line),
# but keep in anyway
continue
for j in range(subrows):
content[j].extend( ('',) * (columns - len(content[j])) )
# Get the width of the content in each column. The minimum width will
# be 2, because that's the shortest length of a formatting string and
# because that also matches an empty column with "bumper" spaces.
widths = [2] * columns
for row in content:
for j in range(columns):
widths[j] = max(len(row[j]), widths[j])
# Add whitespace to make all the columns the same width
formatted = []
for row in content:
formatted.append('|' + '|'.join([ just(s, j, w) for (s, j, w) in zip(row, justify, widths) ]) + '|')
# Recreate the format line with the appropriate column widths.
formatline = '|' + '|'.join([ j[0] + '-' * (n - 2) + j[-1] for (j, n) in zip(justify, widths) ]) + '|'
# "Replace" the newly formatted lines
lines[contentstart:contentstart + subrows] = formatted
# "Replace" the format line back into the list.
lines[i] = formatline
i = contentstart + subrows
# Return the formatted table.
return '\n'.join(lines)
class PrettifyMarkdownTableCommand(sublime_plugin.TextCommand):
def run(self, edit):
sel = self.view.sel()
if sum(1 for reg in sel) == 1 and sel[0].empty():
# collect stuff
reg = sel[0]
row, col = self.view.rowcol(reg.begin())
reg_all = Region(0, self.view.size())
text = self.view.substr(reg_all)
# prettify
self.view.erase(edit, reg_all)
self.view.insert(edit, 0, norm_tables(text))
# scroll back to old line
pt = self.view.text_point(row, col)
self.view.sel().clear()
self.view.sel().add(Region(pt))
self.view.show(pt)
else:
for reg in sel:
if not reg.empty():
# just iterate over every non-empty selection
self.view.replace(edit, reg,
norm_tables(self.view.substr(reg))
)
@dgreen
Copy link

dgreen commented Aug 11, 2012

Thanks for sharing!

Made a couple of minor changes :

  1. Allow blank in format line (per MultiMarkdown)
  2. Added ctrl+k for OS X char mapping

@dgreen
Copy link

dgreen commented Aug 11, 2012

Fixed bug where a row had an empty cell at front or at end (in the forked gist).

Still fails to handle the multi-column cell denoted by two (or more) || at either start of end. There is perl code table_cleanup.pl in Fletcher Penney's Markdown utilities as noted in Brett Terpstra's article.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment