Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

advanced multi-line analyzing, improve live_error and ripper implemen…

…tation
  • Loading branch information...
commit ae5499c0b8ca0d2d5606ff42cbb1d49de96ec13b 1 parent db03cc7
@janlelis authored
View
16 README.rdoc
@@ -11,14 +11,14 @@ Add the following line to your <tt>~/.riplrc</tt>
require 'ripl/multi_line'
-*Hint:* In multi-line situations, you can press <tt>ctrl+c</tt> and the last line will be removed.
+<b>Hint:</b> In multi-line situations, you can press <tt>ctrl+c</tt> and the last line will be removed.
== Options
You can customize your multi-line prompt with the <tt>:multi_line_prompt</tt> option. For example, put this into your <tt>~/.riplrc</tt>:
Ripl.config[:multi_line_prompt] = ' > '
-Another option is <tt>:multi_line_short_history</tt>. It's activated by default and enables the following history tweak:
+Another option is <tt>:multi_line_history</tt>. The default option is <tt>:compact</tt> which tries to squeeze your last multi-line block into one line (works best in mri, in about 98% of all cases).
>> def some_multi_line_block
| 42
@@ -26,13 +26,19 @@ Another option is <tt>:multi_line_short_history</tt>. It's activated by default
# Press <UP>
>> def some_multi_line_block; 42; end
+To deactivate this tweak, set it to </tt>:plain</tt> or a false value.
+
== AutoIndent
Check out the {ripl-auto_indent}[https://github.com/janlelis/ripl-auto_indent] plugin to get ruby syntax indention.
-== Extending MultiLine
-To use your own multi-line detection engine, read the source at <tt>lib/ripl/multi_line.rb</tt>, implement your version at <tt>lib/ripl/multi_line/your_correct_detection.rb</tt> in which you overwrite <tt>Shell#multiline?</tt> in a <tt>Ripl::MultiLine</tt> sub-module named <tt>YourCorrectDetection</tt> and set <tt>Ripl.config[:multi_line_engine]</tt> to <tt>:your_correct_detection</tt>.
+== MultiLine detection
+To use your own multi-line detection engine, read the source at <tt>lib/ripl/multi_line.rb</tt>, implement your version at <tt>lib/ripl/multi_line/your_correct_detection.rb</tt> in which you overwrite <tt>Shell#multiline?</tt> in a <tt>Ripl::MultiLine</tt> sub-module named <tt>YourCorrectDetection</tt> and set <tt>Ripl.config[:multi_line_engine]</tt> to <tt>:your_correct_detection</tt>. See <tt>lib/ripl/multi_line/ripper.rb</tt> for an example.
+
+Currently available engines:
+* LiveError (<b>default</b>): Simply evaluate expression, if it throws a multi-line syntax error, fall back to multi-line input mode. Sounds scary, but works pretty well in practice.
+* Ripper: Analyze input with Ripper which comes with 1.9 (but there is also a 1.8 gem).
== Credits
-Contributions & influences by @cldwalker and @godfat.
+Contributions & influences by cldwalker and godfat.
J-_-L
View
40 lib/ripl/multi_line.rb
@@ -10,7 +10,7 @@ class << self
def before_loop
super
- @buffer = nil
+ @buffer = @buffer_info = nil
# include CamelCased implementation
require File.join( 'ripl', 'multi_line', config[:multi_line_engine].to_s )
Ripl::MultiLine.engine = Ripl::MultiLine.const_get(
@@ -22,7 +22,7 @@ def before_loop
def prompt
if @buffer
config[:multi_line_prompt].respond_to?(:call) ?
- config[:multi_line_prompt].call :
+ config[:multi_line_prompt].call( *@buffer_info[-1] ) :
config[:multi_line_prompt]
else
super
@@ -35,11 +35,24 @@ def prompt
def loop_once
catch(:multiline) do
super
- if config[:multi_line_short_history] && @buffer && @input
+ if config[:multi_line_history] && ( config[:multi_line_history] == :compact ) && @buffer && @input
(@buffer.size + 1).times{ history.pop }
- history << (@buffer << @input).dup.map{|e| e.sub(/(;+$|^;+)/, '') }.join('; ')
+ history_entry = ''
+ @buffer.zip(@buffer_info){ |str, type|
+ history_entry << str
+ history_entry << case
+ when type[0] == :statement
+ '; '
+ when type[0] == :literal && ( type[1] == :string || type[1] == :regexp )
+ '\n'
+ else
+ ''
+ end
+ }
+ history_entry << @input
+ history << history_entry
end
- @buffer = nil
+ @buffer = @buffer_info = nil
end
end
@@ -49,15 +62,19 @@ def multiline?(eval_string)
false
end
- def handle_multiline
+ def handle_multiline(type = :statement) # MAYBE: add second arg for specifc information
@buffer ||= []
+ @buffer_info ||= []
@buffer << @input
+ @buffer_info << type
throw :multiline
end
def loop_eval(input)
eval_string = if @buffer then @buffer*"\n" + "\n" + input else input end
- handle_multiline if multiline?(eval_string)
+ if type = multiline?(eval_string)
+ handle_multiline(type)
+ end
super eval_string
end
@@ -65,10 +82,9 @@ def loop_eval(input)
# MAYBE: terminal rewriting
def handle_interrupt
if @buffer
- @buffer.pop
- history.pop
+ @buffer.pop; @buffer_info.pop; history.pop
if @buffer.empty?
- @buffer = nil
+ @buffer = @buffer_info = nil
print '[buffer empty]'
return super
else
@@ -84,12 +100,12 @@ def handle_interrupt
end
Ripl::Shell.include Ripl::MultiLine # implementation gets included in before_loop
-Ripl.config[:multi_line_engine] ||= :live # not satisfied? try :ripper or implement your own
+Ripl.config[:multi_line_engine] ||= :live_error # not satisfied? try :ripper or implement your own
Ripl.config[:multi_line_prompt] ||= proc do # you can also use a plain string here
'|' + ' '*(Ripl.shell.instance_variable_get(:@prompt).size-1) # '| '
end
-Ripl.config[:multi_line_short_history] = true if Ripl.config[:multi_line_short_history].nil?
+Ripl.config[:multi_line_history] = :compact if Ripl.config[:multi_line_history].nil?
# J-_-L
View
46 lib/ripl/multi_line/live.rb
@@ -1,46 +0,0 @@
-require 'ripl'
-require 'ripl/multi_line'
-
-module Ripl::MultiLine::Live
- ERROR_REGEXP = /#{
- [ %q<unexpected \$end>,
- %q<unterminated [a-z]+ meets end of file>,
- # rubinius
- %q<expecting '.+'( or '.+')*>,
- %q<missing 'end'>,
- # jruby
- %q<syntax error, unexpected end-of-file>,
- ]*'|' }/
-
- def print_eval_error(e)
- if e.is_a?(SyntaxError) && e.message =~ ERROR_REGEXP
- handle_multiline
- else
- super
- end
- end
-
- def eval_input(input)
- if input =~ /;\s*$/ # force multi line with ;
- handle_multiline
- elsif input =~ /^=begin(\s.*)?$/ && !@buffer
- @ignore_mode = true # MAYBE: change prompt
- # elsif @ignore_mode && input == '=end' # see print_result
- # @ignore_mode = false
- else
- super unless @ignore_mode
- end
- end
-
- def print_result(result)
- if @ignore_mode && @input == '=end'
- @ignore_mode = false
- elsif !@ignore_mode
- super
- end
- end
-end
-
-Ripl.config[:multi_line_engine] ||= :live
-
-# J-_-L
View
61 lib/ripl/multi_line/live_error.rb
@@ -0,0 +1,61 @@
+require 'ripl'
+
+Ripl.config[:multi_line_engine] ||= :live_error
+require 'ripl/multi_line'
+
+module Ripl::MultiLine::LiveError
+ ERROR_MESSAGES = {
+ :ruby => [
+ [[:literal, :string], /unterminated string meets end of file/],
+ [[:literal, :regexp], /unterminated regexp meets end of file/],
+ [[:literal, :array], /syntax error, unexpected \$end, expecting '\]'/],
+ [[:literal, :hash], /syntax error, unexpected \$end, expecting '\}'/], # does not work for ranges or {5=>
+ [[:statement], /syntax error, unexpected \$end/],
+ ],
+ :jruby => [
+ [[:literal, :string], /unterminated string meets end of file/],
+ [[:literal, :regexp], /unterminated regexp meets end of file/], # array or hash cannot be detected
+ [[:statement], /syntax error, unexpected end-of-file/],
+ ],
+ :rbx => [
+ [[:literal, :string], /unterminated [a-z]+ meets end of file/], # no extra message for regexes
+ [[:literal], /expecting '\\n' or ';'/], # TODO: better rbx regexes or rbx bug report
+ [[:literal, :hash], /expecting '\}'/],
+ # [:literal, /expecting '\]'/],
+ # [:literal, /expecting '\)'/],
+ [[:statement], /syntax error, unexpected \$end/],
+ [[:statement], /missing 'end'/], # for i in [2,3,4] do;
+ ],
+ }
+ ruby_engine = defined?(RUBY_ENGINE) && RUBY_ENGINE.downcase.to_sym
+ ruby_engine = :ruby if !ERROR_MESSAGES.keys.include?(ruby_engine)
+
+ define_method(:print_eval_error) do |e|
+ if e.is_a? SyntaxError
+ ERROR_MESSAGES[ruby_engine].each{ |type, message|
+ handle_multiline(type) if message === e.message
+ }
+ end
+ super(e)
+ end
+
+ def eval_input(input)
+ if input =~ /;\s*\Z/ # force multi line with ;
+ handle_multiline(:forced)
+ elsif input =~ /^=begin(\s.*)?$/ && !@buffer
+ @ignore_mode = true # MAYBE: change prompt
+ elsif !@ignore_mode
+ super
+ end
+ end
+
+ def print_result(result)
+ if @ignore_mode && @input == '=end'
+ @ignore_mode = false
+ elsif !@ignore_mode
+ super
+ end
+ end
+end
+
+# J-_-L
View
34 lib/ripl/multi_line/ripper.rb
@@ -1,14 +1,38 @@
-# see https://github.com/cldwalker/ripl-ripper/blob/master/lib/ripl/ripper.rb for a standalone plugin
+# see https://github.com/cldwalker/ripl-ripper/blob/master/lib/ripl/ripper.rb for a different (standalone) plugin
require 'ripl'
-require 'ripl/multi_line'
require 'ripper'
+Ripl.config[:multi_line_engine] ||= :ripper
+require 'ripl/multi_line'
+
module Ripl::MultiLine::Ripper
- def multiline?(string) #FIXME allow string literals?
- !Ripper::SexpBuilder.new(string).parse
+ def multiline?(string)
+ return [:forced] if string =~ /;\s*\Z/
+
+ expr = ::Ripper::SexpBuilder.new(string).parse
+
+ return [:statement] if !expr # not always statements...
+
+ # literals are problematic..
+ last_expr = expr[-1][-1]
+
+ return [:literal, :regexp] if last_expr == [:regexp_literal, [:regexp_new], nil]
+
+ delimiters = %q_(?:[\[<({][\]>)}]|(.)\1)_
+
+ return [:literal, :string] if last_expr == [:string_literal, [:string_content]] &&
+ string !~ /(?:""|''|%q?#{delimiters})\Z/i # empty literal at $
+
+ return [:literal, :string] if last_expr == [:xstring_literal, [:xstring_new]] &&
+ string !~ /%x#{delimiters}\Z/i
+
+ return [:literal, :string] if last_expr == [:words_new] &&
+ string !~ /%W#{delimiters}\Z/
+
+ return [:literal, :string] if last_expr == [:qwords_new] &&
+ string !~ /%w#{delimiters}\Z/
end
end
-Ripl.config[:multi_line_engine] ||= :ripper
# J-_-L
Please sign in to comment.
Something went wrong with that request. Please try again.