转自: http://blog.csdn.net/chenyufei1013/article/details/6055093
Source Insight一直在用,windows下不错的源代码查看。同时,经过简单的配置,也可以作为不错的代码编辑工具来使用(参见下图)。
在使用的过程中,我发现在如下的场景下,使用极为不方便(我用的是3.50.0064英文版):
我们从项目文件列表(Project File List)中,输入文件名打开文件后,希望能跳转到文件所在的目录,最好是在项目文件浏览器(Project File Browser)中显示该文件夹的位置。
这个功能貌似source insight没有提供,而且,也没谷歌到。通常解决的办法是复制文件名到资源管理器中,想办法在资源管理器中打开对应的文件夹。
于是乎,经过简单的思考,周末化了两天时间来写了个插件,通过配置,就可以很方便的使用该功能。
一、下载及配置
下载该插件可以到这里,其中包含了插件使用的简要说明。为了方便起见,这里用图片说明一下吧。
首先,打开source insight的Base工程,选择菜单Project->Add and Remove Project Files…,将插件包中的SIToFolder.em添加到Base工程。
其次,将文件SIToFolder.exe拷贝到D盘下。
第三,选择菜单Options->Key Assignments…,将命令(command)Macro: ToProjectFileBrowserFolder的快捷键设置为Ctrl+T。
你也可以将Macro: ToProjectFileBrowserFolder命令添加到菜单中,然后通过菜单来使用该命令。
第四,点击你打开的文件,按下Ctrl+T,你会发现自动显示了项目文件浏览器(Project File Browser)窗口,并跳转到当前文件所在的目录。
第五,你可以使用同样的方式调用宏Macro: ToExplorerFolder,它的作用是在资源管理器中打开当前文件所在的目录。
二、实现原理
这个插件的实现并不是很复杂,关键是它做到了我们需要的。
具体的实现原理我会在后面的文章中介绍。
Source Insight跳转插件用了一段时间,发现一个bug。就是在如下的情况下无法正常跳转:打开某个文件夹中的文件以后,切换至其它文件夹中的文件,执行跳转命令还是会打开下图所示选择的文件。出现这个状态的原因是:文件浏览器支持通过文件名过滤选择下面列出的文件。解决办法,下面有提到。
我们知道,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那样的编译器,它们太大了,以至于我们不敢去触摸它。