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
data/lib/rufio/terminal_ui.rb
CHANGED
|
@@ -48,6 +48,13 @@ module Rufio
|
|
|
48
48
|
@command_mode = CommandMode.new
|
|
49
49
|
@dialog_renderer = DialogRenderer.new
|
|
50
50
|
@command_mode_ui = CommandModeUI.new(@command_mode, @dialog_renderer)
|
|
51
|
+
|
|
52
|
+
# Project mode
|
|
53
|
+
@project_mode = nil
|
|
54
|
+
@project_command = nil
|
|
55
|
+
@project_log = nil
|
|
56
|
+
@in_project_mode = false
|
|
57
|
+
@in_log_mode = false
|
|
51
58
|
end
|
|
52
59
|
|
|
53
60
|
def start(directory_listing, keybind_handler, file_preview)
|
|
@@ -60,6 +67,9 @@ module Rufio
|
|
|
60
67
|
@running = true
|
|
61
68
|
setup_terminal
|
|
62
69
|
|
|
70
|
+
# Show info notices if any
|
|
71
|
+
show_info_notices
|
|
72
|
+
|
|
63
73
|
begin
|
|
64
74
|
main_loop
|
|
65
75
|
ensure
|
|
@@ -109,6 +119,12 @@ module Rufio
|
|
|
109
119
|
# move cursor to top of screen (don't clear)
|
|
110
120
|
print "\e[H"
|
|
111
121
|
|
|
122
|
+
# プロジェクトモードの場合は専用の画面を描画
|
|
123
|
+
if @in_project_mode
|
|
124
|
+
draw_project_mode_screen
|
|
125
|
+
return
|
|
126
|
+
end
|
|
127
|
+
|
|
112
128
|
# header (2 lines)
|
|
113
129
|
draw_header
|
|
114
130
|
draw_base_directory_info
|
|
@@ -418,7 +434,7 @@ module Rufio
|
|
|
418
434
|
width = 0
|
|
419
435
|
string.each_char do |char|
|
|
420
436
|
# 全角文字の判定
|
|
421
|
-
width += if char.ord > 127 || char.match?(/[
|
|
437
|
+
width += if char.ord > 127 || char.match?(/[あ-んア-ン一-龯]/)
|
|
422
438
|
2
|
|
423
439
|
else
|
|
424
440
|
1
|
|
@@ -435,7 +451,7 @@ module Rufio
|
|
|
435
451
|
result = ''
|
|
436
452
|
|
|
437
453
|
string.each_char do |char|
|
|
438
|
-
char_width = char.ord > 127 || char.match?(/[
|
|
454
|
+
char_width = char.ord > 127 || char.match?(/[あ-んア-ン一-龯]/) ? 2 : 1
|
|
439
455
|
|
|
440
456
|
if current_width + char_width > max_width
|
|
441
457
|
# "..."を追加できるかチェック
|
|
@@ -461,7 +477,7 @@ module Rufio
|
|
|
461
477
|
punct_break_point = nil
|
|
462
478
|
|
|
463
479
|
line.each_char.with_index do |char, index|
|
|
464
|
-
char_width = char.ord > 127 || char.match?(/[
|
|
480
|
+
char_width = char.ord > 127 || char.match?(/[あ-んア-ン一-龯]/) ? 2 : 1
|
|
465
481
|
|
|
466
482
|
break if current_width + char_width > max_width
|
|
467
483
|
|
|
@@ -563,7 +579,7 @@ module Rufio
|
|
|
563
579
|
end
|
|
564
580
|
|
|
565
581
|
# キーバインドハンドラーに処理を委譲
|
|
566
|
-
|
|
582
|
+
_result = @keybind_handler.handle_key(input)
|
|
567
583
|
|
|
568
584
|
# 終了処理(qキーのみ)
|
|
569
585
|
if input == 'q'
|
|
@@ -625,6 +641,538 @@ module Rufio
|
|
|
625
641
|
# 画面を再描画
|
|
626
642
|
draw_screen
|
|
627
643
|
end
|
|
644
|
+
|
|
645
|
+
# Show info notices from the info directory if any are unread
|
|
646
|
+
def show_info_notices
|
|
647
|
+
require_relative 'info_notice'
|
|
648
|
+
info_notice = InfoNotice.new
|
|
649
|
+
notices = info_notice.unread_notices
|
|
650
|
+
|
|
651
|
+
notices.each do |notice|
|
|
652
|
+
show_info_notice(notice, info_notice)
|
|
653
|
+
end
|
|
654
|
+
end
|
|
655
|
+
|
|
656
|
+
# Show a single info notice
|
|
657
|
+
# @param notice [Hash] Notice hash with :title and :content
|
|
658
|
+
# @param info_notice [InfoNotice] InfoNotice instance to mark as shown
|
|
659
|
+
def show_info_notice(notice, info_notice)
|
|
660
|
+
# Calculate window dimensions
|
|
661
|
+
width = [@screen_width - 10, 70].min
|
|
662
|
+
# Calculate height based on content length
|
|
663
|
+
content_length = notice[:content].length
|
|
664
|
+
height = [content_length + 4, @screen_height - 4].min # +4 for borders and title
|
|
665
|
+
x = (@screen_width - width) / 2
|
|
666
|
+
y = (@screen_height - height) / 2
|
|
667
|
+
|
|
668
|
+
# Display the notice window
|
|
669
|
+
@dialog_renderer.draw_floating_window(
|
|
670
|
+
x, y, width, height,
|
|
671
|
+
notice[:title],
|
|
672
|
+
notice[:content],
|
|
673
|
+
{
|
|
674
|
+
border_color: "\e[36m", # Cyan
|
|
675
|
+
title_color: "\e[1;36m", # Bold cyan
|
|
676
|
+
content_color: "\e[37m" # White
|
|
677
|
+
}
|
|
678
|
+
)
|
|
679
|
+
|
|
680
|
+
# Force flush to ensure display
|
|
681
|
+
$stdout.flush
|
|
682
|
+
|
|
683
|
+
# Wait for any key press
|
|
684
|
+
require 'io/console'
|
|
685
|
+
IO.console.getch
|
|
686
|
+
|
|
687
|
+
# Mark as shown
|
|
688
|
+
info_notice.mark_as_shown(notice[:file])
|
|
689
|
+
|
|
690
|
+
# Clear the notice window
|
|
691
|
+
@dialog_renderer.clear_area(x, y, width, height)
|
|
692
|
+
|
|
693
|
+
# Redraw the screen
|
|
694
|
+
draw_screen
|
|
695
|
+
end
|
|
696
|
+
|
|
697
|
+
# プロジェクトモードを設定
|
|
698
|
+
def set_project_mode(project_mode, project_command, project_log)
|
|
699
|
+
@project_mode = project_mode
|
|
700
|
+
@project_command = project_command
|
|
701
|
+
@project_log = project_log
|
|
702
|
+
@in_project_mode = true
|
|
703
|
+
@in_log_mode = false
|
|
704
|
+
refresh_display
|
|
705
|
+
draw_screen
|
|
706
|
+
end
|
|
707
|
+
|
|
708
|
+
# プロジェクトモードを終了
|
|
709
|
+
def exit_project_mode
|
|
710
|
+
@in_project_mode = false
|
|
711
|
+
@in_log_mode = false
|
|
712
|
+
@project_mode = nil
|
|
713
|
+
@project_command = nil
|
|
714
|
+
@project_log = nil
|
|
715
|
+
refresh_display
|
|
716
|
+
draw_screen
|
|
717
|
+
end
|
|
718
|
+
|
|
719
|
+
# ログモードに入る
|
|
720
|
+
def enter_log_mode(project_log)
|
|
721
|
+
@in_log_mode = true
|
|
722
|
+
@project_log = project_log
|
|
723
|
+
refresh_display
|
|
724
|
+
draw_screen
|
|
725
|
+
end
|
|
726
|
+
|
|
727
|
+
# プロジェクトモード画面を描画
|
|
728
|
+
def draw_project_mode_screen
|
|
729
|
+
# header
|
|
730
|
+
print "\e[1;1H" # Move to top-left
|
|
731
|
+
header = @in_log_mode ? "📋 Project Mode - Logs" : "📁 Project Mode - Bookmarks"
|
|
732
|
+
print "\e[44m\e[97m#{header.ljust(@screen_width)}\e[0m\n"
|
|
733
|
+
print "\e[0m#{' ' * @screen_width}\n"
|
|
734
|
+
|
|
735
|
+
# calculate dimensions
|
|
736
|
+
content_height = @screen_height - HEADER_FOOTER_MARGIN
|
|
737
|
+
left_width = (@screen_width * LEFT_PANEL_RATIO).to_i
|
|
738
|
+
right_width = @screen_width - left_width
|
|
739
|
+
|
|
740
|
+
if @in_log_mode
|
|
741
|
+
# ログモード: ログファイル一覧と内容
|
|
742
|
+
draw_log_list(left_width, content_height)
|
|
743
|
+
draw_log_preview(right_width, content_height, left_width)
|
|
744
|
+
else
|
|
745
|
+
# ブックマークモード: プロジェクト一覧と詳細
|
|
746
|
+
draw_bookmark_list(left_width, content_height)
|
|
747
|
+
draw_bookmark_detail(right_width, content_height, left_width)
|
|
748
|
+
end
|
|
749
|
+
|
|
750
|
+
# footer(通常モードと同じスタイル)
|
|
751
|
+
footer_line = @screen_height
|
|
752
|
+
print "\e[#{footer_line};1H"
|
|
753
|
+
footer_text = if @in_log_mode
|
|
754
|
+
"ESC:exit log j/k:move"
|
|
755
|
+
else
|
|
756
|
+
"SPACE:select l:logs ::cmd r:rename d:delete ESC:exit j/k:move"
|
|
757
|
+
end
|
|
758
|
+
# 文字列を確実に画面幅に合わせる
|
|
759
|
+
footer_content = footer_text.ljust(@screen_width)[0...@screen_width]
|
|
760
|
+
print "\e[7m#{footer_content}\e[0m"
|
|
761
|
+
|
|
762
|
+
# move cursor to invisible position
|
|
763
|
+
print "\e[#{@screen_height};#{@screen_width}H"
|
|
764
|
+
end
|
|
765
|
+
|
|
766
|
+
# ブックマーク一覧を描画
|
|
767
|
+
def draw_bookmark_list(width, height)
|
|
768
|
+
bookmarks = @project_mode.list_bookmarks
|
|
769
|
+
current_index = @keybind_handler.current_index
|
|
770
|
+
|
|
771
|
+
print "\e[#{CONTENT_START_LINE};1H"
|
|
772
|
+
|
|
773
|
+
if bookmarks.empty?
|
|
774
|
+
print " No bookmarks found"
|
|
775
|
+
(height - 1).times { puts ' ' * width }
|
|
776
|
+
return
|
|
777
|
+
end
|
|
778
|
+
|
|
779
|
+
selected_name = @project_mode.selected_name
|
|
780
|
+
|
|
781
|
+
bookmarks.each_with_index do |bookmark, index|
|
|
782
|
+
line_num = CONTENT_START_LINE + index
|
|
783
|
+
break if index >= height
|
|
784
|
+
|
|
785
|
+
# 選択マーク(通常モードと同じ)
|
|
786
|
+
is_project_selected = (bookmark[:name] == selected_name)
|
|
787
|
+
selection_mark = is_project_selected ? "✓ " : " "
|
|
788
|
+
|
|
789
|
+
# ブックマーク名を表示
|
|
790
|
+
name = bookmark[:name]
|
|
791
|
+
max_name_length = width - 4 # selection_mark分を除く
|
|
792
|
+
display_name = name.length > max_name_length ? name[0...max_name_length - 3] + '...' : name
|
|
793
|
+
line_content = "#{selection_mark}#{display_name}".ljust(width)
|
|
794
|
+
|
|
795
|
+
if index == current_index
|
|
796
|
+
# カーソル位置は選択色でハイライト
|
|
797
|
+
selected_color = ColorHelper.color_to_selected_ansi(ConfigLoader.colors[:selected])
|
|
798
|
+
print "\e[#{line_num};1H#{selected_color}#{line_content[0...width]}#{ColorHelper.reset}"
|
|
799
|
+
else
|
|
800
|
+
# 選択済みブックマークは緑背景、黒文字
|
|
801
|
+
if is_project_selected
|
|
802
|
+
print "\e[#{line_num};1H\e[42m\e[30m#{line_content[0...width]}\e[0m"
|
|
803
|
+
else
|
|
804
|
+
print "\e[#{line_num};1H#{line_content[0...width]}"
|
|
805
|
+
end
|
|
806
|
+
end
|
|
807
|
+
end
|
|
808
|
+
|
|
809
|
+
# 残りの行をクリア
|
|
810
|
+
remaining_lines = height - bookmarks.length
|
|
811
|
+
remaining_lines.times do |i|
|
|
812
|
+
line_num = CONTENT_START_LINE + bookmarks.length + i
|
|
813
|
+
print "\e[#{line_num};1H#{' ' * width}"
|
|
814
|
+
end
|
|
815
|
+
end
|
|
816
|
+
|
|
817
|
+
# ブックマーク詳細を描画
|
|
818
|
+
def draw_bookmark_detail(width, height, left_offset)
|
|
819
|
+
bookmarks = @project_mode.list_bookmarks
|
|
820
|
+
current_index = @keybind_handler.current_index
|
|
821
|
+
|
|
822
|
+
return if bookmarks.empty? || current_index >= bookmarks.length
|
|
823
|
+
|
|
824
|
+
bookmark = bookmarks[current_index]
|
|
825
|
+
path = bookmark[:path]
|
|
826
|
+
|
|
827
|
+
# ディレクトリ内容を取得
|
|
828
|
+
details = [
|
|
829
|
+
"Project: #{bookmark[:name]}",
|
|
830
|
+
"Path: #{path}",
|
|
831
|
+
"",
|
|
832
|
+
"Directory contents:",
|
|
833
|
+
""
|
|
834
|
+
]
|
|
835
|
+
|
|
836
|
+
# ディレクトリが存在する場合、内容を表示
|
|
837
|
+
if Dir.exist?(path)
|
|
838
|
+
begin
|
|
839
|
+
entries = Dir.entries(path).reject { |e| e == '.' || e == '..' }.sort
|
|
840
|
+
|
|
841
|
+
# 最大表示数を計算(ヘッダー分を引く)
|
|
842
|
+
max_entries = height - details.length
|
|
843
|
+
|
|
844
|
+
entries.take(max_entries).each do |entry|
|
|
845
|
+
full_path = File.join(path, entry)
|
|
846
|
+
icon = File.directory?(full_path) ? '📁' : '📄'
|
|
847
|
+
details << " #{icon} #{entry}"
|
|
848
|
+
end
|
|
849
|
+
|
|
850
|
+
# 表示しきれない場合
|
|
851
|
+
if entries.length > max_entries
|
|
852
|
+
details << " ... and #{entries.length - max_entries} more"
|
|
853
|
+
end
|
|
854
|
+
rescue => e
|
|
855
|
+
details << " Error reading directory: #{e.message}"
|
|
856
|
+
end
|
|
857
|
+
else
|
|
858
|
+
details << " Directory does not exist"
|
|
859
|
+
end
|
|
860
|
+
|
|
861
|
+
# 各行にセパレータと内容を表示(通常モードと同じ)
|
|
862
|
+
height.times do |i|
|
|
863
|
+
line_num = CONTENT_START_LINE + i
|
|
864
|
+
|
|
865
|
+
# セパレータを表示
|
|
866
|
+
cursor_position = left_offset + CURSOR_OFFSET
|
|
867
|
+
print "\e[#{line_num};#{cursor_position}H"
|
|
868
|
+
print '│'
|
|
869
|
+
|
|
870
|
+
# 右画面の内容を表示
|
|
871
|
+
if i < details.length
|
|
872
|
+
line = details[i]
|
|
873
|
+
safe_width = width - 2
|
|
874
|
+
content = " #{line}"
|
|
875
|
+
content = content[0...safe_width] if content.length > safe_width
|
|
876
|
+
print content
|
|
877
|
+
|
|
878
|
+
# 残りをスペースで埋める
|
|
879
|
+
remaining = safe_width - content.length
|
|
880
|
+
print ' ' * remaining if remaining > 0
|
|
881
|
+
else
|
|
882
|
+
# 空行
|
|
883
|
+
print ' ' * (width - 2)
|
|
884
|
+
end
|
|
885
|
+
end
|
|
886
|
+
end
|
|
887
|
+
|
|
888
|
+
# ログファイル一覧を描画
|
|
889
|
+
def draw_log_list(width, height)
|
|
890
|
+
log_files = @project_log.list_log_files
|
|
891
|
+
current_index = @keybind_handler.current_index
|
|
892
|
+
|
|
893
|
+
print "\e[#{CONTENT_START_LINE};1H"
|
|
894
|
+
|
|
895
|
+
if log_files.empty?
|
|
896
|
+
print " No log files found"
|
|
897
|
+
(height - 1).times { puts ' ' * width }
|
|
898
|
+
return
|
|
899
|
+
end
|
|
900
|
+
|
|
901
|
+
log_files.each_with_index do |filename, index|
|
|
902
|
+
line_num = CONTENT_START_LINE + index
|
|
903
|
+
break if index >= height
|
|
904
|
+
|
|
905
|
+
cursor_mark = index == current_index ? '>' : ' '
|
|
906
|
+
display_name = filename.ljust(width - 3)
|
|
907
|
+
|
|
908
|
+
if index == current_index
|
|
909
|
+
print "\e[#{line_num};1H\e[7m#{cursor_mark} #{display_name[0...width-3]}\e[0m"
|
|
910
|
+
else
|
|
911
|
+
print "\e[#{line_num};1H #{display_name[0...width-3]}"
|
|
912
|
+
end
|
|
913
|
+
end
|
|
914
|
+
|
|
915
|
+
# 残りの行をクリア
|
|
916
|
+
remaining_lines = height - log_files.length
|
|
917
|
+
remaining_lines.times do |i|
|
|
918
|
+
line_num = CONTENT_START_LINE + log_files.length + i
|
|
919
|
+
print "\e[#{line_num};1H#{' ' * width}"
|
|
920
|
+
end
|
|
921
|
+
end
|
|
922
|
+
|
|
923
|
+
# ログプレビューを描画
|
|
924
|
+
def draw_log_preview(width, height, left_offset)
|
|
925
|
+
log_files = @project_log.list_log_files
|
|
926
|
+
current_index = @keybind_handler.current_index
|
|
927
|
+
|
|
928
|
+
return if log_files.empty? || current_index >= log_files.length
|
|
929
|
+
|
|
930
|
+
filename = log_files[current_index]
|
|
931
|
+
content = @project_log.preview(filename)
|
|
932
|
+
|
|
933
|
+
lines = content.split("\n")
|
|
934
|
+
|
|
935
|
+
# 各行にセパレータと内容を表示(通常モードと同じ)
|
|
936
|
+
height.times do |i|
|
|
937
|
+
line_num = CONTENT_START_LINE + i
|
|
938
|
+
|
|
939
|
+
# セパレータを表示
|
|
940
|
+
cursor_position = left_offset + CURSOR_OFFSET
|
|
941
|
+
print "\e[#{line_num};#{cursor_position}H"
|
|
942
|
+
print '│'
|
|
943
|
+
|
|
944
|
+
# 右画面の内容を表示
|
|
945
|
+
if i < lines.length
|
|
946
|
+
line = lines[i]
|
|
947
|
+
safe_width = width - 2
|
|
948
|
+
content = " #{line}"
|
|
949
|
+
content = content[0...safe_width] if content.length > safe_width
|
|
950
|
+
print content
|
|
951
|
+
|
|
952
|
+
# 残りをスペースで埋める
|
|
953
|
+
remaining = safe_width - content.length
|
|
954
|
+
print ' ' * remaining if remaining > 0
|
|
955
|
+
else
|
|
956
|
+
# 空行
|
|
957
|
+
print ' ' * (width - 2)
|
|
958
|
+
end
|
|
959
|
+
end
|
|
960
|
+
end
|
|
961
|
+
|
|
962
|
+
# ログモードを終了してプロジェクトモードに戻る
|
|
963
|
+
def exit_log_mode
|
|
964
|
+
@in_log_mode = false
|
|
965
|
+
refresh_display
|
|
966
|
+
draw_screen
|
|
967
|
+
end
|
|
968
|
+
|
|
969
|
+
# プロジェクト未選択メッセージ
|
|
970
|
+
def show_project_not_selected_message
|
|
971
|
+
content_lines = [
|
|
972
|
+
'',
|
|
973
|
+
'Please select a project first by pressing SPACE',
|
|
974
|
+
'',
|
|
975
|
+
'Press any key to continue...'
|
|
976
|
+
]
|
|
977
|
+
|
|
978
|
+
width = 50
|
|
979
|
+
height = 8
|
|
980
|
+
x, y = @dialog_renderer.calculate_center(width, height)
|
|
981
|
+
|
|
982
|
+
@dialog_renderer.draw_floating_window(x, y, width, height, 'No Project Selected', content_lines, {
|
|
983
|
+
border_color: "\e[33m", # Yellow (warning)
|
|
984
|
+
title_color: "\e[1;33m", # Bold yellow
|
|
985
|
+
content_color: "\e[37m" # White
|
|
986
|
+
})
|
|
987
|
+
|
|
988
|
+
require 'io/console'
|
|
989
|
+
IO.console.getch
|
|
990
|
+
@dialog_renderer.clear_area(x, y, width, height)
|
|
991
|
+
|
|
992
|
+
# 画面を再描画
|
|
993
|
+
refresh_display
|
|
994
|
+
draw_screen
|
|
995
|
+
end
|
|
996
|
+
|
|
997
|
+
# プロジェクトモードでコマンドを実行
|
|
998
|
+
def activate_project_command_mode(project_mode, project_command, project_log)
|
|
999
|
+
return unless project_mode.selected_path
|
|
1000
|
+
|
|
1001
|
+
# スクリプトまたはコマンドを選択
|
|
1002
|
+
choice = show_script_or_command_dialog(project_mode.selected_name, project_command)
|
|
1003
|
+
return unless choice
|
|
1004
|
+
|
|
1005
|
+
command = nil
|
|
1006
|
+
result = nil
|
|
1007
|
+
|
|
1008
|
+
if choice[:type] == :script
|
|
1009
|
+
# スクリプトを実行
|
|
1010
|
+
command = "ruby script: #{choice[:value]}"
|
|
1011
|
+
result = project_command.execute_script(choice[:value], project_mode.selected_path)
|
|
1012
|
+
else
|
|
1013
|
+
# 通常のコマンドを実行
|
|
1014
|
+
command = choice[:value]
|
|
1015
|
+
result = project_command.execute(command, project_mode.selected_path)
|
|
1016
|
+
end
|
|
1017
|
+
|
|
1018
|
+
# ログを保存
|
|
1019
|
+
project_log.save(project_mode.selected_name, command, result[:output])
|
|
1020
|
+
|
|
1021
|
+
# 結果を表示
|
|
1022
|
+
show_project_command_result_dialog(command, result)
|
|
1023
|
+
|
|
1024
|
+
# 画面を再描画
|
|
1025
|
+
refresh_display
|
|
1026
|
+
draw_screen
|
|
1027
|
+
end
|
|
1028
|
+
|
|
1029
|
+
# スクリプトまたはコマンドを選択
|
|
1030
|
+
def show_script_or_command_dialog(project_name, project_command)
|
|
1031
|
+
scripts = project_command.list_scripts
|
|
1032
|
+
|
|
1033
|
+
content_lines = [
|
|
1034
|
+
'',
|
|
1035
|
+
"Project: #{project_name}",
|
|
1036
|
+
''
|
|
1037
|
+
]
|
|
1038
|
+
|
|
1039
|
+
if scripts.empty?
|
|
1040
|
+
content_lines << 'No scripts found in scripts directory'
|
|
1041
|
+
content_lines << " (#{project_command.scripts_dir})"
|
|
1042
|
+
content_lines << ''
|
|
1043
|
+
content_lines << 'Press C to enter custom command'
|
|
1044
|
+
content_lines << 'Press ESC to cancel'
|
|
1045
|
+
else
|
|
1046
|
+
content_lines << 'Available scripts:'
|
|
1047
|
+
content_lines << ''
|
|
1048
|
+
scripts.each_with_index do |script, index|
|
|
1049
|
+
content_lines << " #{index + 1}. #{script}"
|
|
1050
|
+
end
|
|
1051
|
+
content_lines << ''
|
|
1052
|
+
content_lines << 'Press 1-9 to select script'
|
|
1053
|
+
content_lines << 'Press C to enter custom command'
|
|
1054
|
+
content_lines << 'Press ESC to cancel'
|
|
1055
|
+
end
|
|
1056
|
+
|
|
1057
|
+
width = 70
|
|
1058
|
+
height = [content_lines.length + 4, 25].min
|
|
1059
|
+
x, y = @dialog_renderer.calculate_center(width, height)
|
|
1060
|
+
|
|
1061
|
+
@dialog_renderer.draw_floating_window(x, y, width, height, 'Execute in Project', content_lines, {
|
|
1062
|
+
border_color: "\e[32m",
|
|
1063
|
+
title_color: "\e[1;32m",
|
|
1064
|
+
content_color: "\e[37m"
|
|
1065
|
+
})
|
|
1066
|
+
|
|
1067
|
+
require 'io/console'
|
|
1068
|
+
choice = nil
|
|
1069
|
+
|
|
1070
|
+
loop do
|
|
1071
|
+
input = IO.console.getch.downcase
|
|
1072
|
+
|
|
1073
|
+
case input
|
|
1074
|
+
when "\e" # ESC
|
|
1075
|
+
break
|
|
1076
|
+
when 'c' # Custom command
|
|
1077
|
+
@dialog_renderer.clear_area(x, y, width, height)
|
|
1078
|
+
command = show_project_command_input_dialog(project_name)
|
|
1079
|
+
choice = { type: :command, value: command } if command && !command.empty?
|
|
1080
|
+
break
|
|
1081
|
+
when '1'..'9'
|
|
1082
|
+
number = input.to_i
|
|
1083
|
+
if number > 0 && number <= scripts.length
|
|
1084
|
+
choice = { type: :script, value: scripts[number - 1] }
|
|
1085
|
+
break
|
|
1086
|
+
end
|
|
1087
|
+
end
|
|
1088
|
+
end
|
|
1089
|
+
|
|
1090
|
+
@dialog_renderer.clear_area(x, y, width, height)
|
|
1091
|
+
choice
|
|
1092
|
+
end
|
|
1093
|
+
|
|
1094
|
+
# プロジェクトコマンド入力ダイアログ
|
|
1095
|
+
def show_project_command_input_dialog(project_name)
|
|
1096
|
+
title = "Execute Command in: #{project_name}"
|
|
1097
|
+
prompt = "Enter command:"
|
|
1098
|
+
|
|
1099
|
+
@dialog_renderer.show_input_dialog(title, prompt, {
|
|
1100
|
+
border_color: "\e[32m", # Green
|
|
1101
|
+
title_color: "\e[1;32m", # Bold green
|
|
1102
|
+
content_color: "\e[37m" # White
|
|
1103
|
+
})
|
|
1104
|
+
end
|
|
1105
|
+
|
|
1106
|
+
# プロジェクトコマンド結果ダイアログ
|
|
1107
|
+
def show_project_command_result_dialog(command, result)
|
|
1108
|
+
title = result[:success] ? "Command Success" : "Command Failed"
|
|
1109
|
+
|
|
1110
|
+
# 出力を最初の10行まで表示
|
|
1111
|
+
output_lines = (result[:output] || result[:error] || '').split("\n").take(10)
|
|
1112
|
+
|
|
1113
|
+
content_lines = [
|
|
1114
|
+
'',
|
|
1115
|
+
"Command: #{command}",
|
|
1116
|
+
'',
|
|
1117
|
+
"Output:",
|
|
1118
|
+
''
|
|
1119
|
+
] + output_lines
|
|
1120
|
+
|
|
1121
|
+
if output_lines.length >= 10
|
|
1122
|
+
content_lines << '... (see log for full output)'
|
|
1123
|
+
end
|
|
1124
|
+
|
|
1125
|
+
content_lines << ''
|
|
1126
|
+
content_lines << 'Press any key to continue...'
|
|
1127
|
+
|
|
1128
|
+
width = 80
|
|
1129
|
+
height = [content_lines.length + 4, 20].min
|
|
1130
|
+
x, y = @dialog_renderer.calculate_center(width, height)
|
|
1131
|
+
|
|
1132
|
+
border_color = result[:success] ? "\e[32m" : "\e[31m" # Green or Red
|
|
1133
|
+
title_color = result[:success] ? "\e[1;32m" : "\e[1;31m"
|
|
1134
|
+
|
|
1135
|
+
@dialog_renderer.draw_floating_window(x, y, width, height, title, content_lines, {
|
|
1136
|
+
border_color: border_color,
|
|
1137
|
+
title_color: title_color,
|
|
1138
|
+
content_color: "\e[37m"
|
|
1139
|
+
})
|
|
1140
|
+
|
|
1141
|
+
require 'io/console'
|
|
1142
|
+
IO.console.getch
|
|
1143
|
+
@dialog_renderer.clear_area(x, y, width, height)
|
|
1144
|
+
end
|
|
1145
|
+
|
|
1146
|
+
# プロジェクト選択時の表示
|
|
1147
|
+
def show_project_selected
|
|
1148
|
+
# 選択完了メッセージを表示
|
|
1149
|
+
content_lines = [
|
|
1150
|
+
'',
|
|
1151
|
+
'Project selected!',
|
|
1152
|
+
'',
|
|
1153
|
+
'You can now press : to execute commands',
|
|
1154
|
+
'',
|
|
1155
|
+
'Press any key to continue...'
|
|
1156
|
+
]
|
|
1157
|
+
|
|
1158
|
+
width = 50
|
|
1159
|
+
height = 10
|
|
1160
|
+
x, y = @dialog_renderer.calculate_center(width, height)
|
|
1161
|
+
|
|
1162
|
+
@dialog_renderer.draw_floating_window(x, y, width, height, 'Project Selected', content_lines, {
|
|
1163
|
+
border_color: "\e[32m", # Green
|
|
1164
|
+
title_color: "\e[1;32m", # Bold green
|
|
1165
|
+
content_color: "\e[37m" # White
|
|
1166
|
+
})
|
|
1167
|
+
|
|
1168
|
+
require 'io/console'
|
|
1169
|
+
IO.console.getch
|
|
1170
|
+
@dialog_renderer.clear_area(x, y, width, height)
|
|
1171
|
+
|
|
1172
|
+
# 画面を再描画
|
|
1173
|
+
refresh_display
|
|
1174
|
+
draw_screen
|
|
1175
|
+
end
|
|
628
1176
|
end
|
|
629
1177
|
end
|
|
630
1178
|
|
data/lib/rufio/version.rb
CHANGED
data/lib/rufio.rb
CHANGED
|
@@ -28,6 +28,11 @@ require_relative "rufio/plugin_manager"
|
|
|
28
28
|
require_relative "rufio/command_mode"
|
|
29
29
|
require_relative "rufio/command_mode_ui"
|
|
30
30
|
|
|
31
|
+
# プロジェクトモード
|
|
32
|
+
require_relative "rufio/project_mode"
|
|
33
|
+
require_relative "rufio/project_command"
|
|
34
|
+
require_relative "rufio/project_log"
|
|
35
|
+
|
|
31
36
|
module Rufio
|
|
32
37
|
class Error < StandardError; end
|
|
33
38
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rufio
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.20.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- masisz
|
|
@@ -118,6 +118,7 @@ extra_rdoc_files: []
|
|
|
118
118
|
files:
|
|
119
119
|
- CHANGELOG.md
|
|
120
120
|
- CHANGELOG_v0.10.0.md
|
|
121
|
+
- CHANGELOG_v0.20.0.md
|
|
121
122
|
- CHANGELOG_v0.4.0.md
|
|
122
123
|
- CHANGELOG_v0.5.0.md
|
|
123
124
|
- CHANGELOG_v0.6.0.md
|
|
@@ -131,6 +132,7 @@ files:
|
|
|
131
132
|
- config_example.rb
|
|
132
133
|
- docs/PLUGIN_GUIDE.md
|
|
133
134
|
- docs/plugin_example.rb
|
|
135
|
+
- info/welcome.txt
|
|
134
136
|
- lib/rufio.rb
|
|
135
137
|
- lib/rufio/application.rb
|
|
136
138
|
- lib/rufio/bookmark.rb
|
|
@@ -147,12 +149,16 @@ files:
|
|
|
147
149
|
- lib/rufio/file_preview.rb
|
|
148
150
|
- lib/rufio/filter_manager.rb
|
|
149
151
|
- lib/rufio/health_checker.rb
|
|
152
|
+
- lib/rufio/info_notice.rb
|
|
150
153
|
- lib/rufio/keybind_handler.rb
|
|
151
154
|
- lib/rufio/logger.rb
|
|
152
155
|
- lib/rufio/plugin.rb
|
|
153
156
|
- lib/rufio/plugin_config.rb
|
|
154
157
|
- lib/rufio/plugin_manager.rb
|
|
155
158
|
- lib/rufio/plugins/file_operations.rb
|
|
159
|
+
- lib/rufio/project_command.rb
|
|
160
|
+
- lib/rufio/project_log.rb
|
|
161
|
+
- lib/rufio/project_mode.rb
|
|
156
162
|
- lib/rufio/selection_manager.rb
|
|
157
163
|
- lib/rufio/terminal_ui.rb
|
|
158
164
|
- lib/rufio/text_utils.rb
|