【问题标题】:How would I retrieve the fully qualified name of an identifier in a VS macro?如何在 VS 宏中检索标识符的完全限定名称?
【发布时间】:2010-11-12 23:06:30
【问题描述】:

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

例如,如果光标在“矩形”中,我希望返回“System.Drawing.Rectangle”。

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

如果使用宏或加载项无法做到这一点(即使 VS 确实通过智能感知知道信息),是否可以在 c# 文件中使用反射读取并获取所需的信息?

【问题讨论】:

    标签: c# visual-studio-2008 macros intellisense visual-studio-addins


    【解决方案1】:

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

    为了保持清醒,您需要为 F1 帮助上下文启用调试注册表项。这使您可以随时通过经常受到诟病的动态帮助窗口查看帮助上下文中的内容。为此:

    1. 启动 Visual Studio 并从“帮助”菜单中选择“动态帮助”。
    2. 在下面设置注册表项(您需要第 1 步来创建注册表树)
    3. 重新启动 Visual Studio 以获取更改
    4. 现在,在动态帮助窗口中将会有调试输出,因此您可以查看 F1 帮助上下文中的内容。此答案的其余部分描述了如何以编程方式获取该上下文,以便您的加载项可以使用它。

    这是注册表项:

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

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

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

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

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

    System.Collections.Generic.List`1 
    

    方法如下所示:

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

    您需要聪明地检测并处理反引号。

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

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

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

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

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

    【讨论】:

    • 太棒了!乐意效劳。幸运的是,我在之前的一些调查中发现了这段代码......第一次弄清楚如何做到这一点有点困难。 :-)
    • 你正式成为我的个人英雄。我在 CodeElement 的荒地上徘徊,试图确定如何导出帮助字符串。你让我回到了正确的轨道上。再次感谢!
    • 这种技术只有一个问题。如果动态帮助窗口自 Visual Studio 窗口出现后至少激活一次,它只会更新通过 IVsUserContext 提供的属性。有谁知道在不激活动态帮助窗口的情况下让 IVsUserContext 开始提供属性的方法?
    • 我找到了一种通过 IVsMonitorUserContext 让它工作的方法。等我弄好源代码再贴出来。
    • 酷!我认为有一个不那么 hacky 的解决方案可用。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-05-17
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多