[前][次][番号順一覧][スレッド一覧]

ruby-changes:67292

From: aycabta <ko1@a...>
Date: Sun, 29 Aug 2021 20:30:36 +0900 (JST)
Subject: [ruby-changes:67292] fb0fc20196 (master): [ruby/reline] Implement dialog with autocomplete callback

https://git.ruby-lang.org/ruby.git/commit/?id=fb0fc20196

From fb0fc201963c5e70e62b72e0ac9e27dc39e0f5ec Mon Sep 17 00:00:00 2001
From: aycabta <aycabta@g...>
Date: Tue, 17 Aug 2021 19:21:27 +0900
Subject: [ruby/reline] Implement dialog with autocomplete callback

https://github.com/ruby/reline/commit/1401d6165e
---
 lib/reline.rb             |  24 +++++
 lib/reline/line_editor.rb | 238 +++++++++++++++++++++++++++++++++++++++++++++-
 lib/reline/unicode.rb     |  30 ++++++
 3 files changed, 289 insertions(+), 3 deletions(-)

diff --git a/lib/reline.rb b/lib/reline.rb
index d68627d..fade2da 100644
--- a/lib/reline.rb
+++ b/lib/reline.rb
@@ -34,6 +34,7 @@ module Reline https://github.com/ruby/ruby/blob/trunk/lib/reline.rb#L34
       auto_indent_proc
       pre_input_hook
       dig_perfect_match_proc
+      dialog_proc
     ).each(&method(:attr_reader))
 
     attr_accessor :config
@@ -130,6 +131,11 @@ module Reline https://github.com/ruby/ruby/blob/trunk/lib/reline.rb#L131
       @dig_perfect_match_proc = p
     end
 
+    def dialog_proc=(p)
+      raise ArgumentError unless p.respond_to?(:call) or p.nil?
+      @dialog_proc = p
+    end
+
     def input=(val)
       raise TypeError unless val.respond_to?(:getc) or val.nil?
       if val.respond_to?(:getc)
@@ -175,6 +181,23 @@ module Reline https://github.com/ruby/ruby/blob/trunk/lib/reline.rb#L181
       unless confirm_multiline_termination
         raise ArgumentError.new('#readmultiline needs block to confirm multiline termination')
       end
+      @dialog_proc = ->() {
+        # autocomplete
+        if just_cursor_moving
+          # Auto complete starts only when endited
+          return nil
+        end
+        pre, target, post= retrieve_completion_block(true)
+        if target.nil? or target.empty?
+          result = nil
+        else
+          result = call_completion_proc_with_checking_args(pre, target, post)
+          if result and result.size == 1 and result[0] == target
+            result = nil
+          end
+        end
+        [Reline::CursorPos.new(cursor_pos.x - Reline::Unicode.calculate_width(target), nil), result]
+      }
       inner_readline(prompt, add_hist, true, &confirm_multiline_termination)
 
       whole_buffer = line_editor.whole_buffer.dup
@@ -230,6 +253,7 @@ module Reline https://github.com/ruby/ruby/blob/trunk/lib/reline.rb#L253
       line_editor.auto_indent_proc = auto_indent_proc
       line_editor.dig_perfect_match_proc = dig_perfect_match_proc
       line_editor.pre_input_hook = pre_input_hook
+      line_editor.dialog_proc = dialog_proc
 
       unless config.test_mode
         config.read
diff --git a/lib/reline/line_editor.rb b/lib/reline/line_editor.rb
index 0b5b683..8e620e5 100644
--- a/lib/reline/line_editor.rb
+++ b/lib/reline/line_editor.rb
@@ -249,6 +249,13 @@ class Reline::LineEditor https://github.com/ruby/ruby/blob/trunk/lib/reline/line_editor.rb#L249
     @drop_terminate_spaces = false
     @in_pasting = false
     @auto_indent_proc = nil
+    @with_dialog = nil
+    @dialog_column = nil
+    @dialog_vertical_offset = nil
+    @dialog_contents = nil
+    @dialog_contents_width = nil
+    @dialog_updown = nil
+    @dialog_lines_backup = nil
     reset_line
   end
 
@@ -414,6 +421,7 @@ class Reline::LineEditor https://github.com/ruby/ruby/blob/trunk/lib/reline/line_editor.rb#L421
         Reline::IOGate.erase_after_cursor
       end
       @output.flush
+      clear_dialog
       return
     end
     new_highest_in_this = calculate_height_by_width(prompt_width + calculate_width(@line.nil? ? '' : @line))
@@ -424,6 +432,7 @@ class Reline::LineEditor https://github.com/ruby/ruby/blob/trunk/lib/reline/line_editor.rb#L432
     else
       if @just_cursor_moving and not @rerender_all
         rendered = just_move_cursor
+        render_dialog((prompt_width + @cursor) % @screen_size.last)
         @just_cursor_moving = false
         return
       elsif @previous_line_index or new_highest_in_this != @highest_in_this
@@ -446,18 +455,20 @@ class Reline::LineEditor https://github.com/ruby/ruby/blob/trunk/lib/reline/line_editor.rb#L455
           new_lines = whole_lines
         end
         line = modify_lines(new_lines)[@line_index]
+        clear_dialog
         prompt, prompt_width, prompt_list = check_multiline_prompt(new_lines, prompt)
         render_partial(prompt, prompt_width, line, @first_line_started_from)
         move_cursor_down(@highest_in_all - (@first_line_started_from + @highest_in_this - 1) - 1)
         scroll_down(1)
         Reline::IOGate.move_cursor_column(0)
         Reline::IOGate.erase_after_cursor
-      elsif not rendered
-        unless @in_pasting
+      else
+        if not rendered and not @in_pasting
           line = modify_lines(whole_lines)[@line_index]
           prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines, prompt)
           render_partial(prompt, prompt_width, line, @first_line_started_from)
         end
+        render_dialog((prompt_width + @cursor) % @screen_size.last)
       end
       @buffer_of_lines[@line_index] = @line
       @rest_height = 0 if @scroll_partial_screen
@@ -472,6 +483,227 @@ class Reline::LineEditor https://github.com/ruby/ruby/blob/trunk/lib/reline/line_editor.rb#L483
     end
   end
 
