本文承接自VSIX:C#项目 重命名所有标识符(Visual Studio扩展开发)-CSDN博客,对VS扩展开发完全一无所知的可以先看这个。
代码其实很简单,几个辅助函数,一个主流程而已。主要的困难在于需要了解VSIX的开发模型,如果不专门做这个,很难了解透彻。我也没找到很好的学习材料,只能说,现在的代码确实是可以工作的,都是碎知识。
namespace VSIXProjectShare
{
public sealed class CommandShare
{
private readonly AsyncPackage package;
enum ProjectType { VC,CSharp,OTHER};//项目类型
ProjectType projectType;
private Random r ;//随机数
private string new_name_title;//新名称标题
private long count = 0;//顺序编号
public CommandShare(AsyncPackage _package)
{
package = _package;
Log("初始化插件");
r = new Random();
new_name_title = "_ASDFGHJKL_" + r.Next().ToString() + "_";
}
AsyncPackage package,这个是每个扩展都有的,自动生成的命令框架就有,照着移过来了。在这个文件里,只用于显示消息框。
ProjectType,我自己定义的,是根据项目的Language的GUID一样的串来判断的。我觉得这样不是很科学,但是没找到别的办法。
其它几个变量就是自动重命名时用来生成随机名称的。
//显示消息对话框
private void ShowMessageBox(string title, string message)
{
VsShellUtilities.ShowMessageBox(
package,
message,
title,
OLEMSGICON.OLEMSGICON_INFO,
OLEMSGBUTTON.OLEMSGBUTTON_OK,
OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST);
}
//输出日志
private void Log(string msg)
{
Log(0,msg);
}
private void Log(int level,string msg)
{
ThreadHelper.ThrowIfNotOnUIThread();
IVsOutputWindowPane pane = (IVsOutputWindowPane)Package.GetGlobalService(typeof(SVsGeneralOutputWindowPane));
int tmp = pane.Activate();
if (VSConstants.S_OK != tmp)
{
ShowMessageBox("注意", "未能激活输出窗口 " + tmp.ToString());
}
for (int i = 0; i < level; ++i)
{
pane.OutputStringThreadSafe(" ");
}
pane.OutputStringThreadSafe(msg + "\r\n");
}
显示消息框用的是VsShellUtilities.ShowMessageBox,直接从框架代码贴过来的。
日志输出到VS的输出窗口,获取到后输出字符串就行了。上面的代码给日志加了缩进格式。
public void Execute()
{
ThreadHelper.ThrowIfNotOnUIThread();
string message = string.Format(CultureInfo.CurrentCulture, "Inside {0}.MenuItemCallback()", this.GetType().FullName);
string title = "Command1 2023-04-20 1720";
// Show a message box to prove we were here
//VsShellUtilities.ShowMessageBox(
// this.package,
// message,
// title,
// OLEMSGICON.OLEMSGICON_INFO,
// OLEMSGBUTTON.OLEMSGBUTTON_OK,
// OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST);
Log(title);
try
{
DTE2 dte = (DTE2)Package.GetGlobalService(typeof(SDTE));
Log("DTE:" + dte.Version);
Log("DTE:" + dte.Name);
Log("DTE:" + dte.Edition);
Log("DTE:" + dte.Mode);
var solution = dte.Solution;
var SolutionName = Path.GetFileName(solution.FullName); //解决方案名称
var SolutionPath = Path.GetDirectoryName(solution.FullName);//解决方案路径
Log("解决方案:" + solution.ToString());
Log("解决方案FileName:" + solution.FileName);
Log("解决方案FullName:" + solution.FullName);
Log("解决方案GetFileName:" + SolutionName);
Log("解决方案GetDirectoryName:" + SolutionPath);
Log("解决方案Count:" + solution.Count);
Log("解决方案Projects.Count:" + solution.Projects.Count);
foreach (Project current_project in solution.Projects)
{
//解决方案下的项目
Log(1, "--------------------------Language:" + current_project.CodeModel.Language);
if (current_project.CodeModel.Language == "{B5E9BD34-6D3E-4B5D-925E-8A43B79820B4}")
{
projectType = ProjectType.CSharp;
}
else if (current_project.CodeModel.Language == "{B5E9BD32-6D3E-4B5D-925E-8A43B79820B4}")
{
projectType = ProjectType.VC;
}
else
{
projectType = ProjectType.OTHER;
}
Log(1, "--------------------------项目:" + current_project.Name + " 类型 " + projectType + " 项目子项个数:" + current_project.ProjectItems.Count.ToString());
foreach (ProjectItem current_project_item in current_project.ProjectItems)
{
ProcessProjectItem(2, current_project_item);
}
}
ShowMessageBox(title, "操作完成");
}
catch (Exception ex)
{
ShowMessageBox("", ex.Message);
}
}
这个总入口展示了如果从解决方案开始往下遍历。
DTE2是VS模型的顶级对象,一切从获取顶级对象开始:
DTE2 dte = (DTE2)Package.GetGlobalService(typeof(SDTE));
然后可以获取解决方案:
var solution = dte.Solution;
解决方案的类型其实就是Sloution。var啊、auto啊什么的这些东西,其实也是有利有弊的,写的时候爽,读的时候费劲。
然后遍历解决方案下的项目:
foreach (Project current_project in solution.Projects)
因为项目类型不同处理方式可能很不一样(语言结构就不一样),所以先判断了项目类型,前面已经说了,这个判断方法不理想,但是可以用:
//解决方案下的项目
Log(1, "--------------------------Language:" + current_project.CodeModel.Language);
if (current_project.CodeModel.Language == "{B5E9BD34-6D3E-4B5D-925E-8A43B79820B4}")
{
projectType = ProjectType.CSharp;
}
else if (current_project.CodeModel.Language == "{B5E9BD32-6D3E-4B5D-925E-8A43B79820B4}")
{
projectType = ProjectType.VC;
}
else
{
projectType = ProjectType.OTHER;
}
Log(1, "--------------------------项目:" + current_project.Name + " 类型 " + projectType + " 项目子项个数:" + current_project.ProjectItems.Count.ToString());
这两个长字符串不是哪里查到的,是直接输出来看的。
注意,这里的“项”不一定是文件,是VS里项目下的那些东西,属性、资源等等都是,所以后面处理要逐个根据类型来判断。
foreach (ProjectItem current_project_item in current_project.ProjectItems)
{
ProcessProjectItem(2, current_project_item);
}
对每个项执行ProcessProjectItem方法。
这个方法的功能是判断项的类型,决定如何去做。
private void ProcessProjectItem(int level, ProjectItem projectItem)
{
ThreadHelper.ThrowIfNotOnUIThread();
//项目下的筛选器
Log(level, "===============目录:" + projectItem.Name + " 项目子项FileCount:" + projectItem.FileCount.ToString());
for (short i = 0; i < projectItem.FileCount; i++)
{
Log(3, "文件名:" + projectItem.FileNames[i]);
}
if (projectType == ProjectType.CSharp && projectItem.Name == "Properties")
{
Log(3, "C#项目忽略属性目录");
return;
}
if (null != projectItem.FileCodeModel)
{
String language = "未知语言";
switch (projectItem.FileCodeModel.Language)
{
case CodeModelLanguageConstants.vsCMLanguageVC:
language = "VC";
AddFunction_myToString(5, projectItem.FileCodeModel.CodeElements as VCCodeElements);
break;
case CodeModelLanguageConstants.vsCMLanguageIDL:
language = "IDL";
Log(3, "未支持的语言 " + language);
break;
case CodeModelLanguageConstants.vsCMLanguageVB:
language = "VB";
Log(3, "未支持的语言 " + language);
break;
case CodeModelLanguageConstants.vsCMLanguageMC:
language = "MC";
Log(3, "未支持的语言 " + language);
break;
case CodeModelLanguageConstants.vsCMLanguageCSharp:
language = "CSharp";
Log(3, "语言 " + language);
if (null == projectItem) Log(3, "语言1" + language);
if (null == projectItem.FileCodeModel) Log(3, "语言 2" + language);
if (null == projectItem.FileCodeModel.CodeElements) Log(3, "语言 3" + language);
Log(3, "语言 " + language);
CSharp_Rename(5, projectItem.FileCodeModel.CodeElements);
break;
}
}
foreach (ProjectItem current_project_item_item in projectItem.ProjectItems)
{
ProcessProjectItem(level + 1, current_project_item_item);
}
}
if (projectType == ProjectType.CSharp && projectItem.Name == "Properties")
{
Log(3, "C#项目忽略属性目录");
return;
}
直接用Name来判断的,这个以后会不会变,不知道,反正现在如此。或许有更科学的做法。
另:这个代码之前输出了FileNames,这个有啥用还不太清楚。
后面就是根据项目类型调用处理方法,对C#,调用CSharp_Rename方法:
CSharp_Rename(5, projectItem.FileCodeModel.CodeElements);
CodeElements是源代码构造的语言对象的集合,也就是我们要处理的东西。
最后就是递归调用子项:
foreach (ProjectItem current_project_item_item in projectItem.ProjectItems)
{
ProcessProjectItem(level + 1, current_project_item_item);
}
终于到实际的功能了:
private void CSharp_Rename(int level, CodeElements codeElements)
{
ThreadHelper.ThrowIfNotOnUIThread();
foreach (CodeElement _element in codeElements)
{
Log(level, "Kind " + _element.Kind.ToString());
string name = "未知";//Name属性不是每个都有
if (_element.Kind == vsCMElement.vsCMElementImportStmt)
{
name = "vsCMElementImportStmt";
}
else
{
name = _element.Name;//这个竟然不是每个都支持
}
CodeElement2 element = (CodeElement2)_element;
Log(level, "Kind " + element.Kind.ToString() + " Name " + name + " type " + element.GetType().ToString());
//处理子项
if (0 != element.Children.Count)
{
CSharp_Rename(level + 1, element.Children);
}
bool skip = false;//是否需要跳过
//检查是否已经处理过
if (name.StartsWith(new_name_title))
{
skip = true;
}
if (element.Kind == vsCMElement.vsCMElementVariable)
{
CodeVariable variable = (CodeVariable)element;
Log(level + 1, "变量 Name " + variable.Name
+ " StartPoint " + variable.StartPoint.Line + " " + variable.StartPoint.LineCharOffset
+ " EndPoint " + variable.EndPoint.Line + " " + variable.EndPoint.LineCharOffset);
}
else if (element.Kind == vsCMElement.vsCMElementFunction)
{
Log(level + 1, "函数 " + name);
if (name.Equals("Main"))
{
Log(level + 1, "Main函数(跳过) " + name);
skip = true;
}
if (name.Equals("Dispose"))
{
Log(level + 1, "Dispose函数(跳过) " + name);
skip = true;
}
}
else if (element.Kind == vsCMElement.vsCMElementNamespace)
{
Log(level + 1, "命名空间 " + name);
//skip = true;
}
else if (element.Kind == vsCMElement.vsCMElementAttribute)
{
Log(level + 1, "属性(跳过) " + name);
skip = true;
}
else if (element.Kind == vsCMElement.vsCMElementImportStmt)
{
Log(level + 1, "导入语句(跳过) " + name);
skip = true;
}
else if (element.Kind == vsCMElement.vsCMElementOther)
{
Log(level + 1, "vsCMElementOther(跳过) " + name);
skip = true;
}
if (!skip)
{
Log(level, "重命名 " + name + "(" + element.Kind.ToString() + ") 为 " + new_name_title + count.ToString());
element.RenameSymbol(new_name_title + count.ToString());
count++;
Log(level, "重命名完成");
}
}
}
foreach (CodeElement _element in codeElements)
{
//......
//处理子项
if (0 != element.Children.Count)
{
CSharp_Rename(level + 1, element.Children);
}
//......
}
代码里面有这句:
CodeElement2 element = (CodeElement2)_element;
这两个类型确实是不一样的,虽然觉得困惑,但是这样才能执行。
对每个元素判断类型,element.Kind,属性和导入语句等不需要处理。
特殊方法也不能处理,比如“Main”和“Dispose”,根据需要判断_element.Name(代码赋值给了name变量)即可。
重命名使用element.RenameSymbol即可,C#实在是厉害多了。
element.RenameSymbol(new_name_title + count.ToString());
给C++用的AddFunction_myToString函数就不解释了,差不多,再说也没写得很理想。
(这里是结束)