65.9K
CodeProject 正在变化。 阅读更多。
Home

源代码超级搜索

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.71/5 (21投票s)

2008年10月8日

CPOL

8分钟阅读

viewsIcon

54875

downloadIcon

1518

一个用于搜索源代码目录的简单解决方案

此信息适用于 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 函数

  1. 使用 Process.Start 运行 dir 命令
  2. 使用 StreamReader 匹配输出文件中的每一行中的 RegEx 模式
  3. 构建一个 SearchResult 对象,List<T>
  4. 使用结果填充 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

  1. 基于 Directory.GetFiles 的结果,使用 StreamWriter 生成一个输出文件
  2. 使用 StreamReader 匹配输出文件中的每一行中的 RegEx 模式
  3. 构建一个 SearchResult 对象 List<T>
  4. 使用结果填充 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
© . All rights reserved.