fantasy-cli 1.2.14 → 1.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/README.md +8 -1
- data/lib/gsd/agents/communication.rb +260 -0
- data/lib/gsd/agents/swarm.rb +341 -0
- data/lib/gsd/lsp/client.rb +394 -0
- data/lib/gsd/lsp/completion.rb +266 -0
- data/lib/gsd/lsp/diagnostics.rb +259 -0
- data/lib/gsd/lsp/hover.rb +244 -0
- data/lib/gsd/lsp/protocol.rb +434 -0
- data/lib/gsd/lsp/server_manager.rb +290 -0
- data/lib/gsd/lsp/symbols.rb +368 -0
- data/lib/gsd/plugins/api.rb +340 -0
- data/lib/gsd/plugins/hooks.rb +117 -95
- data/lib/gsd/plugins/hot_reload.rb +293 -0
- data/lib/gsd/plugins/registry.rb +273 -0
- data/lib/gsd/tui/agent_panel.rb +182 -0
- data/lib/gsd/tui/animations.rb +320 -0
- data/lib/gsd/tui/app.rb +442 -2
- data/lib/gsd/tui/colors.rb +15 -0
- data/lib/gsd/tui/effects.rb +263 -0
- data/lib/gsd/tui/header.rb +13 -5
- data/lib/gsd/tui/input_box.rb +10 -7
- data/lib/gsd/tui/mouse.rb +388 -0
- data/lib/gsd/tui/persistence.rb +192 -0
- data/lib/gsd/tui/session.rb +273 -0
- data/lib/gsd/tui/status_bar.rb +63 -15
- data/lib/gsd/tui/tab.rb +112 -0
- data/lib/gsd/tui/tab_manager.rb +191 -0
- data/lib/gsd/tui/transitions.rb +262 -0
- data/lib/gsd/version.rb +1 -1
- metadata +22 -1
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Gsd
|
|
4
|
+
module TUI
|
|
5
|
+
# Effects - Efeitos visuais avançados para TUI
|
|
6
|
+
#
|
|
7
|
+
# Suporta:
|
|
8
|
+
# - Gradientes de cor
|
|
9
|
+
# - Glow/Bloom effects
|
|
10
|
+
# - Sombras
|
|
11
|
+
# - Blur simulation
|
|
12
|
+
# - Rainbow effects
|
|
13
|
+
class Effects
|
|
14
|
+
# Gera gradiente horizontal de texto
|
|
15
|
+
def self.horizontal_gradient(text, colors)
|
|
16
|
+
return text if colors.empty?
|
|
17
|
+
|
|
18
|
+
chars = text.chars
|
|
19
|
+
result = ''
|
|
20
|
+
|
|
21
|
+
chars.each_with_index do |char, i|
|
|
22
|
+
color_idx = (i.to_f / chars.length * (colors.length - 1)).round
|
|
23
|
+
color = colors[color_idx]
|
|
24
|
+
result << "#{color}#{char}"
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
result + Colors::RESET
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Gera gradiente vertical
|
|
31
|
+
def self.vertical_gradient(lines, colors)
|
|
32
|
+
return lines if colors.empty? || lines.empty?
|
|
33
|
+
|
|
34
|
+
lines.map.with_index do |line, i|
|
|
35
|
+
color_idx = (i.to_f / lines.length * (colors.length - 1)).round
|
|
36
|
+
"#{colors[color_idx]}#{line}#{Colors::RESET}"
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Efeito de glow (simulado com cores mais brilhantes)
|
|
41
|
+
def self.glow(text, intensity: 1, color: nil)
|
|
42
|
+
t = Colors.theme
|
|
43
|
+
glow_color = color || t[:accent]
|
|
44
|
+
|
|
45
|
+
case intensity
|
|
46
|
+
when 1
|
|
47
|
+
"#{glow_color}#{text}#{Colors::RESET}"
|
|
48
|
+
when 2
|
|
49
|
+
"#{Colors::BOLD}#{glow_color}#{text}#{Colors::RESET}"
|
|
50
|
+
when 3
|
|
51
|
+
"#{Colors::BOLD}#{Colors::ESC}[1m#{glow_color}#{text}#{Colors::RESET}"
|
|
52
|
+
else
|
|
53
|
+
"#{glow_color}#{text}#{Colors::RESET}"
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Efeito de pulso (variando intensidade)
|
|
58
|
+
def self.pulse(text, phase: 0.0, color: nil)
|
|
59
|
+
t = Colors.theme
|
|
60
|
+
base_color = color || t[:accent]
|
|
61
|
+
|
|
62
|
+
# Varia entre normal e brilhante baseado na fase
|
|
63
|
+
intensity = Math.sin(phase * Math::PI * 2) * 0.5 + 0.5
|
|
64
|
+
|
|
65
|
+
if intensity > 0.7
|
|
66
|
+
"#{Colors::BOLD}#{base_color}#{text}#{Colors::RESET}"
|
|
67
|
+
else
|
|
68
|
+
"#{base_color}#{text}#{Colors::RESET}"
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Rainbow effect
|
|
73
|
+
def self.rainbow(text)
|
|
74
|
+
rainbow_colors = [
|
|
75
|
+
Colors::RED,
|
|
76
|
+
Colors::YELLOW,
|
|
77
|
+
Colors::GREEN,
|
|
78
|
+
Colors::CYAN,
|
|
79
|
+
Colors::BLUE,
|
|
80
|
+
Colors::MAGENTA
|
|
81
|
+
]
|
|
82
|
+
|
|
83
|
+
horizontal_gradient(text, rainbow_colors)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Sombra (simulada com dim colors)
|
|
87
|
+
def self.shadow(text, offset: 1)
|
|
88
|
+
t = Colors.theme
|
|
89
|
+
shadow_color = t[:dim]
|
|
90
|
+
|
|
91
|
+
lines = text.split("\n")
|
|
92
|
+
result = []
|
|
93
|
+
|
|
94
|
+
lines.each do |line|
|
|
95
|
+
result << "#{shadow_color}#{line}#{Colors::RESET}"
|
|
96
|
+
# Linha de sombra
|
|
97
|
+
result << " #{shadow_color}#{line.gsub(/[^\s]/, '░')}#{Colors::RESET}" if offset > 0
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
result.join("\n")
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Blur simulation (usando caracteres de meio-tom)
|
|
104
|
+
BLUR_CHARS = ['█', '▓', '▒', '░', ' '].freeze
|
|
105
|
+
|
|
106
|
+
def self.blur(text, strength: 1)
|
|
107
|
+
return text if strength <= 0
|
|
108
|
+
|
|
109
|
+
chars = text.chars
|
|
110
|
+
result = ''
|
|
111
|
+
|
|
112
|
+
chars.each do |char|
|
|
113
|
+
if char == ' '
|
|
114
|
+
result << char
|
|
115
|
+
else
|
|
116
|
+
blur_idx = [strength, BLUR_CHARS.length - 1].min
|
|
117
|
+
result << BLUR_CHARS[blur_idx]
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
result
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# Efeito de digitação com cursor
|
|
125
|
+
def self.typing(text, cursor_pos:, cursor_char: '▊')
|
|
126
|
+
before = text[0...cursor_pos]
|
|
127
|
+
after = text[cursor_pos..-1] || ''
|
|
128
|
+
|
|
129
|
+
"#{before}#{Colors::BOLD}#{cursor_char}#{Colors::RESET}#{after}"
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# Neon effect (cyan/magenta alternando)
|
|
133
|
+
def self.neon(text, phase: 0.0)
|
|
134
|
+
t = Colors.theme
|
|
135
|
+
colors = [t[:accent], t[:accent2]]
|
|
136
|
+
|
|
137
|
+
horizontal_gradient(text, colors)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# Glitch effect
|
|
141
|
+
GLITCH_CHARS = '!@#$%^&*()_+-=[]{}|;:,.<>?'.chars.freeze
|
|
142
|
+
|
|
143
|
+
def self.glitch(text, intensity: 0.3)
|
|
144
|
+
return text if intensity <= 0
|
|
145
|
+
|
|
146
|
+
chars = text.chars
|
|
147
|
+
result = ''
|
|
148
|
+
|
|
149
|
+
chars.each do |char|
|
|
150
|
+
if rand < intensity && char != ' '
|
|
151
|
+
result << GLITCH_CHARS.sample
|
|
152
|
+
else
|
|
153
|
+
result << char
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
result
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# Marquee / scrolling text
|
|
161
|
+
def self.marquee(text, width:, offset: 0)
|
|
162
|
+
return text if text.length <= width
|
|
163
|
+
|
|
164
|
+
# Cria loop infinito
|
|
165
|
+
looped = text + ' ' + text
|
|
166
|
+
start = offset % text.length
|
|
167
|
+
|
|
168
|
+
looped[start, width] || text[0, width]
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# Border glow (borda pulsante)
|
|
172
|
+
def self.border_glow(width:, height:, phase: 0.0, color: nil)
|
|
173
|
+
t = Colors.theme
|
|
174
|
+
glow_color = color || t[:accent]
|
|
175
|
+
dim_color = t[:dim]
|
|
176
|
+
|
|
177
|
+
# Calcula intensidade baseada na fase
|
|
178
|
+
intensity = Math.sin(phase * Math::PI * 2) * 0.5 + 0.5
|
|
179
|
+
active_color = intensity > 0.5 ? glow_color : dim_color
|
|
180
|
+
|
|
181
|
+
corners = ['╭', '╮', '╰', '╯']
|
|
182
|
+
h_line = '─'
|
|
183
|
+
v_line = '│'
|
|
184
|
+
|
|
185
|
+
top = active_color + corners[0] + (h_line * (width - 2)) + corners[1] + Colors::RESET
|
|
186
|
+
bottom = active_color + corners[2] + (h_line * (width - 2)) + corners[3] + Colors::RESET
|
|
187
|
+
sides = (1..height - 2).map { active_color + v_line + (' ' * (width - 2)) + v_line + Colors::RESET }
|
|
188
|
+
|
|
189
|
+
[top, *sides, bottom]
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# Wave effect (texto ondulando)
|
|
193
|
+
def self.wave(text, phase: 0.0, amplitude: 1)
|
|
194
|
+
chars = text.chars
|
|
195
|
+
lines = Array.new(amplitude * 2 + 1) { '' }
|
|
196
|
+
|
|
197
|
+
chars.each_with_index do |char, i|
|
|
198
|
+
wave_pos = Math.sin((i + phase * 10) * 0.3) * amplitude
|
|
199
|
+
center = amplitude
|
|
200
|
+
|
|
201
|
+
lines.each_with_index do |line, line_idx|
|
|
202
|
+
if (line_idx - center - wave_pos).abs < 0.5
|
|
203
|
+
lines[line_idx] << char
|
|
204
|
+
else
|
|
205
|
+
lines[line_idx] << ' '
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
lines.join("\n")
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
# Scanline effect
|
|
214
|
+
def self.scanline(text, spacing: 2, dim_color: nil)
|
|
215
|
+
t = Colors.theme
|
|
216
|
+
dim = dim_color || t[:dim]
|
|
217
|
+
normal = t[:text]
|
|
218
|
+
|
|
219
|
+
lines = text.split("\n")
|
|
220
|
+
lines.map.with_index do |line, i|
|
|
221
|
+
if i % spacing == 0
|
|
222
|
+
"#{dim}#{line}#{Colors::RESET}"
|
|
223
|
+
else
|
|
224
|
+
"#{normal}#{line}#{Colors::RESET}"
|
|
225
|
+
end
|
|
226
|
+
end.join("\n")
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
# CRT flicker (simula monitor antigo)
|
|
230
|
+
def self.crt_flicker(text, phase: 0.0)
|
|
231
|
+
flicker = Math.sin(phase * Math::PI * 60) * 0.1 + 0.9
|
|
232
|
+
|
|
233
|
+
if flicker > 0.95
|
|
234
|
+
text
|
|
235
|
+
else
|
|
236
|
+
t = Colors.theme
|
|
237
|
+
"#{t[:dim]}#{text}#{Colors::RESET}"
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
# Matrix rain effect preparer
|
|
242
|
+
def self.matrix_rain_column(height:, chars: '0123456789ABCDEF'.chars)
|
|
243
|
+
column = []
|
|
244
|
+
head_pos = rand(height)
|
|
245
|
+
|
|
246
|
+
height.times do |i|
|
|
247
|
+
if i == head_pos
|
|
248
|
+
column << "#{Colors::WHITE}#{chars.sample}#{Colors::RESET}"
|
|
249
|
+
elsif i < head_pos && i > head_pos - 5
|
|
250
|
+
brightness = head_pos - i
|
|
251
|
+
green_level = [255, brightness * 50].min
|
|
252
|
+
color = "#{Colors::ESC}[38;2;0;#{green_level};0m"
|
|
253
|
+
column << "#{color}#{chars.sample}#{Colors::RESET}"
|
|
254
|
+
else
|
|
255
|
+
column << "#{Colors::DIM_GREEN}#{chars.sample}#{Colors::RESET}"
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
column
|
|
260
|
+
end
|
|
261
|
+
end
|
|
262
|
+
end
|
|
263
|
+
end
|
data/lib/gsd/tui/header.rb
CHANGED
|
@@ -20,16 +20,24 @@ module Gsd
|
|
|
20
20
|
|
|
21
21
|
def render
|
|
22
22
|
t = Colors.theme
|
|
23
|
-
|
|
23
|
+
cyan = t[:accent] # Ciano neon
|
|
24
|
+
magenta = t[:accent2] # Roxo neon
|
|
25
|
+
pink = t[:accent3] # Rosa
|
|
26
|
+
white = t[:text]
|
|
24
27
|
dim = t[:dim]
|
|
25
28
|
reset = Colors::RESET
|
|
26
29
|
|
|
30
|
+
# Título estilo Warp com gradiente ciano -> roxo
|
|
31
|
+
title_line = "#{cyan}╔══════════════════════════════════════════╗#{reset}"
|
|
32
|
+
title_text = "#{cyan}║#{reset} #{magenta}⚡#{reset} #{white}Fantasy CLI#{reset} #{dim}v#{Gsd::VERSION}#{reset} #{cyan}║#{reset}"
|
|
33
|
+
title_bottom = "#{cyan}╚══════════════════════════════════════════╝#{reset}"
|
|
34
|
+
|
|
27
35
|
lines = []
|
|
28
36
|
lines << ''
|
|
29
|
-
lines <<
|
|
30
|
-
lines <<
|
|
31
|
-
lines <<
|
|
32
|
-
lines << "#{dim}
|
|
37
|
+
lines << title_line
|
|
38
|
+
lines << title_text
|
|
39
|
+
lines << title_bottom
|
|
40
|
+
lines << "#{dim} Terminal User Interface#{reset}"
|
|
33
41
|
lines << ''
|
|
34
42
|
lines.join("\n")
|
|
35
43
|
end
|
data/lib/gsd/tui/input_box.rb
CHANGED
|
@@ -77,27 +77,30 @@ module Gsd
|
|
|
77
77
|
|
|
78
78
|
def render
|
|
79
79
|
t = Colors.theme
|
|
80
|
-
|
|
80
|
+
cyan = t[:accent] # Ciano neon
|
|
81
|
+
magenta = t[:accent2] # Roxo neon
|
|
81
82
|
dim = t[:dim]
|
|
82
83
|
white = t[:text]
|
|
84
|
+
bg = t[:bg]
|
|
83
85
|
reset = Colors::RESET
|
|
84
86
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
+
# Bordas estilo Warp com cores neon
|
|
88
|
+
top_border = "#{cyan}╭#{'─' * @width}╮#{reset}"
|
|
89
|
+
bottom_border = "#{cyan}╰#{'─' * @width}╯#{reset}"
|
|
87
90
|
|
|
88
|
-
content = build_content(
|
|
91
|
+
content = build_content(cyan, magenta, dim, white, reset)
|
|
89
92
|
|
|
90
93
|
clean_len = content.gsub(/\e\[\d+m/, '').length
|
|
91
94
|
padding = ' ' * [@width - clean_len, 0].max
|
|
92
95
|
|
|
93
96
|
lines = []
|
|
94
97
|
lines << top_border
|
|
95
|
-
lines << "#{
|
|
98
|
+
lines << "#{cyan}│#{reset}#{content}#{padding}#{cyan}│#{reset}"
|
|
96
99
|
lines << bottom_border
|
|
97
100
|
|
|
98
101
|
# Adiciona autocomplete se ativo
|
|
99
102
|
if @autocomplete_active && @autocomplete_items.any?
|
|
100
|
-
lines << render_autocomplete(
|
|
103
|
+
lines << render_autocomplete(cyan, magenta, dim, white, reset)
|
|
101
104
|
end
|
|
102
105
|
|
|
103
106
|
lines.join("\n")
|
|
@@ -188,7 +191,7 @@ module Gsd
|
|
|
188
191
|
lines.join("\n")
|
|
189
192
|
end
|
|
190
193
|
|
|
191
|
-
def build_content(accent, dim, white, reset)
|
|
194
|
+
def build_content(accent, accent2, dim, white, reset)
|
|
192
195
|
prompt = "#{accent}❯#{reset} "
|
|
193
196
|
if @text.empty?
|
|
194
197
|
"#{prompt}#{dim}#{@placeholder}#{reset}"
|