VSIX:C#项目 重命名所有标识符(Visual Studio扩展开发)代码详解

       本文承接自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的输出窗口,获取到后输出字符串就行了。上面的代码给日志加了缩进格式。

三、主入口

3.1 总代码

        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);
            }
        }

        这个总入口展示了如果从解决方案开始往下遍历。

3.2 获取顶级对象DTE2

        DTE2是VS模型的顶级对象,一切从获取顶级对象开始:

DTE2 dte = (DTE2)Package.GetGlobalService(typeof(SDTE));

 3.3 获取解决方案

        然后可以获取解决方案:

 var solution = dte.Solution;

        解决方案的类型其实就是Sloution。var啊、auto啊什么的这些东西,其实也是有利有弊的,写的时候爽,读的时候费劲。

3.4 获取所有项目

        然后遍历解决方案下的项目:

                foreach (Project current_project in solution.Projects)

 3.5 判断项目类型

        因为项目类型不同处理方式可能很不一样(语言结构就不一样),所以先判断了项目类型,前面已经说了,这个判断方法不理想,但是可以用:

                    //解决方案下的项目
                    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());

        这两个长字符串不是哪里查到的,是直接输出来看的。

3.6 遍历项目的每一个项

        注意,这里的“项”不一定是文件,是VS里项目下的那些东西,属性、资源等等都是,所以后面处理要逐个根据类型来判断。

                    foreach (ProjectItem current_project_item in current_project.ProjectItems)
                    {
                        ProcessProjectItem(2, current_project_item);
                    }

        对每个项执行ProcessProjectItem方法。

四、ProcessProjectItem方法

4.1 总代码

        这个方法的功能是判断项的类型,决定如何去做。

        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);
            }
        }

4.2 C#项目忽略属性目录

            if (projectType == ProjectType.CSharp && projectItem.Name == "Properties")
            {
                Log(3, "C#项目忽略属性目录");
                return;
            }

        直接用Name来判断的,这个以后会不会变,不知道,反正现在如此。或许有更科学的做法。

        另:这个代码之前输出了FileNames,这个有啥用还不太清楚。

4.3 根据项目类型调用处理方法

        后面就是根据项目类型调用处理方法,对C#,调用CSharp_Rename方法:

                        CSharp_Rename(5, projectItem.FileCodeModel.CodeElements);

        CodeElements是源代码构造的语言对象的集合,也就是我们要处理的东西。 

4.4 递归调用子项

        最后就是递归调用子项:

            foreach (ProjectItem current_project_item_item in projectItem.ProjectItems)
            {
                ProcessProjectItem(level + 1, current_project_item_item);
            }

五、CSharp_Rename方法

5.1 总代码

        终于到实际的功能了:

        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, "重命名完成");
                }
            }
        }

5.2 递归处理

            foreach (CodeElement _element in codeElements)
            {
                //......

                //处理子项
                if (0 != element.Children.Count)
                {
                    CSharp_Rename(level + 1, element.Children);
                }

                //......
            }

 5.3 解释一下element和_element

        代码里面有这句:

                CodeElement2 element = (CodeElement2)_element;

        这两个类型确实是不一样的,虽然觉得困惑,但是这样才能执行。

5.4 判断名称和类型

        对每个元素判断类型,element.Kind,属性和导入语句等不需要处理。

        特殊方法也不能处理,比如“Main”和“Dispose”,根据需要判断_element.Name(代码赋值给了name变量)即可。

5.5 重命名

        重命名使用element.RenameSymbol即可,C#实在是厉害多了。

element.RenameSymbol(new_name_title + count.ToString());

六、其实挺有趣的

       给C++用的AddFunction_myToString函数就不解释了,差不多,再说也没写得很理想。

(这里是结束)

你可能感兴趣的:(VSIX扩展开发,visual,studio,VSIX,批量rename)