如何在VS宏中检索标识符的完全限定名称?

时间:2022-09-07 20:25:00

I'm trying to resolve the fully qualified name of a c# identifier at a certain point (cursor) of a code window, using a Macro (or even an Add-in) in Visual Studio 2008.

我正在尝试使用Visual Studio 2008中的宏(甚至是加载项)在代码窗口的某个点(光标)处解析c#标识符的完全限定名称。

For example, if the cursor is in "Rectangle", I would like "System.Drawing.Rectangle" returned.

例如,如果光标在“Rectangle”中,我想返回“System.Drawing.Rectangle”。

I've tried FileCodeModel.CodeElements and .CodeElementFromPoint but they only retrieve the containing method or class (and others).

我尝试过FileCodeModel.CodeElements和.CodeElementFromPoint,但它们只检索包含方法或类(和其他)。

If this can't be done using a macro or add-in (even though VS does know the information via intellisense), would it be possible to use Reflection read in the c# file and get the desired info?

如果使用宏或加载项无法完成此操作(即使VS通过intellisense知道信息),是否可以在c#文件中使用Reflection读取并获取所需信息?

1 个解决方案

#1


It can be done. Here's one solution (albeit a somewhat hacky one): use F1 Help Context. In order to make F1 help work, Visual Studio pushes the fully-qualified type name of the current selection or insertion point into a bag of name/value pairs called "F1 Help Context". And there are public APIs in the Visual Studio SDK for querying the contents of F1 Help Context.

可以办到。这是一个解决方案(虽然有点笨拙):使用F1帮助上下文。为了使F1帮助工作,Visual Studio将当前选择或插入点的完全限定类型名称推送到名为“F1帮助上下文”的名称/值对的包中。 Visual Studio SDK中有公共API,用于查询F1帮助上下文的内容。

In order to stay sane, you'll want to enable the debugging registry key for F1 Help Context. This lets you see what's in Help Context at any time via the oft-maligned Dynamic Help window. To do this:

