refactor(search): polish search results rendering with semantic Theme styles
Phase 5 of the UX overhaul. Migrates search result display from raw console styling to the centralized Theme system with semantic methods, improving visual consistency and readability. Search result changes: - Type badges now use semantic styles (issue_ref, mr_ref) with fixed-width alignment for clean columnar layout - Snippet rendering uses Theme::highlight() for matched terms and Theme::muted() for surrounding context, replacing bold+underline - Metadata line uses Theme::username() for authors and per-part styling with middle-dot separators instead of a single dim line - Result numbering uses muted style with right-aligned width - Consistent 8-space indent for metadata, snippets, and explain lines - Header line uses muted style for search mode instead of dim+parens - Trailing blank line moved after the result loop instead of per-result Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
committed by
teernisse
parent
d710403567
commit
96b288ccdd
@@ -309,20 +309,20 @@ fn parse_json_array(json: &str) -> Vec<String> {
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Render FTS snippet with `<mark>` tags as terminal bold+underline.
|
||||
/// Render FTS snippet with `<mark>` tags as terminal highlight style.
|
||||
fn render_snippet(snippet: &str) -> String {
|
||||
let mut result = String::new();
|
||||
let mut remaining = snippet;
|
||||
while let Some(start) = remaining.find("<mark>") {
|
||||
result.push_str(&remaining[..start]);
|
||||
result.push_str(&Theme::muted().render(&remaining[..start]));
|
||||
remaining = &remaining[start + 6..];
|
||||
if let Some(end) = remaining.find("</mark>") {
|
||||
let highlighted = &remaining[..end];
|
||||
result.push_str(&Theme::bold().underline().render(highlighted));
|
||||
result.push_str(&Theme::highlight().render(highlighted));
|
||||
remaining = &remaining[end + 7..];
|
||||
}
|
||||
}
|
||||
result.push_str(remaining);
|
||||
result.push_str(&Theme::muted().render(remaining));
|
||||
result
|
||||
}
|
||||
|
||||
@@ -342,35 +342,37 @@ pub fn print_search_results(response: &SearchResponse) {
|
||||
}
|
||||
|
||||
println!(
|
||||
"\n {} results for '{}' {}",
|
||||
"\n {} results for '{}' {}",
|
||||
Theme::bold().render(&response.total_results.to_string()),
|
||||
Theme::bold().render(&response.query),
|
||||
Theme::dim().render(&format!("({})", response.mode))
|
||||
Theme::muted().render(&response.mode)
|
||||
);
|
||||
println!();
|
||||
|
||||
for (i, result) in response.results.iter().enumerate() {
|
||||
println!();
|
||||
|
||||
let type_badge = match result.source_type.as_str() {
|
||||
"issue" => Theme::info().render("issue"),
|
||||
"merge_request" => Theme::accent().render("mr"),
|
||||
"discussion" => Theme::info().render("disc"),
|
||||
"note" => Theme::info().render("note"),
|
||||
_ => Theme::dim().render(&result.source_type),
|
||||
"issue" => Theme::issue_ref().render("issue"),
|
||||
"merge_request" => Theme::mr_ref().render(" mr "),
|
||||
"discussion" => Theme::info().render(" disc"),
|
||||
"note" => Theme::muted().render(" note"),
|
||||
_ => Theme::muted().render(&format!("{:>5}", &result.source_type)),
|
||||
};
|
||||
|
||||
// Title line: rank, type badge, title
|
||||
println!(
|
||||
" {} {} {}",
|
||||
Theme::dim().render(&format!("{:>2}.", i + 1)),
|
||||
" {:>3}. {} {}",
|
||||
Theme::muted().render(&(i + 1).to_string()),
|
||||
type_badge,
|
||||
Theme::bold().render(&result.title)
|
||||
);
|
||||
|
||||
// Metadata: project, author, labels — compact middle-dot line
|
||||
let sep = Theme::muted().render(" \u{b7} ");
|
||||
let mut meta_parts: Vec<String> = Vec::new();
|
||||
meta_parts.push(result.project_path.clone());
|
||||
meta_parts.push(Theme::muted().render(&result.project_path));
|
||||
if let Some(ref author) = result.author {
|
||||
meta_parts.push(format!("@{author}"));
|
||||
meta_parts.push(Theme::username().render(&format!("@{author}")));
|
||||
}
|
||||
if !result.labels.is_empty() {
|
||||
let label_str = if result.labels.len() <= 3 {
|
||||
@@ -382,20 +384,17 @@ pub fn print_search_results(response: &SearchResponse) {
|
||||
result.labels.len() - 2
|
||||
)
|
||||
};
|
||||
meta_parts.push(label_str);
|
||||
meta_parts.push(Theme::muted().render(&label_str));
|
||||
}
|
||||
println!(
|
||||
" {}",
|
||||
Theme::dim().render(&meta_parts.join(" \u{b7} "))
|
||||
);
|
||||
println!(" {}", meta_parts.join(&sep));
|
||||
|
||||
// Snippet with proper highlighting
|
||||
// Snippet with highlight styling
|
||||
let rendered = render_snippet(&result.snippet);
|
||||
println!(" {}", Theme::dim().render(&rendered));
|
||||
println!(" {rendered}");
|
||||
|
||||
if let Some(ref explain) = result.explain {
|
||||
println!(
|
||||
" {} vec={} fts={} rrf={:.4}",
|
||||
" {} vec={} fts={} rrf={:.4}",
|
||||
Theme::accent().render("explain"),
|
||||
explain
|
||||
.vector_rank
|
||||
@@ -408,9 +407,9 @@ pub fn print_search_results(response: &SearchResponse) {
|
||||
explain.rrf_score
|
||||
);
|
||||
}
|
||||
|
||||
println!();
|
||||
}
|
||||
|
||||
println!();
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
|
||||
Reference in New Issue
Block a user