An ADR Visual Studio Extension – Listing and Reading the files
Tags: visualstudio, csharp, extensions
Published: 2020-01-08 17:23:48

In <a href="https://www.pmichaels.net/2019/12/26/an-adr-visual-studio-tool-part-2-refactoring/">this post</a>, I refactored a VS Extension Plug-in, that I originally started <a href="https://www.pmichaels.net/2019/09/07/an-adr-visual-studio-tool-part-1-creating-a-visual-studio-extension-to-scrape-the-solution-and-list-all-items-including-solution-items-and-solution-folders/">here</a>.

We'll get the plug-in to list the items found in the projects, and read the contents of the files.  The source code for this can be found <a href="https://github.com/pcmichaels/ADRManager">here</a>.  I won't be listing all the source code in this article (most of it is just simple WPF and View Model binding).

To look through all the projects and folders in the solution, we'll need to recursively scan all the files, and then read them; let's have a look at what such a method might look like:

```csharp
        private async Task ScanProjectItems(ProjectItems projectItems, ProjectData projectData)
        {
            await Microsoft.VisualStudio.Shell.ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();

            foreach (EnvDTE.ProjectItem pi in projectItems)
            {
                if (pi.IsKind(ProjectItemTypes.SOLUTION_FOLDER, 
                              ProjectItemTypes.PROJECT_FOLDER,
                              ProjectItemTypes.SOLUTION_ITEM)
                    && pi.ProjectItems != null)
                {                    
                    await ScanProjectItems(pi.ProjectItems, projectData);
                    return;
                }

                string text = await GetDocumentText(pi);
                if (string.IsNullOrWhiteSpace(text)) continue;

                projectData.Items.Add(new Models.ProjectItem()
                {
                    Name = pi.Name,
                    Data = text
                });
            }
        }
```

I wanted to look specifically into two aspects of this method: IsKind() and GetDocumentText().  None of the rest of this is particularly exciting.

<h1>Kind of File</h1>

In a VS Extension, you can read ProjectItems - they represent pretty much anything in the solution, and so it's necessary to be able to find out exactly what the type is.  As you can see above, I have an extension method, which was taken from <a href="https://github.com/madskristensen/MarkdownEditor/">here</a>.  Let's have a quick look at the file that defines the ProjectItemTypes:

```csharp
    public static class ProjectItemTypes
    {
        public const string MISC = "{66A2671D-8FB5-11D2-AA7E-00C04F688DDE}";
        public const string SOLUTION_FOLDER = "{66A26720-8FB5-11D2-AA7E-00C04F688DDE}";
        public const string SOLUTION_ITEM = "{66A26722-8FB5-11D2-AA7E-00C04F688DDE}";                                            
        public const string PROJECT_FOLDER = "{6BB5F8EF-4483-11D3-8BCF-00C04F8EC28C}";        
    }
```

I'm sure there's a better way, but after I realised what Mads was doing in the above linked project, I just stuck a breakpoint in the code, and copied the "Kind" guid from there!  The IsKind method is taken from the same codebase:

```csharp
        public static bool IsKind(this ProjectItem projectItem, params string[] kindGuids)
        {
            Microsoft.VisualStudio.Shell.ThreadHelper.ThrowIfNotOnUIThread();

            foreach (var guid in kindGuids)
            {
                if (projectItem.Kind.Equals(guid, StringComparison.OrdinalIgnoreCase))
                    return true;
            }

            return false;
        }
```

As you can see, it's almost not worth mentioning - except that the extensions are very particular about running in the UI thread, so you'll find ThrowIfNotOnUIThread scattered around your code like confetti!

<h1>Reading File Contents</h1>

If you need to access the file contents in an extension, one way is to convert the project item document to a TextDocument, and then use Edit Points:

```csharp
        public static async Task<string> GetDocumentText(this ProjectItem projectItem)
        {
            if (projectItem == null) return string.Empty;
            await Microsoft.VisualStudio.Shell.ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();

            try
            {
                TextDocument textDocument;
                if (!projectItem.IsOpen)
                {
                    var doc = projectItem.Open(EnvDTE.Constants.vsViewKindCode);
                    textDocument = (TextDocument)doc.Document.Object("TextDocument");
                }
                else
                {
                    textDocument = (TextDocument)projectItem.Document.Object("TextDocument");
                }
                EditPoint editPoint = textDocument.StartPoint.CreateEditPoint();
                return editPoint.GetText(textDocument.EndPoint);
            }
            catch (Exception)
            {
                return string.Empty;
            }
        }
```

Edit Point are much more powerful that this, they allow you to change the text in a document; for example, imagine your extension needed to change every local pascal cased variable into one with an underscore (myVariable to _myVariable), you may choose to use edit points there.


<h1>References</h1>

<a href="https://www.csharpcodi.com/csharp-examples/EnvDTE.Document.Object(string)/">https://www.csharpcodi.com/csharp-examples/EnvDTE.Document.Object(string)/</a>

<a href="https://github.com/madskristensen/MarkdownEditor/">https://github.com/madskristensen/MarkdownEditor/</a>