reline 0.0.1 → 0.0.2

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: beb24e6d3114b6debe540928f1299fd10d420ff447f90401c9a8249b12804b7d
4
- data.tar.gz: 046dfcfa706a95f62acb152238cae3680f918f9af2c9a52eee4fa4e5771ebdbc
3
+ metadata.gz: f47cc0525de297744dff41dc87b9a4ac5c9ff1cad21da7f60a41aa4b17bd1b0e
4
+ data.tar.gz: '04681f2a2dc431f65fa9f2df68c8b7aa1955a7a4032b8d6d5483984e236d4c9b'
5
5
  SHA512:
6
- metadata.gz: a9a1abfe4febd8da98052386b0cca9a4b3c9f333c0a53c439d5980cad64c3b2f9f75c35d2498e42beadd4cea2f6a7d90c005d0112317b8ea90f89fd45b0f8632
7
- data.tar.gz: 6f482d37f4c85ab5f0a5df08e069f81c5289b7bedbcea105a0601a7174bd3ca314c0a04750e2bba91cb31ad7bdc7f56afa4f3b8398bf2bd80e635e356be12615
6
+ metadata.gz: ad38bf76659bc7553ebf084fd4aeedf6aba7cba1a2860fc78af0683222109216d59bdb3b4bf66962ce9a640b9033d21f707442151834e9ccc21c6fc8d115347b
7
+ data.tar.gz: 95d9d943870e64897076cdc97750f6110817bc4ad14e958755164aa862785d7e48399465f7997f900547f797e6304cf48d6761f78612651ebe7aa6dca505fab3
data/README.md CHANGED
@@ -1,3 +1,5 @@
1
+ [![Build Status](https://travis-ci.com/ruby/reline.svg?branch=master)](https://travis-ci.com/ruby/reline)
2
+
1
3
  # Reline
2
4
 
3
5
  Reline is compatible with the API of Ruby's stdlib 'readline', GNU Readline and Editline by pure Ruby implementation.
@@ -1,5 +1,6 @@
1
1
  require 'io/console'
2
2
  require 'timeout'
3
+ require 'forwardable'
3
4
  require 'reline/version'
4
5
  require 'reline/config'
5
6
  require 'reline/key_actor'
@@ -8,404 +9,390 @@ require 'reline/line_editor'
8
9
  require 'reline/history'
9
10
 
10
11
  module Reline
11
- Key = Struct.new('Key', :char, :combined_char, :with_meta)
12
-
13
- extend self
14
12
  FILENAME_COMPLETION_PROC = nil
15
13
  USERNAME_COMPLETION_PROC = nil
16
14
 
17
- if RUBY_PLATFORM =~ /mswin|mingw/
18
- IS_WINDOWS = true
19
- else
20
- IS_WINDOWS = false
21
- end
22
-
15
+ Key = Struct.new('Key', :char, :combined_char, :with_meta)
23
16
  CursorPos = Struct.new(:x, :y)
24
17
 
25
- @@config = Reline::Config.new
26
- @@key_stroke = Reline::KeyStroke.new(@@config)
27
- @@line_editor = Reline::LineEditor.new(@@config)
28
- @@ambiguous_width = nil
29
-
30
- HISTORY = History.new(@@config)
31
-
32
- @@completion_append_character = nil
33
- def self.completion_append_character
34
- @@completion_append_character
35
- end
36
- def self.completion_append_character=(val)
37
- if val.nil?
38
- @@completion_append_character = nil
39
- elsif val.size == 1
40
- @@completion_append_character = val.encode(Encoding::default_external)
41
- elsif val.size > 1
42
- @@completion_append_character = val[0].encode(Encoding::default_external)
18
+ class Core
19
+ if RbConfig::CONFIG['host_os'] =~ /mswin|msys|mingw|cygwin|bccwin|wince|emc/
20
+ IS_WINDOWS = true
43
21
  else
44
- @@completion_append_character = nil
22
+ IS_WINDOWS = false
45
23
  end
46
- end
47
-
48
- @@basic_word_break_characters = " \t\n`><=;|&{("
49
- def self.basic_word_break_characters
50
- @@basic_word_break_characters
51
- end
52
- def self.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
24
 
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
102
- end
25
+ ATTR_READER_NAMES = %i(
26
+ completion_append_character
27
+ basic_word_break_characters
28
+ completer_word_break_characters
29
+ basic_quote_characters
30
+ completer_quote_characters
31
+ filename_quote_characters
32
+ special_prefixes
33
+ completion_proc
34
+ output_modifier_proc
35
+ prompt_proc
36
+ auto_indent_proc
37
+ pre_input_hook
38
+ dig_perfect_match_proc
39
+ ).each(&method(:attr_reader))
40
+
41
+ ATTR_ACCESSOR_NAMES = %i(
42
+ completion_case_fold
43
+ ).each(&method(:attr_accessor))
44
+
45
+ attr_accessor :config
46
+ attr_accessor :key_stroke
47
+ attr_accessor :line_editor
48
+ attr_accessor :ambiguous_width
49
+ attr_reader :output
50
+
51
+ def initialize
52
+ self.output = STDOUT
53
+ yield self
54
+ end
103
55
 
104
- @@completion_proc = nil
105
- def self.completion_proc
106
- @@completion_proc
107
- end
108
- def self.completion_proc=(p)
109
- raise ArgumentError unless p.is_a?(Proc)
110
- @@completion_proc = p
111
- end
56
+ def completion_append_character=(val)
57
+ if val.nil?
58
+ @completion_append_character = nil
59
+ elsif val.size == 1
60
+ @completion_append_character = val.encode(Encoding::default_external)
61
+ elsif val.size > 1
62
+ @completion_append_character = val[0].encode(Encoding::default_external)
63
+ else
64
+ @completion_append_character = nil
65
+ end
66
+ end
112
67
 
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
68
+ def basic_word_break_characters=(v)
69
+ @basic_word_break_characters = v.encode(Encoding::default_external)
70
+ end
121
71
 
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
72
+ def completer_word_break_characters=(v)
73
+ @completer_word_break_characters = v.encode(Encoding::default_external)
74
+ end
130
75
 
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
76
+ def basic_quote_characters=(v)
77
+ @basic_quote_characters = v.encode(Encoding::default_external)
78
+ end
139
79
 
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
80
+ def completer_quote_characters=(v)
81
+ @completer_quote_characters = v.encode(Encoding::default_external)
82
+ end
147
83
 
148
- @@dig_perfect_match_proc = nil
149
- def self.dig_perfect_match_proc
150
- @@dig_perfect_match_proc
151
- end
152
- def self.dig_perfect_match_proc=(p)
153
- raise ArgumentError unless p.is_a?(Proc)
154
- @@dig_perfect_match_proc = p
155
- end
84
+ def filename_quote_characters=(v)
85
+ @filename_quote_characters = v.encode(Encoding::default_external)
86
+ end
156
87
 
157
- def self.insert_text(text)
158
- @@line_editor&.insert_text(text)
159
- self
160
- end
88
+ def special_prefixes=(v)
89
+ @special_prefixes = v.encode(Encoding::default_external)
90
+ end
161
91
 
162
- def self.redisplay
163
- @@line_editor&.rerender
164
- end
92
+ def completion_proc=(p)
93
+ raise ArgumentError unless p.is_a?(Proc)
94
+ @completion_proc = p
95
+ end
165
96
 
166
- def self.line_buffer
167
- @@line_editor&.line
168
- end
97
+ def output_modifier_proc=(p)
98
+ raise ArgumentError unless p.is_a?(Proc)
99
+ @output_modifier_proc = p
100
+ end
169
101
 
170
- def self.point
171
- @@line_editor ? @@line_editor.byte_pointer : 0
172
- end
102
+ def prompt_proc=(p)
103
+ raise ArgumentError unless p.is_a?(Proc)
104
+ @prompt_proc = p
105
+ end
173
106
 
174
- def self.point=(val)
175
- @@line_editor.byte_pointer = val
176
- end
107
+ def auto_indent_proc=(p)
108
+ raise ArgumentError unless p.is_a?(Proc)
109
+ @auto_indent_proc = p
110
+ end
177
111
 
178
- def self.delete_text(start = nil, length = nil)
179
- @@line_editor&.delete_text(start, length)
180
- end
112
+ def pre_input_hook=(p)
113
+ @pre_input_hook = p
114
+ end
181
115
 
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
116
+ def dig_perfect_match_proc=(p)
117
+ raise ArgumentError unless p.is_a?(Proc)
118
+ @dig_perfect_match_proc = p
119
+ end
188
120
 
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
121
+ def input=(val)
122
+ raise TypeError unless val.respond_to?(:getc) or val.nil?
123
+ if val.respond_to?(:getc)
124
+ if defined?(Reline::ANSI) and Reline::IOGate == Reline::ANSI
125
+ Reline::ANSI.input = val
126
+ elsif Reline::IOGate == Reline::GeneralIO
127
+ Reline::GeneralIO.input = val
128
+ end
196
129
  end
197
130
  end
198
- end
199
131
 
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
132
+ def output=(val)
133
+ raise TypeError unless val.respond_to?(:write) or val.nil?
134
+ @output = val
135
+ if defined?(Reline::ANSI) and Reline::IOGate == Reline::ANSI
136
+ Reline::ANSI.output = val
137
+ end
206
138
  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
139
 
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
140
+ def vi_editing_mode
141
+ config.editing_mode = :vi_insert
142
+ nil
143
+ end
226
144
 
227
- def self.get_screen_size
228
- Reline::IOGate.get_screen_size
229
- end
145
+ def emacs_editing_mode
146
+ config.editing_mode = :emacs
147
+ nil
148
+ end
230
149
 
231
- def eof?
232
- @@line_editor.eof?
233
- end
150
+ def vi_editing_mode?
151
+ config.editing_mode_is?(:vi_insert, :vi_command)
152
+ end
234
153
 
235
- def readmultiline(prompt = '', add_hist = false, &confirm_multiline_termination)
236
- unless confirm_multiline_termination
237
- raise ArgumentError.new('#readmultiline needs block to confirm multiline termination')
154
+ def emacs_editing_mode?
155
+ config.editing_mode_is?(:emacs)
238
156
  end
239
- inner_readline(prompt, add_hist, true, &confirm_multiline_termination)
240
157
 
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
158
+ def get_screen_size
159
+ Reline::IOGate.get_screen_size
245
160
  end
246
161
 
247
- @@line_editor.reset_line if @@line_editor.whole_buffer.nil?
248
- whole_buffer
249
- end
162
+ def readmultiline(prompt = '', add_hist = false, &confirm_multiline_termination)
163
+ unless confirm_multiline_termination
164
+ raise ArgumentError.new('#readmultiline needs block to confirm multiline termination')
165
+ end
166
+ inner_readline(prompt, add_hist, true, &confirm_multiline_termination)
250
167
 
251
- def readline(prompt = '', add_hist = false)
252
- inner_readline(prompt, add_hist, false)
168
+ whole_buffer = line_editor.whole_buffer.dup
169
+ whole_buffer.taint
170
+ if add_hist and whole_buffer and whole_buffer.chomp.size > 0
171
+ Reline::HISTORY << whole_buffer
172
+ end
253
173
 
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
174
+ line_editor.reset_line if line_editor.whole_buffer.nil?
175
+ whole_buffer
258
176
  end
259
177
 
260
- @@line_editor.reset_line if @@line_editor.line.nil?
261
- line
262
- end
178
+ def readline(prompt = '', add_hist = false)
179
+ inner_readline(prompt, add_hist, false)
263
180
 
264
- def inner_readline(prompt, add_hist, multiline, &confirm_multiline_termination)
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}"
269
- end
270
- otio = Reline::IOGate.prep
271
-
272
- may_req_ambiguous_char_width
273
- @@line_editor.reset(prompt)
274
- if multiline
275
- @@line_editor.multiline_on
276
- if block_given?
277
- @@line_editor.confirm_multiline_termination_proc = confirm_multiline_termination
181
+ line = line_editor.line.dup
182
+ line.taint
183
+ if add_hist and line and line.chomp.size > 0
184
+ Reline::HISTORY << line.chomp
278
185
  end
279
- else
280
- @@line_editor.multiline_off
186
+
187
+ line_editor.reset_line if line_editor.line.nil?
188
+ line
281
189
  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)
