reline 0.3.9 → 0.5.8

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.
@@ -80,23 +80,11 @@ module Reline::Terminfo
80
80
  def self.setupterm(term, fildes)
81
81
  errret_int = Fiddle::Pointer.malloc(Fiddle::SIZEOF_INT)
82
82
  ret = @setupterm.(term, fildes, errret_int)
83
- errret = errret_int[0, Fiddle::SIZEOF_INT].unpack1('i')
84
83
  case ret
85
84
  when 0 # OK
86
- 0
85
+ @term_supported = true
87
86
  when -1 # ERR
88
- case errret
89
- when 1
90
- raise TerminfoError.new('The terminal is hardcopy, cannot be used for curses applications.')
91
- when 0
92
- raise TerminfoError.new('The terminal could not be found, or that it is a generic type, having too little information for curses applications to run.')
93
- when -1
94
- raise TerminfoError.new('The terminfo database could not be found.')
95
- else # unknown
96
- -1
97
- end
98
- else # unknown
99
- -2
87
+ @term_supported = false
100
88
  end
101
89
  end
102
90
 
@@ -148,9 +136,14 @@ module Reline::Terminfo
148
136
  num
149
137
  end
150
138
 
139
+ # NOTE: This means Fiddle and curses are enabled.
151
140
  def self.enabled?
152
141
  true
153
142
  end
143
+
144
+ def self.term_supported?
145
+ @term_supported
146
+ end
154
147
  end if Reline::Terminfo.curses_dl
155
148
 
156
149
  module Reline::Terminfo
@@ -43,11 +43,13 @@ class Reline::Unicode
43
43
 
44
44
  def self.escape_for_print(str)
45
45
  str.chars.map! { |gr|
46
- escaped = EscapedPairs[gr.ord]
47
- if escaped && gr != -"\n" && gr != -"\t"
48
- escaped
49
- else
46
+ case gr
47
+ when -"\n"
50
48
  gr
49
+ when -"\t"
50
+ -' '
51
+ else
52
+ EscapedPairs[gr.ord] || gr
51
53
  end
52
54
  }.join
53
55
  end
@@ -128,10 +130,10 @@ class Reline::Unicode
128
130
  end
129
131
  end
130
132
 
131
- def self.split_by_width(str, max_width, encoding = str.encoding)
133
+ def self.split_by_width(str, max_width, encoding = str.encoding, offset: 0)
132
134
  lines = [String.new(encoding: encoding)]
133
135
  height = 1
134
- width = 0
136
+ width = offset
135
137
  rest = str.encode(Encoding::UTF_8)
136
138
  in_zero_width = false
137
139
  seq = String.new(encoding: encoding)
@@ -145,7 +147,13 @@ class Reline::Unicode
145
147
  lines.last << NON_PRINTING_END
146
148
  when csi
147
149
  lines.last << csi
148
- seq << csi
150
+ unless in_zero_width
151
+ if csi == -"\e[m" || csi == -"\e[0m"
152
+ seq.clear
153
+ else
154
+ seq << csi
155
+ end
156
+ end
149
157
  when osc
150
158
  lines.last << osc
151
159
  seq << osc
@@ -173,32 +181,78 @@ class Reline::Unicode
173
181
 
174
182
  # Take a chunk of a String cut by width with escape sequences.
175
183
  def self.take_range(str, start_col, max_width)
184
+ take_mbchar_range(str, start_col, max_width).first
185
+ end
186
+
187
+ def self.take_mbchar_range(str, start_col, width, cover_begin: false, cover_end: false, padding: false)
176
188
  chunk = String.new(encoding: str.encoding)
189
+
190
+ end_col = start_col + width
177
191
  total_width = 0
178
192
  rest = str.encode(Encoding::UTF_8)
179
193
  in_zero_width = false
194
+ chunk_start_col = nil
195
+ chunk_end_col = nil
196
+ has_csi = false
180
197
  rest.scan(WIDTH_SCANNER) do |non_printing_start, non_printing_end, csi, osc, gc|
