ruby_rich 0.3.1 → 0.4.1
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/agent_shell.rb +254 -0
- data/lib/ruby_rich/ansi_code.rb +46 -0
- data/lib/ruby_rich/app_shell.rb +374 -0
- data/lib/ruby_rich/attachment.rb +25 -0
- data/lib/ruby_rich/columns.rb +244 -0
- data/lib/ruby_rich/composer.rb +512 -0
- data/lib/ruby_rich/console.rb +191 -29
- data/lib/ruby_rich/dialog.rb +2 -1
- data/lib/ruby_rich/event.rb +29 -0
- data/lib/ruby_rich/focus_manager.rb +77 -0
- data/lib/ruby_rich/layout.rb +117 -29
- data/lib/ruby_rich/line_editor.rb +325 -0
- data/lib/ruby_rich/live.rb +100 -19
- data/lib/ruby_rich/markdown.rb +213 -0
- data/lib/ruby_rich/panel.rb +1 -1
- data/lib/ruby_rich/print.rb +6 -6
- data/lib/ruby_rich/progress_bar.rb +229 -11
- data/lib/ruby_rich/progress_manager.rb +150 -0
- data/lib/ruby_rich/sidebar.rb +85 -0
- data/lib/ruby_rich/slash_input.rb +197 -0
- data/lib/ruby_rich/status.rb +246 -0
- data/lib/ruby_rich/syntax.rb +171 -0
- data/lib/ruby_rich/table.rb +167 -21
- data/lib/ruby_rich/terminal.rb +510 -0
- data/lib/ruby_rich/text.rb +112 -2
- data/lib/ruby_rich/theme.rb +96 -0
- data/lib/ruby_rich/tool_block.rb +92 -0
- data/lib/ruby_rich/transcript.rb +553 -0
- data/lib/ruby_rich/tree.rb +200 -0
- data/lib/ruby_rich/version.rb +1 -1
- data/lib/ruby_rich/viewport.rb +468 -0
- data/lib/ruby_rich.rb +65 -10
- metadata +93 -3
data/lib/ruby_rich/table.rb
CHANGED
|
@@ -2,36 +2,182 @@ require 'unicode/display_width'
|
|
|
2
2
|
|
|
3
3
|
module RubyRich
|
|
4
4
|
class Table
|
|
5
|
-
attr_accessor :
|
|
5
|
+
attr_accessor :rows, :align, :row_height, :border_style
|
|
6
|
+
attr_reader :headers
|
|
7
|
+
|
|
8
|
+
# 边框样式定义
|
|
9
|
+
BORDER_STYLES = {
|
|
10
|
+
none: {
|
|
11
|
+
top: '', bottom: '', left: '', right: '',
|
|
12
|
+
horizontal: '-', vertical: '|',
|
|
13
|
+
top_left: '', top_right: '', bottom_left: '', bottom_right: '',
|
|
14
|
+
cross: '', top_cross: '', bottom_cross: '', left_cross: '', right_cross: ''
|
|
15
|
+
},
|
|
16
|
+
simple: {
|
|
17
|
+
top: '-', bottom: '-', left: '|', right: '|',
|
|
18
|
+
horizontal: '-', vertical: '|',
|
|
19
|
+
top_left: '+', top_right: '+', bottom_left: '+', bottom_right: '+',
|
|
20
|
+
cross: '+', top_cross: '+', bottom_cross: '+', left_cross: '+', right_cross: '+'
|
|
21
|
+
},
|
|
22
|
+
full: {
|
|
23
|
+
top: '─', bottom: '─', left: '│', right: '│',
|
|
24
|
+
horizontal: '─', vertical: '│',
|
|
25
|
+
top_left: '┌', top_right: '┐', bottom_left: '└', bottom_right: '┘',
|
|
26
|
+
cross: '┼', top_cross: '┬', bottom_cross: '┴', left_cross: '├', right_cross: '┤'
|
|
27
|
+
}
|
|
28
|
+
}.freeze
|
|
6
29
|
|
|
7
|
-
def initialize(headers: [], align: :left, row_height: 1)
|
|
30
|
+
def initialize(headers: [], align: :left, row_height: 1, border_style: :none)
|
|
8
31
|
@headers = headers.map { |h| format_cell(h) }
|
|
9
32
|
@rows = []
|
|
10
33
|
@align = align
|
|
11
34
|
@row_height = row_height
|
|
35
|
+
@border_style = border_style
|
|
12
36
|
end
|
|
13
37
|
|
|
14
38
|
def add_row(row)
|
|
15
39
|
@rows << row.map { |cell| format_cell(cell) }
|
|
16
40
|
end
|
|
41
|
+
|
|
42
|
+
def headers=(new_headers)
|
|
43
|
+
@headers = new_headers.map { |h| format_cell(h) }
|
|
44
|
+
end
|
|
17
45
|
|
|
18
46
|
def render
|
|
47
|
+
return render_empty_table if @headers.empty? && @rows.empty?
|
|
48
|
+
|
|
19
49
|
column_widths = calculate_column_widths
|
|
20
50
|
lines = []
|
|
21
|
-
|
|
51
|
+
border_chars = BORDER_STYLES[@border_style] || BORDER_STYLES[:none]
|
|
52
|
+
|
|
53
|
+
# Render top border
|
|
54
|
+
if @border_style != :none && !@headers.empty?
|
|
55
|
+
lines << render_horizontal_border(column_widths, :top)
|
|
56
|
+
end
|
|
57
|
+
|
|
22
58
|
# Render headers
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
59
|
+
unless @headers.empty?
|
|
60
|
+
lines.concat(render_styled_row(@headers, column_widths, bold: true))
|
|
61
|
+
# Header separator line
|
|
62
|
+
lines << render_horizontal_border(column_widths, :middle)
|
|
63
|
+
end
|
|
64
|
+
|
|
26
65
|
# Render rows
|
|
27
|
-
@rows.
|
|
28
|
-
lines.concat(
|
|
66
|
+
@rows.each_with_index do |row, index|
|
|
67
|
+
lines.concat(render_styled_multiline_row(row, column_widths))
|
|
29
68
|
end
|
|
30
|
-
|
|
69
|
+
|
|
70
|
+
# Render bottom border
|
|
71
|
+
if @border_style != :none && (!@headers.empty? || !@rows.empty?)
|
|
72
|
+
lines << render_horizontal_border(column_widths, :bottom)
|
|
73
|
+
end
|
|
74
|
+
|
|
31
75
|
lines.join("\n")
|
|
32
76
|
end
|
|
33
77
|
|
|
34
78
|
private
|
|
79
|
+
|
|
80
|
+
def render_empty_table
|
|
81
|
+
if @border_style == :none
|
|
82
|
+
return "| |\n-"
|
|
83
|
+
else
|
|
84
|
+
border_chars = BORDER_STYLES[@border_style]
|
|
85
|
+
return "#{border_chars[:top_left]}#{border_chars[:horizontal] * 2}#{border_chars[:top_right]}\n" +
|
|
86
|
+
"#{border_chars[:left]} #{border_chars[:right]}\n" +
|
|
87
|
+
"#{border_chars[:bottom_left]}#{border_chars[:horizontal] * 2}#{border_chars[:bottom_right]}"
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def render_horizontal_border(column_widths, position)
|
|
92
|
+
return "-" * (column_widths.sum { |w| w + 3 } + 1) if @border_style == :none
|
|
93
|
+
|
|
94
|
+
border_chars = BORDER_STYLES[@border_style]
|
|
95
|
+
|
|
96
|
+
# 计算每列的边框宽度(包括左右的空格)
|
|
97
|
+
segments = column_widths.map { |width| border_chars[:horizontal] * (width + 2) }
|
|
98
|
+
|
|
99
|
+
case position
|
|
100
|
+
when :top
|
|
101
|
+
left_char = border_chars[:top_left]
|
|
102
|
+
right_char = border_chars[:top_right]
|
|
103
|
+
join_char = border_chars[:top_cross]
|
|
104
|
+
when :middle
|
|
105
|
+
left_char = border_chars[:left_cross]
|
|
106
|
+
right_char = border_chars[:right_cross]
|
|
107
|
+
join_char = border_chars[:cross]
|
|
108
|
+
when :bottom
|
|
109
|
+
left_char = border_chars[:bottom_left]
|
|
110
|
+
right_char = border_chars[:bottom_right]
|
|
111
|
+
join_char = border_chars[:bottom_cross]
|
|
112
|
+
else
|
|
113
|
+
left_char = border_chars[:left_cross]
|
|
114
|
+
right_char = border_chars[:right_cross]
|
|
115
|
+
join_char = border_chars[:cross]
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
left_char + segments.join(join_char) + right_char
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def render_styled_row(row, column_widths, bold: false)
|
|
122
|
+
if @border_style == :none
|
|
123
|
+
return [render_row(row, column_widths, bold: bold)]
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
border_chars = BORDER_STYLES[@border_style]
|
|
127
|
+
|
|
128
|
+
row_content = row.map.with_index do |cell, i|
|
|
129
|
+
content = bold ? cell.render : align_cell(cell.render, column_widths[i])
|
|
130
|
+
aligned_content = align_cell(content, column_widths[i])
|
|
131
|
+
" #{aligned_content} "
|
|
132
|
+
end.join(border_chars[:vertical])
|
|
133
|
+
|
|
134
|
+
["#{border_chars[:left]}#{row_content}#{border_chars[:right]}"]
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def render_styled_multiline_row(row, column_widths)
|
|
138
|
+
if @border_style == :none
|
|
139
|
+
return render_multiline_row(row, column_widths)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
border_chars = BORDER_STYLES[@border_style]
|
|
143
|
+
|
|
144
|
+
# Prepare each cell's lines
|
|
145
|
+
row_lines = row.map.with_index do |cell, i|
|
|
146
|
+
# 获取单元格的样式序列
|
|
147
|
+
style_sequence = cell.render.match(/\e\[[0-9;]*m/)&.to_s || ""
|
|
148
|
+
reset_sequence = style_sequence.empty? ? "" : "\e[0m"
|
|
149
|
+
|
|
150
|
+
# 分割成多行并保持样式
|
|
151
|
+
cell_content = cell.render.split("\n")
|
|
152
|
+
|
|
153
|
+
# 为每一行添加样式
|
|
154
|
+
cell_content.map! { |line|
|
|
155
|
+
line = line.gsub(/\e\[[0-9;]*m/, '') # 移除可能存在的样式序列
|
|
156
|
+
style_sequence + line + reset_sequence
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
# 填充到指定的行高
|
|
160
|
+
padded_content = cell_content + [" "] * [@row_height - cell_content.size, 0].max
|
|
161
|
+
|
|
162
|
+
# 对每一行应用对齐,保持样式
|
|
163
|
+
padded_content.map { |line| align_cell(line, column_widths[i]) }
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# Normalize row height
|
|
167
|
+
max_height = row_lines.map(&:size).max
|
|
168
|
+
row_lines.each do |lines|
|
|
169
|
+
width = column_widths[row_lines.index(lines)]
|
|
170
|
+
style_sequence = lines.first.match(/\e\[[0-9;]*m/)&.to_s || ""
|
|
171
|
+
reset_sequence = style_sequence.empty? ? "" : "\e[0m"
|
|
172
|
+
lines.fill(style_sequence + " " * width + reset_sequence, lines.size...max_height)
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# Render each line of the row
|
|
176
|
+
(0...max_height).map do |line_index|
|
|
177
|
+
row_content = row_lines.map { |lines| " #{lines[line_index]} " }.join(border_chars[:vertical])
|
|
178
|
+
"#{border_chars[:left]}#{row_content}#{border_chars[:right]}"
|
|
179
|
+
end
|
|
180
|
+
end
|
|
35
181
|
|
|
36
182
|
def format_cell(cell)
|
|
37
183
|
cell.is_a?(RubyRich::RichText) ? cell : RubyRich::RichText.new(cell.to_s)
|
|
@@ -73,23 +219,23 @@ module RubyRich
|
|
|
73
219
|
def render_multiline_row(row, column_widths)
|
|
74
220
|
# Prepare each cell's lines
|
|
75
221
|
row_lines = row.map.with_index do |cell, i|
|
|
76
|
-
#
|
|
222
|
+
# Get cell style sequence
|
|
77
223
|
style_sequence = cell.render.match(/\e\[[0-9;]*m/)&.to_s || ""
|
|
78
224
|
reset_sequence = style_sequence.empty? ? "" : "\e[0m"
|
|
79
|
-
|
|
80
|
-
#
|
|
225
|
+
|
|
226
|
+
# Split into multiple lines while preserving styles
|
|
81
227
|
cell_content = cell.render.split("\n")
|
|
82
|
-
|
|
83
|
-
#
|
|
84
|
-
cell_content.map! { |line|
|
|
85
|
-
line = line.gsub(/\e\[[0-9;]*m/, '') #
|
|
86
|
-
style_sequence + line + reset_sequence
|
|
228
|
+
|
|
229
|
+
# Add style to each line
|
|
230
|
+
cell_content.map! { |line|
|
|
231
|
+
line = line.gsub(/\e\[[0-9;]*m/, '') # Remove existing style sequences
|
|
232
|
+
style_sequence + line + reset_sequence
|
|
87
233
|
}
|
|
88
|
-
|
|
89
|
-
#
|
|
234
|
+
|
|
235
|
+
# Pad to specified row height
|
|
90
236
|
padded_content = cell_content + [" "] * [@row_height - cell_content.size, 0].max
|
|
91
|
-
|
|
92
|
-
#
|
|
237
|
+
|
|
238
|
+
# Apply alignment to each line while preserving styles
|
|
93
239
|
padded_content.map { |line| align_cell(line, column_widths[i]) }
|
|
94
240
|
end
|
|
95
241
|
|