190
+
191
+ private def inner_readline(prompt, add_hist, multiline, &confirm_multiline_termination)
192
+ if ENV['RELINE_STDERR_TTY']
193
+ $stderr.reopen(ENV['RELINE_STDERR_TTY'], 'w')
194
+ $stderr.sync = true
195
+ $stderr.puts "Reline is used by #{Process.pid}"
196
+ end
197
+ otio = Reline::IOGate.prep
198
+
199
+ may_req_ambiguous_char_width
200
+ line_editor.reset(prompt)
201
+ if multiline
202
+ line_editor.multiline_on
203
+ if block_given?
204
+ line_editor.confirm_multiline_termination_proc = confirm_multiline_termination
205
+ end
206
+ else
207
+ line_editor.multiline_off
208
+ end
209
+ line_editor.output = output
210
+ line_editor.completion_proc = completion_proc
211
+ line_editor.output_modifier_proc = output_modifier_proc
212
+ line_editor.prompt_proc = prompt_proc
213
+ line_editor.auto_indent_proc = auto_indent_proc
214
+ line_editor.dig_perfect_match_proc = dig_perfect_match_proc
215
+ line_editor.pre_input_hook = pre_input_hook
216
+ line_editor.rerender
217
+
218
+ unless config.test_mode
219
+ config.read
220
+ config.reset_default_key_bindings
221
+ Reline::IOGate::RAW_KEYSTROKE_CONFIG.each_pair do |key, func|
222
+ config.add_default_key_binding(key, func)
223
+ end
296
224
  end
