SI 跳转 插件的实现

      Source Insight跳转插件用了一段时间,发现一个bug。就是在如下的情况下无法正常跳转:打开某个文件夹中的文件以后,切换至其它文件夹中的文件,执行跳转命令还是会打开下图所示选择的文件。出现这个状态的原因是:文件浏览器支持通过文件名过滤选择下面列出的文件。解决办法,下面有提到。

SI 跳转 插件的实现_第1张图片

      我们知道,Source Insight是支持宏(macro)的代码查看工具。通过,可以方便的扩展Source Insight的功能,具体的文档可以参见这里。从文档中我们可以发现:

      1. SI宏实际上是一种类C的脚本语言,只是它的功能仅局限于扩充SI的功能。计算机语言不仅可以用来写应用,还可以用来做更多的事情,那么编译原理啥的就有用了。

      2. SI宏只能扩充操作源代码的功能。比如:删除源文件,为源代码加注释、文件头等。但是,你要是想给SI的窗口增加功能估计没戏。正是出于这一点,“跳转”插件才没法完全用SI宏来完成。

      编写跳转插件主要是由于:这个功能可以为我节省一些时间;同时,在网上又没找到合适的可以使用。最开始我想到了宏,但是我注意到了上面第2点的限制,于是就想到了通过在SI外部的方式实现。这让我想到了Windows 7下VS2008升级补丁,它的思路是运行补丁程序用来显示隐藏的VS2008注册控件。简单的思索之后我就开始了跳转插件代码的实现工作。

      整个插件的大致执行流程如下:

   1: macro ToProjectFileBrowserFolder()
   2: {
   3:     cmdOpenProjectFileBrowser = "Project File Browser";
   4:     if (IsCmdEnabled(cmdOpenProjectFileBrowser)) {
   5:         RunCmd(cmdOpenProjectFileBrowser);
   6:     }
   7:     else {
   8:         Msg("无法打开Project File Browser面板!");
   9:         return;
  10:     }
  11:  
  12:     curPrjName = GetCurrentProjectName();
  13:     if (curPrjName == hNil) {
  14:         return;
  15:     }
  16:  
  17:     curFileDir = GetCurrentFileDir();
  18:     if (curFileDir == hNil) {
  19:         return;
  20:     }
  21:  
  22:     cmdLine = "SIToFolder.exe /"@curPrjName@ Project/"  /"@curFileDir@/"";
  23:     RunCmdLine(cmdLine, Nil, 0);
  24: }

      首先,打开项目文件浏览器窗口。然后,获取当前文件所在的文件夹路径。最后,通过VC程序将路径传至项目文件浏览器的输入框,然后,发送回车键消息,便可在项目文件浏览器中打开对应的文件夹。具体的细节如下:

      打开项目文件浏览器窗口可以使用函数RunCmd,并传递SI的命令"Project File Browser"。但是,要判断该命令是否可用(IsCmdEnabled),因为只有在有源代码工程打开时才可用。

      接下来,就是获取项目(Project)的名称,获取项目的名称的目的是用来区分不同的SI窗口,因为有可能多个SI窗口打开,它是通过以下宏来实现,添加了注释,不明白的地方,可以参照SI文档。

   1: macro GetCurrentProjectName()
   2: {
   3:     // 获取当前项目的句柄
   4:     hprj = GetCurrentProj();
   5:     if (hprj == hNil) {
   6:         Msg("当前无项目(Project)打开!");
   7:         return hNil;
   8:     }
   9:  
  10:     // 获取项目的名称,注意它是指SI工程所在的路径,需要截取
  11:     curPrjPath = GetProjName(hprj);
  12:     pathLen = strlen(curPrjPath);
  13:     if (pathLen <= 0) {
  14:         Msg("无法获取项目(Project)路径!");
  15:         return hNil;
  16:     }
  17:  
  18:     // 取项目名,实际上是取SI工程所在的文件夹名
  19:     index = pathLen - 1;
  20:     while (index >= 0) {
  21:         if (curPrjPath[index] == "//") {
  22:             curPrjName = strmid(curPrjPath, index + 1, pathLen);
  23:             break;
  24:         }
  25:         else {
  26:             index = index - 1;
  27:         }
  28:     }
  29:  
  30:     // 返回项目名
  31:     return curPrjName;
  32: }

      然后,就是获取当前打开文件所在的文件夹。它是通过以下的宏来实现的,也很简单,就不多介绍了。

   1: macro GetCurrentFileDir()
   2: {
   3:     // 获取当前文件buffer的句柄
   4:     buf = GetCurrentBuf();
   5:     if (hNil == buf) {
   6:         Msg("当前无文件打开!");
   7:         return hNil;
   8:     }
   9:  
  10:     // 获取当前文件的全路径
  11:     curFilePath = GetBufName(buf);
  12:     pathLen = strlen(curFilePath);
  13:     if (pathLen <= 0) {
  14:         Msg("无法获取当前文件路径!");
  15:         return hNil;
  16:     }
  17:  
  18:     // 截取所在文件夹路径
  19:     index = pathLen - 1;
  20:     while (index >= 0) {
  21:         if (curFilePath[index] == "//") {
  22:             curFileDir = strmid(curFilePath, 0, index);
  23:             break;
  24:         }
  25:         else {
  26:             index = index - 1;
  27:         }
  28:     }
  29:  
  30:     // 返回当前文件所在的文件夹路径
  31:     return curFileDir;
  32: }

    再然后,是通过执行外部的VC程序,来完成剩余的步骤。VC程序中首要任务是查找SI的窗口,可以通过如下代码实现,SI窗口类的名称可以通过VS的Spy++程序来获取。

   1: HWND FindSourceInsightWindow(TCHAR * projectName)
   2: {
   3:     TCHAR siCaption[MAX_PATH] = {0};
   4:     HWND hWnd = NULL;
   5:  
   6:     do {
   7:         // 查找SI窗口
   8:         hWnd = ::FindWindowEx( NULL, hWnd, L"si_Frame", NULL );
   9:         if (hWnd == NULL) {
  10:             break; // not found.
  11:         }
  12:         
  13:         // 获取SI窗口的标题
  14:         ::GetWindowText(hWnd, siCaption, MAX_PATH);
  15:  
  16:         // 查看是否包含指定的工程名称
  17:         if (wcsstr(siCaption, projectName) != NULL) {
  18:             return hWnd;
  19:         }
  20:     } while (TRUE);
  21:  
  22:     return NULL;
  23: }

      找到窗口以后,就要找到ComboBox输入框。可以参见如下代码,只是参照Spy++给出的结果而已。

   1: HWND hWnd = NULL;
   2: hWnd = ::GetTopWindow( hSIWnd ); // MDIClient
   3: for (int i = 0; i < 4; ++i) {
   4:     hWnd = ::GetNextWindow( hWnd, GW_HWNDNEXT ); // si_DockFrame
   5: }
   6:  
   7: hWnd = ::GetTopWindow( hWnd ); // "Mobile Service Project" si_LW
   8: hWnd = ::GetTopWindow( hWnd ); // ComboBox
   9:  
  10: // ComboBox to enter folder path.
  11: HWND hWndComboBox = hWnd;

      发送文件夹路径到ComboBox窗口:

   1: SendMessage(hWndComboBox, WM_SETFOCUS, NULL, 0);
   2: SendMessage(hWndComboBox, (UINT)CB_RESETCONTENT, 0, 0);
   3: SendMessage(hWndComboBox, WM_SETTEXT, MAX_PATH, (LPARAM)path);

      定位到ComboBox的末端,模拟输入空格键并等待50毫秒,这样就好像真的输入空格一样,用来处理上面提到的Bug。

   1: LPARAM len = (LPARAM)wcslen(path);
   2: len += len << 16;
   3: SendMessage(hWndComboBox, CB_SETEDITSEL, 0, len);
   4: PostMessage(hWndComboBox, WM_KEYDOWN, VK_SPACE, 0);
   5: Sleep(50);

      最后,发送回车消息。

   1: PostMessage(hWndComboBox, WM_KEYDOWN, VK_RETURN, 0);

      VC这部分的实现可以参考MSDN。

      跳转插件本身不复杂,但是,它让我觉得,我本可以用代码做更多的事情的。特别是,SI的宏语言让我觉得,语言还可以有其它用途。我们不一定非得写一个想C、Java那样的编译器,它们太大了,以至于我们不敢去触摸它

你可能感兴趣的:(浏览器,null,文档,语言,Path,browser)