diff --git a/PROJECT_PLAN.md b/PROJECT_PLAN.md index e1f10a1..b7e3266 100644 --- a/PROJECT_PLAN.md +++ b/PROJECT_PLAN.md @@ -453,7 +453,7 @@ Implement `/` keybinding for search across all apps with similar UX: 5. Calendar: Calendar invites sidebar 6. ~~Mail: Add refresh keybinding~~ (DONE - `r` key) 7. ~~Mail: Add mark read/unread action~~ (DONE - `u` key) -8. Mail: Folder message counts +8. ~~Mail: Folder message counts~~ (DONE) 9. ~~Mail: URL compression in markdown view~~ (DONE) 10. Mail: Enhance subject styling 11. Mail: Search feature diff --git a/src/mail/app.py b/src/mail/app.py index 60047cf..017cf64 100644 --- a/src/mail/app.py +++ b/src/mail/app.py @@ -356,7 +356,13 @@ class EmailViewerApp(App): try: list_item = event.item label = list_item.query_one(Label) - folder_name = str(label.renderable).strip() + folder_text = str(label.renderable).strip() + + # Extract folder name (remove count suffix like " [dim](10)[/dim]") + # The format is "FolderName [dim](count)[/dim]" or just "FolderName" + import re + + folder_name = re.sub(r"\s*\[dim\]\(\d+\)\[/dim\]$", "", folder_text) if folder_name and folder_name != self.folder: self.folder = folder_name @@ -492,14 +498,19 @@ class EmailViewerApp(App): async def fetch_folders(self) -> None: folders_list = self.query_one("#folders_list", ListView) folders_list.clear() + + # Store folder names for count updates + folder_names = ["INBOX"] + + # Use the Himalaya client to fetch folders for current account + account = self.current_account if self.current_account else None + folders_list.append( - ListItem(Label("INBOX", classes="folder_name", markup=False)) + ListItem(Label("INBOX", classes="folder_name", markup=True)) ) try: folders_list.loading = True - # Use the Himalaya client to fetch folders for current account - account = self.current_account if self.current_account else None folders, success = await himalaya_client.list_folders(account=account) if success and folders: @@ -508,11 +519,12 @@ class EmailViewerApp(App): # Skip INBOX since we already added it if folder_name.upper() == "INBOX": continue + folder_names.append(folder_name) item = ListItem( Label( folder_name, classes="folder_name", - markup=False, + markup=True, ) ) folders_list.append(item) @@ -523,6 +535,34 @@ class EmailViewerApp(App): finally: folders_list.loading = False + # Fetch counts in background and update labels + self._update_folder_counts(folder_names, account) + + @work(exclusive=False) + async def _update_folder_counts( + self, folder_names: List[str], account: str | None + ) -> None: + """Fetch and display message counts for folders.""" + import asyncio + + folders_list = self.query_one("#folders_list", ListView) + + async def get_count_for_folder(folder_name: str, index: int): + count, success = await himalaya_client.get_folder_count( + folder_name, account + ) + if success and index < len(folders_list.children): + try: + list_item = folders_list.children[index] + label = list_item.query_one(Label) + label.update(f"{folder_name} [dim]({count})[/dim]") + except Exception: + pass # Widget may have been removed + + # Fetch counts in parallel + tasks = [get_count_for_folder(name, i) for i, name in enumerate(folder_names)] + await asyncio.gather(*tasks) + def _populate_list_view(self) -> None: """Populate the ListView with new items using the new EnvelopeListItem widget.""" envelopes_list = self.query_one("#envelopes_list", ListView) diff --git a/src/services/himalaya/client.py b/src/services/himalaya/client.py index 4aa6bb4..4574a56 100644 --- a/src/services/himalaya/client.py +++ b/src/services/himalaya/client.py @@ -115,6 +115,47 @@ async def list_folders( return [], False +async def get_folder_count( + folder: str, + account: Optional[str] = None, +) -> Tuple[int, bool]: + """ + Get the count of messages in a folder. + + Args: + folder: The folder to count messages in + account: The account to use (defaults to default account) + + Returns: + Tuple containing: + - Message count + - Success status (True if operation was successful) + """ + try: + # Use a high limit to get all messages, then count them + # This is the most reliable way with himalaya + cmd = f"himalaya envelope list -o json -s 9999 -f '{folder}'" + if account: + cmd += f" -a '{account}'" + + process = await asyncio.create_subprocess_shell( + cmd, + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.PIPE, + ) + stdout, stderr = await process.communicate() + + if process.returncode == 0: + envelopes = json.loads(stdout.decode()) + return len(envelopes), True + else: + logging.error(f"Error getting folder count: {stderr.decode()}") + return 0, False + except Exception as e: + logging.error(f"Exception during folder count: {e}") + return 0, False + + async def delete_message( message_id: int, folder: Optional[str] = None,