297
- end
298
225
 
299
- begin
300
- loop do
301
- read_io(@@config.keyseq_timeout) { |inputs|
302
- inputs.each { |c|
303
- @@line_editor.input_key(c)
304
- @@line_editor.rerender
226
+ begin
227
+ loop do
228
+ read_io(config.keyseq_timeout) { |inputs|
229
+ inputs.each { |c|
230
+ line_editor.input_key(c)
231
+ line_editor.rerender
232
+ }
305
233
  }
306
- }
307
- break if @@line_editor.finished?
234
+ break if line_editor.finished?
235
+ end
236
+ Reline::IOGate.move_cursor_column(0)
237
+ rescue StandardError => e
238
+ line_editor.finalize
239
+ Reline::IOGate.deprep(otio)
240
+ raise e
308
241
  end
309
- Reline::IOGate.move_cursor_column(0)
310
- rescue StandardError => e
311
- @@line_editor.finalize
242
+
243
+ line_editor.finalize
312
244
  Reline::IOGate.deprep(otio)
313
- raise e
314
245
  end
315
246
 
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
247
+ # Keystrokes of GNU Readline will timeout it with the specification of
248
+ # "keyseq-timeout" when waiting for the 2nd character after the 1st one.
249
+ # If the 2nd character comes after 1st ESC without timeout it has a
250
+ # meta-property of meta-key to discriminate modified key with meta-key
251
+ # from multibyte characters that come with 8th bit on.
252
+ #
253
+ # GNU Readline will wait for the 2nd character with "keyseq-timeout"
254
+ # milli-seconds but wait forever after 3rd characters.
255
+ private def read_io(keyseq_timeout, &block)
256
+ buffer = []
257
+ loop do
258
+ c = Reline::IOGate.getc
259
+ buffer << c
260
+ result = key_stroke.match_status(buffer)
261
+ case result
262
+ when :matched
263
+ block.(key_stroke.expand(buffer).map{ |c| Reline::Key.new(c, c, false) })
264
+ break
265
+ when :matching
266
+ if buffer.size == 1
267
+ begin
268
+ succ_c = nil
269
+ Timeout.timeout(keyseq_timeout / 1000.0) {
270
+ succ_c = Reline::IOGate.getc
271
+ }
272
+ rescue Timeout::Error # cancel matching only when first byte
273
+ block.([Reline::Key.new(c, c, false)])
355
274
  break
356
275
  else
357
- Reline::IOGate.ungetc(succ_c)
276
+ if key_stroke.match_status(buffer.dup.push(succ_c)) == :unmatched
277
+ if c == "\e".ord
278
+ block.([Reline::Key.new(succ_c, succ_c | 0b10000000, true)])
279
+ else
280
+ block.([Reline::Key.new(c, c, false), Reline::Key.new(succ_c, succ_c, false)])
281
+ end
282
+ break
283
+ else
284
+ Reline::IOGate.ungetc(succ_c)
285
+ end
358
286
  end
359
287
  end
288
+ when :unmatched
289
+ if buffer.size == 1 and c == "\e".ord
290
+ read_escaped_key(keyseq_timeout, buffer, block)
291
+ else
292
+ block.(buffer.map{ |c| Reline::Key.new(c, c, false) })
293
+ end
294
+ break
360
295
  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
296
  end
369
297
  end
370
- end
371
298
 
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?
299
+ private def read_escaped_key(keyseq_timeout, buffer, block)
300
+ begin
301
+ escaped_c = nil
302
+ Timeout.timeout(keyseq_timeout / 1000.0) {
303
+ escaped_c = Reline::IOGate.getc
304
+ }
305
+ rescue Timeout::Error # independent ESC
382
306
  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
307
  else
388
- block.([Reline::Key.new(escaped_c, escaped_c | 0b10000000, true)])
308
+ if escaped_c.nil?
309
+ block.([Reline::Key.new(c, c, false)])
310
+ elsif escaped_c >= 128 # maybe, first byte of multi byte
311
+ block.([Reline::Key.new(c, c, false), Reline::Key.new(escaped_c, escaped_c, false)])
312
+ elsif escaped_c == "\e".ord # escape twice
313
+ block.([Reline::Key.new(c, c, false), Reline::Key.new(c, c, false)])
314
+ else
315
+ block.([Reline::Key.new(escaped_c, escaped_c | 0b10000000, true)])
316
+ end
389
317
  end
390
318
  end
319
+
320
+ private def may_req_ambiguous_char_width
321
+ @ambiguous_width = 2 if Reline::IOGate == Reline::GeneralIO or STDOUT.is_a?(File)
322
+ return if @ambiguous_width
323
+ Reline::IOGate.move_cursor_column(0)
324
+ print "\u{25bd}"
325
+ @ambiguous_width = Reline::IOGate.cursor_pos.x
326
+ Reline::IOGate.move_cursor_column(0)
327
+ Reline::IOGate.erase_after_cursor
328
+ end
391
329
  end
392
330
 
393
- def may_req_ambiguous_char_width
394
- @@ambiguous_width = 2 if Reline::IOGate == Reline::GeneralIO or STDOUT.is_a?(File)
395
- return if @@ambiguous_width
396
- Reline::IOGate.move_cursor_column(0)
397
- print "\u{25bd}"
398
- @@ambiguous_width = Reline::IOGate.cursor_pos.x
399
- Reline::IOGate.move_cursor_column(0)
400
- Reline::IOGate.erase_after_cursor
331
+ extend Forwardable
332
+ extend SingleForwardable
333
+
334
+ #--------------------------------------------------------
335
+ # Documented API
336
+ #--------------------------------------------------------
337
+
338
+ (Core::ATTR_READER_NAMES + Core::ATTR_ACCESSOR_NAMES).each { |name|
339
+ def_single_delegators :core, "#{name}", "#{name}="
340
+ }
341
+ def_single_delegators :core, :input=, :output=
342
+ def_single_delegators :core, :vi_editing_mode, :emacs_editing_mode
343
+ def_single_delegators :core, :readline
344
+ def_instance_delegators self, :readline
345
+
346
+
347
+ #--------------------------------------------------------
348
+ # Undocumented API
349
+ #--------------------------------------------------------
350
+
351
+ # Testable in original
352
+ def_single_delegators :core, :get_screen_size
353
+ def_single_delegators :line_editor, :eof?
354
+ def_instance_delegators self, :eof?
355
+ def_single_delegators :line_editor, :delete_text
356
+ def_single_delegator :line_editor, :line, :line_buffer
357
+ def_single_delegator :line_editor, :byte_pointer, :point
358
+ def_single_delegator :line_editor, :byte_pointer=, :point=
359
+
360
+ def self.insert_text(*args, &block)
361
+ line_editor.insert_text(*args, &block)
362
+ self
363
+ end
364
+
365
+ # Untestable in original
366
+ def_single_delegator :line_editor, :rerender, :redisplay
367
+ def_single_delegators :core, :vi_editing_mode?, :emacs_editing_mode?
368
+ def_single_delegators :core, :ambiguous_width
369
+
370
+ def_single_delegators :core, :readmultiline
371
+ def_instance_delegators self, :readmultiline
372
+
373
+ def self.core
374
+ @core ||= Core.new { |core|
375
+ core.config = Reline::Config.new
376
+ core.key_stroke = Reline::KeyStroke.new(core.config)
377
+ core.line_editor = Reline::LineEditor.new(core.config)
378
+
379
+ core.basic_word_break_characters = " \t\n`><=;|&{("
380
+ core.completer_word_break_characters = " \t\n`><=;|&{("
381
+ core.basic_quote_characters = '"\''
382
+ core.completer_quote_characters = '"\''
383
+ core.filename_quote_characters = ""
384
+ core.special_prefixes = ""
385
+ }
401
386
  end
402
387
 
403
- def self.ambiguous_width
404
- @@ambiguous_width
388
+ def self.line_editor
389
+ core.line_editor
405
390
  end
391
+
392
+ HISTORY = History.new(core.config)
406
393
  end
407
394
 
408
- if Reline::IS_WINDOWS
395
+ if Reline::Core::IS_WINDOWS
409
396
  require 'reline/windows'
410
397
  Reline::IOGate = Reline::Windows
411
398
  else
@@ -106,6 +106,11 @@ class Reline::ANSI
106
106
  print "\e[1;1H"
107
107
  end
108
108
 
109
+ @@old_winch_handler = nil
110
+ def self.set_winch_handler(&handler)
111
+ @@old_winch_handler = Signal.trap('WINCH', &handler)
112
+ end
113
+
109
114
  def self.prep
110
115
  int_handle = Signal.trap('INT', 'IGNORE')
111
116
  otio = `stty -g`.chomp
@@ -123,5 +128,6 @@ class Reline::ANSI
123
128
  int_handle = Signal.trap('INT', 'IGNORE')
124
129
  `stty #{otio}`
125
130
  Signal.trap('INT', int_handle)
131
+ Signal.trap('WINCH', @@old_winch_handler) if @@old_winch_handler
126
132
  end
127
133
  end
@@ -126,20 +126,19 @@ class Reline::Config
126
126
  no += 1
127
127
 
128
128
  line = line.chomp.lstrip
129
- if line[0, 1] == '$'
129
+ if line.start_with?('$')
130
130
  handle_directive(line[1..-1], file, no)
131
131
  next
132
132
  end
133
133
 
134
134
  next if @skip_section
135
135
 
136
- if line.match(/^set +([^ ]+) +([^ ]+)/i)
136
+ case line
137
+ when /^set +([^ ]+) +([^ ]+)/i
137
138
  var, value = $1.downcase, $2.downcase
138
139
  bind_variable(var, value)
139
140
  next
140
- end
141
-
142
- if line =~ /\s*("#{KEYSEQ_PATTERN}+")\s*:\s*(.*)\s*$/
141
+ when /\s*("#{KEYSEQ_PATTERN}+")\s*:\s*(.*)\s*$/o
143
142
  key, func_name = $1, $2
144
143
  keystroke, func = bind_key(key, func_name)
145
144
  next unless keystroke
@@ -56,6 +56,9 @@ class Reline::GeneralIO
56
56
  def self.set_screen_size(rows, columns)
57
57
  end
58
58
 
59
+ def self.set_winch_handler(&handler)
60
+ end
61
+
59
62
  def self.prep
60
63
  end
61
64
 
@@ -69,6 +69,63 @@ class Reline::LineEditor
69
69
  Reline::IOGate.move_cursor_column(0)
70
70
  @old_trap.call if @old_trap.respond_to?(:call) # can also be string, ex: "DEFAULT"
71
71
  }
