ruby-changes:55826
From: Takashi <ko1@a...>
Date: Sat, 25 May 2019 15:55:50 +0900 (JST)
Subject: [ruby-changes:55826] Takashi Kokubun: b83119be9e (trunk): Incremental syntax highlight for IRB source lines
https://git.ruby-lang.org/ruby.git/commit/?id=b83119be9e From b83119be9e9a8611063142541993e4823a025622 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun <takashikkbn@g...> Date: Fri, 24 May 2019 21:21:22 -0700 Subject: Incremental syntax highlight for IRB source lines Closes: https://github.com/ruby/ruby/pull/2202 diff --git a/NEWS b/NEWS index 0946dd9..7e9450b 100644 --- a/NEWS +++ b/NEWS @@ -115,8 +115,8 @@ ERB:: https://github.com/ruby/ruby/blob/trunk/NEWS#L115 IRB:: - * Introduce syntax highlight inspired by pry.gem to inspect output for some - core-class objects and binding.irb source lines if $TERM is set and not dumb. + * Introduce syntax highlight inspired by pry.gem to binding.irb source lines, + REPL input, and inspect output of some core-class objects. Net::IMAP:: diff --git a/lib/irb/color.rb b/lib/irb/color.rb index eb95da8..30a8fb5 100644 --- a/lib/irb/color.rb +++ b/lib/irb/color.rb @@ -77,13 +77,21 @@ module IRB # :nodoc: https://github.com/ruby/ruby/blob/trunk/lib/irb/color.rb#L77 return code unless colorable? colored = +'' + length = 0 Ripper.lex(code).each do |(_line, _col), token, str, expr| if seq = dispatch_seq(token, expr, str) - colored << "#{seq.map { |s| "\e[#{s}m" }.join('')}#{str}#{clear}" + str.each_line do |line| + colored << "#{seq.map { |s| "\e[#{s}m" }.join('')}#{line}#{clear}" + end else colored << str end + length += str.length end + + # give up colorizing incomplete Ripper tokens + return code if length != code.length + colored end diff --git a/lib/irb/input-method.rb b/lib/irb/input-method.rb index 412edcc..bc144dc 100644 --- a/lib/irb/input-method.rb +++ b/lib/irb/input-method.rb @@ -222,6 +222,10 @@ module IRB https://github.com/ruby/ruby/blob/trunk/lib/irb/input-method.rb#L222 end Reline.completion_append_character = nil Reline.completion_proc = IRB::InputCompletor::CompletionProc + Reline.output_modifier_proc = proc do |output| + next unless IRB::Color.colorable? + IRB::Color.colorize_code(output) + end Reline.dig_perfect_match_proc = IRB::InputCompletor::PerfectMatchedProc end diff --git a/lib/reline.rb b/lib/reline.rb index 52e40f1..2077087 100644 --- a/lib/reline.rb +++ b/lib/reline.rb @@ -142,6 +142,15 @@ module Reline https://github.com/ruby/ruby/blob/trunk/lib/reline.rb#L142 @@completion_proc = p end + @@output_modifier_proc = nil + def self.output_modifier_proc + @@output_modifier_proc + end + def self.output_modifier_proc=(p) + raise ArgumentError unless p.is_a?(Proc) + @@output_modifier_proc = p + end + @@pre_input_hook = nil def self.pre_input_hook @@pre_input_hook @@ -297,6 +306,7 @@ module Reline https://github.com/ruby/ruby/blob/trunk/lib/reline.rb#L306 end @@line_editor.output = @@output @@line_editor.completion_proc = @@completion_proc + @@line_editor.output_modifier_proc = @@output_modifier_proc @@line_editor.dig_perfect_match_proc = @@dig_perfect_match_proc @@line_editor.pre_input_hook = @@pre_input_hook @@line_editor.retrieve_completion_block = method(:retrieve_completion_block) diff --git a/lib/reline/line_editor.rb b/lib/reline/line_editor.rb index 055b76a..6d6df10 100644 --- a/lib/reline/line_editor.rb +++ b/lib/reline/line_editor.rb @@ -10,6 +10,7 @@ class Reline::LineEditor https://github.com/ruby/ruby/blob/trunk/lib/reline/line_editor.rb#L10 attr_reader :byte_pointer attr_accessor :confirm_multiline_termination_proc attr_accessor :completion_proc + attr_accessor :output_modifier_proc attr_accessor :pre_input_hook attr_accessor :dig_perfect_match_proc attr_writer :retrieve_completion_block @@ -163,16 +164,16 @@ class Reline::LineEditor https://github.com/ruby/ruby/blob/trunk/lib/reline/line_editor.rb#L164 lines = [String.new(encoding: @encoding)] height = 1 width = 0 - rest_prompt = prompt.encode(Encoding::UTF_8) + rest = "#{prompt}#{str}".encode(Encoding::UTF_8) loop do - break if rest_prompt.empty? - if rest_prompt =~ /^#{CSI_REGEXP}/ + break if rest.empty? + if rest =~ /\A#{CSI_REGEXP}/ lines.last << $& - rest_prompt = $' + rest = $' else - gcs = rest_prompt.grapheme_clusters + gcs = rest.grapheme_clusters gc = gcs.first - rest_prompt = gcs[1..-1].join + rest = gcs[1..-1].join mbchar_width = Reline::Unicode.get_mbchar_width(gc) width += mbchar_width if width > max_width @@ -184,19 +185,6 @@ class Reline::LineEditor https://github.com/ruby/ruby/blob/trunk/lib/reline/line_editor.rb#L185 lines.last << gc end end - lines << :split - lines << String.new(encoding: @encoding) - str.encode(Encoding::UTF_8).grapheme_clusters.each do |gc| - mbchar_width = Reline::Unicode.get_mbchar_width(gc) - width += mbchar_width - if width > max_width - width = mbchar_width - lines << nil - lines << String.new(encoding: @encoding) - height += 1 - end - lines.last << gc - end # The cursor moves to next line in first lines << String.new(encoding: @encoding) if width == max_width [lines, height] @@ -290,8 +278,7 @@ class Reline::LineEditor https://github.com/ruby/ruby/blob/trunk/lib/reline/line_editor.rb#L278 Reline::IOGate.clear_screen @cleared = false back = 0 - @buffer_of_lines.each_with_index do |line, index| - line = @line if index == @line_index + modify_lines(whole_lines).each_with_index do |line, index| height = render_partial(prompt, prompt_width, line, false) if index < (@buffer_of_lines.size - 1) move_cursor_down(height) @@ -305,7 +292,6 @@ class Reline::LineEditor https://github.com/ruby/ruby/blob/trunk/lib/reline/line_editor.rb#L292 end # FIXME: end of logical line sometimes breaks if @previous_line_index - previous_line = @line all_height = @buffer_of_lines.inject(0) { |result, line| result + calculate_height_by_width(@prompt_width + calculate_width(line)) } @@ -316,8 +302,7 @@ class Reline::LineEditor https://github.com/ruby/ruby/blob/trunk/lib/reline/line_editor.rb#L302 scroll_down(diff) move_cursor_up(@highest_in_all - 1) back = 0 - @buffer_of_lines.each_with_index do |line, index| - line = @line if index == @previous_line_index + modify_lines(whole_lines(index: @previous_line_index, line: @line)).each_with_index do |line, index| height = render_partial(prompt, prompt_width, line, false) if index < (@buffer_of_lines.size - 1) move_cursor_down(height) @@ -326,6 +311,7 @@ class Reline::LineEditor https://github.com/ruby/ruby/blob/trunk/lib/reline/line_editor.rb#L311 end move_cursor_up(back) else + previous_line = modify_lines(whole_lines(index: @previous_line_index, line: @line))[@previous_line_index] render_partial(prompt, prompt_width, previous_line) move_cursor_up(@first_line_started_from + @started_from) end @@ -364,7 +350,7 @@ class Reline::LineEditor https://github.com/ruby/ruby/blob/trunk/lib/reline/line_editor.rb#L350 end move_cursor_up(@highest_in_all - 1) end - @buffer_of_lines.each_with_index do |line, index| + modify_lines(@buffer_of_lines).each_with_index do |line, index| render_partial(prompt, prompt_width, line, false) if index < (@buffer_of_lines.size - 1) move_cursor_down(1) @@ -384,10 +370,11 @@ class Reline::LineEditor https://github.com/ruby/ruby/blob/trunk/lib/reline/line_editor.rb#L370 move_cursor_down(@first_line_started_from) @rerender_all = false end + line = modify_lines(whole_lines)[@line_index] if !@is_multiline - render_partial(prompt, prompt_width, @line) + render_partial(prompt, prompt_width, line) elsif !finished? - render_partial(prompt, prompt_width, @line) + render_partial(prompt, prompt_width, line) else scroll_down(1) unless whole_lines.last.empty? Reline::IOGate.move_cursor_column(0) @@ -408,24 +395,15 @@ class Reline::LineEditor https://github.com/ruby/ruby/blob/trunk/lib/reline/line_editor.rb#L395 move_cursor_up(@started_from) @started_from = calculate_height_by_width(prompt_width + @cursor) - 1 end - is_prompt = true Reline::IOGate.move_cursor_column(0) visual_lines.each do |line| - if line == :split - is_prompt = false - next - end if line.nil? Reline::IOGate.erase_after_cursor move_cursor_down(1) Reline::IOGate.move_cursor_column(0) next end - if is_prompt - @output.print line - else - escaped_print line - end + @output.print line if @first_prompt @first_prompt = false @pre_input_hook&.call @@ -441,6 +419,16 @@ class Reline::LineEditor https://github.com/ruby/ruby/blob/trunk/lib/reline/line_editor.rb#L419 height end + private def modify_lines(before) + return before if before.nil? || before.empty? + + if after = @output_modifier_proc&.call("#{before.join("\n")}\n") + after.lines(chomp: true) + else + before + end + end + def editing_mode @config.editing_mode end @@ -768,9 +756,9 @@ class Reline::LineEditor https://github.com/ruby/ruby/blob/trunk/lib/reline/line_editor.rb#L756 @cursor_max = calculate_width(@line) end - def whole_lines + def whole_lines(index: @line_index, line: @line) temp_lines = @buffer_of_lines.dup - temp_lines[@line_index] = @line + temp_lines[index] = line temp_lines end diff --git a/test/irb/test_color.rb b/test/irb/test_color.rb index 3305c24..61b4ea4 1 (... truncated) -- ML: ruby-changes@q... Info: http://www.atdot.net/~ko1/quickml/