rufio 0.10.0 → 0.20.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_v0.20.0.md +166 -0
- data/README.md +66 -14
- data/info/welcome.txt +21 -0
- data/lib/rufio/bookmark.rb +19 -3
- data/lib/rufio/bookmark_manager.rb +111 -13
- data/lib/rufio/config_loader.rb +15 -2
- data/lib/rufio/file_operations.rb +66 -0
- data/lib/rufio/file_preview.rb +1 -1
- data/lib/rufio/info_notice.rb +100 -0
- data/lib/rufio/keybind_handler.rb +434 -24
- data/lib/rufio/project_command.rb +147 -0
- data/lib/rufio/project_log.rb +68 -0
- data/lib/rufio/project_mode.rb +58 -0
- data/lib/rufio/terminal_ui.rb +552 -4
- data/lib/rufio/version.rb +1 -1
- data/lib/rufio.rb +5 -0
- metadata +7 -1
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'fileutils'
|
|
4
|
+
|
|
5
|
+
module Rufio
|
|
6
|
+
# Manages info notices from the info directory
|
|
7
|
+
class InfoNotice
|
|
8
|
+
INFO_DIR = File.join(File.dirname(__FILE__), '..', '..', 'info')
|
|
9
|
+
NOTICE_TRACKING_DIR = File.join(Dir.home, '.config', 'rufio', 'notices')
|
|
10
|
+
|
|
11
|
+
attr_accessor :info_dir, :tracking_dir
|
|
12
|
+
|
|
13
|
+
def initialize(info_dir: nil, tracking_dir: nil)
|
|
14
|
+
@info_dir = info_dir || INFO_DIR
|
|
15
|
+
@tracking_dir = tracking_dir || NOTICE_TRACKING_DIR
|
|
16
|
+
ensure_tracking_directory
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Get all available notices that haven't been shown
|
|
20
|
+
# @return [Array<Hash>] Array of notice hashes with :file, :title, :content
|
|
21
|
+
def unread_notices
|
|
22
|
+
return [] unless Dir.exist?(@info_dir)
|
|
23
|
+
|
|
24
|
+
Dir.glob(File.join(@info_dir, '*.txt')).map do |file_path|
|
|
25
|
+
next if shown?(file_path)
|
|
26
|
+
|
|
27
|
+
{
|
|
28
|
+
file: file_path,
|
|
29
|
+
filename: File.basename(file_path),
|
|
30
|
+
title: extract_title(file_path),
|
|
31
|
+
content: read_content(file_path)
|
|
32
|
+
}
|
|
33
|
+
end.compact
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Check if a notice has been shown
|
|
37
|
+
# @param file_path [String] Path to the notice file
|
|
38
|
+
# @return [Boolean] true if already shown
|
|
39
|
+
def shown?(file_path)
|
|
40
|
+
tracking_file = tracking_file_path(file_path)
|
|
41
|
+
File.exist?(tracking_file)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Mark a notice as shown
|
|
45
|
+
# @param file_path [String] Path to the notice file
|
|
46
|
+
def mark_as_shown(file_path)
|
|
47
|
+
tracking_file = tracking_file_path(file_path)
|
|
48
|
+
FileUtils.touch(tracking_file)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Extract title from the first line of the file
|
|
52
|
+
# @param file_path [String] Path to the notice file
|
|
53
|
+
# @return [String] The title
|
|
54
|
+
def extract_title(file_path)
|
|
55
|
+
first_line = File.open(file_path, &:readline).strip
|
|
56
|
+
# Remove markdown heading markers if present
|
|
57
|
+
first_line.gsub(/^#+\s*/, '')
|
|
58
|
+
rescue StandardError
|
|
59
|
+
File.basename(file_path, '.txt')
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Read the content of a notice file
|
|
63
|
+
# @param file_path [String] Path to the notice file
|
|
64
|
+
# @return [Array<String>] Content lines
|
|
65
|
+
def read_content(file_path)
|
|
66
|
+
lines = File.readlines(file_path, chomp: true)
|
|
67
|
+
|
|
68
|
+
# Skip the first line if it's a markdown heading (title)
|
|
69
|
+
lines = lines.drop(1) if lines.first&.start_with?('#')
|
|
70
|
+
|
|
71
|
+
# Add empty lines at the beginning and end for padding
|
|
72
|
+
[''] + lines + ['', 'Press any key to continue...', '']
|
|
73
|
+
rescue StandardError => e
|
|
74
|
+
[
|
|
75
|
+
'',
|
|
76
|
+
"Error reading notice: #{e.message}",
|
|
77
|
+
'',
|
|
78
|
+
'Press any key to continue...',
|
|
79
|
+
''
|
|
80
|
+
]
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
private
|
|
84
|
+
|
|
85
|
+
def ensure_tracking_directory
|
|
86
|
+
FileUtils.mkdir_p(@tracking_dir) unless Dir.exist?(@tracking_dir)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Get the tracking file path for a given notice file
|
|
90
|
+
# @param file_path [String] Path to the notice file
|
|
91
|
+
# @return [String] Path to the tracking file
|
|
92
|
+
def tracking_file_path(file_path)
|
|
93
|
+
filename = File.basename(file_path)
|
|
94
|
+
# Use MD5 hash of the filename to avoid issues with special characters
|
|
95
|
+
require 'digest'
|
|
96
|
+
hash = Digest::MD5.hexdigest(filename)
|
|
97
|
+
File.join(@tracking_dir, ".#{hash}_shown")
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
@@ -44,6 +44,14 @@ module Rufio
|
|
|
44
44
|
@bookmark_manager = BookmarkManager.new(Bookmark.new, @dialog_renderer)
|
|
45
45
|
@zoxide_integration = ZoxideIntegration.new(@dialog_renderer)
|
|
46
46
|
|
|
47
|
+
# Project mode
|
|
48
|
+
log_dir = File.expand_path('~/.config/rufio/logs')
|
|
49
|
+
@project_mode = ProjectMode.new(@bookmark_manager.instance_variable_get(:@bookmark), log_dir)
|
|
50
|
+
@project_command = ProjectCommand.new(log_dir)
|
|
51
|
+
@project_log = ProjectLog.new(log_dir)
|
|
52
|
+
@in_project_mode = false
|
|
53
|
+
@in_log_mode = false
|
|
54
|
+
|
|
47
55
|
# Legacy fields for backward compatibility
|
|
48
56
|
@base_directory = nil
|
|
49
57
|
end
|
|
@@ -72,6 +80,11 @@ module Rufio
|
|
|
72
80
|
def handle_key(key)
|
|
73
81
|
return false unless @directory_listing
|
|
74
82
|
|
|
83
|
+
# プロジェクトモード中の特別処理
|
|
84
|
+
if @in_project_mode
|
|
85
|
+
return handle_project_mode_key(key)
|
|
86
|
+
end
|
|
87
|
+
|
|
75
88
|
# フィルターモード中は他のキーバインドを無効化
|
|
76
89
|
return handle_filter_input(key) if @filter_manager.filter_mode
|
|
77
90
|
|
|
@@ -88,8 +101,12 @@ module Rufio
|
|
|
88
101
|
move_to_top
|
|
89
102
|
when 'G'
|
|
90
103
|
move_to_bottom
|
|
91
|
-
when '
|
|
104
|
+
when 'R' # R - refresh
|
|
92
105
|
refresh
|
|
106
|
+
when 'r' # r - rename file/directory
|
|
107
|
+
rename_current_file
|
|
108
|
+
when 'd' # d - delete file/directory with confirmation
|
|
109
|
+
delete_current_file_with_confirmation
|
|
93
110
|
when 'o' # o
|
|
94
111
|
open_current_file
|
|
95
112
|
when 'e' # e - open directory in file explorer
|
|
@@ -126,12 +143,14 @@ module Rufio
|
|
|
126
143
|
create_directory
|
|
127
144
|
when 'm' # m - move selected files to base directory
|
|
128
145
|
move_selected_to_base
|
|
129
|
-
when '
|
|
146
|
+
when 'C' # C - copy selected files to base directory
|
|
130
147
|
copy_selected_to_base
|
|
131
148
|
when 'x' # x - delete selected files
|
|
132
149
|
delete_selected_files
|
|
133
|
-
when '
|
|
134
|
-
|
|
150
|
+
when 'p' # p - project mode
|
|
151
|
+
enter_project_mode
|
|
152
|
+
when 'b' # b - add bookmark
|
|
153
|
+
add_bookmark
|
|
135
154
|
when 'z' # z - zoxide history navigation
|
|
136
155
|
show_zoxide_menu
|
|
137
156
|
when '1', '2', '3', '4', '5', '6', '7', '8', '9' # number keys - go to bookmark
|
|
@@ -369,12 +388,15 @@ module Rufio
|
|
|
369
388
|
def create_file
|
|
370
389
|
current_path = @directory_listing&.current_path || Dir.pwd
|
|
371
390
|
|
|
372
|
-
#
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
391
|
+
# ダイアログレンダラーを使用して入力ダイアログを表示
|
|
392
|
+
title = "Create File"
|
|
393
|
+
prompt = "Enter file name:"
|
|
394
|
+
filename = @dialog_renderer.show_input_dialog(title, prompt, {
|
|
395
|
+
border_color: "\e[32m", # Green
|
|
396
|
+
title_color: "\e[1;32m", # Bold green
|
|
397
|
+
content_color: "\e[37m" # White
|
|
398
|
+
})
|
|
376
399
|
|
|
377
|
-
filename = read_line_with_escape
|
|
378
400
|
return false if filename.nil? || filename.empty?
|
|
379
401
|
|
|
380
402
|
# FileOperationsを使用してファイルを作成
|
|
@@ -390,22 +412,22 @@ module Rufio
|
|
|
390
412
|
@current_index = new_file_index if new_file_index
|
|
391
413
|
end
|
|
392
414
|
|
|
393
|
-
|
|
394
|
-
puts "\n#{result.message}"
|
|
395
|
-
print ConfigLoader.message('keybind.press_any_key')
|
|
396
|
-
STDIN.getch
|
|
415
|
+
@terminal_ui&.refresh_display
|
|
397
416
|
result.success
|
|
398
417
|
end
|
|
399
418
|
|
|
400
419
|
def create_directory
|
|
401
420
|
current_path = @directory_listing&.current_path || Dir.pwd
|
|
402
421
|
|
|
403
|
-
#
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
422
|
+
# ダイアログレンダラーを使用して入力ダイアログを表示
|
|
423
|
+
title = "Create Directory"
|
|
424
|
+
prompt = "Enter directory name:"
|
|
425
|
+
dirname = @dialog_renderer.show_input_dialog(title, prompt, {
|
|
426
|
+
border_color: "\e[34m", # Blue
|
|
427
|
+
title_color: "\e[1;34m", # Bold blue
|
|
428
|
+
content_color: "\e[37m" # White
|
|
429
|
+
})
|
|
407
430
|
|
|
408
|
-
dirname = read_line_with_escape
|
|
409
431
|
return false if dirname.nil? || dirname.empty?
|
|
410
432
|
|
|
411
433
|
# FileOperationsを使用してディレクトリを作成
|
|
@@ -421,10 +443,110 @@ module Rufio
|
|
|
421
443
|
@current_index = new_dir_index if new_dir_index
|
|
422
444
|
end
|
|
423
445
|
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
446
|
+
@terminal_ui&.refresh_display
|
|
447
|
+
result.success
|
|
448
|
+
end
|
|
449
|
+
|
|
450
|
+
def rename_current_file
|
|
451
|
+
current_item = current_entry()
|
|
452
|
+
return false unless current_item
|
|
453
|
+
|
|
454
|
+
current_name = current_item[:name]
|
|
455
|
+
current_path = @directory_listing&.current_path || Dir.pwd
|
|
456
|
+
|
|
457
|
+
# ダイアログレンダラーを使用して入力ダイアログを表示
|
|
458
|
+
title = "Rename: #{current_name}"
|
|
459
|
+
prompt = "Enter new name:"
|
|
460
|
+
new_name = @dialog_renderer.show_input_dialog(title, prompt, {
|
|
461
|
+
border_color: "\e[33m", # Yellow
|
|
462
|
+
title_color: "\e[1;33m", # Bold yellow
|
|
463
|
+
content_color: "\e[37m" # White
|
|
464
|
+
})
|
|
465
|
+
|
|
466
|
+
return false if new_name.nil? || new_name.empty?
|
|
467
|
+
|
|
468
|
+
# FileOperationsを使用してリネーム
|
|
469
|
+
result = @file_operations.rename(current_path, current_name, new_name)
|
|
470
|
+
|
|
471
|
+
# ディレクトリ表示を更新
|
|
472
|
+
if result.success
|
|
473
|
+
@directory_listing.refresh
|
|
474
|
+
|
|
475
|
+
# リネームしたファイルを選択状態にする
|
|
476
|
+
entries = @directory_listing.list_entries
|
|
477
|
+
new_index = entries.find_index { |entry| entry[:name] == new_name }
|
|
478
|
+
@current_index = new_index if new_index
|
|
479
|
+
end
|
|
480
|
+
|
|
481
|
+
@terminal_ui&.refresh_display
|
|
482
|
+
result.success
|
|
483
|
+
end
|
|
484
|
+
|
|
485
|
+
def delete_current_file_with_confirmation
|
|
486
|
+
current_item = current_entry()
|
|
487
|
+
return false unless current_item
|
|
488
|
+
|
|
489
|
+
current_name = current_item[:name]
|
|
490
|
+
current_path = @directory_listing&.current_path || Dir.pwd
|
|
491
|
+
is_directory = current_item[:type] == :directory
|
|
492
|
+
|
|
493
|
+
# 確認ダイアログを表示
|
|
494
|
+
type_text = is_directory ? 'directory' : 'file'
|
|
495
|
+
content_lines = [
|
|
496
|
+
'',
|
|
497
|
+
"Delete this #{type_text}?",
|
|
498
|
+
" #{current_name}",
|
|
499
|
+
'',
|
|
500
|
+
' [Y]es - Delete',
|
|
501
|
+
' [N]o - Cancel',
|
|
502
|
+
''
|
|
503
|
+
]
|
|
504
|
+
|
|
505
|
+
title = 'Confirm Delete'
|
|
506
|
+
width = [50, current_name.length + 10].max
|
|
507
|
+
height = content_lines.length + 4
|
|
508
|
+
x, y = @dialog_renderer.calculate_center(width, height)
|
|
509
|
+
|
|
510
|
+
@dialog_renderer.draw_floating_window(x, y, width, height, title, content_lines, {
|
|
511
|
+
border_color: "\e[31m", # Red (warning)
|
|
512
|
+
title_color: "\e[1;31m", # Bold red
|
|
513
|
+
content_color: "\e[37m" # White
|
|
514
|
+
})
|
|
515
|
+
|
|
516
|
+
# 確認を待つ
|
|
517
|
+
confirmed = false
|
|
518
|
+
loop do
|
|
519
|
+
input = STDIN.getch.downcase
|
|
520
|
+
|
|
521
|
+
case input
|
|
522
|
+
when 'y'
|
|
523
|
+
confirmed = true
|
|
524
|
+
break
|
|
525
|
+
when 'n', "\e" # n or ESC
|
|
526
|
+
confirmed = false
|
|
527
|
+
break
|
|
528
|
+
end
|
|
529
|
+
end
|
|
530
|
+
|
|
531
|
+
@dialog_renderer.clear_area(x, y, width, height)
|
|
532
|
+
@terminal_ui&.refresh_display
|
|
533
|
+
|
|
534
|
+
return false unless confirmed
|
|
535
|
+
|
|
536
|
+
# FileOperationsを使用して削除
|
|
537
|
+
result = @file_operations.delete([current_name], current_path)
|
|
538
|
+
|
|
539
|
+
# ディレクトリ表示を更新
|
|
540
|
+
if result.success
|
|
541
|
+
@directory_listing.refresh
|
|
542
|
+
|
|
543
|
+
# カーソル位置を調整
|
|
544
|
+
entries = @directory_listing.list_entries
|
|
545
|
+
@current_index = [@current_index, entries.length - 1].min if @current_index >= entries.length
|
|
546
|
+
@current_index = 0 if @current_index < 0
|
|
547
|
+
end
|
|
548
|
+
|
|
549
|
+
@terminal_ui&.refresh_display
|
|
428
550
|
result.success
|
|
429
551
|
end
|
|
430
552
|
|
|
@@ -713,6 +835,10 @@ module Rufio
|
|
|
713
835
|
else
|
|
714
836
|
false
|
|
715
837
|
end
|
|
838
|
+
when :rename
|
|
839
|
+
@bookmark_manager.rename_interactive
|
|
840
|
+
@terminal_ui&.refresh_display
|
|
841
|
+
true
|
|
716
842
|
when :remove
|
|
717
843
|
@bookmark_manager.remove_interactive
|
|
718
844
|
@terminal_ui&.refresh_display
|
|
@@ -740,6 +866,61 @@ module Rufio
|
|
|
740
866
|
end
|
|
741
867
|
end
|
|
742
868
|
|
|
869
|
+
def add_bookmark
|
|
870
|
+
current_path = @directory_listing&.current_path || Dir.pwd
|
|
871
|
+
|
|
872
|
+
# カレントディレクトリが既にブックマークされているかチェック
|
|
873
|
+
bookmarks = @bookmark_manager.list
|
|
874
|
+
existing = bookmarks.find { |b| b[:path] == current_path }
|
|
875
|
+
|
|
876
|
+
if existing
|
|
877
|
+
# 既に存在する場合はメッセージを表示して終了
|
|
878
|
+
content_lines = [
|
|
879
|
+
'',
|
|
880
|
+
'This directory is already bookmarked',
|
|
881
|
+
"Name: #{existing[:name]}",
|
|
882
|
+
'',
|
|
883
|
+
'Press any key to continue...'
|
|
884
|
+
]
|
|
885
|
+
|
|
886
|
+
title = 'Bookmark Exists'
|
|
887
|
+
width = 50
|
|
888
|
+
height = content_lines.length + 4
|
|
889
|
+
x, y = @dialog_renderer.calculate_center(width, height)
|
|
890
|
+
|
|
891
|
+
@dialog_renderer.draw_floating_window(x, y, width, height, title, content_lines, {
|
|
892
|
+
border_color: "\e[33m", # Yellow
|
|
893
|
+
title_color: "\e[1;33m", # Bold yellow
|
|
894
|
+
content_color: "\e[37m" # White
|
|
895
|
+
})
|
|
896
|
+
|
|
897
|
+
STDIN.getch
|
|
898
|
+
@dialog_renderer.clear_area(x, y, width, height)
|
|
899
|
+
@terminal_ui&.refresh_display
|
|
900
|
+
return false
|
|
901
|
+
end
|
|
902
|
+
|
|
903
|
+
# ディレクトリ名を取得
|
|
904
|
+
dir_name = File.basename(current_path)
|
|
905
|
+
|
|
906
|
+
# ダイアログレンダラーを使用して入力ダイアログを表示
|
|
907
|
+
title = "Add Bookmark: #{dir_name}"
|
|
908
|
+
prompt = "Enter bookmark name:"
|
|
909
|
+
bookmark_name = @dialog_renderer.show_input_dialog(title, prompt, {
|
|
910
|
+
border_color: "\e[32m", # Green
|
|
911
|
+
title_color: "\e[1;32m", # Bold green
|
|
912
|
+
content_color: "\e[37m" # White
|
|
913
|
+
})
|
|
914
|
+
|
|
915
|
+
return false if bookmark_name.nil? || bookmark_name.empty?
|
|
916
|
+
|
|
917
|
+
# ブックマークを追加
|
|
918
|
+
result = @bookmark_manager.add(current_path, bookmark_name)
|
|
919
|
+
|
|
920
|
+
@terminal_ui&.refresh_display if @terminal_ui
|
|
921
|
+
result
|
|
922
|
+
end
|
|
923
|
+
|
|
743
924
|
# ヘルパーメソッド
|
|
744
925
|
def wait_for_keypress
|
|
745
926
|
print ConfigLoader.message('keybind.press_any_key') || 'Press any key to continue...'
|
|
@@ -786,8 +967,6 @@ module Rufio
|
|
|
786
967
|
true
|
|
787
968
|
end
|
|
788
969
|
|
|
789
|
-
private
|
|
790
|
-
|
|
791
970
|
# カーソルを画面下部の入力行に移動
|
|
792
971
|
def move_to_input_line
|
|
793
972
|
# 画面の最終行にカーソルを移動
|
|
@@ -833,5 +1012,236 @@ module Rufio
|
|
|
833
1012
|
end
|
|
834
1013
|
end
|
|
835
1014
|
end
|
|
1015
|
+
|
|
1016
|
+
# プロジェクトモード中のキー処理
|
|
1017
|
+
def handle_project_mode_key(key)
|
|
1018
|
+
case key
|
|
1019
|
+
when "\e" # ESC - ログモードならプロジェクトモードに戻る、そうでなければ終了
|
|
1020
|
+
if @in_log_mode
|
|
1021
|
+
exit_log_mode
|
|
1022
|
+
else
|
|
1023
|
+
exit_project_mode
|
|
1024
|
+
end
|
|
1025
|
+
when ' ' # Space - ブックマークを選択
|
|
1026
|
+
if @in_log_mode
|
|
1027
|
+
# ログモード中は何もしない
|
|
1028
|
+
false
|
|
1029
|
+
else
|
|
1030
|
+
select_bookmark_in_project_mode
|
|
1031
|
+
end
|
|
1032
|
+
when 'l' # l - ログディレクトリに移動
|
|
1033
|
+
if @in_log_mode
|
|
1034
|
+
# すでにログモードの場合は何もしない
|
|
1035
|
+
false
|
|
1036
|
+
else
|
|
1037
|
+
@in_log_mode = true
|
|
1038
|
+
@current_index = 0 # ログモードに入るときインデックスをリセット
|
|
1039
|
+
@terminal_ui&.enter_log_mode(@project_log) if @terminal_ui
|
|
1040
|
+
true
|
|
1041
|
+
end
|
|
1042
|
+
when 'j' # 下に移動
|
|
1043
|
+
move_down_in_project_mode
|
|
1044
|
+
when 'k' # 上に移動
|
|
1045
|
+
move_up_in_project_mode
|
|
1046
|
+
when 'g' # 先頭に移動
|
|
1047
|
+
@current_index = 0
|
|
1048
|
+
true
|
|
1049
|
+
when 'G' # 末尾に移動
|
|
1050
|
+
entries = get_project_mode_entries
|
|
1051
|
+
@current_index = [entries.length - 1, 0].max
|
|
1052
|
+
true
|
|
1053
|
+
when ':' # コマンドモード
|
|
1054
|
+
activate_project_command_mode
|
|
1055
|
+
when 'r' # r - ブックマークをリネーム
|
|
1056
|
+
if @in_log_mode
|
|
1057
|
+
false
|
|
1058
|
+
else
|
|
1059
|
+
rename_bookmark_in_project_mode
|
|
1060
|
+
end
|
|
1061
|
+
when 'd' # d - ブックマークを削除
|
|
1062
|
+
if @in_log_mode
|
|
1063
|
+
false
|
|
1064
|
+
else
|
|
1065
|
+
delete_bookmark_in_project_mode
|
|
1066
|
+
end
|
|
1067
|
+
else
|
|
1068
|
+
false
|
|
1069
|
+
end
|
|
1070
|
+
end
|
|
1071
|
+
|
|
1072
|
+
# プロジェクトモード用のエントリ数取得
|
|
1073
|
+
def get_project_mode_entries
|
|
1074
|
+
if @in_log_mode
|
|
1075
|
+
@project_log.list_log_files
|
|
1076
|
+
else
|
|
1077
|
+
@project_mode.list_bookmarks
|
|
1078
|
+
end
|
|
1079
|
+
end
|
|
1080
|
+
|
|
1081
|
+
# プロジェクトモード用の下移動
|
|
1082
|
+
def move_down_in_project_mode
|
|
1083
|
+
entries = get_project_mode_entries
|
|
1084
|
+
@current_index = [@current_index + 1, entries.length - 1].min
|
|
1085
|
+
true
|
|
1086
|
+
end
|
|
1087
|
+
|
|
1088
|
+
# プロジェクトモード用の上移動
|
|
1089
|
+
def move_up_in_project_mode
|
|
1090
|
+
@current_index = [@current_index - 1, 0].max
|
|
1091
|
+
true
|
|
1092
|
+
end
|
|
1093
|
+
|
|
1094
|
+
# プロジェクトモードに入る
|
|
1095
|
+
def enter_project_mode
|
|
1096
|
+
@project_mode.activate
|
|
1097
|
+
@in_project_mode = true
|
|
1098
|
+
@current_index = 0 # プロジェクトモードに入るときインデックスをリセット
|
|
1099
|
+
@terminal_ui&.set_project_mode(@project_mode, @project_command, @project_log) if @terminal_ui
|
|
1100
|
+
true
|
|
1101
|
+
end
|
|
1102
|
+
|
|
1103
|
+
# プロジェクトモードを終了
|
|
1104
|
+
def exit_project_mode
|
|
1105
|
+
@project_mode.deactivate
|
|
1106
|
+
@in_project_mode = false
|
|
1107
|
+
@in_log_mode = false
|
|
1108
|
+
@current_index = 0 # 通常モードに戻るときインデックスをリセット
|
|
1109
|
+
@terminal_ui&.exit_project_mode if @terminal_ui
|
|
1110
|
+
refresh
|
|
1111
|
+
true
|
|
1112
|
+
end
|
|
1113
|
+
|
|
1114
|
+
# プロジェクトモードでコマンドモードを起動
|
|
1115
|
+
def activate_project_command_mode
|
|
1116
|
+
# 選択されたプロジェクトがあるかチェック
|
|
1117
|
+
if @project_mode.selected_path.nil?
|
|
1118
|
+
@terminal_ui&.show_project_not_selected_message if @terminal_ui
|
|
1119
|
+
return false
|
|
1120
|
+
end
|
|
1121
|
+
|
|
1122
|
+
@terminal_ui&.activate_project_command_mode(@project_mode, @project_command, @project_log) if @terminal_ui
|
|
1123
|
+
true
|
|
1124
|
+
end
|
|
1125
|
+
|
|
1126
|
+
# ログモードを終了してプロジェクトモードに戻る
|
|
1127
|
+
def exit_log_mode
|
|
1128
|
+
@in_log_mode = false
|
|
1129
|
+
@current_index = 0 # プロジェクトモードに戻るときインデックスをリセット
|
|
1130
|
+
@terminal_ui&.exit_log_mode if @terminal_ui
|
|
1131
|
+
true
|
|
1132
|
+
end
|
|
1133
|
+
|
|
1134
|
+
# プロジェクトモードでブックマークをリネーム
|
|
1135
|
+
def rename_bookmark_in_project_mode
|
|
1136
|
+
bookmarks = @bookmark_manager.list
|
|
1137
|
+
return false if bookmarks.empty? || @current_index >= bookmarks.length
|
|
1138
|
+
|
|
1139
|
+
current_bookmark = bookmarks[@current_index]
|
|
1140
|
+
old_name = current_bookmark[:name]
|
|
1141
|
+
|
|
1142
|
+
# ダイアログレンダラーを使用して入力ダイアログを表示
|
|
1143
|
+
new_name = @dialog_renderer.show_input_dialog("Rename: #{old_name}", "Enter new name:", {
|
|
1144
|
+
border_color: "\e[33m", # Yellow
|
|
1145
|
+
title_color: "\e[1;33m", # Bold yellow
|
|
1146
|
+
content_color: "\e[37m" # White
|
|
1147
|
+
})
|
|
1148
|
+
|
|
1149
|
+
return false if new_name.nil? || new_name.empty?
|
|
1150
|
+
|
|
1151
|
+
# Bookmarkを使用してリネーム
|
|
1152
|
+
result = @bookmark_manager.instance_variable_get(:@bookmark).rename(old_name, new_name)
|
|
1153
|
+
|
|
1154
|
+
@terminal_ui&.refresh_display if @terminal_ui
|
|
1155
|
+
result
|
|
1156
|
+
end
|
|
1157
|
+
|
|
1158
|
+
# プロジェクトモードでブックマークを削除
|
|
1159
|
+
def delete_bookmark_in_project_mode
|
|
1160
|
+
bookmarks = @bookmark_manager.list
|
|
1161
|
+
return false if bookmarks.empty? || @current_index >= bookmarks.length
|
|
1162
|
+
|
|
1163
|
+
current_bookmark = bookmarks[@current_index]
|
|
1164
|
+
bookmark_name = current_bookmark[:name]
|
|
1165
|
+
|
|
1166
|
+
# 確認ダイアログを表示
|
|
1167
|
+
content_lines = [
|
|
1168
|
+
'',
|
|
1169
|
+
"Delete this bookmark?",
|
|
1170
|
+
" #{bookmark_name}",
|
|
1171
|
+
'',
|
|
1172
|
+
' [Y]es - Delete',
|
|
1173
|
+
' [N]o - Cancel',
|
|
1174
|
+
''
|
|
1175
|
+
]
|
|
1176
|
+
|
|
1177
|
+
title = 'Confirm Delete'
|
|
1178
|
+
width = [50, bookmark_name.length + 10].max
|
|
1179
|
+
height = content_lines.length + 4
|
|
1180
|
+
x, y = @dialog_renderer.calculate_center(width, height)
|
|
1181
|
+
|
|
1182
|
+
@dialog_renderer.draw_floating_window(x, y, width, height, title, content_lines, {
|
|
1183
|
+
border_color: "\e[31m", # Red (warning)
|
|
1184
|
+
title_color: "\e[1;31m", # Bold red
|
|
1185
|
+
content_color: "\e[37m" # White
|
|
1186
|
+
})
|
|
1187
|
+
|
|
1188
|
+
# 確認を待つ
|
|
1189
|
+
confirmed = false
|
|
1190
|
+
loop do
|
|
1191
|
+
input = STDIN.getch.downcase
|
|
1192
|
+
|
|
1193
|
+
case input
|
|
1194
|
+
when 'y'
|
|
1195
|
+
confirmed = true
|
|
1196
|
+
break
|
|
1197
|
+
when 'n', "\e" # n or ESC
|
|
1198
|
+
confirmed = false
|
|
1199
|
+
break
|
|
1200
|
+
end
|
|
1201
|
+
end
|
|
1202
|
+
|
|
1203
|
+
@dialog_renderer.clear_area(x, y, width, height)
|
|
1204
|
+
@terminal_ui&.refresh_display
|
|
1205
|
+
|
|
1206
|
+
return false unless confirmed
|
|
1207
|
+
|
|
1208
|
+
# Bookmarkを使用して削除
|
|
1209
|
+
result = @bookmark_manager.instance_variable_get(:@bookmark).remove(bookmark_name)
|
|
1210
|
+
|
|
1211
|
+
# 削除後、選択されていたブックマークがなくなった場合は選択をクリア
|
|
1212
|
+
if result && @project_mode.selected_path == current_bookmark[:path]
|
|
1213
|
+
@project_mode.clear_selection
|
|
1214
|
+
end
|
|
1215
|
+
|
|
1216
|
+
# カーソル位置を調整
|
|
1217
|
+
if result
|
|
1218
|
+
bookmarks_after = @bookmark_manager.list
|
|
1219
|
+
@current_index = [@current_index, bookmarks_after.length - 1].min if @current_index >= bookmarks_after.length
|
|
1220
|
+
@current_index = 0 if @current_index < 0
|
|
1221
|
+
end
|
|
1222
|
+
|
|
1223
|
+
@terminal_ui&.refresh_display if @terminal_ui
|
|
1224
|
+
result
|
|
1225
|
+
end
|
|
1226
|
+
|
|
1227
|
+
# プロジェクトモードでブックマークの選択をトグル
|
|
1228
|
+
def select_bookmark_in_project_mode
|
|
1229
|
+
bookmarks = @bookmark_manager.list
|
|
1230
|
+
return false if bookmarks.empty? || @current_index >= bookmarks.length
|
|
1231
|
+
|
|
1232
|
+
# ブックマークをプロジェクトとして選択
|
|
1233
|
+
@project_mode.select_bookmark(@current_index + 1)
|
|
1234
|
+
true
|
|
1235
|
+
end
|
|
1236
|
+
|
|
1237
|
+
# プロジェクトモード中かどうか
|
|
1238
|
+
def in_project_mode?
|
|
1239
|
+
@in_project_mode
|
|
1240
|
+
end
|
|
1241
|
+
|
|
1242
|
+
# ログモード中かどうか
|
|
1243
|
+
def in_log_mode?
|
|
1244
|
+
@in_log_mode
|
|
1245
|
+
end
|
|
836
1246
|
end
|
|
837
1247
|
end
|