+  class DialogProcScope
+    def initialize(line_editor, proc_to_exec)
+      @line_editor = line_editor
+      @proc_to_exec = proc_to_exec
+      @cursor_pos = Reline::CursorPos.new
+    end
+
+    def retrieve_completion_block(set_completion_quote_character = false)
+      @line_editor.retrieve_completion_block(set_completion_quote_character)
+    end
+
+    def call_completion_proc_with_checking_args(pre, target, post)
+      @line_editor.call_completion_proc_with_checking_args(pre, target, post)
+    end
+
+    def set_cursor_pos(col, row)
+      @cursor_pos.x = col
+      @cursor_pos.y = row
+    end
+
+    def cursor_pos
+      @cursor_pos
+    end
+
+    def just_cursor_moving
+      @line_editor.instance_variable_get(:@just_cursor_moving)
+    end
+
+    def call
+      instance_exec(&@proc_to_exec)
+    end
+  end
+
+  def dialog_proc=(p)
+    @dialog_proc_scope = DialogProcScope.new(self, p)
+    @dialog_proc = p
+  end
+
+  DIALOG_HEIGHT = 5
+  DIALOG_WIDTH = 40
+  private def render_dialog(cursor_column)
+    return if @dialog_proc_scope.nil?
+    if @in_pasting
+      @dialog_contents = nil
+      return
+    end
+    @dialog_proc_scope.set_cursor_pos(cursor_column, @first_line_started_from + @started_from)
+    pos, result = @dialog_proc_scope.call
+    old_dialog_contents = @dialog_contents
+    old_dialog_contents_width = @dialog_contents_width
+    old_dialog_column = @dialog_column
+    old_dialog_vertical_offset = @dialog_vertical_offset
+    old_dialog_updown = @dialog_updown
+    if result and not result.empty?
+      @dialog_contents = result
+      @dialog_contents_width = @dialog_contents.map{ |c| calculate_width(c) }
+    else
+      clear_dialog
+      @dialog_contents = nil
+      return
+    end
+    upper_space = @first_line_started_from - @started_from
+    lower_space = @highest_in_all - @first_line_started_from - @started_from - 1
+    @dialog_updown = nil
+    @dialog_column = pos.x
+    if (lower_space + @rest_height) >= DIALOG_HEIGHT
+      @dialog_updown = :down
+      @dialog_vertical_offset = 1
+    elsif upper_space >= DIALOG_HEIGHT
+      @dialog_updown = :up
+      @dialog_vertical_offset = -(DIALOG_HEIGHT + 1)
+    else
+      if (lower_space + @rest_height) < DIALOG_HEIGHT
+        down = DIALOG_HEIGHT - (lower_space + @rest_height)
+        scroll_down(down)
+        move_cursor_up(DIALOG_HEIGHT - 1)
+      end
+      @dialog_updown = :down
+      @dialog_vertical_offset = 1
+    end
+    reset_dialog(old_dialog_contents, old_dialog_contents_width, old_dialog_column, old_dialog_vertical_offset, old_dialog_updown)
+    ue = ?/ + ?~ * (DIALOG_WIDTH - 2) + ?\\
+    sita = ?\\ + ?_ * (DIALOG_WIDTH - 2) + ?/
+    yoko = ?:
+    case @dialog_updown
+    when :down
+      move_cursor_down(1)
+    when :up
+    end
+    Reline::IOGate.move_cursor_column(@dialog_column)
+    @output.write ue
+    Reline::IOGate.move_cursor_column(@dialog_column)
+    @dialog_contents = @dialog_contents[0...(DIALOG_HEIGHT - 2)] if @dialog_contents.size > (DIALOG_HEIGHT - 2)
+    @dialog_contents.each do |item|
+      move_cursor_down(1)
+      @output.write yoko
+      @output.write "%-#{DIALOG_WIDTH - 2}s" % item.slice(0, DIALOG_WIDTH - 2)
+      @output.write yoko
+      Reline::IOGate.move_cursor_column(@dialog_column)
+    end
+    move_cursor_down(1)
+    @output.write sita
+    Reline::IOGate.move_cursor_column(cursor_column)
+    case @dialog_updown
+    when :down
+      move_cursor_up(@dialog_contents.size + 2)
+    when :up
+    end
+    @dialog_lines_backup = {
+      lines: modify_lines(whole_lines),
+      line_index: @line_index,
+      started_from: @started_from,
+      byte_pointer: @byte_pointer
+    }
+  end
+
+  private def reset_dialog(old_dialog_contents, old_dialog_contents_width, old_dialog_column, old_dialog_vertical_offset, old_dialog_updown)
+    return if @dialog_lines_backup.nil? or old_dialog_contents.nil?
+    prompt, prompt_width, prompt_list = check_multiline_prompt(@dialog_lines_backup[:lines], prompt)
+    visual_lines = []
+    visual_start = nil
+    @dialog_lines_backup[:lines].each_with_index { |l, i|
+      pr = prompt_list ? prompt_list[i] : prompt
+      vl, height = split_by_width(pr + l, @screen_size.last)
+      if i == @dialog_lines_backup[:line_index]
+        visual_start = visual_lines.size + @dialog_lines_backup[:started_from]
+      end
+      visual_lines.concat(vl)
+    }
+    if old_dialog_vertical_offset < @dialog_vertical_offset
+      move_cursor_down(old_dialog_vertical_offset)
+      start = visual_start + old_dialog_vertical_offset
+      line_num = @dialog_vertical_offset - old_dialog_vertical_offset
+      line_num.times do |i|
+        Reline::IOGate.move_cursor_column(0)
+        @output.write visual_lines[sta (... truncated)

--
ML: ruby-changes@q...
Info: http://www.atdot.net/~ko1/quickml/

[前][次][番号順一覧][スレッド一覧]