summaryrefslogtreecommitdiff
path: root/lib/reline.rb
blob: b1d7718b7ec96309036760a379610be5428d4fc0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
require 'io/console'
require 'reline/version'
require 'reline/config'
require 'reline/key_actor'
require 'reline/key_stroke'
require 'reline/line_editor'

module Reline
  extend self
  FILENAME_COMPLETION_PROC = nil
  USERNAME_COMPLETION_PROC = nil
  HISTORY = Array.new

  if RUBY_PLATFORM =~ /mswin|mingw/
    require 'Win32API'
    IS_WINDOWS = true
  else
    IS_WINDOWS = false
  end

  CursorPos = Struct.new(:x, :y)

  class << self
    attr_accessor :basic_quote_characters
    attr_accessor :completer_quote_characters
    attr_accessor :completer_word_break_characters
    attr_reader :completion_append_character
    attr_accessor :completion_case_fold
    attr_accessor :filename_quote_characters
    attr_writer :input
    attr_writer :output
  end

  @@ambiguous_width = nil
  @@config = nil

  @basic_quote_characters = '"\''
  @completer_quote_characters
  @completer_word_break_characters = @basic_word_break_characters.dup
  @completion_append_character
  def self.completion_append_character=(val)
    if val.nil?
      @completion_append_character = nil
    elsif val.size == 1
      @completion_append_character = val
    elsif val.size > 1
      @completion_append_character = val[0]
    else
      @completion_append_character = val
    end
  end
  @completion_case_fold
  @filename_quote_characters

  @@basic_word_break_characters = " \t\n`><=;|&{("
  def self.basic_word_break_characters
    @@basic_word_break_characters
  end
  def self.basic_word_break_characters=(v)
    @@basic_word_break_characters = v
  end

  @@completion_proc = nil
  def self.completion_proc
    @@completion_proc
  end
  def self.completion_proc=(p)
    @@completion_proc = p
  end

  @@dig_perfect_match_proc = nil
  def self.dig_perfect_match_proc
    @@dig_perfect_match_proc
  end
  def self.dig_perfect_match_proc=(p)
    @@dig_perfect_match_proc = p
  end

  if IS_WINDOWS
    require 'reline/windows'
  else
    require 'reline/ansi'
  end

  def retrieve_completion_block(line, byte_pointer)
    break_regexp = /[#{Regexp.escape(@@basic_word_break_characters)}]/
    before_pointer = line.byteslice(0, byte_pointer)
    break_point = before_pointer.rindex(break_regexp)
    if break_point
      preposing = before_pointer[0..(break_point)]
      block = before_pointer[(break_point + 1)..-1]
    else
      preposing = ''
      block = before_pointer
    end
    postposing = line.byteslice(byte_pointer, line.bytesize)
    [preposing, block, postposing]
  end

  def readmultiline(prompt = '', add_hist = false, &confirm_multiline_termination)
    if block_given?
      inner_readline(prompt, add_hist, true, &confirm_multiline_termination)
    else
      inner_readline(prompt, add_hist, true)
    end

    if add_hist and @line_editor.whole_buffer and @line_editor.whole_buffer.chomp.size > 0
      Reline::HISTORY << @line_editor.whole_buffer
    end

    @line_editor.whole_buffer
  end

  def readline(prompt = '', add_hist = false)
    inner_readline(prompt, add_hist, false)

    if add_hist and @line_editor.line and @line_editor.line.chomp.size > 0
      Reline::HISTORY << @line_editor.line.chomp
    end

    @line_editor.line
  end

  def inner_readline(prompt, add_hist, multiline, &confirm_multiline_termination)
    if @@config.nil?
      @@config = Reline::Config.new
      @@config.read
    end
    otio = prep

    may_req_ambiguous_char_width
    @line_editor = Reline::LineEditor.new(@@config, prompt)
    if multiline
      @line_editor.multiline_on
      if block_given?
        @line_editor.confirm_multiline_termination_proc = confirm_multiline_termination
      end
    end
    @line_editor.completion_proc = @@completion_proc
    @line_editor.dig_perfect_match_proc = @@dig_perfect_match_proc
    @line_editor.retrieve_completion_block = method(:retrieve_completion_block)
    @line_editor.rerender

    if IS_WINDOWS
      config = {
        key_mapping: {
          [224, 72] => :ed_prev_history,    # ↑
          [224, 80] => :ed_next_history,    # ↓
          [224, 77] => :ed_next_char,       # →
          [224, 75] => :ed_prev_char        # ←
        }
      }
    else
      config = {
        key_mapping: {
          [27, 91, 65] => :ed_prev_history,    # ↑
          [27, 91, 66] => :ed_next_history,    # ↓
          [27, 91, 67] => :ed_next_char,       # →
          [27, 91, 68] => :ed_prev_char        # ←
        }
      }
    end

    key_stroke = Reline::KeyStroke.new(config)
    begin
      while c = getc
        key_stroke.input_to!(c)&.then { |inputs|
          inputs.each { |c|
            @line_editor.input_key(c)
            @line_editor.rerender
          }
        }
        break if @line_editor.finished?
      end
      Reline.move_cursor_column(0)
    rescue StandardError => e
      deprep(otio)
      raise e
    end

    deprep(otio)
  end

  def may_req_ambiguous_char_width
    return if @@ambiguous_width
    Reline.move_cursor_column(0)
    print "\u{25bd}"
    @@ambiguous_width = Reline.cursor_pos.x
    Reline.move_cursor_column(0)
    Reline.erase_after_cursor
  end

  def self.ambiguous_width
    @@ambiguous_width
  end
end