refactor(cli): migrate all command modules from console::style to Theme
Replace all console::style() calls in command modules with the centralized Theme API and render:: utility functions. This ensures consistent color behavior across the entire CLI, proper NO_COLOR/--color never support via the LoreRenderer singleton, and eliminates duplicated formatting code. Changes per module: - count.rs: Theme for table headers, render::format_number replacing local duplicate. Removed local format_number implementation. - doctor.rs: Theme::success/warning/error for check status symbols and messages. Unicode escapes for check/warning/cross symbols. - drift.rs: Theme::bold/error/success for drift detection headers and status messages. - embed.rs: Compact output format — headline with count, zero-suppressed detail lines, 'nothing to embed' short-circuit for no-op runs. - generate_docs.rs: Same compact pattern — headline + detail + hint for next step. No-op short-circuit when regenerated==0. - ingest.rs: Theme for project summaries, sync status, dry-run preview. All console::style -> Theme replacements. - list.rs: Replace comfy-table with render::LoreTable for issue/MR listing. Remove local colored_cell, colored_cell_hex, format_relative_time, truncate_with_ellipsis, and format_labels (all moved to render.rs). - list_tests.rs: Update test assertions to use render:: functions. - search.rs: Add render_snippet() for FTS5 <mark> tag highlighting via Theme::bold().underline(). Compact result layout with type badges. - show.rs: Theme for entity detail views, delegate format_date and wrap_text to render module. - stats.rs: Section-based layout using render::section_divider. Compact middle-dot format for document counts. Color-coded embedding coverage percentage (green >=95%, yellow >=50%, red <50%). - sync.rs: Compact sync summary — headline with counts and elapsed time, zero-suppressed detail lines, visually prominent error-only section. - sync_status.rs: Theme for run history headers, removed local format_number duplicate. - timeline.rs: Theme for headers/footers, render:: for date/truncate, standard format! padding replacing console::pad_str. - who.rs: Theme for all expert/workload/active/overlap/review output modes, render:: for relative time and truncation. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use console::style;
|
||||
use crate::cli::render::Theme;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::Config;
|
||||
@@ -309,68 +309,94 @@ fn parse_json_array(json: &str) -> Vec<String> {
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Render FTS snippet with `<mark>` tags as terminal bold+underline.
|
||||
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]);
|
||||
remaining = &remaining[start + 6..];
|
||||
if let Some(end) = remaining.find("</mark>") {
|
||||
let highlighted = &remaining[..end];
|
||||
result.push_str(&Theme::bold().underline().render(highlighted));
|
||||
remaining = &remaining[end + 7..];
|
||||
}
|
||||
}
|
||||
result.push_str(remaining);
|
||||
result
|
||||
}
|
||||
|
||||
pub fn print_search_results(response: &SearchResponse) {
|
||||
if !response.warnings.is_empty() {
|
||||
for w in &response.warnings {
|
||||
eprintln!("{} {}", style("Warning:").yellow(), w);
|
||||
eprintln!("{} {}", Theme::warning().render("Warning:"), w);
|
||||
}
|
||||
}
|
||||
|
||||
if response.results.is_empty() {
|
||||
println!("No results found for '{}'", style(&response.query).bold());
|
||||
println!(
|
||||
"No results found for '{}'",
|
||||
Theme::bold().render(&response.query)
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
println!(
|
||||
"{} results for '{}' ({})",
|
||||
response.total_results,
|
||||
style(&response.query).bold(),
|
||||
response.mode
|
||||
"\n {} results for '{}' {}",
|
||||
Theme::bold().render(&response.total_results.to_string()),
|
||||
Theme::bold().render(&response.query),
|
||||
Theme::dim().render(&format!("({})", response.mode))
|
||||
);
|
||||
println!();
|
||||
|
||||
for (i, result) in response.results.iter().enumerate() {
|
||||
let type_prefix = match result.source_type.as_str() {
|
||||
"issue" => "Issue",
|
||||
"merge_request" => "MR",
|
||||
"discussion" => "Discussion",
|
||||
"note" => "Note",
|
||||
_ => &result.source_type,
|
||||
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),
|
||||
};
|
||||
|
||||
// Title line: rank, type badge, title
|
||||
println!(
|
||||
"[{}] {} - {} (score: {:.2})",
|
||||
i + 1,
|
||||
style(type_prefix).cyan(),
|
||||
result.title,
|
||||
result.score
|
||||
" {} {} {}",
|
||||
Theme::dim().render(&format!("{:>2}.", i + 1)),
|
||||
type_badge,
|
||||
Theme::bold().render(&result.title)
|
||||
);
|
||||
|
||||
if let Some(ref url) = result.url {
|
||||
println!(" {}", style(url).dim());
|
||||
// Metadata: project, author, labels — compact middle-dot line
|
||||
let mut meta_parts: Vec<String> = Vec::new();
|
||||
meta_parts.push(result.project_path.clone());
|
||||
if let Some(ref author) = result.author {
|
||||
meta_parts.push(format!("@{author}"));
|
||||
}
|
||||
|
||||
println!(
|
||||
" {} | {}",
|
||||
style(&result.project_path).dim(),
|
||||
result
|
||||
.author
|
||||
.as_deref()
|
||||
.map(|a| format!("@{}", a))
|
||||
.unwrap_or_default()
|
||||
);
|
||||
|
||||
if !result.labels.is_empty() {
|
||||
println!(" Labels: {}", result.labels.join(", "));
|
||||
let label_str = if result.labels.len() <= 3 {
|
||||
result.labels.join(", ")
|
||||
} else {
|
||||
format!(
|
||||
"{} +{}",
|
||||
result.labels[..2].join(", "),
|
||||
result.labels.len() - 2
|
||||
)
|
||||
};
|
||||
meta_parts.push(label_str);
|
||||
}
|
||||
println!(
|
||||
" {}",
|
||||
Theme::dim().render(&meta_parts.join(" \u{b7} "))
|
||||
);
|
||||
|
||||
let clean_snippet = result.snippet.replace("<mark>", "").replace("</mark>", "");
|
||||
println!(" {}", style(clean_snippet).dim());
|
||||
// Snippet with proper highlighting
|
||||
let rendered = render_snippet(&result.snippet);
|
||||
println!(" {}", Theme::dim().render(&rendered));
|
||||
|
||||
if let Some(ref explain) = result.explain {
|
||||
println!(
|
||||
" {} vector_rank={} fts_rank={} rrf_score={:.6}",
|
||||
style("[explain]").magenta(),
|
||||
" {} vec={} fts={} rrf={:.4}",
|
||||
Theme::accent().render("explain"),
|
||||
explain
|
||||
.vector_rank
|
||||
.map(|r| r.to_string())
|
||||
|
||||
Reference in New Issue
Block a user