feat(search): overhaul search output formatting (GIT-5)
Phase 1: Add source_entity_iid to search results via CASE subquery on hydrate_results() for all 4 source types (issue, MR, discussion, note). Phase 2: Fix visual alignment - compute indent from prefix visible width. Phase 3: Show compact relative time on title line. Phase 4: Add drill-down hint footer (lore issues <iid>). Phase 5: Move labels to --explain mode, limit snippets to 2 terminal lines. Phase 6: Use section_divider() for results header. Also: promote strip_ansi/visible_width to public render utils, update robot mode --fields minimal search preset with source_entity_iid.
This commit is contained in:
@@ -569,6 +569,32 @@ pub fn terminal_width() -> usize {
|
||||
80
|
||||
}
|
||||
|
||||
/// Strip ANSI escape codes (SGR sequences) from a string.
|
||||
pub fn strip_ansi(s: &str) -> String {
|
||||
let mut out = String::with_capacity(s.len());
|
||||
let mut chars = s.chars();
|
||||
while let Some(c) = chars.next() {
|
||||
if c == '\x1b' {
|
||||
// Consume `[`, then digits/semicolons, then the final letter
|
||||
if chars.next() == Some('[') {
|
||||
for c in chars.by_ref() {
|
||||
if c.is_ascii_alphabetic() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
out.push(c);
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
/// Compute the visible width of a string that may contain ANSI escape sequences.
|
||||
pub fn visible_width(s: &str) -> usize {
|
||||
strip_ansi(s).chars().count()
|
||||
}
|
||||
|
||||
/// Truncate a string to `max` characters, appending "..." if truncated.
|
||||
pub fn truncate(s: &str, max: usize) -> String {
|
||||
if max < 4 {
|
||||
@@ -1459,24 +1485,19 @@ mod tests {
|
||||
|
||||
// ── helpers ──
|
||||
|
||||
/// Strip ANSI escape codes (SGR sequences) for content assertions.
|
||||
/// Delegate to the public `strip_ansi` for test assertions.
|
||||
fn strip_ansi(s: &str) -> String {
|
||||
let mut out = String::with_capacity(s.len());
|
||||
let mut chars = s.chars();
|
||||
while let Some(c) = chars.next() {
|
||||
if c == '\x1b' {
|
||||
// Consume `[`, then digits/semicolons, then the final letter
|
||||
if chars.next() == Some('[') {
|
||||
for c in chars.by_ref() {
|
||||
if c.is_ascii_alphabetic() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
out.push(c);
|
||||
}
|
||||
}
|
||||
out
|
||||
super::strip_ansi(s)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn visible_width_strips_ansi() {
|
||||
let styled = "\x1b[1mhello\x1b[0m".to_string();
|
||||
assert_eq!(super::visible_width(&styled), 5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn visible_width_plain_string() {
|
||||
assert_eq!(super::visible_width("hello"), 5);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user