Skip to content

Instantly share code, notes, and snippets.

@mads-hartmann
Created May 24, 2011 14:25
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save mads-hartmann/988793 to your computer and use it in GitHub Desktop.
Save mads-hartmann/988793 to your computer and use it in GitHub Desktop.
align.rb
#!/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