reline 0.3.2 → 0.5.9

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: 8198104fe3ad27c73acaaffc9c8b1c8b9a5c50069a26a71cd940dd2b5dc13afc
4
- data.tar.gz: 171ff8cdf2ae4aebaf4cfb6cea131e2d90e5eb56a2c09dbe19acfa8765522ffc
3
+ metadata.gz: 28ee53a9963a33e9eb1159bea507695d94cc4f67b05ce005175808b7ec2d5175
4
+ data.tar.gz: c165de2edcc223bc4e3e149208fe29994063445ea0cf02b93658010a245df5e4
5
5
  SHA512:
6
- metadata.gz: e3411f0086a0445d2d805fc46528a86b947c58b8e61a664dc08b437596702ef1f360cd7e85a1dab64bf159f6f39691b9d5110f951bd5376f4fce0439e9557532
7
- data.tar.gz: 968ac4a376ba350590fd37d05525a5a2e5e26c44e159b5fd9b0c7400e260850e91088357c007651373d44b7b7d37c699852014d07258428be86081581baf0229
6
+ metadata.gz: af0f93e54d3a414c63dfb369746f00e8fa9cac609c646c0a48ad5f87275e7b6f5c88c47d4c30218075d3774fd41ba7978f49a0ffbfa070ee42921ba0e500c06e
7
+ data.tar.gz: 5b0a45b7ae2cfb0e23c9d3b4a863e381f4d9ba5d6298f864c261e408cb5c366031d6137d623d0d46e3729b7bbe086fc5da5c5e636163bce230ae0f297441e016
data/README.md CHANGED
@@ -1,4 +1,5 @@
1
- [![Build Status](https://travis-ci.com/ruby/reline.svg?branch=master)](https://travis-ci.com/ruby/reline)
1
+ [![Gem Version](https://badge.fury.io/rb/reline.svg)](https://badge.fury.io/rb/reline)
2
+ [![CI](https://github.com/ruby/reline/actions/workflows/reline.yml/badge.svg)](https://github.com/ruby/reline/actions/workflows/reline.yml)
2
3
 
3
4
  This is a screen capture of *IRB improved by Reline*.
4
5
 
@@ -14,7 +15,7 @@ Reline is compatible with the API of Ruby's stdlib 'readline', GNU Readline and
14
15
 
15
16
  It's compatible with the readline standard library.
16
17
 
17
- 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).
18
19
 
19
20
  ### Multi-line editing mode
20
21
 
@@ -54,6 +55,36 @@ end
54
55
 
55
56
  See also: [test/reline/yamatanooroti/multiline_repl](https://github.com/ruby/reline/blob/master/test/reline/yamatanooroti/multiline_repl)
56
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
+
57
88
  ## License
58
89
 
59
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
@@ -8,31 +8,12 @@ class Reline::Config
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,20 @@ 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
+ @additional_key_bindings = { # from inputrc
33
+ emacs: Reline::KeyActor::Base.new,
34
+ vi_insert: Reline::KeyActor::Base.new,
35
+ vi_command: Reline::KeyActor::Base.new
36
+ }
37
+ @oneshot_key_bindings = Reline::KeyActor::Base.new
58
38
  @editing_mode_label = :emacs
59
39
  @keymap_label = :emacs
60
40
  @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
41
+ @default_key_bindings = {
42
+ emacs: Reline::KeyActor::Base.new(Reline::KeyActor::EMACS_MAPPING),
43
+ vi_insert: Reline::KeyActor::Base.new(Reline::KeyActor::VI_INSERT_MAPPING),
44
+ vi_command: Reline::KeyActor::Base.new(Reline::KeyActor::VI_COMMAND_MAPPING)
45
+ }
65
46
  @vi_cmd_mode_string = '(cmd)'
66
47
  @vi_ins_mode_string = '(ins)'
67
48
  @emacs_mode_string = '@'
@@ -71,21 +52,19 @@ class Reline::Config
71
52
  @test_mode = false
72
53
  @autocompletion = false
73
54
  @convert_meta = true if seven_bit_encoding?(Reline::IOGate.encoding)
55
+ @loaded = false
56
+ @enable_bracketed_paste = true
74
57
  end
75
58
 
76
59
  def reset
77
60
  if editing_mode_is?(:vi_command)
78
61
  @editing_mode_label = :vi_insert
79
62
  end
80
- @additional_key_bindings.keys.each do |key|
81
- @additional_key_bindings[key].clear
82
- end
83
63
  @oneshot_key_bindings.clear
84
- reset_default_key_bindings
85
64
  end
86
65
 
87
66
  def editing_mode
88
- @key_actors[@editing_mode_label]
67
+ @default_key_bindings[@editing_mode_label]
89
68
  end
90
69
 
91
70
  def editing_mode=(val)
@@ -93,11 +72,15 @@ class Reline::Config
93
72
  end
94
73
 
95
74
  def editing_mode_is?(*val)
96
- (val.respond_to?(:any?) ? val : [val]).any?(@editing_mode_label)
75
+ val.any?(@editing_mode_label)
97
76
  end
98
77
 
99
78
  def keymap
100
- @key_actors[@keymap_label]
79
+ @default_key_bindings[@keymap_label]
80
+ end
81
+
82
+ def loaded?
83
+ @loaded
101
84
  end
102
85
 
103
86
  def inputrc_path
@@ -131,6 +114,7 @@ class Reline::Config
131
114
  end
132
115
 
133
116
  def read(file = nil)
117
+ @loaded = true
134
118
  file ||= default_inputrc_path
135
119
  begin
136
120
  if file.respond_to?(:readlines)
@@ -151,14 +135,14 @@ class Reline::Config
151
135
 
152
136
  def key_bindings
153
137
  # 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
138
+ Reline::KeyActor::Composite.new([@oneshot_key_bindings, @additional_key_bindings[@editing_mode_label], @default_key_bindings[@editing_mode_label]])
158
139
  end
159
140
 
160
141
  def add_oneshot_key_binding(keystroke, target)
161
- @oneshot_key_bindings[keystroke] = target
142
+ # IRB sets invalid keystroke [Reline::Key]. We should ignore it.
143
+ return unless keystroke.all? { |c| c.is_a?(Integer) }
144
+
145
+ @oneshot_key_bindings.add(keystroke, target)
162
146
  end
163
147
 
164
148
  def reset_oneshot_key_bindings
@@ -166,17 +150,11 @@ class Reline::Config
166
150
  end
167
151
 
168
152
  def add_default_key_binding_by_keymap(keymap, keystroke, target)
169
- @key_actors[keymap].default_key_bindings[keystroke] = target
153
+ @default_key_bindings[keymap].add(keystroke, target)
170
154
  end
171
155
 
172
156
  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
157
+ add_default_key_binding_by_keymap(@keymap_label, keystroke, target)
180
158
  end
181
159
 
182
160
  def read_lines(lines, file = nil)
@@ -190,9 +168,7 @@ class Reline::Config
190
168
  end
191
169
  end
192
170
  end
193
- conditions = [@skip_section, @if_stack]
194
- @skip_section = nil
195
- @if_stack = []
171
+ if_stack = []
196
172
 
197
173
  lines.each_with_index do |line, no|
198
174
  next if line.match(/\A\s*#/)
@@ -201,62 +177,67 @@ class Reline::Config
201
177
 
202
178
  line = line.chomp.lstrip
203
179
  if line.start_with?('$')
204
- handle_directive(line[1..-1], file, no)
180
+ handle_directive(line[1..-1], file, no, if_stack)
205
181
  next
206
182
  end
207
183
 
208
- next if @skip_section
184
+ next if if_stack.any? { |_no, skip| skip }
209
185
 
210
186
  case line
211
- when /^set +([^ ]+) +([^ ]+)/i
212
- var, value = $1.downcase, $2
213
- bind_variable(var, value)
187
+ when /^set +([^ ]+) +(.+)/i
188
+ # value ignores everything after a space, raw_value does not.
189
+ var, value, raw_value = $1.downcase, $2.partition(' ').first, $2
190
+ bind_variable(var, value, raw_value)
214
191
  next
215
192
  when /\s*("#{KEYSEQ_PATTERN}+")\s*:\s*(.*)\s*$/o
216
193
  key, func_name = $1, $2
194
+ func_name = func_name.split.first
217
195
  keystroke, func = bind_key(key, func_name)
218
196
  next unless keystroke
219
- @additional_key_bindings[@keymap_label][@keymap_prefix + keystroke] = func
197
+ @additional_key_bindings[@keymap_label].add(@keymap_prefix + keystroke, func)
220
198
  end
221
199
  end
222
- unless @if_stack.empty?
223
- raise InvalidInputrc, "#{file}:#{@if_stack.last[1]}: unclosed if"
200
+ unless if_stack.empty?
201
+ raise InvalidInputrc, "#{file}:#{if_stack.last[0]}: unclosed if"
224
202
  end
225
- ensure
226
- @skip_section, @if_stack = conditions
227
203
  end
228
204
 
229
- def handle_directive(directive, file, no)
205
+ def handle_directive(directive, file, no, if_stack)
230
206
  directive, args = directive.split(' ')
231
207
  case directive
232
208
  when 'if'
233
209
  condition = false
234
210
  case args
235
- when 'mode'
211
+ when /^mode=(vi|emacs)$/i
212
+ mode = $1.downcase
213
+ # NOTE: mode=vi means vi-insert mode
214
+ mode = 'vi_insert' if mode == 'vi'
215
+ if @editing_mode_label == mode.to_sym
216
+ condition = true
217
+ end
236
218
  when 'term'
237
219
  when 'version'
238
220
  else # application name
239
221
  condition = true if args == 'Ruby'
240
222
  condition = true if args == 'Reline'
241
223
  end
242
- @if_stack << [file, no, @skip_section]
243
- @skip_section = !condition
224
+ if_stack << [no, !condition]
244
225
  when 'else'
245
- if @if_stack.empty?
226
+ if if_stack.empty?
246
227
  raise InvalidInputrc, "#{file}:#{no}: unmatched else"
247
228
  end
248
- @skip_section = !@skip_section
229
+ if_stack.last[1] = !if_stack.last[1]
249
230
  when 'endif'
250
- if @if_stack.empty?
231
+ if if_stack.empty?
251
232
  raise InvalidInputrc, "#{file}:#{no}: unmatched endif"
252
233
  end
253
- @skip_section = @if_stack.pop
234
+ if_stack.pop
254
235
  when 'include'
255
- read(args)
236
+ read(File.expand_path(args))
256
237
  end
257
238
  end
258
239
 
259
- def bind_variable(name, value)
240
+ def bind_variable(name, value, raw_value)
260
241
  case name
261
242
  when 'history-size'
262
243
  begin
@@ -281,7 +262,7 @@ class Reline::Config
281
262
  when 'completion-query-items'
282
263
  @completion_query_items = value.to_i
283
264
  when 'isearch-terminators'
284
- @isearch_terminators = retrieve_string(value)
265
+ @isearch_terminators = retrieve_string(raw_value)
285
266
  when 'editing-mode'
286
267
  case value
287
268
  when 'emacs'
@@ -323,11 +304,11 @@ class Reline::Config
323
304
  @show_mode_in_prompt = false
324
305
  end
325
306
  when 'vi-cmd-mode-string'
326
- @vi_cmd_mode_string = retrieve_string(value)
307
+ @vi_cmd_mode_string = retrieve_string(raw_value)
327
308
  when 'vi-ins-mode-string'
328
- @vi_ins_mode_string = retrieve_string(value)
309
+ @vi_ins_mode_string = retrieve_string(raw_value)
329
310
  when 'emacs-mode-string'
330
- @emacs_mode_string = retrieve_string(value)
311
+ @emacs_mode_string = retrieve_string(raw_value)
331
312
  when *VARIABLE_NAMES then
332
313
  variable_name = :"@#{name.tr(?-, ?_)}"
333
314
  instance_variable_set(variable_name, value.nil? || value == '1' || value == 'on')
@@ -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, hight 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
@@ -62,7 +62,7 @@ class Reline::History < Array
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?