输出色彩重整,添加关键字高亮
This commit is contained in:
Generated
+10
@@ -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"
|
||||
|
||||
@@ -13,3 +13,4 @@ strip = true # 自动剥离所有的调试信息和符号表(相当于
|
||||
[dependencies]
|
||||
walkdir = "2" # 目录递归遍历库
|
||||
crossbeam = "0.8" # crossbeam 线程池
|
||||
termcolor = "1.4" # 颜色管理
|
||||
|
||||
+55
-17
@@ -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
@@ -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
@@ -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)>
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user