Skip to content

Commit ff1e02b

Browse files
kaja-sTjaz Erzen
authored andcommitted
Redesigned the TUI and hierarchicaly organized the information
1 parent 8cf289e commit ff1e02b

6 files changed

Lines changed: 279 additions & 113 deletions

File tree

plain2code_console.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -87,9 +87,7 @@ def _create_tree_from_files(self, root_folder, files):
8787
else:
8888
file_lines = len(content.splitlines())
8989
file_tokens = len(self.llm_encoding.encode(content))
90-
current_level = current_level.add(
91-
f"{part} ({file_lines} lines, {file_tokens} tokens)"
92-
)
90+
current_level = current_level.add(f"{part} ({file_lines} lines, {file_tokens} tokens)")
9391
else:
9492
current_level = current_level.add(part)
9593
else:

tui/components.py

Lines changed: 177 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -11,21 +11,21 @@
1111

1212
class CustomFooter(Horizontal):
1313
"""A custom footer with keyboard shortcuts and render ID."""
14-
14+
1515
def __init__(self, render_id: str = "", **kwargs):
1616
super().__init__(**kwargs)
1717
self.render_id = render_id
18-
18+
1919
def compose(self):
2020
yield Static("ctrl+c: quit * ctrl+l: toggle logs", classes="custom-footer-text")
2121
if self.render_id:
2222
yield Static(f"render id: {self.render_id}", classes="custom-footer-render-id")
2323

2424

2525
class ScriptOutputType(str, Enum):
26-
UNIT_TEST_OUTPUT_TEXT = "Latest unit test script execution output: "
27-
CONFORMANCE_TEST_OUTPUT_TEXT = "Latest conformance tests script execution output: "
28-
TESTING_ENVIRONMENT_OUTPUT_TEXT = "Latest testing environment preparation script execution output: "
26+
UNIT_TEST_OUTPUT_TEXT = "Unit tests: "
27+
CONFORMANCE_TEST_OUTPUT_TEXT = "Conformance tests: "
28+
TESTING_ENVIRONMENT_OUTPUT_TEXT = "Testing environment preparation execution output: "
2929

3030
@staticmethod
3131
def get_max_label_width(active_types: list["ScriptOutputType"]) -> int:
@@ -42,16 +42,16 @@ def get_max_label_width(active_types: list["ScriptOutputType"]) -> int:
4242
return max(len(script_type.value) for script_type in active_types)
4343

