Module: Rich::Win32Console

Extended by:
Fiddle::Importer
Defined in:
lib/rich/win32_console.rb

Constant Summary collapse

STD_INPUT_HANDLE =

Standard handle constants

-10
STD_OUTPUT_HANDLE =
-11
STD_ERROR_HANDLE =
-12
ENABLE_PROCESSED_INPUT =

Console mode flags

0x0001
ENABLE_LINE_INPUT =
0x0002
ENABLE_ECHO_INPUT =
0x0004
ENABLE_WINDOW_INPUT =
0x0008
ENABLE_MOUSE_INPUT =
0x0010
ENABLE_INSERT_MODE =
0x0020
ENABLE_QUICK_EDIT_MODE =
0x0040
ENABLE_EXTENDED_FLAGS =
0x0080
ENABLE_AUTO_POSITION =
0x0100
ENABLE_VIRTUAL_TERMINAL_INPUT =
0x0200
ENABLE_PROCESSED_OUTPUT =

Output mode flags

0x0001
ENABLE_WRAP_AT_EOL_OUTPUT =
0x0002
ENABLE_VIRTUAL_TERMINAL_PROCESSING =
0x0004
DISABLE_NEWLINE_AUTO_RETURN =
0x0008
ENABLE_LVB_GRID_WORLDWIDE =
0x0010
FOREGROUND_BLUE =

Console text attributes (foreground colors)

0x0001
FOREGROUND_GREEN =
0x0002
FOREGROUND_RED =
0x0004
FOREGROUND_INTENSITY =
0x0008
BACKGROUND_BLUE =

Console text attributes (background colors)

0x0010
BACKGROUND_GREEN =
0x0020
BACKGROUND_RED =
0x0040
BACKGROUND_INTENSITY =
0x0080
COMMON_LVB_LEADING_BYTE =

Additional text attributes

0x0100
COMMON_LVB_TRAILING_BYTE =
0x0200
COMMON_LVB_GRID_HORIZONTAL =
0x0400
COMMON_LVB_GRID_LVERTICAL =
0x0800
COMMON_LVB_GRID_RVERTICAL =
0x1000
COMMON_LVB_REVERSE_VIDEO =
0x4000
COMMON_LVB_UNDERSCORE =
0x8000
ANSI_TO_WINDOWS_FG =

ANSI color number to Windows console attribute mapping Maps ANSI color indices (0-15) to Windows FOREGROUND/BACKGROUND values

[
  0,                                                              # 0: Black
  FOREGROUND_RED,                                                 # 1: Red
  FOREGROUND_GREEN,                                               # 2: Green
  FOREGROUND_RED | FOREGROUND_GREEN,                              # 3: Yellow
  FOREGROUND_BLUE,                                                # 4: Blue
  FOREGROUND_RED | FOREGROUND_BLUE,                               # 5: Magenta
  FOREGROUND_GREEN | FOREGROUND_BLUE,                             # 6: Cyan
  FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE,            # 7: White
  FOREGROUND_INTENSITY,                                           # 8: Bright Black (Gray)
  FOREGROUND_RED | FOREGROUND_INTENSITY,                          # 9: Bright Red
  FOREGROUND_GREEN | FOREGROUND_INTENSITY,                        # 10: Bright Green
  FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY,       # 11: Bright Yellow
  FOREGROUND_BLUE | FOREGROUND_INTENSITY,                         # 12: Bright Blue
  FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_INTENSITY,        # 13: Bright Magenta
  FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY,      # 14: Bright Cyan
  FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY # 15: Bright White
].freeze
ANSI_TO_WINDOWS_BG =
[
  0,                                                              # 0: Black
  BACKGROUND_RED,                                                 # 1: Red
  BACKGROUND_GREEN,                                               # 2: Green
  BACKGROUND_RED | BACKGROUND_GREEN,                              # 3: Yellow
  BACKGROUND_BLUE,                                                # 4: Blue
  BACKGROUND_RED | BACKGROUND_BLUE,                               # 5: Magenta
  BACKGROUND_GREEN | BACKGROUND_BLUE,                             # 6: Cyan
  BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE,            # 7: White
  BACKGROUND_INTENSITY,                                           # 8: Bright Black (Gray)
  BACKGROUND_RED | BACKGROUND_INTENSITY,                          # 9: Bright Red
  BACKGROUND_GREEN | BACKGROUND_INTENSITY,                        # 10: Bright Green
  BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_INTENSITY,       # 11: Bright Yellow
  BACKGROUND_BLUE | BACKGROUND_INTENSITY,                         # 12: Bright Blue
  BACKGROUND_RED | BACKGROUND_BLUE | BACKGROUND_INTENSITY,        # 13: Bright Magenta
  BACKGROUND_GREEN | BACKGROUND_BLUE | BACKGROUND_INTENSITY,      # 14: Bright Cyan
  BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE | BACKGROUND_INTENSITY # 15: Bright White
].freeze
INVALID_HANDLE_VALUE =

Sentinel returned by GetStdHandle on failure.

(1 << (Fiddle::SIZEOF_VOIDP * 8)) - 1
CONSOLE_SCREEN_BUFFER_INFO_SIZE =

CONSOLE_SCREEN_BUFFER_INFO structure layout: typedef struct _CONSOLE_SCREEN_BUFFER_INFO

COORD      dwSize;           // 4 bytes (2x SHORT)
COORD      dwCursorPosition; // 4 bytes (2x SHORT)
WORD       wAttributes;      // 2 bytes
SMALL_RECT srWindow;         // 8 bytes (4x SHORT)
COORD      dwMaximumWindowSize; // 4 bytes (2x SHORT)

CONSOLE_SCREEN_BUFFER_INFO; Total: 22 bytes

22
CONSOLE_CURSOR_INFO_SIZE =

CONSOLE_CURSOR_INFO structure layout: typedef struct _CONSOLE_CURSOR_INFO

DWORD dwSize;    // 4 bytes
BOOL  bVisible;  // 4 bytes

CONSOLE_CURSOR_INFO; Total: 8 bytes

8

Class Method Summary collapse

Class Method Details

.ansi_to_windows_attributes(foreground: nil, background: nil) ⇒ Integer

Convert ANSI color number to Windows console attributes

Parameters:

  • foreground (Integer, nil) (defaults to: nil)

    ANSI foreground color (0-15)

  • background (Integer, nil) (defaults to: nil)

    ANSI background color (0-15)

Returns:

  • (Integer)

    Windows console attribute value



607
608
609
610
611
612
# File 'lib/rich/win32_console.rb', line 607

def ansi_to_windows_attributes(foreground: nil, background: nil)
  attributes = 0
  attributes |= ANSI_TO_WINDOWS_FG[foreground] if foreground && foreground < 16
  attributes |= ANSI_TO_WINDOWS_BG[background] if background && background < 16
  attributes
end

.clear_screen(handle = stdout_handle) ⇒ Boolean

Clear the entire screen

Parameters:

  • handle (Integer) (defaults to: stdout_handle)

    Console handle (defaults to stdout)

Returns:

  • (Boolean)

    Success status



491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
# File 'lib/rich/win32_console.rb', line 491

def clear_screen(handle = stdout_handle)
  return false unless windows? && handle

  info = get_screen_buffer_info(handle)
  return false unless info

  size = info[:size][:width] * info[:size][:height]
  attributes = info[:attributes]

  fill_output_character(" ", size, 0, 0, handle)
  fill_output_attribute(attributes, size, 0, 0, handle)
  set_cursor_position(0, 0, handle)

  true
end

.cursor_backward(columns = 1, handle = stdout_handle) ⇒ Boolean

Move cursor backward (left)

Parameters:

  • columns (Integer) (defaults to: 1)

    Number of columns to move

  • handle (Integer) (defaults to: stdout_handle)

    Console handle (defaults to stdout)

Returns:

  • (Boolean)

    Success status



581
582
583
584
585
586
587
588
589
# File 'lib/rich/win32_console.rb', line 581

def cursor_backward(columns = 1, handle = stdout_handle)
  return false unless windows?

  pos = get_cursor_position
  return false unless pos

  new_x = [pos[0] - columns, 0].max
  set_cursor_position(new_x, pos[1], handle)
end

.cursor_down(lines = 1, handle = stdout_handle) ⇒ Boolean

Move cursor down

Parameters:

  • lines (Integer) (defaults to: 1)

    Number of lines to move down

  • handle (Integer) (defaults to: stdout_handle)

    Console handle (defaults to stdout)

Returns:

  • (Boolean)

    Success status



545
546
547
548
549
550
551
552
553
554
555
556
557
# File 'lib/rich/win32_console.rb', line 545

def cursor_down(lines = 1, handle = stdout_handle)
  return false unless windows?

  info = get_screen_buffer_info(handle)
  return false unless info

  pos = get_cursor_position
  return false unless pos

  max_y = info[:size][:height] - 1
  new_y = [pos[1] + lines, max_y].min
  set_cursor_position(pos[0], new_y, handle)
end

.cursor_forward(columns = 1, handle = stdout_handle) ⇒ Boolean

Move cursor forward (right)

Parameters:

  • columns (Integer) (defaults to: 1)

    Number of columns to move

  • handle (Integer) (defaults to: stdout_handle)

    Console handle (defaults to stdout)

Returns:

  • (Boolean)

    Success status



563
564
565
566
567
568
569
570
571
572
573
574
575
# File 'lib/rich/win32_console.rb', line 563

def cursor_forward(columns = 1, handle = stdout_handle)
  return false unless windows?

  info = get_screen_buffer_info(handle)
  return false unless info

  pos = get_cursor_position
  return false unless pos

  max_x = info[:size][:width] - 1
  new_x = [pos[0] + columns, max_x].min
  set_cursor_position(new_x, pos[1], handle)
end

.cursor_to_column(column = 0, handle = stdout_handle) ⇒ Boolean

Move cursor to the beginning of the line

Parameters:

  • handle (Integer) (defaults to: stdout_handle)

    Console handle (defaults to stdout)

Returns:

  • (Boolean)

    Success status



594
595
596
597
598
599
600
601
# File 'lib/rich/win32_console.rb', line 594

def cursor_to_column(column = 0, handle = stdout_handle)
  return false unless windows?

  pos = get_cursor_position
  return false unless pos

  set_cursor_position(column, pos[1], handle)
end

.cursor_up(lines = 1, handle = stdout_handle) ⇒ Boolean

Move cursor up

Parameters:

  • lines (Integer) (defaults to: 1)

    Number of lines to move up

  • handle (Integer) (defaults to: stdout_handle)

    Console handle (defaults to stdout)

Returns:

  • (Boolean)

    Success status



531
532
533
534
535
536
537
538
539
# File 'lib/rich/win32_console.rb', line 531

def cursor_up(lines = 1, handle = stdout_handle)
  return false unless windows?

  pos = get_cursor_position
  return false unless pos

  new_y = [pos[1] - lines, 0].max
  set_cursor_position(pos[0], new_y, handle)
end

.disable_ansi!Boolean

Disable virtual terminal (ANSI) processing

Returns:

  • (Boolean)

    True if ANSI mode was successfully disabled



274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
# File 'lib/rich/win32_console.rb', line 274

def disable_ansi!
  return false unless windows?

  handle = stdout_handle
  return false unless handle

  current_mode = get_console_mode(handle)
  return false unless current_mode

  new_mode = current_mode & ~ENABLE_VIRTUAL_TERMINAL_PROCESSING
  result = set_console_mode(new_mode, handle)

  @supports_ansi = !result if result
  result
end

.enable_ansi!Boolean

Enable virtual terminal (ANSI) processing

Returns:

  • (Boolean)

    True if ANSI mode was successfully enabled



255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
# File 'lib/rich/win32_console.rb', line 255

def enable_ansi!
  return true unless windows?  # Already supported on Unix

  handle = stdout_handle
  return false unless handle

  current_mode = get_console_mode(handle)
  return false unless current_mode

  new_mode = current_mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING
  result = set_console_mode(new_mode, handle)

  # Update cached value
  @supports_ansi = result
  result
end

.erase_line(handle = stdout_handle) ⇒ Boolean

Erase from cursor to end of line

Parameters:

  • handle (Integer) (defaults to: stdout_handle)

    Console handle (defaults to stdout)

Returns:

  • (Boolean)

    Success status



510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
# File 'lib/rich/win32_console.rb', line 510

def erase_line(handle = stdout_handle)
  return false unless windows? && handle

  info = get_screen_buffer_info(handle)
  return false unless info

  x = info[:cursor_position][:x]
  y = info[:cursor_position][:y]
  length = info[:size][:width] - x
  attributes = info[:attributes]

  fill_output_character(" ", length, x, y, handle)
  fill_output_attribute(attributes, length, x, y, handle)

  true
end

.fill_output_attribute(attribute, length, x, y, handle = stdout_handle) ⇒ Integer?

Fill console output with an attribute

Parameters:

  • attribute (Integer)

    Attribute to fill with

  • length (Integer)

    Number of cells to fill

  • x (Integer)

    Starting column

  • y (Integer)

    Starting row

  • handle (Integer) (defaults to: stdout_handle)

    Console handle (defaults to stdout)

Returns:

  • (Integer, nil)

    Number of cells written or nil on failure



410
411
412
413
414
415
416
417
418
419
420
# File 'lib/rich/win32_console.rb', line 410

def fill_output_attribute(attribute, length, x, y, handle = stdout_handle)
  return nil unless windows? && handle

  coord = ((y & 0xFFFF) << 16) | (x & 0xFFFF)
  written_ptr = Fiddle::Pointer.malloc(Fiddle::SIZEOF_LONG, Fiddle::RUBY_FREE)

  result = FillConsoleOutputAttribute(handle, attribute, length, coord, written_ptr)
  return nil if result == 0

  written_ptr[0, Fiddle::SIZEOF_LONG].unpack1("L")
end

.fill_output_character(char, length, x, y, handle = stdout_handle) ⇒ Integer?

Fill console output with a character

Parameters:

  • char (String)

    Character to fill with

  • length (Integer)

    Number of cells to fill

  • x (Integer)

    Starting column

  • y (Integer)

    Starting row

  • handle (Integer) (defaults to: stdout_handle)

    Console handle (defaults to stdout)

Returns:

  • (Integer, nil)

    Number of characters written or nil on failure



386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
# File 'lib/rich/win32_console.rb', line 386

def fill_output_character(char, length, x, y, handle = stdout_handle)
  return nil unless windows? && handle

  # COORD is two SHORTs marshalled as a packed DWORD; mask both fields.
  coord = ((y & 0xFFFF) << 16) | (x & 0xFFFF)
  written_ptr = Fiddle::Pointer.malloc(Fiddle::SIZEOF_LONG, Fiddle::RUBY_FREE)

  # cCharacter is a single UTF-16 code unit. Characters above U+FFFF
  # cannot be represented; mask to the low 16 bits to make the truncation
  # explicit rather than relying on Fiddle's silent narrowing.
  char_code = char.ord & 0xFFFF
  result = FillConsoleOutputCharacterW(handle, char_code, length, coord, written_ptr)
  return nil if result == 0

  written_ptr[0, Fiddle::SIZEOF_LONG].unpack1("L")
end

.get_console_mode(handle = stdout_handle) ⇒ Integer?

Get the current console mode for a handle

Parameters:

  • handle (Integer) (defaults to: stdout_handle)

    Console handle (defaults to stdout)

Returns:

  • (Integer, nil)

    Console mode flags or nil on failure



217
218
219
220
221
222
223
224
225
# File 'lib/rich/win32_console.rb', line 217

def get_console_mode(handle = stdout_handle)
  return nil unless windows? && handle

  mode_ptr = Fiddle::Pointer.malloc(Fiddle::SIZEOF_LONG, Fiddle::RUBY_FREE)
  result = GetConsoleMode(handle, mode_ptr)
  return nil if result == 0

  mode_ptr[0, Fiddle::SIZEOF_LONG].unpack1("L")
end

.get_cursor_positionArray<Integer>?

Get current cursor position

Returns:

  • (Array<Integer>, nil)
    x, y

    or nil on failure



335
336
337
338
339
340
341
342
# File 'lib/rich/win32_console.rb', line 335

def get_cursor_position
  return nil unless windows?

  info = get_screen_buffer_info
  return nil unless info

  [info[:cursor_position][:x], info[:cursor_position][:y]]
end

.get_screen_buffer_info(handle = stdout_handle) ⇒ Hash?

Get console screen buffer info

Parameters:

  • handle (Integer) (defaults to: stdout_handle)

    Console handle (defaults to stdout)

Returns:

  • (Hash, nil)

    Screen buffer info or nil on failure



293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
# File 'lib/rich/win32_console.rb', line 293

def get_screen_buffer_info(handle = stdout_handle)
  return nil unless windows? && handle

  buffer = Fiddle::Pointer.malloc(CONSOLE_SCREEN_BUFFER_INFO_SIZE, Fiddle::RUBY_FREE)
  result = GetConsoleScreenBufferInfo(handle, buffer)
  return nil if result == 0

  data = buffer[0, CONSOLE_SCREEN_BUFFER_INFO_SIZE]

  # Unpack the structure
  values = data.unpack("s2 s2 S s4 s2")

  {
    size: { width: values[0], height: values[1] },
    cursor_position: { x: values[2], y: values[3] },
    attributes: values[4],
    window: {
      left: values[5],
      top: values[6],
      right: values[7],
      bottom: values[8]
    },
    max_window_size: { width: values[9], height: values[10] }
  }
end

.get_sizeArray<Integer>?

Get the console window dimensions

Returns:

  • (Array<Integer>, nil)
    width, height

    or nil on failure



321
322
323
324
325
326
327
328
329
330
331
# File 'lib/rich/win32_console.rb', line 321

def get_size
  return nil unless windows?

  info = get_screen_buffer_info
  return nil unless info

  width = info[:window][:right] - info[:window][:left] + 1
  height = info[:window][:bottom] - info[:window][:top] + 1

  [width, height]
end

.get_text_attributesInteger?

Get current text attributes

Returns:

  • (Integer, nil)

    Current attributes or nil on failure



370
371
372
373
374
375
376
377
# File 'lib/rich/win32_console.rb', line 370

def get_text_attributes
  return nil unless windows?

  info = get_screen_buffer_info
  return nil unless info

  info[:attributes]
end

.hide_cursor(handle = stdout_handle) ⇒ Boolean

Hide the cursor

Parameters:

  • handle (Integer) (defaults to: stdout_handle)

    Console handle (defaults to stdout)

Returns:

  • (Boolean)

    Success status



443
444
445
# File 'lib/rich/win32_console.rb', line 443

def hide_cursor(handle = stdout_handle)
  set_cursor_visibility(false, handle)
end

.last_errorInteger

Returns The calling thread’s last Win32 error code (0 if none).

Returns:

  • (Integer)

    The calling thread’s last Win32 error code (0 if none)



194
195
196
197
198
# File 'lib/rich/win32_console.rb', line 194

def last_error
  return 0 unless windows?

  GetLastError()
end

.set_console_mode(mode, handle = stdout_handle) ⇒ Boolean

Set the console mode for a handle

Parameters:

  • mode (Integer)

    Console mode flags

  • handle (Integer) (defaults to: stdout_handle)

    Console handle (defaults to stdout)

Returns:

  • (Boolean)

    Success status



231
232
233
234
235
# File 'lib/rich/win32_console.rb', line 231

def set_console_mode(mode, handle = stdout_handle)
  return false unless windows? && handle

  SetConsoleMode(handle, mode) != 0
end

.set_cursor_position(x, y, handle = stdout_handle) ⇒ Boolean

Set cursor position

Parameters:

  • x (Integer)

    Column (0-indexed)

  • y (Integer)

    Row (0-indexed)

  • handle (Integer) (defaults to: stdout_handle)

    Console handle (defaults to stdout)

Returns:

  • (Boolean)

    Success status



349
350
351
352
353
354
355
356
# File 'lib/rich/win32_console.rb', line 349

def set_cursor_position(x, y, handle = stdout_handle)
  return false unless windows? && handle

  # Pack COORD structure as DWORD (low word = X, high word = Y); mask
  # both fields so a stray high bit can't corrupt the other coordinate.
  coord = ((y & 0xFFFF) << 16) | (x & 0xFFFF)
  SetConsoleCursorPosition(handle, coord) != 0
end

.set_cursor_visibility(visible, handle = stdout_handle) ⇒ Boolean

Set cursor visibility

Parameters:

  • visible (Boolean)

    Whether cursor should be visible

  • handle (Integer) (defaults to: stdout_handle)

    Console handle (defaults to stdout)

Returns:

  • (Boolean)

    Success status



451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
# File 'lib/rich/win32_console.rb', line 451

def set_cursor_visibility(visible, handle = stdout_handle)
  return false unless windows? && handle

  # Get current cursor info
  buffer = Fiddle::Pointer.malloc(CONSOLE_CURSOR_INFO_SIZE, Fiddle::RUBY_FREE)
  result = GetConsoleCursorInfo(handle, buffer)
  return false if result == 0

  # Modify visibility
  data = buffer[0, CONSOLE_CURSOR_INFO_SIZE].unpack("L L")
  cursor_size = data[0]
  buffer[0, CONSOLE_CURSOR_INFO_SIZE] = [cursor_size, visible ? 1 : 0].pack("L L")

  SetConsoleCursorInfo(handle, buffer) != 0
end

.set_text_attribute(attributes, handle = stdout_handle) ⇒ Boolean

Set console text attributes (foreground/background colors)

Parameters:

  • attributes (Integer)

    Attribute flags

  • handle (Integer) (defaults to: stdout_handle)

    Console handle (defaults to stdout)

Returns:

  • (Boolean)

    Success status



362
363
364
365
366
# File 'lib/rich/win32_console.rb', line 362

def set_text_attribute(attributes, handle = stdout_handle)
  return false unless windows? && handle

  SetConsoleTextAttribute(handle, attributes) != 0
end

.set_title(title) ⇒ Boolean

Set console window title

Parameters:

  • title (String)

    New window title

Returns:

  • (Boolean)

    Success status



425
426
427
428
429
430
431
# File 'lib/rich/win32_console.rb', line 425

def set_title(title)
  return false unless windows?

  # Convert to UTF-16LE with null terminator
  wide_title = (title + "\0").encode("UTF-16LE")
  SetConsoleTitleW(Fiddle::Pointer[wide_title]) != 0
end

.show_cursor(handle = stdout_handle) ⇒ Boolean

Show the cursor

Parameters:

  • handle (Integer) (defaults to: stdout_handle)

    Console handle (defaults to stdout)

Returns:

  • (Boolean)

    Success status



436
437
438
# File 'lib/rich/win32_console.rb', line 436

def show_cursor(handle = stdout_handle)
  set_cursor_visibility(true, handle)
end

.stderr_handleInteger?

Returns Handle to stderr, or nil if unavailable.

