reline 0.6.3 → 0.7.0

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: cac4dc95dc3a9106a6f332d3503a62502f09205161a21157222e2dcd985ab706
4
- data.tar.gz: 586f28ad1d6e68eefe7e5b4a15a4ee69f9d55d5469c144b686b72084b0abebe8
3
+ metadata.gz: 1a8724bc788e9aab24b52fd980230e32b799b9ce5d95d8dcb41e56a2eb953ac4
4
+ data.tar.gz: f5a979522d2f280c9b45b78782329254b8ab40ad95587afb011748f57b93b35f
5
5
  SHA512:
6
- metadata.gz: 6f2e8fc8a404e1e8a0491c083e5ce524c5a5e465fceebcfa6eb7c0a5df20b98e07d337bb2c8703806f54e055efbeeba9c5d1f3171bd8e0faf6a611e30bbbf5da
7
- data.tar.gz: a73fe51d441f70a4bb84ecfac2172251e6db0abf9f3007cbadf4f0374e5772b3ee62f71c753f10e88aa460278d1c0611b32ebca2e6f59fc6e42894377b56eb34
6
+ metadata.gz: b89c6c314821ff7e4a5bbb724197a6930c61b0cfc2f9b01eba6e0cf356d3d66425f8121527dd68f3ee639f0a11bd062ff6ddc253c27c5e9ad6d1fae333d4f840
7
+ data.tar.gz: ae7d3567919f3272bea1cce00435d9d9bd917288f203b6ae816d3856070115b63d40f691596bfefa37aa0beb9bcd8e71db7b82b73692b7c51c24b9f7f1ec9af0
@@ -114,13 +114,15 @@ class Reline::ANSI < Reline::IO
114
114
  end
115
115
 
116
116
  def inner_getc(timeout_second)
117
- unless @buf.empty?
118
- return @buf.shift
119
- end
120
- until @input.wait_readable(0.01)
117
+ while true
118
+ # @buf may change while handling signal, so we need to check it in each loop iteration.
119
+ return @buf.shift unless @buf.empty?
120
+ break if @input.wait_readable(0.01)
121
+
121
122
  timeout_second -= 0.01
122
123
  return nil if timeout_second <= 0
123
124
 
125
+ # handle_signal may call cursor_pos which modifies @buf
124
126
  Reline.core.line_editor.handle_signal
125
127
  end
126
128
  c = @input.getbyte
@@ -31,6 +31,42 @@ class Reline::Windows < Reline::IO
31
31
  @SetConsoleMode = Win32API.new('kernel32', 'SetConsoleMode', ['L', 'L'], 'L')
32
32
  @WaitForSingleObject = Win32API.new('kernel32', 'WaitForSingleObject', ['L', 'L'], 'L')
33
33
 
34
+ # Win32API does not have pointer size integer.
35
+ # Current process pseudo handle (-1)LL seems to fail to be passed to DuplicateHandle.
36
+ # @GetCurrentProcess = Win32API.new('kernel32', 'GetCurrentProcess', [], 'L')
37
+ @GetCurrentProcessId = Win32API.new('kernel32', 'GetCurrentProcessId', [], 'L')
38
+ @OpenProcess = Win32API.new('kernel32', 'OpenProcess', ['L', 'L', 'L'], 'L')
39
+ @CloseHandle = Win32API.new('kernel32', 'CloseHandle', ['L'], 'L')
40
+ @DuplicateHandle = Win32API.new('kernel32', 'DuplicateHandle', ['L', 'L', 'L', 'P', 'I', 'I', 'I'], 'L')
41
+
42
+ current_process_handle = @OpenProcess.call(
43
+ 0x0040, # PROCESS_DUP_HANDLE
44
+ 0, # bInheritHandle
45
+ @GetCurrentProcessId.call()
46
+ )
47
+ duplicate_handle = proc { |handle|
48
+ dupHandle = "\0" * 8
49
+ @DuplicateHandle.call(
50
+ current_process_handle,
51
+ handle,
52
+ current_process_handle,
53
+ dupHandle,
54
+ 0, # dwDesiredAccess
55
+ 0, # bInheritHandle
56
+ 2 # dwOptions = DUPLICATE_SAME_ACCESS
57
+ )
58
+ dupHandle.unpack1("J")
59
+ }
60
+ if (dup = duplicate_handle.call(@hConsoleHandle)) != 0
61
+ @hConsoleHandle = dup
62
+ at_exit { @CloseHandle.call(@hConsoleHandle) }
63
+ end
64
+ if (dup = duplicate_handle.call(@hConsoleInputHandle)) != 0
65
+ @hConsoleInputHandle = dup
66
+ at_exit { @CloseHandle.call(@hConsoleInputHandle) }
67
+ end
68
+ @CloseHandle.call(current_process_handle)
69
+
34
70
  @legacy_console = getconsolemode & ENABLE_VIRTUAL_TERMINAL_PROCESSING == 0
