Monday, August 31, 2015

HOWTO: Get the conditional compile symbols defined in the project for a source file (ITextView) in a Visual Studio Extension

One of the things I ran into when parsing with Roslyn in Stay Frosty was that #if / #endif blocks were treated as strictly in the parser as they are in Visual Studio (well, that should be obvious, it's the parser used by VS, isn't it?). That meant code that was riddled with preprocessor rules wasn't being parsed properly because it didn't know what I had defined.
Fortunately the Visual Studio SDK provides a way at these values. Unfortunately, it's not as straight forward as I would have liked.
Conditionals given an ITextView
Here's a quick extension method I whipped up to pull the "defines" out of the project file given an ITextView/IWpfTextView.
private static IEnumerable<string> Conditionals([NotNull] this ITextView textView)
        {
            if (textView == null) throw new ArgumentNullException(nameof(textView));

            // Get the text document from the text buffer (if it has one)
            ITextDocument textDocument;
            if (textView.TextBuffer.Properties.TryGetProperty(typeof (ITextDocument), out textDocument))
                yield break;

            var componentModel = ServiceProvider.GlobalProvider.GetService(typeof(SComponentModel)) as IComponentModel;

            // Get the VsServiceProvider
            var vsServiceProvider = componentModel?.DefaultExportProvider?.GetExportedValue<SVsServiceProvider>();
            if (vsServiceProvider == null) yield break;

            // Get the DTE ...
            var dte = (DTE) vsServiceProvider.GetService(typeof (DTE));

            ProjectItem projectItem = dte.Solution.FindProjectItem(textDocument.FilePath);
            Configuration activeConfiguration = projectItem?.ContainingProject?.ConfigurationManager?.ActiveConfiguration;

            var defineConstants = activeConfiguration?.Properties?.Item("DefineConstants")?.Value as string;

            if (defineConstants == null)
                yield break;
            
            // DefineConstants entries are listed semicolon and comma delimiated, so we need to split on both.
            string[] constantSplit = defineConstants.Split(new[] { ';', ',' }, StringSplitOptions.RemoveEmptyEntries);

            foreach (var item in constantSplit)
                yield return item.Trim(); // They can contain whitespace on either end, so we'll strip 'em.
            
        }
Of course, you could get some of those services via an Export and eliminate the GetService / GetExported value calls, but I thought I'd include them since I don't know what you've decided to get from MEF.
Have a lot of fun!

No comments: