mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-04-10 03:13:29 +08:00
feat: add ecc2 regex output search
This commit is contained in:
1
ecc2/Cargo.lock
generated
1
ecc2/Cargo.lock
generated
@@ -497,6 +497,7 @@ dependencies = [
|
|||||||
"git2",
|
"git2",
|
||||||
"libc",
|
"libc",
|
||||||
"ratatui",
|
"ratatui",
|
||||||
|
"regex",
|
||||||
"rusqlite",
|
"rusqlite",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ git2 = "0.20"
|
|||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
toml = "0.8"
|
toml = "0.8"
|
||||||
|
regex = "1"
|
||||||
|
|
||||||
# CLI
|
# CLI
|
||||||
clap = { version = "4", features = ["derive"] }
|
clap = { version = "4", features = ["derive"] }
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ use ratatui::{
|
|||||||
Block, Borders, Cell, HighlightSpacing, Paragraph, Row, Table, TableState, Tabs, Wrap,
|
Block, Borders, Cell, HighlightSpacing, Paragraph, Row, Table, TableState, Tabs, Wrap,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
use regex::Regex;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use tokio::sync::broadcast;
|
use tokio::sync::broadcast;
|
||||||
|
|
||||||
@@ -1625,6 +1626,12 @@ impl Dashboard {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Err(error) = compile_search_regex(&query) {
|
||||||
|
self.search_input = Some(query.clone());
|
||||||
|
self.set_operator_note(format!("invalid regex /{query}: {error}"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
self.search_query = Some(query.clone());
|
self.search_query = Some(query.clone());
|
||||||
self.recompute_search_matches();
|
self.recompute_search_matches();
|
||||||
if self.search_matches.is_empty() {
|
if self.search_matches.is_empty() {
|
||||||
@@ -2145,11 +2152,17 @@ impl Dashboard {
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let Ok(regex) = compile_search_regex(&query) else {
|
||||||
|
self.search_matches.clear();
|
||||||
|
self.selected_search_match = 0;
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
self.search_matches = self
|
self.search_matches = self
|
||||||
.selected_output_lines()
|
.selected_output_lines()
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.filter_map(|(index, line)| line_matches_query(&line.text, &query).then_some(index))
|
.filter_map(|(index, line)| regex.is_match(&line.text).then_some(index))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
if self.search_matches.is_empty() {
|
if self.search_matches.is_empty() {
|
||||||
@@ -2959,12 +2972,8 @@ fn configured_pane_size(cfg: &Config, layout: PaneLayout) -> u16 {
|
|||||||
configured.clamp(MIN_PANE_SIZE_PERCENT, MAX_PANE_SIZE_PERCENT)
|
configured.clamp(MIN_PANE_SIZE_PERCENT, MAX_PANE_SIZE_PERCENT)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn line_matches_query(text: &str, query: &str) -> bool {
|
fn compile_search_regex(query: &str) -> Result<Regex, regex::Error> {
|
||||||
if query.is_empty() {
|
Regex::new(query)
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
text.contains(query)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn highlight_output_line(
|
fn highlight_output_line(
|
||||||
@@ -2977,13 +2986,15 @@ fn highlight_output_line(
|
|||||||
return Line::from(text.to_string());
|
return Line::from(text.to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let Ok(regex) = compile_search_regex(query) else {
|
||||||
|
return Line::from(text.to_string());
|
||||||
|
};
|
||||||
|
|
||||||
let mut spans = Vec::new();
|
let mut spans = Vec::new();
|
||||||
let mut cursor = 0;
|
let mut cursor = 0;
|
||||||
let mut search_start = 0;
|
for matched in regex.find_iter(text) {
|
||||||
|
let start = matched.start();
|
||||||
while let Some(relative_index) = text[search_start..].find(query) {
|
let end = matched.end();
|
||||||
let start = search_start + relative_index;
|
|
||||||
let end = start + query.len();
|
|
||||||
|
|
||||||
if start > cursor {
|
if start > cursor {
|
||||||
spans.push(Span::raw(text[cursor..start].to_string()));
|
spans.push(Span::raw(text[cursor..start].to_string()));
|
||||||
@@ -2999,7 +3010,6 @@ fn highlight_output_line(
|
|||||||
};
|
};
|
||||||
spans.push(Span::styled(text[start..end].to_string(), match_style));
|
spans.push(Span::styled(text[start..end].to_string(), match_style));
|
||||||
cursor = end;
|
cursor = end;
|
||||||
search_start = end;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if cursor < text.len() {
|
if cursor < text.len() {
|
||||||
@@ -4091,17 +4101,17 @@ diff --git a/src/next.rs b/src/next.rs
|
|||||||
dashboard.last_output_height = 2;
|
dashboard.last_output_height = 2;
|
||||||
|
|
||||||
dashboard.begin_search();
|
dashboard.begin_search();
|
||||||
for ch in "alpha".chars() {
|
for ch in "alpha.*".chars() {
|
||||||
dashboard.push_search_char(ch);
|
dashboard.push_search_char(ch);
|
||||||
}
|
}
|
||||||
dashboard.submit_search();
|
dashboard.submit_search();
|
||||||
|
|
||||||
assert_eq!(dashboard.search_query.as_deref(), Some("alpha"));
|
assert_eq!(dashboard.search_query.as_deref(), Some("alpha.*"));
|
||||||
assert_eq!(dashboard.search_matches, vec![0, 2]);
|
assert_eq!(dashboard.search_matches, vec![0, 2]);
|
||||||
assert_eq!(dashboard.selected_search_match, 0);
|
assert_eq!(dashboard.selected_search_match, 0);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
dashboard.operator_note.as_deref(),
|
dashboard.operator_note.as_deref(),
|
||||||
Some("search /alpha matched 2 line(s) | n/N navigate matches")
|
Some("search /alpha.* matched 2 line(s) | n/N navigate matches")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4123,7 +4133,7 @@ diff --git a/src/next.rs b/src/next.rs
|
|||||||
vec![
|
vec![
|
||||||
OutputLine {
|
OutputLine {
|
||||||
stream: OutputStream::Stdout,
|
stream: OutputStream::Stdout,
|
||||||
text: "alpha".to_string(),
|
text: "alpha-1".to_string(),
|
||||||
},
|
},
|
||||||
OutputLine {
|
OutputLine {
|
||||||
stream: OutputStream::Stdout,
|
stream: OutputStream::Stdout,
|
||||||
@@ -4131,11 +4141,11 @@ diff --git a/src/next.rs b/src/next.rs
|
|||||||
},
|
},
|
||||||
OutputLine {
|
OutputLine {
|
||||||
stream: OutputStream::Stdout,
|
stream: OutputStream::Stdout,
|
||||||
text: "alpha tail".to_string(),
|
text: "alpha-2".to_string(),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
dashboard.search_query = Some("alpha".to_string());
|
dashboard.search_query = Some(r"alpha-\d".to_string());
|
||||||
dashboard.last_output_height = 1;
|
dashboard.last_output_height = 1;
|
||||||
dashboard.recompute_search_matches();
|
dashboard.recompute_search_matches();
|
||||||
|
|
||||||
@@ -4144,7 +4154,7 @@ diff --git a/src/next.rs b/src/next.rs
|
|||||||
assert_eq!(dashboard.output_scroll_offset, 2);
|
assert_eq!(dashboard.output_scroll_offset, 2);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
dashboard.operator_note.as_deref(),
|
dashboard.operator_note.as_deref(),
|
||||||
Some("search /alpha match 2/2")
|
Some(r"search /alpha-\d match 2/2")
|
||||||
);
|
);
|
||||||
|
|
||||||
dashboard.next_search_match();
|
dashboard.next_search_match();
|
||||||
@@ -4152,6 +4162,36 @@ diff --git a/src/next.rs b/src/next.rs
|
|||||||
assert_eq!(dashboard.output_scroll_offset, 0);
|
assert_eq!(dashboard.output_scroll_offset, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn submit_search_rejects_invalid_regex_and_keeps_input() {
|
||||||
|
let mut dashboard = test_dashboard(
|
||||||
|
vec![sample_session(
|
||||||
|
"focus-12345678",
|
||||||
|
"planner",
|
||||||
|
SessionState::Running,
|
||||||
|
None,
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
)],
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
|
||||||
|
dashboard.begin_search();
|
||||||
|
for ch in "(".chars() {
|
||||||
|
dashboard.push_search_char(ch);
|
||||||
|
}
|
||||||
|
dashboard.submit_search();
|
||||||
|
|
||||||
|
assert_eq!(dashboard.search_input.as_deref(), Some("("));
|
||||||
|
assert!(dashboard.search_query.is_none());
|
||||||
|
assert!(dashboard.search_matches.is_empty());
|
||||||
|
assert!(dashboard
|
||||||
|
.operator_note
|
||||||
|
.as_deref()
|
||||||
|
.unwrap_or_default()
|
||||||
|
.starts_with("invalid regex /(:"));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn clear_search_resets_active_query_and_matches() {
|
fn clear_search_resets_active_query_and_matches() {
|
||||||
let mut dashboard = test_dashboard(Vec::new(), 0);
|
let mut dashboard = test_dashboard(Vec::new(), 0);
|
||||||
|
|||||||
Reference in New Issue
Block a user