4444
def get_padded_label(self, active_types: list["ScriptOutputType"]) -> str:
45-
"""Get the label right-padded to align with other active labels.
45+
"""Get the label left-aligned (no padding).
4646
4747
Args:
4848
active_types: List of ScriptOutputType enum members that are currently active
4949
5050
Returns:
51-
Right-aligned label padded to match the longest active label
51+
Label without padding (left-aligned)
5252
"""
53-
max_width = ScriptOutputType.get_max_label_width(active_types)
54-
return self.value.rjust(max_width)
53+
# Return label as-is without padding for left alignment
54+
return self.value
5555

5656

5757
class TUIComponents(str, Enum):
@@ -189,6 +189,155 @@ async def clear_substates(self):
189189
pass
190190

191191

192+
class RenderingInfoBox(Vertical):
193+
"""Container with ASCII border for module and functionality information."""
194+
195+
def __init__(self, **kwargs):
196+
super().__init__(**kwargs)
197+
self.module_text = ""
198+
self.functionality_text = ""
199+
200+
def update_module(self, text: str) -> None:
201+
"""Update the module name and refresh the display."""
202+
self.module_text = text
203+
self._refresh_content()
204+
205+
def update_functionality(self, text: str) -> None:
206+
"""Update the functionality text and refresh the display."""
207+
self.functionality_text = text
208+
self._refresh_content()
209+
210+
def _refresh_content(self) -> None:
211+
"""Refresh the content of the box."""
212+
try:
213+
widget = self.query_one("#rendering-info-content", Static)
214+
# Calculate the width needed for the border
215+
lines = []
216+
if self.module_text:
217+
lines.append(f" {self.module_text}")
218+
if self.functionality_text:
219+
lines.append(f" {self.functionality_text}")
220+
221+
if not lines:
222+
widget.update("")
223+
return
224+
225+
# Find the longest line to determine border width
226+
max_width = max(len(line) for line in lines) if lines else 40
227+
border_width = max(max_width + 2, 42) # At least 42 chars wide
228+
229+
# Build the bordered box with title
230+
title = " Currently rendering "
231+
top_border = "┌[#FFFFFF]" + title + "[/#FFFFFF]" + "─" * (border_width - len(title)) + "┐"
232+
bottom_border = "└" + "─" * border_width + "┘"
233+
234+
content_lines = [top_border]
235+
# Add top padding (empty line)
236+
content_lines.append(f"│{' ' * border_width}│")
237+
for line in lines:
238+
padding = border_width - len(line)
239+
content_lines.append(f"│{line}{' ' * padding}│")
240+
# Add bottom padding (empty line)
241+
content_lines.append(f"│{' ' * border_width}│")
242+
content_lines.append(bottom_border)
243+
244+
widget.update("\n".join(content_lines))
245+
except Exception:
246+
pass
247+
248+
def on_mount(self) -> None:
249+
"""Initialize the box with empty state on mount."""
250+
# Set default empty labels
251+
self.module_text = "Module: "
252+
self.functionality_text = "Functionality:"
253+
self._refresh_content()
254+
255+
def compose(self):
256+
yield Static("", id="rendering-info-content", classes="rendering-info-box")
257+
258+
259+
class TestScriptsContainer(Vertical):
260+
"""Container with ASCII border for test script outputs."""
261+
262+
def __init__(
263+
self,
264+
show_unit_test: bool = True,
265+
show_conformance_test: bool = True,
266+
show_testing_env: bool = True,
267+
**kwargs,
268+
):
269+
super().__init__(**kwargs)
270+
self.show_unit_test = show_unit_test
271+
self.show_conformance_test = show_conformance_test
272+
self.show_testing_env = show_testing_env
273+
self.unit_test_text = ScriptOutputType.UNIT_TEST_OUTPUT_TEXT.value
274+
self.conformance_test_text = ScriptOutputType.CONFORMANCE_TEST_OUTPUT_TEXT.value
275+
self.testing_env_text = ScriptOutputType.TESTING_ENVIRONMENT_OUTPUT_TEXT.value
276+
277+
def update_unit_test(self, text: str) -> None:
278+
"""Update unit test output and refresh."""
279+
self.unit_test_text = text
280+
self._refresh_content()
281+
282+
def update_conformance_test(self, text: str) -> None:
283+
"""Update conformance test output and refresh."""
284+
self.conformance_test_text = text
285+
self._refresh_content()
286+
287+
def update_testing_env(self, text: str) -> None:
288+
"""Update testing env output and refresh."""
289+
self.testing_env_text = text
290+
self._refresh_content()
291+
292+
def _refresh_content(self) -> None:
293+
"""Refresh the bordered box content."""
294+
try:
295+
widget = self.query_one("#test-scripts-content", Static)
296+
297+
# Collect lines to display
298+
lines = []
299+
if self.show_unit_test:
300+
lines.append(f" {self.unit_test_text}")
301+
if self.show_conformance_test:
302+
lines.append(f" {self.conformance_test_text}")
303+
if self.show_testing_env:
304+
lines.append(f" {self.testing_env_text}")
305+
306+
if not lines:
307+
widget.update("")
308+
return
309+
310+
# Find the longest line to determine border width
311+
max_width = max(len(line) for line in lines)
312+
border_width = max(max_width + 2, 80) # Minimum width of 80 chars
313+
314+
# Build the bordered box with title
315+
title = " Latest test scripts "
316+
top_border = "┌[#FFFFFF]" + title + "[/#FFFFFF]" + "─" * (border_width - len(title)) + "┐"
317+
bottom_border = "└" + "─" * border_width + "┘"
318+
319+
content_lines = [top_border]
320+
# Add top padding (empty line)
321+
content_lines.append(f"│{' ' * border_width}│")
322+
for line in lines:
323+
padding = border_width - len(line)
324+
content_lines.append(f"│{line}{' ' * padding}│")
325+
# Add bottom padding (empty line)
326+
content_lines.append(f"│{' ' * border_width}│")
327+
content_lines.append(bottom_border)
328+
329+
widget.update("\n".join(content_lines))
330+
except Exception:
331+
pass
332+
333+
def on_mount(self) -> None:
334+
"""Initialize the box on mount."""
335+
self._refresh_content()
336+
337+
def compose(self):
338+
yield Static("", id="test-scripts-content", classes="test-scripts-box")
339+
340+
192341
class FRIDProgress(Vertical):
193342
"""A widget to display the status of subcomponent tasks."""
194343

@@ -198,8 +347,8 @@ class FRIDProgress(Vertical):
198347
REFACTORING_TEXT = "Refactoring"
199348
CONFORMANCE_TEST_VALIDATION_TEXT = "Conformance tests"
200349

201-
RENDERING_MODULE_TEXT = "Rendering module: "
202-
RENDERING_FUNCTIONALITY_TEXT = "Rendering functionality:"
350+
RENDERING_MODULE_TEXT = "Module: "
351+
RENDERING_FUNCTIONALITY_TEXT = "Functionality:"
203352

204353
def __init__(
205354
self,
@@ -213,8 +362,9 @@ def __init__(
213362

214363
def update_fr_text(self, text: str) -> None:
215364
try:
216-
widget = self.query_one(f"#{TUIComponents.FRID_PROGRESS_HEADER.value}", Static)
217-
widget.update(text)
365+
# Update the rendering info box instead
366+
info_box = self.query_one(RenderingInfoBox)
367+
info_box.update_functionality(text)
218368
except Exception:
219369
pass
220370

@@ -229,8 +379,7 @@ def on_mount(self) -> None:
229379
self.border_title = "FRID Progress"
230380

231381
def compose(self):
232-
yield Static(self.RENDERING_MODULE_TEXT, id=TUIComponents.RENDER_MODULE_NAME_WIDGET.value)
233-
yield Static(self.RENDERING_FUNCTIONALITY_TEXT, id=TUIComponents.FRID_PROGRESS_HEADER.value)
382+
yield RenderingInfoBox()
234383
yield ProgressItem(
235384
self.IMPLEMENTING_FUNCTIONALITY_TEXT,
236385
id=TUIComponents.FRID_PROGRESS_RENDER_FR.value,
@@ -266,22 +415,23 @@ def compose(self):
266415
# Column 1: Offset time with brackets (just the time portion, not full timestamp)
267416
time_part = self.timestamp.split()[-1] if self.timestamp else ""
268417
yield Static(f"[{time_part}]", classes="log-col-time")
269-
418+
270419
# Column 2: Level
271420
yield Static(self.level, classes="log-col-level")
272-
421+
273422
# Column 3: Location (logger name) - truncate if longer than 20 characters
274423
location = self.logger_name
275424
if len(location) > 9:
276425
location = location[:9] + "..."
277426
yield Static(location, classes="log-col-location")
278-
427+
279428
# Column 4: Message with green checkmark for successful outcomes
280429
message_text = self.message
281430
# Add green checkmark for messages indicating success
282-
if any(keyword in message_text.lower() for keyword in [
283-
"completed", "success", "successfully", "passed", "done", "✓"
284-
]):
431+
if any(
432+
keyword in message_text.lower()
433+
for keyword in ["completed", "success", "successfully", "passed", "done", "✓"]
434+
):
285435
message_text = f"[green]✓[/green] {message_text}"
286436
yield Static(message_text, classes="log-col-message")
287437

@@ -310,15 +460,15 @@ def _should_show_log(self, level: str) -> bool:
310460
async def add_log(self, logger_name: str, level: str, message: str, timestamp: str = ""):
311461
"""Add a new log entry."""
312462
# Check if this is a success message that should have spacing before it
313-
is_success_message = any(keyword in message.lower() for keyword in [
314-
"completed", "success", "successfully", "passed", "done", "✓"
315-
])
316-
463+
is_success_message = any(
464+
keyword in message.lower() for keyword in ["completed", "success", "successfully", "passed", "done", "✓"]
465+
)
466+
317467
# Add empty line before success messages
318468
if is_success_message:
319469
spacer = Static("", classes="log-spacer")
320470
await self.mount(spacer)
321-
471+
322472
entry = LogEntry(logger_name, level, message, timestamp)
323473

324474
# Only show if level is >= minimum level
@@ -350,7 +500,7 @@ class LogLevelFilter(Horizontal):
350500
"""Filter logs by minimum level with buttons."""
351501

352502
LEVELS = ["debug", "info", "warning", "error"]
353-
503+
354504
# Make the widget focusable to receive keyboard events
355505
can_focus = True
356506

0 commit comments

Comments
 (0)