reline 0.5.9 → 0.5.11

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: 28ee53a9963a33e9eb1159bea507695d94cc4f67b05ce005175808b7ec2d5175
4
- data.tar.gz: c165de2edcc223bc4e3e149208fe29994063445ea0cf02b93658010a245df5e4
3
+ metadata.gz: d2e17ec5d6de83746c38c5e0764f7fa1ecda40f01c18b9e8cdf769e4bf2a155e
4
+ data.tar.gz: 7ce06466fae4c8115bb507141998d322595b9e924dfc1ad3001871265088a680
5
5
  SHA512:
6
- metadata.gz: af0f93e54d3a414c63dfb369746f00e8fa9cac609c646c0a48ad5f87275e7b6f5c88c47d4c30218075d3774fd41ba7978f49a0ffbfa070ee42921ba0e500c06e
7
- data.tar.gz: 5b0a45b7ae2cfb0e23c9d3b4a863e381f4d9ba5d6298f864c261e408cb5c366031d6137d623d0d46e3729b7bbe086fc5da5c5e636163bce230ae0f297441e016
6
+ metadata.gz: 31e90ffea6ac235aad5c2c243fbd41163142f94e044ed9f4980860263fab97154857483755dcacd259fa9b578b1c29be5b878cd7f883f18e7b92380cb5865ef3
7
+ data.tar.gz: 54b91a893c536b3e6b5c984c0fc2b053b8978ab586ae361446fb603c4eecfb0240a99c45b25e4996ff58d9ef15fa4e33bab5cef9cb4aef3dc7eab85998af3711
data/lib/reline/config.rb CHANGED
@@ -1,7 +1,7 @@
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
@@ -29,6 +29,17 @@ class Reline::Config
29
29
  attr_accessor :autocompletion
30
30
 
31
31
  def initialize
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
32
43
  @additional_key_bindings = { # from inputrc
33
44
  emacs: Reline::KeyActor::Base.new,
34
45
  vi_insert: Reline::KeyActor::Base.new,
@@ -51,16 +62,11 @@ class Reline::Config
51
62
  @keyseq_timeout = 500
52
63
  @test_mode = false
53
64
  @autocompletion = false
54
- @convert_meta = true if seven_bit_encoding?(Reline::IOGate.encoding)
65
+ @convert_meta = seven_bit_encoding?(Reline::IOGate.encoding)
55
66
  @loaded = false
56
67
  @enable_bracketed_paste = true
57
- end
58
-
59
- def reset
60
- if editing_mode_is?(:vi_command)
61
- @editing_mode_label = :vi_insert
62
- end
63
- @oneshot_key_bindings.clear
68
+ @show_mode_in_prompt = false
69
+ @default_inputrc_path = nil
64
70
  end
65
71
 
66
72
  def editing_mode
@@ -188,13 +194,14 @@ class Reline::Config
188
194
  # value ignores everything after a space, raw_value does not.
189
195
  var, value, raw_value = $1.downcase, $2.partition(' ').first, $2
190
196
  bind_variable(var, value, raw_value)
191
- next
192
- when /\s*("#{KEYSEQ_PATTERN}+")\s*:\s*(.*)\s*$/o
193
- key, func_name = $1, $2
194
- func_name = func_name.split.first
195
- keystroke, func = bind_key(key, func_name)
196
- next unless keystroke
197
- @additional_key_bindings[@keymap_label].add(@keymap_prefix + keystroke, func)
197
+ when /^\s*(?:M|Meta)-([a-zA-Z_])\s*:\s*(.*)\s*$/o
198
+ bind_key("\"\\M-#$1\"", $2)
199
+ when /^\s*(?:C|Control)-([a-zA-Z_])\s*:\s*(.*)\s*$/o
200
+ bind_key("\"\\C-#$1\"", $2)
201
+ when /^\s*(?:(?:C|Control)-(?:M|Meta)|(?:M|Meta)-(?:C|Control))-([a-zA-Z_])\s*:\s*(.*)\s*$/o
202
+ bind_key("\"\\M-\\C-#$1\"", $2)
203
+ when /^\s*("#{KEYSEQ_PATTERN}+")\s*:\s*(.*)\s*$/o
204
+ bind_key($1, $2)
198
205
  end
199
206
  end
200
207
  unless if_stack.empty?
@@ -245,22 +252,6 @@ class Reline::Config
245
252
  rescue ArgumentError
246
253
  @history_size = 500
247
254
  end
248
- when 'bell-style'
249
- @bell_style =
250
- case value
251
- when 'none', 'off'
252
- :none
253
- when 'audible', 'on'
254
- :audible
255
- when 'visible'
256
- :visible
257
- else
258
- :audible
259
- end
260
- when 'comment-begin'
261
- @comment_begin = value.dup
262
- when 'completion-query-items'
263
- @completion_query_items = value.to_i
264
255
  when 'isearch-terminators'
265
256
  @isearch_terminators = retrieve_string(raw_value)
266
257
  when 'editing-mode'
@@ -320,7 +311,12 @@ class Reline::Config
320
311
  parse_keyseq(str).map { |c| c.chr(Reline.encoding_system_needs) }.join
321
312
  end
322
313
 
323
- def bind_key(key, func_name)
314
+ def bind_key(key, value)
315
+ keystroke, func = parse_key_binding(key, value)
316
+ @additional_key_bindings[@keymap_label].add(@keymap_prefix + keystroke, func) if keystroke
317
+ end
318
+
319
+ def parse_key_binding(key, func_name)
324
320
  if key =~ /\A"(.*)"\z/
325
321
  keyseq = parse_keyseq($1)
326
322
  else
@@ -329,27 +325,19 @@ class Reline::Config
329
325
  if func_name =~ /"(.*)"/
330
326
  func = parse_keyseq($1)
331
327
  else
332
- func = func_name.tr(?-, ?_).to_sym # It must be macro.
328
+ func = func_name.split.first.tr(?-, ?_).to_sym # It must be macro.
333
329
  end
334
330
  [keyseq, func]
335
331
  end
336
332
 
337
333
  def key_notation_to_code(notation)
338
334
  case notation
335
+ when /(?:\\(?:C|Control)-\\(?:M|Meta)|\\(?:M|Meta)-\\(?:C|Control))-([A-Za-z_])/
336
+ [?\e.ord, $1.ord % 32]
339
337
  when /\\(?:C|Control)-([A-Za-z_])/
340
- (1 + $1.downcase.ord - ?a.ord)
338
+ ($1.upcase.ord % 32)
341
339
  when /\\(?:M|Meta)-([0-9A-Za-z_])/
342
- modified_key = $1
343
- case $1
344
- when /[0-9]/
345
- ?\M-0.bytes.first + (modified_key.ord - ?0.ord)
346
- when /[A-Z]/
347
- ?\M-A.bytes.first + (modified_key.ord - ?A.ord)
348
- when /[a-z]/
349
- ?\M-a.bytes.first + (modified_key.ord - ?a.ord)
350
- end
351
- when /\\(?:C|Control)-(?:M|Meta)-[A-Za-z_]/, /\\(?:M|Meta)-(?:C|Control)-[A-Za-z_]/
352
- # 129 M-^A
340
+ [?\e.ord, $1.ord]
353
341
  when /\\(\d{1,3})/ then $1.to_i(8) # octal
354
342
  when /\\x(\h{1,2})/ then $1.to_i(16) # hexadecimal
355
343
  when "\\e" then ?\e.ord
@@ -369,11 +357,14 @@ class Reline::Config
369
357
  end
370
358
 
371
359
  def parse_keyseq(str)
372
- ret = []
373
- str.scan(KEYSEQ_PATTERN) do
374
- ret << key_notation_to_code($&)
360
+ str.scan(KEYSEQ_PATTERN).flat_map do |notation|
361
+ key_notation_to_code(notation)
375
362
  end
376
- ret
363
+ end
364
+
365
+ def reload
366
+ reset_variables
367
+ read
377
368
  end
378
369
 
379
370
  private def seven_bit_encoding?(encoding)
data/lib/reline/face.rb CHANGED
@@ -107,7 +107,7 @@ class Reline::Face
107
107
 
108
108
  def sgr_rgb_256color(key, value)
109
109
  # 256 colors are
110
- # 0..15: standard colors, hight intensity colors
110
+ # 0..15: standard colors, high intensity colors
111
111
  # 16..232: 216 colors (R, G, B each 6 steps)
112
112
  # 233..255: grayscale colors (24 steps)
113
113
  # This methods converts rgb_expression to 216 colors
@@ -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,7 +56,7 @@ 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)
@@ -41,7 +41,7 @@ class Reline::ANSI < Reline::IO
41
41
  end
42
42
 
43
43
  def encoding
44
- Encoding.default_external
44
+ @input.external_encoding || Encoding.default_external
45
45
  end
46
46
 
47
47
  def set_default_key_bindings(config, allow_terminfo: true)
@@ -75,7 +75,10 @@ class Reline::ANSI < Reline::IO
75
75
 
76
76
  def set_default_key_bindings_ansi_cursor(config)
77
77
  ANSI_CURSOR_KEY_BINDINGS.each do |char, (default_func, modifiers)|
78
- bindings = [["\e[#{char}", default_func]] # CSI + char
78
+ bindings = [
79
+ ["\e[#{char}", default_func], # CSI + char
80
+ ["\eO#{char}", default_func] # SS3 + char, application cursor key mode
81
+ ]
79
82
  if modifiers[:ctrl]
80
83
  # CSI + ctrl_key_modifier + char
81
84
  bindings << ["\e[1;5#{char}", modifiers[:ctrl]]
@@ -123,27 +126,9 @@ class Reline::ANSI < Reline::IO
123
126
  [27, 91, 49, 126] => :ed_move_to_beg, # Home
124
127
  [27, 91, 52, 126] => :ed_move_to_end, # End
125
128
 
126
- # KDE
127
- # Del is 0x08
128
- [27, 71, 65] => :ed_prev_history, # ↑
129
- [27, 71, 66] => :ed_next_history, # ↓
130
- [27, 71, 67] => :ed_next_char, # →
131
- [27, 71, 68] => :ed_prev_char, # ←
132
-
133
129
  # urxvt / exoterm
134
130
  [27, 91, 55, 126] => :ed_move_to_beg, # Home
135
131
  [27, 91, 56, 126] => :ed_move_to_end, # End
136
-
137
- # GNOME
138
- [27, 79, 72] => :ed_move_to_beg, # Home
139
- [27, 79, 70] => :ed_move_to_end, # End
140
- # Del is 0x08
141
- # Arrow keys are the same of KDE
142
-
143
- [27, 79, 65] => :ed_prev_history, # ↑
144
- [27, 79, 66] => :ed_next_history, # ↓
145
- [27, 79, 67] => :ed_next_char, # →
146
- [27, 79, 68] => :ed_prev_char, # ←
147
132
  }.each_pair do |key, func|
148
133
  config.add_default_key_binding_by_keymap(:emacs, key, func)
149
134
  config.add_default_key_binding_by_keymap(:vi_insert, key, func)
@@ -245,39 +230,30 @@ class Reline::ANSI < Reline::IO
245
230
  self
246
231
  end
247
232
 
