reline 0.3.2 → 0.5.9

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 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?