reline 0.0.1 → 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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.