Created
May 24, 2011 14:25
-
-
Save mads-hartmann/988793 to your computer and use it in GitHub Desktop.
align.rb
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
#!/usr/bin/env ruby | |
# Alignes the source | |
def align(text, regexp, width) | |
text.to_a.map do |line| | |
if offset = offsetOfRegexpInLine(line, regexp) | |
if shouldInsertBefore(line, regexp) | |
before = line[0..offset-1] | |
before + ' ' * (width - (before.length)) + line[offset..line.length-1] | |
else | |
before = line[0..offset] | |
before + ' ' * (width - (before.length-1)) + line[offset+1..line.length-1] | |
end | |
else | |
line | |
end | |
end.join | |
end | |
# Figures out if the spacing should be added before or after the match | |
def shouldInsertBefore(line, regexp) | |
offset = offsetOfRegexpInLine(line,regexp) | |
if line.chars.to_a[offset-1] =~ /\s/ then true else false end | |
end | |
# Finds the width of the line with the most text before the regexp. | |
def width(text, regexp) | |
text.split("\n").collect { |line| offsetOfRegexpInLine(line,regexp) }.max | |
end | |
# The offset of a regexp in a line of text. -1 if it doesn't match | |
def offsetOfRegexpInLine(line, regexp) | |
if match = regexp.match(line) | |
match.offset(match.size > 1 ? 1 : 0)[0] | |
else | |
-1 | |
end | |
end | |
# Checks that the regexp matches every line | |
def hasMatchForAll(text, regexp) | |
text.to_a.select { |x| not x =~ /^\s*$/}.all? { |line| line =~ regexp } | |
end | |
def left_spacing(line) | |
line.chars.take_while { |char| char =~ /\s/ }.join | |
end | |
# squeeces all whitspace in the line (preserves the left spacing) | |
def trim_all_whitespace(text) | |
text.to_a.map do |line| | |
left_spacing(line) + line.squeeze(" ").squeeze(" ").lstrip #the 2. is a tab | |
end.join | |
end | |
# finds the minimum offset of the capture of a regexp across all lines in a text. | |
def minOffsetOfRegexp(text,regexp) | |
min_offset = text.split("\n").collect { |line| offsetOfRegexpInLine(line,regexp) }.min | |
{ "min_offset" => min_offset, "regexp" => regexp } | |
end | |
# Sorts and filters the regular expressions so the ones with the captures with the | |
# lowest offset comes first. The ones that aren't matched or doesn't need alignment | |
# are filtered out | |
def prioritizeRegexps(text, regexps) | |
regexps. | |
select { |r| hasMatchForAll(text, r) }. | |
collect { |r| minOffsetOfRegexp(text, r) }. | |
select { |d| not d['min_offset'].nil? }. | |
sort { |d1,d2| d1['min_offset'] <=> d2['min_offset'] }. | |
select { |d| d['min_offset'] > 0 }. | |
collect { |d| d['regexp'] } | |
end | |
# Finds blocks of code to align. It uses the following heuristics: | |
# | |
# - A line belongs to the previous block if it has the same indentation | |
# - A blank line ends a block (TODO) | |
# | |
# returns an array of dictionaries with the keys 'block', 'from', 'to' that | |
# expresses which lines the block spans. | |
def find_blocks(text, regexps) | |
lines = text.to_a | |
initial = { 'prev' => lines[0], | |
'blocks' => [{ 'lines' => [], 'from' => 0, 'to' => 0 }]} | |
lines.reduce(initial) do |reduced, line| | |
blocks = reduced['blocks'] | |
current_block = blocks.last | |
if left_spacing(line) == left_spacing(reduced['prev']) | |
current_block['lines'] << line | |
current_block['to'] = current_block['to'] + 1 | |
{ 'prev' => line, 'blocks' => blocks} | |
else | |
from = current_block['to'] | |
blocks << { 'lines' => [line],'from' => from, 'to' => from + 1} | |
{ 'prev' => line, 'blocks' => blocks} | |
end | |
end['blocks'] | |
end | |
# Formats a single block. | |
def format_block(block_dict, regexps) | |
text = trim_all_whitespace(block_dict['lines'].join) | |
prioritizeRegexps(text, regexps). | |
reduce(text) { |txt,regexp| align(txt, regexp, width(txt, regexp)) } | |
end | |
# Formats every block in the text | |
def format_all(text, regexps) | |
find_blocks(trim_all_whitespace(text), regexps). | |
map { |block| format_block(block, regexps) } | |
end | |
# Formats the source in the block that contains the given line. All other blocks | |
# are just returned w/o formatting. | |
def format_block_containing_line(text, line_number, regexps) | |
find_blocks(text, regexps).map do |block_dict| | |
if block_dict['from'] <= line_number && block_dict['to'] >= line_number | |
format_block(block_dict, regexps) | |
else | |
block_dict['lines'].join | |
end | |
end | |
end | |
=begin | |
Lets give it a try. Side-effecting code is now allowed. | |
=end | |
regexps = [ /(,)(?!$)/, /\}/, /<-/, /\s[-+\/*|]?(=)\s/, /\s(=>)\s/,/:/,/\/\// ] | |
text = DATA.read | |
print format_all(text, regexps) | |
# print format_block_containing_line(text, 10, regexps) | |
# print format_block_containing_line(text, 1, regexps) | |
__END__ | |
val a = "aa" | |
val bb = "bb" | |
{ capitalizeWord, flag::insert }, | |
{ changeCaseOfLetter, flag::insert }, | |
{ changeCaseOfWord, flag::insert }, | |
{ complete, flag::insert|flag::unselect }, | |
{ deleteBackward, flag::erase }, | |
{ deleteBackwardByDecomposingPreviousCharacter, flag::insert }, | |
{ deleteForward, flag::erase }, | |
{ deleteSubWordBackward, flag::erase }, | |
{ deleteSubWordForward, flag::erase }, | |
{ deleteToBeginningOfLine, flag::erase }, | |
{ deleteToBeginningOfParagraph, flag::erase }, | |
{ deleteToEndOfLine, flag::erase }, | |
{ deleteToEndOfParagraph, flag::erase }, | |
{ deleteToMark, flag::mark }, | |
{ deleteWordBackward, flag::erase }, | |
{ deleteWordForward, flag::erase }, | |
{ indent, flag::insert }, | |
{ insertBacktab, flag::insert|flag::unselect }, | |
{ insertNewline, flag::insert|flag::unselect }, | |
{ insertNewlineIgnoringFieldEditor, flag::insert|flag::unselect }, | |
{ insertTab, flag::insert|flag::unselect }, | |
{ insertTabIgnoringFieldEditor, flag::insert|flag::unselect }, | |
{ moveBackward, flag::simple_movement }, | |
{ moveDown, flag::require_continuous_selection|flag::simple_movement }, | |
{ moveDownAndModifySelection, flag::require_continuous_selection }, | |
{ moveForward, flag::simple_movement }, | |
{ moveParagraphBackwardAndModifySelection, flag::require_continuous_selection }, | |
{ moveParagraphForwardAndModifySelection, flag::require_continuous_selection }, | |
{ moveToBeginningOfColumn, flag::require_continuous_selection }, | |
{ moveToBeginningOfColumnAndModifySelection, flag::require_continuous_selection }, | |
{ moveToBeginningOfDocument, flag::require_continuous_selection }, | |
{ moveToBeginningOfDocumentAndModifySelection, flag::require_continuous_selection }, | |
{ moveToBeginningOfParagraph, flag::require_continuous_selection }, | |
{ moveToEndOfColumn, flag::require_continuous_selection }, | |
{ moveToEndOfColumnAndModifySelection, flag::require_continuous_selection }, | |
{ moveToEndOfDocument, flag::require_continuous_selection }, | |
{ moveToEndOfDocumentAndModifySelection, flag::require_continuous_selection }, | |
{ moveToEndOfParagraph, flag::require_continuous_selection }, | |
{ moveUp, flag::require_continuous_selection|flag::simple_movement }, | |
{ moveUpAndModifySelection, flag::require_continuous_selection }, | |
{ moveWordBackward, flag::simple_movement }, | |
{ moveWordForward, flag::simple_movement }, | |
{ nextCompletion, flag::insert|flag::unselect }, | |
{ pageDown, flag::require_continuous_selection }, | |
{ pageDownAndModifySelection, flag::require_continuous_selection }, | |
{ pageUp, flag::require_continuous_selection }, | |
{ pageUpAndModifySelection, flag::require_continuous_selection }, | |
{ previousCompletion, flag::insert|flag::unselect }, | |
{ reformatText, flag::insert }, | |
{ reformatTextAndJustify, flag::insert }, | |
{ selectToMark, flag::mark }, | |
{ shiftLeft, flag::insert }, | |
{ shiftRight, flag::insert }, | |
{ swapWithMark, flag::mark }, | |
{ transpose, flag::insert }, | |
{ unwrapText, flag::insert }, |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment