rufio 0.83.0 → 0.90.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: b75328867a09d6dfa3b4c759e242d239ab8d087540a43af19471473cc0515c4c
4
- data.tar.gz: 798358451d71a8b74ee70ce2d362b2f24fb887e6de9b574f700c0f25a449e389
3
+ metadata.gz: f01cdc5788947872cf74f4a03e33aebe5d14d8077d141a5446c22a5ec5347567
4
+ data.tar.gz: 88ef6deb3513d4da16c74b1397bb3d0e23063fcb6cf94cf144694e7bd2754f2a
5
5
  SHA512:
6
- metadata.gz: 8072bc03c642bbc3043caca4cfa4899c72b6622ab7a74517ef7964010a9257e938005191de9a8e8aaed444749108ef1ef983b6b4dc8483aa8b4d2a6a8cb075a6
7
- data.tar.gz: d6fb6e10e070e94120518d5a883bd241b46a957e7938b375b09e7dc3e28e73e9652aabe7b6a191e2575872764919581ac6d4b163d48fbb2d5dcd50e7740e2e2f
6
+ metadata.gz: 03be7df8ea060bde82844aaab82fddc4d7d62dc5af7db7bb4d752d012a788486a54e53fcad7a9d671d62229da01b526b3472d1777fa67beb32e28496835fb68c
7
+ data.tar.gz: 87ad971cd279084cc2df4c955c93948444ac6736eb0d11fc35b1984a392aa4ec5946cc58363317317c8645abcf820397f365de2a00a5df741e6e9121362df5da
data/CHANGELOG.md CHANGED
@@ -5,6 +5,23 @@ All notable changes to rufio will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.90.0] - 2026-03-22
9
+
10
+ ### Added
11
+ - **日本語入力(マルチバイト文字)対応**: コマンドモードおよびフィルターモードでマルチバイト文字(日本語等)の入力をサポート
12
+ - UTF-8マルチバイトシーケンスを正しく読み取り、コマンドバッファに追加
13
+ - `MultibyteInputReader` クラスを実装し、マルチバイト文字の読み取り・バリデーション・エラーハンドリングを担当
14
+
15
+ ## [0.83.1] - 2026-03-20
16
+
17
+ ### Changed
18
+ - **Windows console input detection rewritten using Win32 API**: Replaced the background thread + Queue workaround (introduced in v0.82.1 to handle ESC key detection on Windows) with `GetNumberOfConsoleInputEvents` via the Win32 API (`fiddle` stdlib)
19
+ - The previous approach had a race condition where the background `STDIN.read(1)` thread competed with `STDIN.getch` calls in dialogs, potentially dropping dialog input (including multi-byte/Japanese characters)
20
+ - Background thread, Queue, and `windows_read_next_byte` helper are removed
21
+ - `read_next_input_byte` is now a single unified implementation for both Windows and Unix (`read_nonblock`)
22
+ - Cygwin is excluded from the Windows path since it provides POSIX-compatible `IO.select`
23
+ - Falls back to `read_nonblock` if `Fiddle::DLError` occurs (no extra gem required — `fiddle` is a Ruby stdlib)
24
+
8
25
  ## [0.83.0] - 2026-03-14
9
26
 
10
27
  ### Added
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rufio
4
+ # ターミナルからのマルチバイト入力(UTF-8)を読み込むクラス。
5
+ # read_nonblock(1) は1バイトずつしか読まないため、日本語などのマルチバイト文字が
6
+ # 複数のイベントに分割される問題を解決する。
7
+ class MultibyteInputReader
8
+ def initialize(io = STDIN)
9
+ @io = io
10
+ end
11
+
12
+ # 1文字(マルチバイト対応)を読み込む。
13
+ # 読み込めない場合は nil を返す。
14
+ def read_char
15
+ byte = @io.read_nonblock(1)
16
+ return nil if byte.nil?
17
+
18
+ remaining = utf8_remaining_bytes(byte.ord)
19
+ return byte.force_encoding(Encoding::UTF_8) if remaining == 0
20
+
21
+ buf = byte.b
22
+ remaining.times do
23
+ next_byte = @io.read_nonblock(1)
24
+ return nil if next_byte.nil?
25
+
26
+ buf << next_byte.b
27
+ end
28
+
29
+ result = buf.force_encoding(Encoding::UTF_8)
30
+ result.valid_encoding? ? result : nil
31
+ rescue IO::WaitReadable, IO::EAGAINWaitReadable, EOFError
32
+ nil
33
+ end
34
+
35
+ private
36
+
37
+ def utf8_remaining_bytes(byte_val)
38
+ case byte_val
39
+ when 0x00..0x7F then 0 # ASCII(ESC 0x1B を含む)
40
+ when 0xC0..0xDF then 1 # 2バイト文字
41
+ when 0xE0..0xEF then 2 # 3バイト文字(日本語など)
42
+ when 0xF0..0xF7 then 3 # 4バイト文字(絵文字など)
43
+ else 0
44
+ end
45
+ end
46
+ end
47
+ end
@@ -47,6 +47,7 @@ module Rufio
47
47
  end
48
48
  @running = false
49
49
  @test_mode = test_mode
50
+ @multibyte_reader = MultibyteInputReader.new(STDIN)
50
51
  @command_mode_active = false
51
52
  @command_input = ""
52
53
  @command_mode = CommandMode.new
@@ -189,23 +190,6 @@ module Rufio
189
190
  print "\e[?1003h\e[?1006h"
190
191
  STDOUT.flush
191
192
 
192
- # Windows: IO.select + read_nonblockはコンソールハンドルのESCキーを
193
- # 検出できない場合がある。バックグラウンドスレッドでSTDINを読み取り
194
- # Queueに格納することで信頼性のある入力検出を実現する。
195
- if windows?
196
- @windows_input_queue = Queue.new
197
- @windows_input_thread = Thread.new do
198
- loop do
199
- begin
200
- byte = STDIN.read(1)
201
- @windows_input_queue << byte if byte
202
- rescue
203
- break
204
- end
205
- end
206
- end
207
- end
208
-
209
193
  # re-acquire terminal size (just in case)
210
194
  update_screen_size
211
195
  end
@@ -217,31 +201,37 @@ module Rufio
217
201
  @screen_width, @screen_height = console.winsize.reverse
218
202
  end
219
203
 
204
+ # Cygwinは POSIX互換のIO.selectが使えるため除外
220
205
  def windows?
221
- RUBY_PLATFORM =~ /mswin|mingw|cygwin/ ? true : false
206
+ RUBY_PLATFORM =~ /mswin|mingw/ ? true : false
222
207
  end
223
208
 
224
- # Windows: エスケープシーケンスの後続バイトをQueueから短いタイムアウトで読み取る
225
- # VT対応ターミナルでの矢印キー(\e[A 等)の後続バイト読み取りに使用
226
- def windows_read_next_byte
227
- deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + 0.005 # 5ms
228
- loop do
229
- begin
230
- return @windows_input_queue.pop(true)
231
- rescue ThreadError
232
- return nil if Process.clock_gettime(Process::CLOCK_MONOTONIC) >= deadline
233
- sleep 0.001
234
- end
235
- end
209
+ # Windows: GetNumberOfConsoleInputEvents でコンソール入力バッファを確認する。
210
+ # IO.select はWindowsコンソールハンドルでESCキーを取りこぼすため使用しない。
211
+ # fiddle はRuby標準ライブラリなので追加gemは不要。
212
+ def windows_console_input_available?
213
+ require 'fiddle'
214
+ @win32_kernel32 ||= Fiddle.dlopen('kernel32')
215
+ @win32_get_std_handle ||= Fiddle::Function.new(
216
+ @win32_kernel32['GetStdHandle'],
217
+ [Fiddle::TYPE_INT], Fiddle::TYPE_VOIDP
218
+ )
219
+ @win32_get_num_events ||= Fiddle::Function.new(
220
+ @win32_kernel32['GetNumberOfConsoleInputEvents'],
221
+ [Fiddle::TYPE_VOIDP, Fiddle::TYPE_VOIDP], Fiddle::TYPE_INT
222
+ )
223
+ handle = @win32_get_std_handle.call(0xFFFFFFF6) # STD_INPUT_HANDLE = (DWORD)(-10)
224
+ count_ptr = Fiddle::Pointer.malloc(4)
225
+ @win32_get_num_events.call(handle, count_ptr)
226
+ count_ptr[0, 4].unpack1('L') > 0
227
+ rescue Fiddle::DLError
228
+ # fiddle が使えない場合は常に入力ありとみなし read_nonblock に任せる
229
+ true
236
230
  end
237
231
 
238
232
  # エスケープシーケンスの後続バイトを読み取る(Windows/Unix共通ヘルパー)
239
233
  def read_next_input_byte
240
- if @windows_input_queue
241
- windows_read_next_byte
242
- else
243
- STDIN.read_nonblock(1) rescue nil
244
- end
234
+ STDIN.read_nonblock(1) rescue nil
245
235
  end
246
236
 
247
237
  def cleanup_terminal
@@ -250,13 +240,6 @@ module Rufio
250
240
  STDIN.cooked!
251
241
  end
252
242
 
253
- # Windowsバックグラウンド入力スレッドを停止
254
- if @windows_input_thread
255
- @windows_input_thread.kill rescue nil
256
- @windows_input_thread = nil
257
- @windows_input_queue = nil
258
- end
259
-
260
243
  # マウスレポートを無効化
261
244
  print "\e[?1003l\e[?1006l"
262
245
  STDOUT.flush
@@ -442,31 +425,23 @@ module Rufio
442
425
  private
443
426
 
444
427
  # ノンブロッキング入力処理(ゲームループ用)
445
- # Windows: Queueベース、Unix: IO.select + read_nonblock
428
+ # Windows: GetNumberOfConsoleInputEvents で入力確認後 read_nonblock
429
+ # Unix: IO.select(timeout=0) で入力確認後 read_nonblock
446
430
  def handle_input_nonblocking
447
431
  # 入力バイトを1つ読み取る
448
- if @windows_input_queue
449
- # Windows: バックグラウンドスレッドのQueueからノンブロッキングで読み取る
450
- begin
451
- input = @windows_input_queue.pop(true)
452
- rescue ThreadError
453
- return false
454
- end
432
+ if windows?
433
+ # Windows: IO.selectはESCキーを取りこぼすため Win32 API で入力確認
434
+ return false unless windows_console_input_available?
455
435
  else
456
436
  # Unix: 0msタイムアウトで即座にチェック(30FPS = 33.33ms/frame)
457
- ready = IO.select([STDIN], nil, nil, 0)
458
- return false unless ready
459
-
460
- begin
461
- # read_nonblockを使ってノンブロッキングで1文字読み取る
462
- input = STDIN.read_nonblock(1)
463
- rescue IO::WaitReadable, IO::EAGAINWaitReadable
464
- # 入力が利用できない
465
- return false
466
- rescue Errno::ENOTTY, Errno::ENODEV
467
- # ターミナルでない環境
468
- return false
469
- end
437
+ return false unless IO.select([STDIN], nil, nil, 0)
438
+ end
439
+
440
+ begin
441
+ input = @multibyte_reader.read_char
442
+ return false if input.nil?
443
+ rescue Errno::ENOTTY, Errno::ENODEV
444
+ return false
470
445
  end
471
446
 
472
447
  # コマンドモードがアクティブな場合は、エスケープシーケンス処理をスキップ
@@ -802,8 +777,8 @@ module Rufio
802
777
  # Backspace
803
778
  @command_input.chop! unless @command_input.empty?
804
779
  else
805
- # 通常の文字を追加
806
- @command_input += input if input.length == 1
780
+ # 通常の文字を追加(マルチバイト文字含む)
781
+ @command_input += input unless input.nil? || input.empty?
807
782
  end
808
783
  end
809
784
 
data/lib/rufio/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Rufio
4
- VERSION = '0.83.0'
4
+ VERSION = '0.90.0'
5
5
  end
data/lib/rufio.rb CHANGED
@@ -47,6 +47,7 @@ require_relative 'rufio/native_scanner_zig'
47
47
  require_relative 'rufio/async_scanner_promise'
48
48
  require_relative 'rufio/async_scanner_fiber'
49
49
  require_relative 'rufio/parallel_scanner'
50
+ require_relative 'rufio/multibyte_input_reader'
50
51
  require_relative 'rufio/screen'
51
52
  require_relative 'rufio/renderer'
52
53
  require_relative 'rufio/tab_mode_manager'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rufio
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.83.0
4
+ version: 0.90.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - masisz
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-03-14 00:00:00.000000000 Z
11
+ date: 2026-03-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: io-console
@@ -182,6 +182,7 @@ files:
182
182
  - lib/rufio/keybind_handler.rb
183
183
  - lib/rufio/local_script_scanner.rb
184
184
  - lib/rufio/logger.rb
185
+ - lib/rufio/multibyte_input_reader.rb
185
186
  - lib/rufio/native/rufio_zig.bundle
186
187
  - lib/rufio/native_scanner.rb
187
188
  - lib/rufio/native_scanner_zig.rb