Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions src/logsqueak/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -414,12 +414,17 @@ def _display_search_results(results: list[dict], graph_path: Path):
Display search results in terminal-friendly format.

Uses OSC 8 escape codes for clickable links and color coding for readability.
Results are displayed in reverse relevance order (least to most relevant),
but numbered in reverse (most relevant = 1).
"""
for idx, result in enumerate(results, 1):
page_name = result["page_name"]
confidence = result["confidence"]
snippet = result["snippet"]

# Reverse numbering so most relevant result (shown last) gets number "1"
display_idx = len(results) - idx + 1

# Create clickable logseq:// link using OSC 8 escape codes
clickable_link = _create_clickable_link(page_name, graph_path)

Expand All @@ -444,8 +449,8 @@ def _display_search_results(results: list[dict], graph_path: Path):
# Fallback if no bullet content (shouldn't happen)
snippet_display = "(no content preview)"

# Display result
click.echo(f"{idx}. {clickable_link}")
# Display result with reversed numbering
click.echo(f"{display_idx}. {clickable_link}")
click.echo(f" Relevance: {confidence}%")
click.echo(f" {snippet_display}")
click.echo() # Blank line between results
Expand Down
61 changes: 61 additions & 0 deletions tests/integration/test_cli_search.py
Original file line number Diff line number Diff line change
Expand Up @@ -229,3 +229,64 @@ def test_search_respects_top_k_config(temp_config, temp_graph_with_pages, use_sh
# Should have at most 2 results
result_count = result.output.count("\n1.") + result.output.count("\n2.") + result.output.count("\n3.")
assert result_count <= 2


def test_search_displays_reversed_numbering(temp_config, temp_graph_with_pages, use_shared_encoder, monkeypatch):
"""Test that search displays results in reverse order with reversed numbering.

Most relevant result should appear last (closest to command prompt) and be numbered "1".
Less relevant results should appear earlier with higher numbers.
"""
# Monkeypatch config path
monkeypatch.setenv("HOME", str(temp_config.parent.parent))
config_home = temp_config.parent.parent / ".config" / "logsqueak"
config_home.mkdir(parents=True, exist_ok=True)
(config_home / "config.yaml").write_text(temp_config.read_text())
(config_home / "config.yaml").chmod(0o600)

runner = CliRunner()
result = runner.invoke(cli, ["search", "programming"])

# Print output for debugging
print(f"\n=== CLI OUTPUT ===")
print(result.output)
print(f"=== EXIT CODE: {result.exit_code} ===\n")

if result.exit_code != 0:
print(f"=== EXCEPTION ===")
if result.exception:
import traceback
traceback.print_exception(type(result.exception), result.exception, result.exception.__traceback__)
print("=================\n")

assert result.exit_code == 0

# Parse the output to find result numbers and their relevance scores
lines = result.output.split('\n')
results = []

for i, line in enumerate(lines):
# Look for numbered results (e.g., "1. PageName" or "2. PageName")
if line and line[0].isdigit() and '. ' in line:
number = int(line.split('.')[0])
# Next line should have "Relevance: XX%"
if i + 1 < len(lines) and "Relevance:" in lines[i + 1]:
relevance_str = lines[i + 1].split("Relevance:")[1].strip().rstrip('%')
relevance = int(relevance_str)
results.append((number, relevance))

# Should have at least 2 results for meaningful test
assert len(results) >= 2, f"Expected at least 2 results, got {len(results)}"

# Most relevant result should be last in the list
last_result = results[-1]
first_result = results[0]

# The last result (most relevant) should have number "1"
assert last_result[0] == 1, f"Expected last result to be numbered 1, got {last_result[0]}"

# The first result (least relevant) should have the highest number
assert first_result[0] == len(results), f"Expected first result to be numbered {len(results)}, got {first_result[0]}"

# Relevance should decrease as we go from last to first (reversed display)
assert last_result[1] >= first_result[1], f"Expected last result ({last_result[1]}%) to be more relevant than first ({first_result[1]}%)"
109 changes: 109 additions & 0 deletions tests/unit/test_cli_display_reversed_numbering.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
"""Unit test for reversed numbering in search results display."""

from pathlib import Path
from unittest.mock import patch, MagicMock
from io import StringIO


def test_display_search_results_reversed_numbering():
"""Test that search results are numbered in reverse (most relevant = 1)."""
from logsqueak.cli import _display_search_results

# Create mock results (already in reversed order - least to most relevant)
results = [
{
"page_name": "LeastRelevant",
"block_id": "id1",
"confidence": 50,
"snippet": "- Some content"
},
{
"page_name": "MiddleRelevant",
"block_id": "id2",
"confidence": 75,
"snippet": "- Some other content"
},
{
"page_name": "MostRelevant",
"block_id": "id3",
"confidence": 100,
"snippet": "- The best content"
}
]

graph_path = Path("/fake/graph")

# Capture output
output_lines = []

def mock_echo(msg=''):
output_lines.append(msg)

# Mock click.echo and the clickable link function
with patch('logsqueak.cli.click.echo', side_effect=mock_echo):
with patch('logsqueak.cli._create_clickable_link', side_effect=lambda name, path: name):
_display_search_results(results, graph_path)

# Join output for easier analysis
output = '\n'.join(output_lines)

# Parse the output to find numbered results
result_numbers = []
for i, line in enumerate(output_lines):
if '. LeastRelevant' in line or '. MiddleRelevant' in line or '. MostRelevant' in line:
# Extract the number before the dot
number = int(line.split('.')[0])
page_name = line.split('. ')[1]
result_numbers.append((number, page_name))

# Verify we found all 3 results
assert len(result_numbers) == 3, f"Expected 3 results, got {len(result_numbers)}"

# Verify numbering
# First result shown (least relevant) should have highest number (3)
assert result_numbers[0] == (3, "LeastRelevant"), \
f"Expected (3, 'LeastRelevant'), got {result_numbers[0]}"

# Middle result should have number 2
assert result_numbers[1] == (2, "MiddleRelevant"), \
f"Expected (2, 'MiddleRelevant'), got {result_numbers[1]}"

# Last result shown (most relevant) should have number 1
assert result_numbers[2] == (1, "MostRelevant"), \
f"Expected (1, 'MostRelevant'), got {result_numbers[2]}"

print("✓ Numbering is correctly reversed: most relevant result numbered as 1")


def test_display_search_results_reversed_numbering_with_two_results():
"""Test reversed numbering with only 2 results."""
from logsqueak.cli import _display_search_results

results = [
{"page_name": "Less", "block_id": "id1", "confidence": 60, "snippet": "- Content"},
{"page_name": "More", "block_id": "id2", "confidence": 90, "snippet": "- Better content"}
]

graph_path = Path("/fake/graph")
output_lines = []

def mock_echo(msg=''):
output_lines.append(msg)

with patch('logsqueak.cli.click.echo', side_effect=mock_echo):
with patch('logsqueak.cli._create_clickable_link', side_effect=lambda name, path: name):
_display_search_results(results, graph_path)

# Find numbered results
result_numbers = []
for line in output_lines:
if '. Less' in line or '. More' in line:
number = int(line.split('.')[0])
page_name = line.split('. ')[1]
result_numbers.append((number, page_name))

# Verify
assert result_numbers[0] == (2, "Less"), f"Expected (2, 'Less'), got {result_numbers[0]}"
assert result_numbers[1] == (1, "More"), f"Expected (1, 'More'), got {result_numbers[1]}"

print("✓ Two results correctly numbered: 2 (less relevant), 1 (more relevant)")