reline 0.3.5 → 0.6.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: 3654e31f9c5d1aa879ae40dfb28b8de68f2e443fac87856f2387a22d61a5b4e7
4
- data.tar.gz: 0fdccab5b7cfd91274e3e4544dcca69a442176cc85e1eba382da3ffd02f9ac30
3
+ metadata.gz: ec7aa526103ceb5eff3fa7dbbfff8c33f296f9cfb28ec4cc925b28e649dc18c8
4
+ data.tar.gz: 5c86a58b7769f390a81e9f6ca19a1e64eefad4ad05091544e5a01ef752b21983
5
5
  SHA512:
6
- metadata.gz: 9277033707e4a1531e8590ae6a76019c21c60da22f69ea333dcc5c3d3610499303ed7892308b6ae3b495237738344baac7a21c547a19ff77f53fecae29be9a14
7
- data.tar.gz: 5f3f225d7b1ee1f0ab067d8a897366a3d3b922dcedb74ae3ec980e387ad3b520d8ab808f70d89e109d06f7e5017ab6406d30e25ed2b6678b3fc8d6c4d5cf6930
6
+ metadata.gz: f4e586ed0f631e402d0b4b58fb05f0eec016f1290624021e957de1ced06c15e8638dac34e91c72d4b53a83e2c6b5eb5aadec2b749271d47201773dd220c63827
7
+ data.tar.gz: 6dcd53d5a30722bb4884f79eb9c70f49ab267b5e285b231237d339a52efdaa597377d308a264ed2cef23802b27aabb38d5ccbfd0c2dce40d631e15a6da5ede68
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,36 @@ 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
+
65
+ ## Contributing
66
+
67
+ Bug reports and pull requests are welcome on GitHub at https://github.com/ruby/reline.
68
+
69
+ ### Run tests
70
+
71
+ > **Note**
72
+ > Please make sure you have `libvterm` installed for `yamatanooroti` tests (integration tests).
73
+
74
+ If you use Homebrew, you can install it by running `brew install libvterm`.
75
+
76
+ ```bash
77
+ WITH_VTERM=1 bundle install
78
+ WITH_VTERM=1 bundle exec rake test test_yamatanooroti
79
+ ```
80
+
81
+ ## Releasing
82
+
83
+ ```bash
84
+ rake release
85
+ gh release create vX.Y.Z --generate-notes
86
+ ```
87
+
58
88
  ## License
59
89
 
