feat: show line number in search results using match position

This commit is contained in:
Tianyang 2026-08-01 19:29:57 +08:00
parent 2b287ccb35
commit c6cc9b7146
6 changed files with 26 additions and 3 deletions

View File

@ -36,14 +36,21 @@ class SearchClient:
"highlightPostTag": "</em>",
"attributesToCrop": ["content"],
"cropLength": 30,
"showMatchesPosition": True,
})
results = []
for hit in response.get("hits", []):
formatted = hit.get("_formatted", {})
line_number = None
matches = hit.get("_matchesPosition", {}).get("content", [])
if matches and hit.get("content"):
start = matches[0]["start"]
line_number = hit["content"][:start].count("\n") + 1
results.append({
"key": hit["key"],
"title": hit["title"],
"path": hit["path"],
"snippet": formatted.get("content", hit.get("content", "")),
"lineNumber": line_number,
})
return results

View File

@ -104,6 +104,7 @@ export interface SearchResult {
title: string;
path: string;
snippet: string;
lineNumber: number | null;
}
export interface SearchResponse {

View File

@ -32,6 +32,9 @@ export default function SearchResults({ results, query, onJumpToResult }: Search
<div className="result-body">
<div className="result-path">
<span className="result-title">{result.title}</span>
{result.lineNumber != null && (
<span className="result-line"> {result.lineNumber} </span>
)}
</div>
<div className="result-snippet" dangerouslySetInnerHTML={{ __html: result.snippet }} />
</div>

View File

@ -664,6 +664,17 @@ body {
color: var(--ink);
}
.result-line {
font-family: 'IBM Plex Mono', monospace;
font-size: 11px;
color: var(--ash);
background: var(--paper-alt);
border: 1px solid var(--rule);
border-radius: 4px;
padding: 1px 6px;
white-space: nowrap;
}
.result-snippet {
font-size: 12.5px;
color: var(--ash);

View File

@ -85,7 +85,7 @@ test('selecting a file from the tree loads it into the editor', async () => {
test('searching shows the results overlay, and clicking a result opens the document', async () => {
vi.mocked(listDocuments).mockResolvedValue({ folders: [], files: [] });
vi.mocked(search).mockResolvedValue({
results: [{ key: '产品文档/架构设计.md', title: '架构设计.md', path: '产品文档', snippet: '...<em>检索</em>...' }],
results: [{ key: '产品文档/架构设计.md', title: '架构设计.md', path: '产品文档', snippet: '...<em>检索</em>...', lineNumber: 5 }],
});
vi.mocked(getDocument).mockResolvedValue({
key: '产品文档/架构设计.md',
@ -121,7 +121,7 @@ test('searching shows the results overlay, and clicking a result opens the docum
test('jumping to a search result clears the search input so it does not show stale text', async () => {
vi.mocked(listDocuments).mockResolvedValue({ folders: [], files: [] });
vi.mocked(search).mockResolvedValue({
results: [{ key: '产品文档/架构设计.md', title: '架构设计.md', path: '产品文档', snippet: '...<em>检索</em>...' }],
results: [{ key: '产品文档/架构设计.md', title: '架构设计.md', path: '产品文档', snippet: '...<em>检索</em>...', lineNumber: 5 }],
});
vi.mocked(getDocument).mockResolvedValue({
key: '产品文档/架构设计.md',

View File

@ -12,6 +12,7 @@ test('renders result count, titles, and highlighted snippets', () => {
title: '架构设计.md',
path: '产品文档',
snippet: '检索Meilisearch支持 <em>中文全文检索</em>。',
lineNumber: null,
},
]}
onJumpToResult={vi.fn()}
@ -28,7 +29,7 @@ test('clicking a result calls onJumpToResult with its key', () => {
render(
<SearchResults
query="检索"
results={[{ key: '产品文档/架构设计.md', title: '架构设计.md', path: '产品文档', snippet: '...' }]}
results={[{ key: '产品文档/架构设计.md', title: '架构设计.md', path: '产品文档', snippet: '...', lineNumber: null }]}
onJumpToResult={onJumpToResult}
/>
);