reline 0.0.0 → 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 34664ed4926dba843c8d0b5c161a8783384afbe37ef601a6ef7130e6acfe614f
4
- data.tar.gz: c006411fd71550186facf348787d964f51f8e7ad03d87c7718ab2689a973b2c0
3
+ metadata.gz: beb24e6d3114b6debe540928f1299fd10d420ff447f90401c9a8249b12804b7d
4
+ data.tar.gz: 046dfcfa706a95f62acb152238cae3680f918f9af2c9a52eee4fa4e5771ebdbc
5
5
  SHA512:
6
- metadata.gz: f8307b181652977ca88bc9858533a41cdb9305b27c5fdd1584b6c9c7931e96fbf4c86c73d84b0d09cb4d27a7061adbf5e5f7c327f65864986eea2fd75c55937f
7
- data.tar.gz: 47453f12ee20b6438751008216abfb04bf8ff06e9e1b5ab9bcb5e73319bc39aeecb1748d2ad54f5293385a04fba877f3fe6fa219d1e36afacb41851106ebf22c
6
+ metadata.gz: a9a1abfe4febd8da98052386b0cca9a4b3c9f333c0a53c439d5980cad64c3b2f9f75c35d2498e42beadd4cea2f6a7d90c005d0112317b8ea90f89fd45b0f8632
7
+ data.tar.gz: 6f482d37f4c85ab5f0a5df08e069f81c5289b7bedbcea105a0601a7174bd3ca314c0a04750e2bba91cb31ad7bdc7f56afa4f3b8398bf2bd80e635e356be12615
@@ -1,18 +1,20 @@
1
1
  require 'io/console'
2
+ require 'timeout'
2
3
  require 'reline/version'
3
4
  require 'reline/config'
4
5
  require 'reline/key_actor'
5
6
  require 'reline/key_stroke'
6
7
  require 'reline/line_editor'
8
+ require 'reline/history'
7
9
 
8
10
  module Reline
11
+ Key = Struct.new('Key', :char, :combined_char, :with_meta)
12
+
9
13
  extend self
10
14
  FILENAME_COMPLETION_PROC = nil
11
15
  USERNAME_COMPLETION_PROC = nil
12
- HISTORY = Array.new
13
16
 
14
17
  if RUBY_PLATFORM =~ /mswin|mingw/
15
- require 'Win32API'
16
18
  IS_WINDOWS = true
17
19
  else
18
20
  IS_WINDOWS = false
@@ -20,44 +22,83 @@ module Reline
20
22
 
21
23
  CursorPos = Struct.new(:x, :y)
22
24
 
23
- class << self
24
- attr_accessor :basic_quote_characters
25
- attr_accessor :completer_quote_characters
26
- attr_accessor :completer_word_break_characters
27
- attr_reader :completion_append_character
28
- attr_accessor :completion_case_fold
29
- attr_accessor :filename_quote_characters
30
- attr_writer :input
31
- attr_writer :output
32
- end
33
-
25
+ @@config = Reline::Config.new
26
+ @@key_stroke = Reline::KeyStroke.new(@@config)
27
+ @@line_editor = Reline::LineEditor.new(@@config)
34
28
  @@ambiguous_width = nil
35
- @@config = nil
36
29
 
37
- @basic_quote_characters = '"\''
38
- @completer_quote_characters
39
- @completer_word_break_characters = @basic_word_break_characters.dup
40
- @completion_append_character
30
+ HISTORY = History.new(@@config)
31
+
32
+ @@completion_append_character = nil
33
+ def self.completion_append_character
34
+ @@completion_append_character
35
+ end
41
36
  def self.completion_append_character=(val)
42
37
  if val.nil?
43
- @completion_append_character = nil
38
+ @@completion_append_character = nil
44
39
  elsif val.size == 1
45
- @completion_append_character = val
40
+ @@completion_append_character = val.encode(Encoding::default_external)
46
41
  elsif val.size > 1
47
- @completion_append_character = val[0]
42
+ @@completion_append_character = val[0].encode(Encoding::default_external)
48
43
  else
49
- @completion_append_character = val
44
+ @@completion_append_character = nil
50
45
  end
51
46
  end
52
- @completion_case_fold
53
- @filename_quote_characters
54
47
 
55
48
  @@basic_word_break_characters = " \t\n`><=;|&{("
56
49
  def self.basic_word_break_characters
57
50
  @@basic_word_break_characters
58
51
  end
59
52
  def self.basic_word_break_characters=(v)
60
- @@basic_word_break_characters = v
53
+ @@basic_word_break_characters = v.encode(Encoding::default_external)
54
+ end
55
+
56
+ @@completer_word_break_characters = @@basic_word_break_characters.dup
57
+ def self.completer_word_break_characters
58
+ @@completer_word_break_characters
59
+ end
60
+ def self.completer_word_break_characters=(v)
61
+ @@completer_word_break_characters = v.encode(Encoding::default_external)
62
+ end
63
+
64
+ @@basic_quote_characters = '"\''
65
+ def self.basic_quote_characters
66
+ @@basic_quote_characters
67
+ end
68
+ def self.basic_quote_characters=(v)
69
+ @@basic_quote_characters = v.encode(Encoding::default_external)
70
+ end
71
+
72
+ @@completer_quote_characters = '"\''
73
+ def self.completer_quote_characters
74
+ @@completer_quote_characters
75
+ end
76
+ def self.completer_quote_characters=(v)
77
+ @@completer_quote_characters = v.encode(Encoding::default_external)
78
+ end
79
+
80
+ @@filename_quote_characters = ''
81
+ def self.filename_quote_characters
82
+ @@filename_quote_characters
83
+ end
84
+ def self.filename_quote_characters=(v)
85
+ @@filename_quote_characters = v.encode(Encoding::default_external)
86
+ end
87
+
88
+ @@special_prefixes = ''
89
+ def self.special_prefixes
90
+ @@special_prefixes
91
+ end
92
+ def self.special_prefixes=(v)
93
+ @@special_prefixes = v.encode(Encoding::default_external)
94
+ end
95
+
96
+ @@completion_case_fold = nil
97
+ def self.completion_case_fold
98
+ @@completion_case_fold
99
+ end
100
+ def self.completion_case_fold=(v)
101
+ @@completion_case_fold = v
61
102
  end
62
103
 
63
104
  @@completion_proc = nil
@@ -65,132 +106,310 @@ module Reline
65
106
  @@completion_proc
66
107
  end
67
108
  def self.completion_proc=(p)
109
+ raise ArgumentError unless p.is_a?(Proc)
68
110
  @@completion_proc = p
69
111
  end
70
112
 
113
+ @@output_modifier_proc = nil
114
+ def self.output_modifier_proc
115
+ @@output_modifier_proc
116
+ end
117
+ def self.output_modifier_proc=(p)
118
+ raise ArgumentError unless p.is_a?(Proc)
119
+ @@output_modifier_proc = p
120
+ end
121
+
122
+ @@prompt_proc = nil
123
+ def self.prompt_proc
124
+ @@prompt_proc
125
+ end
126
+ def self.prompt_proc=(p)
127
+ raise ArgumentError unless p.is_a?(Proc)
128
+ @@prompt_proc = p
129
+ end
130
+
131
+ @@auto_indent_proc = nil
132
+ def self.auto_indent_proc
133
+ @@auto_indent_proc
134
+ end
135
+ def self.auto_indent_proc=(p)
136
+ raise ArgumentError unless p.is_a?(Proc)
137
+ @@auto_indent_proc = p
138
+ end
139
+
140
+ @@pre_input_hook = nil
141
+ def self.pre_input_hook
142
+ @@pre_input_hook
143
+ end
144
+ def self.pre_input_hook=(p)
145
+ @@pre_input_hook = p
146
+ end
147
+
71
148
  @@dig_perfect_match_proc = nil
72
149
  def self.dig_perfect_match_proc
73
150
  @@dig_perfect_match_proc
74
151
  end
75
152
  def self.dig_perfect_match_proc=(p)
153
+ raise ArgumentError unless p.is_a?(Proc)
76
154
  @@dig_perfect_match_proc = p
77
155
  end
78
156
 
79
- if IS_WINDOWS
80
- require 'reline/windows'
81
- else
82
- require 'reline/ansi'
157
+ def self.insert_text(text)
158
+ @@line_editor&.insert_text(text)
159
+ self
83
160
  end
84
161
 
85
- def retrieve_completion_block(line, byte_pointer)
86
- break_regexp = /[#{Regexp.escape(@@basic_word_break_characters)}]/
87
- before_pointer = line.byteslice(0, byte_pointer)
88
- break_point = before_pointer.rindex(break_regexp)
89
- if break_point
90
- preposing = before_pointer[0..(break_point)]
91
- block = before_pointer[(break_point + 1)..-1]
92
- else
93
- preposing = ''
94
- block = before_pointer
162
+ def self.redisplay
163
+ @@line_editor&.rerender
164
+ end
165
+
166
+ def self.line_buffer
167
+ @@line_editor&.line
168
+ end
169
+
170
+ def self.point
171
+ @@line_editor ? @@line_editor.byte_pointer : 0
172
+ end
173
+
174
+ def self.point=(val)
175
+ @@line_editor.byte_pointer = val
176
+ end
177
+
178
+ def self.delete_text(start = nil, length = nil)
179
+ @@line_editor&.delete_text(start, length)
180
+ end
181
+
182
+ private_class_method def self.test_mode
183
+ remove_const('IOGate') if const_defined?('IOGate')
184
+ const_set('IOGate', Reline::GeneralIO)
185
+ @@config.instance_variable_set(:@test_mode, true)
186
+ @@config.reset
187
+ end
188
+
189
+ def self.input=(val)
190
+ raise TypeError unless val.respond_to?(:getc) or val.nil?
191
+ if val.respond_to?(:getc)
192
+ if defined?(Reline::ANSI) and IOGate == Reline::ANSI
193
+ Reline::ANSI.input = val
194
+ elsif IOGate == Reline::GeneralIO
195
+ Reline::GeneralIO.input = val
196
+ end
95
197
  end
96
- postposing = line.byteslice(byte_pointer, line.bytesize)
97
- [preposing, block, postposing]
198
+ end
199
+
200
+ @@output = STDOUT
201
+ def self.output=(val)
202
+ raise TypeError unless val.respond_to?(:write) or val.nil?
203
+ @@output = val
204
+ if defined?(Reline::ANSI) and IOGate == Reline::ANSI
205
+ Reline::ANSI.output = val
206
+ end
207
+ end
208
+
209
+ def self.vi_editing_mode
210
+ @@config.editing_mode = :vi_insert
211
+ nil
212
+ end
213
+
214
+ def self.emacs_editing_mode
215
+ @@config.editing_mode = :emacs
216
+ nil
217
+ end
218
+
219
+ def self.vi_editing_mode?
220
+ @@config.editing_mode_is?(:vi_insert, :vi_command)
221
+ end
222
+
223
+ def self.emacs_editing_mode?
224
+ @@config.editing_mode_is?(:emacs)
225
+ end
226
+
227
+ def self.get_screen_size
228
+ Reline::IOGate.get_screen_size
229
+ end
230
+
231
+ def eof?
232
+ @@line_editor.eof?
98
233
  end
99
234
 
100
235
  def readmultiline(prompt = '', add_hist = false, &confirm_multiline_termination)
101
- if block_given?
102
- inner_readline(prompt, add_hist, true, &confirm_multiline_termination)
103
- else
104
- inner_readline(prompt, add_hist, true)
236
+ unless confirm_multiline_termination
237
+ raise ArgumentError.new('#readmultiline needs block to confirm multiline termination')
105
238
  end
239
+ inner_readline(prompt, add_hist, true, &confirm_multiline_termination)
106
240
 
107
- if add_hist and @line_editor.whole_buffer and @line_editor.whole_buffer.chomp.size > 0
108
- Reline::HISTORY << @line_editor.whole_buffer
241
+ whole_buffer = @@line_editor.whole_buffer.dup
242
+ whole_buffer.taint
243
+ if add_hist and whole_buffer and whole_buffer.chomp.size > 0
244
+ Reline::HISTORY << whole_buffer
109
245
  end
110
246
 
111
- @line_editor.whole_buffer
247
+ @@line_editor.reset_line if @@line_editor.whole_buffer.nil?
248
+ whole_buffer
112
249
  end
113
250
 
114
251
  def readline(prompt = '', add_hist = false)
115
252
  inner_readline(prompt, add_hist, false)
116
253
 
117
- if add_hist and @line_editor.line and @line_editor.line.chomp.size > 0
118
- Reline::HISTORY << @line_editor.line.chomp
254
+ line = @@line_editor.line.dup
255
+ line.taint
256
+ if add_hist and line and line.chomp.size > 0
257
+ Reline::HISTORY << line.chomp
119
258
  end
120
259
 
121
- @line_editor.line
260
+ @@line_editor.reset_line if @@line_editor.line.nil?
261
+ line
122
262
  end
123
263
 
124
264
  def inner_readline(prompt, add_hist, multiline, &confirm_multiline_termination)
125
- if @@config.nil?
126
- @@config = Reline::Config.new
127
- @@config.read
265
+ if ENV['RELINE_STDERR_TTY']
266
+ $stderr.reopen(ENV['RELINE_STDERR_TTY'], 'w')
267
+ $stderr.sync = true
268
+ $stderr.puts "Reline is used by #{Process.pid}"
128
269
  end
129
- otio = prep
270
+ otio = Reline::IOGate.prep
130
271
 
131
272
  may_req_ambiguous_char_width
132
- @line_editor = Reline::LineEditor.new(@@config, prompt)
273
+ @@line_editor.reset(prompt)
133
274
  if multiline
134
- @line_editor.multiline_on
275
+ @@line_editor.multiline_on
135
276
  if block_given?
136
- @line_editor.confirm_multiline_termination_proc = confirm_multiline_termination
277
+ @@line_editor.confirm_multiline_termination_proc = confirm_multiline_termination
137
278
  end
138
- end
139
- @line_editor.completion_proc = @@completion_proc
140
- @line_editor.dig_perfect_match_proc = @@dig_perfect_match_proc
141
- @line_editor.retrieve_completion_block = method(:retrieve_completion_block)
142
- @line_editor.rerender
143
-
144
- if IS_WINDOWS
145
- config = {
146
- key_mapping: {
147
- [224, 72] => :ed_prev_history, # ↑
148
- [224, 80] => :ed_next_history, # ↓
149
- [224, 77] => :ed_next_char, # →
150
- [224, 75] => :ed_prev_char # ←
151
- }
152
- }
153
279
  else
154
- config = {
155
- key_mapping: {
156
- [27, 91, 65] => :ed_prev_history, # ↑
157
- [27, 91, 66] => :ed_next_history, # ↓
158
- [27, 91, 67] => :ed_next_char, # →
159
- [27, 91, 68] => :ed_prev_char # ←
160
- }
161
- }
280
+ @@line_editor.multiline_off
281
+ end
282
+ @@line_editor.output = @@output
283
+ @@line_editor.completion_proc = @@completion_proc
284
+ @@line_editor.output_modifier_proc = @@output_modifier_proc
285
+ @@line_editor.prompt_proc = @@prompt_proc
286
+ @@line_editor.auto_indent_proc = @@auto_indent_proc
287
+ @@line_editor.dig_perfect_match_proc = @@dig_perfect_match_proc
288
+ @@line_editor.pre_input_hook = @@pre_input_hook
289
+ @@line_editor.rerender
290
+
291
+ unless @@config.test_mode
292
+ @@config.read
293
+ @@config.reset_default_key_bindings
294
+ Reline::IOGate::RAW_KEYSTROKE_CONFIG.each_pair do |key, func|
295
+ @@config.add_default_key_binding(key, func)
296
+ end
162
297
  end
163
298
 
164
- key_stroke = Reline::KeyStroke.new(config)
165
299
  begin
166
- while c = getc
167
- key_stroke.input_to!(c)&.then { |inputs|
300
+ loop do
301
+ read_io(@@config.keyseq_timeout) { |inputs|
168
302
  inputs.each { |c|
169
- @line_editor.input_key(c)
170
- @line_editor.rerender
303
+ @@line_editor.input_key(c)
304
+ @@line_editor.rerender
171
305
  }
172
306
  }
173
- break if @line_editor.finished?
307
+ break if @@line_editor.finished?
174
308
  end
175
- Reline.move_cursor_column(0)
309
+ Reline::IOGate.move_cursor_column(0)
176
310
  rescue StandardError => e
177
- deprep(otio)
311
+ @@line_editor.finalize
312
+ Reline::IOGate.deprep(otio)
178
313
  raise e
179
314
  end
180
315
 
181
- deprep(otio)
316
+ @@line_editor.finalize
317
+ Reline::IOGate.deprep(otio)
318
+ end
319
+
320
+ # Keystrokes of GNU Readline will timeout it with the specification of
321
+ # "keyseq-timeout" when waiting for the 2nd character after the 1st one.
322
+ # If the 2nd character comes after 1st ESC without timeout it has a
323
+ # meta-property of meta-key to discriminate modified key with meta-key
324
+ # from multibyte characters that come with 8th bit on.
325
+ #
326
+ # GNU Readline will wait for the 2nd character with "keyseq-timeout"
327
+ # milli-seconds but wait forever after 3rd characters.
328
+ def read_io(keyseq_timeout, &block)
329
+ buffer = []
330
+ loop do
331
+ c = Reline::IOGate.getc
332
+ buffer << c
333
+ result = @@key_stroke.match_status(buffer)
334
+ case result
335
+ when :matched
336
+ block.(@@key_stroke.expand(buffer).map{ |c| Reline::Key.new(c, c, false) })
337
+ break
338
+ when :matching
339
+ if buffer.size == 1
340
+ begin
341
+ succ_c = nil
342
+ Timeout.timeout(keyseq_timeout / 1000.0) {
343
+ succ_c = Reline::IOGate.getc
344
+ }
345
+ rescue Timeout::Error # cancel matching only when first byte
346
+ block.([Reline::Key.new(c, c, false)])
347
+ break
348
+ else
349
+ if @@key_stroke.match_status(buffer.dup.push(succ_c)) == :unmatched
350
+ if c == "\e".ord
351
+ block.([Reline::Key.new(succ_c, succ_c | 0b10000000, true)])
352
+ else
353
+ block.([Reline::Key.new(c, c, false), Reline::Key.new(succ_c, succ_c, false)])
354
+ end
355
+ break
356
+ else
357
+ Reline::IOGate.ungetc(succ_c)
358
+ end
359
+ end
360
+ end
361
+ when :unmatched
362
+ if buffer.size == 1 and c == "\e".ord
363
+ read_escaped_key(keyseq_timeout, buffer, block)
364
+ else
365
+ block.(buffer.map{ |c| Reline::Key.new(c, c, false) })
366
+ end
367
+ break
368
+ end
369
+ end
370
+ end
371
+
372
+ def read_escaped_key(keyseq_timeout, buffer, block)
373
+ begin
374
+ escaped_c = nil
375
+ Timeout.timeout(keyseq_timeout / 1000.0) {
376
+ escaped_c = Reline::IOGate.getc
377
+ }
378
+ rescue Timeout::Error # independent ESC
379
+ block.([Reline::Key.new(c, c, false)])
380
+ else
381
+ if escaped_c.nil?
382
+ block.([Reline::Key.new(c, c, false)])
383
+ elsif escaped_c >= 128 # maybe, first byte of multi byte
384
+ block.([Reline::Key.new(c, c, false), Reline::Key.new(escaped_c, escaped_c, false)])
385
+ elsif escaped_c == "\e".ord # escape twice
386
+ block.([Reline::Key.new(c, c, false), Reline::Key.new(c, c, false)])
387
+ else
388
+ block.([Reline::Key.new(escaped_c, escaped_c | 0b10000000, true)])
389
+ end
390
+ end
182
391
  end
183
392
 
184
393
  def may_req_ambiguous_char_width
394
+ @@ambiguous_width = 2 if Reline::IOGate == Reline::GeneralIO or STDOUT.is_a?(File)
185
395
  return if @@ambiguous_width
186
- Reline.move_cursor_column(0)
396
+ Reline::IOGate.move_cursor_column(0)
187
397
  print "\u{25bd}"
188
- @@ambiguous_width = Reline.cursor_pos.x
189
- Reline.move_cursor_column(0)
190
- Reline.erase_after_cursor
398
+ @@ambiguous_width = Reline::IOGate.cursor_pos.x
399
+ Reline::IOGate.move_cursor_column(0)
400
+ Reline::IOGate.erase_after_cursor
191
401
  end
192
402
 
193
403
  def self.ambiguous_width
194
404
  @@ambiguous_width
195
405
  end
196
406
  end
407
+
408
+ if Reline::IS_WINDOWS
409
+ require 'reline/windows'
410
+ Reline::IOGate = Reline::Windows
411
+ else
412
+ require 'reline/ansi'
413
+ Reline::IOGate = Reline::ANSI
414
+ end
415
+ require 'reline/general_io'