72
+ Reline::IOGate.set_winch_handler do
73
+ @rest_height = (Reline::IOGate.get_screen_size.first - 1) - Reline::IOGate.cursor_pos.y
74
+ old_screen_size = @screen_size
75
+ @screen_size = Reline::IOGate.get_screen_size
76
+ if old_screen_size.last < @screen_size.last # columns increase
77
+ @rerender_all = true
78
+ rerender
79
+ else
80
+ special_prompt = nil
81
+ if @vi_arg
82
+ prompt = "(arg: #{@vi_arg}) "
83
+ prompt_width = calculate_width(prompt)
84
+ special_prompt = prompt
85
+ elsif @searching_prompt
86
+ prompt = @searching_prompt
87
+ prompt_width = calculate_width(prompt)
88
+ special_prompt = prompt
89
+ else
90
+ prompt = @prompt
91
+ prompt_width = calculate_width(prompt, true)
92
+ end
93
+ back = 0
94
+ new_buffer = whole_lines
95
+ prompt_list = nil
96
+ if @prompt_proc
97
+ prompt_list = @prompt_proc.(new_buffer)
98
+ prompt_list[@line_index] = special_prompt if special_prompt
99
+ prompt = prompt_list[@line_index]
100
+ prompt_width = calculate_width(prompt, true)
101
+ end
102
+ new_buffer.each_with_index do |line, index|
103
+ prompt_width = calculate_width(prompt_list[index], true) if @prompt_proc
104
+ width = prompt_width + calculate_width(line)
105
+ height = calculate_height_by_width(width)
106
+ back += height
107
+ end
108
+ @highest_in_all = back
109
+ @highest_in_this = calculate_height_by_width(prompt_width + @cursor_max)
110
+ @first_line_started_from =
111
+ if @line_index.zero?
112
+ 0
113
+ else
114
+ @buffer_of_lines[0..(@line_index - 1)].inject(0) { |result, line|
115
+ result + calculate_height_by_width(prompt_width + calculate_width(line)) # TODO prompt_list
116
+ }
117
+ end
118
+ if @prompt_proc
119
+ prompt = prompt_list[@line_index]
120
+ prompt_width = calculate_width(prompt, true)
121
+ end
122
+ calculate_nearest_cursor
123
+ @started_from = calculate_height_by_width(prompt_width + @cursor) - 1
124
+ Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
125
+ @highest_in_this = calculate_height_by_width(prompt_width + @cursor_max)
126
+ @rerender_all = true
127
+ end
128
+ end
72
129
  end
