reline 0.3.9 → 0.6.1

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: 7f903d3b8f902335117d484007dcfbef5e8846061a8eb3c8820f9975c4c85ea5
4
- data.tar.gz: 2ccb2d104c5c52b4e4f8ab1dd8a48ceb738fa03826ab62a7b38483d0f4f1288a
3
+ metadata.gz: 2fb74f487afccdfb3b0530a043cfb7090f2a4860f2fdd168bc94112818b5f57d
4
+ data.tar.gz: 96b74e0f611f24022f204e9551d8692ef017c52a197ae169fbfdc1074a130857
5
5
  SHA512:
6
- metadata.gz: d8edd5b7d31464fbc150518f94d45d84466837fe78374dbc127f2226005eb73f2c238f274d685ed42c97db56e906fb90b5e6369dd03dbaeac4b6f2019f36cd68
7
- data.tar.gz: 317225a1374e59f3dbb654b67a732fd3daf669fd13cab0673d73e0b952cc8d6fad554c4318366479d162890d0c538ca09cc902a1ad2fad0e42e4684b54f81c3e
6
+ metadata.gz: ffdcec13818f2445bd9f639e75b36405c76a439378335fa7c05fad90a26f06965e54cd9dce1b8e1411e16df9ffc80c3c0ac356320654af2ebd40a33fd28cb247
7
+ data.tar.gz: 24be10266aebce2d07f1519010f6ce0e6b92b816b17e526ede204943880b54388aa5ebf5da086bde0212b5e1f837936e4972c09a57a2d6c94f7c9a41412966d9
data/README.md CHANGED
@@ -15,7 +15,7 @@ Reline is compatible with the API of Ruby's stdlib 'readline', GNU Readline and
15
15
 
16
16
  It's compatible with the readline standard library.
17
17
 
