forked from textmate/ruby.tmbundle
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
A first pass at a much smarter block toggling command. It's now able …
…to cope with scenarios like braces inside a String literal and it properly handles some nesting. This restores the command to working order after it broke due to an Oniguruma update in the last TextMate update.
- Loading branch information
Showing
4 changed files
with
153 additions
and
159 deletions.
There are no files selected for viewing
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,150 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> | ||
<plist version="1.0"> | ||
<dict> | ||
<key>beforeRunningCommand</key> | ||
<string>nop</string> | ||
<key>command</key> | ||
<string>#!/usr/bin/env ruby -wKU | ||
# encoding: UTF-8 | ||
require "rexml/text" | ||
require "#{ENV["TM_SUPPORT_PATH"]}/lib/escape" | ||
# transform XML into normal and sanitized Ruby | ||
ruby, safe_ruby, scope = "", "", [] | ||
STDIN.read.scan(/<(.*?)>|([^<]+)/) do | ||
if $1 | ||
if $1[0] == ?/ | ||
scope.pop | ||
else | ||
scope.push($1) | ||
end | ||
else | ||
unescaped = REXML::Text.unnormalize($2) | ||
ruby << unescaped | ||
# strip strings, regexes, and comments from safe_ruby but leave byte count | ||
if scope.any? { |s| s =~ /\A(?:string|comment)\b/ } | ||
safe_ruby << " " * unescaped.length | ||
else | ||
safe_ruby << unescaped | ||
end | ||
end | ||
end | ||
# find and mark the cursor | ||
line_number = ENV["TM_LINE_NUMBER"].to_i | ||
input_start_line = ENV["TM_INPUT_START_LINE"].to_i | ||
row = line_number - input_start_line | ||
col = ENV["TM_LINE_INDEX"].to_i | ||
if line_number == input_start_line | ||
col -= ENV["TM_INPUT_START_LINE_INDEX"].to_i | ||
end | ||
cursor = ruby[/\A(?:.*\n){#{row - 1}}.{#{col}}/].size | ||
CURSOR = [0xFFFC].pack("U") | ||
B = "(?:\\b|#{CURSOR})" # Note: /\w/u includes CURSOR | ||
ruby[cursor, 0] = CURSOR | ||
safe_ruby[cursor, 0] = CURSOR | ||
# find the block nearest to the cursor | ||
block_start, block_length = nil, nil | ||
loop do | ||
block_start = safe_ruby.rindex( /(\{|#{B}do#{B})/, | ||
block_start.nil? ? cursor : block_start - 1 ) | ||
if block_start.nil? # block not found: give up and don't change the document | ||
print e_sn(ruby).sub(CURSOR, "$0") | ||
exit | ||
end | ||
block_length, nesting = 0, [] | ||
if $1 == "{" | ||
re, starts, stop = /\{|\}|[^{}]+/, ["{"], "}" | ||
else | ||
re, starts, stop = /#{B}do#{B}|#{B}end#{B}|./m, ["do"], "end" | ||
end | ||
safe_ruby[block_start..-1].scan(re) do |token| | ||
block_length += token.length | ||
token.sub!(/\A#{CURSOR}/, "") | ||
token.sub!(/#{CURSOR}\z/, "") | ||
case token | ||
when *starts | ||
nesting << token | ||
when stop | ||
if nesting.last.nil? | ||
nesting << nil | ||
break | ||
else | ||
nesting.pop | ||
break if nesting.empty? | ||
end | ||
end | ||
end | ||
break if nesting.empty? and ruby[block_start, block_length].include? CURSOR | ||
end | ||
block = ruby[block_start, block_length] | ||
# toggle the block | ||
if block[0] == ?{ | ||
block = block[1..-2] | ||
if block.include? "\n" | ||
block[0, 0] = " " if block =~ /\A#{CURSOR}?[A-Za-z0-9_]/ | ||
block << " " if block =~ /[A-Za-z0-9_]#{CURSOR}?\z/ | ||
block = "do#{block}end" | ||
else # expand the block | ||
block.strip! | ||
lines = %w[do] | ||
if block.sub!(/\A(#{CURSOR}?(\s*)\|[^|]*\|)/, "") | ||
lines.first << "#{' ' if $2.empty?}#{$1}" | ||
end | ||
indent = ruby[0...block_start][/^([ \t]*).*\Z/, 1] | ||
tab = ( ENV["TM_SOFT_TABS"] == "YES" ? " " * ENV["TM_TAB_SIZE"].to_i : | ||
"\t" ) | ||
lines << "#{indent}#{tab}#{block.strip}" | ||
lines << "#{indent}end" | ||
block = lines.join("\n") | ||
end | ||
else | ||
block = block[2..-4] | ||
if block.include? "\n" # collapse the block | ||
lines = block.send(block.respond_to?(:lines) ? :lines : :to_s).to_a | ||
lines.first.send( | ||
"#{'r' if lines.first =~ /\A\s*#{CURSOR}?\s*\|[^|]*\|/}strip!" | ||
) | ||
lines[1..-1].each do |line| | ||
line.strip! | ||
end | ||
lines.first << "; " unless lines.first =~ | ||
/\A\s*#{CURSOR}?\s*(?:\|[^|]*\|)?\s*#{CURSOR}?\z/ | ||
lines.first << " " unless lines.first =~ /\s\z/ | ||
lines[1..-2].each do |line| | ||
line << "; " | ||
end | ||
lines[-2].sub!(/; \z/, "") if lines.size > 2 and lines.last.empty? | ||
cursor_by_end = lines.size > 2 && lines.last == CURSOR | ||
lines[-2].sub!(/; \z/, " ") if cursor_by_end | ||
block = "{#{lines.join}#{' ' unless cursor_by_end}}" | ||
else | ||
block = "{#{block}}" | ||
end | ||
end | ||
# replace document | ||
print e_sn(ruby[0...block_start]) | ||
print e_sn(block).sub(CURSOR, "$0") | ||
print e_sn(ruby[(block_start + block_length)..-1]) | ||
</string> | ||
<key>input</key> | ||
<string>selection</string> | ||
<key>inputFormat</key> | ||
<string>xml</string> | ||
<key>keyEquivalent</key> | ||
<string>^{</string> | ||
<key>name</key> | ||
<string>Toggle ‘do … end’ / ‘{ … }’</string> | ||
<key>output</key> | ||
<string>insertAsSnippet</string> | ||
<key>scope</key> | ||
<string>source.ruby</string> | ||
<key>uuid</key> | ||
<string>59E811FF-E722-46BE-8938-04713612FABB</string> | ||
</dict> | ||
</plist> |
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
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