73
130
 
74
131
  def finalize
@@ -241,7 +298,7 @@ class Reline::LineEditor
241
298
  @byte_pointer = new_byte_pointer
242
299
  end
243
300
 
244
- def rerender # TODO: support physical and logical lines
301
+ def rerender
245
302
  return if @line.nil?
246
303
  if @menu_info
247
304
  scroll_down(@highest_in_all - @first_line_started_from)
@@ -255,12 +312,15 @@ class Reline::LineEditor
255
312
  move_cursor_up(@highest_in_all - 1 - @first_line_started_from)
256
313
  @menu_info = nil
257
314
  end
315
+ special_prompt = nil
258
316
  if @vi_arg
259
317
  prompt = "(arg: #{@vi_arg}) "
260
318
  prompt_width = calculate_width(prompt)
319
+ special_prompt = prompt
261
320
  elsif @searching_prompt
262
321
  prompt = @searching_prompt
263
322
  prompt_width = calculate_width(prompt)
323
+ special_prompt = prompt
264
324
  else
265
325
  prompt = @prompt
266
326
  prompt_width = calculate_width(prompt, true)
@@ -272,6 +332,7 @@ class Reline::LineEditor
272
332
  prompt_list = nil
273
333
  if @prompt_proc
274
334
  prompt_list = @prompt_proc.(whole_lines)
