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

初级代码游戏的专栏介绍与文章目录-CSDN博客

本文承接自VSIX:C#项目 重命名所有标识符(Visual Studio扩展开发)-CSDN博客,对VS扩展开发完全一无所知的可以先看这个。

代码其实很简单,几个辅助函数,一个主流程而已。主要的困难在于需要了解VSIX的开发模型,如果不专门做这个,很难了解透彻。我也没找到很好的学习材料,只能说,现在的代码确实是可以工作的,都是碎知识。

目录

一、成员变量和初始化

二、消息框和日志

三、主入口

[3.1 总代码](#3.1 总代码)

[3.2 获取顶级对象DTE2](#3.2 获取顶级对象DTE2)

[3.3 获取解决方案](#3.3 获取解决方案)

[3.4 获取所有项目](#3.4 获取所有项目)

[3.5 判断项目类型](#3.5 判断项目类型)

[3.6 遍历项目的每一个项](#3.6 遍历项目的每一个项)

四、ProcessProjectItem方法

[4.1 总代码](#4.1 总代码)

4.2 C#项目忽略属性目录

[4.3 根据项目类型调用处理方法](#4.3 根据项目类型调用处理方法)

[4.4 递归调用子项](#4.4 递归调用子项)

五、CSharp_Rename方法

[5.1 总代码](#5.1 总代码)

[5.2 递归处理](#5.2 递归处理)

[5.3 解释一下element和_element](#5.3 解释一下element和_element)

[5.4 判断名称和类型](#5.4 判断名称和类型)

[5.5 重命名](#5.5 重命名)

六、其实挺有趣的


一、成员变量和初始化

cs 复制代码
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一样的串来判断的。我觉得这样不是很科学,但是没找到别的办法。

其它几个变量就是自动重命名时用来生成随机名称的。

二、消息框和日志

cs 复制代码
        //显示消息对话框
        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 总代码

cs 复制代码
        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模型的顶级对象,一切从获取顶级对象开始:

cs 复制代码
DTE2 dte = (DTE2)Package.GetGlobalService(typeof(SDTE));

3.3 获取解决方案

然后可以获取解决方案:

cs 复制代码
 var solution = dte.Solution;

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

3.4 获取所有项目

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

cs 复制代码
                foreach (Project current_project in solution.Projects)

3.5 判断项目类型

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

cs 复制代码
                    //解决方案下的项目
                    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里项目下的那些东西,属性、资源等等都是,所以后面处理要逐个根据类型来判断。

cs 复制代码
                    foreach (ProjectItem current_project_item in current_project.ProjectItems)
                    {
                        ProcessProjectItem(2, current_project_item);
                    }

对每个项执行ProcessProjectItem方法。

四、ProcessProjectItem方法

4.1 总代码

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

cpp 复制代码
        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#项目忽略属性目录

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

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

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

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

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

cs 复制代码
                        CSharp_Rename(5, projectItem.FileCodeModel.CodeElements);

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

4.4 递归调用子项

最后就是递归调用子项:

cs 复制代码
            foreach (ProjectItem current_project_item_item in projectItem.ProjectItems)
            {
                ProcessProjectItem(level + 1, current_project_item_item);
            }

五、CSharp_Rename方法

5.1 总代码

终于到实际的功能了:

cs 复制代码
        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 递归处理

cs 复制代码
            foreach (CodeElement _element in codeElements)
            {
                //......

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

                //......
            }

5.3 解释一下element和_element

代码里面有这句:

cs 复制代码
                CodeElement2 element = (CodeElement2)_element;

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

5.4 判断名称和类型

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

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

5.5 重命名

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

cs 复制代码
element.RenameSymbol(new_name_title + count.ToString());

六、其实挺有趣的

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

(这里是结束)

相关推荐
我不是懒洋洋21 小时前
【C++】string(string的成员变量、auto和范围for、string常用接口的说明、OJ题目、string的模拟实现)
c语言·开发语言·c++·visual studio
C++ 老炮儿的技术栈1 天前
Ubuntu root账号自动登陆
linux·运维·服务器·c语言·c++·ubuntu·visual studio
A.零点2 天前
【2个月 C 语言从入门到精通:零基础系统教程】第十二讲:深入了解指针(五)
c语言·开发语言·网络·笔记·visual studio
彷徨而立2 天前
【VS2026】介绍 Visual Studio 几个重要配置项
visual studio
AndyHuang19762 天前
【避坑指南】Visual Studio 插件报错 “Windows Terminal (wt.exe) was not found in PATH“ 完美解决
ide·windows·visual studio
彷徨而立2 天前
【Visual Studio】msbuild 使用举例
ide·visual studio
blueman88883 天前
VS2022 切换定义(F12 / Go to Definition)反应慢
c++·visual studio
周杰伦fans3 天前
记一次 Visual Studio 突然报错“未能加载 Microsoft.Internal.VisualStudio.Interop”的奇葩经历
microsoft·log4j·visual studio
x138702859573 天前
c语言中srtlen(指针使用计算字符长度)、传值和传址调用
c语言·开发语言·算法·visual studio
robot_???5 天前
Visual studio2022:找不到指定的SDK“Microsoft.NET.Sdk”
microsoft·.net·visual studio