From e2db26574f84f648d03d78b52aa0b9d6d208bb7c Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Sat, 8 Oct 2011 15:30:06 +0200 Subject: [PATCH] Pry::Indent now handles single line statements. Previously certain lines of code would break the indentation process. For example, the following code would result in incorrect indentation: def hello; end puts "Hello world" This would result in the following: def hello; end puts "Hello world" I've worked around this issue by adding a new method (Pry::Indent#skip_indentation?) that does a lookahead on the list of tokens to determine if a line should be indented or not. It's probably not the most efficient way of doing it but it makes it quite easy to add more tokens to the list without adding a lot more complexity. Signed-off-by: Yorick Peterse --- lib/pry/indent.rb | 90 +++++++++++++++++++++++++++++++++++++-------- test/test_indent.rb | 53 +++++++++++++++++++------- 2 files changed, 113 insertions(+), 30 deletions(-) diff --git a/lib/pry/indent.rb b/lib/pry/indent.rb index 66eb8f06..ff7e40be 100644 --- a/lib/pry/indent.rb +++ b/lib/pry/indent.rb @@ -18,19 +18,21 @@ class Pry # The amount of spaces to insert for each indent level. Spaces = ' ' - # Array containing all the tokens that should increase the indentation - # level. - OpenTokens = [ - 'def', - 'class', - 'module', - '[', - '{', - 'do', - 'if', - 'while', - 'for' - ] + # Hash containing all the tokens that should increase the indentation + # level. The keys of this hash are open tokens, the values the matching + # tokens that should prevent a line from being indented if they appear on + # the same line. + OpenTokens = { + 'def' => 'end', + 'class' => 'end', + 'module' => 'end', + 'do' => 'end', + 'if' => 'end', + 'while' => 'end', + 'for' => 'end', + '[' => ']', + '{' => '}', + } # Collection of tokens that decrease the indentation level. ClosingTokens = ['end', ']', '}'] @@ -81,7 +83,8 @@ class Pry # @return [String] The indented version of +input+. # def indent(input) - output = '' + output = '' + open_tokens = OpenTokens.keys input.lines.each do |line| # Remove manually added indentation. @@ -100,7 +103,11 @@ class Pry break # Start token found (such as "class"). Update the stack and indent the # current line. - elsif OpenTokens.include?(token) + elsif open_tokens.include?(token) + # Skip indentation if there's a matching closing token on the same + # line. + next if skip_indentation?(tokens, token) + add = '' last = @stack[-1] @@ -133,9 +140,60 @@ class Pry return output.gsub!(/\s+$/, '') end + ## + # Based on a set of tokens and an open token this method will determine if + # a line has to be indented or not. Perhaps not the most efficient way of + # doing it so if you feel it can be improved patches are more than welcome + # :). + # + # @author Yorick Peterse + # @since 08-10-2011 + # @param [Array] tokens A list of tokens to scan. + # @param [String] open_token The token who's closing token may or may not + # be included in the list of tokens. + # @return [Boolean] + # + def skip_indentation?(tokens, open_token) + return false if !OpenTokens.key?(open_token) - # Fix the indentation for closing tags (notably 'end'). + closing = OpenTokens[open_token] + open = OpenTokens.keys + skip = false + + # If the list of tokens contains a matching closing token the line should + # not be indented (and thus we should return true). + tokens.each do |token, kind| + next if IgnoreTokens.include?(kind) + + # Skip the indentation if we've found a matching closing token. + if token == closing + skip = true + # Sometimes a line contains a matching closing token followed by another + # open token. In this case the line *should* be indented. An example of + # this is the following: + # + # [10, 15].each do |num| + # puts num + # end + # + # Here we have an open token (the "[") as well as it's closing token + # ("]"). However, there's also a "do" which indicates that the next + # line *should* be indented. + elsif open.include?(token) + skip = false + end + end + + return skip + end + + ## + # Fix the indentation for closing tags (notably 'end'). Note that this + # method will not work on Win32 based systems (or other systems that don't + # have the tput command). + # # @param [String] full_line The full line of input, including the prompt. + # def correct_indentation(full_line) # The whitespace is used to "clear" the current line so existing # characters don't show up. diff --git a/test/test_indent.rb b/test/test_indent.rb index ce17dcd6..06796868 100644 --- a/test/test_indent.rb +++ b/test/test_indent.rb @@ -12,21 +12,21 @@ describe Pry::Indent do input = "array = [\n10,\n15\n]" output = "array = [\n 10,\n 15\n]" - @indent.indent(input).should === output + @indent.indent(input).should == output end it 'should indent a hash' do input = "hash = {\n:name => 'Ruby'\n}" output = "hash = {\n :name => 'Ruby'\n}" - @indent.indent(input).should === output + @indent.indent(input).should == output end it 'should indent a function' do input = "def\nreturn 10\nend" output = "def\n return 10\nend" - @indent.indent(input).should === output + @indent.indent(input).should == output end it 'should indent a module and class' do @@ -35,14 +35,39 @@ describe Pry::Indent do input_class = "class Foo\n# Hello world\nend" output_class = "class Foo\n # Hello world\nend" - @indent.indent(input).should === output - @indent.indent(input_class).should === output_class + @indent.indent(input).should == output + @indent.indent(input_class).should == output_class end it 'should indent separate lines' do - @indent.indent('def foo').should === 'def foo' - @indent.indent('return 10').should === ' return 10' - @indent.indent('end').should === 'end' + @indent.indent('def foo').should == 'def foo' + @indent.indent('return 10').should == ' return 10' + @indent.indent('end').should == 'end' + end + + it 'should not indent single line statements' do + input = <