为了保持理智,您需要为F1帮助上下文启用调试注册表项。这使您可以通过常用的动态帮助窗口随时查看帮助上下文中的内容。去做这个:

  1. start visual studio and choose Dynamic Help from the Help menu.
  2. 启动visual studio并从“帮助”菜单中选择“动态帮助”。

  3. set the registry key below (you need step #1 to create the registry tree)
  4. 在下面设置注册表项(您需要步骤#1来创建注册表树)

  5. restart Visual Studio to pick up the changes
  6. 重新启动Visual Studio以获取更改

  7. Now, in the dynamic help window there will be debug output so you can see what's in F1 help context. The rest of this answer describes how to get at that context programmatically so your add-in can use it.
  8. 现在,在动态帮助窗口中将有调试输出,以便您可以看到F1帮助上下文中的内容。本答案的其余部分描述了如何以编程方式获取该上下文,以便您的加载项可以使用它。

Here's the registry key:

这是注册表项:

[HKEY_CURRENT_USER\Software\Microsoft\VisualStudio\9.0\Dynamic Help]
"Display Debug Output in Retail"="YES"

As you'll see from looking at the F1 debug output, Visual Studio doesn't explicitly tell you "this is the identifier's type". Instead, it simply sticks the fully-qualified type name at the head of one or more "Help Keywords" which F1 uses to bring up help. For example, you can have System.String, VS.TextEditor, and VS.Ambient in your help context, and only the first one is related to the current code.

正如您在查看F1调试输出时所看到的,Visual Studio没有明确告诉您“这是标识符的类型”。相反,它只是将完全限定的类型名称添加到一个或多个“帮助关键字”的头部,F1用于提供帮助。例如,您可以在帮助上下文中包含System.String,VS.TextEditor和VS.Ambient,并且只有第一个与当前代码相关。

The trick to make this easier is this: Visual Studio can mark keywords as case-sensitive or case-insensitive. AFAIK, the only part of Visual Studio which injects case-sensitive keywords is the code editor of case-sensitive languages (C#, C++) in response to code context. Therefore, if you filter all keywords to case-sensitive keywords, you know you're looking at code.

使这更容易的技巧是:Visual Studio可以将关键字标记为区分大小写或不区分大小写。 AFAIK是Visual Studio中注入区分大小写关键字的唯一部分,它是区分大小写的语言(C#,C ++)的代码编辑器,用于响应代码上下文。因此,如果您将所有关键字过滤为区分大小写的关键字,则表示您正在查看代码。

Unfortunately, the C# editor also pushes language keywords (not just identifiers) into help context if the insertion point is on top of a language keyword. So you'll need to screen out language keywords. There are two ways to do this. You can simply try to look them up in the type system, and since they're not valid type names (especially not the way VS mangles them, e.g. "string_CSharpKeyword" for the string keyword) you can just fail silently. Or you can detect the lack of dots and assume it's not a type name. Or you can detect the _CSharpKeyword suffix and hope the VS team doesn't change it. :-)

不幸的是,如果插入点位于语言关键字之上,C#编辑器还会将语言关键字(不仅仅是标识符)推送到帮助上下文中。因此,您需要筛选语言关键字。有两种方法可以做到这一点。您可以简单地尝试在类型系统中查找它们,并且因为它们不是有效的类型名称(特别是VS不会破坏它们的方式,例如字符串关键字的“string_CSharpKeyword”),您可以无声地失败。或者您可以检测到缺少点并假设它不是类型名称。或者您可以检测_CSharpKeyword后缀,并希望VS团队不会更改它。 :-)

Another potential issue is generics. The type name you'll get from VS for a generic type looks like this:

另一个潜在的问题是泛型。您从VS获取泛型类型的类型名称如下所示:

System.Collections.Generic.List`1 

and methods look like this:

和方法看起来像这样:

System.Collections.Generic.List`1.FindAll.

You'll need to be smart about detecting the back-tick and dealing with it.

你需要聪明地检测后退并处理它。

Also, you might get interesting behavior in cases like ASP.NET MVC .ASPX files where there's both C# code and other case-sensitive code (e.g. javascript) on the page. In that case, you'll need to look at the attributes as well. In addition to keywords, Help Context also has "attributes", which are name/value pairs describing the current context. For example, devlang=csharp is one attribute. The code below can be used to pull out attributes too. You'll need to experiment to figure out the right attributes to look for so you don't end up acting on javascript or other odd code.

此外,在ASP.NET MVC .ASPX文件的情况下,您可能会遇到有趣的行为,其中包含C#代码和页面上的其他区分大小写的代码(例如javascript)。在这种情况下,您还需要查看属性。除了关键字,“帮助上下文”还具有“属性”,这些属性是描述当前上下文的名称/值对。例如,devlang = csharp是一个属性。下面的代码也可用于提取属性。您需要尝试找出要查找的正确属性,这样您就不会最终使用javascript或其他奇怪的代码。

Anyway, now that you understand (or at least have been exposed to!) all the caveats, here's some code to pull out the case-sensitive keyword (if it exists) from help context, as well as the rest of the name/value pairs. (keywords are simply name/value pairs whose name is "keyword").

无论如何,既然你理解了(或者至少已经暴露过!)所有的警告,这里有一些代码可以从帮助上下文中提取区分大小写的关键字(如果存在),以及名称/值的其余部分对。 (关键字只是名称为“关键字”的名称/值对)。

Keep in mind that this code requires the Visual Studio SDK (not just the regular VS install) in order to build, in order to get the Microsoft.VisualStudio.Shell.Interop, Microsoft.VisualStudio.Shell, and Microsoft.VisualStudio.OLE.Interop namespaces (which you'll need to add as references in your addin project).

请记住,为了获得Microsoft.VisualStudio.Shell.Interop,Microsoft.VisualStudio.Shell和Microsoft.VisualStudio.OLE,此代码需要Visual Studio SDK(而不仅仅是常规的VS安装)才能构建。 Interop命名空间(您需要在addin项目中添加为引用)。

OK, have fun and good luck!

好的,玩得开心,祝你好运!

using System;
using Extensibility;
using EnvDTE;
using EnvDTE80;
using Microsoft.VisualStudio.CommandBars;
using System.Resources;
using System.Reflection;
using System.Globalization;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.OLE.Interop;
using System.Collections.Generic;

public class HelpAttribute
{
    public string Name;
    public string Value;
    public VSUSERCONTEXTPRIORITY Priority;
    public VSUSERCONTEXTATTRIBUTEUSAGE Usage;
}

public class HelpContext2 : List<HelpAttribute>
{
    public static HelpContext2 GetHelpContext(DTE2 dte)
    {
        // Get a reference to the current active window (presumably a code editor).
        Window activeWindow = dte.ActiveWindow;

        // make a few gnarly COM-interop calls in order to get Help Context 
        Microsoft.VisualStudio.OLE.Interop.IServiceProvider sp = (Microsoft.VisualStudio.OLE.Interop.IServiceProvider)activeWindow.DTE;
        Microsoft.VisualStudio.Shell.ServiceProvider serviceProvider = new Microsoft.VisualStudio.Shell.ServiceProvider(sp);
        IVsMonitorUserContext contextMonitor = (IVsMonitorUserContext)serviceProvider.GetService(typeof(IVsMonitorUserContext));
        IVsUserContext userContext;
        int hresult = contextMonitor.get_ApplicationContext(out userContext);
        HelpContext2 attrs = new HelpContext2(userContext);

        return attrs;
    }
    public HelpContext2(IVsUserContext userContext)
    {
        int count;
        userContext.CountAttributes(null, 1, out count);
        for (int i = 0; i < count; i++)
        {
            string name, value;
            int priority;
            userContext.GetAttributePri(i, null, 1, out priority, out name, out value);
            VSUSERCONTEXTATTRIBUTEUSAGE[] usageArray = new VSUSERCONTEXTATTRIBUTEUSAGE[1];
            userContext.GetAttrUsage(i, 1, usageArray);
            VSUSERCONTEXTATTRIBUTEUSAGE usage = usageArray[0];
            HelpAttribute attr = new HelpAttribute();
            attr.Name = name;
            attr.Value = value;
            attr.Priority = (VSUSERCONTEXTPRIORITY)priority;
            attr.Usage = usage; // name == "keyword" ? VSUSERCONTEXTATTRIBUTEUSAGE.VSUC_Usage_Lookup : VSUSERCONTEXTATTRIBUTEUSAGE.VSUC_Usage_Filter;
            this.Add(attr);
        }
    }
    public string CaseSensitiveKeyword
    {
        get
        {
            HelpAttribute caseSensitive = Keywords.Find(attr => 
                attr.Usage == VSUSERCONTEXTATTRIBUTEUSAGE.VSUC_Usage_LookupF1_CaseSensitive
                || attr.Usage == VSUSERCONTEXTATTRIBUTEUSAGE.VSUC_Usage_Lookup_CaseSensitive
                );
            return caseSensitive == null ? null : caseSensitive.Value;
        }
    }
    public List<HelpAttribute> Keywords
    {
        get
        {
            return this.FindAll(attr=> attr.Name == "keyword");
        }
    }
}

#1


It can be done. Here's one solution (albeit a somewhat hacky one): use F1 Help Context. In order to make F1 help work, Visual Studio pushes the fully-qualified type name of the current selection or insertion point into a bag of name/value pairs called "F1 Help Context". And there are public APIs in the Visual Studio SDK for querying the contents of F1 Help Context.

可以办到。这是一个解决方案(虽然有点笨拙):使用F1帮助上下文。为了使F1帮助工作,Visual Studio将当前选择或插入点的完全限定类型名称推送到名为“F1帮助上下文”的名称/值对的包中。 Visual Studio SDK中有公共API,用于查询F1帮助上下文的内容。

In order to stay sane, you'll want to enable the debugging registry key for F1 Help Context. This lets you see what's in Help Context at any time via the oft-maligned Dynamic Help window. To do this:

为了保持理智,您需要为F1帮助上下文启用调试注册表项。这使您可以通过常用的动态帮助窗口随时查看帮助上下文中的内容。去做这个:

  1. start visual studio and choose Dynamic Help from the Help menu.
  2. 启动visual studio并从“帮助”菜单中选择“动态帮助”。

  3. set the registry key below (you need step #1 to create the registry tree)
  4. 在下面设置注册表项(您需要步骤#1来创建注册表树)

  5. restart Visual Studio to pick up the changes
  6. 重新启动Visual Studio以获取更改

  7. Now, in the dynamic help window there will be debug output so you can see what's in F1 help context. The rest of this answer describes how to get at that context programmatically so your add-in can use it.
  8. 现在,在动态帮助窗口中将有调试输出,以便您可以看到F1帮助上下文中的内容。本答案的其余部分描述了如何以编程方式获取该上下文,以便您的加载项可以使用它。

Here's the registry key:

这是注册表项:

[HKEY_CURRENT_USER\Software\Microsoft\VisualStudio\9.0\Dynamic Help]
"Display Debug Output in Retail"="YES"

As you'll see from looking at the F1 debug output, Visual Studio doesn't explicitly tell you "this is the identifier's type". Instead, it simply sticks the fully-qualified type name at the head of one or more "Help Keywords" which F1 uses to bring up help. For example, you can have System.String, VS.TextEditor, and VS.Ambient in your help context, and only the first one is related to the current code.

正如您在查看F1调试输出时所看到的,Visual Studio没有明确告诉您“这是标识符的类型”。相反,它只是将完全限定的类型名称添加到一个或多个“帮助关键字”的头部,F1用于提供帮助。例如,您可以在帮助上下文中包含System.String,VS.TextEditor和VS.Ambient,并且只有第一个与当前代码相关。

The trick to make this easier is this: Visual Studio can mark keywords as case-sensitive or case-insensitive. AFAIK, the only part of Visual Studio which injects case-sensitive keywords is the code editor of case-sensitive languages (C#, C++) in response to code context. Therefore, if you filter all keywords to case-sensitive keywords, you know you're looking at code.

使这更容易的技巧是:Visual Studio可以将关键字标记为区分大小写或不区分大小写。 AFAIK是Visual Studio中注入区分大小写关键字的唯一部分,它是区分大小写的语言(C#,C ++)的代码编辑器,用于响应代码上下文。因此,如果您将所有关键字过滤为区分大小写的关键字,则表示您正在查看代码。

Unfortunately, the C# editor also pushes language keywords (not just identifiers) into help context if the insertion point is on top of a language keyword. So you'll need to screen out language keywords. There are two ways to do this. You can simply try to look them up in the type system, and since they're not valid type names (especially not the way VS mangles them, e.g. "string_CSharpKeyword" for the string keyword) you can just fail silently. Or you can detect the lack of dots and assume it's not a type name. Or you can detect the _CSharpKeyword suffix and hope the VS team doesn't change it. :-)

不幸的是,如果插入点位于语言关键字之上,C#编辑器还会将语言关键字(不仅仅是标识符)推送到帮助上下文中。因此,您需要筛选语言关键字。有两种方法可以做到这一点。您可以简单地尝试在类型系统中查找它们,并且因为它们不是有效的类型名称(特别是VS不会破坏它们的方式,例如字符串关键字的“string_CSharpKeyword”),您可以无声地失败。或者您可以检测到缺少点并假设它不是类型名称。或者您可以检测_CSharpKeyword后缀,并希望VS团队不会更改它。 :-)

Another potential issue is generics. The type name you'll get from VS for a generic type looks like this:

另一个潜在的问题是泛型。您从VS获取泛型类型的类型名称如下所示:

System.Collections.Generic.List`1 

and methods look like this:

和方法看起来像这样:

System.Collections.Generic.List`1.FindAll.

You'll need to be smart about detecting the back-tick and dealing with it.

你需要聪明地检测后退并处理它。

Also, you might get interesting behavior in cases like ASP.NET MVC .ASPX files where there's both C# code and other case-sensitive code (e.g. javascript) on the page. In that case, you'll need to look at the attributes as well. In addition to keywords, Help Context also has "attributes", which are name/value pairs describing the current context. For example, devlang=csharp is one attribute. The code below can be used to pull out attributes too. You'll need to experiment to figure out the right attributes to look for so you don't end up acting on javascript or other odd code.

此外,在ASP.NET MVC .ASPX文件的情况下,您可能会遇到有趣的行为,其中包含C#代码和页面上的其他区分大小写的代码(例如javascript)。在这种情况下,您还需要查看属性。除了关键字,“帮助上下文”还具有“属性”,这些属性是描述当前上下文的名称/值对。例如,devlang = csharp是一个属性。下面的代码也可用于提取属性。您需要尝试找出要查找的正确属性,这样您就不会最终使用javascript或其他奇怪的代码。

Anyway, now that you understand (or at least have been exposed to!) all the caveats, here's some code to pull out the case-sensitive keyword (if it exists) from help context, as well as the rest of the name/value pairs. (keywords are simply name/value pairs whose name is "keyword").

无论如何,既然你理解了(或者至少已经暴露过!)所有的警告,这里有一些代码可以从帮助上下文中提取区分大小写的关键字(如果存在),以及名称/值的其余部分对。 (关键字只是名称为“关键字”的名称/值对)。

Keep in mind that this code requires the Visual Studio SDK (not just the regular VS install) in order to build, in order to get the Microsoft.VisualStudio.Shell.Interop, Microsoft.VisualStudio.Shell, and Microsoft.VisualStudio.OLE.Interop namespaces (which you'll need to add as references in your addin project).

请记住,为了获得Microsoft.VisualStudio.Shell.Interop,Microsoft.VisualStudio.Shell和Microsoft.VisualStudio.OLE,此代码需要Visual Studio SDK(而不仅仅是常规的VS安装)才能构建。 Interop命名空间(您需要在addin项目中添加为引用)。

OK, have fun and good luck!

好的,玩得开心,祝你好运!

using System;
using Extensibility;
using EnvDTE;
using EnvDTE80;
using Microsoft.VisualStudio.CommandBars;
using System.Resources;
using System.Reflection;
using System.Globalization;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.OLE.Interop;
using System.Collections.Generic;

public class HelpAttribute
{
    public string Name;
    public string Value;
    public VSUSERCONTEXTPRIORITY Priority;
    public VSUSERCONTEXTATTRIBUTEUSAGE Usage;
}

public class HelpContext2 : List<HelpAttribute>
{
    public static HelpContext2 GetHelpContext(DTE2 dte)
    {
        // Get a reference to the current active window (presumably a code editor).
        Window activeWindow = dte.ActiveWindow;

        // make a few gnarly COM-interop calls in order to get Help Context 
        Microsoft.VisualStudio.OLE.Interop.IServiceProvider sp = (Microsoft.VisualStudio.OLE.Interop.IServiceProvider)activeWindow.DTE;
        Microsoft.VisualStudio.Shell.ServiceProvider serviceProvider = new Microsoft.VisualStudio.Shell.ServiceProvider(sp);
        IVsMonitorUserContext contextMonitor = (IVsMonitorUserContext)serviceProvider.GetService(typeof(IVsMonitorUserContext));
        IVsUserContext userContext;
        int hresult = contextMonitor.get_ApplicationContext(out userContext);
        HelpContext2 attrs = new HelpContext2(userContext);

        return attrs;
    }
    public HelpContext2(IVsUserContext userContext)
    {
        int count;
        userContext.CountAttributes(null, 1, out count);
        for (int i = 0; i < count; i++)
        {
            string name, value;
            int priority;
            userContext.GetAttributePri(i, null, 1, out priority, out name, out value);
            VSUSERCONTEXTATTRIBUTEUSAGE[] usageArray = new VSUSERCONTEXTATTRIBUTEUSAGE[1];
            userContext.GetAttrUsage(i, 1, usageArray);
            VSUSERCONTEXTATTRIBUTEUSAGE usage = usageArray[0];
            HelpAttribute attr = new HelpAttribute();
            attr.Name = name;
            attr.Value = value;
            attr.Priority = (VSUSERCONTEXTPRIORITY)priority;
            attr.Usage = usage; // name == "keyword" ? VSUSERCONTEXTATTRIBUTEUSAGE.VSUC_Usage_Lookup : VSUSERCONTEXTATTRIBUTEUSAGE.VSUC_Usage_Filter;
            this.Add(attr);
        }
    }
    public string CaseSensitiveKeyword
    {
        get
        {
            HelpAttribute caseSensitive = Keywords.Find(attr => 
                attr.Usage == VSUSERCONTEXTATTRIBUTEUSAGE.VSUC_Usage_LookupF1_CaseSensitive
                || attr.Usage == VSUSERCONTEXTATTRIBUTEUSAGE.VSUC_Usage_Lookup_CaseSensitive
                );
            return caseSensitive == null ? null : caseSensitive.Value;
        }
    }
    public List<HelpAttribute> Keywords
    {
        get
        {
            return this.FindAll(attr=> attr.Name == "keyword");
        }
    }
}