248
- def cursor_pos
249
- if both_tty?
250
- res = +''
251
- m = nil
252
- @input.raw do |stdin|
253
- @output << "\e[6n"
254
- @output.flush
255
- loop do
256
- c = stdin.getc
257
- next if c.nil?
258
- res << c
259
- m = res.match(/\e\[(?<row>\d+);(?<column>\d+)R/)
260
- break if m
261
- end
262
- (m.pre_match + m.post_match).chars.reverse_each do |ch|
263
- stdin.ungetc ch
233
+ private def cursor_pos_internal(timeout:)
234
+ match = nil
235
+ @input.raw do |stdin|
236
+ @output << "\e[6n"
237
+ @output.flush
238
+ timeout_at = Time.now + timeout
239
+ buf = +''
240
+ while (wait = timeout_at - Time.now) > 0 && stdin.wait_readable(wait)
241
+ buf << stdin.readpartial(1024)
242
+ if (match = buf.match(/\e\[(?<row>\d+);(?<column>\d+)R/))
243
+ buf = match.pre_match + match.post_match
244
+ break
264
245
  end
265
246
  end
266
- column = m[:column].to_i - 1
267
- row = m[:row].to_i - 1
268
- else
269
- begin
270
- buf = @output.pread(@output.pos, 0)
271
- row = buf.count("\n")
272
- column = buf.rindex("\n") ? (buf.size - buf.rindex("\n")) - 1 : 0
273
- rescue Errno::ESPIPE, IOError
274
- # Just returns column 1 for ambiguous width because this I/O is not
275
- # tty and can't seek.
276
- row = 0
277
- column = 1
247
+ buf.chars.reverse_each do |ch|
248
+ stdin.ungetc ch
278
249
  end
279
250
  end
280
- Reline::CursorPos.new(column, row)
251
+ [match[:column].to_i - 1, match[:row].to_i - 1] if match
252
+ end
253
+
254
+ def cursor_pos
255
+ col, row = cursor_pos_internal(timeout: 0.5) if both_tty?
256
+ Reline::CursorPos.new(col || 0, row || 0)
281
257
  end
282
258
 
283
259
  def both_tty?
@@ -347,6 +323,13 @@ class Reline::ANSI < Reline::IO
347
323
 
348
324
  def set_winch_handler(&handler)
349
325
  @old_winch_handler = Signal.trap('WINCH', &handler)
326
+ @old_cont_handler = Signal.trap('CONT') do
327
+ @input.raw!(intr: true) if @input.tty?
328
+ # Rerender the screen. Note that screen size might be changed while suspended.
329
+ handler.call
330
+ end
331
+ rescue ArgumentError
332
+ # Signal.trap may raise an ArgumentError if the platform doesn't support the signal.
350
333
  end
351
334
 
352
335
  def prep
@@ -360,5 +343,6 @@ class Reline::ANSI < Reline::IO
360
343
  # Disable bracketed paste
361
344
  @output.write "\e[?2004l" if Reline.core.config.enable_bracketed_paste && both_tty?
362
345
  Signal.trap('WINCH', @old_winch_handler) if @old_winch_handler
346
+ Signal.trap('CONT', @old_cont_handler) if @old_cont_handler
363
347
  end
364
348
  end
@@ -21,7 +21,7 @@ class Reline::Dumb < Reline::IO
21
21
  elsif RUBY_PLATFORM =~ /mswin|mingw/
22
22
  Encoding::UTF_8
23
23
  else
24
- Encoding::default_external
24
+ @input.external_encoding || Encoding::default_external
25
25
  end
26
26
  end
27
27
 
@@ -60,7 +60,7 @@ class Reline::Dumb < Reline::IO
60
60
  end
61
61
 
62
62
  def cursor_pos
63
- Reline::CursorPos.new(1, 1)
63
+ Reline::CursorPos.new(0, 0)
64
64
  end
65
65
 
66
66
  def hide_cursor
@@ -157,16 +157,27 @@ class Reline::Windows < Reline::IO
157
157
  STD_OUTPUT_HANDLE = -11
158
158
  FILE_TYPE_PIPE = 0x0003
159
159
  FILE_NAME_INFO = 2
160
+ ENABLE_WRAP_AT_EOL_OUTPUT = 2
160
161
  ENABLE_VIRTUAL_TERMINAL_PROCESSING = 4
161
162
 
163
+ # Calling Win32API with console handle is reported to fail after executing some external command.
164
+ # We need to refresh console handle and retry the call again.
165
+ private def call_with_console_handle(win32func, *args)
166
+ val = win32func.call(@hConsoleHandle, *args)
167
+ return val if val != 0
168
+
169
+ @hConsoleHandle = @GetStdHandle.call(STD_OUTPUT_HANDLE)
170
+ win32func.call(@hConsoleHandle, *args)
171
+ end
172
+
162
173
  private def getconsolemode
163
- mode = "\000\000\000\000"
164
- @GetConsoleMode.call(@hConsoleHandle, mode)
174
+ mode = +"\0\0\0\0"
175
+ call_with_console_handle(@GetConsoleMode, mode)
165
176
  mode.unpack1('L')
166
177
  end
167
178
 
168
179
  private def setconsolemode(mode)
169
- @SetConsoleMode.call(@hConsoleHandle, mode)
180
+ call_with_console_handle(@SetConsoleMode, mode)
170
181
  end
171
182
 
172
183
  #if @legacy_console
@@ -242,7 +253,7 @@ class Reline::Windows < Reline::IO
242
253
 
243
254
  key = KeyEventRecord.new(virtual_key_code, char_code, control_key_state)
244
255
 
245
- match = KEY_MAP.find { |args,| key.matches?(**args) }
256
+ match = KEY_MAP.find { |args,| key.match?(**args) }
246
257
  unless match.nil?
247
258
  @output_buf.concat(match.last)
248
259
  return
@@ -334,35 +345,38 @@ class Reline::Windows < Reline::IO
334
345
  # [18,2] dwMaximumWindowSize.X
335
346
  # [20,2] dwMaximumWindowSize.Y
336
347
  csbi = 0.chr * 22
337
- return if @GetConsoleScreenBufferInfo.call(@hConsoleHandle, csbi) == 0
338
- csbi
348
+ if call_with_console_handle(@GetConsoleScreenBufferInfo, csbi) != 0
349
+ # returns [width, height, x, y, attributes, left, top, right, bottom]
350
+ csbi.unpack("s9")
351
+ else
352
+ return nil
353
+ end
339
354
  end
340
355
 
356
+ ALTERNATIVE_CSBI = [80, 24, 0, 0, 7, 0, 0, 79, 23].freeze
357
+
341
358
  def get_screen_size
342
- unless csbi = get_console_screen_buffer_info
343
- return [1, 1]
344
- end
345
- csbi[0, 4].unpack('SS').reverse
359
+ width, _, _, _, _, _, top, _, bottom = get_console_screen_buffer_info || ALTERNATIVE_CSBI
360
+ [bottom - top + 1, width]
346
361
  end
347
362
 
348
363
  def cursor_pos
349
- unless csbi = get_console_screen_buffer_info
350
- return Reline::CursorPos.new(0, 0)
351
- end
352
- x = csbi[4, 2].unpack1('s')
353
- y = csbi[6, 2].unpack1('s')
354
- Reline::CursorPos.new(x, y)
364
+ _, _, x, y, _, _, top, = get_console_screen_buffer_info || ALTERNATIVE_CSBI
365
+ Reline::CursorPos.new(x, y - top)
355
366
  end
356
367
 
357
368
  def move_cursor_column(val)
358
- @SetConsoleCursorPosition.call(@hConsoleHandle, cursor_pos.y * 65536 + val)
369
+ _, _, _, y, = get_console_screen_buffer_info
370
+ call_with_console_handle(@SetConsoleCursorPosition, y * 65536 + val) if y
359
371
  end
360
372
 
361
373
  def move_cursor_up(val)
362
374
  if val > 0
363
- y = cursor_pos.y - val
375
+ _, _, x, y, _, _, top, = get_console_screen_buffer_info
376
+ return unless y
377
+ y = (y - top) - val
364
378
  y = 0 if y < 0
365
- @SetConsoleCursorPosition.call(@hConsoleHandle, y * 65536 + cursor_pos.x)
379
+ call_with_console_handle(@SetConsoleCursorPosition, (y + top) * 65536 + x)
366
380
  elsif val < 0
367
381
  move_cursor_down(-val)
368
382
  end
@@ -370,62 +384,43 @@ class Reline::Windows < Reline::IO
370
384
 
371
385
  def move_cursor_down(val)
372
386
  if val > 0
373
- return unless csbi = get_console_screen_buffer_info
374
- screen_height = get_screen_size.first
375
- y = cursor_pos.y + val
376
- y = screen_height - 1 if y > (screen_height - 1)
377
- @SetConsoleCursorPosition.call(@hConsoleHandle, (cursor_pos.y + val) * 65536 + cursor_pos.x)
387
+ _, _, x, y, _, _, top, _, bottom = get_console_screen_buffer_info
388
+ return unless y
389
+ screen_height = bottom - top
390
+ y = (y - top) + val
391
+ y = screen_height if y > screen_height
392
+ call_with_console_handle(@SetConsoleCursorPosition, (y + top) * 65536 + x)
378
393
  elsif val < 0
379
394
  move_cursor_up(-val)
380
395
  end
381
396
  end
382
397
 
383
398
  def erase_after_cursor
384
- return unless csbi = get_console_screen_buffer_info
385
- attributes = csbi[8, 2].unpack1('S')
386
- cursor = csbi[4, 4].unpack1('L')
399
+ width, _, x, y, attributes, = get_console_screen_buffer_info
400
+ return unless x
387
401
  written = 0.chr * 4
388
- @FillConsoleOutputCharacter.call(@hConsoleHandle, 0x20, get_screen_size.last - cursor_pos.x, cursor, written)
389
- @FillConsoleOutputAttribute.call(@hConsoleHandle, attributes, get_screen_size.last - cursor_pos.x, cursor, written)
390
- end
391
-
392
- def scroll_down(val)
393
- return if val < 0
394
- return unless csbi = get_console_screen_buffer_info
395
- buffer_width, buffer_lines, x, y, attributes, window_left, window_top, window_bottom = csbi.unpack('ssssSssx2s')
396
- screen_height = window_bottom - window_top + 1
397
- val = screen_height if val > screen_height
398
-
399
- if @legacy_console || window_left != 0
400
- # unless ENABLE_VIRTUAL_TERMINAL,
401
- # if srWindow.Left != 0 then it's conhost.exe hosted console
402
- # and puts "\n" causes horizontal scroll. its glitch.
403
- # FYI irb write from culumn 1, so this gives no gain.
404
- scroll_rectangle = [0, val, buffer_width, buffer_lines - val].pack('s4')
405
- destination_origin = 0 # y * 65536 + x
406
- fill = [' '.ord, attributes].pack('SS')
407
- @ScrollConsoleScreenBuffer.call(@hConsoleHandle, scroll_rectangle, nil, destination_origin, fill)
408
- else
409
- origin_x = x + 1
410
- origin_y = y - window_top + 1
411
- @output.write [
412
- (origin_y != screen_height) ? "\e[#{screen_height};H" : nil,
413
- "\n" * val,
414
- (origin_y != screen_height or !x.zero?) ? "\e[#{origin_y};#{origin_x}H" : nil
415
- ].join
416
- end
402
+ call_with_console_handle(@FillConsoleOutputCharacter, 0x20, width - x, y * 65536 + x, written)
403
+ call_with_console_handle(@FillConsoleOutputAttribute, attributes, width - x, y * 65536 + x, written)
404
+ end
405
+
406
+ # This only works when the cursor is at the bottom of the scroll range
407
+ # For more details, see https://github.com/ruby/reline/pull/577#issuecomment-1646679623
408
+ def scroll_down(x)
409
+ return if x.zero?
410
+ # We use `\n` instead of CSI + S because CSI + S would cause https://github.com/ruby/reline/issues/576
411
+ @output.write "\n" * x
417
412
  end
418
413
 
419
414
  def clear_screen
420
415
  if @legacy_console
421
- return unless csbi = get_console_screen_buffer_info
422
- buffer_width, _buffer_lines, attributes, window_top, window_bottom = csbi.unpack('ss@8S@12sx2s')
423
- fill_length = buffer_width * (window_bottom - window_top + 1)
424
- screen_topleft = window_top * 65536
416
+ width, _, _, _, attributes, _, top, _, bottom = get_console_screen_buffer_info
417
+ return unless width
418
+ fill_length = width * (bottom - top + 1)
419
+ screen_topleft = top * 65536
425
420
  written = 0.chr * 4
426
- @FillConsoleOutputCharacter.call(@hConsoleHandle, 0x20, fill_length, screen_topleft, written)
427
- @FillConsoleOutputAttribute.call(@hConsoleHandle, attributes, fill_length, screen_topleft, written)
428
- @SetConsoleCursorPosition.call(@hConsoleHandle, screen_topleft)
421
+ call_with_console_handle(@FillConsoleOutputCharacter, 0x20, fill_length, screen_topleft, written)
422
+ call_with_console_handle(@FillConsoleOutputAttribute, attributes, fill_length, screen_topleft, written)
423
+ call_with_console_handle(@SetConsoleCursorPosition, screen_topleft)
429
424
  else
430
425
  @output.write "\e[2J" "\e[H"
431
426
  end
@@ -439,14 +434,14 @@ class Reline::Windows < Reline::IO
439
434
  size = 100
440
435
  visible = 0 # 0 means false
441
436
  cursor_info = [size, visible].pack('Li')
442
- @SetConsoleCursorInfo.call(@hConsoleHandle, cursor_info)
437
+ call_with_console_handle(@SetConsoleCursorInfo, cursor_info)
443
438
  end
444
439
 
445
440
  def show_cursor
446
441
  size = 100
447
442
  visible = 1 # 1 means true
448
443
  cursor_info = [size, visible].pack('Li')
449
- @SetConsoleCursorInfo.call(@hConsoleHandle, cursor_info)
444
+ call_with_console_handle(@SetConsoleCursorInfo, cursor_info)
450
445
  end
451
446
 
452
447
  def set_winch_handler(&handler)
@@ -462,6 +457,28 @@ class Reline::Windows < Reline::IO
462
457
  # do nothing
463
458
  end
464
459
 
460
+ def disable_auto_linewrap(setting = true, &block)
461
+ mode = getconsolemode
462
+ if 0 == (mode & ENABLE_VIRTUAL_TERMINAL_PROCESSING)
463
+ if block
464
+ begin
465
+ setconsolemode(mode & ~ENABLE_WRAP_AT_EOL_OUTPUT)
466
+ block.call
467
+ ensure
468
+ setconsolemode(mode | ENABLE_WRAP_AT_EOL_OUTPUT)
469
+ end
470
+ else
471
+ if setting
472
+ setconsolemode(mode & ~ENABLE_WRAP_AT_EOL_OUTPUT)
473
+ else
474
+ setconsolemode(mode | ENABLE_WRAP_AT_EOL_OUTPUT)
475
+ end
476
+ end
477
+ else
478
+ block.call if block
479
+ end
480
+ end
481
+
465
482
  class KeyEventRecord
466
483
 
467
484
  attr_reader :virtual_key_code, :char_code, :control_key_state, :control_keys
@@ -491,7 +508,7 @@ class Reline::Windows < Reline::IO
491
508
  # Verifies if the arguments match with this key event.
492
509
  # Nil arguments are ignored, but at least one must be passed as non-nil.
493
510
  # To verify that no control keys were pressed, pass an empty array: `control_keys: []`.
494
- def matches?(control_keys: nil, virtual_key_code: nil, char_code: nil)
511
+ def match?(control_keys: nil, virtual_key_code: nil, char_code: nil)
495
512
  raise ArgumentError, 'No argument was passed to match key event' if control_keys.nil? && virtual_key_code.nil? && char_code.nil?
496
513
 
497
514
  (control_keys.nil? || [*control_keys].sort == @control_keys) &&