181
198
  case
182
199
  when non_printing_start
183
200
  in_zero_width = true
201
+ chunk << NON_PRINTING_START
184
202
  when non_printing_end
185
203
  in_zero_width = false
204
+ chunk << NON_PRINTING_END
186
205
  when csi
206
+ has_csi = true
187
207
  chunk << csi
188
208
  when osc
189
209
  chunk << osc
190
210
  when gc
191
211
  if in_zero_width
192
212
  chunk << gc
213
+ next
214
+ end
215
+
216
+ mbchar_width = get_mbchar_width(gc)
217
+ prev_width = total_width
218
+ total_width += mbchar_width
219
+
220
+ if (cover_begin || padding ? total_width <= start_col : prev_width < start_col)
221
+ # Current character haven't reached start_col yet
222
+ next
223
+ elsif padding && !cover_begin && prev_width < start_col && start_col < total_width
224
+ # Add preceding padding. This padding might have background color.
225
+ chunk << ' '
226
+ chunk_start_col ||= start_col
227
+ chunk_end_col = total_width
228
+ next
229
+ elsif (cover_end ? prev_width < end_col : total_width <= end_col)
230
+ # Current character is in the range
231
+ chunk << gc
232
+ chunk_start_col ||= prev_width
233
+ chunk_end_col = total_width
234
+ break if total_width >= end_col
193
235
  else
194
- mbchar_width = get_mbchar_width(gc)
195
- total_width += mbchar_width
196
- break if (start_col + max_width) < total_width
197
- chunk << gc if start_col < total_width
236
+ # Current character exceeds end_col
237
+ if padding && end_col < total_width
238
+ # Add succeeding padding. This padding might have background color.
239
+ chunk << ' '
240
+ chunk_start_col ||= prev_width
241
+ chunk_end_col = end_col
242
+ end
243
+ break
198
244
  end
199
245
  end
200
246
  end
201
- chunk
247
+ chunk_start_col ||= start_col
248
+ chunk_end_col ||= start_col
249
+ if padding && chunk_end_col < end_col
250
+ # Append padding. This padding should not include background color.
251
+ chunk << "\e[0m" if has_csi
252
+ chunk << ' ' * (end_col - chunk_end_col)
253
+ chunk_end_col = end_col
254
+ end
255
+ [chunk, chunk_start_col, chunk_end_col - chunk_start_col]
202
256
  end
203
257
 
204
258
  def self.get_next_mbchar_size(line, byte_pointer)
@@ -1,3 +1,3 @@
1
1
  module Reline
2
- VERSION = '0.3.9'
2
+ VERSION = '0.5.8'
3
3
  end
@@ -1,6 +1,8 @@
1
1
  require 'fiddle/import'
2
2
 
3
3
  class Reline::Windows
4
+ RESET_COLOR = "\e[0m"
5
+
4
6
  def self.encoding
5
7
  Encoding::UTF_8
6
8
  end
@@ -85,7 +87,7 @@ class Reline::Windows
85
87
  def call(*args)
86
88
  import = @proto.split("")
87
89
  args.each_with_index do |x, i|
88
- args[i], = [x == 0 ? nil : x].pack("p").unpack(POINTER_TYPE) if import[i] == "S"
90
+ args[i], = [x == 0 ? nil : +x].pack("p").unpack(POINTER_TYPE) if import[i] == "S"
89
91
  args[i], = [x].pack("I").unpack("i") if import[i] == "I"
90
92
  end
91
93
  ret, = @func.call(*args)
@@ -257,7 +259,7 @@ class Reline::Windows
257
259
  def self.check_input_event
258
260
  num_of_events = 0.chr * 8
259
261
  while @@output_buf.empty?
260
- Reline.core.line_editor.resize
262
+ Reline.core.line_editor.handle_signal
261
263
  if @@WaitForSingleObject.(@@hConsoleInputHandle, 100) != 0 # max 0.1 sec
