源代码超级搜索






4.71/5 (21投票s)
一个用于搜索源代码目录的简单解决方案

此信息适用于 v 0.9 版本。最新版本 v 1.0 在 03-2009 更新部分讨论。
引言
此工具是我开发的一个简单的搜索实用程序,用于搜索我所有的源代码,无论何种语言。该工具与 Visual Studio 2008 集成,因此您可以在编写代码时启动搜索。该实用程序将搜索指定目录中的文件,以查找匹配扩展名的文件,并且它将在匹配的文件中匹配搜索字符串(支持正则表达式)。结果以干净的树状视图格式呈现,并允许您对结果执行各种操作。
背景
我每天大约有 80% 的时间都在电脑屏幕前为客户编写代码,其中很多时间都花在我过去编写过的旧代码中,试图回忆我之前是如何解决某个问题的,以便将其应用到当前项目中。我希望我能为这项工作设定一个精确的时间值,但最接近的值是很多。我早就认为我需要一个 GREP 类型的应用程序,可以用来搜索包含数千个源代码文件的大型目录层次结构。市面上有很多 Windows Grep 程序,我也尝试过其中一些(免费的)。最终,对于我的需求来说,使用其中的许多程序都显得过于繁琐和笨拙,而且我想要一些专门针对 Visual Studio IDE 程序员的功能,例如能够打开与匹配的代码文件关联的 *.sln 解决方案文件,这在一个“万能”型的 Grep 应用程序中是无法实现的。
Visual Studio 的“在文件中查找”选项
当然,Visual Studio 已经提供了一个强大的搜索功能 (CTRL+SHIFT+F),它允许您根据扩展名和用户定义的搜索字符串在文件中搜索模式。此工具的功能模仿了 Visual Studio 功能的功能,但增加了一些附加选项,例如能够(尝试定位并)打开与匹配的代码文件关联的 *.sln 文件,以及能够打开包含匹配文件的文件夹。此工具可以搜索和匹配文件名中的模式,以及文件中的模式。
感谢 Drew Stainton 提供此提示:您可以通过编辑 Visual Studio 的注册表设置来进一步自定义“在文件中查找”Visual Studio 函数的行为:自定义您的“在文件中查找”结果体验!
在 Visual Studio 外部使用
此解决方案作为一项附加功能集成到 Visual Studio 中,因此您可以在 Visual Studio 中工作时启动它,但它不依赖于 Visual Studio 的任何方面。它可以独立运行,并且可以用于搜索任何代码(或任何可以在记事本中读取的文件)。我已经用它来搜索 VBScript、批处理和 Perl 脚本,以及搜索文件服务器日志文件。
项目目标
项目的最终目标是显著减少查找代码文件的工作量。解决方案需要尽可能快地搜索文件系统,并提供一个用户可以交互的简单输出。
总结
- 查找与指定扩展名和搜索模式匹配的文件名
- 查找文件中与指定搜索模式和指定扩展名匹配的值
- 搜索速度需要尽可能快
- 解决方案需要在会话之间持久化用户定义的数据
匹配需要允许这些行为
- 打开文件
- 在记事本中打开文件
- 打开包含的文件夹
- 打开代码文件的所有者解决方案文件 (*.sln)
索引还是不索引
这是我长时间以来一直纠结的问题。如果我索引一个位置的所有文件,那么搜索结果将是即时的。这将使用户非常满意,但会给解决方案增加很多开销。最终,我决定,对于这个实用程序,每次搜索都是实时的,并且所有操作都将在搜索时执行,而不是在执行前进行索引。
搜索方法
有很多方法可以执行搜索。过去,基准测试似乎表明使用...
string[] myFiles = Directory.GetFiles
(USER_PATH, FILEMASK, SearchOption.AllDirectories);
...比使用命令行函数慢得多
c:\>dir /S /B USER_PATH\*.FILEMASK > ApplicationAppDataDirectory\cache.txt
然而,我不再确定这一点了。我针对这两种场景进行了一系列完全非科学的基准测试。
场景 A:Command.com dir 函数
- 使用
Process.Start
运行 dir 命令 - 使用
StreamReader
匹配输出文件中的每一行中的 RegEx 模式 - 构建一个
SearchResult
对象,List<T>
- 使用结果填充
TreeView
控件
Path | FileMasks | 模式 | SType | Found | Searched | 时间 |
c:\ | .log | [Ll]aunching | In Files | 58 | 1525 | 34 sec |
VS2005\Projects | .cs .vb | DirectoryEntry | In Files | 70 | 3081 | 13 sec |
VS2005\Projects | .cs | DirectoryInfo | In Files | 57 | 3037 | 7 |
\\baileyfs01\Music | .mp3 | Megadeth | 文件名 | 225 | 14,903 | 8 |
场景 B:Directory.GetFiles
- 基于
Directory.GetFiles
的结果,使用StreamWriter
生成一个输出文件 - 使用
StreamReader
匹配输出文件中的每一行中的 RegEx 模式 - 构建一个
SearchResult
对象List<T>
- 使用结果填充
TreeView
控件
Path | FileMasks | 模式 | SType | Found | Searched | 时间 |
c:\ | .log | [Ll]aunching | In Files | 58 | 1525 | 57 sec |
VS2005\Projects | .cs .vb | DirectoryEntry | In Files | 70 | 3081 | 14 sec |
VS2005\Projects | .cs | DirectoryInfo | In Files | 57 | 3037 | 7 |
\\baileyfs01\Music | .mp3 | Megadeth | 文件名 | 225 | 14,903 | 13 |
这些基准测试是在一台配备 3GB RAM 和 Q6600 四核处理器的 Vista 机器上运行的。正如您从上面的统计数据中看到的,结果稍微倾向于运行 DIR 命令而不是 Directory.GetFiles()
。