35
71
  end
36
72
 
@@ -162,24 +198,14 @@ class Reline::Windows < Reline::IO
162
198
  ENABLE_WRAP_AT_EOL_OUTPUT = 2
163
199
  ENABLE_VIRTUAL_TERMINAL_PROCESSING = 4
164
200
 
165
- # Calling Win32API with console handle is reported to fail after executing some external command.
166
- # We need to refresh console handle and retry the call again.
167
- private def call_with_console_handle(win32func, *args)
168
- val = win32func.call(@hConsoleHandle, *args)
169
- return val if val != 0
170
-
171
- @hConsoleHandle = @GetStdHandle.call(STD_OUTPUT_HANDLE)
172
- win32func.call(@hConsoleHandle, *args)
173
- end
174
-
175
201
  private def getconsolemode
176
202
  mode = +"\0\0\0\0"
177
- call_with_console_handle(@GetConsoleMode, mode)
203
+ @GetConsoleMode.call(@hConsoleHandle, mode)
178
204
  mode.unpack1('L')
179
205
  end
180
206
 
181
207
  private def setconsolemode(mode)
182
- call_with_console_handle(@SetConsoleMode, mode)
208
+ @SetConsoleMode.call(@hConsoleHandle, mode)
183
209
  end
184
210
 
185
211
  #if @legacy_console
@@ -355,7 +381,7 @@ class Reline::Windows < Reline::IO
355
381
  # [18,2] dwMaximumWindowSize.X
356
382
  # [20,2] dwMaximumWindowSize.Y
357
383
  csbi = 0.chr * 22
358
- if call_with_console_handle(@GetConsoleScreenBufferInfo, csbi) != 0
384
+ if @GetConsoleScreenBufferInfo.call(@hConsoleHandle, csbi) != 0
359
385
  # returns [width, height, x, y, attributes, left, top, right, bottom]
360
386
  csbi.unpack("s9")
361
387
  else
@@ -377,7 +403,7 @@ class Reline::Windows < Reline::IO
377
403
 
378
404
  def move_cursor_column(val)
379
405
  _, _, _, y, = get_console_screen_buffer_info
380
- call_with_console_handle(@SetConsoleCursorPosition, y * 65536 + val) if y
406
+ @SetConsoleCursorPosition.call(@hConsoleHandle, y * 65536 + val) if y
381
407
  end
382
408
 
383
409
  def move_cursor_up(val)
@@ -386,7 +412,7 @@ class Reline::Windows < Reline::IO
386
412
  return unless y
387
413
  y = (y - top) - val
388
414
  y = 0 if y < 0
389
- call_with_console_handle(@SetConsoleCursorPosition, (y + top) * 65536 + x)
415
+ @SetConsoleCursorPosition.call(@hConsoleHandle, (y + top) * 65536 + x)
390
416
  elsif val < 0
391
417
  move_cursor_down(-val)
392
418
  end
@@ -399,7 +425,7 @@ class Reline::Windows < Reline::IO
399
425
  screen_height = bottom - top
400
426
  y = (y - top) + val
401
427
  y = screen_height if y > screen_height
402
- call_with_console_handle(@SetConsoleCursorPosition, (y + top) * 65536 + x)
428
+ @SetConsoleCursorPosition.call(@hConsoleHandle, (y + top) * 65536 + x)
403
429
  elsif val < 0