262
264
  # prevent for background consolemode change
263
265
  @@legacy_console = (getconsolemode() & ENABLE_VIRTUAL_TERMINAL_PROCESSING == 0)
data/lib/reline.rb CHANGED
@@ -7,6 +7,7 @@ require 'reline/key_stroke'
7
7
  require 'reline/line_editor'
8
8
  require 'reline/history'
9
9
  require 'reline/terminfo'
10
+ require 'reline/face'
10
11
  require 'rbconfig'
11
12
 
12
13
  module Reline
@@ -36,10 +37,8 @@ module Reline
36
37
  DialogRenderInfo = Struct.new(
37
38
  :pos,
38
39
  :contents,
39
- :bg_color,
40
- :pointer_bg_color,
41
- :fg_color,
42
- :pointer_fg_color,
40
+ :face,
41
+ :bg_color, # For the time being, this line should stay here for the compatibility with IRB.
43
42
  :width,
44
43
  :height,
45
44
  :scrollbar,
@@ -76,10 +75,10 @@ module Reline
76
75
 
77
76
  def initialize
78
77
  self.output = STDOUT
78
+ @mutex = Mutex.new
79
79
  @dialog_proc_list = {}
80
80
  yield self
81
81
  @completion_quote_character = nil
82
- @bracketed_paste_finished = false
83
82
  end
84
83
 
85
84
  def io_gate
@@ -221,32 +220,25 @@ module Reline
221
220
 
222
221
  Reline::DEFAULT_DIALOG_PROC_AUTOCOMPLETE = ->() {
223
222
  # autocomplete
224
- return nil unless config.autocompletion
225
- if just_cursor_moving and completion_journey_data.nil?
226
- # Auto complete starts only when edited
227
- return nil
228
- end
229
- pre, target, post = retrieve_completion_block(true)
230
- if target.nil? or target.empty? or (completion_journey_data&.pointer == -1 and target.size <= 3)
231
- return nil
232
- end
233
- if completion_journey_data and completion_journey_data.list
234
- result = completion_journey_data.list.dup
235
- result.shift
236
- pointer = completion_journey_data.pointer - 1
237
- else
238
- result = call_completion_proc_with_checking_args(pre, target, post)
239
- pointer = nil
240
- end
241
- if result and result.size == 1 and result[0] == target and pointer != 0
242
- result = nil
243
- end
223
+ return unless config.autocompletion
224
+
225
+ journey_data = completion_journey_data
226
+ return unless journey_data
227
+
228
+ target = journey_data.list.first
229
+ completed = journey_data.list[journey_data.pointer]
230
+ result = journey_data.list.drop(1)
231
+ pointer = journey_data.pointer - 1
232
+ return if completed.empty? || (result == [completed] && pointer < 0)
233
+
244
234
  target_width = Reline::Unicode.calculate_width(target)
245
- x = cursor_pos.x - target_width
246
- if x < 0
247
- x = screen_width + x
235
+ completed_width = Reline::Unicode.calculate_width(completed)
236
+ if cursor_pos.x <= completed_width - target_width
237
+ # When target is rendered on the line above cursor position
238
+ x = screen_width - completed_width
248
239
  y = -1
249
240
  else
241
+ x = [cursor_pos.x - completed_width, 0].max
250
242
  y = 0
251
243
  end
252
244
  cursor_pos_to_render = Reline::CursorPos.new(x, y)
@@ -260,21 +252,21 @@ module Reline
260
252
  contents: result,
261
253
  scrollbar: true,
262
254
  height: [15, preferred_dialog_height].min,
263
- bg_color: 46,
264
- pointer_bg_color: 45,
265
- fg_color: 37,
266
- pointer_fg_color: 37
255
+ face: :completion_dialog
267
256
  )
268
257
  }
269
258
  Reline::DEFAULT_DIALOG_CONTEXT = Array.new
270
259
 
271
260
  def readmultiline(prompt = '', add_hist = false, &confirm_multiline_termination)
272
- Reline.update_iogate
273
- io_gate.with_raw_input do
261
+ @mutex.synchronize do
274
262
  unless confirm_multiline_termination
275
263
  raise ArgumentError.new('#readmultiline needs block to confirm multiline termination')
276
264
  end
277
- inner_readline(prompt, add_hist, true, &confirm_multiline_termination)
265
+
266
+ Reline.update_iogate
267
+ io_gate.with_raw_input do
268
+ inner_readline(prompt, add_hist, true, &confirm_multiline_termination)
269
+ end
278
270
 
279
271
  whole_buffer = line_editor.whole_buffer.dup
280
272
  whole_buffer.taint if RUBY_VERSION < '2.7'
@@ -282,23 +274,32 @@ module Reline
282
274
  Reline::HISTORY << whole_buffer
283
275
  end
284
276
 
285
- line_editor.reset_line if line_editor.whole_buffer.nil?
286
- whole_buffer
277
+ if line_editor.eof?
278
+ line_editor.reset_line
279
+ # Return nil if the input is aborted by C-d.
280
+ nil
281
+ else
282
+ whole_buffer
283
+ end
287
284
  end
288
285
  end
289
286
 
290
287
  def readline(prompt = '', add_hist = false)
291
- Reline.update_iogate
292
- inner_readline(prompt, add_hist, false)
288
+ @mutex.synchronize do
289
+ Reline.update_iogate
290
+ io_gate.with_raw_input do
291
+ inner_readline(prompt, add_hist, false)
292
+ end
293
293
 
294
- line = line_editor.line.dup
295
- line.taint if RUBY_VERSION < '2.7'
296
- if add_hist and line and line.chomp("\n").size > 0
297
- Reline::HISTORY << line.chomp("\n")
298
- end
294
+ line = line_editor.line.dup
295
+ line.taint if RUBY_VERSION < '2.7'
296
+ if add_hist and line and line.chomp("\n").size > 0
297
+ Reline::HISTORY << line.chomp("\n")
298
+ end
299
299
 
300
- line_editor.reset_line if line_editor.line.nil?
301
- line
300
+ line_editor.reset_line if line_editor.line.nil?
301
+ line
302
+ end
302
303
  end
303
304
 
304
305
  private def inner_readline(prompt, add_hist, multiline, &confirm_multiline_termination)
@@ -311,6 +312,10 @@ module Reline
311
312
  $stderr.sync = true
312
313
  $stderr.puts "Reline is used by #{Process.pid}"
313
314
  end
315
+ unless config.test_mode or config.loaded?
316
+ config.read
317
+ io_gate.set_default_key_bindings(config)
318
+ end
314
319
  otio = io_gate.prep
315
320
 
316
321
  may_req_ambiguous_char_width
@@ -330,58 +335,47 @@ module Reline
330
335
  line_editor.prompt_proc = prompt_proc
331
336
  line_editor.auto_indent_proc = auto_indent_proc
332
337
  line_editor.dig_perfect_match_proc = dig_perfect_match_proc
333
- line_editor.pre_input_hook = pre_input_hook
334
- @dialog_proc_list.each_pair do |name_sym, d|
335
- line_editor.add_dialog_proc(name_sym, d.dialog_proc, d.context)
336
- end
337
-
338
- unless config.test_mode
339
- config.read
340
- config.reset_default_key_bindings
341
- io_gate.set_default_key_bindings(config)
338
+ pre_input_hook&.call
339
+ unless Reline::IOGate == Reline::GeneralIO
340
+ @dialog_proc_list.each_pair do |name_sym, d|
341
+ line_editor.add_dialog_proc(name_sym, d.dialog_proc, d.context)
342
+ end
342
343
  end
343
344
 
345
+ line_editor.print_nomultiline_prompt(prompt)
346
+ line_editor.update_dialogs
344
347
  line_editor.rerender
345
348
 
346
349
  begin
