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,320 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Gsd
|
|
4
|
+
module TUI
|
|
5
|
+
# Animations - Engine de animações para TUI
|
|
6
|
+
#
|
|
7
|
+
# Suporta:
|
|
8
|
+
# - Typing effect (texto aparecendo caractere por caractere)
|
|
9
|
+
# - Fade in/out
|
|
10
|
+
# - Pulse/glow effects
|
|
11
|
+
# - Progress bars animadas
|
|
12
|
+
# - Frame-based animations
|
|
13
|
+
class Animation
|
|
14
|
+
attr_reader :type, :duration, :frame, :running, :finished
|
|
15
|
+
|
|
16
|
+
def initialize(type:, duration: 1.0, &block)
|
|
17
|
+
@type = type
|
|
18
|
+
@duration = duration
|
|
19
|
+
@frame = 0
|
|
20
|
+
@running = false
|
|
21
|
+
@finished = false
|
|
22
|
+
@callback = block
|
|
23
|
+
@start_time = nil
|
|
24
|
+
@thread = nil
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def start
|
|
28
|
+
return if @running
|
|
29
|
+
|
|
30
|
+
@running = true
|
|
31
|
+
@finished = false
|
|
32
|
+
@start_time = Time.now
|
|
33
|
+
@frame = 0
|
|
34
|
+
|
|
35
|
+
@thread = Thread.new do
|
|
36
|
+
loop do
|
|
37
|
+
elapsed = Time.now - @start_time
|
|
38
|
+
|
|
39
|
+
if elapsed >= @duration
|
|
40
|
+
@finished = true
|
|
41
|
+
@running = false
|
|
42
|
+
@callback&.call(:finished, self)
|
|
43
|
+
break
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
@frame += 1
|
|
47
|
+
@callback&.call(:frame, self)
|
|
48
|
+
|
|
49
|
+
sleep(1.0 / 60) # 60 FPS
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
self
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def stop
|
|
57
|
+
@running = false
|
|
58
|
+
@thread&.kill
|
|
59
|
+
@thread = nil
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Progresso da animação (0.0 a 1.0)
|
|
63
|
+
def progress
|
|
64
|
+
return 1.0 if @finished
|
|
65
|
+
return 0.0 unless @start_time
|
|
66
|
+
|
|
67
|
+
elapsed = Time.now - @start_time
|
|
68
|
+
[elapsed / @duration, 1.0].min
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Easing functions
|
|
72
|
+
def ease_in_out
|
|
73
|
+
p = progress
|
|
74
|
+
p < 0.5 ? 2 * p * p : 1 - ((-2 * p + 2)**2 / 2)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def ease_out
|
|
78
|
+
p = progress
|
|
79
|
+
1 - (1 - p)**2
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def ease_in
|
|
83
|
+
p = progress
|
|
84
|
+
p * p
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def bounce
|
|
88
|
+
p = progress
|
|
89
|
+
return 0.0 if p < 0.5
|
|
90
|
+
Math.sin(p * Math::PI * 4) * (1 - p) + p
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# AnimationManager - Gerencia múltiplas animações
|
|
95
|
+
class AnimationManager
|
|
96
|
+
def initialize
|
|
97
|
+
@animations = []
|
|
98
|
+
@mutex = Mutex.new
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def create(type:, duration: 1.0, &block)
|
|
102
|
+
anim = Animation.new(type: type, duration: duration, &block)
|
|
103
|
+
@mutex.synchronize { @animations << anim }
|
|
104
|
+
anim
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def update
|
|
108
|
+
@mutex.synchronize do
|
|
109
|
+
@animations.delete_if { |a| a.finished && !a.running }
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def stop_all
|
|
114
|
+
@mutex.synchronize do
|
|
115
|
+
@animations.each(&:stop)
|
|
116
|
+
@animations.clear
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def running_count
|
|
121
|
+
@mutex.synchronize { @animations.count(&:running) }
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# TypingEffect - Animação de digitação
|
|
126
|
+
class TypingEffect
|
|
127
|
+
attr_reader :text, :cursor, :finished
|
|
128
|
+
|
|
129
|
+
def initialize(text:, speed: 0.05, &block)
|
|
130
|
+
@text = text
|
|
131
|
+
@speed = speed
|
|
132
|
+
@cursor = 0
|
|
133
|
+
@finished = false
|
|
134
|
+
@callback = block
|
|
135
|
+
@thread = nil
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def start
|
|
139
|
+
return if @thread&.alive?
|
|
140
|
+
|
|
141
|
+
@finished = false
|
|
142
|
+
@cursor = 0
|
|
143
|
+
|
|
144
|
+
@thread = Thread.new do
|
|
145
|
+
@text.each_char do |char|
|
|
146
|
+
sleep(@speed)
|
|
147
|
+
@cursor += 1
|
|
148
|
+
@callback&.call(self)
|
|
149
|
+
end
|
|
150
|
+
@finished = true
|
|
151
|
+
@callback&.call(self)
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
self
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def stop
|
|
158
|
+
@thread&.kill
|
|
159
|
+
@thread = nil
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# Texto visível até o cursor
|
|
163
|
+
def visible_text
|
|
164
|
+
@text[0...@cursor]
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# Texto restante
|
|
168
|
+
def remaining_text
|
|
169
|
+
@text[@cursor..-1]
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# SpinnerAnimation - Spinner melhorado
|
|
174
|
+
class SpinnerAnimation
|
|
175
|
+
FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'].freeze
|
|
176
|
+
|
|
177
|
+
attr_reader :running
|
|
178
|
+
|
|
179
|
+
def initialize(message: '', speed: 0.08, &block)
|
|
180
|
+
@message = message
|
|
181
|
+
@speed = speed
|
|
182
|
+
@callback = block
|
|
183
|
+
@running = false
|
|
184
|
+
@thread = nil
|
|
185
|
+
@frame_index = 0
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def start
|
|
189
|
+
return if @running
|
|
190
|
+
|
|
191
|
+
@running = true
|
|
192
|
+
@frame_index = 0
|
|
193
|
+
|
|
194
|
+
@thread = Thread.new do
|
|
195
|
+
while @running
|
|
196
|
+
frame = FRAMES[@frame_index % FRAMES.length]
|
|
197
|
+
@callback&.call(frame, @message)
|
|
198
|
+
@frame_index += 1
|
|
199
|
+
sleep(@speed)
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
self
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
def stop
|
|
207
|
+
@running = false
|
|
208
|
+
@thread&.join(0.5)
|
|
209
|
+
@thread = nil
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
def update_message(new_message)
|
|
213
|
+
@message = new_message
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
def render
|
|
217
|
+
frame = FRAMES[@frame_index % FRAMES.length]
|
|
218
|
+
"#{frame} #{@message}"
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
# PulseEffect - Efeito de pulsação
|
|
223
|
+
class PulseEffect
|
|
224
|
+
attr_reader :running
|
|
225
|
+
|
|
226
|
+
def initialize(cycles: 3, speed: 0.5, &block)
|
|
227
|
+
@cycles = cycles
|
|
228
|
+
@speed = speed
|
|
229
|
+
@callback = block
|
|
230
|
+
@running = false
|
|
231
|
+
@thread = nil
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
def start
|
|
235
|
+
return if @running
|
|
236
|
+
|
|
237
|
+
@running = true
|
|
238
|
+
|
|
239
|
+
@thread = Thread.new do
|
|
240
|
+
@cycles.times do |i|
|
|
241
|
+
break unless @running
|
|
242
|
+
|
|
243
|
+
# Intensidade varia de 0.0 a 1.0
|
|
244
|
+
intensity = Math.sin(i * Math::PI / @cycles)
|
|
245
|
+
@callback&.call(intensity, i)
|
|
246
|
+
sleep(@speed)
|
|
247
|
+
end
|
|
248
|
+
@running = false
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
self
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
def stop
|
|
255
|
+
@running = false
|
|
256
|
+
@thread&.kill
|
|
257
|
+
@thread = nil
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
# ProgressBar - Barra de progresso animada
|
|
262
|
+
class ProgressBar
|
|
263
|
+
attr_reader :value, :max, :width
|
|
264
|
+
|
|
265
|
+
def initialize(width: 40, max: 100, fill: '█', empty: '░')
|
|
266
|
+
@width = width
|
|
267
|
+
@max = max
|
|
268
|
+
@value = 0
|
|
269
|
+
@fill = fill
|
|
270
|
+
@empty = empty
|
|
271
|
+
@animation = nil
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
def update(value)
|
|
275
|
+
@value = [[value, 0].max, @max].min
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
def animate_to(target, duration: 1.0, &block)
|
|
279
|
+
start_val = @value
|
|
280
|
+
@animation&.stop
|
|
281
|
+
|
|
282
|
+
@animation = Animation.new(type: :progress, duration: duration) do |event, anim|
|
|
283
|
+
if event == :frame
|
|
284
|
+
@value = start_val + (target - start_val) * anim.ease_out
|
|
285
|
+
block&.call(self)
|
|
286
|
+
end
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
@animation.start
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
def render
|
|
293
|
+
filled = ((@value.to_f / @max) * @width).round
|
|
294
|
+
filled = [@width, filled].min
|
|
295
|
+
|
|
296
|
+
bar = @fill * filled + @empty * (@width - filled)
|
|
297
|
+
percentage = ((@value / @max.to_f) * 100).round
|
|
298
|
+
|
|
299
|
+
"[#{bar}] #{percentage}%"
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
def render_colored(theme: nil)
|
|
303
|
+
t = theme || Colors.theme
|
|
304
|
+
filled = ((@value.to_f / @max) * @width).round
|
|
305
|
+
filled = [@width, filled].min
|
|
306
|
+
|
|
307
|
+
filled_part = @fill * filled
|
|
308
|
+
empty_part = @empty * (@width - filled)
|
|
309
|
+
percentage = ((@value / @max.to_f) * 100).round
|
|
310
|
+
|
|
311
|
+
"#{t[:accent]}[#{filled_part}#{t[:dim]}#{empty_part}#{t[:accent]}] #{percentage}%#{Colors::RESET}"
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
def finish
|
|
315
|
+
@animation&.stop
|
|
316
|
+
@value = @max
|
|
317
|
+
end
|
|
318
|
+
end
|
|
319
|
+
end
|
|
320
|
+
end
|