404
430
  move_cursor_up(-val)
405
431
  end
@@ -409,8 +435,8 @@ class Reline::Windows < Reline::IO
409
435
  width, _, x, y, attributes, = get_console_screen_buffer_info
410
436
  return unless x
411
437
  written = 0.chr * 4
412
- call_with_console_handle(@FillConsoleOutputCharacter, 0x20, width - x, y * 65536 + x, written)
413
- call_with_console_handle(@FillConsoleOutputAttribute, attributes, width - x, y * 65536 + x, written)
438
+ @FillConsoleOutputCharacter.call(@hConsoleHandle, 0x20, width - x, y * 65536 + x, written)
439
+ @FillConsoleOutputAttribute.call(@hConsoleHandle, attributes, width - x, y * 65536 + x, written)
414
440
  end
415
441
 
416
442
  # This only works when the cursor is at the bottom of the scroll range
@@ -428,9 +454,9 @@ class Reline::Windows < Reline::IO
428
454
  fill_length = width * (bottom - top + 1)
429
455
  screen_topleft = top * 65536
430
456
  written = 0.chr * 4
431
- call_with_console_handle(@FillConsoleOutputCharacter, 0x20, fill_length, screen_topleft, written)
432
- call_with_console_handle(@FillConsoleOutputAttribute, attributes, fill_length, screen_topleft, written)
433
- call_with_console_handle(@SetConsoleCursorPosition, screen_topleft)
457
+ @FillConsoleOutputCharacter.call(@hConsoleHandle, 0x20, fill_length, screen_topleft, written)
458
+ @FillConsoleOutputAttribute.call(@hConsoleHandle, attributes, fill_length, screen_topleft, written)
459
+ @SetConsoleCursorPosition.call(@hConsoleHandle, screen_topleft)
434
460
  else
435
461
  @output.write "\e[2J" "\e[H"
436
462
  end
@@ -444,14 +470,14 @@ class Reline::Windows < Reline::IO
444
470
  size = 100
445
471
  visible = 0 # 0 means false
446
472
  cursor_info = [size, visible].pack('Li')
447
- call_with_console_handle(@SetConsoleCursorInfo, cursor_info)
473
+ @SetConsoleCursorInfo.call(@hConsoleHandle, cursor_info)
448
474
  end
449
475
 
450
476
  def show_cursor
451
477
  size = 100
452
478
  visible = 1 # 1 means true
453
479
  cursor_info = [size, visible].pack('Li')
454
- call_with_console_handle(@SetConsoleCursorInfo, cursor_info)
480
+ @SetConsoleCursorInfo.call(@hConsoleHandle, cursor_info)
455
481
  end
456
482
 
457
483
  def set_winch_handler(&handler)
data/lib/reline/io.rb CHANGED
@@ -10,15 +10,21 @@ module Reline
10
10
  require 'reline/io/ansi'
11
11
 
12
12
  case RbConfig::CONFIG['host_os']
13
- when /mswin|msys|mingw|cygwin|bccwin|wince|emc/
13
+ when /mswin|mingw|bccwin|wince|emc/
14
14
  require 'reline/io/windows'
15
15
  io = Reline::Windows.new
16
- if io.msys_tty?
16
+ if io.msys_tty? || !STDIN.tty?
17
+ # In either case stdin is not a console (a Cygwin/MSYS pty pipe such
18
+ # as mintty, or a redirect), so the Win32 console input API cannot
19
+ # read it.
17
20
  Reline::ANSI.new
18
21
  else
19
22
  io
20
23
  end
21
24
  else
25
+ # Ruby built with the msys/cygwin runtime also reaches here. Its tty layer
26
+ # speaks ANSI in any terminal (mintty pty or Windows console), so the
27
+ # Win32 console API must not be used. https://github.com/ruby/reline/issues/903
22
28
  Reline::ANSI.new
23
29
  end
24
30
  end
@@ -307,7 +307,7 @@ module Reline::KeyActor
307
307
  # 152 M-^X
308
308
  nil,
309
309
  # 153 M-^Y
310
- :em_yank_pop,
310
+ nil,
311
311
  # 154 M-^Z
312
312
  nil,
313
313
  # 155 M-^[
@@ -499,7 +499,7 @@ module Reline::KeyActor
499
499
  # 248 M-x
500
500
  nil,
501
501
  # 249 M-y
502
- nil,
502
+ :em_yank_pop,
503
503
  # 250 M-z
504
504
  nil,
505
505
  # 251 M-{
@@ -13,6 +13,7 @@ class Reline::LineEditor
13
13
  attr_accessor :prompt_proc
14
14
  attr_accessor :auto_indent_proc
15
15
  attr_accessor :dig_perfect_match_proc
16
+ attr_accessor :rprompt
16
17
 
17
18
  VI_MOTIONS = %i{
18
19
  ed_prev_char
@@ -43,7 +44,6 @@ class Reline::LineEditor
43
44
  RenderedScreen = Struct.new(:base_y, :lines, :cursor_y, keyword_init: true)
44
45
 
45
46
  CompletionJourneyState = Struct.new(:line_index, :pre, :target, :post, :list, :pointer)
46
- NullActionState = [nil, nil].freeze
47
47
 
48
48
  class MenuInfo
49
49
  attr_reader :list
@@ -223,7 +223,7 @@ class Reline::LineEditor
223
223
 
224
224
  def reset_variables(prompt = '')
225
225
  @prompt = prompt.gsub("\n", "\\n")
226
- @mark_pointer = nil
226
+ @mark_position = nil
227
227
  @is_multiline = false
228
228
  @finished = false
229
229
  @history_pointer = nil
@@ -253,8 +253,8 @@ class Reline::LineEditor
253
253
  @undo_redo_history = [[[""], 0, 0]]
254
254
  @undo_redo_index = 0
255
255
  @restoring = false
256
- @prev_action_state = NullActionState
257
- @next_action_state = NullActionState
256
+ @prev_action_state = {}
257
+ @next_action_state = {}
258
258
  reset_line
259
259
  end
260
260
 
@@ -476,6 +476,20 @@ class Reline::LineEditor
476
476
  prompt_width = Reline::Unicode.calculate_width(prompt, true)
477
477
  [[0, prompt_width, prompt], [prompt_width, Reline::Unicode.calculate_width(line, true), line]]
478
478
  end
479
+
480
+ # Add rprompt to the first visible line if set and there's room
481
+ if @rprompt && !@rprompt.empty? && new_lines[0]
482
+ rprompt_width = Reline::Unicode.calculate_width(@rprompt, true)
483
+ right_col = screen_width - rprompt_width
484
+ first_line = new_lines[0]
485
+ # Calculate the end of the current content (prompt + input)
486
+ content_end = first_line.sum { |_, width, _| width }
487
+ # Only show rprompt if there's at least 1 char gap between content and rprompt
488
+ if right_col > content_end
489
+ first_line << [right_col, rprompt_width, @rprompt]
490
+ end
491
+ end
492
+
479
493
  if @menu_info
480
494
  @menu_info.lines(screen_width).each do |item|
481
495
  new_lines << [[0, Reline::Unicode.calculate_width(item), item]]
@@ -491,8 +505,8 @@ class Reline::LineEditor
491
505
  next if row < 0 || row >= screen_height
492
506
 
493
507
  dialog_rows = new_lines[row] ||= []
494
- # index 0 is for prompt, index 1 is for line, index 2.. is for dialog
495
- dialog_rows[index + 2] = [x_range.begin, dialog.width, dialog.contents[row - y_range.begin]]
508
+ # index 0 is for prompt, index 1 is for line, index 2 is for rprompt, index 3.. is for dialog
509
+ dialog_rows[index + 3] = [x_range.begin, dialog.width, dialog.contents[row - y_range.begin]]
496
510
  end
497
511
  end
498
512
 
@@ -1022,7 +1036,7 @@ class Reline::LineEditor
1022
1036
  @byte_pointer -= byte_size
1023
1037
  end
1024
1038
 
1025
- @prev_action_state, @next_action_state = @next_action_state, NullActionState
1039
+ @prev_action_state, @next_action_state = @next_action_state, {}
1026
1040
 
1027
1041
  unless @completion_occurs
1028
1042
  @completion_state = CompletionState::NORMAL
@@ -1779,17 +1793,24 @@ class Reline::LineEditor
1779
1793
 
1780
1794
  private def em_yank(key)
1781
1795
  yanked = @kill_ring.yank
1782
- insert_text(yanked) if yanked
1796
+ return unless yanked
1797
+
1798
+ before_cursor = current_line.byteslice(0, @byte_pointer)
1799
+ after_cursor = current_line.byteslice(@byte_pointer, current_line.bytesize)
1800
+ set_current_line(before_cursor + yanked + after_cursor, before_cursor.bytesize + yanked.bytesize)
1801
+ set_next_action_state(:em_yank_line, [before_cursor, after_cursor])
1783
1802
  end
1784
1803
  alias_method :yank, :em_yank
1785
1804
 
1786
1805
  private def em_yank_pop(key)
1787
- yanked, prev_yank = @kill_ring.yank_pop
1788
- if yanked
1789
- line, = byteslice!(current_line, @byte_pointer - prev_yank.bytesize, prev_yank.bytesize)
1790
- set_current_line(line, @byte_pointer - prev_yank.bytesize)
1791
- insert_text(yanked)
1792
- end
1806
+ before_cursor, after_cursor = prev_action_state_value(:em_yank_line)
1807
+ return unless before_cursor and after_cursor
1808
+
1809
+ yanked, = @kill_ring.yank_pop
1810
+ return unless yanked
1811
+
1812
+ set_current_line(before_cursor + yanked + after_cursor, before_cursor.bytesize + yanked.bytesize)
1813
+ set_next_action_state(:em_yank_line, [before_cursor, after_cursor])
1793
1814
  end
1794
1815
  alias_method :yank_pop, :em_yank_pop
1795
1816
 
@@ -2303,15 +2324,22 @@ class Reline::LineEditor
2303
2324
  end
2304
2325
 
2305
2326
  private def em_set_mark(key)
2306
- @mark_pointer = [@byte_pointer, @line_index]
2327
+ cursor_column = Reline::Unicode.calculate_width(current_line.byteslice(0, @byte_pointer))
2328
+ @mark_position = [@line_index, cursor_column]
2307
2329
  end
2308
2330
  alias_method :set_mark, :em_set_mark
2309
2331
 
2310
2332
  private def em_exchange_mark(key)
2311
- return unless @mark_pointer
2312
- new_pointer = [@byte_pointer, @line_index]
2313
- @byte_pointer, @line_index = @mark_pointer
2314
- @mark_pointer = new_pointer
2333
+ return unless @mark_position
2334
+ line_index, cursor_column = @mark_position
2335
+ em_set_mark(key)
2336
+ if @buffer_of_lines.size <= line_index
2337
+ @line_index = @buffer_of_lines.size - 1
2338
+ @byte_pointer = current_line.bytesize
2339
+ else
2340
+ @line_index = line_index
2341
+ calculate_nearest_cursor(cursor_column)
2342
+ end
2315
2343
  end
2316
2344
  alias_method :exchange_point_and_mark, :em_exchange_mark
2317
2345
 
@@ -2343,11 +2371,11 @@ class Reline::LineEditor
2343
2371
  end
2344
2372
 
2345
2373
  private def prev_action_state_value(type)
2346
- @prev_action_state[0] == type ? @prev_action_state[1] : nil
2374
+ @prev_action_state[type]
2347
2375
  end
2348
2376
 
2349
2377
  private def set_next_action_state(type, value)
2350
- @next_action_state = [type, value]
2378
+ @next_action_state[type] = value
2351
2379
  end
2352
2380
 
2353
2381
  private def re_read_init_file(_key)
@@ -1,3 +1,3 @@
1
1
  module Reline
2
- VERSION = '0.6.3'
2
+ VERSION = '0.7.0'
3
3
  end
data/lib/reline.rb CHANGED
@@ -247,19 +247,19 @@ module Reline
247
247
  } # :nodoc:
248
248
  Reline::DEFAULT_DIALOG_CONTEXT = Array.new # :nodoc:
249
249
 
250
- def readmultiline(prompt = '', add_hist = false, &confirm_multiline_termination)
250
+ def readmultiline(_prompt = '', _add_history = false, prompt: _prompt, add_history: _add_history, rprompt: nil, &confirm_multiline_termination)
251
251
  @mutex.synchronize do
252
252
  unless confirm_multiline_termination
253
253
  raise ArgumentError.new('#readmultiline needs block to confirm multiline termination')
254
254
  end
255
255
 
256
256
  io_gate.with_raw_input do
257
- inner_readline(prompt, add_hist, true, &confirm_multiline_termination)
257
+ inner_readline(prompt, add_history, true, rprompt: rprompt, &confirm_multiline_termination)
258
258
  end
259
259
 
260
260
  whole_buffer = line_editor.whole_buffer.dup
261
261
  whole_buffer.taint if RUBY_VERSION < '2.7'
262
- if add_hist and whole_buffer and whole_buffer.chomp("\n").size > 0
262
+ if add_history and whole_buffer and whole_buffer.chomp("\n").size > 0
263
263
  Reline::HISTORY << whole_buffer
264
264
  end
265
265
 
@@ -273,15 +273,15 @@ module Reline
273
273
  end
