Files
clide/clide/widgets/components/todos_view.py
T
Jeroen SchweitzerandClaude Opus 4.5 1ed21d2a0f Restructure project and add user settings persistence
- Move src/clide/ to clide/ (flatten directory structure)
- Add SettingsService for persistent user preferences
- Settings stored in ~/.clide/settings.json
- Persist theme, panel visibility, and compact mode state
- Fix terminal rendering glitch with throttled refresh (~60fps)
- Update pyproject.toml, Makefile, and tests for new structure

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-01 00:42:46 +01:00

111 lines
3.0 KiB
Python

"""TODOs view component."""
from textual.app import ComposeResult
from textual.containers import Vertical
from textual.message import Message
from textual.widgets import ListItem, ListView, Static
from clide.models.todos import TodoItem, TodoType
class TodoListItem(ListItem):
"""A single TODO item."""
def __init__(self, item: TodoItem) -> None:
super().__init__()
self.item = item
def compose(self) -> ComposeResult:
icon = self.item.type_icon
type_class = self.item.todo_type.value.lower()
yield Static(
f"[{type_class}]{icon} {self.item.todo_type.value}[/] "
f"[dim]{self.item.file_path}:{self.item.line}[/] "
f"{self.item.text}",
markup=True,
)
class TodosView(Vertical):
"""View for TODO/FIXME comments."""
DEFAULT_CSS = """
TodosView {
height: 100%;
}
TodosView .todos-header {
height: 1;
background: $surface;
padding: 0 1;
}
TodosView ListView {
height: 1fr;
}
TodosView .todo { color: $primary; }
TodosView .fixme { color: $warning; }
TodosView .hack { color: $error; }
TodosView .xxx { color: $error; }
TodosView .note { color: $secondary; }
TodosView .bug { color: $error; }
TodosView .empty-message {
padding: 2;
text-align: center;
color: $success;
}
"""
class TodoClicked(Message):
"""Emitted when a TODO is clicked."""
def __init__(self, item: TodoItem) -> None:
self.item = item
super().__init__()
def __init__(self, items: list[TodoItem] | None = None, **kwargs) -> None:
super().__init__(**kwargs)
self._items = items or []
def compose(self) -> ComposeResult:
count = len(self._items)
yield Static(f"TODOs ({count})", classes="todos-header", id="todos-header")
if self._items:
yield ListView(
*[TodoListItem(item) for item in self._items],
id="todos-list",
)
else:
yield Static("No TODOs found ✓", classes="empty-message")
def update_items(self, items: list[TodoItem]) -> None:
"""Update the TODOs list."""
self._items = items
# Update header
header = self.query_one("#todos-header", Static)
header.update(f"TODOs ({len(items)})")
# Update list
try:
todos_list = self.query_one("#todos-list", ListView)
todos_list.clear()
for item in items:
todos_list.append(TodoListItem(item))
except Exception:
pass
def filter_by_type(self, todo_type: TodoType) -> list[TodoItem]:
"""Filter by TODO type."""
return [i for i in self._items if i.todo_type == todo_type]
def on_list_view_selected(self, event: ListView.Selected) -> None:
"""Handle item selection."""
if isinstance(event.item, TodoListItem):
self.post_message(self.TodoClicked(event.item.item))