335
+ prompt_list[@line_index] = special_prompt if special_prompt
275
336
  prompt = prompt_list[@line_index]
276
337
  prompt_width = calculate_width(prompt, true)
277
338
  end
@@ -303,6 +364,7 @@ class Reline::LineEditor
303
364
  prompt_list = nil
304
365
  if @prompt_proc
305
366
  prompt_list = @prompt_proc.(new_lines)
367
+ prompt_list[@line_index] = special_prompt if special_prompt
306
368
  prompt = prompt_list[@line_index]
307
369
  prompt_width = calculate_width(prompt, true)
308
370
  end
@@ -372,6 +434,7 @@ class Reline::LineEditor
372
434
  prompt_list = nil
373
435
  if @prompt_proc
374
436
  prompt_list = @prompt_proc.(new_buffer)
437
+ prompt_list[@line_index] = special_prompt if special_prompt
375
438
  prompt = prompt_list[@line_index]
376
439
  prompt_width = calculate_width(prompt, true)
377
440
  end
@@ -429,6 +492,7 @@ class Reline::LineEditor
429
492
  prompt_list = nil
430
493
  if @prompt_proc
431
494
  prompt_list = @prompt_proc.(whole_lines)
495
+ prompt_list[@line_index] = special_prompt if special_prompt
432
496
  prompt = prompt_list[@line_index]