274
274
  end
275
275
 
276
- def readline(prompt = '', add_hist = false)
276
+ def readline(_prompt = '', _add_history = false, prompt: _prompt, add_history: _add_history, rprompt: nil)
277
277
  @mutex.synchronize do
278
278
  io_gate.with_raw_input do
279
- inner_readline(prompt, add_hist, false)
279
+ inner_readline(prompt, add_history, false, rprompt: rprompt)
280
280
  end
281
281
 
282
282
  line = line_editor.line.dup
283
283
  line.taint if RUBY_VERSION < '2.7'
284
- if add_hist and line and line.chomp("\n").size > 0
284
+ if add_history and line and line.chomp("\n").size > 0
285
285
  Reline::HISTORY << line.chomp("\n")
286
286
  end
287
287
 
@@ -290,7 +290,7 @@ module Reline
290
290
  end
291
291
  end
292
292
 
293
- private def inner_readline(prompt, add_hist, multiline, &confirm_multiline_termination)
293
+ private def inner_readline(prompt, add_history, multiline, rprompt: nil, &confirm_multiline_termination)
294
294
  if ENV['RELINE_STDERR_TTY']
295
295
  if io_gate.win?
296
296
  $stderr = File.open(ENV['RELINE_STDERR_TTY'], 'a')
@@ -323,6 +323,7 @@ module Reline
323
323
  line_editor.prompt_proc = prompt_proc
324
324
  line_editor.auto_indent_proc = auto_indent_proc
325
325
  line_editor.dig_perfect_match_proc = dig_perfect_match_proc
326
+ line_editor.rprompt = rprompt&.encode(encoding)
326
327
 
327
328
  pre_input_hook&.call
328
329
 
@@ -442,13 +443,13 @@ module Reline
442
443
  ##
443
444
  # :singleton-method: readmultiline
444
445
  # :call-seq:
445
- # readmultiline(prompt = '', add_hist = false, &confirm_multiline_termination) -> string or nil
446
+ # readmultiline(prompt = '', add_history = false, &confirm_multiline_termination) -> string or nil
446
447
  def_single_delegators :core, :readmultiline
447
448
 
448
449
  ##
449
450
  # :singleton-method: readline
450
451
  # :call-seq:
451
- # readline(prompt = '', add_hist = false) -> string or nil
452
+ # readline(prompt = '', add_history = false) -> string or nil
452
453
  def_single_delegators :core, :readline
453
454
  def_single_delegators :core, :completion_case_fold, :completion_case_fold=
454
455
  def_single_delegators :core, :completion_quote_character
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: reline
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.3
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - aycabta