Returns:

  • (Integer, nil)

    Handle to stderr, or nil if unavailable



188
189
190
191
# File 'lib/rich/win32_console.rb', line 188

def stderr_handle
  return nil unless windows?
  @stderr_handle ||= valid_handle(GetStdHandle(STD_ERROR_HANDLE))
end

.stdin_handleInteger?

Returns Handle to stdin, or nil if unavailable.

Returns:

  • (Integer, nil)

    Handle to stdin, or nil if unavailable



182
183
184
185
# File 'lib/rich/win32_console.rb', line 182

def stdin_handle
  return nil unless windows?
  @stdin_handle ||= valid_handle(GetStdHandle(STD_INPUT_HANDLE))
end

.stdout_handleInteger?

Returns Handle to stdout, or nil if unavailable.

Returns:

  • (Integer, nil)

    Handle to stdout, or nil if unavailable



176
177
178
179
# File 'lib/rich/win32_console.rb', line 176

def stdout_handle
  return nil unless windows?
  @stdout_handle ||= valid_handle(GetStdHandle(STD_OUTPUT_HANDLE))
end

.supports_ansi?Boolean

Check if virtual terminal (ANSI) processing is supported

Returns:

  • (Boolean)

    True if ANSI escape sequences are supported



239
240
241
242
243
244
245
246
247
248
249
250
251
# File 'lib/rich/win32_console.rb', line 239

def supports_ansi?
  return @supports_ansi if defined?(@supports_ansi)

  unless windows?
    @supports_ansi = true  # Unix terminals support ANSI
    return @supports_ansi
  end

  mode = get_console_mode
  return @supports_ansi = false if mode.nil?

  @supports_ansi = (mode & ENABLE_VIRTUAL_TERMINAL_PROCESSING) != 0
end

.valid_handle(handle) ⇒ Object

Normalize a raw GetStdHandle result: NULL (0) means “no such handle” (e.g. output redirected to a pipe in a GUI process) and INVALID_HANDLE_VALUE means an error. Both become nil so callers and the ‘&& handle` guards short-circuit instead of issuing API calls against a bad handle. A failed lookup is intentionally NOT memoized.



205
206
207
208
209
210
211
212
# File 'lib/rich/win32_console.rb', line 205

def valid_handle(handle)
  return nil if handle.nil?

  value = handle.respond_to?(:to_i) ? handle.to_i : handle
  return nil if value.zero? || value == INVALID_HANDLE_VALUE

  handle
end

.windows?Boolean

Returns Whether the current platform is Windows.

Returns:

  • (Boolean)

    Whether the current platform is Windows



171
172
173
# File 'lib/rich/win32_console.rb', line 171

def windows?
  Gem.win_platform?
end

.write_console(text, handle = stdout_handle) ⇒ Integer?

Write text to console (bypassing Ruby’s IO buffering)

Parameters:

  • text (String)

    Text to write

  • handle (Integer) (defaults to: stdout_handle)

    Console handle (defaults to stdout)

Returns:

  • (Integer, nil)

    Number of characters written or nil on failure



471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
# File 'lib/rich/win32_console.rb', line 471

def write_console(text, handle = stdout_handle)
  return nil unless windows? && handle

  wide_text = text.encode("UTF-16LE")
  # WriteConsoleW counts UTF-16 code UNITS, not Ruby characters (code
  # points). Characters outside the BMP (e.g. emoji) encode as 2 units, so
  # using text.length would truncate the tail. Derive the count from the
  # encoded buffer.
  char_count = wide_text.bytesize / 2
  written_ptr = Fiddle::Pointer.malloc(Fiddle::SIZEOF_LONG, Fiddle::RUBY_FREE)

  result = WriteConsoleW(handle, Fiddle::Pointer[wide_text], char_count, written_ptr, nil)
  return nil if result == 0

  written_ptr[0, Fiddle::SIZEOF_LONG].unpack1("L")
end