60
90
  The gem is available as open source under the terms of the [Ruby License](https://www.ruby-lang.org/en/about/license.txt).
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,73 +174,82 @@ 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|
180
+ # Even after encoding conversion, we need to verify the encoding is valid
181
+ # as some invalid byte sequences might pass through the conversion.
182
+ unless line.valid_encoding?
183
+ raise InvalidInputrc, "#{file}:#{no + 1}: can't be converted to the locale #{Reline.encoding_system_needs.name}"
184
+ end
198
185
  next if line.match(/\A\s*#/)
199
186
 
200
187
  no += 1
201
188
 
202
189
  line = line.chomp.lstrip
203
190
  if line.start_with?('$')
204
- handle_directive(line[1..-1], file, no)
191
+ handle_directive(line[1..-1], file, no, if_stack)
205
192
  next
206
193
  end
207
194
 
208
- next if @skip_section
195
+ next if if_stack.any? { |_no, skip| skip }
209
196
 
210
197
  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
198
+ when /^set +([^ ]+) +(.+)/i
199
+ # value ignores everything after a space, raw_value does not.
200
+ var, value, raw_value = $1.downcase, $2.partition(' ').first, $2
201
+ bind_variable(var, value, raw_value)
202
+ when /^\s*(?:M|Meta)-([a-zA-Z_])\s*:\s*(.*)\s*$/o
203
+ bind_key("\"\\M-#$1\"", $2)
204
+ when /^\s*(?:C|Control)-([a-zA-Z_])\s*:\s*(.*)\s*$/o
205
+ bind_key("\"\\C-#$1\"", $2)
206
+ when /^\s*(?:(?:C|Control)-(?:M|Meta)|(?:M|Meta)-(?:C|Control))-([a-zA-Z_])\s*:\s*(.*)\s*$/o
207
+ bind_key("\"\\M-\\C-#$1\"", $2)
208
+ when /^\s*("#{KEYSEQ_PATTERN}+")\s*:\s*(.*)\s*$/o
209
+ bind_key($1, $2)
220
210
  end
221
211
  end
222
- unless @if_stack.empty?
223
- raise InvalidInputrc, "#{file}:#{@if_stack.last[1]}: unclosed if"
212
+ unless if_stack.empty?
213
+ raise InvalidInputrc, "#{file}:#{if_stack.last[0]}: unclosed if"
224
214
  end
225
- ensure
226
- @skip_section, @if_stack = conditions
227
215
  end
228
216
 
229
- def handle_directive(directive, file, no)
217
+ def handle_directive(directive, file, no, if_stack)
230
218
  directive, args = directive.split(' ')
231
219
  case directive
232
220
  when 'if'
233
221
  condition = false
234
222
  case args
235
- when 'mode'
223
+ when /^mode=(vi|emacs)$/i
224
+ mode = $1.downcase
225
+ # NOTE: mode=vi means vi-insert mode
226
+ mode = 'vi_insert' if mode == 'vi'
227
+ if @editing_mode_label == mode.to_sym
228
+ condition = true
229
+ end
236
230
  when 'term'
237
231
  when 'version'
238
232
  else # application name
239
233
  condition = true if args == 'Ruby'
240
234
  condition = true if args == 'Reline'
241
235
  end
242
- @if_stack << [file, no, @skip_section]
243
- @skip_section = !condition
236
+ if_stack << [no, !condition]
244
237
  when 'else'
245
- if @if_stack.empty?
238
+ if if_stack.empty?
246
239
  raise InvalidInputrc, "#{file}:#{no}: unmatched else"
247
240
  end
248
- @skip_section = !@skip_section
241
+ if_stack.last[1] = !if_stack.last[1]
249
242
  when 'endif'
250
- if @if_stack.empty?
243
+ if if_stack.empty?
251
244
  raise InvalidInputrc, "#{file}:#{no}: unmatched endif"
252
245
  end
253
- @skip_section = @if_stack.pop
246
+ if_stack.pop
254
247
  when 'include'
255
- read(args)
248
+ read(File.expand_path(args))
256
249
  end
257
250
  end
258
251
 
259
- def bind_variable(name, value)
252
+ def bind_variable(name, value, raw_value)
260
253
  case name
261
254
  when 'history-size'
262
255
  begin
@@ -264,24 +257,8 @@ class Reline::Config
264
257
  rescue ArgumentError
265
258
  @history_size = 500
266
259
  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
260
  when 'isearch-terminators'
284
- @isearch_terminators = retrieve_string(value)
261
+ @isearch_terminators = retrieve_string(raw_value)
285
262
  when 'editing-mode'
286
263
  case value
287
264
  when 'emacs'
@@ -323,11 +300,11 @@ class Reline::Config
323
300
  @show_mode_in_prompt = false
324
301
  end
325
302
  when 'vi-cmd-mode-string'
326
- @vi_cmd_mode_string = retrieve_string(value)
303
+ @vi_cmd_mode_string = retrieve_string(raw_value)
327
304
  when 'vi-ins-mode-string'
328
- @vi_ins_mode_string = retrieve_string(value)
305
+ @vi_ins_mode_string = retrieve_string(raw_value)
329
306
  when 'emacs-mode-string'
330
- @emacs_mode_string = retrieve_string(value)
307
+ @emacs_mode_string = retrieve_string(raw_value)
331
308
  when *VARIABLE_NAMES then
332
309
  variable_name = :"@#{name.tr(?-, ?_)}"
333
310
  instance_variable_set(variable_name, value.nil? || value == '1' || value == 'on')
@@ -339,7 +316,12 @@ class Reline::Config
339
316
  parse_keyseq(str).map { |c| c.chr(Reline.encoding_system_needs) }.join
340
317
  end
341
318
 
342
- def bind_key(key, func_name)
319
+ def bind_key(key, value)
320
+ keystroke, func = parse_key_binding(key, value)
321
+ @additional_key_bindings[@keymap_label].add(@keymap_prefix + keystroke, func) if keystroke
322
+ end
323
+
324
+ def parse_key_binding(key, func_name)
343
325
  if key =~ /\A"(.*)"\z/
344
326
  keyseq = parse_keyseq($1)
345
327
  else
@@ -348,27 +330,19 @@ class Reline::Config
348
330
  if func_name =~ /"(.*)"/
349
331
  func = parse_keyseq($1)
350
332
  else
351
- func = func_name.tr(?-, ?_).to_sym # It must be macro.
333
+ func = func_name.split.first.tr(?-, ?_).to_sym # It must be macro.
352
334
  end
353
335
  [keyseq, func]
354
336
  end
355
337
 
356
338
  def key_notation_to_code(notation)
357
339
  case notation
340
+ when /(?:\\(?:C|Control)-\\(?:M|Meta)|\\(?:M|Meta)-\\(?:C|Control))-([A-Za-z_])/
341
+ [?\e.ord, $1.ord % 32]
358
342
  when /\\(?:C|Control)-([A-Za-z_])/
359
- (1 + $1.downcase.ord - ?a.ord)
343
+ ($1.upcase.ord % 32)
360
344
  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
345
+ [?\e.ord, $1.ord]
372
346
  when /\\(\d{1,3})/ then $1.to_i(8) # octal
373
347
  when /\\x(\h{1,2})/ then $1.to_i(16) # hexadecimal
374
348
  when "\\e" then ?\e.ord
@@ -388,11 +362,14 @@ class Reline::Config
388
362
  end
389
363
 
390
364
  def parse_keyseq(str)
391
- ret = []
392
- str.scan(KEYSEQ_PATTERN) do
393
- ret << key_notation_to_code($&)
365
+ str.scan(KEYSEQ_PATTERN).flat_map do |notation|
366
+ key_notation_to_code(notation)
394
367
  end
395
- ret
368
+ end
369
+
370
+ def reload
371
+ reset_variables
372
+ read
396
373
  end
397
374
 
398
375
  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?