输出色彩重整,添加关键字高亮

This commit is contained in:
2026-05-29 17:50:14 +08:00
parent fed28ac388
commit c3e4fd7e48
5 changed files with 86 additions and 21 deletions
Generated
+10
View File
@@ -63,6 +63,7 @@ name = "rustgrep"
version = "0.1.0"
dependencies = [
"crossbeam",
"termcolor",
"walkdir",
]
@@ -75,6 +76,15 @@ dependencies = [
"winapi-util",
]
[[package]]
name = "termcolor"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755"
dependencies = [
"winapi-util",
]
[[package]]
name = "walkdir"
version = "2.5.0"
+1
View File
@@ -13,3 +13,4 @@ strip = true # 自动剥离所有的调试信息和符号表(相当于
[dependencies]
walkdir = "2" # 目录递归遍历库
crossbeam = "0.8" # crossbeam 线程池
termcolor = "1.4" # 颜色管理
+55 -17
View File
@@ -1,10 +1,12 @@
use std::fs;
use std::path::Path;
use crossbeam::channel;
use termcolor::{Color, ColorSpec, StandardStream, WriteColor};
use walkdir::WalkDir;
use std::io::Write;
pub mod searcher;
pub use searcher::{ should_skip_dir, is_valid_extension, FileResult };
pub use searcher::{ should_skip_dir, is_valid_extension, FileResult, MatchLine};
pub fn search_lines(root_dir: &str,extensions_str: Option<String>, real_query: &str,ignore_case: bool) {
@@ -33,20 +35,55 @@ pub fn search_lines(root_dir: &str,extensions_str: Option<String>, real_query: &
drop(tx);
// 初始化工业级标准输出流(ColorChoice::Auto 代表自动检测:是终端就上色,是文件就纯文本)
let mut stdout = StandardStream::stdout(termcolor::ColorChoice::Auto);
// 路径字体样式
let mut path_color = ColorSpec::new();
path_color.set_fg(Some(Color::Magenta)).set_bold(true);
// 行号字体样式
let mut line_num_color = ColorSpec::new();
line_num_color.set_fg(Some(Color::Green));
// 关键字(搜索的词)的字体样式
let mut keyword_color = ColorSpec::new();
keyword_color.set_fg(Some(Color::Red)).set_bold(true);
while let Ok(file_result) = rx.recv() {
// let normalized_path = Path::new(&file_result.path_name)
// .components()
// .map(|comp| comp.as_os_str().to_string_lossy())
// .collect::<Vec<_>>()
// .join(&MAIN_SEPARATOR.to_string());
let normalized_path = file_result.path_name;
// 输出路径
stdout.set_color(&line_num_color).unwrap();
write!(&mut stdout, "\n\"{}\":\n", normalized_path).unwrap();
stdout.reset().unwrap();
// 最大行号
let max_width = file_result.line_count.to_string().len();
println!("\x1b[35m{:?} :\x1b[0m", normalized_path);
for (line_num, line_str) in file_result.matches {
println!("\x1b[32m{:>width$} |\x1b[0m {}", line_num, line_str, width = max_width);
for mat in file_result.matches {
// 🎨 打印高亮行号并精准对齐
stdout.set_color(&line_num_color).unwrap();
write!(&mut stdout,"{:>width$} | ", mat.line_num, width = max_width).unwrap();
stdout.reset().unwrap();
let mut last_idx = 0;
for (start,end) in mat.sub_matches {
// 先打印普通文本
write!(&mut stdout, "{}", &mat.content[last_idx..start]).unwrap();
// 打印关键字
stdout.set_color(&keyword_color).unwrap();
write!(&mut stdout, "{}", &mat.content[start..end]).unwrap();
stdout.reset().unwrap();
// 移动起始索引
last_idx = end;
}
// 关键字列表外可能还有内容,以普通样式打印
writeln!(&mut stdout, "{}", &mat.content[last_idx..]).unwrap();
}
println!();
}
}).unwrap()
}
@@ -59,20 +96,21 @@ pub fn search_in_file(query: &str, path: &Path, ignore_case: bool) -> Option<Fil
Ok(text) => text,
Err(_) => return None
};
// 🌟 2. 跨模块调用:通过包名或相对路径调用 searcher 里的函数
let results = searcher::search(query, &contents, ignore_case);
if !results.is_empty() {
let mut matches_vec = Vec::new();
for (line_num, line_str) in results {
matches_vec.push((line_num, line_str.to_string()));
}
let file_line_count = contents.lines().count();
Some(FileResult {
path_name: path.to_string_lossy().replace("\\","/"),
matches: matches_vec,
matches: results.into_iter()
.map(|(line_num, line_str, sub_matches)|MatchLine {
line_num,
content: line_str.to_string(),
sub_matches
})
.collect(),
line_count: file_line_count
})
} else {
+13 -3
View File
@@ -1,11 +1,21 @@
/// 核心搜索函数 <'a> 是显式生命周期注解
pub fn search<'a>(query: &str, contents: &'a str, ignore_case: bool) -> Vec<(usize, &'a str)> {
pub fn search<'a>(query: &str, contents: &'a str, ignore_case: bool) -> Vec<(usize,&'a str, Vec<(usize, usize)>)> {
contents
.lines()
.enumerate()
.map(|(idx, line)| (idx + 1, line))
.filter(|(_, line)| {
if ignore_case { line.to_lowercase().contains(&query) } else { line.contains(query) }
.filter_map(|(line_num, line)|{
let haystack = if ignore_case { line.to_lowercase() } else { line.to_string() };
if haystack.contains(query) {
let sub_matches: Vec<(usize,usize)> = haystack
.match_indices(query)
.map(|(start_idx,_)| (start_idx, start_idx+query.len()))
.collect();
Some((line_num,line,sub_matches))
} else {
None
}
})
.collect()
}
+7 -1
View File
@@ -6,6 +6,12 @@ pub use filter::{ should_skip_dir, is_valid_extension };
pub struct FileResult {
pub path_name: String,
pub matches: Vec<(usize, String)>,
pub line_count: usize,
pub matches: Vec<MatchLine>,
}
pub struct MatchLine {
pub line_num: usize,
pub content: String,
pub sub_matches: Vec<(usize, usize)>
}