然而,公平地说,让 Directory.GetFiles
将结果写入文件并没有多大意义,因为实际上,结果已经在一个 Array
中了。
目前,我已弃用 DIR 搜索功能,同时我正在测试使用 GetFiles()
的所有方法。本文可交付成果中包含的版本没有使用 DIR,但为了进行进一步的基准测试,仍然保留了可以重新启用它的类。更新:我从日常使用中注意到,每天第一次运行应用程序时,执行第一次搜索需要很长很长的时间,但这并没有成为一个足够的烦恼让我去寻找其他方向。
CommandLine
引擎包含在一个名为 CommandLine.cs 的类中,去除所有抽象成员(字段、属性、构造函数等),它看起来像这样
private void DoShellExecute()
{
ProcessStartInfo psi =
new ProcessStartInfo(m_ApplicationPath, m_ApplicationParameters);
psi.UseShellExecute = true;
psi.RedirectStandardOutput = false;
psi.CreateNoWindow = true;
psi.WindowStyle = ProcessWindowStyle.Normal;
if (ExecuteType == ExecutionType.SHELL_EXECUTE_HIDDEN)
psi.WindowStyle = ProcessWindowStyle.Hidden;
Process p_exec = new Process();
try
{
p_exec = Process.Start(psi);
if (m_TimeOutThreshold != null || m_TimeOutThreshold == 0)
{
p_exec.WaitForExit((int)m_TimeOutThreshold);
}
else
{
p_exec.WaitForExit();
}
}
catch (SystemException e)
{
m_Error = e;
}
finally
{
p_exec.Close();
}
}
另一方面,GetFiles()
路线不需要自己的类型,所以我们在 IOWorker
类中直接使用它。
/// <summary>
/// Creates the index of the user defined root path
/// </summary>
public void CreateIdx()
{
foreach (string f in m_uso.filters)
{
try
{
m_idx.AddRange(Directory.GetFiles(
m_uso.path, "*" + f,
SearchOption.AllDirectories));
}
catch (UnauthorizedAccessException e)
{
System.Diagnostics.Debug.WriteLine(e);
}
}
//m_idx.Reverse();
m_SearchedCount = m_idx.Count;
SearchResults(m_uso.pattern);
}
添加到 Visual Studio 2005/2008
本文包含源代码以及二进制应用程序文件。如果您希望将此应用程序添加到 Visual Studio 2008 的“工具”菜单中,请将 VS2008 插件 zip 文件中的所有文件解压缩到 Visual Studio 用户路径的 Addins 文件夹中(c:\Users\You\Visual Studio 2008\Addins)。

如前所述,该程序与 Visual Studio 无关,因此您可以直接运行 *.exe 文件来独立运行该程序。
~~ 更新 03-2009 ~~
正则表达式
最近我有一个需求,需要从一个旧网站的每个页面中删除一个代码块。该网站完全由 HTM、HTML 和 PHP 文件组成,总共有大约 3000 个!代码块看起来像这样
<script language="javascript">
var breakThis = "http://customerdomain/cfaq/browse.php"; //break frames
if (window != parent) parent.location.href = breakThis;
//-->
</script>
该工具的先前版本对每个文件中的每一行执行搜索。为了匹配跨越多行的string
,我添加了一个选项,将整个FileStream
视为单个string
。新的选项**搜索所有行在一起**,允许您一次性搜索整个文件,以匹配整个文件中的模式。此外,现在还有一个**忽略大小写**标志,因此搜索不再区分大小写。
在文件中替换
根据上述需求,我必须找到脚本块在所有 3000 多个网页文件中并删除它们。现在提供了一个新的**替换**功能,允许您将 RegEx 搜索中匹配的string
替换为新的string
(或者在我这个例子中是空string
)。
撤销在文件中替换
当您启动替换操作时,原始文件(在搜索操作中匹配的文件)会被备份并赋予 *.orig 扩展名。如果您发现不应该对 3000 个文件执行自动的 Regex.Replace
操作,您可以撤销替换操作。撤销功能有效地删除已修改的文件,并将 *.orig 文件恢复到它们作为原始文件的状态。
报告/导出
现在提供了一个新的报告功能,允许您导出每次搜索的结果。
结论
到目前为止,我对这个项目的成果非常满意。我发现自己全天都在使用这个搜索工具,它使我日常的工作更加轻松。在使用它几天后,我正在考虑将其索引到服务器,以及它可能为查找代码块带来的可能性。
历史
- 2008 年 10 月 8 日 - 文章提交
- 2008 年 10 月 8 日 - 修正了关于现有 VS 功能的不正确陈述
- 2009 年 3 月 9 日 - 发布 v 1.0