From 97fc677ade97919c01f7b5fbe56c83c3e57f54f6 Mon Sep 17 00:00:00 2001 From: shixi-li Date: Fri, 31 Jul 2026 06:58:28 +0800 Subject: [PATCH] fix(cli): handle unencodable search results --- src/cocoindex_code/cli.py | 13 +++++++++++-- tests/test_cli_helpers.py | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/src/cocoindex_code/cli.py b/src/cocoindex_code/cli.py index a58a8c4..c78adb2 100644 --- a/src/cocoindex_code/cli.py +++ b/src/cocoindex_code/cli.py @@ -195,6 +195,15 @@ def print_index_stats(status: ProjectStatusResponse) -> None: _typer.echo(f" {lang}: {count} chunks") +def _echo_search_text(text: str) -> None: + """Echo result text, replacing characters unsupported by the console codec.""" + try: + _typer.echo(text) + except UnicodeEncodeError as error: + safe_text = text.encode(error.encoding, errors="replace").decode(error.encoding) + _typer.echo(safe_text) + + def print_search_results(response: SearchResponse) -> None: """Print formatted search results.""" if not response.success: @@ -207,8 +216,8 @@ def print_search_results(response: SearchResponse) -> None: for i, r in enumerate(response.results, 1): _typer.echo(f"\n--- Result {i} (score: {r.score:.3f}) ---") - _typer.echo(f"File: {r.file_path}:{r.start_line}-{r.end_line} [{r.language}]") - _typer.echo(r.content) + _echo_search_text(f"File: {r.file_path}:{r.start_line}-{r.end_line} [{r.language}]") + _echo_search_text(r.content) def _run_index_with_progress(project_root: str) -> None: diff --git a/tests/test_cli_helpers.py b/tests/test_cli_helpers.py index 22467e6..26ab379 100644 --- a/tests/test_cli_helpers.py +++ b/tests/test_cli_helpers.py @@ -2,6 +2,7 @@ from __future__ import annotations +import io from pathlib import Path import pytest @@ -13,6 +14,37 @@ require_project_root, resolve_default_path, ) +from cocoindex_code.protocol import SearchResponse, SearchResult + + +def test_print_search_results_replaces_unencodable_console_characters( + monkeypatch: pytest.MonkeyPatch, +) -> None: + """Search output stays usable when the console cannot encode a result.""" + raw_output = io.BytesIO() + gbk_stdout = io.TextIOWrapper(raw_output, encoding="gbk", errors="strict") + monkeypatch.setattr(cli.sys, "stdout", gbk_stdout) + response = SearchResponse( + success=True, + results=[ + SearchResult( + file_path="notes↔.md", + language="markdown", + content="可编码内容: left ↔ right", + start_line=1, + end_line=1, + score=0.9, + ) + ], + ) + + cli.print_search_results(response) + gbk_stdout.flush() + + output = raw_output.getvalue().decode("gbk") + assert "可编码内容" in output + assert "File: notes?.md" in output + assert "left ? right" in output def test_require_project_root_success(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: