(stdscr, notes)
| 333 | |
| 334 | # Search |
| 335 | def curses_search(stdscr, notes): |
| 336 | try: |
| 337 | curses.curs_set(1) |
| 338 | h, w = stdscr.getmaxyx() |
| 339 | query = '' |
| 340 | matches = [] |
| 341 | selected = 0 |
| 342 | keys = [k for k in notes.keys() if k != '_reminders'] |
| 343 | while True: |
| 344 | stdscr.erase() |
| 345 | centered_addstr(stdscr, 1, ' Search Notes (type to filter) — Enter select, ESC back ') |
| 346 | stdscr.addstr(3, max(2, (w - 60)//2), 'Query: ' + query + ' ') |
| 347 | scored = [] |
| 348 | for k in keys: |
| 349 | s = score_query(query, k) if query else 0 |
| 350 | s2 = score_query(query, notes.get(k, '')) if query else 0 |
| 351 | score = max(s, s2) |
| 352 | scored.append((score, k)) |
| 353 | scored.sort(reverse=True) |
| 354 | matches = [k for sc,k in scored if sc>0 or not query][:min(20, len(scored))] |
| 355 | start_y = 5 |
| 356 | for idx, key in enumerate(matches): |
| 357 | marker = '>' if idx == selected else ' ' |
| 358 | line = f"{marker} {key}" |
| 359 | try: |
| 360 | stdscr.addstr(start_y+idx, max(2, (w - 60)//2), line[:60]) |
| 361 | except Exception: |
| 362 | pass |
| 363 | stdscr.refresh() |
| 364 | ch = stdscr.getch() |
| 365 | if ch in (27,): |
| 366 | curses.curs_set(0) |
| 367 | return None |
| 368 | elif ch in (10,13): |
| 369 | if not matches: |
| 370 | continue |
| 371 | curses.curs_set(0) |
| 372 | return matches[selected] |
| 373 | elif ch == curses.KEY_UP: |
| 374 | selected = max(0, selected-1) |
| 375 | elif ch == curses.KEY_DOWN: |
| 376 | selected = min(len(matches)-1, selected+1) |
| 377 | elif ch in (curses.KEY_BACKSPACE, 127, 8): |
| 378 | query = query[:-1] |
| 379 | selected = 0 |
| 380 | elif 0 <= ch <= 255: |
| 381 | query += chr(ch) |
| 382 | selected = 0 |
| 383 | except Exception as e: |
| 384 | log_exception(e) |
| 385 | error_dialog(stdscr, 'Search error', traceback.format_exc()) |
| 386 | return None |
| 387 | |
| 388 | # Confirm dialog |
| 389 | def confirm_dialog(stdscr, text): |
no test coverage detected