Created
November 9, 2013 14:26
-
-
Save sellmerfud/7385988 to your computer and use it in GitHub Desktop.
Textmate align command for scala
Adapted from the align bundle. Handles alignment on =>, =, ->, <-
This file contains 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 | |
# | |
# Scala alignment command. | |
# | |
# This script will attempt to align lines of scala code around one of the following | |
# symbols: | |
# case symbol: => | |
# assignment: = | |
# right arrow: -> | |
# left arrow: <- | |
# | |
# To determine which alignment symbol to use, the script will try matching | |
# the current line to see which symbol it contains. The symbols are | |
# tried in the following order (=>, =, ->, <-). | |
# | |
# If there is a selection, then the we start at the top of the selection and move | |
# down to the last line of the selection using the first line that contains one | |
# of the alignment symbols. And the entire operation will be limited | |
# to the selected text. In this case there may be lines that do not contain | |
# the alignment symbol and these will be left alone. | |
# | |
# If there is no selection, then only lines directly above and below the current | |
# line that contain the alignment symbol are affected. As soon as a line above | |
# or below the current line is found that does not contain the alignment symbol, | |
# that forms the boundary of the operation. | |
# | |
# | |
# This should be set up as a TextMate command as follows: | |
# input : 'Selected Text' or 'Document' | |
# output: 'Replace Selected Text | |
# | |
# `lines` will contain either selected text or entire document. | |
# If there is a selection, current_line will be the last line of the selection. | |
def match_symbol(line) | |
alignments = [ | |
{ :symbol => "=>", | |
:line_pattern => /=>/, | |
:col_pattern => /[\t ]*=>/, | |
:split_pattern => /[\t ]*=>[\t ]*/ | |
}, | |
{ :symbol => "=", | |
:line_pattern => /[^=]*[^-+<>=!%\/|&*^]=(?!=|~|>)/, | |
:col_pattern => /[\t ]*=(?!=|~|>)/, | |
:split_pattern => /[\t ]*=(?!=|~|>)[\t ]*/ | |
}, | |
{ :symbol => "->", | |
:line_pattern => /->/, | |
:col_pattern => /[\t ]*->/, | |
:split_pattern => /[\t ]*->[\t ]*/ | |
}, | |
{ :symbol => "<-", | |
:line_pattern => /<-/, | |
:col_pattern => /[\t ]*<-/, | |
:split_pattern => /[\t ]*<-[\t ]*/ | |
} | |
] | |
i = alignments.index { |entry| line =~ entry[:line_pattern] } | |
i ? alignments[i] : nil | |
end | |
lines = STDIN.readlines() | |
have_selection = ENV.member?("TM_SELECTED_TEXT") | |
block_top = 0 | |
block_bottom = lines.length - 1 | |
a = nil | |
if have_selection | |
# Starting at the top of the selection find the first line that contains | |
# one of our alignment symbols. | |
0.upto(lines.length - 1) do |number| | |
a = match_symbol(lines[number]) | |
break unless a.nil? | |
end | |
else | |
# | |
# We start looking on the current line. However, if the | |
# current line doesn't match the line_pattern, we may be just | |
# after or just before a block, and we should check. If | |
# neither, we are done. | |
start_on = ENV["TM_LINE_NUMBER"].to_i - 1 | |
block_top = lines.length | |
block_bottom = 0 | |
a = match_symbol(lines[start_on]) | |
# | |
# Now with the search boundaries set, start looking for | |
# the block top and bottom. | |
unless a.nil? | |
start_on.downto(0) do |number| | |
break unless lines[number] =~ a[:line_pattern] | |
block_top = number | |
end | |
start_on.upto(lines.length - 1) do |number| | |
break unless lines[number] =~ a[:line_pattern] | |
block_bottom = number | |
end | |
end | |
end | |
# If we found a line with an alignment symbol then we are in business | |
unless a.nil? | |
# First off, make sure that the indentation is the same for each line. | |
# The smallest indentation is used. | |
indentation = nil | |
block_top.upto(block_bottom) do |number| | |
line = lines[number] | |
if line =~ a[:line_pattern] then | |
leading = line.match(/^\s*/).to_s | |
indentation = leading if indentation.nil? || leading.size < indentation.size | |
end | |
end | |
# | |
# Indent all relevant lines with the calculated indention. | |
block_top.upto(block_bottom) do |number| | |
line = lines[number] | |
lines[number] = indentation + line.sub(/^\s*/,"") if line =~ a[:line_pattern] | |
end | |
# | |
# Now, iterate over the block and find the best column number | |
# for the `symbol`. The `column_pattern` will tell us the position of the | |
# first bit of whitespace before the equal sign. We put the | |
# equals sign to the right of the furthest-right one. Note that | |
# we cannot assume every line in the block is relevant. | |
# | |
# First find the best column for the symbol. | |
best_column = 0 | |
block_top.upto(block_bottom) do |number| | |
line = lines[number] | |
if line =~ a[:line_pattern] then | |
m = a[:col_pattern].match(line) | |
best_column = m.begin(0) if m.begin(0) > best_column | |
end | |
end | |
# | |
# Now reformat the relevant lines in the block. | |
block_top.upto(block_bottom) do |number| | |
line = lines[number] | |
if line =~ a[:line_pattern] then | |
before, after = line.split(a[:split_pattern], 2) | |
lines[number] = [before.ljust(best_column), after].join(" #{a[:symbol]} ") | |
end | |
end | |
end | |
# | |
# Output the replacement text | |
print lines | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment