reline 0.1.5 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
data/lib/reline.rb CHANGED
@@ -7,14 +7,33 @@ require 'reline/key_actor'
7
7
  require 'reline/key_stroke'
8
8
  require 'reline/line_editor'
9
9
  require 'reline/history'
10
+ require 'reline/terminfo'
10
11
  require 'rbconfig'
11
12
 
12
13
  module Reline
13
14
  FILENAME_COMPLETION_PROC = nil
14
15
  USERNAME_COMPLETION_PROC = nil
15
16
 
16
- Key = Struct.new('Key', :char, :combined_char, :with_meta)
17
+ class ConfigEncodingConversionError < StandardError; end
18
+
19
+ Key = Struct.new('Key', :char, :combined_char, :with_meta) do
20
+ def match?(other)
21
+ case other
22
+ when Reline::Key
23
+ (other.char.nil? or char.nil? or char == other.char) and
24
+ (other.combined_char.nil? or combined_char.nil? or combined_char == other.combined_char) and
25
+ (other.with_meta.nil? or with_meta.nil? or with_meta == other.with_meta)
26
+ when Integer, Symbol
27
+ (combined_char and combined_char == other) or
28
+ (combined_char.nil? and char and char == other)
29
+ else
30
+ false
31
+ end
32
+ end
33
+ alias_method :==, :match?
34
+ end
17
35
  CursorPos = Struct.new(:x, :y)
36
+ DialogRenderInfo = Struct.new(:pos, :contents, :bg_color, :width, :height, :scrollbar, keyword_init: true)
18
37
 
19
38
  class Core
20
39
  ATTR_READER_NAMES = %i(
@@ -36,14 +55,15 @@ module Reline
36
55
  attr_accessor :config
37
56
  attr_accessor :key_stroke
38
57
  attr_accessor :line_editor
39
- attr_accessor :ambiguous_width
40
58
  attr_accessor :last_incremental_search
41
59
  attr_reader :output
42
60
 
43
61
  def initialize
44
62
  self.output = STDOUT
63
+ @dialog_proc_list = {}
45
64
  yield self
46
65
  @completion_quote_character = nil
66
+ @bracketed_paste_finished = false
47
67
  end
48
68
 
49
69
  def encoding
@@ -103,6 +123,14 @@ module Reline
103
123
  @completion_proc = p
104
124
  end
105
125
 
126
+ def autocompletion
127
+ @config.autocompletion
128
+ end
129
+
130
+ def autocompletion=(val)
131
+ @config.autocompletion = val
132
+ end
133
+
106
134
  def output_modifier_proc=(p)
107
135
  raise ArgumentError unless p.respond_to?(:call) or p.nil?
108
136
  @output_modifier_proc = p
@@ -127,6 +155,17 @@ module Reline
127
155
  @dig_perfect_match_proc = p
128
156
  end
129
157
 
158
+ DialogProc = Struct.new(:dialog_proc, :context)
159
+ def add_dialog_proc(name_sym, p, context = nil)
160
+ raise ArgumentError unless p.respond_to?(:call) or p.nil?
161
+ raise ArgumentError unless name_sym.instance_of?(Symbol)
162
+ @dialog_proc_list[name_sym] = DialogProc.new(p, context)
163
+ end
164
+
165
+ def dialog_proc(name_sym)
166
+ @dialog_proc_list[name_sym]
167
+ end
168
+
130
169
  def input=(val)
131
170
  raise TypeError unless val.respond_to?(:getc) or val.nil?
132
171
  if val.respond_to?(:getc)
@@ -168,6 +207,46 @@ module Reline
168
207
  Reline::IOGate.get_screen_size
169
208
  end
170
209
 
210
+ Reline::DEFAULT_DIALOG_PROC_AUTOCOMPLETE = ->() {
211
+ # autocomplete
212
+ return nil unless config.autocompletion
213
+ if just_cursor_moving and completion_journey_data.nil?
214
+ # Auto complete starts only when edited
215
+ return nil
216
+ end
217
+ pre, target, post = retrieve_completion_block(true)
218
+ if target.nil? or target.empty? or (completion_journey_data&.pointer == -1 and target.size <= 3)
219
+ return nil
220
+ end
221
+ if completion_journey_data and completion_journey_data.list
222
+ result = completion_journey_data.list.dup
223
+ result.shift
224
+ pointer = completion_journey_data.pointer - 1
225
+ else
226
+ result = call_completion_proc_with_checking_args(pre, target, post)
227
+ pointer = nil
228
+ end
229
+ if result and result.size == 1 and result[0] == target and pointer != 0
230
+ result = nil
231
+ end
232
+ target_width = Reline::Unicode.calculate_width(target)
233
+ x = cursor_pos.x - target_width
234
+ if x < 0
235
+ x = screen_width + x
236
+ y = -1
237
+ else
238
+ y = 0
239
+ end
240
+ cursor_pos_to_render = Reline::CursorPos.new(x, y)
241
+ if context and context.is_a?(Array)
242
+ context.clear
243
+ context.push(cursor_pos_to_render, result, pointer, dialog)
244
+ end
245
+ dialog.pointer = pointer
246
+ DialogRenderInfo.new(pos: cursor_pos_to_render, contents: result, scrollbar: true, height: 15)
247
+ }
248
+ Reline::DEFAULT_DIALOG_CONTEXT = Array.new
249
+
171
250
  def readmultiline(prompt = '', add_hist = false, &confirm_multiline_termination)
172
251
  unless confirm_multiline_termination
173
252
  raise ArgumentError.new('#readmultiline needs block to confirm multiline termination')
@@ -199,7 +278,11 @@ module Reline
199
278
 
200
279
  private def inner_readline(prompt, add_hist, multiline, &confirm_multiline_termination)
201
280
  if ENV['RELINE_STDERR_TTY']
202
- $stderr.reopen(ENV['RELINE_STDERR_TTY'], 'w')
281
+ if Reline::IOGate.win?
282
+ $stderr = File.open(ENV['RELINE_STDERR_TTY'], 'a')
283
+ else
284
+ $stderr.reopen(ENV['RELINE_STDERR_TTY'], 'w')
285
+ end
203
286
  $stderr.sync = true
204
287
  $stderr.puts "Reline is used by #{Process.pid}"
205
288
  end
@@ -223,25 +306,39 @@ module Reline
223
306
  line_editor.auto_indent_proc = auto_indent_proc
224
307
  line_editor.dig_perfect_match_proc = dig_perfect_match_proc
225
308
  line_editor.pre_input_hook = pre_input_hook
309
+ @dialog_proc_list.each_pair do |name_sym, d|
310
+ line_editor.add_dialog_proc(name_sym, d.dialog_proc, d.context)
311
+ end
226
312
 
227
313
  unless config.test_mode
228
314
  config.read
229
315
  config.reset_default_key_bindings
230
- Reline::IOGate::RAW_KEYSTROKE_CONFIG.each_pair do |key, func|
231
- config.add_default_key_binding(key, func)
232
- end
316
+ Reline::IOGate.set_default_key_bindings(config)
233
317
  end
234
318
 
235
319
  line_editor.rerender
236
320
 
237
321
  begin
322
+ line_editor.set_signal_handlers
323
+ prev_pasting_state = false
238
324
  loop do
325
+ prev_pasting_state = Reline::IOGate.in_pasting?
239
326
  read_io(config.keyseq_timeout) { |inputs|
327
+ line_editor.set_pasting_state(Reline::IOGate.in_pasting?)
240
328
  inputs.each { |c|
241
329
  line_editor.input_key(c)
242
330
  line_editor.rerender
243
331
  }
332
+ if @bracketed_paste_finished
333
+ line_editor.rerender_all
334
+ @bracketed_paste_finished = false
335
+ end
244
336
  }
337
+ if prev_pasting_state == true and not Reline::IOGate.in_pasting? and not line_editor.finished?
338
+ line_editor.set_pasting_state(false)
339
+ prev_pasting_state = false
340
+ line_editor.rerender_all
341
+ end
245
342
  break if line_editor.finished?
246
343
  end
247
344
  Reline::IOGate.move_cursor_column(0)
@@ -251,17 +348,23 @@ module Reline
251
348
  line_editor.finalize
252
349
  Reline::IOGate.deprep(otio)
253
350
  raise e
351
+ rescue Exception
352
+ # Including Interrupt
353
+ line_editor.finalize
354
+ Reline::IOGate.deprep(otio)
355
+ raise
254
356
  end
255
357
 
256
358
  line_editor.finalize
257
359
  Reline::IOGate.deprep(otio)
258
360
  end
259
361
 
260
- # Keystrokes of GNU Readline will timeout it with the specification of
261
- # "keyseq-timeout" when waiting for the 2nd character after the 1st one.
262
- # If the 2nd character comes after 1st ESC without timeout it has a
263
- # meta-property of meta-key to discriminate modified key with meta-key
264
- # from multibyte characters that come with 8th bit on.
362
+ # GNU Readline waits for "keyseq-timeout" milliseconds to see if the ESC
363
+ # is followed by a character, and times out and treats it as a standalone
364
+ # ESC if the second character does not arrive. If the second character
365
+ # comes before timed out, it is treated as a modifier key with the
366
+ # meta-property of meta-key, so that it can be distinguished from
367
+ # multibyte characters with the 8th bit turned on.
265
368
  #
266
369
  # GNU Readline will wait for the 2nd character with "keyseq-timeout"
267
370
  # milli-seconds but wait forever after 3rd characters.
@@ -269,8 +372,13 @@ module Reline
269
372
  buffer = []
270
373
  loop do
271
374
  c = Reline::IOGate.getc
272
- buffer << c
273
- result = key_stroke.match_status(buffer)
375
+ if c == -1
376
+ result = :unmatched
377
+ @bracketed_paste_finished = true
378
+ else
379
+ buffer << c
380
+ result = key_stroke.match_status(buffer)
381
+ end
274
382
  case result
275
383
  when :matched
276
384
  expanded = key_stroke.expand(buffer).map{ |expanded_c|
@@ -280,25 +388,9 @@ module Reline
280
388
  break
281
389
  when :matching
282
390
  if buffer.size == 1
283
- begin
284
- succ_c = nil
285
- Timeout.timeout(keyseq_timeout / 1000.0) {
286
- succ_c = Reline::IOGate.getc
287
- }
288
- rescue Timeout::Error # cancel matching only when first byte
289
- block.([Reline::Key.new(c, c, false)])
290
- break
291
- else
292
- if key_stroke.match_status(buffer.dup.push(succ_c)) == :unmatched
293
- if c == "\e".ord
294
- block.([Reline::Key.new(succ_c, succ_c | 0b10000000, true)])
295
- else
296
- block.([Reline::Key.new(c, c, false), Reline::Key.new(succ_c, succ_c, false)])
297
- end
298
- break
299
- else
300
- Reline::IOGate.ungetc(succ_c)
301
- end
391
+ case read_2nd_character_of_key_sequence(keyseq_timeout, buffer, c, block)
392
+ when :break then break
393
+ when :next then next
302
394
  end
303
395
  end
304
396
  when :unmatched
@@ -315,6 +407,38 @@ module Reline
315
407
  end
316
408
  end
317
409
 
410
+ private def read_2nd_character_of_key_sequence(keyseq_timeout, buffer, c, block)
411
+ begin
412
+ succ_c = nil
413
+ Timeout.timeout(keyseq_timeout / 1000.0) {
414
+ succ_c = Reline::IOGate.getc
415
+ }
416
+ rescue Timeout::Error # cancel matching only when first byte
417
+ block.([Reline::Key.new(c, c, false)])
418
+ return :break
419
+ else
420
+ case key_stroke.match_status(buffer.dup.push(succ_c))
421
+ when :unmatched
422
+ if c == "\e".ord
423
+ block.([Reline::Key.new(succ_c, succ_c | 0b10000000, true)])
424
+ else
425
+ block.([Reline::Key.new(c, c, false), Reline::Key.new(succ_c, succ_c, false)])
426
+ end
427
+ return :break
428
+ when :matching
429
+ Reline::IOGate.ungetc(succ_c)
430
+ return :next
431
+ when :matched
432
+ buffer << succ_c
433
+ expanded = key_stroke.expand(buffer).map{ |expanded_c|
434
+ Reline::Key.new(expanded_c, expanded_c, false)
435
+ }
436
+ block.(expanded)
437
+ return :break
438
+ end
439
+ end
440
+ end
441
+
318
442
  private def read_escaped_key(keyseq_timeout, c, block)
319
443
  begin
320
444
  escaped_c = nil
@@ -336,9 +460,14 @@ module Reline
336
460
  end
337
461
  end
338
462
 
463
+ def ambiguous_width
464
+ may_req_ambiguous_char_width unless defined? @ambiguous_width
465
+ @ambiguous_width
466
+ end
467
+
339
468
  private def may_req_ambiguous_char_width
340
469
  @ambiguous_width = 2 if Reline::IOGate == Reline::GeneralIO or STDOUT.is_a?(File)
341
- return if ambiguous_width
470
+ return if defined? @ambiguous_width
342
471
  Reline::IOGate.move_cursor_column(0)
343
472
  begin
344
473
  output.write "\u{25bd}"
@@ -361,7 +490,7 @@ module Reline
361
490
  #--------------------------------------------------------
362
491
 
363
492
  (Core::ATTR_READER_NAMES).each { |name|
364
- def_single_delegators :core, "#{name}", "#{name}="
493
+ def_single_delegators :core, :"#{name}", :"#{name}="
365
494
  }
366
495
  def_single_delegators :core, :input=, :output=
367
496
  def_single_delegators :core, :vi_editing_mode, :emacs_editing_mode
@@ -396,6 +525,9 @@ module Reline
396
525
  def_single_delegators :core, :ambiguous_width
397
526
  def_single_delegators :core, :last_incremental_search
398
527
  def_single_delegators :core, :last_incremental_search=
528
+ def_single_delegators :core, :add_dialog_proc
529
+ def_single_delegators :core, :dialog_proc
530
+ def_single_delegators :core, :autocompletion, :autocompletion=
399
531
 
400
532
  def_single_delegators :core, :readmultiline
401
533
  def_instance_delegators self, :readmultiline
@@ -417,25 +549,38 @@ module Reline
417
549
  core.completer_quote_characters = '"\''
418
550
  core.filename_quote_characters = ""
419
551
  core.special_prefixes = ""
552
+ core.add_dialog_proc(:autocomplete, Reline::DEFAULT_DIALOG_PROC_AUTOCOMPLETE, Reline::DEFAULT_DIALOG_CONTEXT)
420
553
  }
421
554
  end
422
555
 
556
+ def self.ungetc(c)
557
+ Reline::IOGate.ungetc(c)
558
+ end
559
+
423
560
  def self.line_editor
424
561
  core.line_editor
425
562
  end
426
563
  end
427
564
 
565
+ require 'reline/general_io'
428
566
  if RbConfig::CONFIG['host_os'] =~ /mswin|msys|mingw|cygwin|bccwin|wince|emc/
429
567
  require 'reline/windows'
430
568
  if Reline::Windows.msys_tty?
431
- require 'reline/ansi'
432
- Reline::IOGate = Reline::ANSI
569
+ Reline::IOGate = if ENV['TERM'] == 'dumb'
570
+ Reline::GeneralIO
571
+ else
572
+ require 'reline/ansi'
573
+ Reline::ANSI
574
+ end
433
575
  else
434
576
  Reline::IOGate = Reline::Windows
435
577
  end
436
578
  else
437
- require 'reline/ansi'
438
- Reline::IOGate = Reline::ANSI
579
+ Reline::IOGate = if $stdout.isatty
580
+ require 'reline/ansi'
581
+ Reline::ANSI
582
+ else
583
+ Reline::GeneralIO
584
+ end
439
585
  end
440
586
  Reline::HISTORY = Reline::History.new(Reline.core.config)
441
- require 'reline/general_io'
@@ -0,0 +1,25 @@
1
+ Copyright (c) 2009, Park Heesob
2
+ All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without
5
+ modification, are permitted provided that the following conditions are met:
6
+
7
+ * Redistributions of source code must retain the above copyright notice, this
8
+ list of conditions and the following disclaimer.
9
+ * Redistributions in binary form must reproduce the above copyright notice
10
+ this list of conditions and the following disclaimer in the documentation
11
+ and/or other materials provided with the distribution.
12
+ * Neither the name of Park Heesob nor the names of its contributors
13
+ may be used to endorse or promote products derived from this software
14
+ without specific prior written permission.
15
+
16
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
20
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
22
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
23
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
24
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: reline
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.5
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - aycabta
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-09-13 00:00:00.000000000 Z
11
+ date: 2022-01-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: io-console
@@ -24,48 +24,6 @@ dependencies:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0.5'
27
- - !ruby/object:Gem::Dependency
28
- name: bundler
29
- requirement: !ruby/object:Gem::Requirement
30
- requirements:
31
- - - ">="
32
- - !ruby/object:Gem::Version
33
- version: '0'
34
- type: :development
35
- prerelease: false
36
- version_requirements: !ruby/object:Gem::Requirement
37
- requirements:
38
- - - ">="
39
- - !ruby/object:Gem::Version
40
- version: '0'
41
- - !ruby/object:Gem::Dependency
42
- name: rake
43
- requirement: !ruby/object:Gem::Requirement
44
- requirements:
45
- - - ">="
46
- - !ruby/object:Gem::Version
47
- version: '0'
48
- type: :development
49
- prerelease: false
50
- version_requirements: !ruby/object:Gem::Requirement
51
- requirements:
52
- - - ">="
53
- - !ruby/object:Gem::Version
54
- version: '0'
55
- - !ruby/object:Gem::Dependency
56
- name: test-unit
57
- requirement: !ruby/object:Gem::Requirement
58
- requirements:
59
- - - ">="
60
- - !ruby/object:Gem::Version
61
- version: '0'
62
- type: :development
63
- prerelease: false
64
- version_requirements: !ruby/object:Gem::Requirement
65
- requirements:
66
- - - ">="
67
- - !ruby/object:Gem::Version
68
- version: '0'
69
27
  description: Alternative GNU Readline or Editline implementation by pure Ruby.
70
28
  email:
71
29
  - aycabta@gmail.com
@@ -89,10 +47,13 @@ files:
89
47
  - lib/reline/key_stroke.rb
90
48
  - lib/reline/kill_ring.rb
91
49
  - lib/reline/line_editor.rb
50
+ - lib/reline/sibori.rb
51
+ - lib/reline/terminfo.rb
92
52
  - lib/reline/unicode.rb
93
53
  - lib/reline/unicode/east_asian_width.rb
94
54
  - lib/reline/version.rb
95
55
  - lib/reline/windows.rb
56
+ - license_of_rb-readline
96
57
  homepage: https://github.com/ruby/reline
97
58
  licenses:
98
59
  - Ruby
@@ -112,7 +73,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
112
73
  - !ruby/object:Gem::Version
113
74
  version: '0'
114
75
  requirements: []
115
- rubygems_version: 3.1.2
76
+ rubygems_version: 3.0.3.1
116
77
  signing_key:
117
78
  specification_version: 4
118
79
  summary: Alternative GNU Readline or Editline implementation by pure Ruby.