433
497
  prompt_width = calculate_width(prompt, true)
434
498
  end
@@ -801,17 +865,25 @@ class Reline::LineEditor
801
865
  rest = nil
802
866
  break_pointer = nil
803
867
  quote = nil
868
+ closing_quote = nil
869
+ escaped_quote = nil
804
870
  i = 0
805
871
  while i < @byte_pointer do
806
872
  slice = @line.byteslice(i, @byte_pointer - i)
807
- if quote and slice.start_with?(/(?!\\)#{Regexp.escape(quote)}/) # closing "
873
+ unless slice.valid_encoding?
874
+ i += 1
875
+ next
876
+ end
877
+ if quote and slice.start_with?(closing_quote)
808
878
  quote = nil
809
879
  i += 1
810
- elsif quote and slice.start_with?(/\\#{Regexp.escape(quote)}/) # escaped \"
880
+ elsif quote and slice.start_with?(escaped_quote)
811
881
  # skip
812
882
  i += 2
813
883
  elsif slice =~ quote_characters_regexp # find new "
814
884
  quote = $&
885
+ closing_quote = /(?!\\)#{Regexp.escape(quote)}/
886
+ escaped_quote = /\\#{Regexp.escape(quote)}/
815
887
  i += 1
816
888
  elsif not quote and slice =~ word_break_regexp
817
889
  rest = $'
@@ -839,11 +911,7 @@ class Reline::LineEditor
839
911
  else
840
912
  temp_buffer[@line_index] = @line
841
913
  end
842
- if temp_buffer.any?{ |l| l.chomp != '' }
843
- @confirm_multiline_termination_proc.(temp_buffer.join("\n") + "\n")
844
- else
845
- false
846
- end
914
+ @confirm_multiline_termination_proc.(temp_buffer.join("\n") + "\n")
847
915
  end
848
916
 
849
917
  def insert_text(text)
@@ -1697,8 +1765,8 @@ class Reline::LineEditor
1697
1765
  end
1698
1766
 
1699
1767
  private def ed_delete_next_char(key, arg: 1)
1700
- unless @line.empty?
1701
- byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
1768
+ byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
1769
+ unless @line.empty? || byte_size == 0
1702
1770
  @line, mbchar = byteslice!(@line, @byte_pointer, byte_size)
1703
1771
  copy_for_vi(mbchar)
1704
1772
  width = Reline::Unicode.get_mbchar_width(mbchar)
@@ -1,6 +1,6 @@
1
1
  class Reline::Unicode::EastAsianWidth
2
2
  # This is based on EastAsianWidth.txt
3
- # http://www.unicode.org/Public/10.0.0/ucd/EastAsianWidth.txt
3
+ # http://www.unicode.org/Public/12.1.0/ucd/EastAsianWidth.txt
4
4
 
5
5
  # Fullwidth
6
6
  TYPE_F = /^(
@@ -1,3 +1,3 @@
1
1
  module Reline
2
- VERSION = '0.0.1'
2
+ VERSION = '0.0.2'
3
3
  end
@@ -51,7 +51,9 @@ class Reline::Windows
51
51
 
52
52
  VK_MENU = 0x12
53
53
  VK_SHIFT = 0x10
54
+ STD_INPUT_HANDLE = -10
54
55
  STD_OUTPUT_HANDLE = -11
56
+ WINDOW_BUFFER_SIZE_EVENT = 0x04
55
57
  @@getwch = Win32API.new('msvcrt', '_getwch', [], 'I')
56
58
  @@kbhit = Win32API.new('msvcrt', '_kbhit', [], 'I')
57
59
  @@GetKeyState = Win32API.new('user32', 'GetKeyState', ['L'], 'L')
@@ -61,6 +63,9 @@ class Reline::Windows
61
63
  @@FillConsoleOutputCharacter = Win32API.new('kernel32', 'FillConsoleOutputCharacter', ['L', 'L', 'L', 'L', 'P'], 'L')
62
64
  @@ScrollConsoleScreenBuffer = Win32API.new('kernel32', 'ScrollConsoleScreenBuffer', ['L', 'P', 'P', 'L', 'P'], 'L')
63
65
  @@hConsoleHandle = @@GetStdHandle.call(STD_OUTPUT_HANDLE)
66
+ @@hConsoleInputHandle = @@GetStdHandle.call(STD_INPUT_HANDLE)
67
+ @@GetNumberOfConsoleInputEvents = Win32API.new('kernel32', 'GetNumberOfConsoleInputEvents', ['L', 'P'], 'L')
68
+ @@ReadConsoleInput = Win32API.new('kernel32', 'ReadConsoleInput', ['L', 'P', 'L', 'P'], 'L')
64
69
  @@buf = []
65
70
 
66
71
  def self.getwch
@@ -81,6 +86,17 @@ class Reline::Windows
81
86
  end
82
87
 
83
88
  def self.getc
89
+ num_of_events = 0.chr * 8
90
+ while @@GetNumberOfConsoleInputEvents.(@@hConsoleInputHandle, num_of_events) != 0 and num_of_events.unpack('L').first > 0
91
+ input_record = 0.chr * 18
92
+ read_event = 0.chr * 4
93
+ if @@ReadConsoleInput.(@@hConsoleInputHandle, input_record, 1, read_event) != 0
94
+ event = input_record[0, 2].unpack('s*').first
95
+ if event == WINDOW_BUFFER_SIZE_EVENT
96
+ @@winch_handler.()
97
+ end
98
+ end
99
+ end
84
100
  unless @@buf.empty?
85
101
  return @@buf.shift
86
102
  end
@@ -122,16 +138,16 @@ class Reline::Windows
122
138
  end
123
139
 
124
140
  def self.get_screen_size
125
- csbi = 0.chr * 24
141
+ csbi = 0.chr * 22
126
142
  @@GetConsoleScreenBufferInfo.call(@@hConsoleHandle, csbi)
127
143
  csbi[0, 4].unpack('SS').reverse
128
144
  end
129
145
 
130
146
  def self.cursor_pos
131
- csbi = 0.chr * 24
147
+ csbi = 0.chr * 22
132
148
  @@GetConsoleScreenBufferInfo.call(@@hConsoleHandle, csbi)
133
149
  x = csbi[4, 2].unpack('s*').first
134
- y = csbi[6, 4].unpack('s*').first
150
+ y = csbi[6, 2].unpack('s*').first
135
151
  Reline::CursorPos.new(x, y)
136
152
  end
137
153
 
@@ -181,6 +197,10 @@ class Reline::Windows
181
197
  raise NotImplementedError
182
198
  end
183
199
 
200
+ def self.set_winch_handler(&handler)
201
+ @@winch_handler = handler
202
+ end
203
+
184
204
  def self.prep
185
205
  # do nothing
186
206
  nil
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.0.1
4
+ version: 0.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - aycabta
8
8
  autorequire:
9
- bindir: exe
9
+ bindir: bin
10
10
  cert_chain: []
11
- date: 2019-07-14 00:00:00.000000000 Z
11
+ date: 2019-09-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -98,7 +98,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
98
98
  - !ruby/object:Gem::Version
99
99
  version: '0'
100
100
  requirements: []
101
- rubygems_version: 3.0.4
101
+ rubygems_version: 3.0.6
102
102
  signing_key:
103
103
  specification_version: 4
104
104
  summary: Alternative GNU Readline or Editline implementation by pure Ruby.