18
- See [the document of readline stdlib](https://ruby-doc.org/stdlib/libdoc/readline/rdoc/Readline.html) or [bin/example](https://github.com/ruby/reline/blob/master/bin/example).
18
+ See [the document of readline stdlib](https://ruby-doc.org/stdlib/exts/readline/Readline.html) or [bin/example](https://github.com/ruby/reline/blob/master/bin/example).
19
19
 
20
20
  ### Multi-line editing mode
21
21
 
@@ -55,6 +55,13 @@ end
55
55
 
56
56
  See also: [test/reline/yamatanooroti/multiline_repl](https://github.com/ruby/reline/blob/master/test/reline/yamatanooroti/multiline_repl)
57
57
 
58
+ ## Documentation
59
+
60
+ ### Reline::Face
61
+
62
+ You can modify the text color and text decorations in your terminal emulator.
63
+ See [doc/reline/face.md](./doc/reline/face.md)
64
+
58
65
  ## Contributing
59
66
 
60
67
  Bug reports and pull requests are welcome on GitHub at https://github.com/ruby/reline.
data/lib/reline/config.rb CHANGED
@@ -1,38 +1,19 @@
1
1
  class Reline::Config
2
2
  attr_reader :test_mode
3
3
 
4
- KEYSEQ_PATTERN = /\\(?:C|Control)-[A-Za-z_]|\\(?:M|Meta)-[0-9A-Za-z_]|\\(?:C|Control)-(?:M|Meta)-[A-Za-z_]|\\(?:M|Meta)-(?:C|Control)-[A-Za-z_]|\\e|\\[\\\"\'abdfnrtv]|\\\d{1,3}|\\x\h{1,2}|./
4
+ KEYSEQ_PATTERN = /\\(?:C|Control)-[A-Za-z_]|\\(?:M|Meta)-[0-9A-Za-z_]|\\(?:C|Control)-\\(?:M|Meta)-[A-Za-z_]|\\(?:M|Meta)-\\(?:C|Control)-[A-Za-z_]|\\e|\\[\\\"\'abdfnrtv]|\\\d{1,3}|\\x\h{1,2}|./
5
5
 
6
6
  class InvalidInputrc < RuntimeError
7
7
  attr_accessor :file, :lineno
8
8
  end
9
9
 
10
10
  VARIABLE_NAMES = %w{
11
- bind-tty-special-chars
12
- blink-matching-paren
13
- byte-oriented
14
11
  completion-ignore-case
15
12
  convert-meta
16
13
  disable-completion
17
- enable-keypad
18
- expand-tilde
19
- history-preserve-point
20
14
  history-size
21
- horizontal-scroll-mode
22
- input-meta
23
15
  keyseq-timeout
24
- mark-directories
25
- mark-modified-lines
26
- mark-symlinked-directories
27
- match-hidden-files
28
- meta-flag
29
- output-meta
30
- page-completions
31
- prefer-visible-bell
32
- print-completions-horizontally
33
16
  show-all-if-ambiguous
34
- show-all-if-unmodified
35
- visible-stats
36
17
  show-mode-in-prompt
37
18
  vi-cmd-mode-string
38
19
  vi-ins-mode-string
@@ -48,20 +29,31 @@ class Reline::Config
48
29
  attr_accessor :autocompletion
49
30
 
50
31
  def initialize
51
- @additional_key_bindings = {} # from inputrc
52
- @additional_key_bindings[:emacs] = {}
53
- @additional_key_bindings[:vi_insert] = {}
54
- @additional_key_bindings[:vi_command] = {}
55
- @oneshot_key_bindings = {}
56
- @skip_section = nil
57
- @if_stack = nil
32
+ reset_variables
33
+ end
34
+
35
+ def reset
36
+ if editing_mode_is?(:vi_command)
37
+ @editing_mode_label = :vi_insert
38
+ end
39
+ @oneshot_key_bindings.clear
40
+ end
41
+
42
+ def reset_variables
43
+ @additional_key_bindings = { # from inputrc
44
+ emacs: Reline::KeyActor::Base.new,
45
+ vi_insert: Reline::KeyActor::Base.new,
46
+ vi_command: Reline::KeyActor::Base.new
47
+ }
48
+ @oneshot_key_bindings = Reline::KeyActor::Base.new
58
49
  @editing_mode_label = :emacs
59
50
  @keymap_label = :emacs
60
51
  @keymap_prefix = []
61
- @key_actors = {}
62
- @key_actors[:emacs] = Reline::KeyActor::Emacs.new
63
- @key_actors[:vi_insert] = Reline::KeyActor::ViInsert.new
64
- @key_actors[:vi_command] = Reline::KeyActor::ViCommand.new
52
+ @default_key_bindings = {
53
+ emacs: Reline::KeyActor::Base.new(Reline::KeyActor::EMACS_MAPPING),
54
+ vi_insert: Reline::KeyActor::Base.new(Reline::KeyActor::VI_INSERT_MAPPING),
55
+ vi_command: Reline::KeyActor::Base.new(Reline::KeyActor::VI_COMMAND_MAPPING)
56
+ }
65
57
  @vi_cmd_mode_string = '(cmd)'
66
58
  @vi_ins_mode_string = '(ins)'
67
59
  @emacs_mode_string = '@'
@@ -70,22 +62,15 @@ class Reline::Config
70
62
  @keyseq_timeout = 500
71
63
  @test_mode = false
72
64
  @autocompletion = false
73
- @convert_meta = true if seven_bit_encoding?(Reline::IOGate.encoding)
74
- end
75
-
76
- def reset
77
- if editing_mode_is?(:vi_command)
78
- @editing_mode_label = :vi_insert
79
- end
80
- @additional_key_bindings.keys.each do |key|
81
- @additional_key_bindings[key].clear
82
- end
83
- @oneshot_key_bindings.clear
84
- reset_default_key_bindings
65
+ @convert_meta = seven_bit_encoding?(Reline::IOGate.encoding)
66
+ @loaded = false
67
+ @enable_bracketed_paste = true
68
+ @show_mode_in_prompt = false
69
+ @default_inputrc_path = nil
85
70
  end
86
71
 
87
72
  def editing_mode
88
- @key_actors[@editing_mode_label]
73
+ @default_key_bindings[@editing_mode_label]
89
74
  end
90
75
 
91
76
  def editing_mode=(val)
@@ -97,7 +82,11 @@ class Reline::Config
97
82
  end
98
83
 
99
84
  def keymap
100
- @key_actors[@keymap_label]
85
+ @default_key_bindings[@keymap_label]
86
+ end
87
+
88
+ def loaded?
89
+ @loaded
101
90
  end
102
91
 
103
92
  def inputrc_path
@@ -131,6 +120,7 @@ class Reline::Config
131
120
  end
132
121
 
133
122
  def read(file = nil)
123
+ @loaded = true
134
124
  file ||= default_inputrc_path
135
125
  begin
136
126
  if file.respond_to?(:readlines)
@@ -151,14 +141,14 @@ class Reline::Config
151
141
 
152
142
  def key_bindings
153
143
  # The key bindings for each editing mode will be overwritten by the user-defined ones.
154
- kb = @key_actors[@editing_mode_label].default_key_bindings.dup
155
- kb.merge!(@additional_key_bindings[@editing_mode_label])
156
- kb.merge!(@oneshot_key_bindings)
157
- kb
144
+ Reline::KeyActor::Composite.new([@oneshot_key_bindings, @additional_key_bindings[@editing_mode_label], @default_key_bindings[@editing_mode_label]])
158
145
  end
159
146
 
160
147
  def add_oneshot_key_binding(keystroke, target)
161
- @oneshot_key_bindings[keystroke] = target
148
+ # IRB sets invalid keystroke [Reline::Key]. We should ignore it.
149
+ return unless keystroke.all? { |c| c.is_a?(Integer) }
150
+
151
+ @oneshot_key_bindings.add(keystroke, target)
162
152
  end
163
153
 
164
154
  def reset_oneshot_key_bindings
@@ -166,17 +156,11 @@ class Reline::Config
166
156
  end
167
157
 
168
158
  def add_default_key_binding_by_keymap(keymap, keystroke, target)
169
- @key_actors[keymap].default_key_bindings[keystroke] = target
159
+ @default_key_bindings[keymap].add(keystroke, target)
170
160
  end
171
161
 
172
162
  def add_default_key_binding(keystroke, target)
173
- @key_actors[@keymap_label].default_key_bindings[keystroke] = target
174
- end
175
-
176
- def reset_default_key_bindings
177
- @key_actors.values.each do |ka|
178
- ka.reset_default_key_bindings
179
- end
163
+ add_default_key_binding_by_keymap(@keymap_label, keystroke, target)
180
164
  end
181
165
 
182
166
  def read_lines(lines, file = nil)
@@ -190,9 +174,7 @@ class Reline::Config
190
174
  end
191
175
  end
192
176
  end
193
- conditions = [@skip_section, @if_stack]
194
- @skip_section = nil
195
- @if_stack = []
177
+ if_stack = []
196
178
 
197
179
  lines.each_with_index do |line, no|
198
180
  next if line.match(/\A\s*#/)
@@ -201,62 +183,68 @@ class Reline::Config
201
183
 
202
184
  line = line.chomp.lstrip
203
185
  if line.start_with?('$')
204
- handle_directive(line[1..-1], file, no)
186
+ handle_directive(line[1..-1], file, no, if_stack)
205
187
  next
206
188
  end
207
189
 
208
- next if @skip_section
190
+ next if if_stack.any? { |_no, skip| skip }
209
191
 
210
192
  case line
211
- when /^set +([^ ]+) +([^ ]+)/i
212
- var, value = $1.downcase, $2
213
- bind_variable(var, value)
214
- next
215
- when /\s*("#{KEYSEQ_PATTERN}+")\s*:\s*(.*)\s*$/o
216
- key, func_name = $1, $2
217
- keystroke, func = bind_key(key, func_name)
218
- next unless keystroke
219
- @additional_key_bindings[@keymap_label][@keymap_prefix + keystroke] = func
193
+ when /^set +([^ ]+) +(.+)/i
194
+ # value ignores everything after a space, raw_value does not.
195
+ var, value, raw_value = $1.downcase, $2.partition(' ').first, $2
196
+ bind_variable(var, value, raw_value)
197
+ when /^\s*(?:M|Meta)-([a-zA-Z_])\s*:\s*(.*)\s*$/o
198
+ bind_key("\"\\M-#$1\"", $2)
199
+ when /^\s*(?:C|Control)-([a-zA-Z_])\s*:\s*(.*)\s*$/o
200
+ bind_key("\"\\C-#$1\"", $2)
201
+ when /^\s*(?:(?:C|Control)-(?:M|Meta)|(?:M|Meta)-(?:C|Control))-([a-zA-Z_])\s*:\s*(.*)\s*$/o
202
+ bind_key("\"\\M-\\C-#$1\"", $2)
203
+ when /^\s*("#{KEYSEQ_PATTERN}+")\s*:\s*(.*)\s*$/o
204
+ bind_key($1, $2)
220
205
  end
221
206
  end
222
- unless @if_stack.empty?
223
- raise InvalidInputrc, "#{file}:#{@if_stack.last[1]}: unclosed if"
207
+ unless if_stack.empty?
208
+ raise InvalidInputrc, "#{file}:#{if_stack.last[0]}: unclosed if"
224
209
  end
225
- ensure
226
- @skip_section, @if_stack = conditions
227
210
  end
228
211
 
229
- def handle_directive(directive, file, no)
212
+ def handle_directive(directive, file, no, if_stack)
230
213
  directive, args = directive.split(' ')
231
214
  case directive
232
215
  when 'if'
233
216
  condition = false
234
217
  case args
235
- when 'mode'
218
+ when /^mode=(vi|emacs)$/i
219
+ mode = $1.downcase
220
+ # NOTE: mode=vi means vi-insert mode
221
+ mode = 'vi_insert' if mode == 'vi'
222
+ if @editing_mode_label == mode.to_sym
223
+ condition = true
224
+ end
236
225
  when 'term'
237
226
  when 'version'
238
227
  else # application name
239
228
  condition = true if args == 'Ruby'
240
229
  condition = true if args == 'Reline'
241
230
  end
242
- @if_stack << [file, no, @skip_section]
243
- @skip_section = !condition
231
+ if_stack << [no, !condition]
244
232
  when 'else'
245
- if @if_stack.empty?
233
+ if if_stack.empty?
246
234
  raise InvalidInputrc, "#{file}:#{no}: unmatched else"
247
235
  end
248
- @skip_section = !@skip_section
236
+ if_stack.last[1] = !if_stack.last[1]
249
237
  when 'endif'
250
- if @if_stack.empty?
238
+ if if_stack.empty?
251
239
  raise InvalidInputrc, "#{file}:#{no}: unmatched endif"
252
240
  end
253
- @skip_section = @if_stack.pop
241
+ if_stack.pop
254
242
  when 'include'
255
243
  read(File.expand_path(args))
256
244
  end
257
245
  end
258
246
 
259
- def bind_variable(name, value)
247
+ def bind_variable(name, value, raw_value)
260
248
  case name
261
249
  when 'history-size'
262
250
  begin
@@ -264,24 +252,8 @@ class Reline::Config
264
252
  rescue ArgumentError
265
253
  @history_size = 500
266
254
  end
267
- when 'bell-style'
268
- @bell_style =
269
- case value
270
- when 'none', 'off'
271
- :none
272
- when 'audible', 'on'
273
- :audible
274
- when 'visible'
275
- :visible
276
- else
277
- :audible
278
- end
279
- when 'comment-begin'
280
- @comment_begin = value.dup
281
- when 'completion-query-items'
282
- @completion_query_items = value.to_i
283
255
  when 'isearch-terminators'
284
- @isearch_terminators = retrieve_string(value)
256
+ @isearch_terminators = retrieve_string(raw_value)
285
257
  when 'editing-mode'
286
258
  case value
287
259
  when 'emacs'
@@ -323,11 +295,11 @@ class Reline::Config
323
295
  @show_mode_in_prompt = false
324
296
  end
325
297
  when 'vi-cmd-mode-string'
326
- @vi_cmd_mode_string = retrieve_string(value)
298
+ @vi_cmd_mode_string = retrieve_string(raw_value)
327
299
  when 'vi-ins-mode-string'
328
- @vi_ins_mode_string = retrieve_string(value)
300
+ @vi_ins_mode_string = retrieve_string(raw_value)
329
301
  when 'emacs-mode-string'
330
- @emacs_mode_string = retrieve_string(value)
302
+ @emacs_mode_string = retrieve_string(raw_value)
331
303
  when *VARIABLE_NAMES then
332
304
  variable_name = :"@#{name.tr(?-, ?_)}"
333
305
  instance_variable_set(variable_name, value.nil? || value == '1' || value == 'on')
@@ -339,7 +311,12 @@ class Reline::Config
339
311
  parse_keyseq(str).map { |c| c.chr(Reline.encoding_system_needs) }.join
340
312
  end
341
313
 
342
- def bind_key(key, func_name)
314
+ def bind_key(key, value)
315
+ keystroke, func = parse_key_binding(key, value)
316
+ @additional_key_bindings[@keymap_label].add(@keymap_prefix + keystroke, func) if keystroke
317
+ end
318
+
319
+ def parse_key_binding(key, func_name)
343
320
  if key =~ /\A"(.*)"\z/
344
321
  keyseq = parse_keyseq($1)
345
322
  else
@@ -348,27 +325,19 @@ class Reline::Config
348
325
  if func_name =~ /"(.*)"/
349
326
  func = parse_keyseq($1)
350
327
  else
351
- func = func_name.tr(?-, ?_).to_sym # It must be macro.
328
+ func = func_name.split.first.tr(?-, ?_).to_sym # It must be macro.
352
329
  end
353
330
  [keyseq, func]
354
331
  end
355
332
 
356
333
  def key_notation_to_code(notation)
357
334
  case notation
335
+ when /(?:\\(?:C|Control)-\\(?:M|Meta)|\\(?:M|Meta)-\\(?:C|Control))-([A-Za-z_])/
336
+ [?\e.ord, $1.ord % 32]
358
337
  when /\\(?:C|Control)-([A-Za-z_])/
359
- (1 + $1.downcase.ord - ?a.ord)
338
+ ($1.upcase.ord % 32)
360
339
  when /\\(?:M|Meta)-([0-9A-Za-z_])/
361
- modified_key = $1
362
- case $1
363
- when /[0-9]/
364
- ?\M-0.bytes.first + (modified_key.ord - ?0.ord)
365
- when /[A-Z]/
366
- ?\M-A.bytes.first + (modified_key.ord - ?A.ord)
367
- when /[a-z]/
368
- ?\M-a.bytes.first + (modified_key.ord - ?a.ord)
369
- end
370
- when /\\(?:C|Control)-(?:M|Meta)-[A-Za-z_]/, /\\(?:M|Meta)-(?:C|Control)-[A-Za-z_]/
371
- # 129 M-^A
340
+ [?\e.ord, $1.ord]
372
341
  when /\\(\d{1,3})/ then $1.to_i(8) # octal
373
342
  when /\\x(\h{1,2})/ then $1.to_i(16) # hexadecimal
374
343
  when "\\e" then ?\e.ord
@@ -388,11 +357,14 @@ class Reline::Config
388
357
  end
389
358
 
390
359
  def parse_keyseq(str)
391
- ret = []
392
- str.scan(KEYSEQ_PATTERN) do
393
- ret << key_notation_to_code($&)
360
+ str.scan(KEYSEQ_PATTERN).flat_map do |notation|
361
+ key_notation_to_code(notation)
394
362
  end
395
- ret
363
+ end
364
+
365
+ def reload
366
+ reset_variables
367
+ read
396
368
  end
397
369
 
398
370
  private def seven_bit_encoding?(encoding)
@@ -0,0 +1,199 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Reline::Face
4
+ SGR_PARAMETERS = {
5
+ foreground: {
6
+ black: 30,
7
+ red: 31,
8
+ green: 32,
9
+ yellow: 33,
10
+ blue: 34,
11
+ magenta: 35,
12
+ cyan: 36,
13
+ white: 37,
14
+ bright_black: 90,
15
+ gray: 90,
16
+ bright_red: 91,
17
+ bright_green: 92,
18
+ bright_yellow: 93,
19
+ bright_blue: 94,
20
+ bright_magenta: 95,
21
+ bright_cyan: 96,
22
+ bright_white: 97
23
+ },
24
+ background: {
25
+ black: 40,
26
+ red: 41,
27
+ green: 42,
28
+ yellow: 43,
29
+ blue: 44,
30
+ magenta: 45,
31
+ cyan: 46,
32
+ white: 47,
33
+ bright_black: 100,
34
+ gray: 100,
35
+ bright_red: 101,
36
+ bright_green: 102,
37
+ bright_yellow: 103,
38
+ bright_blue: 104,
39
+ bright_magenta: 105,
40
+ bright_cyan: 106,
41
+ bright_white: 107,
42
+ },
43
+ style: {
44
+ reset: 0,
45
+ bold: 1,
46
+ faint: 2,
47
+ italicized: 3,
48
+ underlined: 4,
49
+ slowly_blinking: 5,
50
+ blinking: 5,
51
+ rapidly_blinking: 6,
52
+ negative: 7,
53
+ concealed: 8,
54
+ crossed_out: 9
55
+ }
56
+ }.freeze
57
+
58
+ class Config
59
+ ESSENTIAL_DEFINE_NAMES = %i(default enhanced scrollbar).freeze
60
+ RESET_SGR = "\e[0m".freeze
61
+
62
+ def initialize(name, &block)
63
+ @definition = {}
64
+ block.call(self)
65
+ ESSENTIAL_DEFINE_NAMES.each do |name|
66
+ @definition[name] ||= { style: :reset, escape_sequence: RESET_SGR }
67
+ end
68
+ end
69
+
70
+ attr_reader :definition
71
+
72
+ def define(name, **values)
73
+ values[:escape_sequence] = format_to_sgr(values.to_a).freeze
74
+ @definition[name] = values
75
+ end
76
+
77
+ def reconfigure
78
+ @definition.each_value do |values|
79
+ values.delete(:escape_sequence)
80
+ values[:escape_sequence] = format_to_sgr(values.to_a).freeze
81
+ end
82
+ end
83
+
84
+ def [](name)
85
+ @definition.dig(name, :escape_sequence) or raise ArgumentError, "unknown face: #{name}"
86
+ end
87
+
88
+ private
89
+
90
+ def sgr_rgb(key, value)
91
+ return nil unless rgb_expression?(value)
92
+ if Reline::Face.truecolor?
93
+ sgr_rgb_truecolor(key, value)
94
+ else
95
+ sgr_rgb_256color(key, value)
96
+ end
97
+ end
98
+
99
+ def sgr_rgb_truecolor(key, value)
100
+ case key
101
+ when :foreground
102
+ "38;2;"
103
+ when :background
104
+ "48;2;"
105
+ end + value[1, 6].scan(/../).map(&:hex).join(";")
106
+ end
107
+
108
+ def sgr_rgb_256color(key, value)
109
+ # 256 colors are
110
+ # 0..15: standard colors, high intensity colors
111
+ # 16..232: 216 colors (R, G, B each 6 steps)
112
+ # 233..255: grayscale colors (24 steps)
113
+ # This methods converts rgb_expression to 216 colors
114
+ rgb = value[1, 6].scan(/../).map(&:hex)
115
+ # Color steps are [0, 95, 135, 175, 215, 255]
116
+ r, g, b = rgb.map { |v| v <= 95 ? v / 48 : (v - 35) / 40 }
117
+ color = (16 + 36 * r + 6 * g + b)
118
+ case key
119
+ when :foreground
120
+ "38;5;#{color}"
121
+ when :background
122
+ "48;5;#{color}"
123
+ end
124
+ end
125
+
126
+ def format_to_sgr(ordered_values)
127
+ sgr = "\e[" + ordered_values.map do |key_value|
128
+ key, value = key_value
129
+ case key
130
+ when :foreground, :background
131
+ case value
132
+ when Symbol
133
+ SGR_PARAMETERS[key][value]
134
+ when String
135
+ sgr_rgb(key, value)
136
+ end
137
+ when :style
138
+ [ value ].flatten.map do |style_name|
139
+ SGR_PARAMETERS[:style][style_name]
140
+ end.then do |sgr_parameters|
141
+ sgr_parameters.include?(nil) ? nil : sgr_parameters
142
+ end
143
+ end.then do |rendition_expression|
144
+ unless rendition_expression
145
+ raise ArgumentError, "invalid SGR parameter: #{value.inspect}"
146
+ end
147
+ rendition_expression
148
+ end
149
+ end.join(';') + "m"
150
+ sgr == RESET_SGR ? RESET_SGR : RESET_SGR + sgr
151
+ end
152
+
153
+ def rgb_expression?(color)
154
+ color.respond_to?(:match?) and color.match?(/\A#[0-9a-fA-F]{6}\z/)
155
+ end
156
+ end
157
+
158
+ private_constant :SGR_PARAMETERS, :Config
159
+
160
+ def self.truecolor?
161
+ @force_truecolor || %w[truecolor 24bit].include?(ENV['COLORTERM'])
162
+ end
163
+
164
+ def self.force_truecolor
165
+ @force_truecolor = true
166
+ @configs&.each_value(&:reconfigure)
167
+ end
168
+
169
+ def self.[](name)
170
+ @configs[name]
171
+ end
172
+
173
+ def self.config(name, &block)
174
+ @configs ||= {}
175
+ @configs[name] = Config.new(name, &block)
176
+ end
177
+
178
+ def self.configs
179
+ @configs.transform_values(&:definition)
180
+ end
181
+
182
+ def self.load_initial_configs
183
+ config(:default) do |conf|
184
+ conf.define :default, style: :reset
185
+ conf.define :enhanced, style: :reset
186
+ conf.define :scrollbar, style: :reset
187
+ end
188
+ config(:completion_dialog) do |conf|
189
+ conf.define :default, foreground: :bright_white, background: :gray
190
+ conf.define :enhanced, foreground: :black, background: :white
191
+ conf.define :scrollbar, foreground: :white, background: :gray
192
+ end
193
+ end
194
+
195
+ def self.reset_to_initial_configs
196
+ @configs = {}
197
+ load_initial_configs
198
+ end
199
+ end
@@ -19,7 +19,7 @@ class Reline::History < Array
19
19
 
20
20
  def []=(index, val)
21
21
  index = check_index(index)
22
- super(index, String.new(val, encoding: Reline.encoding_system_needs))
22
+ super(index, Reline::Unicode.safe_encode(val, Reline.encoding_system_needs))
23
23
  end
24
24
 
25
25
  def concat(*val)
@@ -45,7 +45,7 @@ class Reline::History < Array
45
45
  end
46
46
  end
47
47
  super(*(val.map{ |v|
48
- String.new(v, encoding: Reline.encoding_system_needs)
48
+ Reline::Unicode.safe_encode(v, Reline.encoding_system_needs)
49
49
  }))
50
50
  end
51
51
 
@@ -56,13 +56,13 @@ class Reline::History < Array
56
56
  if @config.history_size.positive?
57
57
  shift if size + 1 > @config.history_size
58
58
  end
59
- super(String.new(val, encoding: Reline.encoding_system_needs))
59
+ super(Reline::Unicode.safe_encode(val, Reline.encoding_system_needs))
60
60
  end
61
61
 
62
62
  private def check_index(index)
63
63
  index += size if index < 0
64
64
  if index < -2147483648 or 2147483647 < index
65
- raise RangeError.new("integer #{index} too big to convert to `int'")
65
+ raise RangeError.new("integer #{index} too big to convert to 'int'")
66
66
  end
67
67
  # If history_size is negative, history size is unlimited.
68
68
  if @config.history_size.positive?