Files
clide/legacy/clide/widgets/components/problems_view.py
T
jpmschweitzerandClaude Opus 4.7 a355751437 move python clide to legacy/
Clide is being rebuilt as a Flutter desktop app. The Python Textual
implementation moves wholesale into legacy/ rather than being deleted:
its pane model, panel set, git skills, and panel communication design
are real thought that should remain readable next to the new code
while the rebuild finds its shape. Git's rename tracking preserves
history, so `git log -- legacy/` still works.

The Flutter rebuild lives at the repo root alongside a Go sidecar
(the architecture claudian was heading toward, which folds into
clide as a core component rather than a separate plugin project).
Bootstrap of the new shape lands in subsequent commits.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 20:30:51 +02:00

116 lines
3.5 KiB
Python

"""Problems view component for linter errors."""
from pathlib import Path
from rich.markup import escape
from textual.app import ComposeResult
from textual.message import Message
from textual.widgets import ListView, Static
from clide.models.problems import Problem
from clide.widgets.components.tile_list import TileItem, TileListView
class ProblemItem(TileItem):
"""A single problem item displayed as a tile."""
def __init__(self, problem: Problem) -> None:
super().__init__()
self.problem = problem
def compose(self) -> ComposeResult:
icon = self.problem.severity_icon
severity_class = self.problem.severity.value
# Show just filename
filename = (
self.problem.file_path.name
if hasattr(self.problem.file_path, "name")
else str(self.problem.file_path).split("/")[-1]
)
safe_message = escape(self.problem.message)
yield Static(
f"[{severity_class}]{icon}[/] [{severity_class}]{safe_message}[/]\n"
f" [dim]{filename}:{self.problem.line}[/]",
markup=True,
)
class ProblemsView(TileListView):
"""View for linter problems/diagnostics."""
DEFAULT_CSS = """
ProblemsView {
height: 1fr;
background: $surface;
}
ProblemsView .problems-header {
height: 1;
background: $surface;
padding: 0 1;
border-bottom: solid $primary;
}
ProblemsView .error { color: $error; }
ProblemsView .warning { color: $warning; }
ProblemsView .info { color: $primary; }
ProblemsView .hint { color: $secondary; }
ProblemsView .empty-message {
padding: 2;
text-align: center;
color: $success;
}
"""
class ProblemClicked(Message):
"""Emitted when a problem is clicked."""
def __init__(self, problem: Problem) -> None:
self.problem = problem
super().__init__()
def __init__(self, problems: list[Problem] | None = None, **kwargs) -> None:
super().__init__(**kwargs)
self._problems = problems or []
def compose(self) -> ComposeResult:
count = len(self._problems)
yield Static(f"Problems ({count})", classes="problems-header", id="problems-header")
if self._problems:
yield ListView(
*[ProblemItem(p) for p in self._problems],
id="problems-list",
)
else:
yield Static("No problems found ✓", classes="empty-message")
def update_problems(self, problems: list[Problem]) -> None:
"""Update the problems list."""
self._problems = problems
# Update header
header = self.query_one("#problems-header", Static)
header.update(f"Problems ({len(problems)})")
# Update list
try:
problems_list = self.query_one("#problems-list", ListView)
problems_list.clear()
for problem in problems:
problems_list.append(ProblemItem(problem))
except Exception:
# List might not exist yet, will be created on next compose
pass
def filter_by_file(self, path: Path) -> list[Problem]:
"""Get problems for a specific file."""
return [p for p in self._problems if p.file_path == path]
def on_list_view_selected(self, event: ListView.Selected) -> None:
"""Handle item selection."""
if isinstance(event.item, ProblemItem):
self.post_message(self.ProblemClicked(event.item.problem))