347
350
  line_editor.set_signal_handlers
348
- prev_pasting_state = false
349
351
  loop do
350
- prev_pasting_state = io_gate.in_pasting?
351
352
  read_io(config.keyseq_timeout) { |inputs|
352
353
  line_editor.set_pasting_state(io_gate.in_pasting?)
353
- inputs.each { |c|
354
- line_editor.input_key(c)
355
- line_editor.rerender
356
- }
357
- if @bracketed_paste_finished
358
- line_editor.rerender_all
359
- @bracketed_paste_finished = false
354
+ inputs.each do |key|
355
+ if key.char == :bracketed_paste_start
356
+ text = io_gate.read_bracketed_paste
357
+ line_editor.insert_pasted_text(text)
358
+ line_editor.scroll_into_view
359
+ else
360
+ line_editor.update(key)
361
+ end
360
362
  end
361
363
  }
362
- if prev_pasting_state == true and not io_gate.in_pasting? and not line_editor.finished?
363
- line_editor.set_pasting_state(false)
364
- prev_pasting_state = false
365
- line_editor.rerender_all
364
+ if line_editor.finished?
365
+ line_editor.render_finished
366
+ break
367
+ else
368
+ line_editor.set_pasting_state(io_gate.in_pasting?)
369
+ line_editor.rerender
366
370
  end
367
- break if line_editor.finished?
368
371
  end
369
372
  io_gate.move_cursor_column(0)
370
373
  rescue Errno::EIO
371
374
  # Maybe the I/O has been closed.
372
- rescue StandardError => e
375
+ ensure
373
376
  line_editor.finalize
374
377
  io_gate.deprep(otio)
375
- raise e
376
- rescue Exception
377
- # Including Interrupt
378
- line_editor.finalize
379
- io_gate.deprep(otio)
380
- raise
381
378
  end
382
-
383
- line_editor.finalize
384
- io_gate.deprep(otio)
385
379
  end
386
380
 
387
381
  # GNU Readline waits for "keyseq-timeout" milliseconds to see if the ESC
@@ -399,7 +393,6 @@ module Reline
399
393
  c = io_gate.getc(Float::INFINITY)
400
394
  if c == -1
401
395
  result = :unmatched
402
- @bracketed_paste_finished = true
403
396
  else
404
397
  buffer << c
405
398
  result = key_stroke.match_status(buffer)
@@ -606,4 +599,6 @@ else
606
599
  io
607
600
  end
608
601
 
602
+ Reline::Face.load_initial_configs
603
+
609
604
  Reline::HISTORY = Reline::History.new(Reline.core.config)
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.3.9
4
+ version: 0.5.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - aycabta
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-10-03 00:00:00.000000000 Z
11
+ date: 2024-05-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: io-console
@@ -37,6 +37,7 @@ files:
37
37
  - lib/reline.rb
38
38
  - lib/reline/ansi.rb
39
39
  - lib/reline/config.rb
40
+ - lib/reline/face.rb
40
41
  - lib/reline/general_io.rb
41
42
  - lib/reline/history.rb
42
43
  - lib/reline/key_actor.rb
@@ -56,7 +57,10 @@ files:
56
57
  homepage: https://github.com/ruby/reline
57
58
  licenses:
58
59
  - Ruby
59
- metadata: {}
60
+ metadata:
61
+ bug_tracker_uri: https://github.com/ruby/reline/issues
62
+ changelog_uri: https://github.com/ruby/reline/releases
63
+ source_code_uri: https://github.com/ruby/reline
60
64
  post_install_message:
61
65
  rdoc_options: []
62
66
  require_paths:
@@ -72,7 +76,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
72
76
  - !ruby/object:Gem::Version
73
77
  version: '0'
74
78
  requirements: []
75
- rubygems_version: 3.4.13
79
+ rubygems_version: 3.5.9
76
80
  signing_key:
77
81
  specification_version: 4
78
82
  summary: Alternative GNU Readline or Editline implementation by pure Ruby.