ruby_rich 0.2.0 → 0.3.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/lib/ruby_rich/ansi_code.rb +195 -0
- data/lib/ruby_rich/console.rb +11 -8
- data/lib/ruby_rich/dialog.rb +46 -116
- data/lib/ruby_rich/layout.rb +76 -7
- data/lib/ruby_rich/live.rb +21 -4
- data/lib/ruby_rich/panel.rb +100 -123
- data/lib/ruby_rich/print.rb +1 -1
- data/lib/ruby_rich/table.rb +1 -1
- data/lib/ruby_rich/text.rb +13 -37
- data/lib/ruby_rich/version.rb +1 -1
- data/lib/ruby_rich.rb +1 -0
- metadata +4 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: '009a5bcd619dc3fa975c6ab3b01d1494e002ccc760b6e8ac335026f63860671e'
|
|
4
|
+
data.tar.gz: 205f8d0f1cdab77854a564c43761c30113f2989216ba2ed91b7ac66e77f2387f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: bedd0da9ee879bcca97fc9faf3c597cf857a05b6b8cca59004e9fbaefdbbee13b885b89902d4909a2b5718d7877201780f44418be7e2f3044f65c9840e40fe58
|
|
7
|
+
data.tar.gz: ac828f4bb8e7552b6a1b7310a0ba7be97d256710ce7f4aca3c523e0493b41e458fb722b33afafe01b90ab568338f14f6d56a3d546b29d191131986b07690acab
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
module RubyRich
|
|
2
|
+
class AnsiCode
|
|
3
|
+
ANSI_CODES = {
|
|
4
|
+
reset: "\e[0m",
|
|
5
|
+
bold: "1",
|
|
6
|
+
faint: "2",
|
|
7
|
+
italic: "3",
|
|
8
|
+
underline: "4",
|
|
9
|
+
curly_underline: "4:3",
|
|
10
|
+
dotted_underline: "4:4",
|
|
11
|
+
dashed_underline: "4:5",
|
|
12
|
+
double_underline: "21",
|
|
13
|
+
blink: "5",
|
|
14
|
+
rapid_blink: "6",
|
|
15
|
+
inverse: "7",
|
|
16
|
+
invisible: "8",
|
|
17
|
+
strikethrough: "9",
|
|
18
|
+
fraktur: "20",
|
|
19
|
+
no_blink: "25",
|
|
20
|
+
no_inverse: "27",
|
|
21
|
+
overline: "53",
|
|
22
|
+
color: {
|
|
23
|
+
black: "30",
|
|
24
|
+
red: "31",
|
|
25
|
+
green: "32",
|
|
26
|
+
yellow: "33",
|
|
27
|
+
blue: "34",
|
|
28
|
+
magenta: "35",
|
|
29
|
+
cyan: "36",
|
|
30
|
+
white: "37"
|
|
31
|
+
},
|
|
32
|
+
bright_color:{
|
|
33
|
+
black: "90",
|
|
34
|
+
red: "91",
|
|
35
|
+
green: "92",
|
|
36
|
+
yellow: "93",
|
|
37
|
+
blue: "94",
|
|
38
|
+
magenta: "95",
|
|
39
|
+
cyan: "96",
|
|
40
|
+
white: "97"
|
|
41
|
+
},
|
|
42
|
+
background: {
|
|
43
|
+
black: "40",
|
|
44
|
+
red: "41",
|
|
45
|
+
green: "42",
|
|
46
|
+
yellow: "43",
|
|
47
|
+
blue: "44",
|
|
48
|
+
magenta: "45",
|
|
49
|
+
cyan: "46",
|
|
50
|
+
white: "47"
|
|
51
|
+
},
|
|
52
|
+
bright_background: {
|
|
53
|
+
black: "100",
|
|
54
|
+
red: "101",
|
|
55
|
+
green: "102",
|
|
56
|
+
yellow: "103",
|
|
57
|
+
blue: "104",
|
|
58
|
+
magenta: "105",
|
|
59
|
+
cyan: "106",
|
|
60
|
+
white: "107"
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
def self.reset
|
|
65
|
+
ANSI_CODES[:reset]
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def self.color(color, bright=false)
|
|
69
|
+
if bright
|
|
70
|
+
"\e[#{ANSI_CODES[:bright_color][color]}m"
|
|
71
|
+
else
|
|
72
|
+
"\e[#{ANSI_CODES[:color][color]}m"
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def self.background(color, bright=false)
|
|
77
|
+
if bright
|
|
78
|
+
"\e[#{ANSI_CODES[:bright_background][color]}m"
|
|
79
|
+
else
|
|
80
|
+
"\e[#{ANSI_CODES[:background][color]}m"
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def self.bold
|
|
85
|
+
"\e[#{ANSI_CODES[:bold]}m"
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def self.italic
|
|
89
|
+
"\e[#{ANSI_CODES[:italic]}m"
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def self.underline(style=nil)
|
|
93
|
+
case style
|
|
94
|
+
when nil
|
|
95
|
+
return "\e[#{ANSI_CODES[:underline]}m"
|
|
96
|
+
when :double
|
|
97
|
+
return "\e[#{ANSI_CODES[:double_underline]}m"
|
|
98
|
+
when :curly
|
|
99
|
+
return "\e[#{ANSI_CODES[:curly_underline]}m"
|
|
100
|
+
when :dotted
|
|
101
|
+
return "\e[#{ANSI_CODES[:dotted_underline]}m"
|
|
102
|
+
when :dashed
|
|
103
|
+
return "\e[#{ANSI_CODES[:dashed_underline]}m"
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def self.blink
|
|
108
|
+
"\e[#{ANSI_CODES[:blink]}m"
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def self.rapid_blink
|
|
112
|
+
"\e[#{ANSI_CODES[:rapid_blink]}m"
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def self.inverse
|
|
116
|
+
"\e[#{ANSI_CODES[:inverse]}m"
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def self.fraktur
|
|
120
|
+
"\e[#{ANSI_CODES[:fraktur]}m"
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def self.invisible
|
|
124
|
+
"\e[#{ANSI_CODES[:invisible]}m"
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def self.strikethrough
|
|
128
|
+
"\e[#{ANSI_CODES[:strikethrough]}m"
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def self.overline
|
|
132
|
+
"\e[#{ANSI_CODES[:overline]}m"
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def self.no_blink
|
|
136
|
+
"\e[#{ANSI_CODES[:no_blink]}m"
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def self.no_inverse
|
|
140
|
+
"\e[#{ANSI_CODES[:no_inverse]}m"
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def self.font(font_color,
|
|
144
|
+
font_bright: false,
|
|
145
|
+
background: nil,
|
|
146
|
+
background_bright: false,
|
|
147
|
+
bold: false,
|
|
148
|
+
italic: false,
|
|
149
|
+
underline: false,
|
|
150
|
+
underline_style: nil,
|
|
151
|
+
strikethrough: false,
|
|
152
|
+
overline: false
|
|
153
|
+
)
|
|
154
|
+
code = if font_bright
|
|
155
|
+
"\e[#{ANSI_CODES[:bright_color][font_color]}"
|
|
156
|
+
else
|
|
157
|
+
"\e[#{ANSI_CODES[:color][font_color]}"
|
|
158
|
+
end
|
|
159
|
+
if background
|
|
160
|
+
code += ";" + if background_bright
|
|
161
|
+
"#{ANSI_CODES[:bright_background][background]}"
|
|
162
|
+
else
|
|
163
|
+
"#{ANSI_CODES[:background][background]}"
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
if bold
|
|
167
|
+
code += ";" + ANSI_CODES[:bold]
|
|
168
|
+
end
|
|
169
|
+
if italic
|
|
170
|
+
code += ";" + ANSI_CODES[:italic]
|
|
171
|
+
end
|
|
172
|
+
if underline
|
|
173
|
+
case underline_style
|
|
174
|
+
when nil
|
|
175
|
+
code += ";" + ANSI_CODES[:underline]
|
|
176
|
+
when :double
|
|
177
|
+
code += ";" + ANSI_CODES[:double_underline]
|
|
178
|
+
when :curly
|
|
179
|
+
code += ";" + ANSI_CODES[:curly_underline]
|
|
180
|
+
when :dotted
|
|
181
|
+
code += ";" + ANSI_CODES[:dotted_underline]
|
|
182
|
+
when :dashed
|
|
183
|
+
code += ";" + ANSI_CODES[:dashed_underline]
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
if strikethrough
|
|
187
|
+
code += ";" + ANSI_CODES[:strikethrough]
|
|
188
|
+
end
|
|
189
|
+
if overline
|
|
190
|
+
code += ";" + ANSI_CODES[:overline]
|
|
191
|
+
end
|
|
192
|
+
return code+"m"
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
end
|
data/lib/ruby_rich/console.rb
CHANGED
|
@@ -18,7 +18,7 @@ module RubyRich
|
|
|
18
18
|
# 其他
|
|
19
19
|
'[5~' => :page_up, '[6~' => :page_down,
|
|
20
20
|
'[H' => :home, '[F' => :end,
|
|
21
|
-
'[2~' => :insert, '[3~' => :delete
|
|
21
|
+
'[2~' => :insert, '[3~' => :delete
|
|
22
22
|
}.freeze
|
|
23
23
|
|
|
24
24
|
def initialize
|
|
@@ -76,16 +76,15 @@ module RubyRich
|
|
|
76
76
|
def get_key(input: $stdin)
|
|
77
77
|
input.raw(intr: true) do |io|
|
|
78
78
|
char = io.getch
|
|
79
|
-
|
|
80
79
|
# 优先处理回车键(ASCII 13 = \r,ASCII 10 = \n)
|
|
81
80
|
if char == "\r" || char == "\n"
|
|
82
|
-
return :enter
|
|
81
|
+
return {:name=>:enter}
|
|
83
82
|
end
|
|
84
83
|
# 单独处理 Tab 键(ASCII 9)
|
|
85
84
|
if char == "\t"
|
|
86
|
-
return :tab
|
|
85
|
+
return {:name=>:tab}
|
|
87
86
|
elsif char.ord == 0x07F
|
|
88
|
-
return :backspace
|
|
87
|
+
return {:name=>:backspace}
|
|
89
88
|
elsif char == "\e" # 检测到转义序列
|
|
90
89
|
sequence = ''
|
|
91
90
|
begin
|
|
@@ -96,13 +95,17 @@ module RubyRich
|
|
|
96
95
|
retry if IO.select([io], nil, nil, 0.01)
|
|
97
96
|
rescue EOFError
|
|
98
97
|
end
|
|
99
|
-
|
|
98
|
+
if sequence.empty?
|
|
99
|
+
return {:name => :escape}
|
|
100
|
+
else
|
|
101
|
+
return {:name => ESCAPE_SEQUENCES[sequence]} || {:name => :escape}
|
|
102
|
+
end
|
|
100
103
|
# 处理 Ctrl 组合键(排除 Tab 和回车)
|
|
101
104
|
elsif char.ord.between?(1, 8) || char.ord.between?(10, 26)
|
|
102
105
|
ctrl_char = (char.ord + 64).chr.downcase
|
|
103
|
-
return :"ctrl_#{ctrl_char}"
|
|
106
|
+
return {:name =>"ctrl_#{ctrl_char}".to_sym}
|
|
104
107
|
else
|
|
105
|
-
char
|
|
108
|
+
{:name => :string, :value => char}
|
|
106
109
|
end
|
|
107
110
|
end
|
|
108
111
|
end
|
data/lib/ruby_rich/dialog.rb
CHANGED
|
@@ -1,136 +1,66 @@
|
|
|
1
1
|
module RubyRich
|
|
2
2
|
class Dialog
|
|
3
|
-
attr_accessor :title, :content, :buttons
|
|
3
|
+
attr_accessor :title, :content, :buttons, :live
|
|
4
|
+
attr_accessor :width, :height
|
|
5
|
+
|
|
6
|
+
def initialize(title: "", content: "", width: 48, height: 8, buttons: [:ok])
|
|
7
|
+
@width = width
|
|
8
|
+
@height = height
|
|
9
|
+
terminal_width = `tput cols`.to_i
|
|
10
|
+
terminal_height = `tput lines`.to_i
|
|
11
|
+
@event_listeners = {}
|
|
12
|
+
@layout = RubyRich::Layout.new(name: :title, width: width, height: height)
|
|
13
|
+
@panel = RubyRich::Panel.new("", title: title, border_style: :white)
|
|
14
|
+
@layout.update_content(@panel)
|
|
15
|
+
@layout.calculate_dimensions(terminal_width, terminal_height)
|
|
16
|
+
@button_str = build_button(buttons)
|
|
17
|
+
@panel.content = " \n \n#{content}"+AnsiCode.reset+"\n \n \n" + " "*((@panel.inner_width - @button_str.display_width)/2) + @button_str
|
|
18
|
+
end
|
|
4
19
|
|
|
5
|
-
def
|
|
6
|
-
@
|
|
7
|
-
@content = content
|
|
8
|
-
@buttons = buttons
|
|
9
|
-
@selected_index = 0
|
|
10
|
-
@result = nil
|
|
20
|
+
def content=(content)
|
|
21
|
+
@panel.content = " \n \n#{content}"+AnsiCode.reset+"\n \n \n" + " "*((@panel.inner_width - @button_str.display_width)/2) + @button_str
|
|
11
22
|
end
|
|
12
23
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
24
|
+
def build_button(buttons)
|
|
25
|
+
str = ""
|
|
26
|
+
buttons.each do |btn|
|
|
27
|
+
case btn
|
|
28
|
+
when :ok
|
|
29
|
+
str += " "+AnsiCode.color(:blue) + "OK(enter)"+ AnsiCode.reset
|
|
30
|
+
when :cancel
|
|
31
|
+
str += " "+AnsiCode.color(:red) + "Cancel(esc)"+ AnsiCode.reset
|
|
21
32
|
end
|
|
22
33
|
end
|
|
23
|
-
|
|
34
|
+
str.strip
|
|
24
35
|
end
|
|
25
36
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
# 渲染对话框
|
|
29
|
-
def render
|
|
30
|
-
build_layout unless @layout
|
|
31
|
-
update_panels
|
|
32
|
-
@layout.draw
|
|
37
|
+
def render_to_buffer
|
|
38
|
+
@layout.render_to_buffer
|
|
33
39
|
end
|
|
34
40
|
|
|
35
|
-
def
|
|
36
|
-
@
|
|
37
|
-
|
|
38
|
-
RubyRich::Layout.new(size: 1, name: :title_area), # 标题区域
|
|
39
|
-
RubyRich::Layout.new(name: :content_area), # 内容区域
|
|
40
|
-
RubyRich::Layout.new(size: 3, name: :button_area) # 按钮区域
|
|
41
|
-
)
|
|
42
|
-
|
|
43
|
-
@title_panel = RubyRich::Panel.new("", title: @title, border_style: :white)
|
|
44
|
-
@content_panel = RubyRich::Panel.new("", border_style: :default)
|
|
45
|
-
@button_panel = RubyRich::Panel.new("", border_style: :blue)
|
|
46
|
-
|
|
47
|
-
@layout[:title_area].update_content(@title_panel)
|
|
48
|
-
@layout[:content_area].update_content(@content_panel)
|
|
49
|
-
@layout[:button_area].update_content(@button_panel)
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
def update_panels
|
|
53
|
-
# 更新内容面板
|
|
54
|
-
@content_panel.content = @content.lines.map { |line|
|
|
55
|
-
line.chomp.gsub(/\n/, ' ').scan(/.{1,#{@content_panel.inner_width}}/)
|
|
56
|
-
}.flatten.join("\n")
|
|
57
|
-
|
|
58
|
-
# 更新按钮面板
|
|
59
|
-
button_row = @buttons.each_with_index.map { |btn, i|
|
|
60
|
-
i == @selected_index ? "[#{btn}]" : " #{btn} "
|
|
61
|
-
}.join(" ")
|
|
62
|
-
|
|
63
|
-
@button_panel.content = button_row.center(@button_panel.inner_width)
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
# 渲染按钮区域
|
|
67
|
-
def render_buttons(right_x, bottom_y)
|
|
68
|
-
# 已通过布局系统处理按钮渲染
|
|
69
|
-
end
|
|
70
|
-
|
|
71
|
-
# 处理键盘输入
|
|
72
|
-
def handle_input(key)
|
|
73
|
-
case key
|
|
74
|
-
when :left then @selected_index = (@selected_index - 1) % @buttons.size
|
|
75
|
-
when :right then @selected_index = (@selected_index + 1) % @buttons.size
|
|
76
|
-
when :enter then @result = @buttons[@selected_index]
|
|
77
|
-
when :q, "\u0003" then @result = :cancel # Ctrl+C
|
|
41
|
+
def key(event_name, priority = 0, &block)
|
|
42
|
+
unless @event_listeners[event_name]
|
|
43
|
+
@event_listeners[event_name] = []
|
|
78
44
|
end
|
|
45
|
+
@event_listeners[event_name] << { priority: priority, block: block }
|
|
46
|
+
@event_listeners[event_name].sort_by! { |l| -l[:priority] } # Higher priority first
|
|
79
47
|
end
|
|
80
|
-
end
|
|
81
|
-
|
|
82
|
-
# 确认对话框
|
|
83
|
-
class ConfirmDialog < Dialog
|
|
84
|
-
def initialize(title: "确认", content: "确定要执行此操作吗?")
|
|
85
|
-
super(
|
|
86
|
-
title: title,
|
|
87
|
-
content: content,
|
|
88
|
-
buttons: ["取消", "确定"]
|
|
89
|
-
)
|
|
90
|
-
end
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
# 输入对话框
|
|
94
|
-
class InputDialog < Dialog
|
|
95
|
-
def initialize(title: "输入", prompt: "请输入:")
|
|
96
|
-
super(title: title, buttons: ["确定"])
|
|
97
|
-
@input = ""
|
|
98
|
-
@prompt = prompt
|
|
99
|
-
end
|
|
100
|
-
|
|
101
|
-
private
|
|
102
|
-
|
|
103
|
-
def build_layout
|
|
104
|
-
super
|
|
105
|
-
# 在内容区域下方添加输入行
|
|
106
|
-
@layout[:content_area].split_column(
|
|
107
|
-
RubyRich::Layout.new(ratio: 1, name: :main_content),
|
|
108
|
-
RubyRich::Layout.new(size: 3, name: :input_line)
|
|
109
|
-
)
|
|
110
48
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
def update_panels
|
|
116
|
-
super
|
|
117
|
-
@input_panel.content = "#{@prompt}#{@input}"
|
|
118
|
-
end
|
|
119
|
-
|
|
120
|
-
def handle_input(key)
|
|
121
|
-
case key
|
|
122
|
-
when String
|
|
123
|
-
@input << key
|
|
124
|
-
when :backspace
|
|
125
|
-
@input.chop!
|
|
126
|
-
else
|
|
127
|
-
super
|
|
49
|
+
def on(event_name, &block)
|
|
50
|
+
if event_name==:close
|
|
51
|
+
@event_listeners[event_name] = [{block: block}]
|
|
128
52
|
end
|
|
129
53
|
end
|
|
130
54
|
|
|
131
|
-
def
|
|
132
|
-
|
|
133
|
-
|
|
55
|
+
def notify_listeners(event_data)
|
|
56
|
+
event_name = event_data[:name]
|
|
57
|
+
result = nil
|
|
58
|
+
if @event_listeners[event_name]
|
|
59
|
+
@event_listeners[event_name].each do |listener|
|
|
60
|
+
result = listener[:block].call(event_data, @live)
|
|
61
|
+
end
|
|
62
|
+
return result
|
|
63
|
+
end
|
|
134
64
|
end
|
|
135
65
|
end
|
|
136
66
|
end
|
data/lib/ruby_rich/layout.rb
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
module RubyRich
|
|
2
2
|
class Layout
|
|
3
|
-
attr_accessor :name, :ratio, :size, :children, :content, :parent, :
|
|
3
|
+
attr_accessor :name, :ratio, :size, :children, :content, :parent, :live, :dialog
|
|
4
|
+
attr_accessor :x_offset, :y_offset, :width, :height, :show
|
|
4
5
|
attr_reader :split_direction
|
|
5
6
|
|
|
6
|
-
def initialize(name: nil, ratio: 1, size: nil)
|
|
7
|
+
def initialize(name: nil, ratio: 1, size: nil, width: nil, height: nil)
|
|
7
8
|
@name = name
|
|
8
9
|
@ratio = ratio
|
|
9
10
|
@size = size
|
|
@@ -12,9 +13,49 @@ module RubyRich
|
|
|
12
13
|
@parent = nil
|
|
13
14
|
@x_offset = 0
|
|
14
15
|
@y_offset = 0
|
|
15
|
-
@width =
|
|
16
|
-
@height =
|
|
16
|
+
@width = width if width
|
|
17
|
+
@height = height if height
|
|
17
18
|
@split_direction = nil
|
|
19
|
+
@show = true
|
|
20
|
+
@event_listeners = {}
|
|
21
|
+
@event_intercepted = false
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def key(event_name, priority = 0, &block)
|
|
25
|
+
unless @event_listeners[event_name]
|
|
26
|
+
@event_listeners[event_name] = []
|
|
27
|
+
end
|
|
28
|
+
@event_listeners[event_name] << { priority: priority, block: block }
|
|
29
|
+
@event_listeners[event_name].sort_by! { |l| -l[:priority] } # Higher priority first
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def show=(flag)
|
|
33
|
+
@show = flag
|
|
34
|
+
@event_intercepted = !flag
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def notify_listeners(event_data)
|
|
38
|
+
return if @event_intercepted
|
|
39
|
+
if @dialog
|
|
40
|
+
@dialog.notify_listeners(event_data)
|
|
41
|
+
else
|
|
42
|
+
event_name = event_data[:name]
|
|
43
|
+
if @event_listeners[event_name]
|
|
44
|
+
@event_listeners[event_name].each do |listener|
|
|
45
|
+
next if @event_intercepted
|
|
46
|
+
result = listener[:block].call(event_data, self.root.live)
|
|
47
|
+
if result == true
|
|
48
|
+
@event_intercepted = true
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
unless @event_intercepted
|
|
54
|
+
@children.each do |child|
|
|
55
|
+
child.notify_listeners(event_data)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
18
59
|
end
|
|
19
60
|
|
|
20
61
|
def root
|
|
@@ -33,6 +74,10 @@ module RubyRich
|
|
|
33
74
|
@children.concat(layouts)
|
|
34
75
|
end
|
|
35
76
|
|
|
77
|
+
def add_child(layout)
|
|
78
|
+
@children << layout
|
|
79
|
+
end
|
|
80
|
+
|
|
36
81
|
def update_content(content)
|
|
37
82
|
@content = content
|
|
38
83
|
end
|
|
@@ -56,18 +101,27 @@ module RubyRich
|
|
|
56
101
|
nil
|
|
57
102
|
end
|
|
58
103
|
|
|
104
|
+
def show_dialog(dialog)
|
|
105
|
+
@dialog = dialog
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def hide_dialog
|
|
109
|
+
@dialog.notify_listeners({:name=>:close})
|
|
110
|
+
@dialog = nil
|
|
111
|
+
end
|
|
112
|
+
|
|
59
113
|
def render
|
|
60
114
|
# 将缓冲区转换为字符串(每行用换行符连接)
|
|
61
|
-
|
|
115
|
+
buffer = render_to_buffer
|
|
116
|
+
buffer.map { |line| line.compact.join("") }.join("\n")
|
|
62
117
|
end
|
|
63
118
|
|
|
64
119
|
def render_to_buffer
|
|
65
120
|
# 初始化缓冲区(二维数组,每个元素代表一个字符)
|
|
66
121
|
buffer = Array.new(@height) { Array.new(@width, " ") }
|
|
67
|
-
|
|
68
122
|
# 递归填充内容到缓冲区
|
|
69
123
|
render_into(buffer)
|
|
70
|
-
|
|
124
|
+
render_dialog_into(buffer) if @dialog
|
|
71
125
|
return buffer
|
|
72
126
|
end
|
|
73
127
|
|
|
@@ -75,6 +129,21 @@ module RubyRich
|
|
|
75
129
|
puts render
|
|
76
130
|
end
|
|
77
131
|
|
|
132
|
+
def render_dialog_into(buffer)
|
|
133
|
+
start_x = (@width - 2 - @dialog.width) / 2 + 1
|
|
134
|
+
start_y = (@height - 2 - @dialog.height) / 2 + 1
|
|
135
|
+
dialog_buffer = @dialog.render_to_buffer
|
|
136
|
+
buffer.each_with_index do |arr, y|
|
|
137
|
+
arr.each_with_index do |char, x|
|
|
138
|
+
if x >= start_x && y >= start_y
|
|
139
|
+
if y-start_y <= dialog_buffer.size-1 && x-start_x <= dialog_buffer[y-start_y].size-1
|
|
140
|
+
buffer[y][x] = dialog_buffer[y-start_y][x-start_x]
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
78
147
|
def render_into(buffer)
|
|
79
148
|
children.each { |child| child.render_into(buffer) } if children
|
|
80
149
|
return unless content
|
data/lib/ruby_rich/live.rb
CHANGED
|
@@ -8,13 +8,13 @@ module RubyRich
|
|
|
8
8
|
def initialize
|
|
9
9
|
@cache = nil
|
|
10
10
|
end
|
|
11
|
-
|
|
11
|
+
|
|
12
12
|
def print_with_pos(x,y,char)
|
|
13
13
|
print "\e[?25l" # 隐藏光标
|
|
14
14
|
print "\e[#{y};#{x}H" # 移动光标到左上角
|
|
15
15
|
print char
|
|
16
16
|
end
|
|
17
|
-
|
|
17
|
+
|
|
18
18
|
def draw(buffer)
|
|
19
19
|
unless @cache
|
|
20
20
|
system("clear")
|
|
@@ -34,10 +34,12 @@ module RubyRich
|
|
|
34
34
|
end
|
|
35
35
|
|
|
36
36
|
class Live
|
|
37
|
+
attr_accessor :params, :app, :listening, :layout
|
|
37
38
|
class << self
|
|
38
|
-
def start(layout, refresh_rate: 30,
|
|
39
|
+
def start(layout, refresh_rate: 30, &proc)
|
|
39
40
|
setup_terminal
|
|
40
41
|
live = new(layout, refresh_rate)
|
|
42
|
+
proc.call(live) if proc
|
|
41
43
|
live.run(proc)
|
|
42
44
|
rescue => e
|
|
43
45
|
puts e.message
|
|
@@ -60,29 +62,44 @@ module RubyRich
|
|
|
60
62
|
|
|
61
63
|
def initialize(layout, refresh_rate)
|
|
62
64
|
@layout = layout
|
|
65
|
+
@layout.live = self
|
|
63
66
|
@refresh_rate = refresh_rate
|
|
64
67
|
@running = true
|
|
65
68
|
@last_frame = Time.now
|
|
66
69
|
@cursor = TTY::Cursor
|
|
67
70
|
@render = CacheRender.new
|
|
71
|
+
@console = RubyRich::Console.new
|
|
72
|
+
@params = {}
|
|
68
73
|
end
|
|
69
74
|
|
|
70
75
|
def run(proc = nil)
|
|
71
76
|
while @running
|
|
72
77
|
render_frame
|
|
73
|
-
|
|
78
|
+
if @listening
|
|
79
|
+
event_data = @console.get_key()
|
|
80
|
+
@layout.notify_listeners(event_data)
|
|
81
|
+
end
|
|
74
82
|
sleep 1.0 / @refresh_rate
|
|
75
83
|
end
|
|
76
84
|
end
|
|
77
85
|
|
|
78
86
|
def stop
|
|
79
87
|
@running = false
|
|
88
|
+
system("clear")
|
|
80
89
|
end
|
|
81
90
|
|
|
82
91
|
def move_cursor(x,y)
|
|
83
92
|
print @cursor.move_to(x, y)
|
|
84
93
|
end
|
|
85
94
|
|
|
95
|
+
def find_layout(name)
|
|
96
|
+
@layout[name]
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def find_panel(name)
|
|
100
|
+
@layout[name].content
|
|
101
|
+
end
|
|
102
|
+
|
|
86
103
|
private
|
|
87
104
|
|
|
88
105
|
def render_frame
|
data/lib/ruby_rich/panel.rb
CHANGED
|
@@ -1,47 +1,72 @@
|
|
|
1
1
|
module RubyRich
|
|
2
2
|
class Panel
|
|
3
3
|
attr_accessor :width, :height, :content, :line_pos, :border_style, :title
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
blue: "\e[34m",
|
|
8
|
-
yellow: "\e[33m",
|
|
9
|
-
cyan: "\e[36m",
|
|
10
|
-
white: "\e[37m",
|
|
11
|
-
reset: "\e[0m"
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
def initialize(content = "", title: nil, border_style: :white)
|
|
4
|
+
attr_accessor :title_align, :content_changed
|
|
5
|
+
|
|
6
|
+
def initialize(content = "", title: nil, border_style: :white, title_align: :center)
|
|
15
7
|
@content = content
|
|
16
8
|
@title = title
|
|
17
9
|
@border_style = border_style
|
|
18
10
|
@width = 0
|
|
19
11
|
@height = 0
|
|
20
12
|
@line_pos = 0
|
|
13
|
+
@title_align = title_align
|
|
21
14
|
end
|
|
22
15
|
|
|
23
16
|
def inner_width
|
|
24
17
|
@width - 2 # Account for border characters
|
|
25
18
|
end
|
|
26
19
|
|
|
27
|
-
def
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
20
|
+
def page_up
|
|
21
|
+
@line_pos -= ( @height - 4 )
|
|
22
|
+
if @line_pos < 0
|
|
23
|
+
@line_pos = 0
|
|
24
|
+
end
|
|
25
|
+
@content_changed = false
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def page_down
|
|
29
|
+
unless @content.empty?
|
|
30
|
+
content_lines = wrap_content(@content)
|
|
31
|
+
@line_pos += ( @height - 4 )
|
|
32
|
+
if @line_pos + ( @height - 4 ) > content_lines.size
|
|
33
|
+
@line_pos = content_lines.size - @height + 2
|
|
34
|
+
end
|
|
35
|
+
@content_changed = false
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def home
|
|
40
|
+
@line_pos = 0
|
|
41
|
+
@content_changed = false
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def end
|
|
45
|
+
unless @content.empty?
|
|
46
|
+
content_lines = wrap_content(@content)
|
|
47
|
+
@line_pos = content_lines.size - @height + 2
|
|
48
|
+
@content_changed = false
|
|
31
49
|
end
|
|
32
|
-
width
|
|
33
50
|
end
|
|
34
51
|
|
|
35
52
|
def render
|
|
36
53
|
lines = []
|
|
37
|
-
color_code =
|
|
38
|
-
reset_code =
|
|
54
|
+
color_code = AnsiCode.color(@border_style) || AnsiCode.color(:white)
|
|
55
|
+
reset_code = AnsiCode.reset
|
|
39
56
|
|
|
40
57
|
# Top border
|
|
41
58
|
top_border = color_code + "┌"
|
|
42
59
|
if @title
|
|
43
|
-
title_text = "[ #{@title} ]"
|
|
44
|
-
|
|
60
|
+
title_text = "[ #{@title} ]"
|
|
61
|
+
bar_width = @width - @title.display_width-6
|
|
62
|
+
case @title_align
|
|
63
|
+
when :left
|
|
64
|
+
top_border += title_text + '─' * bar_width
|
|
65
|
+
when :center
|
|
66
|
+
top_border += '─' * (bar_width/2) + title_text + '─' * (bar_width - bar_width/2)
|
|
67
|
+
when :right
|
|
68
|
+
top_border += '─' * bar_width + title_text
|
|
69
|
+
end
|
|
45
70
|
else
|
|
46
71
|
top_border += '─' * (@width - 2)
|
|
47
72
|
end
|
|
@@ -50,13 +75,29 @@ module RubyRich
|
|
|
50
75
|
|
|
51
76
|
# Content area
|
|
52
77
|
content_lines = wrap_content(@content)
|
|
53
|
-
if
|
|
54
|
-
|
|
55
|
-
|
|
78
|
+
if @line_pos==0
|
|
79
|
+
if @content_changed == false
|
|
80
|
+
if content_lines.size > @height - 2
|
|
81
|
+
content_lines=content_lines[0..@height - 3]
|
|
82
|
+
end
|
|
83
|
+
else
|
|
84
|
+
if content_lines.size > @height - 2
|
|
85
|
+
@line_pos = content_lines.size - @height + 2
|
|
86
|
+
content_lines=content_lines[@line_pos..-1]
|
|
87
|
+
@content_changed = false
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
else
|
|
91
|
+
if @line_pos+@height-2 >= content_lines.size
|
|
92
|
+
content_lines=content_lines[@line_pos..-1]
|
|
93
|
+
else
|
|
94
|
+
content_lines=content_lines[@line_pos..@line_pos+@height-3]
|
|
95
|
+
end
|
|
56
96
|
end
|
|
97
|
+
|
|
57
98
|
content_lines.each do |line|
|
|
58
99
|
lines << color_code + "│" + reset_code +
|
|
59
|
-
line + " "*(@width -
|
|
100
|
+
line + " "*(@width - line.display_width - 2) +
|
|
60
101
|
color_code + "│" + reset_code
|
|
61
102
|
end
|
|
62
103
|
|
|
@@ -74,8 +115,13 @@ module RubyRich
|
|
|
74
115
|
lines
|
|
75
116
|
end
|
|
76
117
|
|
|
77
|
-
def
|
|
118
|
+
def content=(new_content)
|
|
78
119
|
@content = new_content
|
|
120
|
+
content_lines = wrap_content(@content)
|
|
121
|
+
if content_lines.size > @height - 2
|
|
122
|
+
@line_pos = content_lines.size - @height + 2
|
|
123
|
+
end
|
|
124
|
+
@content_changed = true
|
|
79
125
|
end
|
|
80
126
|
|
|
81
127
|
private
|
|
@@ -84,22 +130,32 @@ module RubyRich
|
|
|
84
130
|
result = []
|
|
85
131
|
current_line = ""
|
|
86
132
|
current_width = 0
|
|
87
|
-
|
|
88
|
-
text
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
133
|
+
|
|
134
|
+
# Split text into tokens of ANSI codes and regular text
|
|
135
|
+
tokens = text.scan(/(\e\[[0-9;]*m)|(.)/)
|
|
136
|
+
.map { |m| m.compact.first }
|
|
137
|
+
|
|
138
|
+
tokens.each do |token|
|
|
139
|
+
# Calculate width for regular text, ANSI codes have 0 width
|
|
140
|
+
if token.start_with?("\e[")
|
|
141
|
+
token_width = 0
|
|
142
|
+
else
|
|
143
|
+
token_width = token.chars.sum { |c| Unicode::DisplayWidth.of(c) }
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
if current_width + token_width <= @width - 4
|
|
147
|
+
current_line += token
|
|
148
|
+
current_width += token_width
|
|
93
149
|
else
|
|
94
150
|
result << current_line
|
|
95
|
-
current_line =
|
|
96
|
-
current_width =
|
|
151
|
+
current_line = token
|
|
152
|
+
current_width = token_width
|
|
97
153
|
end
|
|
98
154
|
end
|
|
99
|
-
|
|
100
|
-
#
|
|
155
|
+
|
|
156
|
+
# Add remaining line
|
|
101
157
|
result << current_line unless current_line.empty?
|
|
102
|
-
|
|
158
|
+
|
|
103
159
|
result
|
|
104
160
|
end
|
|
105
161
|
|
|
@@ -110,97 +166,18 @@ module RubyRich
|
|
|
110
166
|
end
|
|
111
167
|
end
|
|
112
168
|
end
|
|
113
|
-
module RubyRich
|
|
114
|
-
class RichPanel
|
|
115
|
-
# ANSI escape codes for styling
|
|
116
|
-
ANSI_CODES = {
|
|
117
|
-
reset: "\e[0m",
|
|
118
|
-
bold: "\e[1m",
|
|
119
|
-
underline: "\e[4m",
|
|
120
|
-
color: {
|
|
121
|
-
black: "\e[30m",
|
|
122
|
-
red: "\e[31m",
|
|
123
|
-
green: "\e[32m",
|
|
124
|
-
yellow: "\e[33m",
|
|
125
|
-
blue: "\e[34m",
|
|
126
|
-
magenta: "\e[35m",
|
|
127
|
-
cyan: "\e[36m",
|
|
128
|
-
white: "\e[37m"
|
|
129
|
-
},
|
|
130
|
-
background: {
|
|
131
|
-
black: "\e[40m",
|
|
132
|
-
red: "\e[41m",
|
|
133
|
-
green: "\e[42m",
|
|
134
|
-
yellow: "\e[43m",
|
|
135
|
-
blue: "\e[44m",
|
|
136
|
-
magenta: "\e[45m",
|
|
137
|
-
cyan: "\e[46m",
|
|
138
|
-
white: "\e[47m"
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
attr_accessor :title, :content, :border_color, :title_color, :footer
|
|
143
|
-
|
|
144
|
-
def initialize(content, title: nil, footer: nil, border_color: :white, title_color: :white)
|
|
145
|
-
@content = content.is_a?(String) ? content.split("\n") : content
|
|
146
|
-
@title = title
|
|
147
|
-
@footer = footer
|
|
148
|
-
@border_color = border_color
|
|
149
|
-
@title_color = title_color
|
|
150
|
-
end
|
|
151
|
-
|
|
152
|
-
def render
|
|
153
|
-
content_lines = format_content
|
|
154
|
-
panel_width = calculate_panel_width(content_lines)
|
|
155
|
-
|
|
156
|
-
lines = []
|
|
157
|
-
lines << top_border(panel_width)
|
|
158
|
-
lines += content_lines.map { |line| format_line(line, panel_width) }
|
|
159
|
-
lines << bottom_border(panel_width)
|
|
160
|
-
|
|
161
|
-
lines.join("\n")
|
|
162
|
-
end
|
|
163
|
-
|
|
164
|
-
private
|
|
165
|
-
|
|
166
|
-
def top_border(width)
|
|
167
|
-
title_text = @title ? colorize(" #{@title} ", @title_color) : ""
|
|
168
|
-
padding = (width - title_text.uncolorize.length - 2) / 2
|
|
169
|
-
"#{colorize("╭", @border_color)}#{colorize("─" * padding, @border_color)}#{title_text}#{colorize("─" * (width - title_text.uncolorize.length - padding - 2), @border_color)}#{colorize("╮", @border_color)}"
|
|
170
|
-
end
|
|
171
|
-
|
|
172
|
-
def bottom_border(width)
|
|
173
|
-
footer_text = @footer ? colorize(" #{@footer} ", @title_color) : ""
|
|
174
|
-
padding = (width - footer_text.uncolorize.length - 2) / 2
|
|
175
|
-
"#{colorize("╰", @border_color)}#{colorize("─" * padding, @border_color)}#{footer_text}#{colorize("─" * (width - footer_text.uncolorize.length - padding - 2), @border_color)}#{colorize("╯", @border_color)}"
|
|
176
|
-
end
|
|
177
|
-
|
|
178
|
-
def format_line(line, width)
|
|
179
|
-
"#{colorize("│", @border_color)} #{line.ljust(width - 4)} #{colorize("│", @border_color)}"
|
|
180
|
-
end
|
|
181
|
-
|
|
182
|
-
def format_content
|
|
183
|
-
@content.map(&:strip)
|
|
184
|
-
end
|
|
185
|
-
|
|
186
|
-
def calculate_panel_width(content_lines)
|
|
187
|
-
[
|
|
188
|
-
@title ? @title.uncolorize.length + 4 : 0,
|
|
189
|
-
@footer ? @footer.uncolorize.length + 4 : 0,
|
|
190
|
-
content_lines.map(&:length).max + 4
|
|
191
|
-
].max
|
|
192
|
-
end
|
|
193
|
-
|
|
194
|
-
def colorize(text, color)
|
|
195
|
-
code = ANSI_CODES[:color][color] || ""
|
|
196
|
-
"#{code}#{text}#{ANSI_CODES[:reset]}"
|
|
197
|
-
end
|
|
198
|
-
end
|
|
199
|
-
end
|
|
200
169
|
|
|
201
170
|
# Extend String to remove ANSI codes for alignment
|
|
202
171
|
class String
|
|
203
172
|
def uncolorize
|
|
204
173
|
gsub(/\e\[[0-9;]*m/, '')
|
|
205
174
|
end
|
|
175
|
+
|
|
176
|
+
def display_width
|
|
177
|
+
width = 0
|
|
178
|
+
self.uncolorize.each_char do |char|
|
|
179
|
+
width += Unicode::DisplayWidth.of(char)
|
|
180
|
+
end
|
|
181
|
+
width
|
|
182
|
+
end
|
|
206
183
|
end
|
data/lib/ruby_rich/print.rb
CHANGED
data/lib/ruby_rich/table.rb
CHANGED
data/lib/ruby_rich/text.rb
CHANGED
|
@@ -8,35 +8,6 @@ module RubyRich
|
|
|
8
8
|
warning: { color: :yellow, bold: true }
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
# ANSI 转义码常量
|
|
12
|
-
ANSI_CODES = {
|
|
13
|
-
reset: "\e[0m",
|
|
14
|
-
bold: "\e[1m",
|
|
15
|
-
italic: "\e[3m",
|
|
16
|
-
underline: "\e[4m",
|
|
17
|
-
blink: "\e[5m",
|
|
18
|
-
color: {
|
|
19
|
-
black: "\e[30m",
|
|
20
|
-
red: "\e[31m",
|
|
21
|
-
green: "\e[32m",
|
|
22
|
-
yellow: "\e[33m",
|
|
23
|
-
blue: "\e[34m",
|
|
24
|
-
magenta: "\e[35m",
|
|
25
|
-
cyan: "\e[36m",
|
|
26
|
-
white: "\e[37m"
|
|
27
|
-
},
|
|
28
|
-
background: {
|
|
29
|
-
black: "\e[40m",
|
|
30
|
-
red: "\e[41m",
|
|
31
|
-
green: "\e[42m",
|
|
32
|
-
yellow: "\e[43m",
|
|
33
|
-
blue: "\e[44m",
|
|
34
|
-
magenta: "\e[45m",
|
|
35
|
-
cyan: "\e[46m",
|
|
36
|
-
white: "\e[47m"
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
11
|
def self.set_theme(new_theme)
|
|
41
12
|
@@theme.merge!(new_theme)
|
|
42
13
|
end
|
|
@@ -47,18 +18,23 @@ module RubyRich
|
|
|
47
18
|
apply_theme(style) if style
|
|
48
19
|
end
|
|
49
20
|
|
|
50
|
-
def style(color:
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
21
|
+
def style(color: :white,
|
|
22
|
+
font_bright: false,
|
|
23
|
+
background: nil,
|
|
24
|
+
background_bright: false,
|
|
25
|
+
bold: false,
|
|
26
|
+
italic: false,
|
|
27
|
+
underline: false,
|
|
28
|
+
underline_style: nil,
|
|
29
|
+
strikethrough: false,
|
|
30
|
+
overline: false
|
|
31
|
+
)
|
|
32
|
+
@styles << AnsiCode.font(color, font_bright, background, background_bright, bold, italic, underline, underline_style, strikethrough, overline)
|
|
57
33
|
self
|
|
58
34
|
end
|
|
59
35
|
|
|
60
36
|
def render
|
|
61
|
-
"#{@styles.join}#{@text}#{
|
|
37
|
+
"#{@styles.join}#{@text}#{AnsiCode.reset}"
|
|
62
38
|
end
|
|
63
39
|
|
|
64
40
|
private
|
data/lib/ruby_rich/version.rb
CHANGED
data/lib/ruby_rich.rb
CHANGED
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ruby_rich
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- zhuang biaowei
|
|
8
8
|
bindir: bin
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date: 2025-
|
|
10
|
+
date: 2025-03-10 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: rake
|
|
@@ -45,6 +45,7 @@ extensions: []
|
|
|
45
45
|
extra_rdoc_files: []
|
|
46
46
|
files:
|
|
47
47
|
- lib/ruby_rich.rb
|
|
48
|
+
- lib/ruby_rich/ansi_code.rb
|
|
48
49
|
- lib/ruby_rich/console.rb
|
|
49
50
|
- lib/ruby_rich/dialog.rb
|
|
50
51
|
- lib/ruby_rich/layout.rb
|
|
@@ -73,7 +74,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
73
74
|
- !ruby/object:Gem::Version
|
|
74
75
|
version: '0'
|
|
75
76
|
requirements: []
|
|
76
|
-
rubygems_version: 3.6.
|
|
77
|
+
rubygems_version: 3.6.4
|
|
77
78
|
specification_version: 4
|
|
78
79
|
summary: Rich text formatting and console output for Ruby
|
|
79
80
|
test_files: []
|