rufio 0.40.0 → 0.41.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 +4 -4
- data/CHANGELOG.md +25 -0
- data/bin/rufio +11 -1
- data/docs/CHANGELOG_v0.41.0.md +533 -0
- data/lib/rufio/background_command_executor.rb +64 -2
- data/lib/rufio/command_mode.rb +15 -1
- data/lib/rufio/file_preview.rb +28 -11
- data/lib/rufio/renderer.rb +15 -2
- data/lib/rufio/terminal_ui.rb +145 -47
- data/lib/rufio/text_utils.rb +42 -19
- data/lib/rufio/version.rb +1 -1
- metadata +3 -3
- data/publish_gem.zsh +0 -131
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
require 'open3'
|
|
4
4
|
|
|
5
5
|
module Rufio
|
|
6
|
-
#
|
|
6
|
+
# バックグラウンドでシェルコマンドまたはRubyコードを実行するクラス
|
|
7
7
|
class BackgroundCommandExecutor
|
|
8
8
|
attr_reader :command_logger
|
|
9
9
|
|
|
@@ -13,11 +13,12 @@ module Rufio
|
|
|
13
13
|
@command_logger = command_logger
|
|
14
14
|
@thread = nil
|
|
15
15
|
@command = nil
|
|
16
|
+
@command_type = nil # :shell または :ruby
|
|
16
17
|
@completed = false
|
|
17
18
|
@completion_message = nil
|
|
18
19
|
end
|
|
19
20
|
|
|
20
|
-
#
|
|
21
|
+
# シェルコマンドを非同期で実行
|
|
21
22
|
# @param command [String] 実行するコマンド
|
|
22
23
|
# @return [Boolean] 実行を開始した場合はtrue、既に実行中の場合はfalse
|
|
23
24
|
def execute_async(command)
|
|
@@ -25,6 +26,7 @@ module Rufio
|
|
|
25
26
|
return false if running?
|
|
26
27
|
|
|
27
28
|
@command = command
|
|
29
|
+
@command_type = :shell
|
|
28
30
|
@completed = false
|
|
29
31
|
@completion_message = nil
|
|
30
32
|
|
|
@@ -73,6 +75,54 @@ module Rufio
|
|
|
73
75
|
true
|
|
74
76
|
end
|
|
75
77
|
|
|
78
|
+
# Rubyコード(プラグインコマンド)を非同期で実行
|
|
79
|
+
# @param command_name [String] コマンド名(表示用)
|
|
80
|
+
# @param block [Proc] 実行するコードブロック
|
|
81
|
+
# @return [Boolean] 実行を開始した場合はtrue、既に実行中の場合はfalse
|
|
82
|
+
def execute_ruby_async(command_name, &block)
|
|
83
|
+
# 既に実行中の場合は新しいコマンドを開始しない
|
|
84
|
+
return false if running?
|
|
85
|
+
|
|
86
|
+
@command = command_name
|
|
87
|
+
@command_type = :ruby
|
|
88
|
+
@completed = false
|
|
89
|
+
@completion_message = nil
|
|
90
|
+
|
|
91
|
+
@thread = Thread.new do
|
|
92
|
+
begin
|
|
93
|
+
# Rubyコードを実行
|
|
94
|
+
result = block.call
|
|
95
|
+
|
|
96
|
+
# 結果をログに保存
|
|
97
|
+
output = result.to_s
|
|
98
|
+
|
|
99
|
+
@command_logger.log(
|
|
100
|
+
command_name,
|
|
101
|
+
output,
|
|
102
|
+
success: true,
|
|
103
|
+
error: nil
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
# 完了メッセージを生成
|
|
107
|
+
@completion_message = "✓ #{command_name} 完了"
|
|
108
|
+
@completed = true
|
|
109
|
+
rescue StandardError => e
|
|
110
|
+
# エラーが発生した場合もログに記録
|
|
111
|
+
@command_logger.log(
|
|
112
|
+
command_name,
|
|
113
|
+
"",
|
|
114
|
+
success: false,
|
|
115
|
+
error: e.message
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
@completion_message = "✗ #{command_name} エラー: #{e.message}"
|
|
119
|
+
@completed = true
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
true
|
|
124
|
+
end
|
|
125
|
+
|
|
76
126
|
# コマンドが実行中かどうか
|
|
77
127
|
# @return [Boolean] 実行中の場合はtrue
|
|
78
128
|
def running?
|
|
@@ -85,6 +135,18 @@ module Rufio
|
|
|
85
135
|
@completion_message
|
|
86
136
|
end
|
|
87
137
|
|
|
138
|
+
# 現在実行中のコマンド名を取得
|
|
139
|
+
# @return [String, nil] コマンド名(実行中でない場合はnil)
|
|
140
|
+
def current_command
|
|
141
|
+
running? ? @command : nil
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# コマンドタイプを取得
|
|
145
|
+
# @return [Symbol, nil] :shell または :ruby(実行中でない場合はnil)
|
|
146
|
+
def command_type
|
|
147
|
+
running? ? @command_type : nil
|
|
148
|
+
end
|
|
149
|
+
|
|
88
150
|
private
|
|
89
151
|
|
|
90
152
|
# コマンド文字列からコマンド名を抽出
|
data/lib/rufio/command_mode.rb
CHANGED
|
@@ -43,7 +43,21 @@ module Rufio
|
|
|
43
43
|
return "⚠️ コマンドが見つかりません: #{command_name}"
|
|
44
44
|
end
|
|
45
45
|
|
|
46
|
-
#
|
|
46
|
+
# バックグラウンドエグゼキュータが利用可能な場合は非同期実行
|
|
47
|
+
if @background_executor
|
|
48
|
+
command_method = @commands[command_name][:method]
|
|
49
|
+
command_display_name = command_name.to_s
|
|
50
|
+
|
|
51
|
+
if @background_executor.execute_ruby_async(command_display_name) do
|
|
52
|
+
command_method.call
|
|
53
|
+
end
|
|
54
|
+
return "🔄 バックグラウンドで実行中: #{command_display_name}"
|
|
55
|
+
else
|
|
56
|
+
return "⚠️ 既にコマンドが実行中です"
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# バックグラウンドエグゼキュータがない場合は同期実行
|
|
47
61
|
begin
|
|
48
62
|
command_method = @commands[command_name][:method]
|
|
49
63
|
command_method.call
|
data/lib/rufio/file_preview.rb
CHANGED
|
@@ -62,18 +62,27 @@ module Rufio
|
|
|
62
62
|
truncated = false
|
|
63
63
|
encoding = "UTF-8"
|
|
64
64
|
|
|
65
|
-
|
|
65
|
+
# UTF-8で読み込み、不正なバイト列は置換文字に変換
|
|
66
|
+
File.open(file_path, "r:UTF-8:UTF-8", invalid: :replace, undef: :replace, replace: '�') do |file|
|
|
66
67
|
file.each_line.with_index do |line, index|
|
|
67
68
|
break if index >= max_lines
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
line = line
|
|
69
|
+
|
|
70
|
+
begin
|
|
71
|
+
# Ensure line is properly encoded as UTF-8
|
|
72
|
+
line = line.encode('UTF-8', invalid: :replace, undef: :replace, replace: '?')
|
|
73
|
+
|
|
74
|
+
# truncate too long lines
|
|
75
|
+
if line.length > MAX_LINE_LENGTH
|
|
76
|
+
line = line[0...MAX_LINE_LENGTH] + "..."
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
lines << line.chomp
|
|
80
|
+
rescue EncodingError, ArgumentError => e
|
|
81
|
+
# If encoding fails, add placeholder
|
|
82
|
+
lines << "[encoding error in line #{index + 1}]"
|
|
72
83
|
end
|
|
73
|
-
|
|
74
|
-
lines << line.chomp
|
|
75
84
|
end
|
|
76
|
-
|
|
85
|
+
|
|
77
86
|
# check if there are more lines to read
|
|
78
87
|
truncated = !file.eof?
|
|
79
88
|
end
|
|
@@ -83,14 +92,22 @@ module Rufio
|
|
|
83
92
|
truncated: truncated,
|
|
84
93
|
encoding: encoding
|
|
85
94
|
}
|
|
86
|
-
rescue Encoding::InvalidByteSequenceError
|
|
95
|
+
rescue Encoding::InvalidByteSequenceError, Encoding::CompatibilityError
|
|
87
96
|
# try Shift_JIS if UTF-8 fails
|
|
88
97
|
begin
|
|
89
98
|
lines = []
|
|
90
|
-
File.open(file_path, "r:Shift_JIS:UTF-8") do |file|
|
|
99
|
+
File.open(file_path, "r:Shift_JIS:UTF-8", invalid: :replace, undef: :replace, replace: '�') do |file|
|
|
91
100
|
file.each_line.with_index do |line, index|
|
|
92
101
|
break if index >= max_lines
|
|
93
|
-
|
|
102
|
+
|
|
103
|
+
begin
|
|
104
|
+
# Ensure line is properly encoded as UTF-8
|
|
105
|
+
line = line.encode('UTF-8', invalid: :replace, undef: :replace, replace: '?')
|
|
106
|
+
lines << line.chomp
|
|
107
|
+
rescue EncodingError, ArgumentError => e
|
|
108
|
+
# If encoding fails, add placeholder
|
|
109
|
+
lines << "[encoding error in line #{index + 1}]"
|
|
110
|
+
end
|
|
94
111
|
end
|
|
95
112
|
truncated = !file.eof?
|
|
96
113
|
end
|
data/lib/rufio/renderer.rb
CHANGED
|
@@ -22,20 +22,33 @@ module Rufio
|
|
|
22
22
|
# Render the screen with differential updates
|
|
23
23
|
#
|
|
24
24
|
# @param screen [Screen] The back buffer to render
|
|
25
|
+
# @return [Boolean] true if rendering was performed, false if skipped
|
|
25
26
|
def render(screen)
|
|
27
|
+
# CPU最適化: Dirty rowsが空の場合は完全にスキップ
|
|
28
|
+
dirty = screen.dirty_rows
|
|
29
|
+
if dirty.empty?
|
|
30
|
+
return false
|
|
31
|
+
end
|
|
32
|
+
|
|
26
33
|
# Phase1: Only process dirty rows (rows that have changed)
|
|
27
|
-
|
|
34
|
+
rendered_count = 0
|
|
35
|
+
dirty.each do |y|
|
|
28
36
|
line = screen.row(y)
|
|
29
37
|
next if line == @front[y] # Skip if content is actually the same
|
|
30
38
|
|
|
31
39
|
# Move cursor to line y (1-indexed) and output the line
|
|
32
40
|
@output.print "\e[#{y + 1};1H#{line}"
|
|
33
41
|
@front[y] = line
|
|
42
|
+
rendered_count += 1
|
|
34
43
|
end
|
|
35
44
|
|
|
36
45
|
# Phase1: Clear dirty tracking after rendering
|
|
37
46
|
screen.clear_dirty
|
|
38
|
-
|
|
47
|
+
|
|
48
|
+
# Only flush if we actually rendered something
|
|
49
|
+
@output.flush if rendered_count > 0
|
|
50
|
+
|
|
51
|
+
true
|
|
39
52
|
end
|
|
40
53
|
|
|
41
54
|
# Resize the front buffer
|
data/lib/rufio/terminal_ui.rb
CHANGED
|
@@ -71,6 +71,10 @@ module Rufio
|
|
|
71
71
|
@cached_bookmarks = nil
|
|
72
72
|
@cached_bookmark_time = nil
|
|
73
73
|
@bookmark_cache_ttl = 1.0 # 1秒間キャッシュ
|
|
74
|
+
|
|
75
|
+
# Command execution lamp (footer indicator)
|
|
76
|
+
@completion_lamp_message = nil
|
|
77
|
+
@completion_lamp_time = nil
|
|
74
78
|
end
|
|
75
79
|
|
|
76
80
|
def start(directory_listing, keybind_handler, file_preview, background_executor = nil)
|
|
@@ -159,75 +163,143 @@ module Rufio
|
|
|
159
163
|
puts ConfigLoader.message('app.terminated')
|
|
160
164
|
end
|
|
161
165
|
|
|
162
|
-
# ゲームループパターンのmain_loop
|
|
166
|
+
# ゲームループパターンのmain_loop(CPU最適化版:フレームスキップ対応)
|
|
163
167
|
# UPDATE → DRAW → RENDER → SLEEP のサイクル
|
|
168
|
+
# 変更がない場合は描画をスキップしてCPU使用率を削減
|
|
164
169
|
def main_loop
|
|
165
|
-
|
|
166
|
-
|
|
170
|
+
# CPU最適化: 固定FPSをやめて、イベントドリブンに変更
|
|
171
|
+
# 最小スリープ時間(入力チェック間隔)
|
|
172
|
+
min_sleep_interval = 0.0333 # 30FPS(約33.33ms/フレーム)
|
|
173
|
+
check_interval = 0.1 # バックグラウンドタスクのチェック間隔
|
|
167
174
|
|
|
168
175
|
# Phase 3: Screen/Rendererを初期化
|
|
169
176
|
@screen = Screen.new(@screen_width, @screen_height)
|
|
170
177
|
@renderer = Renderer.new(@screen_width, @screen_height)
|
|
171
178
|
|
|
179
|
+
# 初回描画
|
|
180
|
+
@screen.clear
|
|
181
|
+
draw_screen_to_buffer(@screen, nil, nil)
|
|
182
|
+
@renderer.render(@screen)
|
|
183
|
+
|
|
172
184
|
last_notification_check = Time.now
|
|
185
|
+
last_lamp_check = Time.now
|
|
173
186
|
notification_message = nil
|
|
174
187
|
notification_time = nil
|
|
188
|
+
previous_notification = nil
|
|
189
|
+
previous_lamp_message = @completion_lamp_message
|
|
175
190
|
|
|
176
191
|
# FPS計測用
|
|
177
192
|
frame_times = []
|
|
178
193
|
last_frame_time = Time.now
|
|
179
194
|
current_fps = 0.0
|
|
195
|
+
last_fps_update = Time.now
|
|
196
|
+
|
|
197
|
+
# 再描画フラグ
|
|
198
|
+
needs_redraw = false
|
|
180
199
|
|
|
181
200
|
while @running
|
|
182
201
|
start = Time.now
|
|
183
202
|
|
|
203
|
+
# FPS計算(毎フレームで記録)- ループの最初で計測してsleep時間を含める
|
|
204
|
+
if @test_mode
|
|
205
|
+
frame_time = start - last_frame_time
|
|
206
|
+
last_frame_time = start
|
|
207
|
+
frame_times << frame_time
|
|
208
|
+
frame_times.shift if frame_times.size > 60 # 直近60フレームで平均
|
|
209
|
+
|
|
210
|
+
# FPS表示の更新は1秒ごと
|
|
211
|
+
if (start - last_fps_update) > 1.0
|
|
212
|
+
avg_frame_time = frame_times.sum / frame_times.size
|
|
213
|
+
current_fps = 1.0 / avg_frame_time if avg_frame_time > 0
|
|
214
|
+
last_fps_update = start
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
# test_modeでは毎フレーム描画してFPS計測の精度を上げる
|
|
218
|
+
needs_redraw = true
|
|
219
|
+
end
|
|
220
|
+
|
|
184
221
|
# UPDATE phase - ノンブロッキング入力処理
|
|
185
|
-
|
|
222
|
+
# 入力があった場合は再描画が必要
|
|
223
|
+
had_input = handle_input_nonblocking
|
|
224
|
+
needs_redraw = true if had_input
|
|
186
225
|
|
|
187
|
-
# バックグラウンドコマンドの完了チェック(0.
|
|
188
|
-
if @background_executor && (
|
|
226
|
+
# バックグラウンドコマンドの完了チェック(0.1秒ごと)
|
|
227
|
+
if @background_executor && (start - last_notification_check) > check_interval
|
|
189
228
|
if !@background_executor.running? && @background_executor.get_completion_message
|
|
190
|
-
|
|
191
|
-
|
|
229
|
+
completion_msg = @background_executor.get_completion_message
|
|
230
|
+
# 通知メッセージとして表示
|
|
231
|
+
notification_message = completion_msg
|
|
232
|
+
notification_time = start
|
|
233
|
+
# フッターのランプ表示用にも設定
|
|
234
|
+
@completion_lamp_message = completion_msg
|
|
235
|
+
@completion_lamp_time = start
|
|
192
236
|
@background_executor.instance_variable_set(:@completion_message, nil) # メッセージをクリア
|
|
237
|
+
needs_redraw = true
|
|
193
238
|
end
|
|
194
|
-
last_notification_check =
|
|
239
|
+
last_notification_check = start
|
|
195
240
|
end
|
|
196
241
|
|
|
197
|
-
#
|
|
198
|
-
if @
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
last_frame_time = Time.now
|
|
242
|
+
# バックグラウンドコマンドの実行状態が変わった場合も再描画
|
|
243
|
+
if @background_executor
|
|
244
|
+
current_running = @background_executor.running?
|
|
245
|
+
if @last_bg_running != current_running
|
|
246
|
+
@last_bg_running = current_running
|
|
247
|
+
needs_redraw = true
|
|
248
|
+
end
|
|
205
249
|
end
|
|
206
250
|
|
|
207
|
-
#
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
251
|
+
# 完了ランプの表示状態をチェック(0.5秒ごと)
|
|
252
|
+
if (start - last_lamp_check) > 0.5
|
|
253
|
+
current_lamp = @completion_lamp_message
|
|
254
|
+
if current_lamp != previous_lamp_message
|
|
255
|
+
previous_lamp_message = current_lamp
|
|
256
|
+
needs_redraw = true
|
|
257
|
+
end
|
|
258
|
+
# 完了ランプのタイムアウトチェック
|
|
259
|
+
if @completion_lamp_message && @completion_lamp_time && (start - @completion_lamp_time) >= 3.0
|
|
260
|
+
@completion_lamp_message = nil
|
|
261
|
+
needs_redraw = true
|
|
262
|
+
end
|
|
263
|
+
last_lamp_check = start
|
|
214
264
|
end
|
|
215
265
|
|
|
216
|
-
#
|
|
217
|
-
|
|
266
|
+
# 通知メッセージの変化をチェック
|
|
267
|
+
current_notification = notification_message && (start - notification_time) < 3.0 ? notification_message : nil
|
|
268
|
+
if current_notification != previous_notification
|
|
269
|
+
previous_notification = current_notification
|
|
270
|
+
notification_message = nil if current_notification.nil?
|
|
271
|
+
needs_redraw = true
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
# DRAW & RENDER phase - 変更があった場合のみ描画
|
|
275
|
+
if needs_redraw
|
|
276
|
+
# Screenバッファに描画(clearは呼ばない。必要な部分だけ更新)
|
|
277
|
+
if notification_message && (start - notification_time) < 3.0
|
|
278
|
+
draw_screen_to_buffer(@screen, notification_message, current_fps)
|
|
279
|
+
else
|
|
280
|
+
draw_screen_to_buffer(@screen, nil, current_fps)
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
# 差分レンダリング(dirty rowsのみ)
|
|
284
|
+
@renderer.render(@screen)
|
|
285
|
+
|
|
286
|
+
# 描画後にカーソルを画面外に移動
|
|
287
|
+
if !@command_mode_active
|
|
288
|
+
print "\e[#{@screen_height};#{@screen_width}H"
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
needs_redraw = false
|
|
292
|
+
end
|
|
218
293
|
|
|
219
294
|
# コマンドモードがアクティブな場合はフローティングウィンドウを表示
|
|
220
295
|
# Phase 4: 暫定的に直接描画(Screenバッファ外)
|
|
221
296
|
if @command_mode_active
|
|
222
297
|
@command_mode_ui.show_input_prompt(@command_input)
|
|
223
|
-
else
|
|
224
|
-
# カーソルを画面外に移動
|
|
225
|
-
print "\e[#{@screen_height};#{@screen_width}H"
|
|
226
298
|
end
|
|
227
299
|
|
|
228
|
-
# SLEEP phase -
|
|
300
|
+
# SLEEP phase - CPU使用率削減のため適切にスリープ
|
|
229
301
|
elapsed = Time.now - start
|
|
230
|
-
sleep_time = [
|
|
302
|
+
sleep_time = [min_sleep_interval - elapsed, 0].max
|
|
231
303
|
sleep sleep_time if sleep_time > 0
|
|
232
304
|
end
|
|
233
305
|
end
|
|
@@ -829,13 +901,36 @@ module Rufio
|
|
|
829
901
|
end
|
|
830
902
|
bookmark_text = bookmark_parts.join(" ")
|
|
831
903
|
|
|
832
|
-
# 右側の情報: FPS(test modeの時のみ)| ?:help
|
|
904
|
+
# 右側の情報: コマンド実行ランプ | FPS(test modeの時のみ)| ?:help
|
|
905
|
+
right_parts = []
|
|
906
|
+
|
|
907
|
+
# バックグラウンドコマンドの実行状態をランプで表示
|
|
908
|
+
if @background_executor
|
|
909
|
+
if @background_executor.running?
|
|
910
|
+
# 実行中ランプ(緑色の回転矢印)
|
|
911
|
+
command_name = @background_executor.current_command || "処理中"
|
|
912
|
+
right_parts << "\e[32m🔄\e[0m #{command_name}"
|
|
913
|
+
elsif @completion_lamp_message && @completion_lamp_time
|
|
914
|
+
# 完了ランプ(3秒間表示)
|
|
915
|
+
if (Time.now - @completion_lamp_time) < 3.0
|
|
916
|
+
right_parts << @completion_lamp_message
|
|
917
|
+
else
|
|
918
|
+
@completion_lamp_message = nil
|
|
919
|
+
@completion_lamp_time = nil
|
|
920
|
+
end
|
|
921
|
+
end
|
|
922
|
+
end
|
|
923
|
+
|
|
924
|
+
# FPS表示(test modeの時のみ)
|
|
833
925
|
if @test_mode && fps
|
|
834
|
-
|
|
835
|
-
else
|
|
836
|
-
right_info = "?:help"
|
|
926
|
+
right_parts << "#{fps.round(1)} FPS"
|
|
837
927
|
end
|
|
838
928
|
|
|
929
|
+
# ヘルプ表示
|
|
930
|
+
right_parts << "?:help"
|
|
931
|
+
|
|
932
|
+
right_info = right_parts.join(" | ")
|
|
933
|
+
|
|
839
934
|
# ブックマーク一覧を利用可能な幅に収める
|
|
840
935
|
available_width = @screen_width - right_info.length - 3
|
|
841
936
|
if bookmark_text.length > available_width && available_width > 3
|
|
@@ -918,26 +1013,26 @@ module Rufio
|
|
|
918
1013
|
# ノンブロッキング入力処理(ゲームループ用)
|
|
919
1014
|
# IO.selectでタイムアウト付きで入力をチェック
|
|
920
1015
|
def handle_input_nonblocking
|
|
921
|
-
#
|
|
922
|
-
ready = IO.select([STDIN], nil, nil, 0
|
|
923
|
-
return unless ready
|
|
1016
|
+
# 0msタイムアウトで即座にチェック(30FPS = 33.33ms/frame)
|
|
1017
|
+
ready = IO.select([STDIN], nil, nil, 0)
|
|
1018
|
+
return false unless ready
|
|
924
1019
|
|
|
925
1020
|
begin
|
|
926
1021
|
# read_nonblockを使ってノンブロッキングで1文字読み取る
|
|
927
1022
|
input = STDIN.read_nonblock(1)
|
|
928
1023
|
rescue IO::WaitReadable, IO::EAGAINWaitReadable
|
|
929
1024
|
# 入力が利用できない
|
|
930
|
-
return
|
|
1025
|
+
return false
|
|
931
1026
|
rescue Errno::ENOTTY, Errno::ENODEV
|
|
932
1027
|
# ターミナルでない環境
|
|
933
|
-
return
|
|
1028
|
+
return false
|
|
934
1029
|
end
|
|
935
1030
|
|
|
936
1031
|
# コマンドモードがアクティブな場合は、エスケープシーケンス処理をスキップ
|
|
937
1032
|
# ESCキーをそのまま handle_command_input に渡す
|
|
938
1033
|
if @command_mode_active
|
|
939
1034
|
handle_command_input(input)
|
|
940
|
-
return
|
|
1035
|
+
return true
|
|
941
1036
|
end
|
|
942
1037
|
|
|
943
1038
|
# 特殊キーの処理(エスケープシーケンス)(コマンドモード外のみ)
|
|
@@ -967,12 +1062,15 @@ module Rufio
|
|
|
967
1062
|
end
|
|
968
1063
|
|
|
969
1064
|
# キーバインドハンドラーに処理を委譲
|
|
970
|
-
@keybind_handler.handle_key(input) if input
|
|
1065
|
+
result = @keybind_handler.handle_key(input) if input
|
|
971
1066
|
|
|
972
|
-
# 終了処理(q
|
|
973
|
-
if input == 'q'
|
|
1067
|
+
# 終了処理(qキーのみ、確認ダイアログの結果を確認)
|
|
1068
|
+
if input == 'q' && result == true
|
|
974
1069
|
@running = false
|
|
975
1070
|
end
|
|
1071
|
+
|
|
1072
|
+
# 入力があったことを返す
|
|
1073
|
+
true
|
|
976
1074
|
end
|
|
977
1075
|
|
|
978
1076
|
def handle_input
|
|
@@ -1028,10 +1126,10 @@ module Rufio
|
|
|
1028
1126
|
end
|
|
1029
1127
|
|
|
1030
1128
|
# キーバインドハンドラーに処理を委譲
|
|
1031
|
-
|
|
1129
|
+
result = @keybind_handler.handle_key(input)
|
|
1032
1130
|
|
|
1033
|
-
# 終了処理(q
|
|
1034
|
-
if input == 'q'
|
|
1131
|
+
# 終了処理(qキーのみ、確認ダイアログの結果を確認)
|
|
1132
|
+
if input == 'q' && result == true
|
|
1035
1133
|
@running = false
|
|
1036
1134
|
end
|
|
1037
1135
|
end
|
data/lib/rufio/text_utils.rb
CHANGED
|
@@ -126,8 +126,17 @@ module Rufio
|
|
|
126
126
|
|
|
127
127
|
wrapped = []
|
|
128
128
|
lines.each do |line|
|
|
129
|
-
#
|
|
130
|
-
|
|
129
|
+
# Handle encoding errors: scrub invalid UTF-8 sequences
|
|
130
|
+
begin
|
|
131
|
+
# Force UTF-8 encoding and replace invalid bytes
|
|
132
|
+
line = line.encode('UTF-8', invalid: :replace, undef: :replace, replace: '?')
|
|
133
|
+
# Remove trailing whitespace
|
|
134
|
+
line = line.rstrip
|
|
135
|
+
rescue EncodingError, ArgumentError => e
|
|
136
|
+
# If encoding fails completely, skip this line
|
|
137
|
+
wrapped << '[encoding error]'
|
|
138
|
+
next
|
|
139
|
+
end
|
|
131
140
|
|
|
132
141
|
# If line is empty, keep it
|
|
133
142
|
if line.empty?
|
|
@@ -136,31 +145,45 @@ module Rufio
|
|
|
136
145
|
end
|
|
137
146
|
|
|
138
147
|
# If line fits within max_width, keep it as is
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
148
|
+
begin
|
|
149
|
+
if display_width(line) <= max_width
|
|
150
|
+
wrapped << line
|
|
151
|
+
next
|
|
152
|
+
end
|
|
153
|
+
rescue ArgumentError => e
|
|
154
|
+
# If display_width fails, just truncate by byte length
|
|
155
|
+
if line.bytesize <= max_width
|
|
156
|
+
wrapped << line
|
|
157
|
+
next
|
|
158
|
+
end
|
|
142
159
|
end
|
|
143
160
|
|
|
144
161
|
# Split long lines
|
|
145
162
|
current_line = []
|
|
146
163
|
current_width = 0
|
|
147
164
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
165
|
+
begin
|
|
166
|
+
line.each_char do |char|
|
|
167
|
+
cw = char_width(char)
|
|
168
|
+
|
|
169
|
+
if current_width + cw > max_width
|
|
170
|
+
# Start a new line
|
|
171
|
+
wrapped << current_line.join
|
|
172
|
+
current_line = [char]
|
|
173
|
+
current_width = cw
|
|
174
|
+
else
|
|
175
|
+
current_line << char
|
|
176
|
+
current_width += cw
|
|
177
|
+
end
|
|
159
178
|
end
|
|
160
|
-
end
|
|
161
179
|
|
|
162
|
-
|
|
163
|
-
|
|
180
|
+
# Add remaining characters
|
|
181
|
+
wrapped << current_line.join unless current_line.empty?
|
|
182
|
+
rescue ArgumentError, EncodingError => e
|
|
183
|
+
# If character iteration fails, just add the line truncated
|
|
184
|
+
truncated = line.byteslice(0, [max_width, line.bytesize].min)
|
|
185
|
+
wrapped << (truncated || line)
|
|
186
|
+
end
|
|
164
187
|
end
|
|
165
188
|
|
|
166
189
|
wrapped
|
data/lib/rufio/version.rb
CHANGED
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.
|
|
4
|
+
version: 0.41.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-01-
|
|
11
|
+
date: 2026-01-17 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: io-console
|
|
@@ -132,6 +132,7 @@ files:
|
|
|
132
132
|
- docs/CHANGELOG_v0.33.0.md
|
|
133
133
|
- docs/CHANGELOG_v0.4.0.md
|
|
134
134
|
- docs/CHANGELOG_v0.40.0.md
|
|
135
|
+
- docs/CHANGELOG_v0.41.0.md
|
|
135
136
|
- docs/CHANGELOG_v0.5.0.md
|
|
136
137
|
- docs/CHANGELOG_v0.6.0.md
|
|
137
138
|
- docs/CHANGELOG_v0.7.0.md
|
|
@@ -192,7 +193,6 @@ files:
|
|
|
192
193
|
- lib_zig/rufio_native/build.zig
|
|
193
194
|
- lib_zig/rufio_native/src/main.zig
|
|
194
195
|
- lib_zig/rufio_native/src/main.zig.sync
|
|
195
|
-
- publish_gem.zsh
|
|
196
196
|
- retag.sh
|
|
197
197
|
- test_delete/test1.txt
|
|
198
198
|
- test_delete/test2.txt
|