Add search functionality to Mail TUI with / keybinding
- Add reusable SearchScreen modal and ClearableSearchInput widget - Implement filter_by_query in MessageStore for client-side filtering - Search matches subject, sender name/email, recipient name/email - Press / to open search, Escape to clear search filter - Shows search query in list subtitle when filter is active
This commit is contained in:
@@ -11,6 +11,7 @@ from src.services.taskwarrior import client as taskwarrior_client
|
||||
from src.services.himalaya import client as himalaya_client
|
||||
from src.utils.shared_config import get_theme_name
|
||||
from src.utils.ipc import IPCListener, IPCMessage
|
||||
from src.utils.search import SearchScreen
|
||||
from textual.containers import Container, ScrollableContainer, Vertical, Horizontal
|
||||
from textual.timer import Timer
|
||||
from textual.binding import Binding
|
||||
@@ -73,6 +74,7 @@ class EmailViewerApp(App):
|
||||
sort_order_ascending: Reactive[bool] = reactive(True)
|
||||
selected_messages: Reactive[set[int]] = reactive(set())
|
||||
main_content_visible: Reactive[bool] = reactive(True)
|
||||
search_query: Reactive[str] = reactive("") # Current search filter
|
||||
|
||||
def get_system_commands(self, screen: Screen) -> Iterable[SystemCommand]:
|
||||
yield from super().get_system_commands(screen)
|
||||
@@ -126,6 +128,7 @@ class EmailViewerApp(App):
|
||||
Binding("x", "toggle_selection", "Toggle selection", show=False),
|
||||
Binding("space", "toggle_selection", "Toggle selection"),
|
||||
Binding("escape", "clear_selection", "Clear selection"),
|
||||
Binding("/", "search", "Search"),
|
||||
]
|
||||
)
|
||||
|
||||
@@ -511,7 +514,13 @@ class EmailViewerApp(App):
|
||||
|
||||
config = get_config()
|
||||
|
||||
for item in self.message_store.envelopes:
|
||||
# Use filtered envelopes if search is active
|
||||
if self.search_query:
|
||||
display_envelopes = self.message_store.filter_by_query(self.search_query)
|
||||
else:
|
||||
display_envelopes = self.message_store.envelopes
|
||||
|
||||
for item in display_envelopes:
|
||||
if item and item.get("type") == "header":
|
||||
# Use the new GroupHeader widget for date groupings
|
||||
envelopes_list.append(ListItem(GroupHeader(label=item["label"])))
|
||||
@@ -872,10 +881,17 @@ class EmailViewerApp(App):
|
||||
self._update_list_view_subtitle()
|
||||
|
||||
def action_clear_selection(self) -> None:
|
||||
"""Clear all selected messages."""
|
||||
self.selected_messages.clear()
|
||||
self.refresh_list_view_items() # Refresh all items to uncheck checkboxes
|
||||
self._update_list_view_subtitle()
|
||||
"""Clear all selected messages and search filter."""
|
||||
if self.selected_messages:
|
||||
self.selected_messages.clear()
|
||||
self.refresh_list_view_items() # Refresh all items to uncheck checkboxes
|
||||
self._update_list_view_subtitle()
|
||||
elif self.search_query:
|
||||
# Clear search if no selection
|
||||
self.search_query = ""
|
||||
self._populate_list_view()
|
||||
self._update_list_view_subtitle()
|
||||
self.show_status("Search cleared")
|
||||
|
||||
def action_oldest(self) -> None:
|
||||
self.fetch_envelopes() if self.reload_needed else None
|
||||
@@ -885,6 +901,40 @@ class EmailViewerApp(App):
|
||||
self.fetch_envelopes() if self.reload_needed else None
|
||||
self.show_message(self.message_store.get_newest_id())
|
||||
|
||||
def action_search(self) -> None:
|
||||
"""Open search dialog to filter messages."""
|
||||
|
||||
def handle_search_result(query: str | None) -> None:
|
||||
if query is None:
|
||||
return # User cancelled
|
||||
self.search_query = query
|
||||
self._apply_search_filter()
|
||||
|
||||
self.push_screen(
|
||||
SearchScreen(
|
||||
title="Search Messages",
|
||||
placeholder="Search by subject, sender, or recipient...",
|
||||
initial_value=self.search_query,
|
||||
),
|
||||
handle_search_result,
|
||||
)
|
||||
|
||||
def _apply_search_filter(self) -> None:
|
||||
"""Apply the current search filter to the envelope list."""
|
||||
self._populate_list_view()
|
||||
|
||||
# Update the title to show search status
|
||||
if self.search_query:
|
||||
self.query_one("#envelopes_list").border_subtitle = f"[{self.search_query}]"
|
||||
else:
|
||||
self._update_list_view_subtitle()
|
||||
|
||||
# Focus the list and select first message
|
||||
self.query_one("#envelopes_list").focus()
|
||||
envelopes_list = self.query_one("#envelopes_list", ListView)
|
||||
if envelopes_list.children:
|
||||
envelopes_list.index = 0
|
||||
|
||||
def action_focus_1(self) -> None:
|
||||
self.query_one("#envelopes_list").focus()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user